Path::addRoundedQuadrilateral! (tadah now with better ASCII Graphics)


#1

Hey there JUCE team. I developed a version of Path::addRoundedRectangle() (the version that lets you choose which corners are rounded) that combines itself with Path::addQuadrilateral()

I present to you: addRoundedQuadrilateral!!

usage, as shown in the picture:

    g.fillAll(Colours::white);
    g.setColour( Colours::black );

    Path p = addRoundedQuadrilateral(Point<float>(10,10),
                                     Point<float>(187,29),
                                     Point<float>(50,96),
                                     Point<float>(62,225),
                                     29, 47, 52, 67,
                                     false, true, true, true);

    g.strokePath(p, PathStrokeType(2));

for my use case, I just made it return a Path object. the method doesn’t operate on a path member of the path class. Easy enough to modify it to do that. I just didn’t want to bother modifying the Path class.
Here’s the code:

Path MainContentComponent::addRoundedQuadrilateral (const Point<float> p1,
                                                    const Point<float> p2,
                                                    const Point<float> p3,
                                                    const Point<float> p4,
                                                    float cs1, float cs2, float cs3, float cs4,
                                                    const bool curveTopLeft, const bool curveTopRight,
                                                    const bool curveBottomRight, const bool curveBottomLeft )
{
    Path p;

    Point<float> pA, pB, pC, pD;
    if (curveTopLeft)
    {
        /*
         p1 pC pD                p2
         O--o--o-----------o--o--O
         |                       |
         o pB                    o
         |                       |
         o pA                    o
         |                       |
         |                       |
         |                       |
         |                       |
         |                       |
         o                       o
         |                       |
         o                       o
         |                       |
      p4 O--o--o-----------o--o--O p3
         */
        roundedQuadHelper(p4, p1, p2, cs1, pA, pB, pC, pD); //calculate pA,pB,pC,pD
        p.startNewSubPath( pA );
        p.cubicTo(pB, pC, pD);
    }
    else
    {
        p.startNewSubPath(p1);
    }
    
    if (curveTopRight)
    {
        /*
         p1                pA pB  p2
         O--o--o-----------o--o--O
         |                       |
         o                       o pC
         |                       |
         o                       o pD
         |                       |
         |                       |
         |                       |
         |                       |
         |                       |
         o                       o
         |                       |
         o                       o
         |                       |
      p4 O--o--o-----------o--o--O p3
         */
        roundedQuadHelper(p1, p2, p3, cs2, pA, pB, pC, pD);
        p.lineTo(pA);
        p.cubicTo(pB, pC, pD);
    }
    else
    {
        p.lineTo(p2);
    }

    if (curveBottomRight)
    {
    /*
     p1                      p2
     O--o--o-----------o--o--O
     |                       |
     o                       o
     |                       |
     o                       o
     |                       |
     |                       |
     |                       |
     |                       |
     |                       |
     o                       o pA
     |                       |
     o                       o pB
     |                       |
  p4 O--o--o-----------o--o--O p3
                       pD pC
     */
        roundedQuadHelper(p2, p3, p4, cs3, pA, pB, pC, pD);
        p.lineTo(pA);
        p.cubicTo(pB, pC, pD);
    }
    else
    {
        p.lineTo(p3);
    }

    if (curveBottomLeft)
    {
        /*
         p1                      p2
         O--o--o-----------o--o--O
         |                       |
         o                       o
         |                       |
         o                       o
         |                       |
         |                       |
         |                       |
         |                       |
         |                       |
         o pD                    o
         |                       |
         o pC                    o
         |                       |
      p4 O--o--o-----------o--o--O p3
            pB pA
         */
        roundedQuadHelper(p3, p4, p1, cs4, pA, pB, pC, pD);
        p.lineTo(pA);
        p.cubicTo(pB, pC, pD);
    }
    else
    {
        p.lineTo(p4);
    }

    p.closeSubPath();
    return p;
}
void MainContentComponent::roundedQuadHelper(const Point<float> &p1, const Point<float> &p2, const Point<float> &p3, float cs, Point<float> &pA, Point<float> &pB, Point<float> &pC, Point<float> &pD)
{
    /*
     p2                p3
     O--o--o-----------O
     |  pC pD
     o pB
     |
     o pA
     |
     |
     |
     |
     |
  p1 O

     pA is calculated as a point on the line starting at p2 going to p1 with a distance of cs from p2
     pB is calculated as a point on the line starting at p2 going to pA with a distance of cs45 from p2
     pD is calculated as a point on the line starting at p2 going to p3 with a distance of cs from p2
     pC is calculated as a point on the line starting at p2 going to pD with a distance of cs45 from p2
     
     the Path is defined as:
        path.startNewSubPath(pA);
        path.cubicTo(pB, pC, pD);
     
     https://math.stackexchange.com/questions/134112/find-a-point-on-a-line-segment-located-at-a-distance-d-from-one-endpoint
     */

    auto denomHelper = [](const Point<float> &start, const Point<float> &end)
    {
        return sqrtf((start.x - end.x)*(start.x-end.x) + (start.y-end.y)*(start.y-end.y) );
    };

    auto pointHelper = [denomHelper](const Point<float> &start, const Point<float> &end, float d)
    {
        Point<float> p;
        p.setX(start.x + (d * (end.x - start.x)) / denomHelper(start,end) );
        p.setY(start.y + (d * (end.y - start.y)) / denomHelper(start,end) );
        return p;
    };


    float cs_ = jmin(cs, Line<float>(p2, p1).getLength() * 0.5f);
    pA = pointHelper(p2, p1, cs_); //calculate pA
    pB = pointHelper(p2, pA, cs_ * 0.45f);
    cs_ = jmin(cs, Line<float>(p2, p3).getLength() * 0.5f);
    pD = pointHelper(p2, p3, cs_);
    pC = pointHelper(p2, pD, cs_ * 0.45f);
}

Maybe it could be added to the Path class? I had a need for it in my current project.

Anyway, hopefully someone finds it useful!


#2

edit: Figured out how to close the path properly by borrowing from the path construction algorithm used in Path::drawRoundedRectangle();