draw_textured_triangle.hpp

Back to 2d drawing

pastel/gfx/drawing/

#ifndef PASTELGFX_DRAW_TEXTURED_TRIANGLE_HPP
#define PASTELGFX_DRAW_TEXTURED_TRIANGLE_HPP

#include "pastel/gfx/drawing/draw_textured_triangle.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 "pastel/sys/vector/vector_tools.h"

#include <vector>
#include <algorithm>

namespace Pastel
{

    namespace DrawTexturedTriangle_
    {

        class TextureVertex
        {
        public:
            TextureVertex()
                : x_(0)
                , y_(0)
                , uv_(0)
            {
            }

            TextureVertex(
                const Vector2& position,
                const Vector2& uv,
                integer index)
                : x_(position[0])
                , y_(position[1])
                , uv_(uv)
                , index_(index)
            {
            }

            real x_;
            real y_;
            Vector2 uv_;
            integer index_;
        };

        class VerticalComparison
        {
        public:
            bool operator()(
                const TextureVertex& left,
                const TextureVertex& right) const
            {
                if (left.y_ != right.y_)
                {
                    return left.y_ < right.y_;
                }

                if (left.x_ != right.x_)
                {
                    return left.x_ < right.x_;
                }

                return left.index_ < right.index_;
            }
        };

        class HorizontalComparison
        {
        public:
            bool operator()(
                const TextureVertex& left,
                const TextureVertex& right) const
            {
                if (left.x_ != right.x_)
                {
                    return left.x_ < right.x_;
                }

                if (left.y_ != right.y_)
                {
                    return left.y_ < right.y_;
                }

                return left.index_ < right.index_;
            }
        };

    }

