////////////////////////////////////////////////////////////////////////////////
/** 
  \file
  \brief \ru Расширенная матрица преобразования в двумерном пространстве.
         \en The extended matrix of transformation in a two-dimensional space. \~

*/
////////////////////////////////////////////////////////////////////////////////

#ifndef __MB_MATRIX_H
#define __MB_MATRIX_H


#include <mb_cart_point.h>
#include <system_atomic.h>
#include <io_base.h>
#include <mb_homogeneous.h>


#define MATRIX_DIM_2D  3


class  MATH_CLASS MbPlacement;
class  MATH_CLASS MbHomogeneous;


template<class Transform>
void CheckOrigin  ( const Transform & trans, atomic_uint8 & flag, bool resetFlag );
template<class Transform>
void CheckRotation( const Transform & trans, atomic_uint8 & flag, bool resetFlag );


//------------------------------------------------------------------------------
/** \brief \ru Расширенная матрица преобразования в двумерном пространстве.
           \en The extended matrix of transformation in a two-dimensional space. \~
  \details \ru Расширенная матрица преобразования в двумерном пространстве. \n
    Расширенная матрица имеет размерность (3, 3) и представляет собой обычную матрицу, 
    окаймлённую снизу вектором сдвига а справа нулевым столбцом. 
    Трансформация точки p по матрице M имеет вид: r = p * M (строка координат умножается на матрицу слева).\n 
    Матрица преобразования из локальной системы координат может быть получена следующим образом:\n
    первая строка матрицы должна быть заполнена соответствующими компонентами первого вектора локальной системы,
    вторая строка матрицы должна быть заполнена соответствующими компонентами второго вектора локальной системы,
    последняя строка матрицы должна быть заполнена соответствующими координатами положения начала локальной системы.
    Матрица трансформации состоит из четырёх блоков:\n 
    | R, P |
    | V, S |
    где R - матрица вращения, тензор размерностью (2, 2),
    V - вектор сдвига, тензор (0, 2),
    P - вектор перспективы, тензор (2, 0) (всегда ноль),
    S - скаляр масштабного преобразования (единица).\n
           \en The extended matrix of transformation in a two-dimensional space. \n
    The extended matrix has dimension (3, 3) and is a regular matrix 
    bordered below by a translation vector and by the null column to the right. 
    The transformation of point "p" by the matrix "M" has the form: r = p * M (the row of coordinates is multiplied by the matrix to the left).\n 
    The transformation matrix from a local coordinate system can be obtained as follows:\n
    the first row must be filled with the corresponding components of the first vector of a local system,
    the second row must be filled with the corresponding components of the second vector of a local system,
    the last row of the matrix must be filled with the corresponding coordinates of a local system origin.
    The matrix of transformation consists of four blocks:\n 
    | R, P |
    | V, S |
    where R - is a rotation matrix, the tensor with dimension (2, 2),
    V - translation vector, tensor (0, 2),
    P - perspective vector, the tensor (2, 0) (always null),
    S - scalar of scale transformation (the unit). \ n \~ 
  \ingroup Mathematic_Base_2D
*/
// ---
class MATH_CLASS MbMatrix {
  friend MATH_FUNC (void) MulMatrix( const MbMatrix &, const MbMatrix &, MbMatrix & );
protected:
  double el[MATRIX_DIM_2D][MATRIX_DIM_2D]; ///< \ru Элементы матрицы. \en Elements of matrix. 
private:
  /**
    \brief \ru Состояние матрицы определяется установкой битовых полей.
           \en State of the matrix is defined by setting of bit fields. \~
    \details \ru Состояние матрицы определяется установкой битовых полей: \n
      MB_TRANSLATION - вектор трансляции не ноль, \n
      MB_ROTATION    - матрица вращения не единичная, \n
      MB_SCALING     - масштабный компонент не 1.0, \n
      MB_REFLECTION  - детерминант матрицы вращения отрицателен, \n
      MB_ORTOGONAL   - матрица вращения ортогональная, взводится только в случае аффинной системы координат, \n
      MB_AFFINE      - матрица вращения произвольная аффинная, \n
      MB_PERSPECTIVE - присутствует перспективное преобразование (не нулевой вектор перспективы), \n
      MB_UNSET       - битовые флаги не установлены. \n
      При изменении элементов матрицы (el[..][..]) flag должен быть сброшен в неустановленное состояние MB_UNSET, 
    при котором происходит полный пересчет состояния матрицы по требованию, для оптимизации функционала матрицы 
    рекомендуется устанавливать его в ручную соответственно тому как изменилось содержание el[..][..]. \n
      При модификации извне пользоваться методами Set* которые автоматически сбрасыват флаг в неустановленное состояние
    при получении данных матрицы из вне пользоваться методами Get*, 
    НЕ ПОЛЬЗОВАТСЯ МЕТОДАМИ Get* ДЛЯ ИЗМЕННИЯ ЭЛЕМЕНТОВ МАТРИЦЫ ПЕРЕИМЕНОВЫВАЯ const В НЕ const, 
    НЕ ДОСТУПАТЬСЯ К ДАННЫМ МАТРИЦЫ НАПРЯМУЮ В ОБХОД Get* и Set*.
             \en State of the matrix is defined by setting of bit fields: \n
      MB_TRANSLATION - translation vector is not zero \n
      MB_ROTATION    - rotation matrix is not unit \n
      MB_SCALING     - scale component is not 1.0, \n
      MB_REFLECTION  - determinant of the rotation matrix is negative, \n
      MB_ORTOGONAL   - orthogonal matrix of rotation, it is used only if coordinate system is affine \n
      MB_AFFINE      - arbitrary affine rotation matrix, \n
      MB_PERSPECTIVE - is perspective transformation (non-zero perspective vector), \n
      MB_UNSET       - bit flags are not set. \n
      If matrix elements (el[..][..]) are changed, then 'flag' must be set to unspecified MB_UNSET state, 
    for which the full recalculation of the matrix state is performed (on request) to optimize the functional of the matrix. 
    It is recommended to set it manually according to how the content of el [..] [..] is changed. \n
      To modify data of a matrix from the outside use "Set..." methods which automatically reset the flag to unspecified state,
    use "Get..." methods to get data of matrix from the outside, 
    DO NOT USE "Get..." METHODS TO MODIFY MATRIX ELEMENTS BY RENAMING const TO non-const, 
    USE ONLY Get* AND Set* TO ACCESS AND MODIFY OF MATRIX DATA. \~
  */
  mutable atomic_uint8 flag;

public: 
  static const MbMatrix identity; ///< \ru Единичная матрица, I = diag(1,1,1); \en Identity matrix, I = diag(1,1,1); 

public :
  /// \ru Конструктор по умолчанию. \en Default constructor. 
  MbMatrix() : flag( MB_UNSET ) { Init(); }
  /// \ru Конструктор копирования. \en Copy constructor. 
  MbMatrix( const MbMatrix & init ) : flag( init.flag.load() ) { ::memcpy( &el, init.el, sizeof(el) ); }
  /// \ru Конструктор по локальной системе координат. \en The constructor by placement. 
  explicit MbMatrix( const MbPlacement & place ) : flag( MB_UNSET ) { Set( place ); }
public:
  /**
    \brief \ru Конструктор по точке и нормализованному вектору.
           \en The constructor by a point and a normalized vector \~
    \details \ru Конструктор по точке и нормализованному вектору.
             \en The constructor by a point and a normalized vector \~
    \param[in] pnt - \ru Точка. Задает сдвиг относительно нуля.
                     \en A point. Sets translation relative to zero. \~
    \param[in] dir - \ru Единичный вектор. Задает поворот.
                     \en Unit vector. Sets a rotation. \~
  */
  MbMatrix( const MbCartPoint & pnt, const MbDirection & dir ) : flag( MB_UNSET ) { Init( pnt, dir ); }
  /**
    \brief \ru Конструктор матрицы масштабирования по x и y.
           \en The constructor of scaling matrix by x and y. \~
    \details \ru Конструктор матрицы масштабирования по x и y.
             \en The constructor of scaling matrix by x and y. \~
    \param[in] pnt - \ru Точка. Задает сдвиг относительно нуля.
                     \en A point. Sets translation relative to zero. \~
    \param[in] sx, sy - \ru Коэффициенты масштабирования по x и y соответственно.
                        \en Scaling coefficients by x and y, respectively. \~
  */
  MbMatrix( const MbCartPoint & pc, double sx, double sy ) : flag( MB_UNSET ) { Init( pc, sx, sy ); }
  /**
    \brief \ru Конструктор матрицы поворота.
           \en The constructor of rotation matrix. \~
    \details \ru Конструктор матрицы поворота вокруг точки pc на угол angle.
             \en The constructor of rotation matrix around the point "pc" on the angle "angle". \~
    \param[in] pc - \ru Точка.
                    \en A point. \~
    \param[in] angle - \ru Угол поворота.
                       \en A rotation angle. \~
  */
  MbMatrix( const MbCartPoint & pc, double angle ) : flag( MB_UNSET ) { Init( pc, angle ); } 
public:
  /// \ru Деструктор. \en Destructor. 
 ~MbMatrix();

public :
  /** 
    \ru \name Функции инициализации.
    \en \name Initialization functions.
    \{ */
  /// \ru Инициализировать матрицу как единичную. \en Initialize a matrix as unit one. 
  MbMatrix &  Init();
  /**
    \brief \ru Инициализировать матрицу по точке и нормализованному вектору.
           \en Initialize a matrix by a point and normalized vector. \~
    \details \ru Инициализировать матрицу по точке и нормализованному вектору.
             \en Initialize a matrix by a point and normalized vector. \~
    \param[in] pnt - \ru Точка. Задает сдвиг относительно нуля.
                     \en A point. Sets translation relative to zero. \~
    \param[in] dir - \ru Единичный вектор. Задает поворот.
                     \en Unit vector. Sets a rotation. \~
  */
  MbMatrix &  Init( const MbCartPoint & pnt, const MbDirection & dir ); 
  /**
    \brief \ru Инициализировать матрицу масштабирования по x и y.
           \en Initialize scaling matrix by x and y. \~
    \details \ru Инициализировать матрицу масштабирования по x и y.
             \en Initialize scaling matrix by x and y. \~
    \param[in] pnt - \ru Точка. Задает сдвиг относительно нуля.
                     \en A point. Sets translation relative to zero. \~
    \param[in] sx, sy - \ru Коэффициенты масштабирования по x и y соответственно.
                        \en Scaling coefficients by the x and y, respectively. \~
  */
  MbMatrix &  Init( const MbCartPoint & pc, double sx, double sy );
  /**
    \brief \ru Инициализировать матрицу поворота.
           \en Initialize a rotation matrix. \~
    \details \ru Инициализировать матрицу поворота вокруг точки pc на угол angle.
             \en Initialize a rotation matrix around the point "pc" on the angle "angle". \~
    \param[in] pc - \ru Точка.
                    \en A point. \~
    \param[in] angle - \ru Угол поворота.
                       \en A rotation angle. \~
  */
  MbMatrix &  Init( const MbCartPoint & pc, double angle );
  /**
    \brief \ru Инициализировать матрицу согласно плейсменту.
           \en Initialize a matrix according to a placement. \~
    \details \ru Инициализировать матрицу согласно плейсменту.
             \en Initialize a matrix according to a placement. \~
    \param[in] place - \ru Исходный плейсмент.
                       \en The initial placement. \~
  */
  MbMatrix &  Set ( const MbPlacement & place );
  /// \ru Задать нулевую матрицу. \en Set a zero matrix.
  MbMatrix &  SetZero();

