conformalaffine2d_least_squares.hpp

Back to Conformal affine algorithms

pastel/math/conformalaffine2d/

#ifndef PASTELMATH_CONFORMALAFFINE2D_LEAST_SQUARES_HPP
#define PASTELMATH_CONFORMALAFFINE2D_LEAST_SQUARES_HPP

#include "pastel/math/conformalaffine2d/conformalaffine2d_least_squares.h"
#include "pastel/sys/set/zip_set.h"

namespace Pastel
{

    template <
        typename From_PointSet, 
        typename To_PointSet,
        Requires<
            Models<From_PointSet, PointSet_Concept>,
            Models<To_PointSet, PointSet_Concept>
        >
    >
    ConformalAffine2D<PointSet_Real<From_PointSet>> 
        lsConformalAffine(
            From_PointSet fromSet,
            To_PointSet toSet)
    {
        using Real = PointSet_Real<From_PointSet>;
        using FromPoint =  PointSet_PointId<From_PointSet>;
        using ToPoint = PointSet_PointId<To_PointSet>;

        static constexpr integer N = PointSet_Dimension<From_PointSet>::value;

        ENSURE_OP(setSize(fromSet), ==, setSize(toSet));

        integer n = setSize(fromSet);
        auto fromIndex = fromSet.begin();
        auto toIndex = toSet.begin();

        if (n == 0)
        {
            // There are no points in either set.
            // Return the identity transformation.
            return ConformalAffine2D<Real>();
        }

        if (n == 1)
        {
            // There is exactly one point in each set.
            // Return a pure translation, without
            // rotation or scaling.

            return ConformalAffine2D<Real>(
                1, 0, 
                pointAsVector(*toIndex) - 
                pointAsVector(*fromIndex));
        }

        Vector<Real, N> sumFrom(ofDimension(2), 0);
        Vector<Real, N> sumTo(ofDimension(2) ,0);
        Real sumSquareFrom = 0;
        Real dotSum = 0;
        Real crossDotSum = 0;

        RANGES_FOR(auto&& elements, zipSet(fromSet, toSet))
        {
            auto from = pointAsVector(elements.first);
            auto to = pointAsVector(elements.second);

            sumFrom += from;
            sumTo += to;

            sumSquareFrom += dot(from);
            dotSum += dot(from, to);
            crossDotSum += dot(cross(from), to);
        }

        Real det = n * sumSquareFrom - dot(sumFrom);
        Real invDet = inverse(det);

        Vector<Real, N> translation(
            (sumSquareFrom * sumTo[0] - sumFrom[0] * dotSum + sumFrom[1] * crossDotSum) * invDet,
            (sumSquareFrom * sumTo[1] - sumFrom[1] * dotSum - sumFrom[0] * crossDotSum) * invDet);

        // scaledCos = scale * cos(angle)

        Real scaledCos =
            (-sumFrom[0] * sumTo[0] - sumFrom[1] * sumTo[1] + n * dotSum) * invDet;

        // scaledSin = scale * sin(angle)

        Real scaledSin =
            (sumFrom[1] * sumTo[0] - sumFrom[0] * sumTo[1] + n * crossDotSum) * invDet;

        // scaledCos^2 + scaledSin^2 = scale * cos^2(angle) + scale * sin^2(angle)
        // = scale * (cos^2(angle) + sin^2(angle)) = scale

        Real scale =
            std::sqrt(scaledCos * scaledCos + scaledSin * scaledSin);

        // atan(scaledSin / scaledCos) = atan((scale * sin(angle)) / (scale * cos(angle)))
        // = atan(tan(angle)) = angle

        Real angle = 
            positiveRadians<Real>(std::atan2(scaledSin, scaledCos));

        return ConformalAffine2D<Real>(scale, angle, translation);
    }

}

#endif