    template <typename Type,
        typename Image_View, typename ColorMixer>
    void drawTriangle(
        const Triangle2& triangle,
        const Triangle2& uvTriangle,
        const Texture<Type>& textureSampler,
        const View<2, Type, Image_View>& image,
        const ColorMixer& colorMixer)
    {
        using Vertex = DrawTexturedTriangle_::TextureVertex;

        // Order vertices vertically.

        std::vector<Vertex> yVertex;
        yVertex.reserve(3);
        yVertex.push_back(Vertex(triangle[0], uvTriangle[0], 0));
        yVertex.push_back(Vertex(triangle[1], uvTriangle[1], 1));
        yVertex.push_back(Vertex(triangle[2], uvTriangle[2], 2));
        std::sort(yVertex.begin(), yVertex.end(),
            DrawTexturedTriangle_::VerticalComparison());

        const Vertex& yMinVertex = yVertex[0];
        const Vertex& yMidVertex = yVertex[1];
        const Vertex& yMaxVertex = yVertex[2];

        // Order vertices horizontally.

        std::vector<Vertex> xVertex;
        xVertex.reserve(3);
        xVertex.push_back(Vertex(triangle[0], uvTriangle[0], 0));
        xVertex.push_back(Vertex(triangle[1], uvTriangle[1], 1));
        xVertex.push_back(Vertex(triangle[2], uvTriangle[2], 2));
        std::sort(xVertex.begin(), xVertex.end(),
            DrawTexturedTriangle_::HorizontalComparison());

        const Vertex& xMinVertex = xVertex[0];
        const Vertex& xMidVertex = xVertex[1];
        const Vertex& xMaxVertex = xVertex[2];

        // Cull and clip.

        integer xMin = toPixelSpanPoint(xMinVertex.x_);
        integer xMax = toPixelSpanPoint(xMaxVertex.x_);

        integer yMin = toPixelSpanPoint(yMinVertex.y_);
        integer yMid = toPixelSpanPoint(yMidVertex.y_);
        integer yMax = toPixelSpanPoint(yMaxVertex.y_);

        integer width = image.width();
        integer height = image.height();

        if (xMax <= 0 || xMin >= width ||
            yMax <= 0 || yMin >= height)
        {
            return;
        }

        if (yMin < 0)
        {
            yMin = 0;
        }
        if (yMax > height)
        {
            yMax = height;
        }
        if (yMid < 0)
        {
            yMid = 0;
        }
        if (yMid > height)
        {
            yMid = height;
        }

        // The polygon spans the following pixel y ranges:
        // [yMin, yMid[
        // [yMid, yMax[

        // Compute delta's w.r.t. y.

        real xLeftDelta = 0;
        real yLeftDelta = 0;
        Vector2 uvLeftDelta;

        real xRightDelta = 0;
        real yRightDelta = 0;
        Vector2 uvRightDelta;

        bool longLeftEdge =
            side(
            Vector2(yMidVertex.x_, yMidVertex.y_),
            Plane2(
            Vector2(yMinVertex.x_, yMinVertex.y_),
            cross(Vector2(yMaxVertex.x_, yMaxVertex.y_) -
            Vector2(yMinVertex.x_, yMinVertex.y_)))) < 0;

        if (longLeftEdge)
        {
            xLeftDelta = yMaxVertex.x_ - yMinVertex.x_;
            yLeftDelta = yMaxVertex.y_ - yMinVertex.y_;
            uvLeftDelta = yMaxVertex.uv_ - yMinVertex.uv_;

            xRightDelta = yMidVertex.x_ - yMinVertex.x_;
            yRightDelta = yMidVertex.y_ - yMinVertex.y_;
            uvRightDelta = yMidVertex.uv_ - yMinVertex.uv_;
        }
        else
        {
            xLeftDelta = yMidVertex.x_ - yMinVertex.x_;
            yLeftDelta = yMidVertex.y_ - yMinVertex.y_;
            uvLeftDelta = yMidVertex.uv_ - yMinVertex.uv_;

            xRightDelta = yMaxVertex.x_ - yMinVertex.x_;
            yRightDelta = yMaxVertex.y_ - yMinVertex.y_;
            uvRightDelta = yMaxVertex.uv_ - yMinVertex.uv_;
        }

        // Compute delta's w.r.t. x.

        real yBottomDelta = 0;
        real xBottomDelta = 0;
        Vector2 uvBottomDelta;

        real yTopDelta = 0;
        real xTopDelta = 0;
        Vector2 uvTopDelta;

        bool longBottomEdge =
            side(
            Vector2(xMidVertex.x_, xMidVertex.y_),
            Plane2(
            Vector2(xMinVertex.x_, xMinVertex.y_),
            cross(Vector2(xMaxVertex.x_, xMaxVertex.y_) -
            Vector2(xMinVertex.x_, xMinVertex.y_)))) < 0;

        if (longBottomEdge)
        {
            yBottomDelta = xMaxVertex.y_ - xMinVertex.y_;
            xBottomDelta = xMaxVertex.x_ - xMinVertex.x_;
            uvBottomDelta = xMaxVertex.uv_ - xMinVertex.uv_;

            yTopDelta = xMidVertex.y_ - xMinVertex.y_;
            xTopDelta = xMidVertex.x_ - xMinVertex.x_;
            uvTopDelta = xMidVertex.uv_ - xMinVertex.uv_;
        }
        else
        {
            yBottomDelta = xMidVertex.y_ - xMinVertex.y_;
            xBottomDelta = xMidVertex.x_ - xMinVertex.x_;
            uvBottomDelta = xMidVertex.uv_ - xMinVertex.uv_;

            yTopDelta = xMaxVertex.y_ - xMinVertex.y_;
            xTopDelta = xMaxVertex.x_ - xMinVertex.x_;
            uvTopDelta = xMaxVertex.uv_ - xMinVertex.uv_;
        }

        // Compute boundary derivatives w.r.t. y.

        real dxLeftDy = xLeftDelta;
        Vector2 dUvLeftDy = uvLeftDelta;
        if (yLeftDelta != 0)
        {
            real yLeftDeltaInv = inverse(yLeftDelta);

            dxLeftDy *= yLeftDeltaInv;
            dUvLeftDy *= yLeftDeltaInv;
        }

        real dxRightDy = xRightDelta;
        Vector2 dUvRightDy = uvRightDelta;
        if (yRightDelta != 0)
        {
            real yRightDeltaInv = inverse(yRightDelta);

            dxRightDy *= yRightDeltaInv;
            dUvRightDy *= yRightDeltaInv;
        }

        // Compute boundary derivatives w.r.t. x.

        real dyBottomDx = yBottomDelta;
        Vector2 dUvBottomDx = uvBottomDelta;
        if (xBottomDelta != 0)
        {
            real xBottomDeltaInv = inverse(xBottomDelta);

            dyBottomDx *= xBottomDeltaInv;
            dUvBottomDx *= xBottomDeltaInv;
        }

        real dyTopDx = yTopDelta;
        Vector2 dUvTopDx = uvTopDelta;
        if (xTopDelta != 0)
        {
            real xTopDeltaInv = inverse(xTopDelta);

            dyTopDx *= xTopDeltaInv;
            dUvTopDx *= xTopDeltaInv;
        }

        // Offset the start scanline to pixel rows.

        real yOffset = ((real)yMin + 0.5) - yMinVertex.y_;

        real xLeft = yMinVertex.x_ + dxLeftDy * yOffset;
        Vector2 uvLeft = yMinVertex.uv_ + dUvLeftDy * yOffset;

        real xRight = yMinVertex.x_ + dxRightDy * yOffset;
        Vector2 uvRight = yMinVertex.uv_ + dUvRightDy * yOffset;

        // Compute data derivatives w.r.t. x.

        Vector2 dUvDx;
        if (longLeftEdge)
        {
            real yDelta = yMidVertex.y_ - yMinVertex.y_;

            real dx =
                yMidVertex.x_ -

                (yMinVertex.x_ + dxLeftDy * yDelta);
            Vector2 dUv =
                yMidVertex.uv_ -

                (yMinVertex.uv_ + dUvLeftDy * yDelta);

            real invDx = inverse(dx);

            dUvDx = dUv * invDx;
        }
        else
        {
            real yDelta = yMidVertex.y_ - yMinVertex.y_;

            real dx =
                (yMinVertex.x_ + dxRightDy * yDelta) -
                yMidVertex.x_;
            Vector2 dUv =
                (yMinVertex.uv_ + dUvRightDy * yDelta) -
                yMidVertex.uv_;

            real invDx = inverse(dx);

            dUvDx = dUv * invDx;
        }

        // Compute data derivatives w.r.t. y.

        Vector2 dUvDy;
        if (longBottomEdge)
        {
            real xDelta = xMidVertex.x_ - xMinVertex.x_;

            real dy =
                xMidVertex.y_ -

                (xMinVertex.y_ + dyBottomDx * xDelta);
            Vector2 dUv =
                xMidVertex.uv_ -

                (xMinVertex.uv_ + dUvBottomDx * xDelta);

            real invDy = inverse(dy);

            dUvDy = dUv * invDy;
        }
        else
        {
            real xDelta = xMidVertex.x_ - xMinVertex.x_;

            real dy =
                (xMinVertex.y_ + dyTopDx * xDelta) -
                xMidVertex.y_;
            Vector2 dUv =
                (xMinVertex.uv_ + dUvTopDx * xDelta) -
                xMidVertex.uv_;

            real invDy = inverse(dy);

            dUvDy = dUv * invDy;
        }

        // Scan convert the bottom part.

        for (integer y = yMin;y < yMid;++y)
        {
            integer xBegin = std::max(toPixelSpanPoint(xLeft), (integer)0);
            integer xEnd = std::min(toPixelSpanPoint(xRight), width);

            if (xEnd - xBegin > 0)
            {
                real xOffset = ((real)xBegin + 0.5) - xLeft;

                Vector2 uv = uvLeft + dUvDx * xOffset;

                for (integer x = xBegin;x < xEnd;++x)
                {
                    image(x, y) =
                        colorMixer(image(x, y),
                        textureSampler(uv, matrix2x2<real>(dUvDx, dUvDy)));

                    uv += dUvDx;
                }
            }

            xLeft += dxLeftDy;
            uvLeft += dUvLeftDy;

            xRight += dxRightDy;
            uvRight += dUvRightDy;
        }

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

        if (longLeftEdge)
        {
            yRightDelta = yMaxVertex.y_ - yMidVertex.y_;

            xRightDelta = yMaxVertex.x_ - yMidVertex.x_;
            uvRightDelta = yMaxVertex.uv_ - yMidVertex.uv_;

            dxRightDy = xRightDelta;
            dUvRightDy = uvRightDelta;
            if (yRightDelta != 0)
            {
                real yRightDeltaInv = inverse(yRightDelta);

                dxRightDy *= yRightDeltaInv;
                dUvRightDy *= yRightDeltaInv;
            }

            // Offset the scanline to pixel rows.

            real yOffset = ((real)yMid + 0.5) - yMidVertex.y_;

            xRight = yMidVertex.x_ + dxRightDy * yOffset;
            uvRight = yMidVertex.uv_ + dUvRightDy * yOffset;
        }
        else
        {
            yLeftDelta = yMaxVertex.y_ - yMidVertex.y_;

            xLeftDelta = yMaxVertex.x_ - yMidVertex.x_;
            uvLeftDelta = yMaxVertex.uv_ - yMidVertex.uv_;

            dxLeftDy = xLeftDelta;
            dUvLeftDy = uvLeftDelta;
            if (yLeftDelta != 0)
            {
                real yLeftDeltaInv = inverse(yLeftDelta);

                dxLeftDy *= yLeftDeltaInv;
                dUvLeftDy *= yLeftDeltaInv;
            }

            // Offset the scanline to pixel rows.

            real yOffset = ((real)yMid + 0.5) - yMidVertex.y_;

            xLeft = yMidVertex.x_ + dxLeftDy * yOffset;
            uvLeft = yMidVertex.uv_ + dUvLeftDy * yOffset;
        }

        // Scan convert the top part.

        for (integer y = yMid;y < yMax;++y)
        {
            integer xBegin = std::max(toPixelSpanPoint(xLeft), (integer)0);
            integer xEnd = std::min(toPixelSpanPoint(xRight), width);

            if (xEnd - xBegin > 0)
            {
                real xOffset = ((real)xBegin + 0.5) - xLeft;

                Vector2 uv = uvLeft + dUvDx * xOffset;

                for (integer x = xBegin;x < xEnd;++x)
                {
                    image(x, y) =
                        colorMixer(image(x, y),
                        textureSampler(uv, matrix2x2<real>(dUvDx, dUvDy)));

                    uv += dUvDx;
                }
            }

            xLeft += dxLeftDy;
            uvLeft += dUvLeftDy;

            xRight += dxRightDy;
            uvRight += dUvRightDy;
        }
    }

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

}

#endif