  /** \} */
public:
  /** 
    \ru \name Функции проверки свойств матриц.
    \en \name Functions for check of matrices properties.
    \{ */

  
  /// \ru Выдать признак отрицательности детерминанта матрицы вращения. \en Get an attribute of negativity of the determinant of a rotation matrix. 
  bool        IsInvert() const { return IsLeft(); }
  /// \ru Выдать признак отрицательности детерминанта матрицы вращения. \en Get an attribute of negativity of the determinant of a rotation matrix. 
  bool        IsInvertEps( double eps = EXTENT_EPSILON ) const { return (el[0][0] * el[1][1] - el[0][1] * el[1][0]) < -eps;  }
  /// \ru Выдать признак единичности матрицы. \en Get an attribute of unit matrix. 
  bool        IsSingle() const { return (MB_IDENTITY == CheckFlag()); }
  /// \ru Выдать признак единичности матрицы с заданной точностью. \en Get an attribute of the identity matrix with a given tolerance. 
  bool        IsSingleEps( double eps = LENGTH_EPSILON ) const;

  /// \ru Выдать признак не равенства нулю вектора трансляции. \en Get an attribute of inequality to zero of translation vector. 
  bool        IsTranslation() const { return !!( CheckFlag() & MB_TRANSLATION ); }
  /// \ru Выдать признак не единичности матрицы вращения. \en Get an attribute of non-identity of a rotation matrix. 
  bool        IsRotation   () const { return !!( CheckFlag() & MB_ROTATION    ); }
  /// \ru Выдать признак лево-определенной матрицы. \en Get an attribute of the left-definite matrix. 
  bool        IsLeft       () const { return !!( CheckFlag() & MB_LEFT        ); }
  /// \ru Выдать признак ортогональности для случая аффинной матрицы. \en Get an attribute of orthogonality for the case if the matrix is affine. 
  bool        IsOrt        () const { return !!( CheckFlag() & MB_ORTOGONAL   ); }
  /// \ru Выдать признак ортогональности для матрицы вращения. \en Get an attribute of orthogonality for the case if the matrix is a rotation matrix. 
  bool        IsOrthogonal () const { CheckFlag(); return ( !(flag & MB_AFFINE) || !!(flag & MB_ORTOGONAL) ); }
  /// \ru Выдать признак того, что матрица вращения произвольная аффинная. \en Get an attribute that the rotational matrix is arbitrary and affine. 
  bool        IsAffine     () const { return !!( CheckFlag() & MB_AFFINE      ); }

