/*
 * @(#)TransformFactory.java    1.0  29 September 2003
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.util.*;

import java.awt.geom.*;

/**
 * <P><CODE>TransformFactory</CODE> provides a collection of static
 * methods that construct <CODE>AffineTransform</CODE> objects for
 * common 2-dimensional transforms.</P>
 *
 * <P>Angle measurement is in degrees not radians.</P>
 *
 * <P>A positive angle is in the direction from the positive x-axis
 * towards the positive y-axis.</P>
 *
 * <P>Class <CODE>TransformFactory</CODE> cannot be instantiated.</P>
 *
 * @see java.awt.geom.AffineTransform
 * @author  Richard Rasala
 * @version 2.3
 * @since   2.3
 */
public class TransformFactory {
    
    /** Private constructor to prevent instantiation. */
    private TransformFactory() {}
    
    
    /**
     * Returns the transform to translate by (tx, ty).
     *
     * @param  tx the x-coordinate of the translate
     * @param  ty the y-coordinate of the translate
     * @return the transform to translate by (tx, ty)
     */
    public static AffineTransform translate(double tx, double ty)
    {
        AffineTransform T = new AffineTransform();
        
        T.translate(tx, ty);
        
        return T;
    }
    
    
    /**
     * Returns the transform to rotate with center at (x, y)
     * by the given angle in degrees.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  degrees the rotation angle in degrees
     * @return the transform to rotate with center at (x, y)
     *         by the given angle in degrees
     */
    public static AffineTransform rotate(double x, double y, double degrees)
    {
        AffineTransform T = new AffineTransform();
        
        double radians = Math.toRadians(degrees);
        
        T.translate( x,  y);
        T.rotate(radians);
        T.translate(-x, -y);
        
        return T;
    }
    
    
    /**
     * Returns the transform to scale with center at (x, y),
     * with scale factor s along the line at the given angle in degrees,
     * and with scale factor t along the perpendicular line.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  degrees the angle in degrees of the main scaling axis
     * @param  s the scale factor along the axis at angle degrees
     * @param  t the scale factor along the axis at angle degrees+90
     * @return the transform to scale with center at (x, y), with scale
     *         factor s along the line at the given angle in degrees,
     *         and with scale factor t along the perpendicular line
     */
    public static AffineTransform scale
        (double x, double y, double degrees, double s, double t)
    {
        AffineTransform T = new AffineTransform();
        
        double radians = Math.toRadians(degrees);
        
        T.translate( x,  y);
        T.rotate( radians);
        T.scale(s, t);
        T.rotate(-radians);
        T.translate(-x, -y);
        
        return T;
    }
    

    /**
     * <P>Returns the transform to shear with center at (x, y) and with
     * shear factor s along the line at the given angle in degrees.</P>
     *
     * <P>In the special case when x = 0, y = 0, degrees = 0, this mapping
     * produces the transform: (u, v) maps to (u + s * v, v), which fixes
     * the x-axis and shears parallel to that axis.</P>
     *
     * <P>In the general case, the fixed axis for the shear is the line at
     * the given angle degrees through the point (x, y).</P>
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  degrees the angle in degrees of the fixed axis for shear
     * @param  s determines the amount of shear along lines parallel to
     *         the fixed axis for shear
     * @return the transform to shear with center at (x, y) and with shear
     *         factor s along the line at the given angle in degrees
     */
    public static AffineTransform shear
        (double x, double y, double degrees, double s)
    {
        AffineTransform T = new AffineTransform();
        
        double radians = Math.toRadians(degrees);
        
        T.translate( x,  y);
        T.rotate( radians);
        T.shear(s, 0);
        T.rotate(-radians);
        T.translate(-x, -y);
        
        return T;
    }
    

