root/src/libs/icon/transformer/PerspectiveTransformer.cpp
/*
 * Copyright 2006-2007, 2023, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Zardshard
 */


#include "PerspectiveTransformer.h"

#include <new>
#include <stdio.h>

#include <agg_basics.h>
#include <agg_bounding_rect.h>
#include <Message.h>

#include "Shape.h"


_USING_ICON_NAMESPACE
using std::nothrow;


PerspectiveTransformer::PerspectiveTransformer(VertexSource& source, Shape* shape)
        : Transformer("Perspective"),
          PathTransformer(source),
          Perspective(source, *this),
          fShape(shape)
#ifdef ICON_O_MATIC
        , fInverted(false)
#endif
{
#ifdef ICON_O_MATIC
        if (fShape != NULL) {
                fShape->AcquireReference();
                fShape->AddObserver(this);
                ObjectChanged(fShape); // finish initialization
        }
#endif
}


PerspectiveTransformer::PerspectiveTransformer(
                VertexSource& source, Shape* shape, BMessage* archive)
        : Transformer(archive),
          PathTransformer(source),
          Perspective(source, *this),
          fShape(shape)
#ifdef ICON_O_MATIC
        , fInverted(false)
#endif
{
        double matrix[9];
        for (int i = 0; i < 9; i++) {
                if (archive->FindDouble("matrix", i, &matrix[i]) != B_OK)
                        matrix[i] = 0;
        }
        load_from(matrix);

#ifdef ICON_O_MATIC
        if (fShape != NULL) {
                fShape->AcquireReference();
                fShape->AddObserver(this);
                ObjectChanged(fShape); // finish initialization
        }
#endif
}


PerspectiveTransformer::PerspectiveTransformer(const PerspectiveTransformer& other)
#ifdef ICON_O_MATIC
        : Transformer(other.Name()),
#else
        : Transformer(""),
#endif
          PathTransformer(*other.fSource),
          Perspective(*fSource, *this),
          fShape(other.fShape)
#ifdef ICON_O_MATIC
        , fInverted(other.fInverted),
          fFromBox(other.fFromBox),
          fToLeftTop(other.fToLeftTop),
          fToRightTop(other.fToRightTop),
          fToLeftBottom(other.fToLeftBottom),
          fToRightBottom(other.fToRightBottom),
          fValid(other.fValid)
#endif
{
        double matrix[9];
        other.store_to(matrix);
        load_from(matrix);

#ifdef ICON_O_MATIC
        if (fShape != NULL) {
                fShape->AcquireReference();
                fShape->AddObserver(this);
        }
#endif
}


PerspectiveTransformer::~PerspectiveTransformer()
{
#ifdef ICON_O_MATIC
        if (fShape != NULL) {
                fShape->RemoveObserver(this);
                fShape->ReleaseReference();
        }
#endif
}


// #pragma mark -


Transformer*
PerspectiveTransformer::Clone() const
{
        return new (nothrow) PerspectiveTransformer(*this);
}


// #pragma mark -


void
PerspectiveTransformer::rewind(unsigned path_id)
{
        Perspective::rewind(path_id);
}


unsigned
PerspectiveTransformer::vertex(double* x, double* y)
{
#ifdef ICON_O_MATIC
        if (fValid)
                return Perspective::vertex(x, y);
        else
                return agg::path_cmd_stop;
#else
        return Perspective::vertex(x, y);
#endif
}


void
PerspectiveTransformer::SetSource(VertexSource& source)
{
        PathTransformer::SetSource(source);
        Perspective::attach(source);

#ifdef ICON_O_MATIC
        ObjectChanged(fShape);
#endif
}


double
PerspectiveTransformer::ApproximationScale() const
{
        return fSource->ApproximationScale() * scale();
}


// #pragma mark -


void
PerspectiveTransformer::Invert()
{
#ifdef ICON_O_MATIC
        fInverted = !fInverted;

        // TODO: degenerate matrices may not be adequately handled
        bool degenerate = !invert();
        fValid = fValid && !degenerate;
#else
        invert();
#endif
}


// #pragma mark -