  /// \ru Выдать признак не равенства 1.0 масштабного компонента. \en Get an attribute of inequality to 1.0 of a scale component. 
  bool        IsScaling    () const { return !!( CheckFlag() & MB_SCALING     ); }
  ///< \ru Выдать признак не равенства нулю вектора перспективы. \en Get an attribute of inequality to zero of perspective vector. 
  bool        IsPerspective() const { return !!( CheckFlag() & MB_PERSPECTIVE ); }
  /// \ru Проверить, что битовые флаги не установлены. \en Check whether bit flags are not set. 
  bool        IsUnSet      () const { return !!( flag & MB_UNSET ); }

  /// \ru Являются ли объекты равными? \en Determine whether an object is equal?
  bool        IsSame( const MbMatrix &, double accuracy ) const;

  /// \ru Выдать признак, что есть только перенос. \en Get an attribute that there is only translation. 
  bool        IsTranslationOnly() const;
  /// \ru Совпадают ли оси описываемой матрицей ЛСК с глобальными осями с точностью до поворотов на 90 градусов? \en Are the axis described by the matrix local system coincide with global axes up to a 90-degree rotation?
  bool        IsSubstitutionOnly( double epsilon = METRIC_EPSILON ) const;
  /**
    \brief \ru Выдать признак изотропности.
           \en Get an attribute of isotropism. \~
    \details \ru Выполняется проверка, является ли матрица ортогональной с равными по длине 
      осями X, Y (круг остается кругом).
             \en Checks whether the matrix is orthogonal with equal lengths of  
      X and Y axes (circle remains a circle). \~
    \param[out] l - \ru Длина ортов осей X, Y.
                    \en The length of the X and Y axes orts. \~
    \return \ru true, если матрица изотропна.
            \en Returns true if the matrix is isotropic. \~
  */
  bool        IsIsotropic( double & l ) const;

