draw_triangle.hpp

Back to 2d drawing

pastel/gfx/drawing/

#ifndef PASTELGFX_DRAW_TRIANGLE_HPP
#define PASTELGFX_DRAW_TRIANGLE_HPP

#include "pastel/gfx/drawing/draw_triangle.h"
#include "pastel/gfx/drawing/draw_horizontal_line.h"
#include "pastel/gfx/color/colormixer/assign_colormixer.h"

#include "pastel/geometry/shape/plane.h"
#include "pastel/geometry/predicates.h"

#include "pastel/sys/syscommon.h"

#include <vector>
#include <algorithm>

namespace Pastel
{

    namespace SolidTriangle_
    {

        class Vertex
        {
        public:
            Vertex()
                : position_()
                , index_(0)
            {
            }

            Vertex(
                const Vector2& position,
                integer index)
                : position_(position)
                , index_(index)
            {
            }

            bool operator<(const Vertex& that) const
            {
                if (position_.y() != that.position_.y())
                {
                    return position_.y() < that.position_.y();
                }

                if (position_.x() != that.position_.x())
                {
                    return position_.x() < that.position_.x();
                }

                return index_ < that.index_;
            }

            Vector2 position_;
            integer index_;
        };

    }

    template <typename Type, typename Image_View, typename ColorMixer>
    void drawTriangle(
        const Triangle2& triangle,
        const NoDeduction<Type>& color,
        const View<2, Type, Image_View>& image,
        const ColorMixer& colorMixer)
    {
        using Vertex = SolidTriangle_::Vertex;

        std::vector<Vertex> vertex;
        vertex.reserve(3);
        vertex.push_back(Vertex(triangle[0], 0));
        vertex.push_back(Vertex(triangle[1], 1));
        vertex.push_back(Vertex(triangle[2], 2));
        // Sort the triangle vertices lexicographically.
        std::sort(vertex.begin(), vertex.end());

        bool longLeftEdge = side(vertex[1].position_,
            Plane2(vertex[0].position_,
            cross(vertex[2].position_ - vertex[0].position_))) < 0;

        integer yBottom = toPixelSpanPoint(vertex[0].position_.y());
        integer yMiddle = toPixelSpanPoint(vertex[1].position_.y());
        integer yTop = toPixelSpanPoint(vertex[2].position_.y());

        // Cull

        if (yTop <= 0 || yBottom >= image.height())
        {
            return;
        }

        // Clip y.

        if (yBottom < 0)
        {
            yBottom = 0;
        }
        if (yTop > image.height())
        {
            yTop = image.height();
        }
        if (yMiddle < 0)
        {
            yMiddle = 0;
        }
        if (yMiddle > image.height())
        {
            yMiddle = image.height();
        }

        // The polygon spans the following pixel y ranges:
        // [yBottom, yMiddle[
        // [yMiddle, yTop[

        // Prepare for scan converting
        // the lower part.

        Vector2 leftDelta;
        Vector2 rightDelta;

        if (longLeftEdge)
        {
            leftDelta = vertex[2].position_ - vertex[0].position_;
            rightDelta = vertex[1].position_ - vertex[0].position_;
        }
        else
        {
            leftDelta = vertex[1].position_ - vertex[0].position_;
            rightDelta = vertex[2].position_ - vertex[0].position_;
        }

        real xLeftAdd = leftDelta.x();
        if (leftDelta.y() != 0)
        {
            xLeftAdd /= leftDelta.y();
        }
        real xLeft = vertex[0].position_.x() +

            xLeftAdd * (((real)yBottom + 0.5) - vertex[0].position_.y());

        real xRightAdd = rightDelta.x();
        if (rightDelta.y() != 0)
        {
            xRightAdd /= rightDelta.y();
        }
        real xRight = vertex[0].position_.x() +
            xRightAdd * (((real)yBottom + 0.5) - vertex[0].position_.y());

        // Scan convert the bottom part.

        for (integer y = yBottom;y < yMiddle;++y)
        {
            drawHorizontalLine(xLeft, y, xRight, color, image, colorMixer);

            xLeft += xLeftAdd;
            xRight += xRightAdd;
        }

        // Prepare for scan converting the upper part.
        // Only one of the side changes direction.

        if (longLeftEdge)
        {
            rightDelta = vertex[2].position_ - vertex[1].position_;
            xRightAdd = rightDelta.x();
            if (rightDelta.y() != 0)
            {
                xRightAdd /= rightDelta.y();
            }
            xRight = vertex[1].position_.x() +
                xRightAdd * (((real)yMiddle + 0.5) - vertex[1].position_.y());
        }
        else
        {
            leftDelta = vertex[2].position_ - vertex[1].position_;
            xLeftAdd = leftDelta.x();
            if (leftDelta.y() != 0)
            {
                xLeftAdd /= leftDelta.y();
            }
            xLeft = vertex[1].position_.x() +
                xLeftAdd * (((real)yMiddle + 0.5) - vertex[1].position_.y());
        }

        // Scan convert the upper part

        for (integer y = yMiddle;y < yTop;++y)
        {
            drawHorizontalLine(xLeft, y, xRight, color, image, colorMixer);

            xLeft += xLeftAdd;
            xRight += xRightAdd;
        }
    }

    template <typename Type, typename Image_View>
    void drawTriangle(
        const Triangle2& triangle,
        const NoDeduction<Type>& color,
        const View<2, Type, Image_View>& image)
    {
        drawTriangle(triangle, color, image, assignColorMixer<Type>());
    }

}

#endif