From 7235b0836a932ef0590b66f512e0e31a3a100709 Mon Sep 17 00:00:00 2001
From: Rodney <rodney.baker@gmail.com>
Date: Oct 03 2023 19:34:54 +0000
Subject: Merge pull request #5100 from blackwarthog/feature08j_assistants_squash


Improve assistants
---

diff --git a/toonz/sources/common/tgeometry/tgeometry.cpp b/toonz/sources/common/tgeometry/tgeometry.cpp
index d8bb6b6..7724948 100644
--- a/toonz/sources/common/tgeometry/tgeometry.cpp
+++ b/toonz/sources/common/tgeometry/tgeometry.cpp
@@ -61,9 +61,8 @@ TAffine TAffine::inv() const {
         (a22 == 0.0 ? std::numeric_limits<double>::max() / (1 << 16)
                     : 1.0 / a22);
     return TAffine(inv_a11, 0, -a13 * inv_a11, 0, inv_a22, -a23 * inv_a22);
-  } else if (a11 == 0.0 && a22 == 0.0) {
-    assert(a12 != 0.0);
-    assert(a21 != 0.0);
+  } else
+  if (a11 == 0.0 && a22 == 0.0) {
     double inv_a21 =
         (a21 == 0.0 ? std::numeric_limits<double>::max() / (1 << 16)
                     : 1.0 / a21);
@@ -235,8 +234,79 @@ TScale::TScale(const TPointD &center, double s) {
 
 //==================================================================================================
 
-TPoint4D TAffine4::operator*(const TPoint4D &b) const {
-  return TPoint4D(
+T3DPointD TAffine3::operator*(const T3DPointD &b) const {
+  return T3DPointD(
+    b.x*a11 + b.y*a21 + b.z*a31,
+    b.x*a12 + b.y*a22 + b.z*a32,
+    b.x*a13 + b.y*a23 + b.z*a33 );
+}
+
+TAffine3 TAffine3::operator*(const TAffine3 &b) const {
+  return TAffine3(
+    *this * b.rowX(),
+    *this * b.rowY(),
+    *this * b.rowZ() );
+}
+
+TAffine3 TAffine3::operator*=(const TAffine3 &b)
+  { return *this = *this * b; }
+
+TAffine3 TAffine3::inv() const {
+  TAffine3 r;
+  r.a11 = a22*a33 - a32*a23;
+  r.a12 = a32*a13 - a12*a33;
+  r.a13 = a12*a23 - a22*a13;
+
+  double det = r.a11*a11 + r.a12*a21 + r.a12*a31;
+  det = fabs(det) > TConsts::epsilon ? 1.0/det : 0.0;
+  r.a11 *= det;
+  r.a12 *= det;
+  r.a13 *= det;
+
+  r.a21 = (a31*a23 - a21*a33)*det;
+  r.a22 = (a11*a33 - a31*a13)*det;
+  r.a23 = (a21*a13 - a11*a23)*det;
+  r.a31 = (a21*a32 - a31*a22)*det;
+  r.a32 = (a31*a12 - a11*a32)*det;
+  r.a33 = (a11*a22 - a21*a12)*det;
+  return r;
+}
+
+TAffine TAffine3::get2d() const {
+  return TAffine(
+    a11, a21, a31,
+    a12, a22, a32 );
+}
+
+TAffine3 TAffine3::translation2d(double x, double y) {
+  TAffine3 r;
+  r.rowZ().x = x;
+  r.rowZ().y = y;
+  return r;
+}
+
+TAffine3 TAffine3::scale2d(double x, double y) {
+  TAffine3 r;
+  r.a11 = x;
+  r.a22 = y;
+  return r;
+}
+
+TAffine3 TAffine3::rotation2d(double angle) {
+  TAffine3 r;
+  double s = sin(angle);
+  double c = cos(angle);
+  r.a11 =  c;
+  r.a12 =  s;
+  r.a21 = -s;
+  r.a22 =  c;
+  return r;
+}
+
+//==================================================================================================
+
+T4DPointD TAffine4::operator*(const T4DPointD &b) const {
+  return T4DPointD(
     b.x*a11 + b.y*a21 + b.z*a31 + b.w*a41,
     b.x*a12 + b.y*a22 + b.z*a32 + b.w*a42,
     b.x*a13 + b.y*a23 + b.z*a33 + b.w*a43,
@@ -290,6 +360,13 @@ TAffine TAffine4::get2d(double z) const {
     a12, a22, z*a32 + a42 );
 }
 
+TAffine3 TAffine4::get2dPersp(double z) const {
+  return TAffine3(
+    T3DPointD( a11       , a12       , a14       ),
+    T3DPointD( a21       , a22       , a24       ),
+    T3DPointD( a31*z+a41 , a32*z+a42 , a34*z+a44 ) );
+}
+
 TAffine4 TAffine4::translation(double x, double y, double z) {
   TAffine4 r;
   r.rowW().x = x;
diff --git a/toonz/sources/include/tgeometry.h b/toonz/sources/include/tgeometry.h
index 34b9902..9542e3e 100644
--- a/toonz/sources/include/tgeometry.h
+++ b/toonz/sources/include/tgeometry.h
@@ -26,7 +26,8 @@ inline double logNormalDistribuition(double x, double x0, double w)
 
 //=============================================================================
 
-template <class T> class TPoint4T;
+template <class T> class T3DPointT;
+template <class T> class T4DPointT;
 
 /*
 * This is an example of how to use the TPointT, the TRectT and the TAffine
@@ -40,149 +41,104 @@ public:
   T x, y;
 
   inline TPointT() : x(0), y(0){};
-  inline TPointT(T _x, T _y) : x(_x), y(_y){};
-  inline TPointT(const TPointT &point) : x(point.x), y(point.y){};
-  inline explicit TPointT(const TPoint4T<T> &point);
-
-  inline TPointT &operator=(const TPointT &a) {
-    x = a.x;
-    y = a.y;
-    return *this;
-  };
-
-  inline TPointT &operator+=(const TPointT &a) {
-    x += a.x;
-    y += a.y;
-    return *this;
-  };
-  inline TPointT &operator-=(const TPointT &a) {
-    x -= a.x;
-    y -= a.y;
-    return *this;
-  };
-  inline TPointT operator+(const TPointT &a) const {
-    return TPointT(x + a.x, y + a.y);
-  };
-  inline TPointT operator-(const TPointT &a) const {
-    return TPointT(x - a.x, y - a.y);
-  };
-  inline TPointT operator-() const { return TPointT(-x, -y); };
-};
-
-template <class T>
-class TPoint4T {
-public:
-  union {
-    struct { T x, y, z, w; };
-    T a[4];
-  };
+  inline TPointT(T x, T y) : x(x), y(y){};
+  inline explicit TPointT(const T3DPointT<T> &p);
+  inline explicit TPointT(const T4DPointT<T> &p);
+
+  inline TPointT& operator+=(const TPointT &a)
+    { return x += a.x, y += a.y, *this; };
+  inline TPointT& operator-=(const TPointT &a)
+    { return x -= a.x, y -= a.y, *this; };
+  inline TPointT operator+(const TPointT &a) const
+    { return TPointT(x + a.x, y + a.y); };
+  inline TPointT operator-(const TPointT &a) const
+    { return TPointT(x - a.x, y - a.y); };
+  inline TPointT operator-() const
+    { return TPointT(-x, -y); };
+  
+  //! Scalar(dot) Product
+  inline T operator*(const TPointT &a) const
+    { return x*a.x + y*a.y; }
+
+  inline TPointT operator*=(T a)
+    { return x *= a, y *= a, *this; }
+  inline TPointT operator*(T a) const
+    { return TPointT(x*a, y*a); }
+  friend inline TPointT operator*(T a, const TPointT &b)
+    { return TPointT(a*b.x, a*b.y); }
+
+  inline TPointT operator/(T a) const
+    { return TPointT(x/a, y/a); }
+  inline TPointT operator/=(T a)
+    { return x /= a, y /= a, *this; }
+
+  inline bool operator==(const TPointT &a) const
+    { return x == a.x && y == a.y; }
+  inline bool operator!=(const TPointT &a) const
+    { return !(*this == a); }
+  
+  friend inline std::ostream &operator<<(std::ostream &out, const TPointT &p)
+    { return out << "(" << p.x << ", " << p.y << ")"; }
+  
+  /*! Rotate a point 90 degrees (counterclockwise).
+  \param p a point.
+  \return the rotated point
+  \sa rotate270 */
+  friend inline TPointT rotate90(const TPointT &p) // 90 counterclockwise
+    { return TPointT(-p.y, p.x); }
+  
+  /*! Rotate a point 270 degrees (clockwise).
+  \param p a point.
+  \return the rotated point
+  \sa rotate90 */
+  friend inline TPointT rotate270(const TPointT &p)  // 90 clockwise
+    { return TPointT(p.y, -p.x); }
+
+  //! This helper function returns the square of the absolute value of the point
+  friend inline T norm2(const TPointT &a)
+    { return a*a; }
+  
+  //! This helper function returns the square of the distance between two points
+  friend inline T tdistance2(const TPointT &a, const TPointT &b)
+    { return norm2(a - b); }
 
-  inline TPoint4T():
-    x(), y(), z(), w() { };
-  inline TPoint4T(T x, T y, T z, T w):
-    x(x), y(y), z(z), w(w) { };
-  inline explicit TPoint4T(const TPointT<T> &p, T w = (T)1):
-      x(p.x), y(p.y), z(), w(w) { };
+  //! the cross product
+  friend inline T cross(const TPointT &a, const TPointT &b)
+    { return a.x*b.y - a.y*b.x; }
 };
 
-template <class T>
-inline TPointT<T>::TPointT(const TPoint4T<T> &point) : x(point.x), y(point.y){};
-
-/*! \relates TPointT
-* Rotate a point 90 degrees (counterclockwise).
-\param p a point.
-\return the rotated point
-\sa rotate270
-*/
-template <class T>
-inline TPointT<T> rotate90(const TPointT<T> &p)  // 90 counterclockwise
-{
-  return TPointT<T>(-p.y, p.x);
-}
-/*! \relates TPointT
-* Rotate a point 270 degrees (clockwise).
-\param p a point.
-\return the rotated point
-\sa rotate90
-*/
-template <class T>
-inline TPointT<T> rotate270(const TPointT<T> &p)  // 90 clockwise
-{
-  return TPointT<T>(p.y, -p.x);
-}
-
-/*!
-\relates TPointT
-*/
-template <class T>  // Scalar(dot) Product
-inline T operator*(const TPointT<T> &a, const TPointT<T> &b) {
-  return a.x * b.x + a.y * b.y;
-}
-
-//-----------------------------------------------------------------------------
+template <>
+inline bool TPointT<double>::operator==(const TPointT<double> &a) const
+  { return tdistance2(*this, a) <= TConsts::epsilon * TConsts::epsilon; }
 
-template <class T>
-inline std::ostream &operator<<(std::ostream &out, const TPointT<T> &p) {
-  return out << "(" << p.x << ", " << p.y << ")";
-}
 
 //-----------------------------------------------------------------------------
 
 typedef TPointT<int> TPoint, TPointI;
 typedef TPointT<double> TPointD;
-typedef TPoint4T<double> TPoint4D;
 
 #ifdef _WIN32
 template class DVAPI TPointT<int>;
 template class DVAPI TPointT<double>;
 #endif
 
-template <class T>
-inline bool operator==(const TPointT<T> &p0, const TPointT<T> &p1) {
-  return p0.x == p1.x && p0.y == p1.y;
-}
-template<class T>
-inline bool operator!=(const TPointT<T> &p0, const TPointT<T> &p1) {
-  return p0.x != p1.x || p0.y != p1.y;
-}
 
 //-----------------------------------------------------------------------------
 
-//!\relates TPointT
-inline TPoint operator*(int a, const TPoint &p) {
-  return TPoint(a * p.x, a * p.y);
-}
-
-//!\relates TPointT
-inline TPoint operator*(const TPoint &p, int a) {
-  return TPoint(a * p.x, a * p.y);
-}
-
-//!\relates TPointT
-inline TPointD operator*(double a, const TPointD &p) {
-  return TPointD(a * p.x, a * p.y);
-}
-
-//!\relates TPointT
-inline TPointD operator*(const TPointD &p, double a) {
-  return TPointD(a * p.x, a * p.y);
-}
-
-//-----------------------------------------------------------------------------
 /*!
 \relates TPointT
-This helper function returns the square of the absolute value of the specified
-point (a TPointI)
+This helper function converts a TPoint (TPointT<int>) into a TPointD
 */
-inline int norm2(const TPointI &p) { return p.x * p.x + p.y * p.y; }
+inline TPointD convert(const TPoint &p)
+  { return TPointD(p.x, p.y); }
 
-//-----------------------------------------------------------------------------
 /*!
 \relates TPointT
-This helper function returns the square of the absolute value of the specified
-point (a TPointD)
+This helper function converts a TPointD (TPointT<double>) into a TPoint
 */
-inline double norm2(const TPointD &p) { return p.x * p.x + p.y * p.y; }
+inline TPoint convert(const TPointD &p)
+  { return TPoint(tround(p.x), tround(p.y)); }
+
 
 /*!
 \relates TPointT
@@ -196,8 +152,8 @@ This helper function returns the normalized version of the specified point
 */
 inline TPointD normalize(const TPointD &p) {
   double n = norm(p);
-  assert(n != 0.0);
-  return (1.0 / n) * p;
+  assert(n);
+  return p*(1/n);
 }
 
 /*!
@@ -212,56 +168,10 @@ inline TPointD normalizeOrZero(const TPointD &p) {
 
 /*!
 \relates TPointT
-This helper function converts a TPoint (TPointT<int>) into a TPointD
-*/
-inline TPointD convert(const TPoint &p) { return TPointD(p.x, p.y); }
-
-/*!
-\relates TPointT
-This helper function converts a TPointD (TPointT<double>) into a TPoint
-*/
-inline TPoint convert(const TPointD &p) {
-  return TPoint(tround(p.x), tround(p.y));
-}
-
-/*!
-\relates TPointT
-This helper function returns the square of the distance between two points
-*/
-inline double tdistance2(const TPointD &p1, const TPointD &p2) {
-  return norm2(p2 - p1);
-}
-
-inline bool operator==(const TPointD &p0, const TPointD &p1) {
-  return tdistance2(p0, p1) <= TConsts::epsilon * TConsts::epsilon;
-}
-inline bool operator!=(const TPointD &p0, const TPointD &p1) {
-  return !(p0 == p1);
-}
-
-/*!
-\relates TPointT
 This helper function returns the distance between two points
 */
-inline double tdistance(const TPointD &p1, const TPointD &p2) {
-  return norm(p2 - p1);
-}
-
-/*!
-the cross product
-\relates TPointT
-*/
-inline double cross(const TPointD &a, const TPointD &b) {
-  return a.x * b.y - a.y * b.x;
-}
-
-/*!
-the cross product
-\relates TPoint
-*/
-inline int cross(const TPoint &a, const TPoint &b) {
-  return a.x * b.y - a.y * b.x;
-}
+inline double tdistance(const TPointD &p1, const TPointD &p2)
+  { return norm(p2 - p1); }
 
 /*!
 returns the angle of the point p in polar coordinates
@@ -276,59 +186,73 @@ class DVAPI T3DPointT {
 public:
   T x, y, z;
 
-  T3DPointT() : x(0), y(0), z(0) {}
-
-  T3DPointT(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {}
-  T3DPointT(const TPointT<T> &_p, T _z) : x(_p.x), y(_p.y), z(_z) {}
-
-  T3DPointT(const T3DPointT &_p) : x(_p.x), y(_p.y), z(_p.z) {}
-
-  inline T3DPointT &operator=(const T3DPointT &a) {
-    x = a.x;
-    y = a.y;
-    z = a.z;
-    return *this;
-  }
-
-  inline T3DPointT &operator+=(const T3DPointT &a) {
-    x += a.x;
-    y += a.y;
-    z += a.z;
-    return *this;
-  }
-
-  inline T3DPointT &operator-=(const T3DPointT &a) {
-    x -= a.x;
-    y -= a.y;
-    z -= a.z;
-    return *this;
-  }
-
-  inline T3DPointT operator+(const T3DPointT &a) const {
-    return T3DPointT(x + a.x, y + a.y, z + a.z);
-  }
-
-  inline T3DPointT operator-(const T3DPointT &a) const {
-    return T3DPointT(x - a.x, y - a.y, z - a.z);
-  }
-
-  inline T3DPointT operator-() const { return T3DPointT(-x, -y, -z); }
-
-  bool operator==(const T3DPointT &p) const {
-    return x == p.x && y == p.y && z == p.z;
-  }
-
-  bool operator!=(const T3DPointT &p) const {
-    return x != p.x || y != p.y || z != p.z;
+  inline T3DPointT() : x(), y(), z() {}
+  inline T3DPointT(T x, T y, T z) : x(x), y(y), z(z) {}
+  inline T3DPointT(const TPointT<T> &p, T z) : x(p.x), y(p.y), z(z) {}
+  inline explicit T3DPointT(const T4DPointT<T> &p);
+
+  inline TPointT<T>& xy() { return *(TPointT<T>*)this; }
+  inline const TPointT<T>& xy() const { return *(const TPointT<T>*)this; }
+
+  inline T3DPointT &operator+=(const T3DPointT &a)
+    { return x += a.x, y += a.y, z += a.z, *this; }
+  inline T3DPointT &operator-=(const T3DPointT &a)
+    { return x -= a.x, y -= a.y, z -= a.z, *this; }
+  inline T3DPointT operator+(const T3DPointT &a) const
+    { return T3DPointT(x + a.x, y + a.y, z + a.z); }
+  inline T3DPointT operator-(const T3DPointT &a) const
+    { return T3DPointT(x - a.x, y - a.y, z - a.z); }
+  inline T3DPointT operator-() const
+    { return T3DPointT(-x, -y, -z); }
+
+  //! Scalar(dot) Product
+  inline T operator*(const T3DPointT &a) const
+    { return x*a.x + y*a.y + z*a.z; }
+
+  inline T3DPointT operator*=(T a)
+    { return x *= a, y *= a, z *= a, *this; }
+  inline T3DPointT operator*(T a) const
+    { return T3DPointT(x*a, y*a, z*a); }
+  friend inline T3DPointT operator*(T a, const T3DPointT &b)
+    { return T3DPointT(a*b.x, a*b.y, a*b.z); }
+
+  inline T3DPointT operator/(T a) const
+    { return T3DPointT(x/a, y/a, z/a); }
+  inline T3DPointT operator/=(T a)
+    { return x /= a, y /= a, z /= a, *this; }
+
+  inline bool operator==(const T3DPointT &a) const
+    { return x == a.x && y == a.y && z == a.z; }
+  inline bool operator!=(const T3DPointT &a) const
+    { return !(*this == a); }
+  
+  friend inline std::ostream &operator<<(std::ostream &out, const T3DPointT &p)
+    { return out << "(" << p.x << ", " << p.y << ", " << p.z << ")"; }
+  
+  //! This helper function returns the square of the absolute value of the point
+  friend inline T norm2(const T3DPointT &a)
+    { return a*a; }
+  
+  //! This helper function returns the square of the distance between two points
+  friend inline T tdistance2(const T3DPointT &a, const T3DPointT &b)
+    { return norm2(a - b); }
+
+  //! the cross product
+  friend inline T3DPointT cross(const T3DPointT &a, const T3DPointT &b) {
+    return T3DPointT( a.y*b.z - b.y*a.z,
+                      a.z*b.x - b.z*a.x,
+                      a.x*b.y - b.x*a.y);
   }
 };
 
-//=============================================================================
+template <>
+inline bool T3DPointT<double>::operator==(const T3DPointT<double> &a) const
+  { return tdistance2(*this, a) <= TConsts::epsilon * TConsts::epsilon; }
 
 template <class T>
-inline std::ostream &operator<<(std::ostream &out, const T3DPointT<T> &p) {
-  return out << "(" << p.x << ", " << p.y << ", " << p.z << ")";
-}
+inline TPointT<T>::TPointT(const T3DPointT<T> &p) : x(p.x), y(p.y) {};
+
+//=============================================================================
 
 typedef T3DPointT<int> T3DPoint, T3DPointI;
 typedef T3DPointT<double> T3DPointD;
@@ -340,72 +264,86 @@ template class DVAPI T3DPointT<double>;
 
 //-----------------------------------------------------------------------------
 
-//!\relates T3DPointT
-template <class T>
-inline T3DPointT<T> operator*(T a, const T3DPointT<T> &p) {
-  return T3DPointT<T>(a * p.x, a * p.y, a * p.z);
-}
+inline T3DPointD convert(const T3DPoint &p)
+  { return T3DPointD(p.x, p.y, p.z); }
+inline T3DPoint convert(const T3DPointD &p)
+  { return T3DPoint(tround(p.x), tround(p.y), tround(p.z)); }
 
-//!\relates TPointT
-template <class T>
-inline T3DPointT<T> operator*(const T3DPointT<T> &p, T a) {
-  return T3DPointT<T>(a * p.x, a * p.y, a * p.z);
+inline double norm(const T3DPointD &p)
+  { return std::sqrt(norm2(p)); }
+
+inline T3DPointD normalize(const T3DPointD &p) {
+  double n = norm(p);
+  assert(n);
+  return p*(1/n);
 }
 
-//-----------------------------------------------------------------------------
-/*!
-\relates TPointT
-This helper function returns the square of the absolute value of the specified
-point (a TPointI)
-*/
-template <class T>
-inline T norm2(const T3DPointT<T> &p) {
-  return p.x * p.x + p.y * p.y + p.z * p.z;
+inline T3DPointD normalizeOrZero(const T3DPointD &p) {
+  double n = norm2(p);
+  return fabs(n) > TConsts::epsilon*TConsts::epsilon ? p*(1/sqrt(n)) : T3DPointD();
 }
 
-/*!
-*/
+inline double tdistance(const T3DPointD &p1, const T3DPointD &p2)
+  { return norm(p2 - p1); }
+
+//=============================================================================
+
 template <class T>
-inline T norm(const T3DPointT<T> &p) {
-  return std::sqrt(norm2(p));
-}
+class DVAPI T4DPointT {
+public:
+  T x, y, z, w;
 
-/*!
-*/
-inline T3DPointD normalize(const T3DPointD &p) {
-  double n = norm(p);
-  assert(n != 0.0);
-  return (1.0 / n) * p;
-}
+  inline T4DPointT() : x(), y(), z(), w() {}
+  inline T4DPointT(T x, T y, T z, T w) : x(x), y(y), z(z), w(w) {}
+  inline T4DPointT(const TPointT<T> &p, T z, T w) : x(p.x), y(p.y), z(z), w(w) {}
+  inline T4DPointT(const T3DPointT<T> &p, T w) : x(p.x), y(p.y), z(p.z), w(w) {}
 
-/*!
-*/
-inline T3DPointD convert(const T3DPoint &p) { return T3DPointD(p.x, p.y, p.z); }
+  inline TPointT<T>& xy() { return *(TPointT<T>*)this; }
+  inline T3DPointT<T>& xyz() { return *(T3DPointT<T>*)this; }
 
-/*!
-*/
-inline T3DPoint convert(const T3DPointD &p) {
-  return T3DPoint(tround(p.x), tround(p.y), tround(p.z));
-}
+  inline const TPointT<T>& xy() const { return *(const TPointT<T>*)this; }
+  inline const T3DPointT<T>& xyz() const { return *(const T3DPointT<T>*)this; }
+  
+  inline bool operator==(const T4DPointT &p) const
+    { return x == p.x && y == p.y && z == p.z && w == p.w; }
+  inline bool operator!=(const T4DPointT &p) const
+    { return !(*this == p); }
+  
+  friend inline std::ostream &operator<<(std::ostream &out, const T4DPointT &p)
+    { return out << "(" << p.x << ", " << p.y << ", " << p.z << ", " << p.w << ")"; }
+};
 
-//!
-template <class T>
-inline T tdistance(const T3DPointT<T> &p1, const T3DPointT<T> &p2) {
-  return norm<T>(p2 - p1);
+template <>
+inline bool T4DPointT<double>::operator==(const T4DPointT<double> &a) const {
+  T4DPointT<double> d(x - a.x, y - a.y, z - a.z, w - a.w);
+  return d.x*d.x + d.y*d.y + d.z*d.z + d.w*d.w
+      <= TConsts::epsilon * TConsts::epsilon;
 }
 
-//!
 template <class T>
-inline T tdistance2(const T3DPointT<T> &p1, const T3DPointT<T> &p2) {
-  return norm2<T>(p2 - p1);
-}
-
-//!
+inline TPointT<T>::TPointT(const T4DPointT<T> &p) : x(p.x), y(p.y) {};
 template <class T>
-inline T3DPointT<T> cross(const T3DPointT<T> &a, const T3DPointT<T> &b) {
-  return T3DPointT<T>(a.y * b.z - b.y * a.z, a.z * b.x - b.z * a.x,
-                      a.x * b.y - b.x * a.y);
-}
+inline T3DPointT<T>::T3DPointT(const T4DPointT<T> &p) : x(p.x), y(p.y), z(p.z) {};
+
+//=============================================================================
+
+typedef T4DPointT<int> T4DPoint, T4DPointI;
+typedef T4DPointT<double> T4DPointD;
+
+#ifdef _WIN32
+template class DVAPI T4DPointT<int>;
+template class DVAPI T4DPointT<double>;
+#endif
+
+//-----------------------------------------------------------------------------
+
+//!\relates T4DPointT
+
+inline T4DPointD convert(const T4DPoint &p)
+  { return T4DPointD(p.x, p.y, p.z, p.w); }
+inline T4DPoint convert(const T4DPointD &p)
+  { return T4DPoint(tround(p.x), tround(p.y), tround(p.z), tround(p.w)); }
+
 //=============================================================================
 /*!
 TThickPoint describe a thick point.
@@ -1267,6 +1205,71 @@ inline std::ostream &operator<<(std::ostream &out, const TAffine &a) {
 
 //=============================================================================
 
+//! This class performs basic manipulations of affine transformations in 2D space.
+//! with ability of perspective transform
+//! the matrix is transposed to TAffine and equal to OpenGL cells order
+
+class DVAPI TAffine3 {
+public:
+  union {
+    struct {
+      double a11, a12, a13;
+      double a21, a22, a23;
+      double a31, a32, a33;
+    };
+    double m[3][3];
+    double a[9];
+  };
+
+  inline TAffine3():
+    a11(1.0), a12(0.0), a13(0.0),
+    a21(0.0), a22(1.0), a23(0.0),
+    a31(0.0), a32(0.0), a33(1.0) { }
+
+  inline explicit TAffine3(const TAffine &a):
+    a11(a.a11), a12(a.a21), a13(0.0),
+    a21(a.a12), a22(a.a22), a23(0.0),
+    a31(a.a13), a32(a.a23), a33(1.0) { }
+
+  inline TAffine3(
+    const T3DPointD &rowX,
+    const T3DPointD &rowY,
+    const T3DPointD &rowZ
+  ):
+    a11(rowX.x), a12(rowX.y), a13(rowX.z),
+    a21(rowY.x), a22(rowY.y), a23(rowY.z),
+    a31(rowZ.x), a32(rowZ.y), a33(rowZ.z) { }
+
+  inline T3DPointD& row(int index)
+    { return *(T3DPointD*)(m[index]); }
+  inline const T3DPointD& row(int index) const
+    { return *(const T3DPointD*)(m[index]); }
+
+  inline T3DPointD& rowX() { return row(0); }
+  inline T3DPointD& rowY() { return row(1); }
+  inline T3DPointD& rowZ() { return row(2); }
+
+  inline const T3DPointD& rowX() const { return row(0); }
+  inline const T3DPointD& rowY() const { return row(1); }
+  inline const T3DPointD& rowZ() const { return row(2); }
+
+  T3DPointD operator*(const T3DPointD &b) const;
+  TAffine3 operator*(const TAffine3 &b) const;
+  TAffine3 operator*=(const TAffine3 &b);
+
+  TAffine3 inv() const;
+
+  TAffine get2d() const;
+
+  inline static TAffine3 identity() { return TAffine3(); }
+  static TAffine3 translation2d(double x, double y);
+  static TAffine3 scale2d(double x, double y);
+  static TAffine3 rotation2d(double angle);
+};
+
+
+//=============================================================================
+
 //! This class performs basic manipulations of affine transformations in 3D space.
 //! the matrix is transposed to TAffine and equal to OpenGL
 
@@ -1296,38 +1299,39 @@ public:
     a41(a.a13), a42(a.a23), a43(0.0), a44(1.0) { }
 
   inline TAffine4(
-    const TPoint4D &rowX,
-    const TPoint4D &rowY,
-    const TPoint4D &rowZ,
-    const TPoint4D &rowW
+    const T4DPointD &rowX,
+    const T4DPointD &rowY,
+    const T4DPointD &rowZ,
+    const T4DPointD &rowW
   ):
     a11(rowX.x), a12(rowX.y), a13(rowX.z), a14(rowX.w),
     a21(rowY.x), a22(rowY.y), a23(rowY.z), a24(rowY.w),
     a31(rowZ.x), a32(rowZ.y), a33(rowZ.z), a34(rowZ.w),
     a41(rowW.x), a42(rowW.y), a43(rowW.z), a44(rowW.w) { }
 
-  inline TPoint4D& row(int index)
-    { return *(TPoint4D*)(m[index]); }
-  inline const TPoint4D& row(int index) const
-    { return *(const TPoint4D*)(m[index]); }
+  inline T4DPointD& row(int index)
+    { return *(T4DPointD*)(m[index]); }
+  inline const T4DPointD& row(int index) const
+    { return *(const T4DPointD*)(m[index]); }
 
-  inline TPoint4D& rowX() { return row(0); }
-  inline TPoint4D& rowY() { return row(1); }
-  inline TPoint4D& rowZ() { return row(2); }
-  inline TPoint4D& rowW() { return row(3); }
+  inline T4DPointD& rowX() { return row(0); }
+  inline T4DPointD& rowY() { return row(1); }
+  inline T4DPointD& rowZ() { return row(2); }
+  inline T4DPointD& rowW() { return row(3); }
 
-  inline const TPoint4D& rowX() const { return row(0); }
-  inline const TPoint4D& rowY() const { return row(1); }
-  inline const TPoint4D& rowZ() const { return row(2); }
-  inline const TPoint4D& rowW() const { return row(3); }
+  inline const T4DPointD& rowX() const { return row(0); }
+  inline const T4DPointD& rowY() const { return row(1); }
+  inline const T4DPointD& rowZ() const { return row(2); }
+  inline const T4DPointD& rowW() const { return row(3); }
 
-  TPoint4D operator*(const TPoint4D &b) const;
+  T4DPointD operator*(const T4DPointD &b) const;
   TAffine4 operator*(const TAffine4 &b) const;
   TAffine4 operator*=(const TAffine4 &b);
 
   TAffine4 inv() const;
 
   TAffine get2d(double z = 0.0) const;
+  TAffine3 get2dPersp(double z = 0.0) const;
 
   inline static TAffine4 identity() { return TAffine4(); }
   static TAffine4 translation(double x, double y, double z);
diff --git a/toonz/sources/include/tmetaimage.h b/toonz/sources/include/tmetaimage.h
index 9d360c7..5992c4f 100644
--- a/toonz/sources/include/tmetaimage.h
+++ b/toonz/sources/include/tmetaimage.h
@@ -186,7 +186,7 @@ public:
     data().touch();
   }
   void dataChanged(const TVariant &value)
-    { if (m_locks == 0) onDataChanged(value); }
+    { LockEvents lock(*this); if (m_locks == 1) onDataChanged(value); }
   void fixData()
     { LockEvents lock(*this); onFixData(); }
 };
diff --git a/toonz/sources/include/tools/assistant.h b/toonz/sources/include/tools/assistant.h
index f79377a..a8e8ea3 100644
--- a/toonz/sources/include/tools/assistant.h
+++ b/toonz/sources/include/tools/assistant.h
@@ -108,6 +108,7 @@ public:
     Circle,
     CircleFill,
     CircleCross,
+    CircleDiagonalCross,
     CircleDots,
     CircleDoubleDots,
   };
@@ -211,6 +212,8 @@ protected:
   const TStringId m_idPoints;
   const TStringId m_idX;
   const TStringId m_idY;
+  const TStringId m_idZ;
+  const TStringId m_idW;
   const TStringId m_idMagnetism;
 
   TAssistantPointMap m_points;
@@ -315,12 +318,14 @@ protected:
   double getDrawingAlpha(bool enabled = true) const;
   double getDrawingGridAlpha() const;
 
-  void drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha) const;
-  void drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha) const;
-  void drawDot(const TPointD &p, double alpha) const;
-  void drawPoint(const TAssistantPoint &point, double pixelSize) const;
-  void drawIndex(const TPointD &p, int index, bool selected, double pixelSize) const;
+  static void drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha0, double alpha1);
+  static void drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha);
+  static void drawDot(const TPointD &p, double alpha);
+  static void drawPoint(const TAssistantPoint &point, double pixelSize);
+  static void drawIndex(const TPointD &p, int index, bool selected, double pixelSize);
 
+  static inline void drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha)
+    { drawSegment(p0, p1, pixelSize, alpha, alpha); }
   inline void drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize) const
     { drawSegment(p0, p1, pixelSize, getDrawingAlpha()); }
   inline void drawDot(const TPointD &p) const
@@ -366,6 +371,7 @@ public:
   void updateTranslation() const override;
   virtual void getGuidelines(const TPointD &position, const TAffine &toTool, TGuidelineList &outGuidelines) const;
 
+  // calc W-coefficient and i-bounds for formula: x0 + 1/(i*W + 1)
   static bool calcPerspectiveStep(
     double minStep,
     double minX,
@@ -373,9 +379,9 @@ public:
     double x0,
     double x1,
     double x2,
-    double &outK,
-    double &outMin,
-    double &outMax );
+    double &outW,
+    double &outMinI,
+    double &outMaxI );
   
   static bool scanAssistants(
     TTool *tool,
diff --git a/toonz/sources/include/tools/modifiers/modifierclone.h b/toonz/sources/include/tools/modifiers/modifierclone.h
index 214e70f..763d28c 100644
--- a/toonz/sources/include/tools/modifiers/modifierclone.h
+++ b/toonz/sources/include/tools/modifiers/modifierclone.h
@@ -44,12 +44,21 @@ public:
 public:
   bool keepOriginals;
   TTrackTransformList transforms;
+  int skipFirst;
+  int skipLast;
 
-  TModifierClone(bool keepOriginals = true);
+  explicit TModifierClone(
+    bool keepOriginals = true,
+    int skipFirst = 0,
+    int skipLast = 0 );
 
   void modifyTrack(
     const TTrack &track,
     TTrackList &outTracks ) override;
+
+  void modifyTracks(
+    const TTrackList &tracks,
+    TTrackList &outTracks ) override;
 };
 
 #endif
diff --git a/toonz/sources/include/tools/modifiers/modifierjitter.h b/toonz/sources/include/tools/modifiers/modifierjitter.h
index 2f048e4..b2ee006 100644
--- a/toonz/sources/include/tools/modifiers/modifierjitter.h
+++ b/toonz/sources/include/tools/modifiers/modifierjitter.h
@@ -32,7 +32,14 @@ public:
     const unsigned int seedY;
     const double frequency;
     const double amplitude;
-    Interpolator(TTrack &track, double period, double amplitude);
+    const bool keepFirstPoint;
+    const bool keepLastPoint;
+    Interpolator(
+      TTrack &track,
+      double period,
+      double amplitude,
+      bool keepFirstPoint,
+      bool keepLastPoint );
     TTrackPoint interpolateFromOriginal(double originalIndex);
     TTrackPoint interpolate(double index) override;
   };
@@ -41,11 +48,17 @@ public:
   double period;
   double amplitude;
   int skipFirst;
+  int skipLast;
+  bool keepFirstPoint;
+  bool keepLastPoint;
   
-  TModifierJitter(
+  explicit TModifierJitter(
     double period = 30,
     double amplitude = 10,
-    int skipFirst = 0 );
+    int skipFirst = 0,
+    int skipLast = 0,
+    bool keepFirstPoint = false,
+    bool keepLastPoint = false );
 
   void modifyTrack(
     const TTrack &track,
diff --git a/toonz/sources/include/tools/replicator.h b/toonz/sources/include/tools/replicator.h
index 0556802..71c89b4 100644
--- a/toonz/sources/include/tools/replicator.h
+++ b/toonz/sources/include/tools/replicator.h
@@ -27,16 +27,31 @@ class DVAPI TReplicator : public TAssistantBase {
 public:
   typedef std::vector<TPointD> PointList;
   
+  const TStringId m_idSkipFirst;
+  const TStringId m_idSkipLast;
+
   static const int multiplierSoftLimit;
   static const int multiplierLimit;
   
   TReplicator(TMetaObject &object);
 
+  void updateTranslation() const override;
+
+  inline int getSkipFirst() const
+    { return std::max(0, (int)data()[m_idSkipFirst].getDouble()); }
+  inline int getSkipLast() const
+    { return std::max(0, (int)data()[m_idSkipLast].getDouble()); }
+  
+  inline void setSkipFirst(int x)
+    { if (getSkipFirst() != (double)x) data()[m_idSkipFirst].setDouble((double)x); }
+  inline void setSkipLast(int x)
+    { if (getSkipLast() != (double)x) data()[m_idSkipLast].setDouble((double)x); }
+  
   virtual int getMultipler() const;
   virtual void getPoints(const TAffine &toTool, PointList &points) const;
   virtual void getModifiers(const TAffine &toTool, TInputModifier::List &outModifiers) const;
   
-  static void transformPoints(const TAffine &aff, PointList &points, int count);
+  static void transformPoints(const TAffine &aff, PointList &points, int i0, int i1);
   static void drawReplicatorPoints(const TPointD *points, int count);
   
   //! return summary multiplier, or 0 is no replicators found
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 1518eee..8c77431 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -33,6 +33,9 @@ set(HEADERS
     stylepickertool.h
     toonzvectorbrushtool.h
     vectorselectiontool.h
+    assistants/assistantellipse.h
+    assistants/assistantline.h
+    assistants/assistantvanishingpoint.h
     ../include/tools/RGBpicker.h
     ../include/tools/cursormanager.h
     ../include/tools/cursors.h
@@ -141,9 +144,11 @@ set(SOURCES
     modifiers/modifiertest.cpp
     assistants/guidelineline.cpp
     assistants/guidelineellipse.cpp
-    assistants/assistantvanishingpoint.cpp
-    assistants/assistantline.cpp
     assistants/assistantellipse.cpp
+    assistants/assistantfisheye.cpp
+    assistants/assistantline.cpp
+    assistants/assistantperspective.cpp
+    assistants/assistantvanishingpoint.cpp
     assistants/replicatoraffine.cpp
     assistants/replicatorgrid.cpp
     assistants/replicatorjitter.cpp
diff --git a/toonz/sources/tnztools/assistant.cpp b/toonz/sources/tnztools/assistant.cpp
index a342f5e..40061e4 100644
--- a/toonz/sources/tnztools/assistant.cpp
+++ b/toonz/sources/tnztools/assistant.cpp
@@ -158,6 +158,8 @@ TAssistantBase::TAssistantBase(TMetaObject &object):
   m_idPoints("points"),
   m_idX("x"),
   m_idY("y"),
+  m_idZ("z"),
+  m_idW("w"),
   m_basePoint()
 {
   addProperty( new TBoolProperty(m_idEnabled.str(), getEnabled()) );
@@ -433,11 +435,16 @@ TAssistantBase::getDrawingGridAlpha() const
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistantBase::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha) const {
-  double colorBlack[4] = { 0.0, 0.0, 0.0, alpha };
-  double colorWhite[4] = { 1.0, 1.0, 1.0, alpha };
+TAssistantBase::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSize, double alpha0, double alpha1) {
+  double colors[][4] = {
+    { 1, 1, 1, alpha0 },
+    { 1, 1, 1, alpha1 },
+    { 0, 0, 0, alpha0 },
+    { 0, 0, 0, alpha1 },
+  };
   
-  if (drawFlags & DRAW_ERROR) colorBlack[0] = 1;
+  if (drawFlags & DRAW_ERROR)
+    colors[2][0] = colors[3][0] = 1;
 
   glPushAttrib(GL_ALL_ATTRIB_BITS);
   tglEnableBlending();
@@ -447,10 +454,12 @@ TAssistantBase::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSi
   if (k > TConsts::epsilon*TConsts::epsilon) {
     k = 0.5*pixelSize*lineWidthScale/sqrt(k);
     d = TPointD(-k*d.y, k*d.x);
-    glColor4dv(colorWhite);
-    tglDrawSegment(p0 - d, p1 - d);
-    glColor4dv(colorBlack);
-    tglDrawSegment(p0 + d, p1 + d);
+    glBegin(GL_LINES);
+    glColor4dv(colors[0]); tglVertex(p0 - d);
+    glColor4dv(colors[1]); tglVertex(p1 - d);
+    glColor4dv(colors[2]); tglVertex(p0 + d);
+    glColor4dv(colors[3]); tglVertex(p1 + d);
+    glEnd();
   }
   glPopAttrib();
 }
@@ -458,7 +467,7 @@ TAssistantBase::drawSegment(const TPointD &p0, const TPointD &p1, double pixelSi
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistantBase::drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha) const {
+TAssistantBase::drawMark(const TPointD &p, const TPointD &normal, double pixelSize, double alpha) {
   TPointD d = normal*5*pixelSize;
   drawSegment(p - d,p + d, pixelSize, alpha);
 }
@@ -466,7 +475,7 @@ TAssistantBase::drawMark(const TPointD &p, const TPointD &normal, double pixelSi
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistantBase::drawDot(const TPointD &p, double alpha) const {
+TAssistantBase::drawDot(const TPointD &p, double alpha) {
   double colorBlack[4] = { 0.0, 0.0, 0.0, alpha };
   double colorWhite[4] = { 1.0, 1.0, 1.0, alpha };
 
@@ -491,7 +500,7 @@ TAssistantBase::drawDot(const TPointD &p, double alpha) const {
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistantBase::drawPoint(const TAssistantPoint &point, double pixelSize) const {
+TAssistantBase::drawPoint(const TAssistantPoint &point, double pixelSize) {
   if (!point.visible) return;
 
   double radius = point.radius;
@@ -518,26 +527,31 @@ TAssistantBase::drawPoint(const TAssistantPoint &point, double pixelSize) const 
     tglDrawDisk(point.position, radius*pixelSize);
   }
 
-  TPointD crossDx(pixelSize*crossSize, 0.0);
-  TPointD crossDy(0.0, pixelSize*crossSize);
+  TPointD crossA(pixelSize*crossSize, 0.0);
+  TPointD crossB(0.0, pixelSize*crossSize);
   TPointD gridDx(pixelSize*radius, 0.0);
   TPointD gridDy(0.0, pixelSize*radius);
 
+  bool cross = point.type == TAssistantPoint::CircleCross
+            || point.type == TAssistantPoint::CircleDiagonalCross;
+  if (point.type == TAssistantPoint::CircleDiagonalCross)
+    { crossA.y = crossB.y; crossB.x = crossA.x; }
+  
   // back line
   tglEnableLineSmooth(true, 2.0*width*lineWidthScale);
   glColor4dv(colorWhite);
-  if (point.type == TAssistantPoint::CircleCross) {
-    tglDrawSegment(point.position - crossDx, point.position + crossDx);
-    tglDrawSegment(point.position - crossDy, point.position + crossDy);
+  if (cross) {
+    tglDrawSegment(point.position - crossA, point.position + crossA);
+    tglDrawSegment(point.position - crossB, point.position + crossB);
   }
   tglDrawCircle(point.position, radius*pixelSize);
 
   // front line
   glLineWidth(width * lineWidthScale);
   glColor4dv(colorBlack);
-  if (point.type == TAssistantPoint::CircleCross) {
-    tglDrawSegment(point.position - crossDx, point.position + crossDx);
-    tglDrawSegment(point.position - crossDy, point.position + crossDy);
+  if (cross) {
+    tglDrawSegment(point.position - crossA, point.position + crossA);
+    tglDrawSegment(point.position - crossB, point.position + crossB);
   }
   tglDrawCircle(point.position, radius*pixelSize);
 
@@ -568,7 +582,7 @@ TAssistantBase::drawPoint(const TAssistantPoint &point, double pixelSize) const 
 //---------------------------------------------------------------------------------------------------
 
 void
-TAssistantBase::drawIndex(const TPointD &p, int index, bool selected, double pixelSize) const {
+TAssistantBase::drawIndex(const TPointD &p, int index, bool selected, double pixelSize) {
   static const int segments[7][4] = {
     { 0, 2, 1, 2 },   // A
     { 1, 1, 1, 2 },   // B    + A +
@@ -720,28 +734,67 @@ TAssistant::calcPerspectiveStep(
   double x0,
   double x1,
   double x2,
-  double &outK,
-  double &outMin,
-  double &outMax )
+  double &outW,
+  double &outMinI,
+  double &outMaxI )
 {
-  outK = outMin = outMax = 0.0;
-
-  double dx1 = x1 - x0;
-  double dx2 = x2 - x0;
-  if (fabs(dx1) <= TConsts::epsilon) return false;
-  if (fabs(dx2) <= TConsts::epsilon) return false;
-  if ((dx1 < 0.0) != (dx2 < 0.0)) dx2 = -dx2;
-  if (fabs(dx2 - dx1) <= minStep) return false;
-  if (fabs(dx2) < fabs(dx1)) std::swap(dx1, dx2);
-
-  if (x0 <= minX + TConsts::epsilon && dx1 < 0.0) return false;
-  if (x0 >= maxX - TConsts::epsilon && dx1 > 0.0) return false;
-
-  outK = dx2/dx1;
-  double minI = log(minStep/fabs(dx1*(1.0 - 1.0/outK)))/log(outK);
-  outMin = dx1*pow(outK, floor(minI - TConsts::epsilon));
-  if (fabs(outMin) < TConsts::epsilon) return false;
-  outMax = (dx1 > 0.0 ? maxX : minX) - x0;
+  outW = outMinI = outMaxI = 0;
+  if (!(minStep > TConsts::epsilon)) return false;
+  if (!(minX + TConsts::epsilon < maxX)) return false;
+  
+  minX -= x0;
+  maxX -= x0;
+  x1   -= x0;
+  x2   -= x0;
+
+  // check and fix input
+  if (!(fabs(x1) > TConsts::epsilon)) return false;
+  if (!(fabs(x2) > TConsts::epsilon)) return false;
+  if (!(fabs(x1 - x2) > TConsts::epsilon)) return false;
+  if ((x1 < 0) != (x2 < 0)) x2 = -x2;
+  if (fabs(x2) < fabs(x1)) std::swap(x1, x2);
+
+  // check if bounds is behind the horizon
+  if (x1 < 0 && !(minX < -TConsts::epsilon)) return false;
+  if (x1 > 0 && !(maxX >  TConsts::epsilon)) return false;
+  
+  // calc W and initial bounds
+  double w = 1/x2 - 1/x1;
+  double d = sqrt(fabs(w/minStep));
+  if (x1 < 0) d = -d;
+  double i0 = (d - 1)/w;
+  double i1 = -1/w;
+  
+  // min/max bounds
+  if (x1 < 0) {
+    if (maxX < -TConsts::epsilon)
+      i0 = std::max( i0, (1 - maxX)/(maxX*w) );
+    i1 = std::min( i1, (1 - minX)/(minX*w) );
+  } else {
+    if (minX > TConsts::epsilon)
+      i0 = std::max( i0, (1 - minX)/(minX*w) );
+    i1 = std::min( i1, (1 - maxX)/(maxX*w) );
+  }
+
+  // i must be iterable (i < i + 1)
+  const double bound = 1e10;
+  i0 = i0 > -bound ? (i0 < bound ? i0 : bound) : -bound;
+  i1 = i1 > -bound ? (i1 < bound ? i1 : bound) : -bound;
+  
+  // tune beginning of grid to place it axact to grid points
+  // must be: i0 + integer == ig
+  double ig = (1 - x1)/(x1*w);
+  double frac = ig - floor(ig);
+  i0 = ceil(i0 - frac + TConsts::epsilon) + frac;
+  i1 -= TConsts::epsilon;
+  
+  // restrict count
+  if (i1 - i0 > 10000) return false;
+  if (!(i0 < i1)) return false;
+  
+  outW = w;
+  outMinI = i0;
+  outMaxI = i1;
   return true;
 }
 
diff --git a/toonz/sources/tnztools/assistants/assistantellipse.cpp b/toonz/sources/tnztools/assistants/assistantellipse.cpp
index 29e2060..dbca015 100644
--- a/toonz/sources/tnztools/assistants/assistantellipse.cpp
+++ b/toonz/sources/tnztools/assistants/assistantellipse.cpp
@@ -1,528 +1,666 @@
 
 
 // TnzTools includes
-#include <tools/assistant.h>
-#include <tools/assistants/guidelineline.h>
-#include <tools/assistants/guidelineellipse.h>
-
-// TnzCore includes
-#include <tgl.h>
-
-// std includes
-#include <limits>
+#include "assistantellipse.h"
 
 
 //*****************************************************************************************
 //    TAssistantEllipse implementation
 //*****************************************************************************************
 
-class TAssistantEllipse final : public TAssistant {
-  Q_DECLARE_TR_FUNCTIONS(TAssistantEllipse)
-public:
-  const TStringId m_idRestricktA;
-  const TStringId m_idRestricktB;
-  const TStringId m_idRepeat;
-  const TStringId m_idGrid;
-  const TStringId m_idPerspective;
-
-protected:
-  TAssistantPoint &m_center;
-  TAssistantPoint &m_a;
-  TAssistantPoint &m_b;
-  TAssistantPoint &m_grid0;
-  TAssistantPoint &m_grid1;
-
-public:
-  TAssistantEllipse(TMetaObject &object):
-    TAssistant(object),
-    m_idRestricktA("restrictA"),
-    m_idRestricktB("restrictB"),
-    m_idRepeat("repeat"),
-    m_idGrid("grid"),
-    m_idPerspective("perspective"),
-    m_center( addPoint("center", TAssistantPoint::CircleCross) ),
-    m_a( addPoint("a", TAssistantPoint::CircleFill, TPointD(100.0, 0.0)) ),
-    m_b( addPoint("b", TAssistantPoint::Circle,     TPointD(0.0,  50.0)) ),
-    m_grid0( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD(  0.0,-50.0)) ),
-    m_grid1( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD( 25.0,-75.0)) )
-  {
-    addProperty( new TBoolProperty(m_idRestricktA.str(), getRestrictA()) );
-    addProperty( new TBoolProperty(m_idRestricktB.str(), getRestrictB()) );
-    addProperty( new TBoolProperty(m_idRepeat.str(), getRepeat()) );
-    addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
-    addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) );
-  }
-
-  static QString getLocalName()
-    { return tr("Ellipse"); }
-
-  void updateTranslation() const override {
-    TAssistant::updateTranslation();
-    setTranslation(m_idRestricktA, tr("Restrict A"));
-    setTranslation(m_idRestricktB, tr("Restrict B"));
-    setTranslation(m_idRepeat, tr("Repeat"));
-    setTranslation(m_idGrid, tr("Grid"));
-    setTranslation(m_idPerspective, tr("Perspective"));
-  }
-
-  inline bool getRestrictA() const
-    { return data()[m_idRestricktA].getBool(); }
-  inline bool getRestrictB() const
-    { return data()[m_idRestricktB].getBool(); }
-  inline bool getRepeat() const
-    { return data()[m_idRepeat].getBool(); }
-  inline bool getGrid() const
-    { return data()[m_idGrid].getBool(); }
-  inline bool getPerspective() const
-    { return data()[m_idPerspective].getBool(); }
-
-  void onDataChanged(const TVariant &value) override {
-    TAssistant::onDataChanged(value);
-    m_grid0.visible = m_grid1.visible = getGrid();
+TAssistantEllipse::TAssistantEllipse(TMetaObject &object):
+  TAssistant(object),
+  m_idCircle("circle"),
+  m_idRestrictA("restrictA"),
+  m_idRestrictB("restrictB"),
+  m_idRepeat("repeat"),
+  m_idGrid("grid"),
+  m_idPerspective("perspective"),
+  m_idPerspectiveDepth("perspectiveDepth"),
+  m_center( addPoint("center", TAssistantPoint::CircleCross) ),
+  m_a( addPoint("a", TAssistantPoint::CircleFill, TPointD(150,   0)) ),
+  m_b( addPoint("b", TAssistantPoint::Circle,     TPointD(  0, 100)) ),
+  m_grid0( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD( 10, -30)) ),
+  m_grid1( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD( 60, -60)) )
+{
+  addProperty( new TBoolProperty(m_idCircle.str(), getCircle()) );
+  addProperty( new TBoolProperty(m_idRestrictA.str(), getRestrictA()) );
+  addProperty( new TBoolProperty(m_idRestrictB.str(), getRestrictB()) );
+  addProperty( new TBoolProperty(m_idRepeat.str(), getRepeat()) );
+  addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
+  addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) );
+  addProperty( new TBoolProperty(m_idPerspectiveDepth.str(), getPerspectiveDepth()) );
+}
+
+
+QString TAssistantEllipse::getLocalName()
+  { return tr("Ellipse"); }
+
+
+void TAssistantEllipse::updateTranslation() const {
+  TAssistant::updateTranslation();
+  setTranslation(m_idCircle, tr("Circle"));
+  setTranslation(m_idRestrictA, tr("Restrict A"));
+  setTranslation(m_idRestrictB, tr("Restrict B"));
+  setTranslation(m_idRepeat, tr("Repeat"));
+  setTranslation(m_idGrid, tr("Grid"));
+  setTranslation(m_idPerspective, tr("Perspective"));
+  setTranslation(m_idPerspectiveDepth, tr("Depth"));
+}
+
+
+void TAssistantEllipse::onDataChanged(const TVariant &value) {
+  TAssistant::onDataChanged(value);
+  m_grid0.visible = m_grid1.visible = getGrid();
+  if (getCircle() == m_b.visible) {
+    m_b.visible = !getCircle();
+    if (!m_b.visible)
+      fixBAndGrid(m_center.position, m_a.position, m_b.position);
   }
-
-private:
-  void fixBAndGgid1(const TPointD &previousCenter, const TPointD &previousA) {
-    TPointD dx = previousA - previousCenter;
-    double l = norm2(dx);
-    if (l <= TConsts::epsilon*TConsts::epsilon) return;
-    dx = dx*(1.0/sqrt(l));
-    TPointD dy(-dx.y, dx.x);
-
-    double r2 = dy*(m_b.position - m_center.position);
-
-    TPointD g1 = m_grid1.position - m_grid0.position;
-    g1 = TPointD(dx*g1, dy*g1);
-
-    dx = m_a.position - m_center.position;
-    l = norm2(dx);
-    if (l <= TConsts::epsilon*TConsts::epsilon) return;
-    dx = dx*(1.0/sqrt(l));
-    dy = TPointD(-dx.y, dx.x);
-
-    m_grid1.position = m_grid0.position + dx*g1.x + dy*g1.y;
-    m_b.position = m_center.position + dy*r2;
+}
+
+
+void TAssistantEllipse::fixBAndGrid(
+  TPointD prevCenter,
+  TPointD prevA,
+  TPointD prevB )
+{
+  const TPointD &center = m_center.position;
+  TPointD da0 = prevA - prevCenter;
+  TPointD da1 = m_a.position - center;
+  double la0 = norm2(da0);
+  double la1 = norm2(da1);
+  if (!(la0 > TConsts::epsilon) || !(la1 > TConsts::epsilon))
+    return;
+  
+  TPointD db = m_b.position - center;
+  TPointD dp0 = TPointD(-da0.y, da0.x);
+  TPointD dp1 = TPointD(-da1.y, da1.x);
+  if (getCircle()) {
+    m_b.position = center + (db*dp0 < 0 ? -dp1 : dp1);
+  } else {
+    m_b.position = db*dp0/la0*dp1 + center;
   }
 
-public:
-  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
-    TPointD previousCenter = m_center.position;
-    TPointD previousA = m_a.position;
-    point.position = position;
-    if (&point == &m_center) {
-      m_a.position += m_center.position - previousCenter;
-      m_b.position += m_center.position - previousCenter;
-    } else
-    if (&point == &m_a || &point == &m_b)
-      fixBAndGgid1(previousCenter, previousA);
+  TPointD db0 = prevB - prevCenter;
+  TPointD db1 = m_b.position - center;
+  double lb0 = norm2(db0);
+  double lb1 = norm2(db1);
+  if (!(lb0 > TConsts::epsilon) || !(lb1 > TConsts::epsilon))
+    return;
+
+  TPointD dg0 = m_grid0.position - center;
+  TPointD dg1 = m_grid1.position - center;
+  m_grid0.position = dg0*da0/la0*da1 + dg0*db0/lb0*db1 + center;
+  m_grid1.position = dg1*da0/la0*da1 + dg1*db0/lb0*db1 + center;
+}
+
+
+void TAssistantEllipse::onMovePoint(TAssistantPoint &point, const TPointD &position) {
+  TPointD prevCenter = m_center.position;
+  TPointD prevA = m_a.position;
+  TPointD prevB = m_b.position;
+  point.position = position;
+  if (&point == &m_center) {
+    TPointD d = m_center.position - prevCenter;
+    m_a.position += d;
+    m_b.position += d;
+    m_grid0.position += d;
+    m_grid1.position += d;
+  } else
+  if (&point == &m_a || &point == &m_b) {
+    fixBAndGrid(prevCenter, prevA, prevB);
   }
-
-  TAffine calcEllipseMatrix() const {
-    TPointD da = m_a.position - m_center.position;
-    TPointD db = m_b.position - m_center.position;
-    double r1 = norm(da);
-    if (r1 <= TConsts::epsilon) return TAffine::zero();
-    double r2 = fabs( (rotate90(da)*db)*(1.0/r1) );
-    if (r2 <= TConsts::epsilon) return TAffine::zero();
-    return TAffine::translation(m_center.position)
-         * TAffine::rotation(atan(da))
-         * TAffine::scale(r1, r2);
+}
+
+
+TAffine TAssistantEllipse::calcEllipseMatrix() const {
+  TPointD da = m_a.position - m_center.position;
+  TPointD db = m_b.position - m_center.position;
+  double r1 = norm(da);
+  if (r1 <= TConsts::epsilon) return TAffine::zero();
+  double r2 = fabs( (rotate90(da)*db)*(1.0/r1) );
+  if (r2 <= TConsts::epsilon) return TAffine::zero();
+  return TAffine::translation(m_center.position)
+        * TAffine::rotation(atan(da))
+        * TAffine::scale(r1, r2);
+}
+
+
+void TAssistantEllipse::getGuidelines(
+  const TPointD &position,
+  const TAffine &toTool,
+  TGuidelineList &outGuidelines ) const
+{
+  bool restrictA = getRestrictA();
+  bool restrictB = getRestrictB();
+  bool repeat = getRepeat();
+
+  TAffine matrix = calcEllipseMatrix();
+  if (matrix.isZero()) return;
+  if (!restrictA && restrictB) {
+    std::swap(matrix.a11, matrix.a12);
+    std::swap(matrix.a21, matrix.a22);
+    std::swap(restrictA, restrictB);
   }
 
-  void getGuidelines(
-    const TPointD &position,
-    const TAffine &toTool,
-    TGuidelineList &outGuidelines ) const override
-  {
-    bool restrictA = getRestrictA();
-    bool restrictB = getRestrictB();
-    bool repeat = getRepeat();
-
-    TAffine matrix = calcEllipseMatrix();
-    if (matrix.isZero()) return;
-    if (!restrictA && restrictB) {
-      std::swap(matrix.a11, matrix.a12);
-      std::swap(matrix.a21, matrix.a22);
-      std::swap(restrictA, restrictB);
+  matrix = toTool*matrix;
+  TAffine matrixInv = matrix.inv();
+
+  if (restrictA && restrictB) {
+    // ellipse
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineEllipse(
+        getEnabled(),
+        getMagnetism(),
+        matrix,
+        matrixInv )));
+  } else
+  if (!restrictA && !restrictB) {
+    // scaled ellipse
+    TPointD p = matrixInv*position;
+    double l = norm(p);
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineEllipse(
+        getEnabled(),
+        getMagnetism(),
+        matrix * TAffine::scale(l) )));
+  } else { // restrictA
+    TPointD p = matrixInv*position;
+    if (repeat) {
+      double ox = round(0.5*p.x)*2.0;
+      p.x -= ox;
+      matrix *= TAffine::translation(ox, 0.0);
     }
 
-    matrix = toTool*matrix;
-    TAffine matrixInv = matrix.inv();
-
-    if (restrictA && restrictB) {
-      // ellipse
+    // scale by Y
+    if (p.x <= TConsts::epsilon - 1.0) {
+      // line x = -1
       outGuidelines.push_back(TGuidelineP(
-        new TGuidelineEllipse(
+        new TGuidelineInfiniteLine(
           getEnabled(),
           getMagnetism(),
-          matrix,
-          matrixInv )));
+          matrix*TPointD(-1.0, 0.0),
+          matrix*TPointD(-1.0, 1.0) )));
     } else
-    if (!restrictA && !restrictB) {
-      // scaled ellipse
-      TPointD p = matrixInv*position;
-      double l = norm(p);
+    if (p.x >= 1.0 - TConsts::epsilon) {
+      // line x = 1
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineInfiniteLine(
+          getEnabled(),
+          getMagnetism(),
+          matrix*TPointD(1.0, 0.0),
+          matrix*TPointD(1.0, 1.0) )));
+    } else {
+      // ellipse scaled by Y
+      double k = fabs(p.y/sqrt(1.0 - p.x*p.x));
       outGuidelines.push_back(TGuidelineP(
         new TGuidelineEllipse(
           getEnabled(),
           getMagnetism(),
-          matrix * TAffine::scale(l) )));
-    } else { // restrictA
-      TPointD p = matrixInv*position;
-      if (repeat) {
-        double ox = round(0.5*p.x)*2.0;
-        p.x -= ox;
-        matrix *= TAffine::translation(ox, 0.0);
-      }
-
-      // scale by Y
-      if (p.x <= TConsts::epsilon - 1.0) {
-        // line x = -1
-        outGuidelines.push_back(TGuidelineP(
-          new TGuidelineInfiniteLine(
-            getEnabled(),
-            getMagnetism(),
-            matrix*TPointD(-1.0, 0.0),
-            matrix*TPointD(-1.0, 1.0) )));
-      } else
-      if (p.x >= 1.0 - TConsts::epsilon) {
-        // line x = 1
-        outGuidelines.push_back(TGuidelineP(
-          new TGuidelineInfiniteLine(
-            getEnabled(),
-            getMagnetism(),
-            matrix*TPointD(1.0, 0.0),
-            matrix*TPointD(1.0, 1.0) )));
-      } else {
-        // ellipse scaled by Y
-        double k = fabs(p.y/sqrt(1.0 - p.x*p.x));
-        outGuidelines.push_back(TGuidelineP(
-          new TGuidelineEllipse(
-            getEnabled(),
-            getMagnetism(),
-            matrix * TAffine::scale(1.0, k) )));
-      }
+          matrix * TAffine::scale(1.0, k) )));
     }
   }
-
-private:
-  void drawEllipseRanges(
-    const TAngleRangeSet &ranges,
-    const TAffine &ellipseMatrix,
-    const TAffine &screenMatrixInv,
-    double pixelSize,
-    double alpha ) const
-  {
-    assert(ranges.check());
-    TAngleRangeSet actualRanges(ranges);
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    if (!TGuidelineEllipse::truncateEllipse(actualRanges, ellipseMatrix.inv()*screenMatrixInv, oneBox))
-      return;
-    assert(actualRanges.check());
-
-    int segments = TGuidelineEllipse::calcSegmentsCount(ellipseMatrix, pixelSize);
-    double da = M_2PI/segments;
-    double s = sin(da);
-    double c = cos(da);
-
-    for(TAngleRangeSet::Iterator i(actualRanges); i; ++i) {
-      double a0 = i.d0();
-      double a1 = i.d1greater();
-      int cnt = (int)floor((a1 - a0)/da);
-      TPointD r(cos(a0), sin(a0));
-      TPointD p0 = ellipseMatrix*r;
-      for(int j = 0; j < cnt; ++j) {
-        r = TPointD(r.x*c - r.y*s, r.y*c + r.x*s);
-        TPointD p1 = ellipseMatrix*r;
-        drawSegment(p0, p1, pixelSize, alpha);
-        p0 = p1;
-      }
-      drawSegment(p0, ellipseMatrix*TPointD(cos(a1), sin(a1)), pixelSize, alpha);
+}
+
+
+void TAssistantEllipse::drawEllipseRanges(
+  const TAngleRangeSet &ranges,
+  const TAffine &ellipseMatrix,
+  const TAffine &screenMatrixInv,
+  double pixelSize,
+  double alpha )
+{
+  assert(ranges.check());
+  TAngleRangeSet actualRanges(ranges);
+  const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+  if (!TGuidelineEllipse::truncateEllipse(actualRanges, ellipseMatrix.inv()*screenMatrixInv, oneBox))
+    return;
+  assert(actualRanges.check());
+
+  int segments = TGuidelineEllipse::calcSegmentsCount(ellipseMatrix, pixelSize);
+  double da = M_2PI/segments;
+  double s = sin(da);
+  double c = cos(da);
+
+  for(TAngleRangeSet::Iterator i(actualRanges); i; ++i) {
+    double a0 = i.d0();
+    double a1 = i.d1greater();
+    int cnt = (int)floor((a1 - a0)/da);
+    TPointD r(cos(a0), sin(a0));
+    TPointD p0 = ellipseMatrix*r;
+    for(int j = 0; j < cnt; ++j) {
+      r = TPointD(r.x*c - r.y*s, r.y*c + r.x*s);
+      TPointD p1 = ellipseMatrix*r;
+      drawSegment(p0, p1, pixelSize, alpha);
+      p0 = p1;
     }
+    drawSegment(p0, ellipseMatrix*TPointD(cos(a1), sin(a1)), pixelSize, alpha);
   }
-
-  void drawEllipse(
-    const TAffine &ellipseMatrix,
-    const TAffine &screenMatrixInv,
-    double pixelSize,
-    double alpha ) const
-      { drawEllipseRanges(TAngleRangeSet(true), ellipseMatrix, screenMatrixInv, pixelSize, alpha); }
-
-  void drawRuler(const TAffine &ellipseMatrix, double pixelSize) const {
-    double minStep = 10.0*pixelSize;
-    double alpha = getDrawingAlpha();
-
-    TAffine em = ellipseMatrix;
-    TAffine ellipseMatrixInv = ellipseMatrix.inv();
-    TPointD g0 = ellipseMatrixInv*m_grid0.position;
-    TPointD g1 = ellipseMatrixInv*m_grid1.position;
-    if (norm2(g0) <= TConsts::epsilon*TConsts::epsilon) return;
-    if (norm2(g1) <= TConsts::epsilon*TConsts::epsilon) return;
-    double ga0 = atan(g0);
-    double ga1 = atan(g1);
-
-    // x and y radiuses
-    TPointD r( norm2(TPointD(em.a11, em.a21)), norm2(TPointD(em.a12, em.a22)) );
-    double avgR = 0.5*(r.x + r.y);
-    if (avgR <= TConsts::epsilon*TConsts::epsilon) return;
-    avgR = sqrt(avgR);
-    double actualMinStep = minStep/avgR;
-    r.x = sqrt(r.x);
-    r.y = sqrt(r.y);
-    
-    // remove radiuses from ellipse matrix
-    double rkx = r.x > TConsts::epsilon ? 1.0/r.x : 0.0;
-    double rky = r.y > TConsts::epsilon ? 1.0/r.y : 0.0;
-    em.a11 *= rkx; em.a21 *= rkx;
-    em.a12 *= rky; em.a22 *= rky;
-    
-    if (getPerspective()) {
-      // draw perspective
-      if (ga0 < 0.0) { if (ga1 > 0.0) ga1 -= M_2PI; }
-                else { if (ga1 < 0.0) ga1 += M_2PI; }
-      double k = 0.0, begin = 0.0, end = 0.0;
-      if (!calcPerspectiveStep(actualMinStep, 0.0, M_2PI, 0.0, fabs(ga0), ga1, k, begin, end)) return;
-      for(double a = begin; fabs(a) < fabs(end); a *= k) {
-        TPointD p( cos(a), (ga0 < 0.0 ? -1.0 : 1.0)*sin(a) );
-        TPointD n( p.x*r.y, p.y*r.x ); // perp to allipse
-        double nl2 = norm2(n);
-        if (nl2 > TConsts::epsilon*TConsts::epsilon) {
-          p.x *= r.x;
-          p.y *= r.y;
-          n = n*(1.0/sqrt(nl2));
-          drawMark(em*p, em.transformDirection(n), pixelSize, alpha);
-        }
+}
+
+
+void TAssistantEllipse::drawRuler(
+  const TAffine &ellipseMatrix,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  bool perspective,
+  double alpha
+) {
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = (perspective ? 5 : 10)*pixelSize;
+
+  TAffine em = ellipseMatrix;
+  TAffine ellipseMatrixInv = ellipseMatrix.inv();
+  TPointD g0 = ellipseMatrixInv * grid0;
+  TPointD g1 = ellipseMatrixInv * grid1;
+  if (norm2(g0) <= TConsts::epsilon*TConsts::epsilon) return;
+  if (norm2(g1) <= TConsts::epsilon*TConsts::epsilon) return;
+  double ga0 = atan(g0);
+  double ga1 = atan(g1);
+
+  // x and y radiuses
+  TPointD r( norm2(TPointD(em.a11, em.a21)), norm2(TPointD(em.a12, em.a22)) );
+  double avgR = 0.5*(r.x + r.y);
+  if (avgR <= TConsts::epsilon*TConsts::epsilon) return;
+  avgR = sqrt(avgR);
+  double actualMinStep = minStep/avgR;
+  r.x = sqrt(r.x);
+  r.y = sqrt(r.y);
+  
+  // remove radiuses from ellipse matrix
+  double rkx = r.x > TConsts::epsilon ? 1.0/r.x : 0.0;
+  double rky = r.y > TConsts::epsilon ? 1.0/r.y : 0.0;
+  em.a11 *= rkx; em.a21 *= rkx;
+  em.a12 *= rky; em.a22 *= rky;
+  
+  if (perspective) {
+    // draw perspective
+    if (ga0 < 0.0) { if (ga1 > 0.0) ga1 -= M_2PI; }
+              else { if (ga1 < 0.0) ga1 += M_2PI; }
+    double w, i0, i1;
+    double bound0 = ga0 < 0 ? -M_2PI : 0;
+    double bound1 = ga0 < 0 ? 0 : M_2PI;
+    if (!calcPerspectiveStep(actualMinStep, bound0, bound1, 0, ga0, ga1, w, i0, i1)) return;
+    for(double i = i0; i < i1; i += 1) {
+      double a = 1/(i*w + 1);
+      TPointD p( cos(a), sin(a) );
+      TPointD n( p.x*r.y, p.y*r.x ); // perp to allipse
+      double nl2 = norm2(n);
+      if (nl2 > TConsts::epsilon*TConsts::epsilon) {
+        p.x *= r.x;
+        p.y *= r.y;
+        n = n*(1.0/sqrt(nl2));
+        drawMark(em*p, em.transformDirection(n), pixelSize, alpha);
       }
-    } else {
-      // draw linear
-      double da = ga1 - ga0;
-      if (da < 0.0)         { da = -da;        std::swap(ga0, ga1); }
-      if (ga1 - ga0 > M_PI) { da = M_2PI - da; std::swap(ga0, ga1); }
-      if (da < actualMinStep) return;
-      for(double a = ga0 - floor(M_PI/da)*da; a < ga0 + M_PI; a += da) {
-        TPointD p( cos(a), sin(a) );
-        TPointD n( p.x*r.y, p.y*r.x ); // perp to allipse
-        double nl2 = norm2(n);
-        if (nl2 > TConsts::epsilon*TConsts::epsilon) {
-          p.x *= r.x;
-          p.y *= r.y;
-          n = n*(1.0/sqrt(nl2));
-          drawMark(em*p, em.transformDirection(n), pixelSize, alpha);
-        }
+    }
+  } else {
+    // draw linear
+    double da = ga1 - ga0;
+    if (da < 0.0)         { da = -da;        std::swap(ga0, ga1); }
+    if (ga1 - ga0 > M_PI) { da = M_2PI - da; std::swap(ga0, ga1); }
+    if (da < actualMinStep) return;
+    for(double a = ga0 - floor(M_PI/da)*da; a < ga0 + M_PI; a += da) {
+      TPointD p( cos(a), sin(a) );
+      TPointD n( p.x*r.y, p.y*r.x ); // perp to allipse
+      double nl2 = norm2(n);
+      if (nl2 > TConsts::epsilon*TConsts::epsilon) {
+        p.x *= r.x;
+        p.y *= r.y;
+        n = n*(1.0/sqrt(nl2));
+        drawMark(em*p, em.transformDirection(n), pixelSize, alpha);
       }
     }
   }
+}
+
+
+void TAssistantEllipse::drawConcentricGrid(
+  const TAffine &ellipseMatrix,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  bool perspective,
+  double alpha )
+{
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine screenMatrix = (projection*modelview).get2d();
+  TAffine screenMatrixInv = screenMatrix.inv();
+  
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = (perspective ? 2.5 : 10.0)*pixelSize;
+  TAffine ellipseMatrixInv = ellipseMatrix.inv();
+
+  // calculate bounds
+  TAffine matrixInv = ellipseMatrixInv * screenMatrixInv;
+  TPointD o  = matrixInv * TPointD(-1.0, -1.0);
+  TPointD dx = matrixInv.transformDirection( TPointD(2.0, 0.0) );
+  TPointD dy = matrixInv.transformDirection( TPointD(0.0, 2.0) );
+  double max = 0.0;
+  double min = std::numeric_limits<double>::infinity();
+
+  // distance to points
+  TPointD corners[] = { o, o+dx, o+dx+dy, o+dy };
+  for(int i = 0; i < 4; ++i) {
+    double k = norm(corners[i]);
+    if (k < min) min = k;
+    if (k > max) max = k;
+  }
 
-  void drawConcentricGrid(
-    const TAffine &ellipseMatrix,
-    const TAffine &screenMatrixInv,
-    double pixelSize ) const
-  {
-    double minStep = 20.0*pixelSize;
-    double alpha = getDrawingGridAlpha();
-    TAffine ellipseMatrixInv = ellipseMatrix.inv();
-
-    // calculate bounds
-    TAffine matrixInv = ellipseMatrixInv * screenMatrixInv;
-    TPointD o  = matrixInv * TPointD(-1.0, -1.0);
-    TPointD dx = matrixInv.transformDirection( TPointD(2.0, 0.0) );
-    TPointD dy = matrixInv.transformDirection( TPointD(0.0, 2.0) );
-    double max = 0.0;
-    double min = std::numeric_limits<double>::infinity();
-
-    // distance to points
-    TPointD corners[] = { o, o+dx, o+dx+dy, o+dy };
-    for(int i = 0; i < 4; ++i) {
-      double k = norm(corners[i]);
-      if (k < min) min = k;
-      if (k > max) max = k;
-    }
-
-    // distance to sides
-    TPointD lines[] = { dx, dy, -1.0*dx, -1.0*dy };
-    int positive = 0, negative = 0;
-    for(int i = 0; i < 4; ++i) {
-      double len2 = norm2(lines[i]);
-      if (len2 <= TConsts::epsilon*TConsts::epsilon) continue;
-      double k = (corners[i]*rotate90(lines[i]))/sqrt(len2);
-      if (k > TConsts::epsilon) ++positive;
-      if (k < TConsts::epsilon) ++negative;
-      double l = -(corners[i]*lines[i]);
-      if (l <= TConsts::epsilon || l >= len2 - TConsts::epsilon) continue;
-      k = fabs(k);
-      if (k < min) min = k;
-      if (k > max) max = k;
-    }
-
-    // if center is inside bounds
-    if (min < 0.0 || positive == 0 || negative == 0) min = 0.0;
-    if (max <= min) return;
+  // distance to sides
+  TPointD lines[] = { dx, dy, -1.0*dx, -1.0*dy };
+  int positive = 0, negative = 0;
+  for(int i = 0; i < 4; ++i) {
+    double len2 = norm2(lines[i]);
+    if (len2 <= TConsts::epsilon*TConsts::epsilon) continue;
+    double k = (corners[i]*rotate90(lines[i]))/sqrt(len2);
+    if (k > TConsts::epsilon) ++positive;
+    if (k < TConsts::epsilon) ++negative;
+    double l = -(corners[i]*lines[i]);
+    if (l <= TConsts::epsilon || l >= len2 - TConsts::epsilon) continue;
+    k = fabs(k);
+    if (k < min) min = k;
+    if (k > max) max = k;
+  }
 
-    // draw
-    const TAffine &em = ellipseMatrix;
-    double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22))));
-    double actualMinStep = minStep/r;
-    double gs0 = norm(ellipseMatrixInv*m_grid0.position);
-    double gs1 = norm(ellipseMatrixInv*m_grid1.position);
-    if (gs0 <= TConsts::epsilon*TConsts::epsilon) return;
-    if (gs1 <= TConsts::epsilon*TConsts::epsilon) return;
-
-    if (getPerspective()) {
-      // draw perspective
-      double k = 0.0, begin = 0.0, end = 0.0;
-      if (!calcPerspectiveStep(actualMinStep, min, max, 0.0, gs0, gs1, k, begin, end)) return;
-      for(double x = begin; fabs(x) < fabs(end); x *= k)
-        drawEllipse(ellipseMatrix * TAffine::scale(x), screenMatrixInv, pixelSize, alpha);
-    } else {
-      // draw linear
-      double dx = fabs(gs1 - gs0);
-      if (dx*r < minStep) return;
-      for(double x = gs0 + ceil((min - gs0)/dx)*dx; x < max; x += dx)
-        drawEllipse(ellipseMatrix * TAffine::scale(x), screenMatrixInv, pixelSize, alpha);
+  // if center is inside bounds
+  if (min < 0.0 || positive == 0 || negative == 0) min = 0.0;
+  if (max <= min) return;
+
+  // draw
+  const TAffine &em = ellipseMatrix;
+  double r = sqrt(std::min( norm2(TPointD(em.a11, em.a21)), norm2(TPointD(em.a12, em.a22)) ));
+  double actualMinStep = minStep/r;
+  double gs0 = norm(ellipseMatrixInv*grid0);
+  double gs1 = norm(ellipseMatrixInv*grid1);
+  if (gs0 <= TConsts::epsilon*TConsts::epsilon) return;
+  if (gs1 <= TConsts::epsilon*TConsts::epsilon) return;
+
+  if (perspective) {
+    // draw perspective
+    double w, i0, i1;
+    actualMinStep /= 2;
+    if (!calcPerspectiveStep(actualMinStep, min, max, 0, gs0, gs1, w, i0, i1)) return;
+    for(double i = i0; i < i1; i += 1) {
+      double x = 1/(i*w + 1);
+      
+      double curStep = fabs(w*x*x);
+      double curAlpha = (curStep - actualMinStep)/actualMinStep;
+      if (curAlpha < 0) continue;
+      if (curAlpha > 1) curAlpha = 1;
+      
+      drawEllipse(ellipseMatrix * TAffine::scale(x), screenMatrixInv, pixelSize, alpha*curAlpha);
     }
+  } else {
+    // draw linear
+    double dx = fabs(gs1 - gs0);
+    if (dx*r < minStep) return;
+    for(double x = gs0 + ceil((min - gs0)/dx)*dx; x < max; x += dx)
+      drawEllipse(ellipseMatrix * TAffine::scale(x), screenMatrixInv, pixelSize, alpha);
   }
-
-  void drawParallelGrid(
-    const TAffine &ellipseMatrix,
-    const TAffine &screenMatrixInv,
-    double pixelSize ) const
-  {
-    double minStep = 10.0*pixelSize;
-    double alpha = getDrawingGridAlpha();
-    TAffine ellipseMatrixInv = ellipseMatrix.inv();
-
-    const TAffine &em = ellipseMatrix;
-    double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22))));
-    double actualMinStep = minStep/r;
-    TPointD g0 = ellipseMatrixInv*m_grid0.position;
-    TPointD g1 = ellipseMatrixInv*m_grid1.position;
-    if (getRepeat())
-      { g0.x -= round(0.5*g0.x)*2.0; g1.x -= round(0.5*g1.x)*2.0; }
-    if (fabs(g0.x) >= 1.0 - TConsts::epsilon) return;
-    if (fabs(g1.x) >= 1.0 - TConsts::epsilon) return;
-    double gs0 = g0.y/sqrt(1.0 - g0.x*g0.x);
-    double gs1 = g1.y/sqrt(1.0 - g1.x*g1.x);
-    if (fabs(gs0) >= 1.0 - TConsts::epsilon) return;
-    if (fabs(gs1) >= 1.0 - TConsts::epsilon) return;
-
-    TAngleRangeSet ranges;
-    ranges.add( TAngleRangeSet::fromDouble(0.0), TAngleRangeSet::fromDouble(M_PI) );
-
-    if (getPerspective()) {
-      // draw perspective (actually angular)
-      double k = 0.0, begin = 0.0, end = 0.0;
-      double a0 = asin(gs0);
-      double a1 = asin(gs1);
-      double da = fabs(a1 - a0);
-      if (fabs(sin(da)) < 2.0*actualMinStep) return;
-      for(double a = a0 + ceil((-M_PI_2 - a0)/da)*da; a < M_PI_2; a += da)
-        drawEllipseRanges(
-          ranges,
-          ellipseMatrix*TAffine::scale(a < 0.0 ? -1.0 : 1.0, sin(a)),
-          screenMatrixInv,
-          pixelSize,
-          alpha );
-    } else {
-      // draw linear
-      double dx = fabs(gs1 - gs0);
-      if (dx < actualMinStep) return;
-      for(double x = gs0 + ceil((-1.0 - gs0)/dx)*dx; x < 1.0; x += dx)
-        drawEllipseRanges(
-          ranges,
-          ellipseMatrix*TAffine::scale(x < 0.0 ? -1.0 : 1.0, x),
-          screenMatrixInv,
-          pixelSize,
-          alpha );
+}
+
+
+void TAssistantEllipse::drawParallelGrid(
+  const TAffine &ellipseMatrix,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  const TPointD *bound0,
+  const TPointD *bound1,
+  bool perspective,
+  bool perspectiveDepth,
+  bool repeat,
+  double alpha )
+{
+  struct {
+    const bool repeat;
+    const TAffine ellipseMatrixInv;
+    double convert(const TPointD &p) {
+      TPointD pp = ellipseMatrixInv*p;
+      if (repeat)
+        pp.x -= round(pp.x/2)*2;
+      if (!(fabs(pp.x) < 1 - TConsts::epsilon))
+        return pp.y < 0 ? -INFINITY : INFINITY;
+      return pp.y/sqrt(1 - pp.x*pp.x);
     }
+  } helper = { repeat, ellipseMatrix.inv() };
+  
+  
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine screenMatrix = (projection*modelview).get2d();
+  TAffine screenMatrixInv = screenMatrix.inv();
+  
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = (perspective ? 5 : 10)*pixelSize;
+
+  const TAffine &em = ellipseMatrix;
+  double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22))));
+  double actualMinStep = minStep/r;
+  
+  // convert grid
+  double gs0 = helper.convert(grid0);
+  double gs1 = helper.convert(grid1);
+  if (!(fabs(gs0) < 1 - TConsts::epsilon)) return;
+  if (!(fabs(gs1) < 1 - TConsts::epsilon)) return;
+  
+  // convert bounds
+  double bs0 = -INFINITY, bs1 = INFINITY;
+  if (bound0 && bound1) {
+    bs0 = helper.convert(*bound0);
+    bs1 = helper.convert(*bound1);
+  } else
+  if (bound0) {
+    bs0 = helper.convert(*bound0);
+    if (bs0 < 0) bs1 = -INFINITY;
+  } else
+  if (bound1) {
+    bs1 = helper.convert(*bound1);
+    if (bs1 < 0) bs0 = INFINITY;
   }
-
-  void draw(
-    const TAffine &ellipseMatrix,
-    const TAffine &screenMatrixInv,
-    double ox,
-    double pixelSize,
-    bool enabled ) const
-  {
-    const double crossSize = 0.1;
-
-    double alpha = getDrawingAlpha(enabled);
-    bool grid = getGrid();
-    bool ruler = getRestrictA() && getRestrictB();
-    bool concentric = !getRestrictA() && !getRestrictB();
-
-    drawSegment( ellipseMatrix*TPointD(-crossSize, 0.0),
-                 ellipseMatrix*TPointD( crossSize, 0.0), pixelSize, alpha);
-    drawSegment( ellipseMatrix*TPointD(0.0, -crossSize),
-                 ellipseMatrix*TPointD(0.0,  crossSize), pixelSize, alpha);
-    drawEllipse(ellipseMatrix, screenMatrixInv, pixelSize, alpha);
-    if (ox > 1.0)
-      drawSegment( ellipseMatrix*TPointD(-1.0, -1.0),
-                   ellipseMatrix*TPointD(-1.0,  1.0), pixelSize, alpha);
-    else if (ox < -1.0)
-      drawSegment( ellipseMatrix*TPointD( 1.0, -1.0),
-                   ellipseMatrix*TPointD( 1.0,  1.0), pixelSize, alpha);
-
-    if (!grid) return;
-
-    if (ruler) {
-      drawRuler(ellipseMatrix, pixelSize);
-    } else
-    if (concentric) {
-      drawConcentricGrid(ellipseMatrix, screenMatrixInv, pixelSize);
-    } else {
-      drawParallelGrid(ellipseMatrix, screenMatrixInv, pixelSize);
+  
+  if (bs0 > bs1) std::swap(bs0, bs1);
+  bs0 -= TConsts::epsilon;
+  bs1 += TConsts::epsilon;
+  
+  // prepare ranges
+  TAngleRangeSet ranges;
+  ranges.add( TAngleRangeSet::fromDouble(0.0), TAngleRangeSet::fromDouble(M_PI) );
+
+  if (perspectiveDepth) {
+    // draw perspective depth
+    if (!( fabs(gs0) > TConsts::epsilon
+        && fabs(gs1) > TConsts::epsilon
+        && fabs(gs0) < 1 - TConsts::epsilon
+        && fabs(gs1) < 1 - TConsts::epsilon
+        && fabs(gs1 - gs0) > TConsts::epsilon )) return;
+
+    // the formula is: x = 1/sqrt(1 + i*i)
+    double ig0 = sqrt(1/(gs0*gs0) - 1);
+    double ig1 = sqrt(1/(gs1*gs1) - 1);
+    double di = fabs(ig1 - ig0);
+    double i0 = ig0 - floor(ig0/di)*di;
+    if (i0 <= TConsts::epsilon) i0 += di;
+    
+    actualMinStep /= 2;
+    double sign = gs0 < 0 ? -1 : 1;
+    for(double i = i0; i+di != i; i += di) {
+      double x = 1/sqrt(1 + i*i);
+      
+      double curStep = fabs( di*i*x*x*x );
+      double curAlpha = (curStep - actualMinStep)/actualMinStep;
+      if (curAlpha < 0) { if (i == i0) continue; else break; }
+      if (curAlpha > 1) curAlpha = 1;
+      
+      x *= sign;
+      if (x < bs0 || bs1 < x) continue;
+      
+      drawEllipseRanges(
+        ranges,
+        ellipseMatrix*TAffine::scale(sign, x),
+        screenMatrixInv,
+        pixelSize,
+        alpha * curAlpha );
+    }
+  } else
+  if (perspective) {
+    // draw perspective
+    if (!( fabs(gs0) < 1 - TConsts::epsilon
+        && fabs(gs1) < 1 - TConsts::epsilon
+        && fabs(gs1 - gs0) > TConsts::epsilon )) return;
+
+    // the formula is: x = i/sqrt(1 + i*i)
+    double ig0 = gs0/sqrt(1 - gs0*gs0);
+    double ig1 = gs1/sqrt(1 - gs1*gs1);
+    double di = fabs(ig1 - ig0);
+    double i0 = ig0 - round(ig0/di)*di;
+    
+    actualMinStep /= 2;
+    for(int j = 0; j < 2; ++j, di = -di, i0 += di)
+    for(double i = i0; i+di != i; i += di) {
+      double x = i/sqrt(1 + i*i);
+      
+      double curAlpha = 1;
+      if (fabs(i) > TConsts::epsilon) {
+        double curStep = fabs( di*x*(1 - x*x)/i );
+        curAlpha = (curStep - actualMinStep)/actualMinStep;
+        if (curAlpha < 0) { if (i == i0) continue; else break; }
+        if (curAlpha > 1) curAlpha = 1;
+      }
+      if (x < bs0 || bs1 < x) continue;
+        
+      drawEllipseRanges(
+        ranges,
+        ellipseMatrix*TAffine::scale(x < 0.0 ? -1.0 : 1.0, x),
+        screenMatrixInv,
+        pixelSize,
+        alpha * curAlpha );
     }
+  } else {
+    // draw linear
+    double dx = fabs(gs1 - gs0);
+    if (dx < actualMinStep) return;
+    for(double x = gs0 + ceil((-1.0 - gs0)/dx)*dx; x < 1.0; x += dx) {
+      if (x < bs0 || bs1 < x) continue;
+      drawEllipseRanges(
+        ranges,
+        ellipseMatrix*TAffine::scale(x < 0.0 ? -1.0 : 1.0, x),
+        screenMatrixInv,
+        pixelSize,
+        alpha );
+    }
+  }
+}
+
+
+void TAssistantEllipse::draw(
+  const TAffine &ellipseMatrix,
+  const TAffine &screenMatrixInv,
+  double ox,
+  double pixelSize,
+  bool enabled ) const
+{
+  const double crossSize = 0.1;
+
+  double alpha = getDrawingAlpha(enabled);
+  double gridAlpha = getDrawingGridAlpha();
+  bool grid = getGrid();
+  bool ruler = getRestrictA() && getRestrictB();
+  bool concentric = !getRestrictA() && !getRestrictB();
+
+  drawSegment( ellipseMatrix*TPointD(-crossSize, 0.0),
+                ellipseMatrix*TPointD( crossSize, 0.0), pixelSize, alpha);
+  drawSegment( ellipseMatrix*TPointD(0.0, -crossSize),
+                ellipseMatrix*TPointD(0.0,  crossSize), pixelSize, alpha);
+  drawEllipse(ellipseMatrix, screenMatrixInv, pixelSize, alpha);
+  if (ox > 1.0)
+    drawSegment( ellipseMatrix*TPointD(-1.0, -1.0),
+                  ellipseMatrix*TPointD(-1.0,  1.0), pixelSize, alpha);
+  else if (ox < -1.0)
+    drawSegment( ellipseMatrix*TPointD( 1.0, -1.0),
+                  ellipseMatrix*TPointD( 1.0,  1.0), pixelSize, alpha);
+
+  if (!grid) return;
+
+  if (ruler) {
+    drawRuler(
+      ellipseMatrix,
+      m_grid0.position,
+      m_grid1.position,
+      getPerspective(),
+      alpha );
+  } else
+  if (concentric) {
+    drawConcentricGrid(
+      ellipseMatrix,
+      m_grid0.position,
+      m_grid1.position,
+      getPerspective(),
+      gridAlpha );
+  } else {
+    drawParallelGrid(
+      ellipseMatrix,
+      m_grid0.position,
+      m_grid1.position,
+      nullptr,
+      nullptr,
+      getPerspective(),
+      getPerspectiveDepth(),
+      getRepeat(),
+      gridAlpha );
   }
+}
 
-public:
-  void draw(TToolViewer *viewer, bool enabled) const override {
-    bool restrictA = getRestrictA();
-    bool restrictB = getRestrictB();
-    bool repeat = getRepeat();
-    double minStep = 30.0;
-
-    TAffine ellipseMatrix = calcEllipseMatrix();
-    if (ellipseMatrix.isZero()) return;
-    if (!restrictA && restrictB) {
-      std::swap(ellipseMatrix.a11, ellipseMatrix.a12);
-      std::swap(ellipseMatrix.a21, ellipseMatrix.a22);
-    }
 
-    // common data about viewport
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    TAffine4 modelview, projection;
-    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
-    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
-    TAffine matrix = (projection*modelview).get2d();
-    TAffine matrixInv = matrix.inv();
-    double pixelSize = sqrt(tglGetPixelSize2());
-
-    if (!repeat || restrictA == restrictB || norm(TPointD(ellipseMatrix.a11, ellipseMatrix.a21)) < minStep*pixelSize) {
-      draw(ellipseMatrix, matrixInv, 0.0, pixelSize, enabled);
-    } else {
-      // calculate bounds
-      TPointD o(ellipseMatrix.a13, ellipseMatrix.a23);
-      TPointD proj(ellipseMatrix.a11, ellipseMatrix.a21);
-      proj = proj * (1.0/norm2(proj));
-      TPointD corners[4] = {
-        TPointD(oneBox.x0, oneBox.y0),
-        TPointD(oneBox.x0, oneBox.y1),
-        TPointD(oneBox.x1, oneBox.y0),
-        TPointD(oneBox.x1, oneBox.y1) };
-      double minX = 0.0, maxX = 0.0;
-      for(int i = 0; i < 4; ++i) {
-        double x = proj * (matrixInv*corners[i] - o);
-        if (i == 0 || x < minX) minX = x;
-        if (i == 0 || x > maxX) maxX = x;
-      }
-      if (maxX <= minX) return;
+void TAssistantEllipse::draw(TToolViewer*, bool enabled) const {
+  bool restrictA = getRestrictA();
+  bool restrictB = getRestrictB();
+  bool repeat = getRepeat();
+  double minStep = 30.0;
+
+  TAffine ellipseMatrix = calcEllipseMatrix();
+  if (ellipseMatrix.isZero()) return;
+  if (!restrictA && restrictB) {
+    std::swap(ellipseMatrix.a11, ellipseMatrix.a12);
+    std::swap(ellipseMatrix.a21, ellipseMatrix.a22);
+  }
 
-      // draw
-      for(double ox = round(0.5*minX)*2.0; ox - 1.0 < maxX; ox += 2.0)
-        draw(ellipseMatrix*TAffine::translation(ox, 0.0), matrixInv, ox, pixelSize, enabled);
+  // common data about viewport
+  const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine matrix = (projection*modelview).get2d();
+  TAffine matrixInv = matrix.inv();
+  double pixelSize = sqrt(tglGetPixelSize2());
+
+  if (!repeat || restrictA == restrictB || norm(TPointD(ellipseMatrix.a11, ellipseMatrix.a21)) < minStep*pixelSize) {
+    draw(ellipseMatrix, matrixInv, 0.0, pixelSize, enabled);
+  } else {
+    // calculate bounds
+    TPointD o(ellipseMatrix.a13, ellipseMatrix.a23);
+    TPointD proj(ellipseMatrix.a11, ellipseMatrix.a21);
+    proj = proj * (1.0/norm2(proj));
+    TPointD corners[4] = {
+      TPointD(oneBox.x0, oneBox.y0),
+      TPointD(oneBox.x0, oneBox.y1),
+      TPointD(oneBox.x1, oneBox.y0),
+      TPointD(oneBox.x1, oneBox.y1) };
+    double minX = 0.0, maxX = 0.0;
+    for(int i = 0; i < 4; ++i) {
+      double x = proj * (matrixInv*corners[i] - o);
+      if (i == 0 || x < minX) minX = x;
+      if (i == 0 || x > maxX) maxX = x;
     }
+    if (maxX <= minX) return;
+
+    // draw
+    for(double ox = round(0.5*minX)*2.0; ox - 1.0 < maxX; ox += 2.0)
+      draw(ellipseMatrix*TAffine::translation(ox, 0.0), matrixInv, ox, pixelSize, enabled);
   }
-};
+}
 
 
 //*****************************************************************************************
diff --git a/toonz/sources/tnztools/assistants/assistantellipse.h b/toonz/sources/tnztools/assistants/assistantellipse.h
new file mode 100644
index 0000000..394ea96
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/assistantellipse.h
@@ -0,0 +1,133 @@
+#pragma once
+
+#ifndef ASSISTANTELLIPSE_INCLUDED
+#define ASSISTANTELLIPSE_INCLUDED
+
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/assistants/guidelineline.h>
+#include <tools/assistants/guidelineellipse.h>
+
+// TnzCore includes
+#include <tgl.h>
+
+// std includes
+#include <limits>
+
+
+//*****************************************************************************************
+//    TAssistantEllipse definition
+//*****************************************************************************************
+
+class TAssistantEllipse final : public TAssistant {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantEllipse)
+public:
+  const TStringId m_idCircle;
+  const TStringId m_idRestrictA;
+  const TStringId m_idRestrictB;
+  const TStringId m_idRepeat;
+  const TStringId m_idGrid;
+  const TStringId m_idPerspective;
+  const TStringId m_idPerspectiveDepth;
+
+protected:
+  TAssistantPoint &m_center;
+  TAssistantPoint &m_a;
+  TAssistantPoint &m_b;
+  TAssistantPoint &m_grid0;
+  TAssistantPoint &m_grid1;
+
+public:
+  TAssistantEllipse(TMetaObject &object);
+
+  static QString getLocalName();
+
+  void updateTranslation() const override;
+
+  inline bool getCircle() const
+    { return data()[m_idCircle].getBool(); }
+  inline bool getRestrictA() const
+    { return data()[m_idRestrictA].getBool(); }
+  inline bool getRestrictB() const
+    { return data()[m_idRestrictB].getBool(); }
+  inline bool getRepeat() const
+    { return data()[m_idRepeat].getBool(); }
+  inline bool getGrid() const
+    { return data()[m_idGrid].getBool(); }
+  inline bool getPerspective() const
+    { return data()[m_idPerspective].getBool(); }
+  inline bool getPerspectiveDepth() const
+    { return data()[m_idPerspectiveDepth].getBool(); }
+
+  void onDataChanged(const TVariant &value) override;
+
+private:
+  void fixBAndGrid(
+    TPointD prevCenter,
+    TPointD prevA,
+    TPointD prevB );
+
+public:
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override;
+
+  TAffine calcEllipseMatrix() const;
+
+  void getGuidelines(
+    const TPointD &position,
+    const TAffine &toTool,
+    TGuidelineList &outGuidelines ) const override;
+
+  static void drawEllipseRanges(
+    const TAngleRangeSet &ranges,
+    const TAffine &ellipseMatrix,
+    const TAffine &screenMatrixInv,
+    double pixelSize,
+    double alpha );
+
+  static inline void drawEllipse(
+    const TAffine &ellipseMatrix,
+    const TAffine &screenMatrixInv,
+    double pixelSize,
+    double alpha )
+      { drawEllipseRanges(TAngleRangeSet(true), ellipseMatrix, screenMatrixInv, pixelSize, alpha); }
+
+  static void drawRuler(
+    const TAffine &ellipseMatrix,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    bool perspective,
+    double alpha );
+
+  static void drawConcentricGrid(
+    const TAffine &ellipseMatrix,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    bool perspective,
+    double alpha );
+
+  static void drawParallelGrid(
+    const TAffine &ellipseMatrix,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    const TPointD *bound0,
+    const TPointD *bound1,
+    bool perspective,
+    bool perspectiveDepth,
+    bool repeat,
+    double alpha );
+
+private:
+  void draw(
+    const TAffine &ellipseMatrix,
+    const TAffine &screenMatrixInv,
+    double ox,
+    double pixelSize,
+    bool enabled ) const;
+
+public:
+  void draw(TToolViewer *viewer, bool enabled) const override;
+};
+
+
+#endif
diff --git a/toonz/sources/tnztools/assistants/assistantfisheye.cpp b/toonz/sources/tnztools/assistants/assistantfisheye.cpp
new file mode 100644
index 0000000..6ebfe5f
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/assistantfisheye.cpp
@@ -0,0 +1,315 @@
+
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/assistants/guidelineline.h>
+#include <tools/assistants/guidelineellipse.h>
+
+#include "assistantellipse.h"
+
+// TnzCore includes
+#include <tgl.h>
+
+// std includes
+#include <limits>
+
+
+//*****************************************************************************************
+//    TAssistantFisheye implementation
+//*****************************************************************************************
+
+class TAssistantFisheye final : public TAssistant {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantEllipse)
+public:
+  const TStringId m_idCircle;
+  const TStringId m_idGrid;
+  const TStringId m_idGridDepth;
+  const TStringId m_idFlipGrids;
+
+protected:
+  TAssistantPoint &m_center;
+  TAssistantPoint &m_a;
+  TAssistantPoint &m_b;
+  TAssistantPoint &m_grid0;
+  TAssistantPoint &m_grid1;
+  TAssistantPoint &m_gridD0;
+  TAssistantPoint &m_gridD1;
+
+public:
+  TAssistantFisheye(TMetaObject &object):
+    TAssistant(object),
+    m_idCircle("circle"),
+    m_idGrid("grid"),
+    m_idGridDepth("gridDepth"),
+    m_idFlipGrids("flipGrids"),
+    m_center( addPoint("center", TAssistantPoint::CircleCross) ),
+    m_a( addPoint("a", TAssistantPoint::CircleFill, TPointD(200,   0)) ),
+    m_b( addPoint("b", TAssistantPoint::Circle,     TPointD(  0, 200)) ),
+    m_grid0(  addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD( -25,  25)) ),
+    m_grid1(  addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD(  25, -25)) ),
+    m_gridD0( addPoint("gridD0", TAssistantPoint::CircleDoubleDots, TPointD( -70, -70)) ),
+    m_gridD1( addPoint("gridD1", TAssistantPoint::CircleDots,       TPointD( -90, -90)) )
+  {
+    addProperty( new TBoolProperty(m_idCircle.str(), getCircle()) );
+    addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
+    addProperty( new TBoolProperty(m_idGridDepth.str(), getGridDepth()) );
+    addProperty( new TBoolProperty(m_idFlipGrids.str(), getFlipGrids()) );
+  }
+
+  static QString getLocalName()
+    { return tr("Fish Eye"); }
+
+  void updateTranslation() const override {
+    TAssistant::updateTranslation();
+    setTranslation(m_idCircle, tr("Circle"));
+    setTranslation(m_idGrid, tr("Grid"));
+    setTranslation(m_idGridDepth, tr("Depth Grid"));
+    setTranslation(m_idFlipGrids, tr("Flip Grids"));
+  }
+
+  inline bool getCircle() const
+    { return data()[m_idCircle].getBool(); }
+  inline bool getGrid() const
+    { return data()[m_idGrid].getBool(); }
+  inline bool getGridDepth() const
+    { return data()[m_idGridDepth].getBool(); }
+  inline bool getFlipGrids() const
+    { return data()[m_idFlipGrids].getBool(); }
+
+  void onDataChanged(const TVariant &value) override {
+    TAssistant::onDataChanged(value);
+    m_grid0.visible  = m_grid1.visible  = getGrid();
+    m_gridD0.visible = m_gridD1.visible = getGridDepth();
+    bool prevB = m_b.visible;
+    m_b.visible = !getCircle();
+    if (prevB && !m_b.visible)
+      fixBAndGrid(m_center.position, m_a.position, m_b.position);
+  }
+
+private:
+  void fixBAndGrid(
+    TPointD prevCenter,
+    TPointD prevA,
+    TPointD prevB )
+  {
+    const TPointD &center = m_center.position;
+    TPointD da0 = prevA - prevCenter;
+    TPointD da1 = m_a.position - center;
+    double la0 = norm2(da0);
+    double la1 = norm2(da1);
+    if (!(la0 > TConsts::epsilon) || !(la1 > TConsts::epsilon))
+      return;
+    
+    TPointD db = m_b.position - center;
+    TPointD dp0 = TPointD(-da0.y, da0.x);
+    TPointD dp1 = TPointD(-da1.y, da1.x);
+    if (getCircle()) {
+      m_b.position = center + (db*dp0 < 0 ? -dp1 : dp1);
+    } else {
+      m_b.position = db*dp0/la0*dp1 + center;
+    }
+
+    TPointD db0 = prevB - prevCenter;
+    TPointD db1 = m_b.position - center;
+    double lb0 = norm2(db0);
+    double lb1 = norm2(db1);
+    if (!(lb0 > TConsts::epsilon) || !(lb1 > TConsts::epsilon))
+      return;
+
+    TPointD dg0  = m_grid0.position  - center;
+    TPointD dg1  = m_grid1.position  - center;
+    TPointD dgD0 = m_gridD0.position - center;
+    TPointD dgD1 = m_gridD1.position - center;
+    m_grid0.position  =  dg0*da0/la0*da1 +  dg0*db0/lb0*db1 + center;
+    m_grid1.position  =  dg1*da0/la0*da1 +  dg1*db0/lb0*db1 + center;
+    m_gridD0.position = dgD0*da0/la0*da1 + dgD0*db0/lb0*db1 + center;
+    m_gridD1.position = dgD1*da0/la0*da1 + dgD1*db0/lb0*db1 + center;
+  }
+
+  void fixGridD1() {
+    TAffine em = calcEllipseMatrix();
+    TAffine emi = em.inv();
+    TPointD d0 = emi*m_gridD0.position;
+    TPointD d1 = emi*m_gridD1.position;
+    double l0 = norm2(d0);
+    double l1 = norm2(d1);
+    if ( l0 > TConsts::epsilon*TConsts::epsilon
+      && l1 > TConsts::epsilon*TConsts::epsilon )
+      m_gridD1.position = em*( d0*sqrt(l1/l0) );
+  }
+
+public:
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    TPointD prevCenter = m_center.position;
+    TPointD prevA = m_a.position;
+    TPointD prevB = m_b.position;
+    point.position = position;
+    if (&point == &m_center) {
+      TPointD d = m_center.position - prevCenter;
+      m_a.position += d;
+      m_b.position += d;
+      m_grid0.position += d;
+      m_grid1.position += d;
+      m_gridD0.position += d;
+      m_gridD1.position += d;
+    } else
+    if (&point == &m_a || &point == &m_b) {
+      fixBAndGrid(prevCenter, prevA, prevB);
+    } else
+    if (&point == &m_gridD0 || &point == &m_gridD1) {
+      fixGridD1();
+    }
+  }
+
+  TAffine calcEllipseMatrix() const {
+    TPointD da = m_a.position - m_center.position;
+    TPointD db = m_b.position - m_center.position;
+    double r1 = norm(da);
+    if (r1 <= TConsts::epsilon) return TAffine::zero();
+    double r2 = fabs( (rotate90(da)*db)*(1.0/r1) );
+    if (r2 <= TConsts::epsilon) return TAffine::zero();
+    return TAffine::translation(m_center.position)
+         * TAffine::rotation(atan(da))
+         * TAffine::scale(r1, r2);
+  }
+
+  void getGuidelines(
+    const TPointD &position,
+    const TAffine &toTool,
+    TGuidelineList &outGuidelines ) const override
+  {
+    TAffine matrix = calcEllipseMatrix();
+    if (matrix.isZero()) return;
+
+    matrix = toTool*matrix;
+    TAffine matrixInv = matrix.inv();
+
+    TPointD p = matrixInv*position;
+    double l = norm(p);
+    
+    if (l > TConsts::epsilon) {
+      // radius
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineLine(
+          getEnabled(),
+          getMagnetism(),
+          matrix*TPointD(0, 0),
+          matrix*(p/l) )));
+    }
+    
+    if (!(l < 1.0 - TConsts::epsilon)) {
+      // bound ellipse
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineEllipse(
+          getEnabled(),
+          getMagnetism(),
+          matrix )));
+    } else {
+      // ellipse scaled by X
+      double kx = fabs(p.x/sqrt(1.0 - p.y*p.y));
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineEllipse(
+          getEnabled(),
+          getMagnetism(),
+          matrix * TAffine::scale(kx, 1.0) )));
+
+      // ellipse scaled by Y
+      double ky = fabs(p.y/sqrt(1.0 - p.x*p.x));
+      outGuidelines.push_back(TGuidelineP(
+        new TGuidelineEllipse(
+          getEnabled(),
+          getMagnetism(),
+          matrix * TAffine::scale(1.0, ky) )));
+    }
+  }
+
+public:
+  void draw(TToolViewer*, bool enabled) const override {
+    TAffine ellipseMatrix = calcEllipseMatrix();
+    if (ellipseMatrix.isZero()) return;
+    
+    TAffine ellipseMatrix2 = ellipseMatrix;
+    std::swap(ellipseMatrix.a11, ellipseMatrix.a12);
+    std::swap(ellipseMatrix.a21, ellipseMatrix.a22);
+
+    // common data about viewport
+    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+    TAffine4 modelview, projection;
+    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+    TAffine matrix = (projection*modelview).get2d();
+    TAffine matrixInv = matrix.inv();
+
+    double pixelSize = sqrt(tglGetPixelSize2());
+    const double crossSize = 0.1;
+    double alpha = getDrawingAlpha(enabled);
+    double gridAlpha = getDrawingGridAlpha();
+    
+    drawSegment( ellipseMatrix*TPointD(-crossSize, 0.0),
+                 ellipseMatrix*TPointD( crossSize, 0.0), pixelSize, alpha);
+    drawSegment( ellipseMatrix*TPointD(0.0, -crossSize),
+                 ellipseMatrix*TPointD(0.0,  crossSize), pixelSize, alpha);
+    TAssistantEllipse::drawEllipse(ellipseMatrix, matrixInv, pixelSize, alpha);
+
+    const TPointD *bound = getGrid() && getGridDepth() ? &m_gridD0.position : nullptr;
+    const TPointD *boundA = nullptr;
+    const TPointD *boundB = bound;
+    if (getFlipGrids()) std::swap(boundA, boundB);
+    
+    if (getGrid()) {
+      TAssistantEllipse::drawParallelGrid(
+        ellipseMatrix, m_grid0.position, m_grid1.position,
+        boundA, boundB, true, false, false, gridAlpha );
+      TAssistantEllipse::drawParallelGrid(
+        ellipseMatrix2, m_grid0.position, m_grid1.position,
+        boundA, boundB, true, false, false, gridAlpha );
+    }
+
+    if (getGridDepth()) {
+      TAssistantEllipse::drawParallelGrid(
+        ellipseMatrix, m_gridD0.position, m_gridD1.position,
+        boundB, boundA, false, true, false, gridAlpha );
+      TAssistantEllipse::drawParallelGrid(
+        ellipseMatrix2, m_gridD0.position, m_gridD1.position,
+        boundB, boundA, false, true, false, gridAlpha );
+    }
+    
+    if (bound) {
+      TPointD b = ellipseMatrix.inv()*(*bound);
+      double r = norm2(b);
+      if (r < 1 - TConsts::epsilon) {
+        double bx = b.x/sqrt(1 - b.y*b.y);
+        double by = b.y/sqrt(1 - b.x*b.x);
+        
+        TAngleRangeSet ranges;
+        ranges.add( TAngleRangeSet::fromDouble(-M_PI/2), TAngleRangeSet::fromDouble(M_PI/2) );
+        TAssistantEllipse::drawEllipseRanges(
+          ranges,
+          ellipseMatrix*TAffine::scale(bx, 1),
+          matrixInv,
+          pixelSize,
+          alpha );
+        
+        ranges.clear();
+        ranges.add( TAngleRangeSet::fromDouble(0.0), TAngleRangeSet::fromDouble(M_PI) );
+        TAssistantEllipse::drawEllipseRanges(
+          ranges,
+          ellipseMatrix*TAffine::scale(1, by),
+          matrixInv,
+          pixelSize,
+          alpha );
+        
+        TPointD bb = bound == boundA ? TPointD(0, 0) : b/sqrt(r);
+        drawSegment( ellipseMatrix*b, ellipseMatrix*bb, pixelSize, alpha );
+      }
+    }
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TAssistantFisheye> assistantFisheye("assistantFisheye");
+
diff --git a/toonz/sources/tnztools/assistants/assistantline.cpp b/toonz/sources/tnztools/assistants/assistantline.cpp
index f0a1fd6..6563d01 100644
--- a/toonz/sources/tnztools/assistants/assistantline.cpp
+++ b/toonz/sources/tnztools/assistants/assistantline.cpp
@@ -1,354 +1,374 @@
 
 
 // TnzTools includes
-#include <tools/assistant.h>
-#include <tools/assistants/guidelineline.h>
+#include "assistantline.h"
 
 
-// TnzCore includes
-#include <tgl.h>
-
 
 //*****************************************************************************************
 //    TAssistantLine implementation
 //*****************************************************************************************
 
-class TAssistantLine final : public TAssistant {
-  Q_DECLARE_TR_FUNCTIONS(TAssistantVanishingPoint)
-public:
-  const TStringId m_idRestricktA;
-  const TStringId m_idRestricktB;
-  const TStringId m_idParallel;
-  const TStringId m_idGrid;
-  const TStringId m_idPerspective;
-
-protected:
-  TAssistantPoint &m_a;
-  TAssistantPoint &m_b;
-  TAssistantPoint &m_grid0;
-  TAssistantPoint &m_grid1;
-
-public:
-  TAssistantLine(TMetaObject &object):
-    TAssistant(object),
-    m_idRestricktA("restrictA"),
-    m_idRestricktB("restrictB"),
-    m_idParallel("parallel"),
-    m_idGrid("grid"),
-    m_idPerspective("perspective"),
-    m_a( addPoint("a", TAssistantPoint::CircleCross) ),
-    m_b( addPoint("b", TAssistantPoint::Circle, TPointD(100.0, 0.0)) ),
-    m_grid0( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD(  0.0,-50.0)) ),
-    m_grid1( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD( 25.0,-75.0)) )
-  {
-    addProperty( new TBoolProperty(m_idRestricktA.str(), getRestrictA()) );
-    addProperty( new TBoolProperty(m_idRestricktB.str(), getRestrictB()) );
-    addProperty( new TBoolProperty(m_idParallel.str(), getParallel()) );
-    addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
-    addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) );
-  }
-
-  static QString getLocalName()
-    { return tr("Line"); }
-
-  void updateTranslation() const override {
-    TAssistant::updateTranslation();
-    setTranslation(m_idRestricktA, tr("Restrict A"));
-    setTranslation(m_idRestricktB, tr("Restrict B"));
-    setTranslation(m_idParallel, tr("Parallel"));
-    setTranslation(m_idGrid, tr("Grid"));
-    setTranslation(m_idPerspective, tr("Perspective"));
-  }
-
-  inline bool getRestrictA() const
-    { return data()[m_idRestricktA].getBool(); }
-  inline bool getRestrictB() const
-    { return data()[m_idRestricktB].getBool(); }
-  inline bool getParallel() const
-    { return data()[m_idParallel].getBool(); }
-  inline bool getGrid() const
-    { return data()[m_idGrid].getBool(); }
-  inline bool getPerspective() const
-    { return data()[m_idPerspective].getBool(); }
-
-  void onDataChanged(const TVariant &value) override {
-    TAssistant::onDataChanged(value);
-    m_grid0.visible = getGrid()
-                   || (getParallel() && (getRestrictA() || getRestrictB()));
-    m_grid1.visible = getGrid();
-  }
-
-private:
-  void fixGrid1(const TPointD &previousA, const TPointD &previousB) {
-    TPointD dx = previousB - previousA;
-    double l = norm2(dx);
-    if (l <= TConsts::epsilon*TConsts::epsilon) return;
-    dx = dx*(1.0/sqrt(l));
-    TPointD dy(-dx.y, dx.x);
 
+TAssistantLine::TAssistantLine(TMetaObject &object):
+  TAssistant(object),
+  m_idRestricktA("restrictA"),
+  m_idRestricktB("restrictB"),
+  m_idParallel("parallel"),
+  m_idGrid("grid"),
+  m_idPerspective("perspective"),
+  m_a( addPoint("a", TAssistantPoint::CircleCross) ),
+  m_b( addPoint("b", TAssistantPoint::Circle, TPointD(100.0, 0.0)) ),
+  m_grid0( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD(  0.0,-50.0)) ),
+  m_grid1( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD( 25.0,-75.0)) )
+{
+  addProperty( new TBoolProperty(m_idRestricktA.str(), getRestrictA()) );
+  addProperty( new TBoolProperty(m_idRestricktB.str(), getRestrictB()) );
+  addProperty( new TBoolProperty(m_idParallel.str(), getParallel()) );
+  addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
+  addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) );
+}
+
+
+QString TAssistantLine::getLocalName()
+  { return tr("Line"); }
+
+
+void TAssistantLine::updateTranslation() const {
+  TAssistant::updateTranslation();
+  setTranslation(m_idRestricktA, tr("Restrict A"));
+  setTranslation(m_idRestricktB, tr("Restrict B"));
+  setTranslation(m_idParallel, tr("Parallel"));
+  setTranslation(m_idGrid, tr("Grid"));
+  setTranslation(m_idPerspective, tr("Perspective"));
+}
+
+
+void TAssistantLine::onDataChanged(const TVariant &value) {
+  TAssistant::onDataChanged(value);
+  m_grid0.visible = getGrid()
+                  || (getParallel() && (getRestrictA() || getRestrictB()));
+  m_grid1.visible = getGrid();
+}
+
+
+void TAssistantLine::fixGrid(const TPointD &prevA, const TPointD &prevB) {
+  TPointD dx0 = prevB - prevA;
+  TPointD dx1 = m_b.position - m_a.position;
+  double l0 = norm2(dx0);
+  double l1 = norm2(dx1);
+  if (!( l0 > TConsts::epsilon*TConsts::epsilon
+     &&  l1 > TConsts::epsilon*TConsts::epsilon )) return;
+  dx0 *= 1/sqrt(l0);
+  dx1 *= 1/sqrt(l1);
+  TPointD dy0(-dx0.y, dx0.x);
+  TPointD dy1(-dx1.y, dx1.x);
+
+  if (getParallel()) {
     TPointD g1 = m_grid1.position - m_grid0.position;
-    g1 = TPointD(dx*g1, dy*g1);
-
-    dx = m_b.position - m_a.position;
-    l = norm2(dx);
-    if (l <= TConsts::epsilon*TConsts::epsilon) return;
-    dx = dx*(1.0/sqrt(l));
-    dy = TPointD(-dx.y, dx.x);
-
-    m_grid1.position = m_grid0.position + dx*g1.x + dy*g1.y;
-  }
-
-public:
-  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
-    TPointD previousA = m_a.position;
-    TPointD previousB = m_b.position;
-    point.position = position;
-    if (&point != &m_grid1)
-      fixGrid1(previousA, previousB);
+    m_grid1.position = m_grid0.position + g1*dx0*dx1 + g1*dy0*dy1;
+  } else {
+    TPointD g0 = m_grid0.position - prevA;
+    TPointD g1 = m_grid1.position - prevA;
+    m_grid0.position = m_a.position + g0*dx0*dx1 + g0*dy0*dy1;
+    m_grid1.position = m_a.position + g1*dx0*dx1 + g1*dy0*dy1;
   }
-
-  void getGuidelines(
-    const TPointD &position,
-    const TAffine &toTool,
-    TGuidelineList &outGuidelines ) const override
-  {
-    bool restrictA = getRestrictA();
-    bool restrictB = getRestrictB();
-    bool parallel = getParallel();
-    bool perspective = getPerspective();
-
-    TPointD a = toTool*m_a.position;
-    TPointD b = toTool*m_b.position;
-    TPointD ab = b - a;
-    double abLen2 = norm2(ab);
-    if (abLen2 < TConsts::epsilon*TConsts::epsilon) return;
-
-    if (parallel) {
-      TPointD abp = rotate90(ab);
-      TPointD ag = toTool*m_grid0.position - a;
-      double k = abp*ag;
-      if (fabs(k) <= TConsts::epsilon) {
-        if (restrictA || restrictB) return;
-        a = position;
-      } else {
-        k = (abp*(position - a))/k;
-        a = a + ag*k;
-      }
-      if (perspective && (restrictA || restrictB))
-        ab = ab*k;
-      b = a + ab;
+}
+
+
+void TAssistantLine::onMovePoint(TAssistantPoint &point, const TPointD &position) {
+  TPointD prevA = m_a.position;
+  TPointD prevB = m_b.position;
+  point.position = position;
+  if (&point != &m_grid1)
+    fixGrid(prevA, prevB);
+}
+
+
+void TAssistantLine::getGuidelines(
+  const TPointD &position,
+  const TAffine &toTool,
+  TGuidelineList &outGuidelines ) const
+{
+  bool restrictA = getRestrictA();
+  bool restrictB = getRestrictB();
+  bool parallel = getParallel();
+  bool perspective = getPerspective();
+
+  TPointD a = toTool*m_a.position;
+  TPointD b = toTool*m_b.position;
+  TPointD ab = b - a;
+  double abLen2 = norm2(ab);
+  if (abLen2 < TConsts::epsilon*TConsts::epsilon) return;
+
+  if (parallel) {
+    TPointD abp = rotate90(ab);
+    TPointD ag = toTool*m_grid0.position - a;
+    double k = abp*ag;
+    if (fabs(k) <= TConsts::epsilon) {
+      if (restrictA || restrictB) return;
+      a = position;
+    } else {
+      k = (abp*(position - a))/k;
+      a = a + ag*k;
     }
-
-    if (restrictA && restrictB)
-      outGuidelines.push_back(TGuidelineP(
-        new TGuidelineLine(
-          getEnabled(), getMagnetism(), a,  b )));
-    else if (restrictA)
-      outGuidelines.push_back(TGuidelineP(
-        new TGuidelineRay(
-          getEnabled(), getMagnetism(), a,  b )));
-    else if (restrictB)
-      outGuidelines.push_back(TGuidelineP(
-        new TGuidelineRay(
-          getEnabled(), getMagnetism(), b,  a ))); // b first
-    else
-      outGuidelines.push_back(TGuidelineP(
-        new TGuidelineInfiniteLine(
-          getEnabled(), getMagnetism(), a,  b )));
+    if (perspective && (restrictA || restrictB))
+      ab = ab*k;
+    b = a + ab;
   }
 
-private:
-  void drawRuler(const TPointD &a, const TPointD &b, double pixelSize, bool perspective) const {
-    double minStep = 10.0*pixelSize;
-    double alpha = getDrawingAlpha();
-
-    TPointD direction = b - a;
-    double l2 = norm2(direction);
-    if (l2 <= TConsts::epsilon*TConsts::epsilon) return;
-    double dirLen = sqrt(l2);
-    TPointD dirProj = direction*(1.0/l2);
-    TPointD normal = TPointD(-direction.y, direction.x)*(1.0/dirLen);
-
-    double xg0 = dirProj*(m_grid0.position - a);
-    double xg1 = dirProj*(m_grid1.position - a);
-
-    if (perspective) {
-      // draw perspective
-      double xa0 = dirProj*(m_a.position - a);
-      double k = 0.0, begin = 0.0, end = 0.0;
-      if (!calcPerspectiveStep(minStep/dirLen, 0.0, 1.0, xa0, xg0, xg1, k, begin, end)) return;
-      for(double x = begin; fabs(x) < fabs(end); x *= k)
-        drawMark(a + direction*(xa0 + x), normal, pixelSize, alpha);
-    } else {
-      // draw linear
-      double dx = fabs(xg1 - xg0);
-      if (dx*dirLen < minStep) return;
-      for(double x = xg0 - floor(xg0/dx)*dx; x < 1.0; x += dx)
-        drawMark(a + direction*x, normal, pixelSize, alpha);
+  if (restrictA && restrictB)
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineLine(
+        getEnabled(), getMagnetism(), a,  b )));
+  else if (restrictA)
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineRay(
+        getEnabled(), getMagnetism(), a,  b )));
+  else if (restrictB)
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineRay(
+        getEnabled(), getMagnetism(), b,  a ))); // b first
+  else
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineInfiniteLine(
+        getEnabled(), getMagnetism(), a,  b )));
+}
+
+
+void TAssistantLine::drawRuler(
+  const TPointD &a,
+  const TPointD &b,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  const TPointD *perspectiveBase,
+  double alpha )
+{
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = (perspectiveBase ? 5 : 10)*pixelSize;
+
+  TPointD direction = b - a;
+  double l2 = norm2(direction);
+  if (l2 <= TConsts::epsilon*TConsts::epsilon) return;
+  double dirLen = sqrt(l2);
+  TPointD dirProj = direction*(1.0/l2);
+  TPointD normal = TPointD(-direction.y, direction.x)*(1.0/dirLen);
+
+  double xg0 = dirProj*(grid0 - a);
+  double xg1 = dirProj*(grid1 - a);
+
+  if (perspectiveBase) {
+    // draw perspective
+    double xa0 = dirProj*(*perspectiveBase - a);
+    double w, i0, i1;
+    if (!calcPerspectiveStep(minStep/dirLen, 0, 1, xa0, xg0, xg1, w, i0, i1)) return;
+    for(double i = i0; i < i1; i += 1) {
+      double x = xa0 + 1/(i*w + 1);
+      drawMark(a + direction*x, normal, pixelSize, alpha);
     }
+  } else {
+    // draw linear
+    double dx = fabs(xg1 - xg0);
+    if (dx*dirLen < minStep) return;
+    for(double x = xg0 - floor(xg0/dx)*dx; x < 1.0; x += dx)
+      drawMark(a + direction*x, normal, pixelSize, alpha);
+  }
+}
+
+
+void TAssistantLine::drawLine(
+  const TAffine &matrix,
+  const TAffine &matrixInv,
+  double pixelSize,
+  const TPointD &a,
+  const TPointD &b,
+  bool restrictA,
+  bool restrictB,
+  double alpha )
+{
+  const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+  TPointD aa = matrix*a;
+  TPointD bb = matrix*b;
+  if ( restrictA && restrictB ? TGuidelineLineBase::truncateLine(oneBox, aa, bb)
+      : restrictA             ? TGuidelineLineBase::truncateRay (oneBox, aa, bb)
+      : restrictB             ? TGuidelineLineBase::truncateRay (oneBox, bb, aa) // aa first
+      :                 TGuidelineLineBase::truncateInfiniteLine(oneBox, aa, bb) )
+        drawSegment(matrixInv*aa, matrixInv*bb, pixelSize, alpha);
+}
+
+
+void TAssistantLine::drawGrid(
+  const TPointD &a,
+  const TPointD &b,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  bool restrictA,
+  bool restrictB,
+  bool perspective,
+  double alpha )
+{
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine matrix = (projection*modelview).get2d();
+  TAffine matrixInv = matrix.inv();
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = (perspective ? 2.5 : 10)*pixelSize;
+
+  TPointD ab = b - a;
+  double abLen2 = norm2(ab);
+  if (abLen2 < TConsts::epsilon*TConsts::epsilon) return;
+  double abLen = sqrt(abLen2);
+
+  TPointD abp = rotate90(ab);
+  TPointD ag = grid0 - a;
+  if (fabs(abp*ag) <= TConsts::epsilon) {
+    if (restrictA || restrictB) return;
+    ag = abp;
+  }
+  double agLen2 = norm2(ag);
+  if (agLen2 < TConsts::epsilon*TConsts::epsilon) return;
+  double abpAgK = 1.0/(abp*ag);
+  TPointD abpAgProj = abp*abpAgK;
+
+  // draw restriction lines
+  if (perspective) {
+    if (restrictA) drawLine(matrix, matrixInv, pixelSize, a, a + ag, false, false, alpha);
+    if (restrictB) drawLine(matrix, matrixInv, pixelSize, a, a + ag + ab, false, false, alpha);
+    // horizon
+    if (!restrictA) drawLine(matrix, matrixInv, pixelSize, a - ab, a, restrictA, restrictB, alpha); else
+    if (!restrictB) drawLine(matrix, matrixInv, pixelSize, a, a + ab, restrictA, restrictB, alpha);
+  } else {
+    if (restrictA) drawLine(matrix, matrixInv, pixelSize, a, a + ag, false, false, alpha);
+    if (restrictB) drawLine(matrix, matrixInv, pixelSize, b, b + ag, false, false, alpha);
   }
 
-  void drawLine(
-    const TAffine &matrix,
-    const TAffine &matrixInv,
-    double pixelSize,
-    const TPointD &a,
-    const TPointD &b,
-    bool restrictA,
-    bool restrictB,
-    double alpha ) const
-  {
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    TPointD aa = matrix*a;
-    TPointD bb = matrix*b;
-    if ( restrictA && restrictB ? TGuidelineLineBase::truncateLine(oneBox, aa, bb)
-       : restrictA              ? TGuidelineLineBase::truncateRay (oneBox, aa, bb)
-       : restrictB              ? TGuidelineLineBase::truncateRay (oneBox, bb, aa) // aa first
-       :                  TGuidelineLineBase::truncateInfiniteLine(oneBox, aa, bb) )
-          drawSegment(matrixInv*aa, matrixInv*bb, pixelSize, alpha);
+  double minStepX = fabs(minStep*abLen*abpAgK);
+  if (minStepX <= TConsts::epsilon) return;
+
+  // calculate bounds
+  const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+  TPointD corners[4] = {
+    TPointD(oneBox.x0, oneBox.y0),
+    TPointD(oneBox.x0, oneBox.y1),
+    TPointD(oneBox.x1, oneBox.y0),
+    TPointD(oneBox.x1, oneBox.y1) };
+  double minX = 0.0, maxX = 0.0;
+  for(int i = 0; i < 4; ++i) {
+    double x = abpAgProj * (matrixInv*corners[i] - a);
+    if (i == 0 || x < minX) minX = x;
+    if (i == 0 || x > maxX) maxX = x;
   }
+  if (maxX <= minX) return;
 
-  void drawGrid(
-    const TAffine &matrix,
-    const TAffine &matrixInv,
-    double pixelSize,
-    bool restrictA,
-    bool restrictB,
-    bool perspective ) const
-  {
-    double minStep = 10.0*pixelSize;
-
-    double alpha = getDrawingGridAlpha();
-    TPointD a = m_a.position;
-    TPointD b = m_b.position;
-    TPointD ab = b - a;
-    double abLen2 = norm2(ab);
-    if (abLen2 < TConsts::epsilon*TConsts::epsilon) return;
-    double abLen = sqrt(abLen2);
-
-    TPointD g0 = m_grid0.position;
-    TPointD g1 = m_grid1.position;
+  double x0 = abpAgProj*(grid0 - a);
+  double x1 = abpAgProj*(grid1 - a);
 
-    TPointD abp = rotate90(ab);
-    TPointD ag = g0 - a;
-    if (fabs(abp*ag) <= TConsts::epsilon) {
-      if (restrictA || restrictB) return;
-      ag = abp;
-    }
-    double agLen2 = norm2(ag);
-    if (agLen2 < TConsts::epsilon*TConsts::epsilon) return;
-    double agLen = sqrt(agLen2);
-    double abpAgK = 1.0/(abp*ag);
-    TPointD abpAgProj = abp*abpAgK;
-
-    // draw restriction lines
-    if (perspective) {
-      if (restrictA) drawLine(matrix, matrixInv, pixelSize, a, a + ag, false, false, alpha);
-      if (restrictB) drawLine(matrix, matrixInv, pixelSize, a, a + ag + ab, false, false, alpha);
-    } else {
-      if (restrictA) drawLine(matrix, matrixInv, pixelSize, a, a + ag, false, false, alpha);
-      if (restrictB) drawLine(matrix, matrixInv, pixelSize, b, b + ag, false, false, alpha);
+  if (perspective) {
+    double w, i0, i1;
+    minStepX /= 2;
+    
+    if (!calcPerspectiveStep(minStepX, minX, maxX, 0, x0, x1, w, i0, i1)) return;
+    double abk = 1.0/fabs(x0);
+    for(double i = i0; i < i1; i += 1) {
+      double x = 1/(i*w + 1);
+      
+      double curStep = fabs(w*x*x);
+      double curAlpha = (curStep - minStepX)/minStepX;
+      if (curAlpha < 0) continue;
+      if (curAlpha > 1) curAlpha = 1;
+      
+      TPointD ca = a + ag*x;
+      TPointD cb = ca + ab*(abk*x);
+      drawLine(matrix, matrixInv, pixelSize, ca, cb, restrictA, restrictB, alpha*curAlpha);
     }
-
-    double minStepX = fabs(minStep*abLen*abpAgK);
-    if (minStepX <= TConsts::epsilon) return;
-
-    // calculate bounds
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    TPointD corners[4] = {
-      TPointD(oneBox.x0, oneBox.y0),
-      TPointD(oneBox.x0, oneBox.y1),
-      TPointD(oneBox.x1, oneBox.y0),
-      TPointD(oneBox.x1, oneBox.y1) };
-    double minX = 0.0, maxX = 0.0;
-    for(int i = 0; i < 4; ++i) {
-      double x = abpAgProj * (matrixInv*corners[i] - a);
-      if (i == 0 || x < minX) minX = x;
-      if (i == 0 || x > maxX) maxX = x;
+  } else {
+    double dx = fabs(x1 - x0);
+    if (dx < minStepX) return;
+    for(double x = x0 + ceil((minX - x0)/dx)*dx; x < maxX; x += dx) {
+      TPointD ca = a + ag*x;
+      drawLine(matrix, matrixInv, pixelSize, ca, ca + ab, restrictA, restrictB, alpha);
     }
-    if (maxX <= minX) return;
-
-    double x0 = abpAgProj*(g0 - a);
-    double x1 = abpAgProj*(g1 - a);
-
-    if (perspective) {
-      double k = 0.0, begin = 0.0, end = 0.0;
-      if (!calcPerspectiveStep(minStepX, minX, maxX, 0.0, x0, x1, k, begin, end)) return;
-      double abk = 1.0/fabs(x0);
-      for(double x = begin; fabs(x) < fabs(end); x *= k) {
-        TPointD ca = a + ag*x;
-        TPointD cb = ca + ab*(abk*x);
-        drawLine(matrix, matrixInv, pixelSize, ca, cb, restrictA, restrictB, alpha);
-      }
+  }
+}
+
+
+void TAssistantLine::draw(TToolViewer*, bool enabled) const {
+  double alpha = getDrawingAlpha(enabled);
+  bool restrictA = getRestrictA();
+  bool restrictB = getRestrictB();
+  bool parallel = getParallel();
+  bool grid = getGrid();
+  bool perspective = getPerspective();
+
+  // common data about viewport
+  const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine matrix = (projection*modelview).get2d();
+  TAffine matrixInv = matrix.inv();
+  double pixelSize = sqrt(tglGetPixelSize2());
+
+  // calculate range
+  TPointD aa = matrix*m_a.position;
+  TPointD bb = matrix*m_b.position;
+  bool success = false;
+  if (restrictA && restrictB)
+    success = TGuidelineLineBase::truncateLine(oneBox, aa, bb);
+  else if (restrictA)
+    success = TGuidelineLineBase::truncateRay(oneBox, aa, bb);
+  else if (restrictB)
+    success = TGuidelineLineBase::truncateRay(oneBox, bb, aa);
+  else
+    success = TGuidelineLineBase::truncateInfiniteLine(oneBox, aa, bb);
+
+  if (!success) {
+    // line is out of screen, bud grid still can be visible
+    if (grid && getParallel())
+        drawGrid(
+          m_a.position,
+          m_b.position,
+          m_grid0.position,
+          m_grid1.position,
+          restrictA,
+          restrictB,
+          perspective,
+          getDrawingGridAlpha() );
+    return;
+  }
+  
+  TPointD a = matrixInv*aa;
+  TPointD b = matrixInv*bb;
+
+  // draw line
+  drawSegment(a, b, pixelSize, alpha);
+
+  // draw restriction marks
+  if (restrictA || (!parallel && grid && perspective))
+    drawDot(m_a.position);
+  if (restrictB)
+    drawDot(m_b.position);
+
+  if (grid) {
+    if (getParallel()) {
+      drawGrid(
+        m_a.position,
+        m_b.position,
+        m_grid0.position,
+        m_grid1.position,
+        restrictA,
+        restrictB,
+        perspective,
+        getDrawingGridAlpha() );
     } else {
-      double dx = fabs(x1 - x0);
-      if (dx < minStepX) return;
-      for(double x = x0 + ceil((minX - x0)/dx)*dx; x < maxX; x += dx) {
-        TPointD ca = a + ag*x;
-        drawLine(matrix, matrixInv, pixelSize, ca, ca + ab, restrictA, restrictB, alpha);
-      }
+      drawRuler(
+        a, b, m_grid0.position, m_grid1.position,
+        perspective ? &m_a.position : nullptr, getDrawingAlpha() );
     }
   }
+}
 
-public:
-  void draw(TToolViewer *viewer, bool enabled) const override {
-    double alpha = getDrawingAlpha(enabled);
-    bool restrictA = getRestrictA();
-    bool restrictB = getRestrictB();
-    bool parallel = getParallel();
-    bool grid = getGrid();
-    bool perspective = getPerspective();
-
-    // common data about viewport
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    TAffine4 modelview, projection;
-    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
-    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
-    TAffine matrix = (projection*modelview).get2d();
-    TAffine matrixInv = matrix.inv();
-    double pixelSize = sqrt(tglGetPixelSize2());
-
-    // calculate range
-    TPointD aa = matrix*m_a.position;
-    TPointD bb = matrix*m_b.position;
-    bool success = false;
-    if (restrictA && restrictB)
-      success = TGuidelineLineBase::truncateLine(oneBox, aa, bb);
-    else if (restrictA)
-      success = TGuidelineLineBase::truncateRay(oneBox, aa, bb);
-    else if (restrictB)
-      success = TGuidelineLineBase::truncateRay(oneBox, bb, aa);
-    else
-      success = TGuidelineLineBase::truncateInfiniteLine(oneBox, aa, bb);
-
-    if (!success) {
-      // line is out of screen, bud grid still can be visible
-      if (grid && getParallel())
-          drawGrid(matrix, matrixInv, pixelSize, restrictA, restrictB, perspective);
-      return;
-    }
-    
-    TPointD a = matrixInv*aa;
-    TPointD b = matrixInv*bb;
-
-    // draw line
-    drawSegment(a, b, pixelSize, alpha);
-
-    // draw restriction marks
-    if (restrictA || (!parallel && grid && perspective))
-      drawDot(m_a.position);
-    if (restrictB)
-      drawDot(m_b.position);
-
-    if (grid) {
-      if (getParallel()) {
-        drawGrid(matrix, matrixInv, pixelSize, restrictA, restrictB, perspective);
-      } else {
-        drawRuler(a, b, pixelSize, perspective);
-      }
-    }
-  }
-};
 
 
 //*****************************************************************************************
diff --git a/toonz/sources/tnztools/assistants/assistantline.h b/toonz/sources/tnztools/assistants/assistantline.h
new file mode 100644
index 0000000..135ac5f
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/assistantline.h
@@ -0,0 +1,98 @@
+#pragma once
+
+#ifndef ASSISTANTLINE_INCLUDED
+#define ASSISTANTLINE_INCLUDED
+
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/assistants/guidelineline.h>
+
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TAssistantLine definition
+//*****************************************************************************************
+
+class TAssistantLine final : public TAssistant {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantVanishingPoint)
+public:
+  const TStringId m_idRestricktA;
+  const TStringId m_idRestricktB;
+  const TStringId m_idParallel;
+  const TStringId m_idGrid;
+  const TStringId m_idPerspective;
+
+protected:
+  TAssistantPoint &m_a;
+  TAssistantPoint &m_b;
+  TAssistantPoint &m_grid0;
+  TAssistantPoint &m_grid1;
+
+public:
+  TAssistantLine(TMetaObject &object);
+
+  static QString getLocalName();
+
+  void updateTranslation() const override;
+
+  inline bool getRestrictA() const
+    { return data()[m_idRestricktA].getBool(); }
+  inline bool getRestrictB() const
+    { return data()[m_idRestricktB].getBool(); }
+  inline bool getParallel() const
+    { return data()[m_idParallel].getBool(); }
+  inline bool getGrid() const
+    { return data()[m_idGrid].getBool(); }
+  inline bool getPerspective() const
+    { return data()[m_idPerspective].getBool(); }
+
+  void onDataChanged(const TVariant &value) override;
+
+private:
+  void fixGrid(const TPointD &prevA, const TPointD &prevB);
+
+public:
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override;
+
+  void getGuidelines(
+    const TPointD &position,
+    const TAffine &toTool,
+    TGuidelineList &outGuidelines ) const override;
+
+  static void drawRuler(
+    const TPointD &a,
+    const TPointD &b,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    const TPointD *perspectiveBase,
+    double alpha );
+
+  static void drawLine(
+    const TAffine &matrix,
+    const TAffine &matrixInv,
+    double pixelSize,
+    const TPointD &a,
+    const TPointD &b,
+    bool restrictA,
+    bool restrictB,
+    double alpha );
+
+  static void drawGrid(
+    const TPointD &a,
+    const TPointD &b,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    bool restrictA,
+    bool restrictB,
+    bool perspective,
+    double alpha );
+
+  void draw(TToolViewer *viewer, bool enabled) const override;
+};
+
+
+#endif
diff --git a/toonz/sources/tnztools/assistants/assistantperspective.cpp b/toonz/sources/tnztools/assistants/assistantperspective.cpp
new file mode 100644
index 0000000..3aabbe3
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/assistantperspective.cpp
@@ -0,0 +1,453 @@
+
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/assistants/guidelineline.h>
+
+#include "assistantline.h"
+#include "assistantvanishingpoint.h"
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TAssistantPerspective implementation
+//*****************************************************************************************
+
+class TAssistantPerspective final : public TAssistant {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantPerspective)
+public:
+  const TStringId m_idParallelX;
+  const TStringId m_idParallelY;
+  const TStringId m_idParallelZ;
+  const TStringId m_idGridXY;
+  const TStringId m_idGridYZ;
+  const TStringId m_idGridZX;
+  const TStringId m_idShowBox;
+
+protected:
+  TAssistantPoint &m_o;
+  TAssistantPoint &m_x;
+  TAssistantPoint &m_y;
+  TAssistantPoint &m_z;
+  TAssistantPoint &m_xy;
+  TAssistantPoint &m_yz;
+  TAssistantPoint &m_zx;
+  TAssistantPoint &m_vx;
+  TAssistantPoint &m_vy;
+  TAssistantPoint &m_vz;
+  
+public:
+  TAssistantPerspective(TMetaObject &object):
+    TAssistant(object),
+    m_idParallelX("parallelX"),
+    m_idParallelY("parallelY"),
+    m_idParallelZ("parallelZ"),
+    m_idGridXY("gridXY"),
+    m_idGridYZ("gridYZ"),
+    m_idGridZX("gridZX"),
+    m_idShowBox("showBox"),
+    m_o    ( addPoint("o",     TAssistantPoint::CircleCross)                          ),
+    m_x    ( addPoint("x",     TAssistantPoint::CircleFill,       TPointD(  50,  0 )) ),
+    m_y    ( addPoint("y",     TAssistantPoint::CircleFill,       TPointD(   0, 50 )) ),
+    m_z    ( addPoint("z",     TAssistantPoint::CircleFill,       TPointD(  25, 25 )) ),
+    m_xy   ( addPoint("xy",    TAssistantPoint::Circle,           TPointD(  50, 50 )) ),
+    m_yz   ( addPoint("yz",    TAssistantPoint::Circle,           TPointD(  25, 25 )) ),
+    m_zx   ( addPoint("zx",    TAssistantPoint::Circle,           TPointD(  75, 25 )) ),
+    m_vx   ( addPoint("vx",    TAssistantPoint::Circle)                               ),
+    m_vy   ( addPoint("vy",    TAssistantPoint::Circle)                               ),
+    m_vz   ( addPoint("vz",    TAssistantPoint::Circle)                               )
+  {
+    addProperty( new TBoolProperty(m_idParallelX.str(), getParallelX()) );
+    addProperty( new TBoolProperty(m_idParallelY.str(), getParallelY()) );
+    addProperty( new TBoolProperty(m_idParallelZ.str(), getParallelZ()) );
+    addProperty( new TBoolProperty(m_idGridXY.str(), getGridXY()) );
+    addProperty( new TBoolProperty(m_idGridYZ.str(), getGridYZ()) );
+    addProperty( new TBoolProperty(m_idGridZX.str(), getGridZX()) );
+    addProperty( new TBoolProperty(m_idShowBox.str(), getShowBox()) );
+  }
+
+
+  static QString getLocalName()
+    { return tr("Perspective"); }
+
+
+  void updateTranslation() const override {
+    TAssistant::updateTranslation();
+    setTranslation(m_idParallelX, tr("Parallel X"));
+    setTranslation(m_idParallelY, tr("Parallel Y"));
+    setTranslation(m_idParallelZ, tr("Parallel Z"));
+    setTranslation(m_idGridXY, tr("Grid XY"));
+    setTranslation(m_idGridYZ, tr("Grid YZ"));
+    setTranslation(m_idGridZX, tr("Grid ZX"));
+    setTranslation(m_idShowBox, tr("Show Box"));
+  }
+
+
+  inline bool getParallelX() const
+    { return data()[m_idParallelX].getBool(); }
+  inline bool getParallelY() const
+    { return data()[m_idParallelY].getBool(); }
+  inline bool getParallelZ() const
+    { return data()[m_idParallelZ].getBool(); }
+  inline bool getGridXY() const
+    { return data()[m_idGridXY].getBool(); }
+  inline bool getGridYZ() const
+    { return data()[m_idGridYZ].getBool(); }
+  inline bool getGridZX() const
+    { return data()[m_idGridZX].getBool(); }
+  inline bool getShowBox() const
+    { return data()[m_idShowBox].getBool(); }
+
+  void onDataChanged(const TVariant &value) override {
+    TAssistant::onDataChanged(value);
+    m_xy.visible = !getParallelX() || !getParallelY();
+    m_yz.visible = !getParallelY() || !getParallelZ();
+    m_zx.visible = !getParallelZ() || !getParallelX();
+    fixPoints();
+  }
+
+
+private:
+  void fixAxisPoint(
+    TPointD &a,
+    const TAssistantPoint &v,
+    const TPointD &oldV ) const
+  {
+    const TPointD &o = m_o.position;
+    if (!v.visible)
+      { a = o + v.position; return; }
+    
+    TPointD dv = v.position - o;
+    TPointD oldDv = oldV - o;
+    double l = norm2(oldDv);
+    double ln = norm2(dv);
+    if (!(l > TConsts::epsilon) || !(ln > TConsts::epsilon))
+      return;
+    
+    double d = (a - o)*oldDv;
+    a = o + dv*(d/l);
+  }
+  
+
+  inline void fixAxisPoint(
+    TAssistantPoint &a,
+    const TAssistantPoint &v,
+    const TPointD &oldV ) const
+      { fixAxisPoint(a.position, v, oldV); }
+
+  
+  void fixVanishingPoint(
+    TAssistantPoint &v,
+    const TPointD &a,
+    const TPointD &oldA ) const
+  {
+    const TPointD &o = m_o.position;
+    TPointD da = a - o;
+    if (!v.visible)
+      { v.position = da; return; }
+    
+    TPointD oldDa = oldA - o;
+    double l = norm2(oldDa);
+    double ln = norm2(da);
+    if (!(l > TConsts::epsilon) || !(ln > TConsts::epsilon))
+      return;
+    
+    double d = (v.position - o)*oldDa;
+    v.position = o + da*(d/l);
+  }
+  
+
+  inline void fixVanishingPoint(
+    TAssistantPoint &v,
+    const TAssistantPoint &a,
+    const TPointD &oldA ) const
+      { fixVanishingPoint(v, a.position, oldA); }
+
+
+  void fixVanishingPoint(
+    TAssistantPoint &v,
+    const TPointD &a0,
+    const TPointD &a1,
+    const TPointD &b0,
+    const TPointD &b1 ) const
+  {
+    TPointD da = a1 - a0;
+    TPointD db = b1 - b0;
+    const TPointD ab = b0 - a0;
+    double k = db.x*da.y - db.y*da.x;
+
+    if ( (&v == &m_vx && getParallelX())
+      || (&v == &m_vy && getParallelY())
+      || (&v == &m_vz && getParallelZ()) )
+        k = 0;
+    
+    if (fabs(k) > TConsts::epsilon) {
+      double lb = (da.x*ab.y - da.y*ab.x)/k;
+      v.position.x = lb*db.x + b0.x;
+      v.position.y = lb*db.y + b0.y;
+      v.visible = true;
+    } else {
+      v.position = da;
+      v.visible = false;
+    }
+  }
+
+
+  inline void fixVanishingPoint(
+    TAssistantPoint &v,
+    const TAssistantPoint &a0,
+    const TAssistantPoint &a1,
+    const TAssistantPoint &b0,
+    const TAssistantPoint &b1 ) const
+      { fixVanishingPoint(v, a0.position, a1.position, b0.position, b1.position); }
+
+  
+  static bool lineCross(
+    TPointD &p,
+    const TPointD &a,
+    const TPointD &b,
+    const TPointD &da,
+    const TPointD &db )
+  {
+    double d = da.x*db.y - da.y*db.x;
+    if (!(fabs(d) > TConsts::epsilon))
+      return false;
+    d = 1/d;
+    p = TPointD(
+      (a.y*db.x + b.x*db.y)*da.x - (a.x*da.y + b.y*da.x)*db.x,
+      (a.y*da.x + b.x*da.y)*db.y - (a.x*db.y + b.y*db.x)*da.y )*d;
+    return true;
+  }
+  
+
+  void fixSidePoint(
+    TPointD &p,
+    const TPointD &a, // pass 'a' and 'b' by copy
+    const TPointD &b, 
+    const TAssistantPoint &va,
+    const TAssistantPoint &vb ) const
+  {
+    
+    TPointD da = va.visible ? va.position - a : va.position;
+    TPointD db = vb.visible ? vb.position - b : vb.position;
+    lineCross(p, a, b, da, db);
+  }
+
+
+  inline void fixSidePoint(
+    TAssistantPoint &p,
+    const TAssistantPoint &a,
+    const TAssistantPoint &b,
+    const TAssistantPoint &va,
+    const TAssistantPoint &vb ) const
+  { fixSidePoint(p.position, a.position, b.position, va, vb); }
+
+
+  void fixSidePoints() {
+    fixSidePoint(m_xy, m_x, m_y, m_vy, m_vx);
+    fixSidePoint(m_yz, m_y, m_z, m_vz, m_vy);
+    fixSidePoint(m_zx, m_z, m_x, m_vx, m_vz);
+  }
+
+  
+  void addGuideline(
+    const TPointD &position,
+    const TAffine &toTool,
+    const TAssistantPoint &v,
+    TGuidelineList &outGuidelines ) const
+  {
+    if (v.visible) {
+      TPointD p = toTool * v.position;
+      if (tdistance2(p, position) > 4*TConsts::epsilon*TConsts::epsilon)
+        outGuidelines.push_back(
+          new TGuidelineRay(
+            getEnabled(),
+            getMagnetism(),
+            p,
+            position ));
+    } else {
+      TPointD d = toTool.transformDirection(v.position);
+      if (norm2(d) > 4*TConsts::epsilon*TConsts::epsilon)
+        outGuidelines.push_back(
+          new TGuidelineInfiniteLine(
+            getEnabled(),
+            getMagnetism(),
+            position,
+            position + d ));
+    }
+  }
+      
+public:
+  void onFixPoints() override {
+    fixVanishingPoint(m_vx, m_o, m_x, m_y, m_xy);
+    fixVanishingPoint(m_vy, m_o, m_y, m_x, m_xy);
+    fixVanishingPoint(m_vz, m_o, m_z, m_x, m_zx);
+    fixSidePoints();
+  }
+
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
+    if (!point.visible)
+      return;
+
+    TPointD old = point.position;
+    point.position = position;
+    
+    if (&point == &m_o) {
+      TPointD d = m_o.position - old;
+      m_x.position  += d;
+      m_y.position  += d;
+      m_z.position  += d;
+      m_xy.position += d;
+      m_yz.position += d;
+      m_zx.position += d;
+      if (m_vx.visible) m_vx.position += d;
+      if (m_vy.visible) m_vy.position += d;
+      if (m_vz.visible) m_vz.position += d;
+    } else
+    if (&point == &m_x) {
+      fixVanishingPoint(m_vx, m_x, old);
+      fixSidePoints();
+    } else
+    if (&point == &m_y) {
+      fixVanishingPoint(m_vy, m_y, old);
+      fixSidePoints();
+    } else
+    if (&point == &m_z) {
+      fixVanishingPoint(m_vz, m_z, old);
+      fixSidePoints();
+    } else
+    if (&point == &m_xy) {
+      fixVanishingPoint(m_vx, m_o, m_x, m_y, m_xy);
+      fixVanishingPoint(m_vy, m_o, m_y, m_x, m_xy);
+      fixSidePoints();
+    } else
+    if (&point == &m_yz) {
+      fixVanishingPoint(m_vy, m_o, m_y, m_z, m_yz);
+      fixVanishingPoint(m_vz, m_o, m_z, m_y, m_yz);
+      fixSidePoints();
+    } else
+    if (&point == &m_zx) {
+      fixVanishingPoint(m_vz, m_o, m_z, m_x, m_zx);
+      fixVanishingPoint(m_vx, m_o, m_x, m_z, m_zx);
+      fixSidePoints();
+    } else
+    if (&point == &m_vx) {
+      fixAxisPoint(m_x, m_vx, old);
+      fixSidePoints();
+    } else
+    if (&point == &m_vy) {
+      fixAxisPoint(m_y, m_vy, old);
+      fixSidePoints();
+    } else
+    if (&point == &m_vz) {
+      fixAxisPoint(m_z, m_vz, old);
+      fixSidePoints();
+    }
+  }
+
+  void getGuidelines(
+    const TPointD &position,
+    const TAffine &toTool,
+    TGuidelineList &outGuidelines ) const override
+  {
+    addGuideline(position, toTool, m_vx, outGuidelines);
+    addGuideline(position, toTool, m_vy, outGuidelines);
+    addGuideline(position, toTool, m_vz, outGuidelines);
+  }
+
+  void drawGrid(
+    const TAssistantPoint &vx,
+    const TAssistantPoint &vy,
+    const TPointD &y ) const
+  {
+    double alpha = getDrawingGridAlpha();
+    const TPointD &o = m_o.position;
+    
+    if (!vx.visible && !vy.visible) {
+      TAssistantLine::drawGrid(
+        o, o + vx.position, o, y, false, false, false, alpha );
+      return;
+    }
+    
+    if (!vx.visible) {
+      TAssistantLine::drawGrid(
+        vy.position, vy.position + vx.position, o, y, false, false, true, alpha );
+      return;
+    }
+    
+    TPointD p = y;
+    if (vy.visible) {
+      const TPointD &a = vx.position;
+      const TPointD &b = o;
+      const TPointD da = y - a;
+      const TPointD db = vy.position - vx.position;
+      lineCross(p, a, b, da, db);
+    }
+    
+    TAssistantVanishingPoint::drawPerspectiveGrid(vx.position, o, p, alpha);
+  }
+  
+  void drawVanishingPoint(const TAssistantPoint &v, double pixelSize, double alpha) const {
+    if (!v.visible)
+      return;
+    const TPointD &p = v.position;
+    TPointD dx(20.0*pixelSize, 0.0);
+    TPointD dy(0.0, 10.0*pixelSize);
+    drawSegment(p-dx-dy, p+dx+dy, pixelSize, alpha);
+    drawSegment(p-dx+dy, p+dx-dy, pixelSize, alpha);
+  }
+
+  void drawBox(double alpha) const {
+    double pixelSize = sqrt(tglGetPixelSize2());
+    TPointD xyz;
+    fixSidePoint(xyz, m_xy.position, m_zx.position, m_vz, m_vy);
+    drawSegment(xyz, m_xy.position, pixelSize, alpha);
+    drawSegment(xyz, m_yz.position, pixelSize, alpha);
+    drawSegment(xyz, m_zx.position, pixelSize, alpha);
+    drawSegment(m_xy.position, m_x.position, pixelSize, alpha);
+    drawSegment(m_xy.position, m_y.position, pixelSize, alpha);
+    drawSegment(m_yz.position, m_y.position, pixelSize, alpha);
+    drawSegment(m_yz.position, m_z.position, pixelSize, alpha);
+    drawSegment(m_zx.position, m_z.position, pixelSize, alpha);
+    drawSegment(m_zx.position, m_x.position, pixelSize, alpha);
+    drawSegment(m_o.position, m_x.position, pixelSize, alpha);
+    drawSegment(m_o.position, m_y.position, pixelSize, alpha);
+    drawSegment(m_o.position, m_z.position, pixelSize, alpha);
+  }
+  
+  void draw(TToolViewer*, bool enabled) const override {
+    double pixelSize = sqrt(tglGetPixelSize2());
+    double alpha = getDrawingAlpha(enabled);
+    drawVanishingPoint(m_vx, pixelSize, alpha);
+    drawVanishingPoint(m_vy, pixelSize, alpha);
+    drawVanishingPoint(m_vz, pixelSize, alpha);
+    if (getGridXY()) {
+      drawGrid(m_vx, m_vy, m_y.position);
+      drawGrid(m_vy, m_vx, m_x.position);
+    }
+    if (getGridYZ()) {
+      drawGrid(m_vy, m_vz, m_z.position);
+      drawGrid(m_vz, m_vy, m_y.position);
+    }
+    if (getGridZX()) {
+      drawGrid(m_vz, m_vx, m_x.position);
+      drawGrid(m_vx, m_vz, m_z.position);
+    }
+    if (getShowBox())
+      drawBox(alpha);
+  }
+
+  void drawEdit(TToolViewer *viewer) const override {
+    if (!getShowBox()) drawBox(getDrawingAlpha(false));
+    TAssistant::drawEdit(viewer);
+  }
+};
+
+
+//*****************************************************************************************
+//    Registration
+//*****************************************************************************************
+
+static TAssistantTypeT<TAssistantPerspective> assistantPerspective("assistantPerspective");
diff --git a/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp b/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp
index 61b0792..23e8aa3 100644
--- a/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp
+++ b/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp
@@ -1,375 +1,365 @@
 
 
 // TnzTools includes
-#include <tools/assistant.h>
-#include <tools/assistants/guidelineline.h>
+#include "assistantvanishingpoint.h"
 
-// TnzCore includes
-#include <tgl.h>
 
 
 //*****************************************************************************************
 //    TAssistantVanishingPoint implementation
 //*****************************************************************************************
 
-class TAssistantVanishingPoint final : public TAssistant {
-  Q_DECLARE_TR_FUNCTIONS(TAssistantVanishingPoint)
-public:
-  const TStringId m_idPassThrough;
-  const TStringId m_idGrid;
-  const TStringId m_idPerspective;
-
-protected:
-  TAssistantPoint &m_center;
-  TAssistantPoint &m_a0;
-  TAssistantPoint &m_a1;
-  TAssistantPoint &m_b0;
-  TAssistantPoint &m_b1;
-  TAssistantPoint &m_grid0;
-  TAssistantPoint &m_grid1;
-
-public:
-  TAssistantVanishingPoint(TMetaObject &object):
-    TAssistant(object),
-    m_idPassThrough("passThrough"),
-    m_idGrid("grid"),
-    m_idPerspective("perspective"),
-    m_center( addPoint("center", TAssistantPoint::CircleCross) ),
-    m_a0    ( addPoint("a0",     TAssistantPoint::Circle, TPointD(-50.0, 0.0)) ),
-    m_a1    ( addPoint("a1",     TAssistantPoint::Circle, TPointD(-75.0, 0.0)) ),
-    m_b0    ( addPoint("b0",     TAssistantPoint::Circle, TPointD( 50.0, 0.0)) ),
-    m_b1    ( addPoint("b1",     TAssistantPoint::Circle, TPointD( 75.0, 0.0)) ),
-    m_grid0 ( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD(  0.0,-50.0)) ),
-    m_grid1 ( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD( 25.0,-50.0)) )
-  {
-    addProperty( new TBoolProperty(m_idPassThrough.str(), getPassThrough()) );
-    addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
-    addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) );
-  }
 
-  static QString getLocalName()
+TAssistantVanishingPoint::TAssistantVanishingPoint(TMetaObject &object):
+  TAssistant(object),
+  m_idPassThrough("passThrough"),
+  m_idGrid("grid"),
+  m_idPerspective("perspective"),
+  m_center( addPoint("center", TAssistantPoint::CircleCross) ),
+  m_a0    ( addPoint("a0",     TAssistantPoint::Circle, TPointD(-50.0, 0.0)) ),
+  m_a1    ( addPoint("a1",     TAssistantPoint::Circle, TPointD(-75.0, 0.0)) ),
+  m_b0    ( addPoint("b0",     TAssistantPoint::Circle, TPointD( 50.0, 0.0)) ),
+  m_b1    ( addPoint("b1",     TAssistantPoint::Circle, TPointD( 75.0, 0.0)) ),
+  m_grid0 ( addPoint("grid0",  TAssistantPoint::CircleDoubleDots, TPointD(  0.0,-50.0)) ),
+  m_grid1 ( addPoint("grid1",  TAssistantPoint::CircleDots,       TPointD( 25.0,-50.0)) )
+{
+  addProperty( new TBoolProperty(m_idPassThrough.str(), getPassThrough()) );
+  addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) );
+  addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) );
+}
+
+
+QString TAssistantVanishingPoint::getLocalName()
     { return tr("Vanishing Point"); }
 
-  void updateTranslation() const override {
-    TAssistant::updateTranslation();
-    setTranslation(m_idPassThrough, tr("Pass Through"));
-    setTranslation(m_idGrid, tr("Grid"));
-    setTranslation(m_idPerspective, tr("Perspective"));
-  }
 
-  inline bool getPassThrough() const
-    { return data()[m_idPassThrough].getBool(); }
-  inline bool getGrid() const
-    { return data()[m_idGrid].getBool(); }
-  inline bool getPerspective() const
-    { return data()[m_idPerspective].getBool(); }
+void TAssistantVanishingPoint::updateTranslation() const {
+  TAssistant::updateTranslation();
+  setTranslation(m_idPassThrough, tr("Pass Through"));
+  setTranslation(m_idGrid, tr("Grid"));
+  setTranslation(m_idPerspective, tr("Perspective"));
+}
 
-  void onDataChanged(const TVariant &value) override {
-    TAssistant::onDataChanged(value);
-    m_grid0.visible = m_grid1.visible = getGrid();
-  }
 
-private:
-  void fixCenter() {
-    if ( !(m_a0.position == m_a1.position)
-      && !(m_b0.position == m_b1.position) )
-    {
-      const TPointD &a = m_a0.position;
-      const TPointD &b = m_b0.position;
-      const TPointD da = m_a1.position - a;
-      const TPointD db = m_b1.position - b;
-      const TPointD ab = b - a;
-      double k = db.x*da.y - db.y*da.x;
-      if (fabs(k) > TConsts::epsilon) {
-        double lb = (da.x*ab.y - da.y*ab.x)/k;
-        m_center.position.x = lb*db.x + b.x;
-        m_center.position.y = lb*db.y + b.y;
-      }
+void TAssistantVanishingPoint::onDataChanged(const TVariant &value) {
+  TAssistant::onDataChanged(value);
+  m_grid0.visible = m_grid1.visible = getGrid();
+}
+
+
+void TAssistantVanishingPoint::fixCenter() {
+  if ( !(m_a0.position == m_a1.position)
+    && !(m_b0.position == m_b1.position) )
+  {
+    const TPointD &a = m_a0.position;
+    const TPointD &b = m_b0.position;
+    const TPointD da = m_a1.position - a;
+    const TPointD db = m_b1.position - b;
+    const TPointD ab = b - a;
+    double k = db.x*da.y - db.y*da.x;
+    if (fabs(k) > TConsts::epsilon) {
+      double lb = (da.x*ab.y - da.y*ab.x)/k;
+      m_center.position.x = lb*db.x + b.x;
+      m_center.position.y = lb*db.y + b.y;
     }
   }
+}
 
-  void fixSidePoint(TAssistantPoint &p0, TAssistantPoint &p1, TPointD previousP0) {
-    if (p0.position != m_center.position && p0.position != p1.position) {
-      TPointD dp0 = p0.position - m_center.position;
-      TPointD dp1 = p1.position - previousP0;
-      double l0 = norm(dp0);
-      double l1 = norm(dp1);
-      if (l0 > TConsts::epsilon && l0 + l1 > TConsts::epsilon)
-        p1.position = m_center.position + dp0*((l0 + l1)/l0);
-    }
+
+void TAssistantVanishingPoint::fixSidePoint(TAssistantPoint &p0, TAssistantPoint &p1, TPointD previousP0) {
+  if (p0.position != m_center.position && p0.position != p1.position) {
+    TPointD dp0 = p0.position - m_center.position;
+    TPointD dp1 = p1.position - previousP0;
+    double l0 = norm(dp0);
+    double l1 = norm(dp1);
+    if (l0 > TConsts::epsilon && l0 + l1 > TConsts::epsilon)
+      p1.position = m_center.position + dp0*((l0 + l1)/l0);
   }
+}
 
-  void fixSidePoint(TAssistantPoint &p0, TAssistantPoint &p1)
-    { fixSidePoint(p0, p1, p0.position); }
 
-  void fixGrid1(const TPointD &previousCenter, const TPointD &previousGrid0) {
-    TPointD dx = previousCenter - previousGrid0;
-    double l = norm2(dx);
-    if (l <= TConsts::epsilon*TConsts::epsilon) return;
-    dx = dx*(1.0/sqrt(l));
-    TPointD dy(-dx.y, dx.x);
+void TAssistantVanishingPoint::fixSidePoint(TAssistantPoint &p0, TAssistantPoint &p1)
+  { fixSidePoint(p0, p1, p0.position); }
 
-    TPointD d = m_grid1.position - previousGrid0;
-    double x = (dx*d);
-    double y = (dy*d);
 
-    dx = m_center.position - m_grid0.position;
-    l = norm2(dx);
-    if (l <= TConsts::epsilon*TConsts::epsilon) return;
-    dx = dx*(1.0/sqrt(l));
-    dy = TPointD(-dx.y, dx.x);
+void TAssistantVanishingPoint::fixGrid1(const TPointD &previousCenter, const TPointD &previousGrid0) {
+  TPointD dx = previousCenter - previousGrid0;
+  double l = norm2(dx);
+  if (l <= TConsts::epsilon*TConsts::epsilon) return;
+  dx = dx*(1.0/sqrt(l));
+  TPointD dy(-dx.y, dx.x);
 
-    m_grid1.position = m_grid0.position + dx*x + dy*y;
-  }
+  TPointD d = m_grid1.position - previousGrid0;
+  double x = (dx*d);
+  double y = (dy*d);
 
-public:
-  void onFixPoints() override {
+  dx = m_center.position - m_grid0.position;
+  l = norm2(dx);
+  if (l <= TConsts::epsilon*TConsts::epsilon) return;
+  dx = dx*(1.0/sqrt(l));
+  dy = TPointD(-dx.y, dx.x);
+
+  m_grid1.position = m_grid0.position + dx*x + dy*y;
+}
+
+
+void TAssistantVanishingPoint::onFixPoints() {
+  fixSidePoint(m_a0, m_a1);
+  fixSidePoint(m_b0, m_b1);
+  fixCenter();
+}
+
+
+void TAssistantVanishingPoint::onMovePoint(TAssistantPoint &point, const TPointD &position) {
+  TPointD previousCenter = m_center.position;
+  TPointD previous = point.position;
+  point.position = position;
+  if (&point == &m_center) {
     fixSidePoint(m_a0, m_a1);
     fixSidePoint(m_b0, m_b1);
+  } else
+  if (&point == &m_a0) {
+    fixSidePoint(m_a0, m_a1, previous);
+    fixSidePoint(m_b0, m_b1);
+  } else
+  if (&point == &m_b0) {
+    fixSidePoint(m_a0, m_a1);
+    fixSidePoint(m_b0, m_b1, previous);
+  } else
+  if (&point == &m_a1) {
     fixCenter();
+    fixSidePoint(m_a0, m_a1);
+    fixSidePoint(m_b0, m_b1);
+  } else
+  if (&point == &m_b1) {
+    fixCenter();
+    fixSidePoint(m_b0, m_b1);
+    fixSidePoint(m_a0, m_a1);
   }
 
-  void onMovePoint(TAssistantPoint &point, const TPointD &position) override {
-    TPointD previousCenter = m_center.position;
-    TPointD previous = point.position;
-    point.position = position;
-    if (&point == &m_center) {
-      fixSidePoint(m_a0, m_a1);
-      fixSidePoint(m_b0, m_b1);
-    } else
-    if (&point == &m_a0) {
-      fixSidePoint(m_a0, m_a1, previous);
-      fixSidePoint(m_b0, m_b1);
-    } else
-    if (&point == &m_b0) {
-      fixSidePoint(m_a0, m_a1);
-      fixSidePoint(m_b0, m_b1, previous);
-    } else
-    if (&point == &m_a1) {
-      fixCenter();
-      fixSidePoint(m_a0, m_a1);
-      fixSidePoint(m_b0, m_b1);
-    } else
-    if (&point == &m_b1) {
-      fixCenter();
-      fixSidePoint(m_b0, m_b1);
-      fixSidePoint(m_a0, m_a1);
-    }
-
-    if (&point == &m_grid0) {
-      fixGrid1(previousCenter, previous);
-    } else
-    if (&point != &m_grid1) {
-      fixGrid1(previousCenter, m_grid0.position);
-    }
+  if (&point == &m_grid0) {
+    fixGrid1(previousCenter, previous);
+  } else
+  if (&point != &m_grid1) {
+    fixGrid1(previousCenter, m_grid0.position);
   }
-
-  void getGuidelines(
-    const TPointD &position,
-    const TAffine &toTool,
-    TGuidelineList &outGuidelines ) const override
-  {
-    if (getPassThrough()) {
-      outGuidelines.push_back(TGuidelineP(
-        new TGuidelineInfiniteLine(
-          getEnabled(),
-          getMagnetism(),
-          toTool * m_center.position,
-          position )));
-    } else {
-      outGuidelines.push_back(TGuidelineP(
-        new TGuidelineRay(
-          getEnabled(),
-          getMagnetism(),
-          toTool * m_center.position,
-          position )));
-    }
+}
+
+
+void TAssistantVanishingPoint::getGuidelines(
+  const TPointD &position,
+  const TAffine &toTool,
+  TGuidelineList &outGuidelines ) const
+{
+  if (getPassThrough()) {
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineInfiniteLine(
+        getEnabled(),
+        getMagnetism(),
+        toTool * m_center.position,
+        position )));
+  } else {
+    outGuidelines.push_back(TGuidelineP(
+      new TGuidelineRay(
+        getEnabled(),
+        getMagnetism(),
+        toTool * m_center.position,
+        position )));
   }
-
-  void drawSimpleGrid() const {
-    double alpha = getDrawingGridAlpha();
-    const TPointD &p = m_center.position;
-    double pixelSize = sqrt(tglGetPixelSize2());
-    double minStep = 5.0*pixelSize;
-
-    // calculate rays count and step
-    TPointD d0 = m_grid0.position - p;
-    TPointD d1 = m_grid1.position - p;
-    TPointD dp = d0;
-    double l = norm(d0);
-    if (l <= TConsts::epsilon) return;
-    if (norm2(d1) <= TConsts::epsilon*TConsts::epsilon) return;
-    double a0 = atan(d0);
-    double a1 = atan(d1);
-    double da = fabs(a1 - a0);
-    if (da > M_PI) da = M_PI - da;
-    if (da < TConsts::epsilon) da = TConsts::epsilon;
-    double count = M_2PI/da;
-    if (count > 1e6) return;
-    double radiusPart = minStep/(da*l);
-    if (radiusPart > 1.0) return;
-    int raysCount = (int)round(count);
-    double step = M_2PI/(double)raysCount;
-
-    // common data about viewport
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    TAffine4 modelview, projection;
-    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
-    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
-    TAffine matrix = (projection*modelview).get2d();
-    TAffine matrixInv = matrix.inv();
-
-    // calculate range
-    if (!(matrixInv*oneBox).contains(p)) {
-      TPointD corners[4] = {
-        TPointD(oneBox.x0, oneBox.y0),
-        TPointD(oneBox.x0, oneBox.y1),
-        TPointD(oneBox.x1, oneBox.y0),
-        TPointD(oneBox.x1, oneBox.y1) };
-      double angles[4];
-      double a0 = 0.0, a1 = 0.0, da = 0.0;
-      for(int i = 0; i < 4; ++i) {
-        angles[i] = atan(matrixInv*corners[i] - p) + M_2PI;
-        for(int j = 0; j < i; ++j) {
-          double d = fabs(angles[i] - angles[j]);
-          if (d > M_PI) d = M_2PI - d;
-          if (d > da) da = d, a0 = angles[i], a1 = angles[j];
-        }
+}
+
+
+void TAssistantVanishingPoint::drawSimpleGrid(
+  const TPointD &center,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  double alpha )
+{
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = 5.0*pixelSize;
+
+  // calculate rays count and step
+  TPointD d0 = grid0 - center;
+  TPointD d1 = grid1 - center;
+  TPointD dp = d0;
+  double l = norm(d0);
+  if (l <= TConsts::epsilon) return;
+  if (norm2(d1) <= TConsts::epsilon*TConsts::epsilon) return;
+  double a0 = atan(d0);
+  double a1 = atan(d1);
+  double da = fabs(a1 - a0);
+  if (da > M_PI) da = M_PI - da;
+  if (da < TConsts::epsilon) da = TConsts::epsilon;
+  double count = M_2PI/da;
+  if (count > 1e6) return;
+  double radiusPart = minStep/(da*l);
+  if (radiusPart > 1.0) return;
+  int raysCount = (int)round(count);
+  double step = M_2PI/(double)raysCount;
+
+  // common data about viewport
+  const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine matrix = (projection*modelview).get2d();
+  TAffine matrixInv = matrix.inv();
+
+  // calculate range
+  if (!(matrixInv*oneBox).contains(center)) {
+    TPointD corners[4] = {
+      TPointD(oneBox.x0, oneBox.y0),
+      TPointD(oneBox.x0, oneBox.y1),
+      TPointD(oneBox.x1, oneBox.y0),
+      TPointD(oneBox.x1, oneBox.y1) };
+    double angles[4];
+    double a0 = 0.0, a1 = 0.0, da = 0.0;
+    for(int i = 0; i < 4; ++i) {
+      angles[i] = atan(matrixInv*corners[i] - center) + M_2PI;
+      for(int j = 0; j < i; ++j) {
+        double d = fabs(angles[i] - angles[j]);
+        if (d > M_PI) d = M_2PI - d;
+        if (d > da) da = d, a0 = angles[i], a1 = angles[j];
       }
-      if (a1 < a0) std::swap(a1, a0);
-      if (a1 - a0 > M_PI) { std::swap(a1, a0); a1 += M_2PI; }
-      double a = atan(dp) + M_2PI;
-      a0 = ceil ((a0 - a)/step)*step + a;
-      a1 = floor((a1 - a)/step)*step + a;
-
-      double s = sin(a0 - a);
-      double c = cos(a0 - a);
-      dp = TPointD(c*dp.x - s*dp.y, s*dp.x + c*dp.y);
-      raysCount = (int)round((a1 - a0)/step);
-    }
-
-    // draw rays
-    double s = sin(step);
-    double c = cos(step);
-    for(int i = 0; i < raysCount; ++i) {
-      TPointD p0 = matrix*(p + dp*radiusPart);
-      TPointD p1 = matrix*(p + dp);
-      if (TGuidelineLineBase::truncateRay(oneBox, p0, p1))
-        drawSegment(matrixInv*p0, matrixInv*p1, pixelSize, alpha);
-      dp = TPointD(c*dp.x - s*dp.y, s*dp.x + c*dp.y);
     }
+    if (a1 < a0) std::swap(a1, a0);
+    if (a1 - a0 > M_PI) { std::swap(a1, a0); a1 += M_2PI; }
+    double a = atan(dp) + M_2PI;
+    a0 = ceil ((a0 - a)/step)*step + a;
+    a1 = floor((a1 - a)/step)*step + a;
+
+    double s = sin(a0 - a);
+    double c = cos(a0 - a);
+    dp = TPointD(c*dp.x - s*dp.y, s*dp.x + c*dp.y);
+    raysCount = (int)round((a1 - a0)/step);
   }
 
-  void drawPerspectiveGrid() const {
-    // initial calculations
-    double alpha = getDrawingGridAlpha();
-    const TPointD &center = m_center.position;
-    double pixelSize = sqrt(tglGetPixelSize2());
-    double minStep = 5.0*pixelSize;
-
-    TPointD step = m_grid1.position - m_grid0.position;
-    double stepLen2 = norm2(step);
-    double stepLen = sqrt(stepLen2);
-    if (stepLen <= minStep) return;
-    TPointD stepProj = step*(1.0/stepLen2);
-
-    TPointD dp = center - m_grid0.position;
-    double startX = dp*stepProj;
-    TPointD zeroPoint = m_grid0.position + step*startX;
-    TPointD cz = zeroPoint - center;
-    double czLen2 = norm2(cz);
-    double czLen = sqrt(czLen2);
-    if (czLen <= TConsts::epsilon) return;
-    TPointD zeroProj = cz*(1.0/czLen2);
-
-    double smallK = minStep/stepLen;
-    TPointD smallGrid0 = center - dp*smallK;
-    TPointD smallStep = step*smallK;
-    TPointD smallStepProj = stepProj*(1/smallK);
-
-    // common data about viewport
-    const TRectD oneBox(-1.0, -1.0, 1.0, 1.0);
-    TAffine4 modelview, projection;
-    glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
-    glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
-    TAffine matrix = (projection*modelview).get2d();
-    TAffine matrixInv = matrix.inv();
-
-    // calculate bounds
-    bool found = false;
-    double minx = 0.0, maxx = 0.0;
-    TPointD p0 = matrix*(smallGrid0);
-    TPointD p1 = matrix*(smallGrid0 + smallStep);
-    if (TGuidelineLineBase::truncateInfiniteLine(oneBox, p0, p1)) {
-      p0 = matrixInv*p0;
-      p1 = matrixInv*p1;
-      minx = (p0 - smallGrid0)*smallStepProj;
-      maxx = (p1 - smallGrid0)*smallStepProj;
-      if (maxx < minx) std::swap(maxx, minx);
-      found = true;
-    }
-    if (!oneBox.contains(matrix*center)) {
-      TPointD corners[4] = {
-        TPointD(oneBox.x0, oneBox.y0),
-        TPointD(oneBox.x0, oneBox.y1),
-        TPointD(oneBox.x1, oneBox.y0),
-        TPointD(oneBox.x1, oneBox.y1) };
-      for(int i = 0; i < 4; ++i) {
-        TPointD p = matrixInv*corners[i] - center;
-        double k = p*zeroProj;
-        if (k < TConsts::epsilon) continue;
-        double x = startX + (p*stepProj)/k;
-        if (!found || x < minx) minx = x;
-        if (!found || x > maxx) maxx = x;
-        found = true;
-      }
-      if (maxx <= minx) return;
-    }
-
-    // draw grid
-    if (maxx - minx > 1e6) return;
-    for(double x = ceil(minx); x < maxx; ++x) {
-      TPointD p = smallGrid0 + smallStep*x - center;
-      TPointD p0 = matrix*(center + p);
-      TPointD p1 = matrix*(center + p*2.0);
-      if (TGuidelineLineBase::truncateRay(oneBox, p0, p1))
-        drawSegment(matrixInv*p0, matrixInv*p1, pixelSize, alpha);
-    }
-
-    // draw horizon
-    p0 = matrix*(center);
-    p1 = matrix*(center + step);
-    if (TGuidelineLineBase::truncateInfiniteLine(oneBox, p0, p1))
+  // draw rays
+  double s = sin(step);
+  double c = cos(step);
+  for(int i = 0; i < raysCount; ++i) {
+    TPointD p0 = matrix*(center + dp*radiusPart);
+    TPointD p1 = matrix*(center + dp);
+    if (TGuidelineLineBase::truncateRay(oneBox, p0, p1))
       drawSegment(matrixInv*p0, matrixInv*p1, pixelSize, alpha);
+    dp = TPointD(c*dp.x - s*dp.y, s*dp.x + c*dp.y);
   }
-
-  void draw(TToolViewer *viewer, bool enabled) const override {
-    double pixelSize = sqrt(tglGetPixelSize2());
-    const TPointD &p = m_center.position;
-    TPointD dx(20.0*pixelSize, 0.0);
-    TPointD dy(0.0, 10.0*pixelSize);
-    double alpha = getDrawingAlpha(enabled);
-    drawSegment(p-dx-dy, p+dx+dy, pixelSize, alpha);
-    drawSegment(p-dx+dy, p+dx-dy, pixelSize, alpha);
-    if (getGrid()) {
-      if (getPerspective())
-        drawPerspectiveGrid();
-      else
-        drawSimpleGrid();
+}
+
+
+void TAssistantVanishingPoint::drawPerspectiveGrid(
+  const TPointD &center,
+  const TPointD &grid0,
+  const TPointD &grid1,
+  double alpha )
+{
+  // initial calculations
+  double pixelSize = sqrt(tglGetPixelSize2());
+  double minStep = 5.0*pixelSize;
+  
+  TPointD ox = grid1 - grid0;
+  double lx = norm2(ox);
+  if (!(lx > TConsts::epsilon*TConsts::epsilon)) return;
+
+  // common data about viewport
+  const TRectD oneBox(-1, -1, 1, 1);
+  TAffine4 modelview, projection;
+  glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a);
+  glGetDoublev(GL_PROJECTION_MATRIX, projection.a);
+  TAffine matrix = (projection*modelview).get2d();
+  TAffine matrixInv = matrix.inv();
+  
+  // draw horizon
+  TPointD p0 = matrix*center;
+  TPointD p1 = matrix*(center + ox);
+  if (TGuidelineLineBase::truncateInfiniteLine(oneBox, p0, p1))
+    drawSegment(matrixInv*p0, matrixInv*p1, pixelSize, alpha);
+
+  // build grid matrix
+  TPointD dg = grid0 - center;
+  lx = sqrt(lx);
+  ox *= 1/lx;
+  TPointD oy(-ox.y, ox.x);
+  double baseY = dg*oy;
+  if (!(fabs(baseY) > TConsts::epsilon)) return;
+  if (baseY < 0) { oy = -oy; baseY = -baseY; }
+  double baseX = dg*ox/baseY;
+  double stepX = lx/baseY;
+  double k = stepX*4/minStep;
+  double dk = 1/k;
+  TAffine gridMatrix( ox.x, oy.x, center.x,
+                      ox.y, oy.y, center.y );
+  matrix = matrix*gridMatrix;
+  matrixInv = matrix.inv();
+  
+  const TRectD bounds = matrixInv*oneBox;
+  
+  // top bound
+  double x1 = bounds.y1*k - 1;
+  if (x1 < TConsts::epsilon) return;
+  double x0 = -x1;
+  
+  // angle bounds
+  double y;
+  y = bounds.x0 < 0 ? bounds.y0 : bounds.y1;
+  if (y > TConsts::epsilon) x0 = std::max(x0, bounds.x0/y);
+  y = bounds.x1 < 0 ? bounds.y1 : bounds.y0;
+  if (y > TConsts::epsilon) x1 = std::min(x1, bounds.x1/y);
+  
+  // delta bounds
+  if (bounds.x0 < 0)
+    x0 = std::max( x0, (1 - sqrt(1 - 4*bounds.x0*dk))*k/2 );
+  if (bounds.x1 > 0)
+    x1 = std::min( x1, (sqrt(1 + 4*bounds.x1*dk) - 1)*k/2 );
+  
+  // draw grid
+  double i0 = ceil((x0 - baseX)/stepX);
+  x0 = baseX + stepX*i0;
+  for(double x = x0; x < x1; x += stepX) {
+    double l = dk*(fabs(x) + 1);
+    TPointD p0 = gridMatrix*TPointD(x*l, l);
+    TPointD p1 = gridMatrix*TPointD(x*l*2, l*2);
+    drawSegment(p0, p1, pixelSize, 0, alpha);
+    if (bounds.y1 > l*2 + TConsts::epsilon) {
+      TPointD p2 = gridMatrix*TPointD(x*bounds.y1, bounds.y1);
+      drawSegment(p1, p2, pixelSize, alpha);
     }
   }
-
-  void drawEdit(TToolViewer *viewer) const override {
-    double pixelSize = sqrt(tglGetPixelSize2());
-    drawSegment(m_center.position, m_a1.position, pixelSize);
-    drawSegment(m_center.position, m_b1.position, pixelSize);
-    TAssistant::drawEdit(viewer);
+}
+
+
+void TAssistantVanishingPoint::draw(TToolViewer*, bool enabled) const {
+  double pixelSize = sqrt(tglGetPixelSize2());
+  const TPointD &p = m_center.position;
+  TPointD dx(20.0*pixelSize, 0.0);
+  TPointD dy(0.0, 10.0*pixelSize);
+  double alpha = getDrawingAlpha(enabled);
+  drawSegment(p-dx-dy, p+dx+dy, pixelSize, alpha);
+  drawSegment(p-dx+dy, p+dx-dy, pixelSize, alpha);
+  if (getGrid()) {
+    const TPointD &p0 = m_grid0.position;
+    const TPointD &p1 = m_grid1.position;
+    double gridAlpha = getDrawingGridAlpha();
+    if (getPerspective())
+      drawPerspectiveGrid(p, p0, p1, gridAlpha);
+    else
+      drawSimpleGrid(p, p0, p1, gridAlpha);
   }
-};
+}
+
+
+void TAssistantVanishingPoint::drawEdit(TToolViewer *viewer) const {
+  double pixelSize = sqrt(tglGetPixelSize2());
+  drawSegment(m_center.position, m_a1.position, pixelSize);
+  drawSegment(m_center.position, m_b1.position, pixelSize);
+  TAssistant::drawEdit(viewer);
+}
+
 
 
 //*****************************************************************************************
 //    Registration
 //*****************************************************************************************
 
+
 static TAssistantTypeT<TAssistantVanishingPoint> assistantVanishingPoint("assistantVanishingPoint");
diff --git a/toonz/sources/tnztools/assistants/assistantvanishingpoint.h b/toonz/sources/tnztools/assistants/assistantvanishingpoint.h
new file mode 100644
index 0000000..b7b276c
--- /dev/null
+++ b/toonz/sources/tnztools/assistants/assistantvanishingpoint.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#ifndef ASSISTANTVANISHINGPOINT_INCLUDED
+#define ASSISTANTVANISHINGPOINT_INCLUDED
+
+
+// TnzTools includes
+#include <tools/assistant.h>
+#include <tools/assistants/guidelineline.h>
+
+// TnzCore includes
+#include <tgl.h>
+
+
+//*****************************************************************************************
+//    TAssistantVanishingPoint definition
+//*****************************************************************************************
+
+class TAssistantVanishingPoint final : public TAssistant {
+  Q_DECLARE_TR_FUNCTIONS(TAssistantVanishingPoint)
+public:
+  const TStringId m_idPassThrough;
+  const TStringId m_idGrid;
+  const TStringId m_idPerspective;
+
+protected:
+  TAssistantPoint &m_center;
+  TAssistantPoint &m_a0;
+  TAssistantPoint &m_a1;
+  TAssistantPoint &m_b0;
+  TAssistantPoint &m_b1;
+  TAssistantPoint &m_grid0;
+  TAssistantPoint &m_grid1;
+
+public:
+  TAssistantVanishingPoint(TMetaObject &object);
+
+  static QString getLocalName();
+
+  void updateTranslation() const override;
+
+  inline bool getPassThrough() const
+    { return data()[m_idPassThrough].getBool(); }
+  inline bool getGrid() const
+    { return data()[m_idGrid].getBool(); }
+  inline bool getPerspective() const
+    { return data()[m_idPerspective].getBool(); }
+
+  void onDataChanged(const TVariant &value) override;
+
+private:
+  void fixCenter();
+  void fixSidePoint(TAssistantPoint &p0, TAssistantPoint &p1, TPointD previousP0);
+  void fixSidePoint(TAssistantPoint &p0, TAssistantPoint &p1);
+  void fixGrid1(const TPointD &previousCenter, const TPointD &previousGrid0);
+
+public:
+  void onFixPoints() override;
+  void onMovePoint(TAssistantPoint &point, const TPointD &position) override;
+
+  void getGuidelines(
+    const TPointD &position,
+    const TAffine &toTool,
+    TGuidelineList &outGuidelines ) const override;
+
+  static void drawSimpleGrid(
+    const TPointD &center,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    double alpha );
+  
+  static void drawPerspectiveGrid(
+    const TPointD &center,
+    const TPointD &grid0,
+    const TPointD &grid1,
+    double alpha );
+  
+  void draw(TToolViewer *viewer, bool enabled) const override;
+  void drawEdit(TToolViewer *viewer) const override;
+};
+
+
+#endif
diff --git a/toonz/sources/tnztools/assistants/replicatoraffine.cpp b/toonz/sources/tnztools/assistants/replicatoraffine.cpp
index f637c90..02c36cc 100644
--- a/toonz/sources/tnztools/assistants/replicatoraffine.cpp
+++ b/toonz/sources/tnztools/assistants/replicatoraffine.cpp
@@ -249,6 +249,8 @@ public:
   void getPoints(const TAffine &toTool, PointList &points) const override {
     points.reserve(points.size() * getMultipler());
     int pointsCount = (int)points.size();
+    int i0 = getSkipFirst();
+    int i1 = pointsCount - getSkipLast();
 
     TAffine aff = getAffine(toTool);
     struct {
@@ -262,7 +264,7 @@ public:
       TAffine a;
       for(int j = 0; j < t[i].count; ++j) {
         a = t[i].aff * a;
-        transformPoints(a, points, pointsCount);
+        transformPoints(a, points, i0, i1);
       }
     }
   }
@@ -285,7 +287,7 @@ public:
       { getCountInv(), aff.inv(), pressureInv },
     };
 
-    TModifierClone *modifier = new TModifierClone();
+    TModifierClone *modifier = new TModifierClone(true, getSkipFirst(), getSkipLast());
     for(int i = 0; i < 2; ++i) {
       TTrackTransform tt;
       for(int j = 0; j < t[i].count; ++j) {
diff --git a/toonz/sources/tnztools/assistants/replicatorgrid.cpp b/toonz/sources/tnztools/assistants/replicatorgrid.cpp
index b276024..7928f2f 100644
--- a/toonz/sources/tnztools/assistants/replicatorgrid.cpp
+++ b/toonz/sources/tnztools/assistants/replicatorgrid.cpp
@@ -177,6 +177,8 @@ public:
   void getPoints(const TAffine &toTool, PointList &points) const override {
     points.reserve(points.size() * getMultipler());
     int pointsCount = (int)points.size();
+    int i0 = getSkipFirst();
+    int i1 = pointsCount - getSkipLast();
     
     TPointD c = toTool*m_center.position;
     TPointD da = toTool*m_a.position - c;
@@ -212,22 +214,22 @@ public:
         transformPoints(
           TAffine( 1, 0, o.x,
                    0, 1, o.y ),
-          points, pointsCount );
+          points, i0, i1 );
       if (mirrorA)
         transformPoints(
           TAffine( ma.a11, ma.a12, ma.a13 + o.x,
                    ma.a21, ma.a22, ma.a23 + o.y ),
-          points, pointsCount );
+          points, i0, i1 );
       if (mirrorB)
         transformPoints(
           TAffine( mb.a11, mb.a12, mb.a13 + o.x,
                    mb.a21, mb.a22, mb.a23 + o.y ),
-          points, pointsCount );
+          points, i0, i1 );
       if (mirrorA && mirrorB)
         transformPoints(
           TAffine( mc.a11, mc.a12, mc.a13 + o.x,
                    mc.a21, mc.a22, mc.a23 + o.y ),
-          points, pointsCount );
+          points, i0, i1 );
     }
   }
 
@@ -263,7 +265,7 @@ public:
     int a0 = -getCountAInv();
     int b0 = -getCountBInv();
     
-    TModifierClone *modifier = new TModifierClone();
+    TModifierClone *modifier = new TModifierClone(true, getSkipFirst(), getSkipLast());
     for(int ib = b0; ib < b1; ++ib)
     for(int ia = a0; ia < a1; ++ia) {
       TPointD o = da*ia + db*ib;
diff --git a/toonz/sources/tnztools/assistants/replicatorjitter.cpp b/toonz/sources/tnztools/assistants/replicatorjitter.cpp
index befb329..433a432 100644
--- a/toonz/sources/tnztools/assistants/replicatorjitter.cpp
+++ b/toonz/sources/tnztools/assistants/replicatorjitter.cpp
@@ -16,9 +16,10 @@
 class TReplicatorJitter final : public TReplicator {
   Q_DECLARE_TR_FUNCTIONS(TReplicatorJitter)
 public:
-  const TStringId m_idSkipFirst;
   const TStringId m_idPeriod;
   const TStringId m_idAmplitude;
+  const TStringId m_idKeepFirstPoint;
+  const TStringId m_idKeepLastPoint;
 
 protected:
   TAssistantPoint &m_center;
@@ -26,13 +27,12 @@ protected:
 public:
   TReplicatorJitter(TMetaObject &object):
     TReplicator(object),
-    m_idSkipFirst("skipFirst"),
     m_idPeriod("period"),
-    m_idAmplitude("m_idAmplitude"),
+    m_idAmplitude("amplitude"),
+    m_idKeepFirstPoint("keepFirstPoint"),
+    m_idKeepLastPoint("keepLastPoint"),
     m_center( addPoint("center", TAssistantPoint::CircleCross) )
   {
-    addProperty( createSpinProperty(m_idSkipFirst, getSkipFirst(), 0) );
-
     TDoubleProperty *p;
     
     p = new TDoubleProperty(m_idPeriod.str(), 0.0, 1000, getPeriod());
@@ -42,6 +42,9 @@ public:
     p = new TDoubleProperty(m_idAmplitude.str(), 0.0, 1000, getAmplitude());
     p->setNonLinearSlider();
     addProperty(p);
+    
+    addProperty( new TBoolProperty(m_idKeepFirstPoint.str(), getKeepFirstPoint()) );
+    addProperty( new TBoolProperty(m_idKeepLastPoint.str(), getKeepLastPoint()) );
   }
 
   
@@ -51,22 +54,23 @@ public:
     
   void updateTranslation() const override {
     TReplicator::updateTranslation();
-    setTranslation(m_idSkipFirst, tr("Skip First Tracks"));
     setTranslation(m_idPeriod, tr("Period"));
     setTranslation(m_idAmplitude, tr("Amplitude"));
+    setTranslation(m_idKeepFirstPoint, tr("Fix First Point"));
+    setTranslation(m_idKeepLastPoint, tr("Fix Last Point"));
   }
 
   
-  inline int getSkipFirst() const
-    { return (int)data()[m_idSkipFirst].getDouble(); }
   inline double getPeriod() const
     { return data()[m_idPeriod].getDouble(); }
   inline double getAmplitude() const
     { return data()[m_idAmplitude].getDouble(); }
+  inline bool getKeepFirstPoint() const
+    { return data()[m_idKeepFirstPoint].getBool(); }
+  inline bool getKeepLastPoint() const
+    { return data()[m_idKeepLastPoint].getBool(); }
 
 protected:
-  inline void setSkipFirst(int x)
-    { if (getSkipFirst() != (double)x) data()[m_idSkipFirst].setDouble((double)x); }
   inline void setPeriod(double x)
     { if (getPeriod() != x) data()[m_idPeriod].setDouble(x); }
   inline void setAmplitude(double x)
@@ -95,19 +99,22 @@ protected:
 public:
   
   void getPoints(const TAffine &toTool, PointList &points) const override {
-    int skipFirst = getSkipFirst();
-    if (skipFirst < 0) skipFirst = 0;
-    if (skipFirst >= (int)points.size()) return;
+    int pointsCount = (int)points.size();
+    int i0 = getSkipFirst();
+    int i1 = pointsCount - getSkipLast();
+    if (i0 < 0) i0 = 0;
+    if (i1 > pointsCount) i1 = pointsCount;
+    if (i0 >= i1) return;
     
     double scale = getScale(toTool);
     double period = getPeriod()*scale;
     double amplitude = getAmplitude()*scale;
-    if (!(period > TConsts::epsilon && amplitude > TConsts::epsilon)) {
+    if (period > TConsts::epsilon && amplitude > TConsts::epsilon) {
       int seedX = 0;
       int seedY = 7722441;
-      for(PointList::iterator i = points.begin() + skipFirst; i != points.end(); ++i) {
-        i->x += TModifierJitter::func(seedX, 0)*amplitude;
-        i->y += TModifierJitter::func(seedY, 0)*amplitude;
+      for(int i = i0; i < i1; ++i) {
+        points[i].x += TModifierJitter::func(seedX, points[i].x/period)*amplitude;
+        points[i].y += TModifierJitter::func(seedY, points[i].y/period)*amplitude;
         ++seedX, ++seedY;
       }
     }
@@ -122,7 +129,10 @@ public:
     outModifiers.push_back(new TModifierJitter(
       getPeriod()*scale,
       getAmplitude()*scale,
-      getSkipFirst() ));
+      getSkipFirst(),
+      getSkipLast(),
+      getKeepFirstPoint(),
+      getKeepLastPoint() ));
   }
 
   
diff --git a/toonz/sources/tnztools/assistants/replicatormirror.cpp b/toonz/sources/tnztools/assistants/replicatormirror.cpp
index 0c608c8..04ad2cb 100644
--- a/toonz/sources/tnztools/assistants/replicatormirror.cpp
+++ b/toonz/sources/tnztools/assistants/replicatormirror.cpp
@@ -117,15 +117,19 @@ public:
     { return 2; }
   
   
-  void getPoints(const TAffine &toTool, PointList &points) const override
-    { transformPoints(getAffine(toTool), points, (int)points.size()); }
+  void getPoints(const TAffine &toTool, PointList &points) const override {
+    int pointsCount = (int)points.size();
+    int i0 = getSkipFirst();
+    int i1 = pointsCount - getSkipLast();
+    transformPoints(getAffine(toTool), points, i0, i1);
+  }
   
   
   void getModifiers(
     const TAffine &toTool,
     TInputModifier::List &outModifiers ) const override
   {
-    TModifierClone *modifier = new TModifierClone();
+    TModifierClone *modifier = new TModifierClone(true, getSkipFirst(), getSkipLast());
     modifier->transforms.push_back(TTrackTransform(
       getAffine(toTool), getPressure() ));
     outModifiers.push_back(modifier);
diff --git a/toonz/sources/tnztools/assistants/replicatorstar.cpp b/toonz/sources/tnztools/assistants/replicatorstar.cpp
index f9821d7..e60f10b 100644
--- a/toonz/sources/tnztools/assistants/replicatorstar.cpp
+++ b/toonz/sources/tnztools/assistants/replicatorstar.cpp
@@ -116,6 +116,8 @@ public:
   void getPoints(const TAffine &toTool, PointList &points) const override {
     points.reserve(points.size() * getMultipler());
     int pointsCount = (int)points.size();
+    int i0 = getSkipFirst();
+    int i1 = pointsCount - getSkipLast();
     
     int count = getCount();
     bool mirror = getMirror();
@@ -131,12 +133,12 @@ public:
     
     TAffine t0 = t1.inv();
     TRotation r(360.0/getCount());
-                          
+    
     for(int i = 0; i < count; ++i) {
       if (i)
-        transformPoints(t1*t0, points, pointsCount);
+        transformPoints(t1*t0, points, i0, i1);
       if (mirror) {
-        transformPoints(t2*t0, points, pointsCount);
+        transformPoints(t2*t0, points, i0, i1);
         t2 *= r;
       }
       t1 *= r;
@@ -163,7 +165,7 @@ public:
     TAffine t0 = t1.inv();
     TRotation r(360.0/getCount());
                           
-    TModifierClone *modifier = new TModifierClone();
+    TModifierClone *modifier = new TModifierClone(true, getSkipFirst(), getSkipLast());
     for(int i = 0; i < count; ++i) {
       if (i)
         modifier->transforms.push_back(TTrackTransform(t1*t0));
diff --git a/toonz/sources/tnztools/editassistantstool.cpp b/toonz/sources/tnztools/editassistantstool.cpp
index 2c98f9e..67cd997 100644
--- a/toonz/sources/tnztools/editassistantstool.cpp
+++ b/toonz/sources/tnztools/editassistantstool.cpp
@@ -502,7 +502,7 @@ protected:
       m_currentImage.set(m_readImage);
       if (index < (*m_reader)->size())
       if (const TMetaObjectPC &obj = (**m_reader)[index])
-      if (const TAssistant *assistant = obj->getHandler<TAssistant>()) {
+      if (const TAssistantBase *assistant = obj->getHandler<TAssistantBase>()) {
         assistant->deselectAll();
         m_currentAssistant.set(obj);
         m_currentAssistantIndex = index;
@@ -806,7 +806,7 @@ public:
     TAssistant::scanAssistants(
       this,          // tool
       &position, 1,  // pointer positions
-      nullptr,       // out guidelines
+      &m_currentGuidelines, // out guidelines
       true,          // draw
       false,         // enabled only
       false,         // mark enabled
diff --git a/toonz/sources/tnztools/modifiers/modifierclone.cpp b/toonz/sources/tnztools/modifiers/modifierclone.cpp
index 05d96b8..52db00a 100644
--- a/toonz/sources/tnztools/modifiers/modifierclone.cpp
+++ b/toonz/sources/tnztools/modifiers/modifierclone.cpp
@@ -16,8 +16,8 @@ TTrackPoint TModifierClone::Interpolator::interpolate(double index)
 //    TModifierClone implementation
 //*****************************************************************************************
 
-TModifierClone::TModifierClone(bool keepOriginals):
-  keepOriginals(keepOriginals) { }
+TModifierClone::TModifierClone(bool keepOriginals, int skipFirst, int skipLast):
+  keepOriginals(keepOriginals), skipFirst(skipFirst), skipLast(skipLast) { }
 
 void TModifierClone::modifyTrack(const TTrack &track,
                                  TTrackList &outTracks)
@@ -73,3 +73,15 @@ void TModifierClone::modifyTrack(const TTrack &track,
   track.resetChanges();
 }
 
+void TModifierClone::modifyTracks(
+  const TTrackList &tracks,
+  TTrackList &outTracks )
+{
+  int cnt = (int)tracks.size();
+  int i0 = skipFirst;
+  int i1 = cnt - skipLast;
+  for(int i = 0; i < cnt; ++i)
+    if (i0 <= i && i < i1) modifyTrack(*tracks[i], outTracks);
+      else TInputModifier::modifyTrack(*tracks[i], outTracks);
+}
+
diff --git a/toonz/sources/tnztools/modifiers/modifierjitter.cpp b/toonz/sources/tnztools/modifiers/modifierjitter.cpp
index 292bb82..61a5c72 100644
--- a/toonz/sources/tnztools/modifiers/modifierjitter.cpp
+++ b/toonz/sources/tnztools/modifiers/modifierjitter.cpp
@@ -81,20 +81,46 @@ static inline unsigned int trackSeedY(const TTrack &track) {
 //*****************************************************************************************
 
 
-TModifierJitter::Interpolator::Interpolator(TTrack &track, double period, double amplitude):
+TModifierJitter::Interpolator::Interpolator(
+  TTrack &track,
+  double period,
+  double amplitude,
+  bool keepFirstPoint,
+  bool keepLastPoint
+):
   TTrackInterpolator(track),
   seedX(trackSeedX(track)),
   seedY(trackSeedY(track)),
   frequency(fabs(period) > TConsts::epsilon ? 1/period : 0),
-  amplitude(fabs(period) > TConsts::epsilon ? amplitude : 0)
+  amplitude(fabs(period) > TConsts::epsilon ? amplitude : 0),
+  keepFirstPoint(keepFirstPoint),
+  keepLastPoint(keepLastPoint)
   { }
 
 
 TTrackPoint TModifierJitter::Interpolator::interpolateFromOriginal(double originalIndex) {
   TTrackPoint p = track.calcPointFromOriginal(originalIndex);
+  double a = amplitude;
   double l = p.length*frequency;
-  p.position.x += Jitter::func(seedX, l)*amplitude;
-  p.position.y += Jitter::func(seedY, l)*amplitude;
+  if (frequency && a && track.original && (keepFirstPoint || keepLastPoint)) {
+    double ll = track.original->back().length*frequency;
+    if (l < 0) l = 0;
+    if (l > ll) l = ll;
+    if (ll < TConsts::epsilon) {
+      a = 0;
+    } else
+    if (keepFirstPoint && keepLastPoint && ll < 2) {
+      a *= 0.5 - 0.5*cos(l/ll*M_2PI);
+    } else
+    if (keepFirstPoint && l < 1) {
+      a *= 0.5 - 0.5*cos(l*M_PI);
+    } else
+    if (keepLastPoint && (ll - l) < 1) {
+      a *= 0.5 - 0.5*cos((ll - l)*M_PI);
+    }
+  }
+  p.position.x += Jitter::func(seedX, l)*a;
+  p.position.y += Jitter::func(seedY, l)*a;
   return p;
 }
 
@@ -109,8 +135,21 @@ TTrackPoint TModifierJitter::Interpolator::interpolate(double index)
 //*****************************************************************************************
 
 
-TModifierJitter::TModifierJitter(double period, double amplitude, int skipFirst):
-  period(period), amplitude(amplitude), skipFirst(skipFirst) { }
+TModifierJitter::TModifierJitter(
+  double period,
+  double amplitude,
+  int skipFirst,
+  int skipLast,
+  bool keepFirstPoint,
+  bool keepLastPoint
+):
+  period(period),
+  amplitude(amplitude),
+  skipFirst(skipFirst),
+  skipLast(skipLast),
+  keepFirstPoint(keepFirstPoint),
+  keepLastPoint(keepLastPoint)
+{ }
 
 
 void TModifierJitter::modifyTrack(const TTrack &track,
@@ -120,7 +159,7 @@ void TModifierJitter::modifyTrack(const TTrack &track,
     Handler *handler = new Handler();
     track.handler = handler;
     handler->track = new TTrack(track);
-    new Interpolator(*handler->track, period, amplitude);
+    new Interpolator(*handler->track, period, amplitude, keepFirstPoint, keepLastPoint);
   }
   
   Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
@@ -137,8 +176,14 @@ void TModifierJitter::modifyTrack(const TTrack &track,
   if (!intr)
     return;
 
+  bool preview = intr->keepLastPoint && intr->frequency && intr->amplitude;
+  
   int start = track.size() - track.pointsAdded;
   if (start < 0) start = 0;
+  if (preview) {
+    double l = track[start].length - 1/intr->frequency;
+    start = track.floorIndex( track.indexByLength(l) );
+  }
 
   // process sub-track
   subTrack.truncate(start);
@@ -146,7 +191,13 @@ void TModifierJitter::modifyTrack(const TTrack &track,
     subTrack.push_back(intr->interpolateFromOriginal(i), false);
   
   // fit points
-  subTrack.fix_to(track.fixedSize());
+  if (track.fixedFinished() || !preview) {
+    subTrack.fix_to(track.fixedSize());
+  } else
+  if (track.fixedSize()) {
+    double l = track[track.fixedSize() - 1].length - 1/intr->frequency;
+    subTrack.fix_to(subTrack.floorIndex( track.indexByLength(l) ));
+  }
   
   track.resetChanges();
 }
@@ -154,15 +205,15 @@ void TModifierJitter::modifyTrack(const TTrack &track,
 
 void
 TModifierJitter::modifyTracks(
-    const TTrackList &tracks,
-    TTrackList &outTracks )
+  const TTrackList &tracks,
+  TTrackList &outTracks )
 {
-  int cnt = std::min( std::max(0, skipFirst), (int)tracks.size() );
-  TTrackList::const_iterator split = tracks.begin() + cnt;
-  for(TTrackList::const_iterator i = tracks.begin(); i != split; ++i)
-    TInputModifier::modifyTrack(**i, outTracks);
-  for(TTrackList::const_iterator i = split; i != tracks.end(); ++i)
-    modifyTrack(**i, outTracks);
+  int cnt = (int)tracks.size();
+  int i0 = skipFirst;
+  int i1 = cnt - skipLast;
+  for(int i = 0; i < cnt; ++i)
+    if (i0 <= i && i < i1) modifyTrack(*tracks[i], outTracks);
+      else TInputModifier::modifyTrack(*tracks[i], outTracks);
 }
 
 
diff --git a/toonz/sources/tnztools/replicator.cpp b/toonz/sources/tnztools/replicator.cpp
index 6e63cb6..b7c9d27 100644
--- a/toonz/sources/tnztools/replicator.cpp
+++ b/toonz/sources/tnztools/replicator.cpp
@@ -24,7 +24,22 @@ const int TReplicator::multiplierLimit = 256;
 //************************************************************************
 
 TReplicator::TReplicator(TMetaObject &object):
-  TAssistantBase(object) { }
+  TAssistantBase(object),
+  m_idSkipFirst("skipFirst"),
+  m_idSkipLast("skipLast")
+{
+  addProperty( createSpinProperty(m_idSkipFirst, getSkipFirst(), 0) );
+  addProperty( createSpinProperty(m_idSkipLast, getSkipLast(), 0) );
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TReplicator::updateTranslation() const {
+  TAssistantBase::updateTranslation();
+  setTranslation(m_idSkipFirst, tr("Skip First Tracks"));
+  setTranslation(m_idSkipLast, tr("Skip Last Tracks"));
+}
 
 //---------------------------------------------------------------------------------------------------
 
@@ -57,9 +72,13 @@ TReplicator::createCountProperty(const TStringId &id, int def, int min, int max)
 //---------------------------------------------------------------------------------------------------
 
 void
-TReplicator::transformPoints(const TAffine &aff, PointList &points, int count) {
-  points.reserve(points.size() + count);
-  for(int i = 0; i < count; ++i)
+TReplicator::transformPoints(const TAffine &aff, PointList &points, int i0, int i1) {
+  int cnt = (int)points.size();
+  if (i0 < 0) i0 = 0;
+  if (i1 > cnt) i1 = cnt;
+  if (i0 >= i1) return;
+  points.reserve(points.size() + i1 - i0);
+  for(int i = i0; i < i1; ++i)
     points.push_back(aff*points[i]);
 }
 
diff --git a/toonz/sources/toonzqt/intfield.cpp b/toonz/sources/toonzqt/intfield.cpp
index e9ad097..918f28e 100644
--- a/toonz/sources/toonzqt/intfield.cpp
+++ b/toonz/sources/toonzqt/intfield.cpp
@@ -309,8 +309,13 @@ IntField::IntField(QWidget *parent, bool isMaxRangeLimited, bool isRollerHide,
    
   if (isSpinnerHide) enableSpinner(false);
   
-  layout->addWidget(m_inc);
+  // TODO:
+  // Commonly in OpenToonz spin-buttons has been placed in that order: [+][-]
+  // This seems unusual behavior.
+  // And in this particular case buttons has been placed in another order: [-][+]
+  // We need to know what is better
   layout->addWidget(m_dec);
+  layout->addWidget(m_inc);
   
   m_slider = new QSlider(Qt::Horizontal, this);
   ret      = ret && connect(m_slider, SIGNAL(valueChanged(int)), this,