  /// \ru Можно ли трансформировать габарит без искажений? \en Is it possible to transform a bounding box without distortion? 
  bool        CanTransformGabarit() const;

  /** \} */
public:
  /** 
    \ru \name Функции доступа к элементам матрицы.
    \en \name Functions of access to matrix elements.
    \{ */

  /// \ru Выдать элемент матрицы. \en Get an element of the matrix. 
        double    El( size_t i, size_t j ) const { return el[i][j]; }
  /// \ru Выдать указатель на первый элемент матрицы. \en Get a pointer to the first matrix element. 
  const double *  GetEl() const { return (const double *)el; }

  /// \ru Выдать первую строку (ось X). \en Get the first row (the X-axis). 
  const MbVector &    GetAxisX() const { return (const MbVector &)*el[0]; }
  /// \ru Выдать вторую строку (ось Y). \en Get the second row (the Y-axis). 
  const MbVector &    GetAxisY() const { return (const MbVector &)*el[1]; }
  /// \ru Выдать третью строку (начало системы координат). \en Get the third row (the origin of coordinates) 
  const MbCartPoint & GetOrigin() const { return (const MbCartPoint &)*el[2]; }

  /// \ru Выдать i-ый вектор-столбец матрицы. \en Get the i-th column vector of the matrix. 
  MbVector      GetColumn    ( size_t i ) const { return MbVector( el[0][i], el[1][i] ); }
  /// \ru Выдать i-ую вектор-строку матрицы. \en Get the i-th row-vector of the matrix. 
  MbVector      GetRow       ( size_t i ) const { return MbVector( el[i][0], el[i][1] ); }
  /// \ru Выдать i-ый вектор-столбец матрицы. \en Get the i-th column vector of the matrix. 
  MbHomogeneous GetFullColumn( size_t i ) const { return MbHomogeneous( el[0][i], el[1][i], el[2][i] ); }
  /// \ru Выдать i-ую вектор-строку матрицы. \en Get the i-th row-vector of the matrix. 
  MbHomogeneous GetFullRow   ( size_t i ) const { return MbHomogeneous( el[i][0], el[i][1], el[i][2] ); }

  /** \} */
public:
  /** 
  \ru \name Функции модификации элементов матрицы.
  \en \name Functions for matrix elements modification.
  \{ */

