// -*- C++ -*-
// ---------------------------------------------------------------------------
//
// This file is a part of the CLHEP - a Class Library for High Energy Physics.
//
// Hep geometrical 3D Transformation class
//
// Author: Evgeni Chernyaev <Evgueni.Tcherniaev@cern.ch>
//
//          ******************************************
//          *                                        *
//          *               Transform                *
//          *               /  / \  \                *
//          *       --------  /   \  --------        *
//          *      /         /     \         \       *
//          *   Rotate Translate  Reflect   Scale    *
//          *    / | \    / | \    / | \    / | \    *
//          *   X  Y  Z  X  Y  Z  X  Y  Z  X  Y  Z   *
//          *                                        *
//          ******************************************
//
// Identity transformation:
//   Transform3D::Identity   - global identity transformation;
//   any constructor without parameters, e.g. Transform3D();
//   m.setIdentity()            - set "m" to identity;
//
// General transformations:
//   Transform3D(m,v)         - transformation given by Rotation "m"
//                              and CLHEP::Hep3Vector "v";
//   Transform3D(a0,a1,a2, b0,b1,b2) - transformation given by initial
//                               and transformed positions of three points;
// Rotations:
//   Rotate3D(m)              - rotation given by CLHEP::HepRotation "m";
//   Rotate3D(ang,v)          - rotation through the angle "ang" around
//                              vector "v";
//   Rotate3D(ang,p1,p2)      - rotation through the angle "ang"
//                              counterclockwise around the axis given by
//                              two points p1->p2;
//   Rotate3D(a1,a2, b1,b2)   - rotation around the origin defined by initial
//                              and transformed positions of two points;
//   RotateX3D(ang)           - rotation around X-axis;
//   RotateY3D(ang)           - rotation around Y-axis;
//   RotateZ3D(ang)           - rotation around Z-axis;
//
// Translations:
//   Translate3D(v)           - translation given by CLHEP::Hep3Vector "v";
//   Translate3D(dx,dy,dz)    - translation on vector (dx,dy,dz);
//   TraslateX3D(dx)          - translation along X-axis;
//   TraslateY3D(dy)          - translation along Y-axis;
//   TraslateZ3D(dz)          - translation along Z-axis;
//
// Reflections:
//   Reflect3D(a,b,c,d)       - reflection in the plane a*x+b*y+c*z+d=0;
//   Reflect3D(normal,p)      - reflection in the plane going through "p"
//                              and whose normal is equal to "normal";
//   ReflectX3D(a)            - reflect X in the plane x=a (default a=0);
//   ReflectY3D(a)            - reflect Y in the plane y=a (default a=0);
//   ReflectZ3D(a)            - reflect Z in the plane z=a (default a=0);
//
// Scalings:
//   Scale3D(sx,sy,sz)        - general scaling with factors "sx","sy","sz"
//                                 along X, Y and Z;
//   Scale3D(s)               - scaling with constant factor "s" along all
//                                 directions;
//   ScaleX3D(sx)             - scale X;
//   ScaleY3D(sy)             - scale Y;
//   ScaleZ3D(sz)             - scale Z;
//
// Inverse transformation:
//   m.inverse() or           - returns inverse transformation;
//
// Compound transformation:
//   m3 = m2 * m1             - it is relatively slow in comparison with
//                              transformation of a vector. Use parenthesis
//                              to avoid this operation (see example below);
// Transformation of point:
//   p2 = m * p1
//
// Transformation of vector:
//   v2 = m * v1
//
// Transformation of normal:
//   n2 = m * n1
//
// The following table explains how different transformations affect
// point, vector and normal. "+" means affect, "-" means do not affect,
// "*" meas affect but in different way than "+"
//
//                     Point  Vector  Normal
//      -------------+-------+-------+-------
//       Rotation    !   +   !   +   !   +
//       Translation !   +   !   -   !   -
//       Reflection  !   +   !   +   !   *
//       Scaling     !   +   !   +   !   *
//      -------------+-------+-------+-------
//
// Example of the usage:
//
//   Transform3D m1, m2, m3;
//   HepVector3D    v2, v1(0,0,0);
//
//   m1 = Rotate3D(angle, Vector3D(1,1,1));
//   m2 = Translate3D(dx,dy,dz);
//   m3 = m1.inverse();
//
//   v2 = m3*(m2*(m1*v1));
//
// History:
// 24.09.96 E.Chernyaev - initial version
//
// 26.02.97 E.Chernyaev
// - added global Identity by request of John Allison
//   (to avoid problems with compilation on HP)
// - added getRotation and getTranslation
//
// 29.01.01 E.Chernyaev - added subscripting
// 11.06.01 E.Chernyaev - added getDecomposition

