// Affine manipulation routines for PLplot.
//
// Copyright (C) 2009-2014 Alan W. Irwin
//
// This file is part of PLplot.
//
// PLplot is free software; you can redistribute it and/or modify
// it under the terms of the GNU Library General Public License as published
// by the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// PLplot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public License
// along with PLplot; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//

#include "plplotP.h"

//! @file
//!  These function perform variuos affine transformations.
//!
//! For background on these affine functions see SVG specification, e.g.,
//! http://www.w3.org/TR/SVGTiny12/coords.html#EstablishingANewUserSpace.
//! Affine 3x3 matrices with elements A_i,j always have a last row of
//! 0, 0, 1 so the elements are conveniently stored in a vector in the order
//! A_1_1, A_2_1, A_1_2, A_2_2, A_1_3, A_2_3, with the last row implied.
//!
//! N.B.  The PLplot affine interpretation of translate, scale, etc., is
//! as actions on coordinate systems, rather than actions on objects.  This
//! is identical to the SVG specficiation interpretation.  However, the
//! SVG specification interprets the affine matrix as follows:
//! old_coord_vector = affine_matrix * new_coordinate vector.  In PLplot
//! we use the alternative interpretation
//! new_coord_vector = affine_matrix * old_coordinate vector.
//! The result is all affine matrices below are the inverses of the SVG
//! equivalents.
//!
//! N.B. All PLplot affine functions below return the affine matrix result (in
//! vector form) as the first argument).  It is the calling routine's
//! responsibility to provide the space for all affine matrix arguments,
//! i.e., a PLFLT array with a dimension of 6.
//!
//

//! Returns affine identity matrix
//!
//! @param affine_vector Initialize a (pre-allocated) transform matrix.
//!
void
plP_affine_identity( PLFLT *affine_vector )
{
    affine_vector[0] = 1.;
    affine_vector[1] = 0.;
    affine_vector[2] = 0.;
    affine_vector[3] = 1.;
    affine_vector[4] = 0.;
    affine_vector[5] = 0.;
}

//! Translate new coordinate system axes relative to the old.
//!
//! @param affine_vector Pre-allocated storage for a translation matrix.
//! @param xtranslate Amount to translate in x.
//! @param ytranslate Amount to translate in y.
//!
void
plP_affine_translate( PLFLT *affine_vector, PLFLT xtranslate, PLFLT ytranslate )
{
    affine_vector[0] = 1.;
    affine_vector[1] = 0.;
    affine_vector[2] = 0.;
    affine_vector[3] = 1.;
    // If the new coordinate system axis is shifted by xtranslate and ytranslate
    // relative to the old, then the actual new coordinates are shifted in
    // the opposite direction.
    affine_vector[4] = -xtranslate;
    affine_vector[5] = -ytranslate;
}

//! Scale new coordinate system axes relative to the old.
//!
//! @param affine_vector Pre-allocate storage for a scale matrix.
//! @param xscale Amount to scale in x.
//! @param yscale Amount to scale in y.
//!
void
plP_affine_scale( PLFLT *affine_vector, PLFLT xscale, PLFLT yscale )
{
    // If the new coordinate system axes are scaled by xscale and yscale
    // relative to the old, then the actual new coordinates are scaled
    // by the inverses.
    if ( xscale == 0. )
    {
        plwarn( "plP_affine_scale: attempt to scale X coordinates by zero." );
        xscale = 1.;
    }
    if ( yscale == 0. )
    {
        plwarn( "plP_affine_scale: attempt to scale Y coordinates by zero." );
        yscale = 1.;
    }
    affine_vector[0] = 1. / xscale;
    affine_vector[1] = 0.;
    affine_vector[2] = 0.;
    affine_vector[3] = 1. / yscale;
    affine_vector[4] = 0.;
    affine_vector[5] = 0.;
}

//! Rotate new coordinate system axes relative to the old.
//! angle is in degrees.
//!
//! @param affine_vector Pre-allocated storage for a rotation matrix.
//! @param angle Amount to rotate in degrees.
//!
void
plP_affine_rotate( PLFLT *affine_vector, PLFLT angle )
{
    PLFLT cosangle = cos( PI * angle / 180. );
    PLFLT sinangle = sin( PI * angle / 180. );
    affine_vector[0] = cosangle;
    affine_vector[1] = -sinangle;
    affine_vector[2] = sinangle;
    affine_vector[3] = cosangle;
    affine_vector[4] = 0.;
    affine_vector[5] = 0.;
}

//! Skew new X coordinate axis relative to the old.
//! angle is in degrees.
//!
//! @param affine_vector Pre-allocated storage for a skew (in x) matrix.
//! @param angle Amount to skew in degrees.
//!

void
plP_affine_xskew( PLFLT *affine_vector, PLFLT angle )
{
    PLFLT tanangle = tan( PI * angle / 180. );
    affine_vector[0] = 1.;
    affine_vector[1] = 0.;
    affine_vector[2] = -tanangle;
    affine_vector[3] = 1.;
    affine_vector[4] = 0.;
    affine_vector[5] = 0.;
}

//! Skew new Y coordinate axis relative to the old.
//! angle is in degrees.
//!
//! @param affine_vector Pre-allocated storage for a skew (in y) matrix.
//! @param angle Amount to skew in degrees.
//!

void
plP_affine_yskew( PLFLT *affine_vector, PLFLT angle )
{
    PLFLT tanangle = tan( PI * angle / 180. );
    affine_vector[0] = 1.;
    affine_vector[1] = -tanangle;
    affine_vector[2] = 0.;
    affine_vector[3] = 1.;
    affine_vector[4] = 0.;
    affine_vector[5] = 0.;
}

//! Multiply two affine transformation matrices to form a third.
//!
//! A = B * C
//!
//! @param affine_vectorA Pre-allocated storage for the result of
//! multiplying matrix affine_vectorB by matrix affine_vectorC.
//! @param affine_vectorB First matrix to multiply.
//! @param affine_vectorC Second matrix to multiply.
//!

void
plP_affine_multiply(
    PLFLT *affine_vectorA,
    const PLFLT *affine_vectorB,
    const PLFLT *affine_vectorC )
{
    int   i;
    PLFLT result[NAFFINE];
    // Multiply two affine matrices stored in affine vector form.
    result[0] = affine_vectorB[0] * affine_vectorC[0] +
                affine_vectorB[2] * affine_vectorC[1];
    result[2] = affine_vectorB[0] * affine_vectorC[2] +
                affine_vectorB[2] * affine_vectorC[3];
    result[4] = affine_vectorB[0] * affine_vectorC[4] +
                affine_vectorB[2] * affine_vectorC[5] +
                affine_vectorB[4];

    result[1] = affine_vectorB[1] * affine_vectorC[0] +
                affine_vectorB[3] * affine_vectorC[1];
    result[3] = affine_vectorB[1] * affine_vectorC[2] +
                affine_vectorB[3] * affine_vectorC[3];
    result[5] = affine_vectorB[1] * affine_vectorC[4] +
                affine_vectorB[3] * affine_vectorC[5] +
                affine_vectorB[5];

    for ( i = 0; i < NAFFINE; i++ )
        affine_vectorA[i] = result[i];
}