  /**
    \brief \ru Присвоить значение элементу матрицы.
           \en Assign a value to the matrix element. \~
    \details \ru Присвоить значение элементу матрицы.
             \en Assign a value to the matrix element. \~
    \param[in] i - \ru Индекс строки.
                   \en A row index. \~
    \param[in] j - \ru Индекс столбца.
                   \en A column index. \~
    \param[in] e - \ru Исходное значение, которое надо присвоить элементу матрицы.
                   \en Initial value which to be assigned to an element of the matrix. \~
  */
  void        El( size_t i, size_t j, double e ) { flag = MB_UNSET; el[i][j] = e; }
  /**
    \brief \ru Прибавить число к элементу матрицы.
           \en Add a number to the matrix element. \~
    \details \ru Прибавить число к элементу матрицы.
             \en Add a number to the matrix element. \~
    \param[in] i - \ru Индекс строки.
                   \en A row index. \~
    \param[in] j - \ru Индекс столбца.
                   \en A column index. \~
    \param[in] e - \ru Исходное число, которое надо прибавить к элементу матрицы.
                   \en Initial number which to be added to the matrix element. \~
  */
  void        AddEl( size_t i, size_t j, double e ) { flag = MB_UNSET; el[i][j] += e; }
  /**
    \brief \ru Умножить элемент матрицы на число.
           \en Multiply the matrix element by a number. \~
    \details \ru Умножить элемент матрицы на число.
             \en Multiply the matrix element by a number. \~
    \param[in] i - \ru Индекс строки.
                   \en A row index. \~
    \param[in] j - \ru Индекс столбца.
                   \en A column index. \~
    \param[in] e - \ru Исходное число, на которое надо умножить элемент матрицы.
                   \en Initial number by which to multiply the matrix element. \~
  */
  void        MulEl( size_t i, size_t j, double e ) { flag = MB_UNSET; el[i][j] *= e; }
  /**
    \brief \ru Присвоить элементам столбца значения координат точки.
           \en Assign point coordinates values to column elements. \~
    \details \ru Присвоить элементам столбца значения координат точки.
             \en Assign point coordinates values to column elements. \~
    \param[in] icol - \ru Индекс столбца.
                      \en A column index. \~
    \param[in] column - \ru Исходная точка.
                        \en The initial point. \~
  */
  void        SetColumn( size_t icol, const MbCartPoint & column );
  /**
    \brief \ru Присвоить элементам столбца значения компонент вектора.
           \en Assign vector components values to column elements. \~
    \details \ru Присвоить элементам столбца значения компонент вектора.
             \en Assign vector components values to column elements. \~
    \param[in] icol - \ru Индекс столбца.
                      \en A column index. \~
    \param[in] column - \ru Исходный вектор.
                        \en The initial vector. \~
  */
  void        SetColumn( size_t icol, const MbVector & column );
  /**
    \brief \ru Присвоить элементам столбца значения координат однородной точки.
           \en Assign coordinates of the uniform point to column elements. \~
    \details \ru Присвоить элементам столбца значения координат однородной точки.
             \en Assign coordinates of the uniform point to column elements. \~
    \param[in] icol - \ru Индекс столбца.
                      \en A column index. \~
    \param[in] column - \ru Исходная точка.
                        \en The initial point. \~
  */
  void        SetColumn( size_t icol, const MbHomogeneous & column );
  /**
    \brief \ru Присвоить элементам строки значения компонент вектора.
           \en Assign vector components values to row elements. \~
    \details \ru Присвоить элементам строки значения компонент вектора.
             \en Assign vector components values to row elements. \~
    \param[in] irow - \ru Индекс столбца.
                      \en A column index. \~
    \param[in] row - \ru Исходный вектор.
                     \en The initial vector. \~
  */
  void        SetRow( size_t irow, const MbVector & row );
  /**
    \brief \ru Присвоить элементам строки значения координат точки.
           \en Assign point coordinates values to row elements. \~
    \details \ru Присвоить элементам строки значения координат точки.
             \en Assign point coordinates values to row elements. \~
    \param[in] irow - \ru Индекс столбца.
                      \en A column index. \~
    \param[in] row - \ru Исходная точка.
                     \en The initial point. \~
  */
  void        SetRow( size_t irow, const MbCartPoint & row );
  /**
    \brief \ru Присвоить элементам строки значения координат однородной точки.
           \en Assign coordinates of the uniform point to row elements. \~
    \details \ru Присвоить элементам строки значения координат однородной точки.
             \en Assign coordinates of the uniform point to row elements. \~
    \param[in] irow - \ru Индекс столбца.
                      \en A column index. \~
    \param[in] row - \ru Исходная точка.
                     \en The initial point. \~
  */
  void        SetRow( size_t irow, const MbHomogeneous & row );
  /// \ru Установить компоненты сдвига матрицы. \en Set components of matrix translation.  
  void        SetOrigin( const MbCartPoint & p );
  /** \} */

public:
  /** 
    \ru \name Функции умножения матриц.
    \en \name Matrices multiplication.
    \{ */

  /// \ru Домножить матрицу слева \en Multiply a matrix on the left 
  MbMatrix &  PreMultiply ( const MbMatrix & );
  /// \ru Домножить матрицу справа \en Multiply a matrix on the right. 
  MbMatrix &  PostMultiply ( const MbMatrix & );

  /** \} */
public:
  /** 
    \ru \name Функции масштабирования.
    \en \name Scaling functions.
    \{ */
  /// \ru Масштабировать по X и Y. \en Scale by X and Y. 
  MbMatrix &  Scale ( double s );
  /// \ru Масштабировать по X. \en Scale by X. 
  void        ScaleX( double s );
  /// \ru Масштабировать по Y. \en Scale by Y. 
  void        ScaleY( double s );
  /// \ru Выдать коэффициент масштабирования по X. \en Get scaling coefficient by X. 
  double      GetScaleX() const;
  /// \ru Выдать коэффициент масштабирования по Y. \en Get scaling coefficient by Y. 
  double      GetScaleY() const;
  /// \ru Проверить, различаются ли коэффициенты масштабирования по X и Y. \en Check differences of scaling coefficients (X and Y). 
  bool        IsDifferentScale() const;