#ifndef HEP_TRANSFROM3D_H
#define HEP_TRANSFROM3D_H

#include "CLHEP/Vector/ThreeVector.h"

namespace HepGeom {

  template<class T> class Point3D;
  template<class T> class Vector3D;
  template<class T> class Normal3D;

  class Translate3D;
  class Rotate3D;
  class Scale3D;

  /**
   * Class for transformation of 3D geometrical objects.
   * It allows different translations, rotations, scalings and reflections.
   * Several specialized classes are derived from it:
   *
   * TranslateX3D, TranslateY3D, TranslateZ3D, Translate3D,<br>
   * RotateX3D,    RotateY3D,    RotateZ3D,    Rotate3D,   <br>
   * ScaleX3D,     ScaleY3D,     ScaleZ3D,     Scale3D,    <br>
   * ReflectX3D,   ReflectY3D,   ReflectZ3D,   Reflect3D.
   *
   * The idea behind these classes is to provide some additional constructors
   * for Transform3D, they normally should not be used as separate classes.
   *
   * Example:
   * @code
   *   HepGeom::Transform3D m;
   *   m = HepGeom::TranslateX3D(10.*cm);
   * @endcode
   *
   * Remark:
   * For the reason that the operator* is left associative, the notation
   * @code
   *   v2 = m3*(m2*(m1*v1));
   * @endcode
   * is much more effective then the notation
   * @code
   *   v2 = m3*m2*m1*v1;
   * @endcode
   * In the first case three operations Transform3D*Vector3D are executed,
   * in the second case two operations Transform3D*Transform3D and one
   * Transform3D*Vector3D are performed. Transform3D*Transform3D is
   * roughly three times slower than Transform3D*Vector3D.
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class Transform3D {
  protected:
    double xx_, xy_, xz_, dx_,     // 4x3  Transformation Matrix
           yx_, yy_, yz_, dy_,
           zx_, zy_, zz_, dz_;

    // Protected constructor
    Transform3D(double XX, double XY, double XZ, double DX,
		double YX, double YY, double YZ, double DY,
		double ZX, double ZY, double ZZ, double DZ)
      : xx_(XX), xy_(XY), xz_(XZ), dx_(DX),
	yx_(YX), yy_(YY), yz_(YZ), dy_(DY),
	zx_(ZX), zy_(ZY), zz_(ZZ), dz_(DZ) {}

    // Set transformation matrix
    void setTransform(double XX, double XY, double XZ, double DX,
		      double YX, double YY, double YZ, double DY,
		      double ZX, double ZY, double ZZ, double DZ) {
      xx_ = XX; xy_ = XY; xz_ = XZ; dx_ = DX;
      yx_ = YX; yy_ = YY; yz_ = YZ; dy_ = DY;
      zx_ = ZX; zy_ = ZY; zz_ = ZZ; dz_ = DZ;
    }

  public:
    /**
     * Global identity transformation. */
    DLL_API static const Transform3D Identity;

    // Helper class for implemention of C-style subscripting r[i][j]
    class Transform3D_row {
    public:
      inline Transform3D_row(const Transform3D &, int);
      inline double operator [] (int) const;
    private:
      const Transform3D & rr;
      int ii;
    };

    /**
     * Default constructor - sets the Identity transformation. */
    Transform3D()
      : xx_(1), xy_(0), xz_(0), dx_(0),
	yx_(0), yy_(1), yz_(0), dy_(0),
	zx_(0), zy_(0), zz_(1), dz_(0) {}

