Scanline Rasterization

Looping trough every edge for every pixel is very inefficient. In this section we're going to optimize the DrawPolygon function by tightly coupling it with the code PointInPolygon function.

Since all we care about are the edges that cross the current scanline, an easy optimization is to keep an active list of edges and only loop trough those. As we're moving from scanline to scanline, we can check if an edge started or stopped crossing the scanline, and update the active edge list accordingly.

We will need to find where each active edge crosses the scanline, and sort the active edge list left to right based on this coordinate. This makes sure we count every edge as it's passed when rasterizing the scanline. Any time an edge is passed, update the winding number of the scanlie accordingly.

Implementation

We will need to know where each edge intersects the scanline. We're going to create a RenderEdge structure to help keep track of this data.

struct RenderEdge {
    Point p0;
    Point p1;
    float x;
};

Follow these steps to implement the algorithm described above. The sample code adds 0.5f to all pixel coordinates because it assumes the pixel coordinate represents the center of the pixel.

void DrawPolygon(int _x, int _y, const Polygon& poly, unsigned char r, unsigned char g, unsigned char b) {
    BoundingBox bbox = GetBoundingBox(poly);
    int s_x = bbox.min.x < 0.0f ? bbox.min.x - 0.5f : bbox.min.x + 0.5f;
    int e_x = bbox.max.x < 0.0f ? bbox.max.x - 0.5f : bbox.max.x + 0.5f;
    int s_y = bbox.min.y < 0.0f ? bbox.min.y - 0.5f : bbox.min.y + 0.5f;
    int e_y = bbox.max.y < 0.0f ? bbox.max.y - 0.5f : bbox.max.y + 0.5f;

    // Build a list of edges for the polygon
    std::vector< RenderEdge > edges;
    for (int c = 0, cSize = poly.size(); c < cSize; ++c) {
        const Contour& countour = poly[c];
        int numPoints = countour.size();
        for (int p = 0; p < numPoints; ++p) {
            const Point& point = countour[p];
            const Point& next = countour[(p + 1) % numPoints];
            edges.push_back(RenderEdge{ point, next, 0.0f });
        }
    }

    std::sort(edges.begin(), edges.end(), TopmostY());
    std::list< RenderEdge > active;

    unsigned int numEdges = edges.size();
    unsigned int firstEdgeBelowScanline = 0;

    for (int y = s_y; y <= e_y; ++y) {
        // Sample from center of pixel
        float sample_y = float(y) + 0.5f;

        // Add any edges that start above the scanline
        while (firstEdgeBelowScanline < numEdges) {
            const RenderEdge& edge = edges[firstEdgeBelowScanline];
            float min_y = edge.p0.y < edge.p1.y ? edge.p0.y : edge.p1.y;
            if (min_y <= sample_y) { // Inclusive of topmost point
                // firstEdgeBelowScanline was above the scanline
                active.push_back(edges[firstEdgeBelowScanline]);
                firstEdgeBelowScanline += 1;
            }
            else { // firstEdgeBelowScanline is below the scanline
                break;
            }
        }

        // Horizontal edges are not a problem, 
        // they are added above and removed immediateley below

        // Remove any active edges that end above the scanline
        for (std::list< RenderEdge >::iterator it = active.begin(); it != active.end();) {
            float max_y = it->p0.y > it->p1.y ? it->p0.y : it->p1.y;

            if (max_y <= sample_y) { // Exclusive of bottom most point
                it = active.erase(it);
            }
            else { // Iterator crosses scanline, move to next one
                it++;
            }
        }

        // Find the x value of where each edge intersects the scanline
        for (std::list< RenderEdge >::iterator it = active.begin(), end = active.end(); it != end; ++it) {
            if (fabs(it->p0.x - it->p1.x) < 0.0001f) { // Vertical
                it->x = it->p0.x;
            }
            else {
                float m = (it->p1.y - it->p0.y) / (it->p1.x - it->p0.x);
                float b = it->p0.y - m * it->p0.x;
                it->x = (sample_y - b) / m;
            }
        }

        // Sort edges by ascending x intersection order
        active.sort(LeftmostXIntersection);

        // Init winding for scanline
        int winding = 0;

        // Draw scanline
        std::list< RenderEdge >::iterator it = active.begin();
        std::list< RenderEdge >::iterator end = active.end();
        for (int x = s_x; x <= e_x; ++x) {
            float sample_x = float(x) + 0.5f;
                
            // If any edges are on the left of the sample point
            // update the running winding number and remove the edge
            while (it != end && it->x < sample_x) {
                // Crossing scanline top to bottom
                if (it->p0.y <= sample_y && it->p1.y > sample_y) { 
                    winding -= 1;
                }
                // Crossing scanline bottom up
                else if (it->p1.y <= sample_y && it->p0.y > sample_y) { 
                    winding += 1;
                }
                ++it;
            }
                
            if (winding != 0) {
                DrawPixel(_x + x, _y + y, r, g, b);
            }
        }
    }
}

These are the sorting functions for the above sample:

struct TopmostY {
    inline bool operator() (const RenderEdge& left, const RenderEdge& right) {
        float lY = left.p0.y < left.p1.y ? left.p0.y : left.p1.y;
        float rY = right.p0.y < right.p1.y ? right.p0.y : right.p1.y;
        return lY < rY;
    }
};

bool LeftmostXIntersection(const RenderEdge& left, const RenderEdge& right) {
    return left.x < right.x;
}