  /** \} */
public:
  /** 
    \ru \name Функции преобразований матрицы: сдвиг, поворот и т.д.
    \en \name Functions of matrix transformations: translation, rotation, etc.
    \{ */

  /// \ru Сместить по X и Y. \en Shift by X and Y. 
  void        Shift ( double shift );
  /// \ru Сместить по X. \en Shift by X. 
  void        ShiftX( double shift );
  /// \ru Сместить по Y. \en Shift by Y. 
  void        ShiftY( double shift );

  /// \ru Сдвинуть на вектор (домножение справа на матрицу сдвига). \en Translate by the vector (multiply by translation matrix on the right). 
  MbMatrix &  Move( const MbVector & v ) { return Move( v.x, v.y ); }
  /// \ru Сдвинуть на заданные приращения. \en Translate by given increments. 
  MbMatrix &  Move  ( double dx, double dy );

  /**
    \brief \ru Повернуть на угол.
           \en Rotate by an angle. \~
    \details \ru Поворот совершается вокруг оси Z.
             \en Rotation is around the axis Z. \~
    \param[in] angle - \ru Угол вращения.
                       \en A rotation angle. \~
  */
  MbMatrix &  Rotate( double angle );
  /**
    \brief \ru Повернуть согласно вектору направления.
           \en Rotate according to the direction vector. \~
    \details \ru Поворот совершается вокруг оси Z, т.к. матрица двумерная. Вектор направления 
      определяет угол поворота.
             \en Rotation is around the Z axis, because matrix is two-dimensional. A direction vector 
      defines a rotation angle. \~
    \param[in] - \ru Вектор направления.
                 \en A direction vector. \~
  */
  MbMatrix &  Rotate( const MbDirection & );
  /**
    \brief \ru Повернуть вокруг точки на угол.
           \en Rotate at angle around a point. \~
    \details \ru Повернуть вокруг точки на угол.
             \en Rotate at angle around a point. \~
    \param[in] - \ru Точка.
                 \en A point. \~
    \param[in] angle - \ru Вектор направления, задающий угол вращения.
                       \en A direction vector which defines a rotation angle \~
  */
  MbMatrix &  Rotate( const MbCartPoint &, const MbDirection & angle );

  /**
    \brief \ru Преобразовать координаты.
           \en Transform coordinates. \~
    \details \ru Координаты преобразуются согласно матрице.
             \en Coordinates are transformed according to a matrix. \~
    \param[in] x, y - \ru Старые координаты по x и y.
                      \en Old coordinates by x and y. \~
    \param[in] xn, yn - \ru Преобразованные координаты.
                        \en Transformed coordinates. \~
  */
  void        TransformCoord ( double x, double y, double & xn, double & yn ) const;
  /**
    \brief \ru Преобразовать длину по направлению 0X.
           \en Transform the length in the direction of 0X. \~
    \details \ru Длина преобразуются согласно матрице. Если матрица имеет разные масштабы по X и Y,
      то масштаб по Y проигнорируется.
             \en The length is transformed according to a matrix. If the matrix has a different scales for the X and Y,
      then the scale by Y is ignored. \~
    \param[in, out] len - \ru Длина по направлению 0X.
                          \en The length in the direction of 0X. \~
  */
  void        TransformScalarX( double & len ) const;

  /**
    \brief \ru Задать матрицу преобразования симметрии (отражение).
           \en Set the matrix to symmetry transformation (reflection). \~
    \details \ru Находится матрица для преобразования симметрии относительно прямой, заданной точкой и нормалью к прямой.
             \en The matrix of symmetry transformation is found relative to a line which is defined by a point and the normal. \~
    \param[in] origin - \ru Точка прямой.
                        \en A point of a line. \~
    \param[in] normal - \ru Нормаль прямой.
                        \en The normal of a line. \~
  */
  MbMatrix &  Symmetry( const MbCartPoint & origin, const MbVector & normal );
  /// \ru Найти матрицу для преобразования симметрии относительно прямой, заданной точкой и направлением. \en Find a matrix for transformation of symmetry relative to a line given by a point and direction. 
  MbMatrix &  Symmetry( const MbCartPoint & origin, const MbDirection & direction );  

  /** \} */
public:
  /** 
    \ru \name Расчет алгебраических свойств матрицы.
    \en \name Calculation of the algebraic properties of a matrix.
    \{ */

  /// \ru Транспонировать матрицу. \en Transpose a matrix. 
  void        Adj();
  /// \ru Вычислить алгебраическое дополнение. \en Calculate the algebraic adjunct. 
  double      Delta( size_t line, size_t column, size_t dim ) const;
  /// \ru Вычислить определитель матрицы. \en Calculate the determinant of a matrix. 
  double      Det  ( size_t dim ) const;
  /// \ru Вычислить обратную матрицу. \en Calculate inverse matrix.
  void        Div  ( MbMatrix &, double eps = DOUBLE_EPSILON ) const;
  /// \ru Наибольший элемент матрицы по абсолютному значению. \en Maximal element of the matrix in absolute value.
  double      NormMax() const;