    /**
     * Constructor: rotation and then translation. */
    inline Transform3D(const CLHEP::HepRotation & mt, const CLHEP::Hep3Vector & v);

    /**
     * Constructor: transformation of basis (assumed - no reflection). */
    Transform3D(const Point3D<double> & fr0,
		const Point3D<double> & fr1,
		const Point3D<double> & fr2,
		const Point3D<double> & to0,
		const Point3D<double> & to1,
		const Point3D<double> & to2);

    /**
     * Copy constructor. */
    Transform3D(const Transform3D & mt) = default;

    /**
     * Move constructor. */
    Transform3D(Transform3D && mt) = default;

    /**
     * Destructor. */
    ~Transform3D() = default;

    /**
     * Assignment. */
    Transform3D & operator=(const Transform3D & mt) = default;

    /**
     * Move assignment. */
    Transform3D & operator=(Transform3D && mt) = default;

    /**
     * Returns object of the helper class for C-style subscripting r[i][j] */
    inline const Transform3D_row operator [] (int) const;

    /** Fortran-style subscripting: returns (i,j) element of the matrix. */
    double operator () (int, int) const;

    /**
     * Gets xx-element of the transformation matrix. */
    double xx() const { return xx_; }
    /**
     * Gets xy-element of the transformation matrix. */
    double xy() const { return xy_; }
    /**
     * Gets xz-element of the transformation matrix. */
    double xz() const { return xz_; }
    /**
     * Gets yx-element of the transformation matrix. */
    double yx() const { return yx_; }
    /**
     * Gets yy-element of the transformation matrix. */
    double yy() const { return yy_; }
    /**
     * Gets yz-element of the transformation matrix. */
    double yz() const { return yz_; }
    /**
     * Gets zx-element of the transformation matrix. */
    double zx() const { return zx_; }
    /**
     * Gets zy-element of the transformation matrix. */
    double zy() const { return zy_; }
    /**
     * Gets zz-element of the transformation matrix. */
    double zz() const { return zz_; }
    /**
     * Gets dx-element of the transformation matrix. */
    double dx() const { return dx_; }
    /**
     * Gets dy-element of the transformation matrix. */
    double dy() const { return dy_; }
    /**
     * Gets dz-element of the transformation matrix. */
    double dz() const { return dz_; }

    /**
     * Sets the Identity transformation. */
    void setIdentity() {
      xy_= xz_= dx_= yx_= yz_= dy_= zx_= zy_= dz_= 0; xx_= yy_= zz_= 1;
    }

    /**
     * Returns the inverse transformation. */
    Transform3D inverse() const;

    /**
     * Transformation by another Transform3D. */
    Transform3D operator*(const Transform3D & b) const;

    /**
     * Decomposition of general transformation.
     * This function gets decomposition of the transformation
     * in three consequentive specific transformations: Scale3D,
     * then Rotate3D, then Translate3, i.e.
     * @code
     *   Transform3D = Translate3D * Rotate3D * Scale3D
     * @endcode
     *
     * @param scale       output: scaling transformation;
     *                    if there was a reflection, then scale factor for
     *                    z-component (scale(2,2)) will be negative.
     * @param rotation    output: rotation transformaion.
     * @param translation output: translation transformaion.
     */
    void getDecomposition(Scale3D & scale,
			  Rotate3D & rotation,
			  Translate3D & translation) const;

    /**
     * Returns true if the difference between corresponding
     * matrix elements is less than the tolerance.
     */
    bool isNear(const Transform3D & t, double tolerance = 2.2E-14 ) const;

    /**
     * Extracts the rotation matrix.
     * This functions is obsolete - use getDecomposition() instead.
     */
    inline CLHEP::HepRotation getRotation() const;

    /**
     * Extracts the translation vector.
     * This functions is obsolete - use getDecomposition() instead.
     */
    inline CLHEP::Hep3Vector getTranslation() const;

    /**
     * Test for equality. */
    bool operator == (const Transform3D & transform) const;