#ifdef ICON_O_MATIC

status_t
PerspectiveTransformer::Archive(BMessage* into, bool deep) const
{
        status_t ret = Transformer::Archive(into, deep);

        into->what = archive_code;

        double matrix[9];
        store_to(matrix);

        for (int i = 0; i < 9; i++) {
                if (ret == B_OK)
                        ret = into->AddDouble("matrix", matrix[i]);
        }

        return ret;
}


// #prama mark -


void
PerspectiveTransformer::ObjectChanged(const Observable* object)
{
        if (fInverted) {
                printf("calculating the validity or bounding box of an inverted "
                        "perspective transformer is currently unsupported.");
                return;
        }

        uint32 pathID[1];
        pathID[0] = 0;
        double left, top, right, bottom;
        agg::bounding_rect(*fSource, pathID, 0, 1, &left, &top, &right, &bottom);
        BRect newFromBox = BRect(left, top, right, bottom);

        // Stop if nothing we care about has changed
        // TODO: Can this be done earlier? It would be nice to avoid having to
        // recalculate the bounding box before realizing nothing needs to be done.
    if (fFromBox == newFromBox)
                return;

        fFromBox = newFromBox;

        _CheckValidity();

        double x = fFromBox.left; double y = fFromBox.top;
        Transform(&x, &y);
        fToLeftTop = BPoint(x, y);

        x = fFromBox.right; y = fFromBox.top;
        Transform(&x, &y);
        fToRightTop = BPoint(x, y);

        x = fFromBox.left; y = fFromBox.bottom;
        Transform(&x, &y);
        fToLeftBottom = BPoint(x, y);

        x = fFromBox.right; y = fFromBox.bottom;
        Transform(&x, &y);
        fToRightBottom = BPoint(x, y);
}


// #pragma mark -


void
PerspectiveTransformer::TransformTo(
        BPoint leftTop, BPoint rightTop, BPoint leftBottom, BPoint rightBottom)
{
        fToLeftTop = leftTop;
        fToRightTop = rightTop;
        fToLeftBottom = leftBottom;
        fToRightBottom = rightBottom;

        double quad[8] = {
                fToLeftTop.x, fToLeftTop.y,
                fToRightTop.x, fToRightTop.y,
                fToRightBottom.x, fToRightBottom.y,
                fToLeftBottom.x, fToLeftBottom.y
        };

        if (!fInverted) {
                rect_to_quad(
                        fFromBox.left, fFromBox.top,
                        fFromBox.right, fFromBox.bottom, quad);
        } else {
                quad_to_rect(quad,
                        fFromBox.left, fFromBox.top,
                        fFromBox.right, fFromBox.bottom);
        }

        _CheckValidity();
        Notify();
}


// #pragma mark -


void
PerspectiveTransformer::_CheckValidity()
{
        // Checks that none of the points are too close to the camera. These tend to
        // lead to very big numbers or a divide by zero error. Also checks that all
        // points are on the same side of the camera. Transformations with points on
        // different sides of the camera look weird and tend to cause crashes.

        fValid = true;
        double w;
        bool positive;

        if (!fInverted) {
                w = fFromBox.left * w0 + fFromBox.top * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                positive = w > 0;

                w = fFromBox.right * w0 + fFromBox.top * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                fValid &= (w>0)==positive;

                w = fFromBox.left * w0 + fFromBox.bottom * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                fValid &= (w>0)==positive;

                w = fFromBox.right * w0 + fFromBox.bottom * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                fValid &= (w>0)==positive;
        } else {
                w = fToLeftTop.x * w0 + fToLeftTop.y * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                positive = w > 0;

                w = fToRightTop.x * w0 + fToRightTop.y * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                fValid &= (w>0)==positive;

                w = fToLeftBottom.x * w0 + fToLeftBottom.y * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                fValid &= (w>0)==positive;

                w = fToRightBottom.x * w0 + fToRightBottom.y * w1 + w2;
                fValid &= (fabs(w) > 0.00001);
                fValid &= (w>0)==positive;
        }
}

#endif // ICON_O_MATIC