    /**
     * Returns the transform to reflect along the line through (x, y) at the
     * given angle in degrees.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  degrees the angle in degrees of the line of reflection
     * @return the transform to reflect along the line through (x, y) at the
     *         given angle in degrees
     */
    public static AffineTransform reflect(double x, double y, double degrees)
    {
        return scale(x, y, degrees, 1, -1);
    }
    
    
    /**
     * Returns the transform to glide reflect along the line through (x, y)
     * at the given angle in degrees and the given glide distance.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  degrees the angle in degrees of the line of glide reflection
     * @param  distance the translation distance along the line of glide reflection
     * @return the transform to glide reflect along the line through (x, y)
     *         at the given angle in degrees and the given glide distance
     */
    public static AffineTransform glidereflect
        (double x, double y, double degrees, double distance)
    {
        double radians = Math.toRadians(degrees);
        double tx = distance * Math.cos(radians);
        double ty = distance * Math.sin(radians);
        
        return compose(new AffineTransform[] {
            translate(tx, ty),
            reflect(x, y, degrees)
        });
    }
    
    
    /**
     * <P>Returns the affine transform with the given 2-by-3 matrix.</P>
     *
     * <P>The given 2-by-3 matrix has the form:</P>
     *
     * <CODE>
     * <P>m00 m01 m02</P>
     * <P>m10 m11 m12</P>
     * </CODE>.
     *
     * @param  m00 matrix coefficient for row 0 and column 0
     * @param  m10 matrix coefficient for row 1 and column 0
     * @param  m01 matrix coefficient for row 0 and column 1
     * @param  m11 matrix coefficient for row 1 and column 1
     * @param  m02 matrix coefficient for row 0 and column 2
     * @param  m12 matrix coefficient for row 1 and column 2
     * @return the affine transform with the matrix:
     *         <CODE>m00 m10 m01 m11 m02 m12</CODE>
     */
    public static AffineTransform transform
        (double m00, double m10, double m01, double m11, double m02, double m12)
    {
        AffineTransform T = new AffineTransform();
        
        T.setTransform(m00, m10, m01, m11, m02, m12);
        
        return T;
    }
    
    
    /**
     * <P>Returns the affine transform centered at the given point (x, y)
     * whose corresponding 2-by-3 matrix at the origin is the given matrix.</P>
     *
     * <P>The given 2-by-3 matrix has the form:</P>
     *
     * <CODE>
     * <P>m00 m01 m02</P>
     * <P>m10 m11 m12</P>
     * </CODE>.
     *
     * <P>The affine transform returned is the composite of the following
     * three transforms from left to right:</P>
     * <UL>
     *  <LI>translate(+x, +y)</LI>
     *  <LI>transform(m00, m10, m01, m11, m02, m12)</LI>
     *  <LI>translate(-x, -y)</LI>
     * </UL>.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  m00 matrix coefficient for row 0 and column 0
     * @param  m10 matrix coefficient for row 1 and column 0
     * @param  m01 matrix coefficient for row 0 and column 1
     * @param  m11 matrix coefficient for row 1 and column 1
     * @param  m02 matrix coefficient for row 0 and column 2
     * @param  m12 matrix coefficient for row 1 and column 2
     * @return the centered affine transform
     */
    public static AffineTransform centeredTransform
        (double x,   double y,
         double m00, double m10, double m01, double m11, double m02, double m12)
    {
        return compose(new AffineTransform[] {
            translate( x,  y),
            transform(m00, m10, m01, m11, m02, m12),
            translate(-x, -y)});
    }
    
    
    /**
     * <P>Returns the transform produced by composition of the transforms
     * in the given array of transforms.</P>
     *
     * <P>The composition process takes place by repeated concatenation on
     * the right of the i-th transform with the composite of the earlier
     * transforms in the array.</P>
     *
     * <P>If the array is <CODE>null</CODE> return the identity transform.</P>
     *
     * <P>If an individual transform is <CODE>null</CODE> then ignore that
     * particular transform in the composition process.</P>
     *
     * @param  transforms the array of transforms to compose
     * @return the transform produced by composition of the transforms
     */
    public static AffineTransform compose(AffineTransform[] transforms) {
        AffineTransform T = new AffineTransform();
        
        if (transforms == null)
            return T;
        
        int length = transforms.length;
        
        for (int i = 0; i < length; i++)
            if (transforms[i] != null)
                T.concatenate(transforms[i]);
        
        return T;
    }
    
    
    