  /** \} */
public:
  /** 
    \ru \name Перегрузка алгебраических и логических операций.
    \en \name Overload of arithmetical and logical operations.
    \{ */
  /// \ru Умножить на матрицу справа. \en Multiply by the matrix on the right. 
  MbMatrix    operator *  ( const MbMatrix & ) const;
  /// \ru Домножить на матрицу справа. \en Multiply by the matrix on the right. 
  MbMatrix &  operator *= ( const MbMatrix & m ) { return PostMultiply( m ); }
  /// \ru Присвоить значение. \en Assign a value. 
  MbMatrix &  operator =  ( const MbMatrix & m ) { flag = m.flag.load(); ::memcpy( &el, m.el, sizeof(el) ); return *this; }
  /// \ru Сравнить с матрицей (точность - LENGTH_EPSILON). \en Compare with a matrix(tolerance- LENGTH_EPSILON). 
  bool        operator == ( const MbMatrix & ) const;

  /// \ru Доступ по ссылке к элементу матрицы. \en Access to a matrix element by a reference. 
        double &  operator() ( size_t i, size_t j )       { C3D_ASSERT( std_max( i, j ) < MATRIX_DIM_2D ); flag = MB_UNSET; return el[i][j]; }
  /// \ru Значение элемента матрицы. \en The value of a matrix element. 
  const double &  operator() ( size_t i, size_t j ) const { C3D_ASSERT( std_max( i, j ) < MATRIX_DIM_2D );                  return el[i][j]; }

  /** \} */

  /// \ru Выдать свойства объекта. \en Get properties of the object. 
  void        GetProperties( MbProperties & properties );
  /// \ru Записать свойства объекта. \en Set properties of the object. 
  void        SetProperties( const MbProperties & properties );

private:
  /// \ru Выставить флаги. \en Set flags. 
  uint8       ResetFlag() const;
  // Оценить флаги, если оценки не было
  uint8       CheckFlag() const { return IsUnSet() ? ResetFlag() : flag.load(); }
  // Проверить флаг смещения.
  void        CheckOrigin() const { ::CheckOrigin( *this, flag, true ); }
  // \ru Проверить флаг вращения. \en Check rotation flag. 
  void        CheckRotation() const { ::CheckRotation( *this, flag, true ); }

public:
  KNOWN_OBJECTS_RW_REF_OPERATORS_EX_BASE( MbMatrix, MATH_FUNC_EX ) // \ru Для работы со ссылками и объектами класса \en For working with references and objects of the class. 
  DECLARE_NEW_DELETE_CLASS( MbMatrix )
  DECLARE_NEW_DELETE_CLASS_EX( MbMatrix )
};


//-------------------------------------------------------------------------------
// \ru Установка флага смещения для матрицы и системы координат. \en Check translation 
/**  
\attention \ru Только для внутреннего использования.
\en For internal use only. \~
*/
// ---
template<class Transform>
void CheckOrigin( const Transform & trans, atomic_uint8 & flag, bool resetFlag )
{
  uint8 val = flag.load();
  if ( !(val & MB_UNSET) ) {
    const MbCartPoint & pOrigin = trans.GetOrigin();
    const double lengthEpsilon = LENGTH_EPSILON; 

    if ( (::fabs(pOrigin.x) > lengthEpsilon) || 
         (::fabs(pOrigin.y) > lengthEpsilon) )
      val |= MB_TRANSLATION;
    else if ( resetFlag ) {
      val &= ~MB_TRANSLATION;
    }
  }
  flag = val;
}


//-------------------------------------------------------------------------------
// \ru Установка флага вращения для матрицы и системы координат. \en Check rotation.
// ( Использование корректно только для ортонормированных СК)
/**  
  \attention \ru Только для внутреннего использования.
             \en For internal use only. \~
*/
// ---
template<class Transform>
void CheckRotation( const Transform & trans, atomic_uint8 & flag, bool resetFlag )
{
  uint8 val = flag.load();
  if ( !(val & MB_UNSET) ) {
    const MbVector & axisX = trans.GetAxisX();
    const MbVector & axisY = trans.GetAxisY();

    // Барьер нулевых элементов матрицы
    const double eps = EXTENT_EPSILON;
    if ( ::fabs(axisX.y) > eps ||
         ::fabs(axisY.x) > eps ||
         ::fabs(axisX.x - 1.0) > eps ||
         ::fabs(axisY.y - 1.0) > eps )
    {
      val |= MB_ROTATION;
    }
    else if ( resetFlag ) {
      val &= ~MB_ROTATION;
      val &= ~MB_LEFT;
      val &= ~MB_ORTOGONAL;
      val &= ~MB_AFFINE;
    }
  }
  flag = val;
}