    /**
     * Test for inequality. */
    bool operator != (const Transform3D & transform) const {
      return ! operator==(transform);
    }
  };

  //   R O T A T I O N S

  /**
   * Constructs a rotation transformation.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = Rotate3D(30.*deg, HepVector3D(1.,1.,1.));
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class Rotate3D : public Transform3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    Rotate3D() : Transform3D() {}

    /**
     * Constructor from CLHEP::HepRotation. */
    inline Rotate3D(const CLHEP::HepRotation &mt);

    /**
     * Constructor from angle and axis given by two points.
     * @param a  angle of rotation
     * @param p1 begin point of the axis
     * @param p2 end point of the axis
     */
    Rotate3D(double a,
	     const Point3D<double> & p1,
	     const Point3D<double> & p2);

    /**
     * Constructor from angle and axis.
     * @param a angle of rotation
     * @param v axis of rotation
     */
    inline Rotate3D(double a, const Vector3D<double> & v);

    /**
     * Constructor for rotation given by original and rotated position of
     * two points. It is assumed that there is no reflection.
     * @param fr1 original position of 1st point
     * @param fr2 original position of 2nd point
     * @param to1 rotated position of 1st point
     * @param to2 rotated position of 2nd point
     */
    inline Rotate3D(const Point3D<double> & fr1,
		    const Point3D<double> & fr2,
		    const Point3D<double> & to1,
		    const Point3D<double> & to2);
  };

  /**
   * Constructs a rotation around x-axis.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = RotateX3D(30.*deg);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class RotateX3D : public Rotate3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    RotateX3D() : Rotate3D() {}

    /**
     * Constructs a rotation around x-axis by angle a. */
    RotateX3D(double a) {
      double cosa = std::cos(a), sina = std::sin(a);
      setTransform(1,0,0,0,  0,cosa,-sina,0,  0,sina,cosa,0);
    }
  };

  /**
   * Constructs a rotation around y-axis.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = RotateY3D(30.*deg);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class RotateY3D : public Rotate3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    RotateY3D() : Rotate3D() {}

    /**
     * Constructs a rotation around y-axis by angle a. */
    RotateY3D(double a) {
      double cosa = std::cos(a), sina = std::sin(a);
      setTransform(cosa,0,sina,0,  0,1,0,0,  -sina,0,cosa,0);
    }
  };

  /**
   * Constructs a rotation around z-axis.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = RotateZ3D(30.*deg);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class RotateZ3D : public Rotate3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    RotateZ3D() : Rotate3D() {}

    /**
     * Constructs a rotation around z-axis by angle a. */
    RotateZ3D(double a) {
      double cosa = std::cos(a), sina = std::sin(a);
      setTransform(cosa,-sina,0,0,  sina,cosa,0,0,  0,0,1,0);
    }
  };

  //   T R A N S L A T I O N S

  /**
   * Constructs a translation transformation.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = Translate3D(10.,20.,30.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class Translate3D : public Transform3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    Translate3D() : Transform3D() {}

    /**
     * Constructor from CLHEP::Hep3Vector. */
    inline Translate3D(const CLHEP::Hep3Vector &v);

    /**
     * Constructor from three numbers. */
    Translate3D(double x, double y, double z)
      : Transform3D(1,0,0,x, 0,1,0,y, 0,0,1,z) {}
  };

  /**
   * Constructs a translation along x-axis.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = TranslateX3D(10.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class TranslateX3D : public Translate3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    TranslateX3D() : Translate3D() {}

    /**
     * Constructor from a number. */
    TranslateX3D(double x) : Translate3D(x, 0, 0) {}
  };

  /**
   * Constructs a translation along y-axis.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = TranslateY3D(10.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class TranslateY3D : public Translate3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    TranslateY3D() : Translate3D() {}

    /**
     * Constructor from a number. */
    TranslateY3D(double y) : Translate3D(0, y, 0) {}
  };

  /**
   * Constructs a translation along z-axis.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = TranslateZ3D(10.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class TranslateZ3D : public Translate3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    TranslateZ3D() : Translate3D() {}

    /**
     * Constructor from a number. */
    TranslateZ3D(double z) : Translate3D(0, 0, z) {}
  };

  //   R E F L E C T I O N S

  /**
   * Constructs a reflection transformation.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = Reflect3D(1.,1.,1.,0.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class Reflect3D : public Transform3D {
  protected:
    Reflect3D(double XX, double XY, double XZ, double DX,
              double YX, double YY, double YZ, double DY,
              double ZX, double ZY, double ZZ, double DZ)
      : Transform3D(XX,XY,XZ,DX, YX,YY,YZ,DY, ZX,ZY,ZZ,DZ) {}

  public:
    /**
     * Default constructor: sets the Identity transformation. */
    Reflect3D() : Transform3D() {}

    /**
     * Constructor from four numbers.
     * Sets reflection in a plane a*x+b*y+c*z+d=0
     */
    Reflect3D(double a, double b, double c, double d);

    /**
     * Constructor from a plane given by its normal and origin. */
    inline Reflect3D(const Normal3D<double> & normal,
                     const Point3D<double> & point);
  };

  /**
   * Constructs reflection in a plane x=const.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = ReflectX3D(1.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class ReflectX3D : public Reflect3D {
  public:
    /**
     * Constructor from a number. */
    ReflectX3D(double x=0) : Reflect3D(-1,0,0,x+x, 0,1,0,0, 0,0,1,0) {}
  };

  /**
   * Constructs reflection in a plane y=const.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = ReflectY3D(1.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class ReflectY3D : public Reflect3D {
  public:
    /**
     * Constructor from a number. */
    ReflectY3D(double y=0) : Reflect3D(1,0,0,0, 0,-1,0,y+y, 0,0,1,0) {}
  };

  /**
   * Constructs reflection in a plane z=const.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = ReflectZ3D(1.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class ReflectZ3D : public Reflect3D {
  public:
    /**
     *  Constructor from a number. */
    ReflectZ3D(double z=0) : Reflect3D(1,0,0,0, 0,1,0,0, 0,0,-1,z+z) {}
  };

  //   S C A L I N G S

  /**
   * Constructs a scaling transformation.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = Scale3D(2.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class Scale3D : public Transform3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    Scale3D() : Transform3D() {}

    /**
     * Constructor from three numbers - scale factors in different directions.
     */
    Scale3D(double x, double y, double z)
      : Transform3D(x,0,0,0, 0,y,0,0, 0,0,z,0) {}

    /**
     * Constructor from a number: sets uniform scaling in all directions. */
    Scale3D(double sc)
      : Transform3D(sc,0,0,0, 0,sc,0,0, 0,0,sc,0) {}
  };

  /**
   * Constructs a scaling transformation in x-direction.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = ScaleX3D(2.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class ScaleX3D : public Scale3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    ScaleX3D() : Scale3D() {}

    /**
     * Constructor from a number (scale factor in x-direction). */
    ScaleX3D(double x) : Scale3D(x, 1, 1) {}
  };

  /**
   * Constructs a scaling transformation in y-direction.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = ScaleY3D(2.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class ScaleY3D : public Scale3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    ScaleY3D() : Scale3D() {}

    /**
     * Constructor from a number (scale factor in y-direction). */
    ScaleY3D(double y) : Scale3D(1, y, 1) {}
  };

  /**
   * Constructs a scaling transformation in z-direction.
   * This class provides additional constructors for Transform3D
   * and should not be used as a separate class.
   *
   * Example of use:
   * @code
   *   Transform3D m;
   *   m = ScaleZ3D(2.);
   * @endcode
   *
   * @author <Evgueni.Tcherniaev@cern.ch>
   * @ingroup geometry
   */
  class ScaleZ3D : public Scale3D {
  public:
    /**
     * Default constructor: sets the Identity transformation. */
    ScaleZ3D() : Scale3D() {}
    /**
     * Constructor from a number (scale factor in z-direction). */
    ScaleZ3D(double z) : Scale3D(1, 1, z) {}
  };
} /* namespace HepGeom */

#include "CLHEP/Geometry/Transform3D.icc"

#endif /* HEP_TRANSFROM3D_H */
