c++ – Edges on polygon outlines not always correct

What about:

  1. Draw each line up to the inside of the corner
  2. Draw an extra line at each corner perpendicular to the angle of the corner

Like this:

alt text http://www.geekops.co.uk/photos/0000-00-02%20%28Forum%20images%29/CorrectAngleDrawing.png

Blue / red represent the two lines you’re trying to connect. Dotted green is the extra one you add to smooth the corner. The image above shows the content would be clipped very slightly for sharp corners. If that’s a problem, you could extend the two connecting lines further outwards and draw the extra line further out.

[Edit] I’ve spotted a flaw in my suggestion. You have some concave sections in there which won’t work at all well. For those occasions you’ll want to do something like drawing a chamfered edge instead:

alt text http://www.geekops.co.uk/photos/0000-00-02%20%28Forum%20images%29/CorrectAngleDrawing2.png

[Edit2] I’ve done a little debugging on the code I posted previously. The following should be of more use:

    // PolygonOutlineGen.cpp : A small program to calculate 4-point polygons 
// to surround an input polygon.

#include <vector>
#include <math.h>
#include <iostream>
#include <iomanip>

using namespace std;

// Describe some structures etc. so the code will compile without 
// requiring the GL libraries.
typedef double GLdouble;
typedef float GLfloat;
typedef struct POINTFLOAT
{
    float x;
    float y;
} POINTFLOAT;

// A function to generate two coordinates representing the start and end
// of a line perpendicular to start/end, offset by 'width' units.
void GenerateOffsetLineCoords(
    POINTFLOAT start, 
    POINTFLOAT end, 
    int width,
    POINTFLOAT& perpStart,
    POINTFLOAT& perpEnd)
{
    float dirlen;
    POINTFLOAT dir;
    POINTFLOAT ndir;
    POINTFLOAT nperp;
    POINTFLOAT perpoffset;

    // Work out the offset for a parallel line which is space outwards by 'width' units
    dir.x = end.x - start.x;
    dir.y = end.y - start.y;
    dirlen = sqrt((dir.x * dir.x) + (dir.y * dir.y));
    ndir.x = static_cast<float>(dir.x * 1.0 / dirlen);
    ndir.y = static_cast<float>(dir.y * 1.0 / dirlen);
    nperp.x = -ndir.y;
    nperp.y = ndir.x;
    perpoffset.x = static_cast<float>(nperp.x * width);
    perpoffset.y = static_cast<float>(nperp.y * width);

    // Calculate the offset coordinates for the new line
    perpStart.x = start.x + perpoffset.x;
    perpStart.y = start.y + perpoffset.y;
    perpEnd.x = end.x + perpoffset.x;
    perpEnd.y = end.y + perpoffset.y;
}

// Function to generate quads of coordinate pairs to surround the 'input'
// polygon.
void GenerateLinePoly(const std::vector<std::vector<GLdouble>> &input,
    std::vector<GLfloat> &output, int width)
{
    // Make sure we have something to produce an outline for and that it's not contaminated with previous results
    output.clear();
    if(input.size() < 2)
    {
        return;
    }

    // Storage for the pairs of lines which form sections of the outline
    POINTFLOAT line1_start;
    POINTFLOAT line1_end;
    POINTFLOAT line2_start;
    POINTFLOAT line2_end;

    // Storage for the outer edges of the quads we'll be generating
    POINTFLOAT line1offset_start;
    POINTFLOAT line1offset_end;
    POINTFLOAT line2offset_start;
    POINTFLOAT line2offset_end;

    // Storage for the line we'll use to make smooth joints between polygon sections.
    POINTFLOAT joininglineoffset_start;
    POINTFLOAT joininglineoffset_end;

    for(unsigned int i = 0; i < input.size() - 2; ++i)
    {
        // Grab the raw line input for the first line or if we've already done one, just re-use the last results
        if( i == 0 )
        {
            line1_start.x = static_cast<float>(input[i][0]);
            line1_start.y = static_cast<float>(input[i][1]);
            line1_end.x = static_cast<float>(input[i + 1][0]);
            line1_end.y = static_cast<float>(input[i + 1][1]);

            GenerateOffsetLineCoords(line1_start, line1_end, width, line1offset_start, line1offset_end);
        }
        else
        {
            line1_start = line2_start;
            line1offset_start = line2offset_start;
            line1_end = line2_end;
            line1offset_end = line2offset_end;
        }

        // Grab the second line and work out the coords of it's offset 
        line2_start.x = static_cast<float>(input[i+1][0]);
        line2_start.y = static_cast<float>(input[i+1][1]);
        line2_end.x = static_cast<float>(input[i+2][0]);
        line2_end.y = static_cast<float>(input[i+2][1]);
        GenerateOffsetLineCoords(line2_start, line2_end, width, line2offset_start, line2offset_end);

        // Grab the offset for the line which joins the open end
        GenerateOffsetLineCoords(line2offset_start, line1offset_end, width, joininglineoffset_start, joininglineoffset_end);

        // Push line 1 onto the output
        output.push_back(line1_start.x);
        output.push_back(line1_start.y);
        output.push_back(line1_end.x);
        output.push_back(line1_end.y);
        output.push_back(line1offset_end.x);
        output.push_back(line1offset_end.y);
        output.push_back(line1offset_start.x);
        output.push_back(line1offset_start.y);

        // Push the new section onto the output
        output.push_back(line1offset_end.x);
        output.push_back(line1offset_end.y);
        output.push_back(line2offset_start.x);
        output.push_back(line2offset_start.y);
        output.push_back(joininglineoffset_start.x);
        output.push_back(joininglineoffset_start.y);
        output.push_back(joininglineoffset_end.x);
        output.push_back(joininglineoffset_end.y);
    }

    // TODO: Push the remaining line 2 on.

    // TODO: Add one last joining piece between the end and the beginning.
}

int main(int argc, char* argv[])
{
    // Describe some input data
    std::vector<std::vector<GLdouble>> input;
    std::vector<GLdouble> val1; val1.push_back(010.0); val1.push_back(010.0); input.push_back(val1);
    std::vector<GLdouble> val2; val2.push_back(050.0); val2.push_back(100.0); input.push_back(val2);
    std::vector<GLdouble> val3; val3.push_back(100.0); val3.push_back(100.0); input.push_back(val3);
    std::vector<GLdouble> val4; val4.push_back(010.0); val4.push_back(010.0); input.push_back(val4);

    // Generate the quads required to outline the shape
    std::vector<GLfloat> output;
    GenerateLinePoly(input, output, 5);

    // Dump the output as pairs of coordinates, grouped into the quads they describe
    cout << setiosflags(ios::fixed) << setprecision(1);
    for(unsigned int i=0; i < output.size(); i++)
    {
       if( (i > 0) && ((i)%2==0) ) { cout << endl; }
       if( (i > 0) && ((i)%8==0) ) { cout << endl; }
       cout << setw(7) << output[i];
    }
    cout << endl;
    return 0;
}

..which looks like it works for convex polygons as far as I can see 🙂

Leave a Comment