    /**
     * <P>Returns the transform produced by composition of the transforms
     * M and N with M on the left and N on the right.</P>
     *
     * <P>This method is equivalent to:</P>
     *
     * <P><CODE>return compose(new AffineTransform[] { M, N });</CODE></P>
     *
     * @param  M the transform on the left
     * @param  N the transform on the right
     * @return the transform produced by composition of the transforms
     */
    public static AffineTransform compose(AffineTransform M, AffineTransform N)
    {
        return compose(new AffineTransform[] { M, N });
    }
    
    
    /**
     * Returns a random translation transform whose shift in each direction
     * is bounded in absolute value by the given value maxshift.
     *
     * @param  maxshift the bound for the translation along each axis
     * @return a random translation
     */
    public static AffineTransform randomTranslate(double maxshift) {
        maxshift = Math.abs(maxshift);
        
        double tx = MathUtilities.randomDouble(-maxshift, maxshift);
        double ty = MathUtilities.randomDouble(-maxshift, maxshift);
        
        return translate(tx, ty);
    }
    
    
    /**
     * Returns a random rotation transform about the given center (x, y)
     * whose rotation angle is bounded by the given maxangle in degrees.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  maxangle the bound for the rotation angle in degrees
     * @return a random rotation
     */
    public static AffineTransform randomRotate(double x, double y, double maxangle)
    {
        maxangle = Math.abs(maxangle);
        
        double degrees = MathUtilities.randomDouble(-maxangle, maxangle);
        
        return rotate(x, y, degrees);
    }
    
    
    /**
     * Returns a composite transform that combines a random translation with
     * a random rotation about the given center (x, y).
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  maxshift the bound for the translation along each axis
     * @param  maxangle the bound for the rotation angle in degrees
     * @return the composite of a random translation and a random rotation
     */
    public static AffineTransform randomTranslateRotate
        (double x, double y, double maxshift, double maxangle)
    {
        return compose(new AffineTransform[]
            { randomTranslate(maxshift), randomRotate(x, y, maxangle) });
    }
    
    
    /**
     * Returns a random scale with center at (x, y)
     * along the line at the given angle in degrees
     * and with scale factors within maxdelta of 1.
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  degrees the angle in degrees of the main scaling axis
     * @param  maxdelta the bound relative to 1 of the scale factors
     * @return a random scale
     */
    public static AffineTransform randomScale
        (double x, double y, double degrees, double maxdelta)
    {
        maxdelta = Math.abs(maxdelta);
        double s = MathUtilities.randomDouble(-maxdelta, maxdelta) + 1;
        double t = MathUtilities.randomDouble(-maxdelta, maxdelta) + 1;
        
        return scale(x, y, degrees, s, t);
    }
    
    /**
     * <P>Returns a transform that is a perturbation of the identity transform
     * with focus at the given center (x, y).</P>
     *
     * <P>In the special case when x = 0 and y = 0, the transform returned has
     * the form:</P>
     *
     * <CODE>
     * <P>m00 m01 m02</P>
     * <P>m10 m11 m12</P>
     * </CODE>
     *
     * <P>where the diagonal elements satisfy:</P>
     *
     * <CODE><P>abs(mii - 1) &lt= abs(maxshift)</P></CODE>
     *
     * <P>and the off-diagonal elements satisfy:</P>
     *
     * <CODE><P>abs(mij) &lt= abs(maxshift)</P></CODE>
     *
     * <P>In the general case, there is the same behavior but centered at (x, y).</P>
     *
     * @param  x the x-coordinate of the center
     * @param  y the y-coordinate of the center
     * @param  maxshift the bound on the perturbation coefficients
     * @return a random transform centered at (x, y)
     * @see    #centeredTransform
     *         (double, double, double, double, double, double, double, double)
     */
    public static AffineTransform randomCenteredTransform
        (double x, double y, double maxshift)
    {
        maxshift = Math.abs(maxshift);
        
        double m00 = MathUtilities.randomDouble(-maxshift, maxshift) + 1;
        double m10 = MathUtilities.randomDouble(-maxshift, maxshift);
        double m01 = MathUtilities.randomDouble(-maxshift, maxshift);
        double m11 = MathUtilities.randomDouble(-maxshift, maxshift) + 1;
        double m02 = MathUtilities.randomDouble(-maxshift, maxshift);
        double m12 = MathUtilities.randomDouble(-maxshift, maxshift);
        
        return centeredTransform(x, y, m00, m10, m01, m11, m02, m12);
    }
    
}