//------------------------------------------------------------------------------
// \ru Инициализация матрицы. \en Initialize matrix. 
// ---
inline MbMatrix & MbMatrix::Init() 
{
             el[0][1] = el[0][2] = 0.0;            //-V525
  el[1][0] =            el[1][2] = 0.0;
  el[2][0] = el[2][1] =            0.0;
  el[0][0] = el[1][1] = el[2][2] = 1.0;
  flag = MB_IDENTITY;
  return *this;
}

//------------------------------------------------------------------------------
// \ru Инициализация матрицы по точке и нормализованному вектору направления \en Initialization of a matrix by a point and normalized direction vector 
// ---
inline MbMatrix & MbMatrix::Init( const MbCartPoint & pnt, const MbDirection & dir ) 
{
  Init(); // \ru Инициализация матрицы как единичной \en Initialization of unit matrix 
  Rotate( dir ); // \ru Поворот на угол dir \en Rotation by the "dir" angle. 
  Move( pnt.x, pnt.y ); // \ru Сдвиг в точку pnt \en Translation to "pnt" point 
  flag = MB_UNSET;
  return *this;
}


//-------------------------------------------------------------------------------
// \ru Выдать признак, что есть только перенос. \en Get an attribute that there is only translation. 
// ---
inline bool MbMatrix::IsTranslationOnly() const
{
  CheckFlag();
  if ( !!(flag & MB_TRANSLATION) && !(flag & MB_ROTATION) && !(flag & MB_LEFT) && !(flag & MB_SCALING) && !(flag & MB_PERSPECTIVE) )
    return true;
  return false;
}


//------------------------------------------------------------------------------
// \ru Проверка на равенство матриц. \en Check for equality of matrices.
// ---
inline bool MbMatrix::operator == ( const MbMatrix & m ) const
{
  bool bRes = true;
  for ( size_t  i = 0; (i < MATRIX_DIM_2D) && bRes; i++ ) {   
    bRes = ( (::fabs(el[i][0] - m.el[i][0]) <= LENGTH_EPSILON) &&
             (::fabs(el[i][1] - m.el[i][1]) <= LENGTH_EPSILON) &&
             (::fabs(el[i][2] - m.el[i][2]) <= LENGTH_EPSILON) );
  }
  return bRes;
}


//------------------------------------------------------------------------------
// \ru Преобразовать длину по направлению 0X                     \en Transform the length in the direction of 0X 
// \ru Если вдруг матрица начинена разными масштабами по X и Y,  \en If the matrix has a different scales by X and Y, 
// \ru То масштаб по Y игнорируется                              \en Then the scale by Y is ignored 
// ---
inline void MbMatrix::TransformScalarX( double & len ) const {
  len *= MbVector( el[0][0], el[0][1] ).Length();
}


//------------------------------------------------------------------------------
// \ru Двумерная матрица - преобразовать координаты \en Two-dimensional matrix - transform coordinates 
// ---
inline void MbMatrix::TransformCoord( double x, double y, double & xn, double & yn ) const {
  xn = x * el[0][0] + y * el[1][0] + el[2][0];
  yn = x * el[0][1] + y * el[1][1] + el[2][1];
}

//------------------------------------------------------------------------------
// \ru Являются ли объекты равными? \en Determine whether an object is equal?
// ---
inline bool MbMatrix::IsSame( const MbMatrix & m2, double accuracy ) const
{
  const MbMatrix & m1 = *this;

  bool isSame = (
    (::fabs(m1.el[0][0] - m2.el[0][0]) <= accuracy) &&
    (::fabs(m1.el[0][1] - m2.el[0][1]) <= accuracy) &&
    (::fabs(m1.el[0][2] - m2.el[0][2]) <= accuracy) &&

    (::fabs(m1.el[1][0] - m2.el[1][0]) <= accuracy) &&
    (::fabs(m1.el[1][1] - m2.el[1][1]) <= accuracy) &&
    (::fabs(m1.el[1][2] - m2.el[1][2]) <= accuracy) &&

    (::fabs(m1.el[2][0] - m2.el[2][0]) <= accuracy) &&
    (::fabs(m1.el[2][1] - m2.el[2][1]) <= accuracy) &&
    (::fabs(m1.el[2][2] - m2.el[2][2]) <= accuracy) );

  return isSame;
}


//------------------------------------------------------------------------------
/** 
  \brief \ru Перемножить матрицы.
         \en Multiply matrices. \~
  \details \ru Умножение матрицы m1 на матрицу m2 (вместо res = m1 * m2).
           \en Multiply m1 matrix by m2 matrix (instead of res = m1 * m2). \~
  \param[in] m1, m2 - \ru Исходные матрицы.
                      \en Initial matrices. \~
  \param[out] res - \ru Результирующая матрица.
                    \en The required matrix. \~
  \ingroup Mathematic_Base_2D
*/
MATH_FUNC (void) MulMatrix( const MbMatrix & m1, const MbMatrix & m2, MbMatrix & res );


#endif // __MB_MATRIX_H
