diff --git a/toonz/sources/include/tgeometry.h b/toonz/sources/include/tgeometry.h
index f5c9a81..34b9902 100644
--- a/toonz/sources/include/tgeometry.h
+++ b/toonz/sources/include/tgeometry.h
@@ -202,6 +202,16 @@ inline TPointD normalize(const TPointD &p) {
 
 /*!
 \relates TPointT
+This helper function returns the normalized version of the specified point
+or zero if it is not possible
+*/
+inline TPointD normalizeOrZero(const TPointD &p) {
+  double n = norm2(p);
+  return fabs(n) > TConsts::epsilon*TConsts::epsilon ? p*(1/sqrt(n)) : TPointD();
+}
+
+/*!
+\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); }
@@ -1426,9 +1436,9 @@ public:
     inline Range range() const
       { return Range(a0(), a1()); }
     inline int size() const
-      { return (m_end - m_begin)/2 + 1; }
+      { return (int)(m_end - m_begin)/2 + 1; }
     inline int index() const
-      { return (m_current - m_begin)/2; }
+      { return (int)(m_current - m_begin)/2; }
     inline int reverseIndex() const
       { int i = index(); return i == 0 ? 0 : size() - i; }
     inline bool lapped() const
diff --git a/toonz/sources/include/tools/assistant.h b/toonz/sources/include/tools/assistant.h
index 14931aa..5be247d 100644
--- a/toonz/sources/include/tools/assistant.h
+++ b/toonz/sources/include/tools/assistant.h
@@ -39,6 +39,7 @@
 class TProperty;
 class TPropertyGroup;
 
+class TTool;
 class TToolViewer;
 class TAssistant;
 class TAssistantPoint;
@@ -340,6 +341,17 @@ public:
     double &outK,
     double &outMin,
     double &outMax );
+  
+  static bool scanAssistants(
+    TTool *tool,
+    const TPointD *positions,
+    int positionsCount,
+    TGuidelineList *outGuidelines,
+    bool draw,
+    bool enabledOnly,
+    bool markEnabled,
+    bool drawGuidelines,
+    TImage *skipImage );
 };
 
 
diff --git a/toonz/sources/include/tools/inputmanager.h b/toonz/sources/include/tools/inputmanager.h
index 323dba1..3dfcc51 100644
--- a/toonz/sources/include/tools/inputmanager.h
+++ b/toonz/sources/include/tools/inputmanager.h
@@ -231,8 +231,10 @@ public:
     TInputState::DeviceId deviceId,
     TInputState::TouchId touchId,
     const TPointD &position,
-    const double *pressure,
-    const TPointD *tilt,
+    const double pressure,
+    const TPointD &tilt,
+    bool hasPressure,
+    bool hasTilt,
     bool final,
     TTimerTicks ticks );
   bool keyEvent(
diff --git a/toonz/sources/include/tools/modifiers/modifierassistants.h b/toonz/sources/include/tools/modifiers/modifierassistants.h
index 0ba8059..35a286a 100644
--- a/toonz/sources/include/tools/modifiers/modifierassistants.h
+++ b/toonz/sources/include/tools/modifiers/modifierassistants.h
@@ -26,13 +26,16 @@
 
 class DVAPI TModifierAssistants: public TInputModifier {
 public:
-  class DVAPI Modifier: public TTrackModifier {
+  typedef TSubTrackHandler Handler;
+  class DVAPI Interpolator: public TTrackInterpolator {
   public:
-    bool initialized;
+    const double magnetism;
     TGuidelineList guidelines;
-
-    Modifier(TTrackHandler &handler);
-    TTrackPoint calcPoint(double originalIndex) override;
+    inline Interpolator(TTrack &track, double magnetism):
+      TTrackInterpolator(track),
+      magnetism(magnetism > 0 ? (magnetism < 1 ? magnetism : 1) : 0)
+      { }
+    TTrackPoint interpolate(double index) override;
   };
 
 private:
@@ -41,13 +44,14 @@ private:
     int positionsCount,
     TGuidelineList *outGuidelines,
     bool draw,
-    bool enabledOnly ) const;
+    bool enabledOnly,
+    bool drawGuidelines ) const;
 
 public:
-  bool drawOnly;
+  double magnetism;
   double sensitiveLength;
 
-  explicit TModifierAssistants(bool drawOnly = false);
+  explicit TModifierAssistants(double magnetism = 1);
 
   void modifyTrack(
     const TTrack &track,
diff --git a/toonz/sources/include/tools/modifiers/modifierline.h b/toonz/sources/include/tools/modifiers/modifierline.h
index 495f711..622e2ae 100644
--- a/toonz/sources/include/tools/modifiers/modifierline.h
+++ b/toonz/sources/include/tools/modifiers/modifierline.h
@@ -24,17 +24,12 @@
 //    TModifierLine definition
 //*****************************************************************************************
 
-class TModifierLine: public TInputModifier {
+class DVAPI TModifierLine: public TInputModifier {
 public:
-  class Modifier: public TTrackModifier {
+  class DVAPI Handler: public TSubTrackHandler {
   public:
-    explicit Modifier(TTrackHandler &handler):
-      TTrackModifier(handler), fixAngle(), maxPressure() { }
-
-    bool fixAngle;
     double maxPressure;
-    
-    TTrackPoint calcPoint(double originalIndex) override;
+    inline Handler(): maxPressure() { }
   };
 
   void modifyTrack(
diff --git a/toonz/sources/include/tools/modifiers/modifiersegmentation.h b/toonz/sources/include/tools/modifiers/modifiersegmentation.h
index 48d8872..acfb76b 100644
--- a/toonz/sources/include/tools/modifiers/modifiersegmentation.h
+++ b/toonz/sources/include/tools/modifiers/modifiersegmentation.h
@@ -25,6 +25,10 @@
 //*****************************************************************************************
 
 class DVAPI TModifierSegmentation: public TInputModifier {
+public:
+  typedef TSubTrackHandler Handler;
+  typedef TTrackIntrOrig Interpolator;
+  
 private:
   TPointD m_step;
   int m_maxLevel;
@@ -32,7 +36,7 @@ private:
   void addSegments(TTrack &track, const TTrackPoint &p0, const TTrackPoint &p1, int maxLevel);
 
 public:
-  TModifierSegmentation(const TPointD &step = TPointD(1.0, 1.0), int level = 10);
+  explicit TModifierSegmentation(const TPointD &step = TPointD(1.0, 1.0), int level = 10);
 
   void setStep(const TPointD &step);
   inline const TPointD& getStep() const { return m_step; }
diff --git a/toonz/sources/include/tools/modifiers/modifiersimplify.h b/toonz/sources/include/tools/modifiers/modifiersimplify.h
new file mode 100644
index 0000000..155dd4a
--- /dev/null
+++ b/toonz/sources/include/tools/modifiers/modifiersimplify.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#ifndef MODIFIERSIMPLIFY_INCLUDED
+#define MODIFIERSIMPLIFY_INCLUDED
+
+// TnzTools includes
+#include <tools/inputmanager.h>
+
+
+#undef DVAPI
+#undef DVVAR
+#ifdef TNZTOOLS_EXPORTS
+#define DVAPI DV_EXPORT_API
+#define DVVAR DV_EXPORT_VAR
+#else
+#define DVAPI DV_IMPORT_API
+#define DVVAR DV_IMPORT_VAR
+#endif
+
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierSimplify definition
+//*****************************************************************************************
+
+class DVAPI TModifierSimplify: public TInputModifier {
+public:
+  typedef TSubTrackHandler Handler;
+  typedef TTrackIntrOrig Interpolator;
+  
+  double step;
+  
+  explicit TModifierSimplify(double step = 1.0);
+
+  void modifyTrack(
+    const TTrack &track,
+    TTrackList &outTracks ) override;
+};
+
+
+#endif
diff --git a/toonz/sources/include/tools/modifiers/modifiersmooth.h b/toonz/sources/include/tools/modifiers/modifiersmooth.h
index 6968d38..e814b73 100644
--- a/toonz/sources/include/tools/modifiers/modifiersmooth.h
+++ b/toonz/sources/include/tools/modifiers/modifiersmooth.h
@@ -26,23 +26,15 @@
 
 class DVAPI TModifierSmooth: public TInputModifier {
 public:
-  class DVAPI Modifier: public TTrackModifier {
+  class DVAPI Handler: public TSubTrackHandler {
   public:
     const int radius;
-    TTrack *smoothedTrack;
-
-    Modifier(TTrackHandler &handler, int radius);
-    TTrackPoint calcPoint(double originalIndex) override;
+    inline explicit Handler(int radius): radius(radius) { }
   };
 
-private:
-  int m_radius;
-  
-public:
-  TModifierSmooth(int radius = 10);
+  int radius;
 
-  void setRadius(int radius);
-  int getRadius() const { return m_radius; }
+  explicit TModifierSmooth(int radius = 10);
   
   void modifyTrack(
     const TTrack &track,
diff --git a/toonz/sources/include/tools/modifiers/modifiertangents.h b/toonz/sources/include/tools/modifiers/modifiertangents.h
index a7e6232..960564e 100644
--- a/toonz/sources/include/tools/modifiers/modifiertangents.h
+++ b/toonz/sources/include/tools/modifiers/modifiertangents.h
@@ -26,17 +26,15 @@
 
 class DVAPI TModifierTangents: public TInputModifier {
 public:
-  class DVAPI Modifier: public TTrackModifier {
+  typedef TSubTrackHandler Handler;
+  class DVAPI Interpolator: public TTrackInterpolator {
   public:
-    explicit Modifier(TTrackHandler &handler):
-      TTrackModifier(handler) { }
-
     TTrackTangentList tangents;
-
-    TTrackPoint calcPoint(double originalIndex) override;
+    using TTrackInterpolator::TTrackInterpolator;
+    TTrackPoint interpolate(double index) override;
   };
 
-  TTrackTangent calcTangent(const TTrack &track, int index) const;
+  static TTrackTangent calcTangent(const TTrack &track, int index);
 
   void modifyTrack(
     const TTrack &track,
diff --git a/toonz/sources/include/tools/modifiers/modifiertest.h b/toonz/sources/include/tools/modifiers/modifiertest.h
index e410547..4732695 100644
--- a/toonz/sources/include/tools/modifiers/modifiertest.h
+++ b/toonz/sources/include/tools/modifiers/modifiertest.h
@@ -26,28 +26,31 @@
 
 class DVAPI TModifierTest : public TInputModifier {
 public:
-  class DVAPI Handler : public TTrackHandler {
+  class DVAPI Handler : public TMultiTrackHandler {
   public:
+    const double radius;
     std::vector<double> angles;
-    Handler(const TTrack &original) : TTrackHandler(original) {}
+    inline explicit Handler(double radius):
+      radius(std::max(TConsts::epsilon, radius)) { }
   };
 
-  class DVAPI Modifier : public TTrackModifier {
+  class DVAPI Interpolator : public TTrackInterpolator {
   public:
-    double angle;
-    double radius;
-    double speed;
-
-    Modifier(TTrackHandler &handler, double angle, double radius,
-             double speed = 0.25);
-    TTrackPoint calcPoint(double originalIndex) override;
+    const double angle;
+    const double radius;
+    const double speed;
+    inline Interpolator(TTrack &track, double angle, double radius, double speed):
+      TTrackInterpolator(track), angle(angle), radius(radius), speed(speed) { }
+    TTrackPoint interpolateFromOriginal(double originalIndex);
+    TTrackPoint interpolate(double index) override;
   };
 
 public:
-  const int count;
-  const double radius;
+  int count;
+  double radius;
+  double speed;
 
-  TModifierTest(int count, double radius);
+  TModifierTest(int count = 3, double radius = 40, double speed = 0.25);
 
   void modifyTrack(const TTrack &track,
                    TTrackList &outTracks) override;
diff --git a/toonz/sources/include/tools/tool.h b/toonz/sources/include/tools/tool.h
index fde613e..2be17c8 100644
--- a/toonz/sources/include/tools/tool.h
+++ b/toonz/sources/include/tools/tool.h
@@ -300,6 +300,17 @@ public:
     AllTargets = 0xffffffff,
   };
 
+  enum ToolHints  //!  Misc flags related with tool
+  { HintNone              = 0,
+    HintAssistants        = 1 << 0, //!< Draw asistants when tool active
+    HintGuidelines        = 1 << 1, //!< Draw asistant guidelines
+    HintAssistantsEnabled = 1 << 2, //!< Tool will use assisnats
+    
+    HintAssistantsAll     = HintAssistants
+                          | HintGuidelines
+                          | HintAssistantsEnabled,
+  };
+
 public:
   static TTool *getTool(std::string toolName, ToolTargetType targetType);
 
@@ -346,6 +357,7 @@ public:
 
   virtual ToolType getToolType() const = 0;
   ToolTargetType getTargetType() const { return (ToolTargetType)m_targetType; }
+  virtual unsigned int getToolHints() const;
 
   std::string getName() const { return m_name; }
 
diff --git a/toonz/sources/include/tools/track.h b/toonz/sources/include/tools/track.h
index c9268d4..a8177b3 100644
--- a/toonz/sources/include/tools/track.h
+++ b/toonz/sources/include/tools/track.h
@@ -37,13 +37,15 @@ class TTrack;
 class TTrackPoint;
 class TTrackTangent;
 class TTrackHandler;
-class TTrackToolHandler;
-class TTrackModifier;
+class TSubTrackHandler;
+class TMultiTrackHandler;
+class TTrackInterpolator;
 
 typedef TSmartPointerT<TTrack> TTrackP;
 typedef TSmartPointerT<TTrackHandler> TTrackHandlerP;
-typedef TSmartPointerT<TTrackToolHandler> TTrackToolHandlerP;
-typedef TSmartPointerT<TTrackModifier> TTrackModifierP;
+typedef TSmartPointerT<TSubTrackHandler> TSubTrackHandlerP;
+typedef TSmartPointerT<TMultiTrackHandler> TMultiTrackHandlerP;
+typedef TSmartPointerT<TTrackInterpolator> TTrackInterpolatorP;
 
 typedef std::vector<TTrackPoint> TTrackPointList;
 typedef std::vector<TTrackTangent> TTrackTangentList;
@@ -53,21 +55,15 @@ typedef std::vector<TTrackP> TTrackList;
 
 
 //*****************************************************************************************
-//    TTrackToolHandler definition
-//*****************************************************************************************
-
-class DVAPI TTrackToolHandler : public TSmartObject { };
-
-
-//*****************************************************************************************
 //    export template implementations for win32
 //*****************************************************************************************
 
 #ifdef _WIN32
 template class DVAPI TSmartPointerT<TTrack>;
 template class DVAPI TSmartPointerT<TTrackHandler>;
-template class DVAPI TSmartPointerT<TTrackToolHandler>;
-template class DVAPI TSmartPointerT<TTrackModifier>;
+template class DVAPI TSmartPointerT<TSubTrackHandler>;
+template class DVAPI TSmartPointerT<TMultiTrackHandler>;
+template class DVAPI TSmartPointerT<TTrackInterpolator>;
 #endif
 
 
@@ -133,28 +129,38 @@ public:
 //    TTrackHandler definition
 //*****************************************************************************************
 
-class DVAPI TTrackHandler : public TSmartObject {
+class DVAPI TTrackHandler : public TSmartObject { };
+
+
+//*****************************************************************************************
+//    TSubTrackHandler definition
+//*****************************************************************************************
+
+class DVAPI TSubTrackHandler: public TTrackHandler {
 public:
-  const TTrack &original;
-  std::vector<TTrackP> tracks;
-  TTrackHandler(const TTrack &original):
-    original(original) { }
+  TTrackP track;
 };
 
 
 //*****************************************************************************************
-//    TTrackModifier definition
+//    TMultiTrackHandler definition
 //*****************************************************************************************
 
-class DVAPI TTrackModifier : public TSmartObject {
+class DVAPI TMultiTrackHandler: public TTrackHandler {
 public:
-    TTrackHandler &handler;
-    const TTrack &original;
-    const double timeOffset;
+  std::vector<TTrackP> tracks;
+};
+
+
+//*****************************************************************************************
+//    TTrackInterpolator definition
+//*****************************************************************************************
 
-    explicit TTrackModifier(TTrackHandler &handler, double timeOffset = 0.0):
-      handler(handler), original(handler.original), timeOffset(timeOffset) { }
-    virtual TTrackPoint calcPoint(double originalIndex);
+class DVAPI TTrackInterpolator : public TSmartObject {
+public:
+  TTrack &track;
+  inline explicit TTrackInterpolator(TTrack &track);
+  virtual TTrackPoint interpolate(double index) = 0;
 };
 
 
@@ -177,15 +183,19 @@ public:
   const TInputState::ButtonHistory::Holder buttonHistory;
   const bool hasPressure;
   const bool hasTilt;
-  const TTrackModifierP modifier;
 
+  const TTrack* const original;
+  const double timeOffset;
+  const double rootTimeOffset;
+  
   mutable TTrackHandlerP handler;
-  mutable TTrackToolHandlerP toolHandler;
   mutable int pointsRemoved;
   mutable int pointsAdded;
   mutable int fixedPointsAdded;
 
 private:
+  friend class TTrackInterpolator;
+  TTrackInterpolatorP interpolator;
   TTrackPointList m_points;
   const TTrackPoint m_none;
   int m_pointsFixed;
@@ -198,15 +208,17 @@ public:
     const TInputState::KeyHistory::Holder &keyHistory = TInputState::KeyHistory::Holder(),
     const TInputState::ButtonHistory::Holder &buttonHistory = TInputState::ButtonHistory::Holder(),
     bool hasPressure = false,
-    bool hasTilt = false
+    bool hasTilt = false,
+    double timeOffset = 0
   );
 
-  explicit TTrack(const TTrackModifierP &modifier);
+  explicit TTrack(const TTrack &original, double timeOffset = 0);
+
+  const TTrackInterpolatorP& getInterpolator() const
+    { return interpolator; }
+  void removeInterpolator()
+    { interpolator.reset(); }
 
-  inline const TTrack* original() const
-    { return modifier ? &modifier->original : NULL; }
-  inline double timeOffset() const
-    { return modifier ? modifier->timeOffset : 0.0; }
   inline TTimerTicks ticks() const
     { return keyHistory.ticks(); }
   inline bool changed() const
@@ -217,12 +229,14 @@ public:
 
   inline int clampIndex(int index) const
     { return std::min(std::max(index, 0), size() - 1); }
+  inline double clampIndexFloat(double index) const
+    { return std::min(std::max(index, 0.0), (double)(size() - 1)); }
   inline int floorIndexNoClamp(double index) const
     { return (int)floor(index + TConsts::epsilon); }
   inline int floorIndex(double index) const
     { return clampIndex(floorIndexNoClamp(index)); }
   inline int ceilIndexNoClamp(double index) const
-    { return (int)ceil(index - TConsts::epsilon); }
+    { return floorIndexNoClamp(index) + 1; }
   inline int ceilIndex(double index) const
     { return clampIndex(ceilIndexNoClamp(index)); }
 
@@ -287,15 +301,15 @@ public:
   inline TInputState::KeyState::Holder getKeyState(double time) const
     { return keyHistory.get(time); }
   inline TInputState::KeyState::Holder getKeyState(const TTrackPoint &point) const
-    { return getKeyState(timeOffset() + point.time); }
+    { return getKeyState(rootTimeOffset + point.time); }
   inline TInputState::KeyState::Holder getCurrentKeyState() const
-    { return getKeyState(timeOffset() + current().time); }
+    { return getKeyState(rootTimeOffset + current().time); }
   inline TInputState::ButtonState::Holder getButtonState(double time) const
     { return buttonHistory.get(time); }
   inline TInputState::ButtonState::Holder getButtonState(const TTrackPoint &point) const
-    { return getButtonState(timeOffset() + point.time); }
+    { return getButtonState(rootTimeOffset + point.time); }
   inline TInputState::ButtonState::Holder getCurrentButtonState() const
-    { return getButtonState(timeOffset() + current().time); }
+    { return getButtonState(rootTimeOffset + current().time); }
 
 private:
   template<double TTrackPoint::*Field>
@@ -345,11 +359,25 @@ public:
     return interpolationLinear(p0.length, p1.length, frac);
   }
 
-  TTrackPoint calcPoint(double index) const;
+  inline TTrackPoint calcPoint(double index) const
+    { return interpolator ? interpolator->interpolate(index) : interpolateLinear(index); }
   TPointD calcTangent(double index, double distance = 0.1) const;
   double rootIndexByIndex(double index) const;
   TTrackPoint calcRootPoint(double index) const;
 
+  inline TTrackPoint pointFromOriginal(const TTrackPoint &originalPoint, double originalIndex) const {
+    TTrackPoint p = originalPoint;
+    p.originalIndex = original ? original->clampIndexFloat(originalIndex) : originalIndex;
+    p.time -= timeOffset;
+    return p;
+  }
+  
+  inline TTrackPoint pointFromOriginal(int originalIndex) const
+    { return original ? pointFromOriginal(original->point(originalIndex), originalIndex) : TTrackPoint(); }
+  
+  inline TTrackPoint calcPointFromOriginal(double originalIndex) const
+    { return original ? pointFromOriginal(original->calcPoint(originalIndex), originalIndex) : TTrackPoint(); }
+
   inline TTrackPoint interpolateLinear(double index) const {
     double frac;
     const TTrackPoint &p0 = floorPoint(index, &frac);
@@ -407,4 +435,24 @@ public:
 };
 
 
+
+//*****************************************************************************************
+//    TTrackInterpolator implemantation
+//*****************************************************************************************
+
+inline TTrackInterpolator::TTrackInterpolator(TTrack &track):
+  track(track) { track.interpolator = this; }
+
+
+//*****************************************************************************************
+//    TTrackIntrOrig definition
+//*****************************************************************************************
+
+class DVAPI TTrackIntrOrig : public TTrackInterpolator {
+public:
+  using TTrackInterpolator::TTrackInterpolator;
+  TTrackPoint interpolate(double index) override;
+};
+
+
 #endif
diff --git a/toonz/sources/include/toonz/strokegenerator.h b/toonz/sources/include/toonz/strokegenerator.h
index bc96dc7..1d253f0 100644
--- a/toonz/sources/include/toonz/strokegenerator.h
+++ b/toonz/sources/include/toonz/strokegenerator.h
@@ -39,6 +39,9 @@ class DVAPI StrokeGenerator {
 
   //! Ultimo punto del frammento visualizzato
   TPointD m_p0, /*! Ultimo punto del frammento visualizzato*/ m_p1;
+  
+  //! mark that stroke must be looped
+  bool m_loop;
 
   //! Visualizza i frammenti
   /*!
@@ -66,10 +69,19 @@ public:
     di 4*pixelSize2
 
     \param point      TThickPoint da aggiungere al vettore
-    \param pixelSize2 Dimensione pixel
+    \param pixelSize2 Size of pixel, use 0 to guarantee that new point will be added
+    \returns          true if point was actually added
   */
-  void add(const TThickPoint &point, double pixelSize2);
-
+  bool add(const TThickPoint &point, double pixelSize2);
+  
+  //! Remove last point (keep in mind that not each 'add' call produces new point)
+  void pop();
+  
+  //! Mark/unmark track as looped
+  void setLoop(bool loop = true);
+  
+  inline bool getLoop() const { return m_loop; }
+  
   TPointD getFirstPoint();  // returns the first point
 
   //! Filtra i punti di m_points
@@ -107,7 +119,7 @@ onlyLastPoint elementi di m_points
           \param onlyLastPoints Numero elementi sulla base dei quali creare la
 stroke
   */
-  TStroke *makeStroke(double error, UINT onlyLastPoints = 0) const;
+  TStroke *makeStroke(double error, UINT onlyLastPoints = 0, bool useLoop = false) const;
 };
 
 //===============================================================
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index 2675853..9ca30f7 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -51,10 +51,11 @@ set(HEADERS
     ../include/tools/assistant.h
     ../include/tools/modifiers/modifierassistants.h
     ../include/tools/modifiers/modifierline.h
-    ../include/tools/modifiers/modifiertangents.h
-    ../include/tools/modifiers/modifiertest.h
     ../include/tools/modifiers/modifiersegmentation.h
+    ../include/tools/modifiers/modifiersimplify.h
     ../include/tools/modifiers/modifiersmooth.h
+    ../include/tools/modifiers/modifiertangents.h
+    ../include/tools/modifiers/modifiertest.h
     ../include/tools/assistants/guidelineline.h
     ../include/tools/assistants/guidelineellipse.h
 )
@@ -127,10 +128,11 @@ set(SOURCES
     assistant.cpp
     modifiers/modifierassistants.cpp
     modifiers/modifierline.cpp
-    modifiers/modifiertangents.cpp
-    modifiers/modifiertest.cpp
     modifiers/modifiersegmentation.cpp
+    modifiers/modifiersimplify.cpp
     modifiers/modifiersmooth.cpp
+    modifiers/modifiertangents.cpp
+    modifiers/modifiertest.cpp
     assistants/guidelineline.cpp
     assistants/guidelineellipse.cpp
     assistants/assistantvanishingpoint.cpp
diff --git a/toonz/sources/tnztools/assistant.cpp b/toonz/sources/tnztools/assistant.cpp
index 826ab4e..d43d4c8 100644
--- a/toonz/sources/tnztools/assistant.cpp
+++ b/toonz/sources/tnztools/assistant.cpp
@@ -1,12 +1,24 @@
 
 #include <tools/assistant.h>
 
+#include <tools/tool.h>
+
+#include <toonz/tapplication.h>
+#include <toonz/txsheet.h>
+#include <toonz/txsheethandle.h>
+#include <toonz/txshlevelhandle.h>
+#include <toonz/tframehandle.h>
+#include <toonz/tobjecthandle.h>
+#include <toonz/dpiscale.h>
+
 #include <tgl.h>
 #include <tproperty.h>
 
 #include <limits>
 #include <cassert>
 
+
+
 #ifdef MACOSX
 const double line_width_scale = 1.5;
 #else
@@ -594,3 +606,83 @@ TAssistant::calcPerspectiveStep(
   outMax = (dx1 > 0.0 ? maxX : minX) - x0;
   return true;
 }
+
+
+bool
+TAssistant::scanAssistants(
+  TTool *tool,
+  const TPointD *positions,
+  int positionsCount,
+  TGuidelineList *outGuidelines,
+  bool draw,
+  bool enabledOnly,
+  bool markEnabled,
+  bool drawGuidelines,
+  TImage *skipImage )
+{
+  TGuidelineList guidelines;
+  if (drawGuidelines && !outGuidelines)
+    outGuidelines = &guidelines;
+
+  bool found = false;
+  bool findGuidelines = (positions && positionsCount > 0 && outGuidelines);
+  if (!findGuidelines) drawGuidelines = false;
+  bool doSomething = findGuidelines || draw;
+  
+  if (tool)
+  if (TToolViewer *viewer = tool->getViewer())
+  if (TApplication *application = tool->getApplication())
+  if (TXshLevelHandle *levelHandle = application->getCurrentLevel())
+  if (TXshLevel *level = levelHandle->getLevel())
+  if (TXshSimpleLevel *simpleLevel = level->getSimpleLevel())
+  if (TFrameHandle *frameHandle = application->getCurrentFrame())
+  if (TXsheetHandle *XsheetHandle = application->getCurrentXsheet())
+  if (TXsheet *Xsheet = XsheetHandle->getXsheet())
+  {
+    TPointD dpiScale = getCurrentDpiScale(simpleLevel, tool->getCurrentFid());
+    int frame = frameHandle->getFrame();
+    int count = Xsheet->getColumnCount();
+    TAffine worldToTrack;
+    if ( tool->getToolType() & TTool::LevelTool
+      && !application->getCurrentObject()->isSpline() )
+    {
+      worldToTrack.a11 /= dpiScale.x;
+      worldToTrack.a22 /= dpiScale.y;
+    }
+
+    for(int i = 0; i < count; ++i)
+      if (TXshColumn *column = Xsheet->getColumn(i))
+      if (column->isCamstandVisible())
+      if (column->isPreviewVisible())
+      if (TImageP image = Xsheet->getCell(frame, i).getImage(false))
+      if (image != skipImage)
+      if (image->getType() == TImage::META)
+      if (TMetaImage *metaImage = dynamic_cast<TMetaImage*>(image.getPointer()))
+      {
+        TAffine imageToTrack = worldToTrack * tool->getColumnMatrix(i);
+        if (draw) { glPushMatrix(); tglMultMatrix(imageToTrack); }
+
+        TMetaImage::Reader reader(*metaImage);
+        for(TMetaObjectListCW::iterator i = reader->begin(); i != reader->end(); ++i)
+          if (*i)
+          if (const TAssistant *assistant = (*i)->getHandler<TAssistant>())
+          if (!enabledOnly || assistant->getEnabled())
+          {
+            found = true;
+            if (findGuidelines)
+              for(int i = 0; i < positionsCount; ++i)
+                assistant->getGuidelines(positions[i], imageToTrack, *outGuidelines);
+            if (draw) assistant->draw(viewer, assistant->getEnabled() && markEnabled);
+            if (!doSomething) return true;
+          }
+
+        if (draw) glPopMatrix();
+      }
+  }
+  
+  if (drawGuidelines)
+    for(TGuidelineList::const_iterator i = outGuidelines->begin(); i != outGuidelines->end(); ++i)
+      (*i)->draw();
+  
+  return found;
+}
diff --git a/toonz/sources/tnztools/editassistantstool.cpp b/toonz/sources/tnztools/editassistantstool.cpp
index 25299d9..ed217b9 100644
--- a/toonz/sources/tnztools/editassistantstool.cpp
+++ b/toonz/sources/tnztools/editassistantstool.cpp
@@ -200,6 +200,8 @@ public:
 
   ToolType getToolType() const override
     { return TTool::LevelWriteTool; }
+  unsigned int getToolHints() const override
+    { return TTool::getToolHints() & ~HintAssistantsAll; }
   int getCursorId() const override
     { return ToolCursor::StrokeSelectCursor; }
   void onImageChanged() override {
@@ -599,56 +601,21 @@ public:
       {
         assistant->drawEdit(getViewer());
         assistant->getGuidelines(
-          m_currentPosition + m_currentPointOffset,
+          position,
           TAffine(),
           m_currentGuidelines );
       }
     
-    // draw assistans from other layers
-    TImage *currentImage = getImage(false);
-    if (TToolViewer *viewer = getViewer())
-    if (TApplication *application = getApplication())
-    if (TXshLevelHandle *levelHandle = application->getCurrentLevel())
-    if (TXshLevel *level = levelHandle->getLevel())
-    if (TXshSimpleLevel *simpleLevel = level->getSimpleLevel())
-    if (TFrameHandle *frameHandle = application->getCurrentFrame())
-    if (TXsheetHandle *XsheetHandle = application->getCurrentXsheet())
-    if (TXsheet *Xsheet = XsheetHandle->getXsheet())
-    {
-      TPointD dpiScale = getCurrentDpiScale(simpleLevel, getCurrentFid());
-      int frame = frameHandle->getFrame();
-      int count = Xsheet->getColumnCount();
-      TAffine worldToTrack;
-      worldToTrack.a11 /= dpiScale.x;
-      worldToTrack.a22 /= dpiScale.y;
-
-      for(int i = 0; i < count; ++i)
-      if (TXshColumn *column = Xsheet->getColumn(i))
-      if (column->isCamstandVisible())
-      if (column->isPreviewVisible())
-      if (TImageP image = Xsheet->getCell(frame, i).getImage(false))
-      if (image->getType() == TImage::META)
-      if (image != currentImage)
-      if (TMetaImage *metaImage = dynamic_cast<TMetaImage*>(image.getPointer()))
-      {
-        TAffine imageToTrack = worldToTrack * getColumnMatrix(i);
-        glPushMatrix(); tglMultMatrix(imageToTrack);
-
-        TMetaImage::Reader reader(*metaImage);
-        for(TMetaObjectListCW::iterator i = reader->begin(); i != reader->end(); ++i)
-        if (*i)
-        if (const TAssistant *assistant = (*i)->getHandler<TAssistant>()) {
-          assistant->getGuidelines(position, imageToTrack, m_currentGuidelines);
-          assistant->draw(viewer, false);
-        }
-          
-        glPopMatrix();
-      }
-    }
-
-    // draw guidelines
-    for(TGuidelineList::const_iterator i = m_currentGuidelines.begin(); i != m_currentGuidelines.end(); ++i)
-      (*i)->draw();
+    // draw assistans and guidelines from other layers
+    TAssistant::scanAssistants(
+      this,          // tool
+      &position, 1,  // pointer positions
+      nullptr,       // out guidelines
+      true,          // draw
+      false,         // enabled only
+      false,         // mark enabled
+      true,          // draw guidelines
+      nullptr );     // skip image
   }
 };
 
diff --git a/toonz/sources/tnztools/fullcolorbrushtool.cpp b/toonz/sources/tnztools/fullcolorbrushtool.cpp
index a2f3c28..d764b82 100644
--- a/toonz/sources/tnztools/fullcolorbrushtool.cpp
+++ b/toonz/sources/tnztools/fullcolorbrushtool.cpp
@@ -55,7 +55,7 @@ TEnv::DoubleVar FullcolorModifierSize("FullcolorModifierSize", 0);
 TEnv::DoubleVar FullcolorModifierOpacity("FullcolorModifierOpacity", 100);
 TEnv::IntVar FullcolorModifierEraser("FullcolorModifierEraser", 0);
 TEnv::IntVar FullcolorModifierLockAlpha("FullcolorModifierLockAlpha", 0);
-TEnv::IntVar FullcolorAssistants("FullcolorAssistants", 0);
+TEnv::IntVar FullcolorAssistants("FullcolorAssistants", 1);
 TEnv::StringVar FullcolorBrushPreset("FullcolorBrushPreset", "<custom>");
 
 //----------------------------------------------------------------------------------
@@ -150,7 +150,7 @@ FullColorBrushTool::FullColorBrushTool(std::string name)
 
   m_inputmanager.setHandler(this);
 #ifndef NDEBUG
-  m_modifierTest = new TModifierTest(5, 40);
+  m_modifierTest = new TModifierTest();
 #endif
   m_modifierLine         = new TModifierLine();
   m_modifierTangents     = new TModifierTangents();
@@ -305,8 +305,8 @@ bool FullColorBrushTool::askWrite(const TRect &rect) {
 //--------------------------------------------------------------------------------------------------
 
 bool FullColorBrushTool::preLeftButtonDown() {
-  m_modifierAssistants->drawOnly = !FullcolorAssistants;
-  m_inputmanager.drawPreview     = false; //!m_modifierAssistants->drawOnly;
+  m_modifierAssistants->magnetism = m_assistants.getValue() ? 1 : 0;
+  m_inputmanager.drawPreview      = false; //!m_modifierAssistants->drawOnly;
 
   m_inputmanager.clearModifiers();
   m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
@@ -342,7 +342,7 @@ void FullColorBrushTool::handleMouseEvent(MouseEventType type,
   bool control  = e.getModifiersMask() & TMouseEvent::CTRL_KEY;
 
   if (shift && type == ME_DOWN && e.button() == Qt::LeftButton && !m_started) {
-    m_modifierAssistants->drawOnly = true;
+    m_modifierAssistants->magnetism = 0;
     m_inputmanager.clearModifiers();
     m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
     m_inputmanager.addModifier(
@@ -363,9 +363,14 @@ void FullColorBrushTool::handleMouseEvent(MouseEventType type,
     THoverList hovers(1, pos);
     m_inputmanager.hoverEvent(hovers);
   } else {
-    m_inputmanager.trackEvent(e.isTablet(), 0, pos,
-                              e.isTablet() ? &e.m_pressure : nullptr, nullptr,
-                              type == ME_UP, t);
+    bool   isMyPaint   = getApplication()->getCurrentLevelStyle()->getTagId() == 4001;
+    int    deviceId    = e.isTablet() ? 1 : 0;
+    double defPressure = isMyPaint ? 0.5 : 1.0;
+    bool   hasPressure = e.isTablet();
+    double pressure    = hasPressure ? e.m_pressure : defPressure;
+    bool   final       = type == ME_UP;
+    m_inputmanager.trackEvent(
+      deviceId, 0, pos, pressure, TPointD(), hasPressure, false, final, t);
     m_inputmanager.processTracks();
   }
 }
@@ -519,20 +524,24 @@ void FullColorBrushTool::inputPaintTrackPoint(const TTrackPoint &point,
 
   // init brush
   TrackHandler *handler;
-  if (track.size() == track.pointsAdded && !track.toolHandler && m_workRaster) {
+  if (track.size() == track.pointsAdded && !track.handler && m_workRaster) {
     mypaint::Brush mypaintBrush;
     applyToonzBrushSettings(mypaintBrush);
     handler = new TrackHandler(m_workRaster, *this, mypaintBrush);
     handler->brush.beginStroke();
-    track.toolHandler = handler;
+    track.handler = handler;
   }
-  handler = dynamic_cast<TrackHandler *>(track.toolHandler.getPointer());
+  handler = dynamic_cast<TrackHandler *>(track.handler.getPointer());
   if (!handler) return;
 
+  bool   isMyPaint   = getApplication()->getCurrentLevelStyle()->getTagId() == 4001;
+  double defPressure = isMyPaint ? 0.5 : 1.0;
+  double pressure    = m_enabledPressure ? point.pressure : defPressure;
+  
   // paint stroke
   m_strokeSegmentRect.empty();
   handler->brush.strokeTo(point.position + rasCenter,
-                          m_enabledPressure ? point.pressure : 0.5, point.tilt,
+                          pressure, point.tilt,
                           point.time - track.previous().time);
   if (track.pointsAdded == 1 && track.finished()) handler->brush.endStroke();
 
diff --git a/toonz/sources/tnztools/fullcolorbrushtool.h b/toonz/sources/tnztools/fullcolorbrushtool.h
index cdb8450..dd9b831 100644
--- a/toonz/sources/tnztools/fullcolorbrushtool.h
+++ b/toonz/sources/tnztools/fullcolorbrushtool.h
@@ -42,7 +42,7 @@ class FullColorBrushTool final : public TTool,
                                  public TInputHandler {
   Q_DECLARE_TR_FUNCTIONS(FullColorBrushTool)
 public:
-  class TrackHandler : public TTrackToolHandler {
+  class TrackHandler : public TTrackHandler {
   public:
     MyPaintToonzBrush brush;
 
@@ -59,7 +59,10 @@ private:
 public:
   FullColorBrushTool(std::string name);
 
-  ToolType getToolType() const override { return TTool::LevelWriteTool; }
+  ToolType getToolType() const override
+    { return TTool::LevelWriteTool; }
+  unsigned int getToolHints() const override
+    { return TTool::getToolHints() & ~HintAssistantsAll; }
 
   ToolOptionsBox *createOptionsBox() override;
 
diff --git a/toonz/sources/tnztools/inputmanager.cpp b/toonz/sources/tnztools/inputmanager.cpp
index 06b7f8d..ee5c10c 100644
--- a/toonz/sources/tnztools/inputmanager.cpp
+++ b/toonz/sources/tnztools/inputmanager.cpp
@@ -33,27 +33,28 @@ TInputModifier::modifyTrack(
   TTrackList &outTracks )
 {
   if (!track.handler) {
-      track.handler = new TTrackHandler(track);
-      track.handler->tracks.push_back(
-        new TTrack(
-          new TTrackModifier(*track.handler) ));
+    TSubTrackHandler *handler = new TSubTrackHandler();
+    track.handler = handler;
+    handler->track = new TTrack(track);
+    new TTrackIntrOrig(*handler->track);
   }
 
-  outTracks.insert(
-    outTracks.end(),
-    track.handler->tracks.begin(),
-    track.handler->tracks.end() );
-  if (!track.changed())
+  TSubTrackHandler *handler = dynamic_cast<TSubTrackHandler*>(track.handler.getPointer());
+  if (!handler)
     return;
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
 
+  if (!track.changed())
+    return;
+  
   int start = std::max(0, track.size() - track.pointsAdded);
-  for(TTrackList::const_iterator ti = track.handler->tracks.begin(); ti != track.handler->tracks.end(); ++ti) {
-    TTrack &subTrack = **ti;
-    subTrack.truncate(start);
-    for(int i = start; i < track.size(); ++i)
-      subTrack.push_back( subTrack.modifier->calcPoint(i), false );
-    subTrack.fix_to(track.fixedSize());
-  }
+  subTrack.truncate(start);
+  for(int i = start; i < track.size(); ++i)
+    subTrack.push_back(subTrack.pointFromOriginal(i), false);
+  subTrack.fix_to(track.fixedSize());
+  
   track.resetChanges();
 }
 
@@ -190,7 +191,7 @@ TInputHandler::inputPaintTracks(const TTrackList &tracks) {
       const TTrack &t = **i;
       if (t.pointsAdded > 0) {
         TTimerTicks ticks = t.ticks();
-        double timeOffset = t.timeOffset() + t.current().time;
+        double timeOffset = t.rootTimeOffset + t.current().time;
         if (!track || (ticks - minTicks)*TToolTimer::frequency + timeOffset - minTimeOffset < 0.0) {
           track = *i;
           minTicks = ticks;
@@ -485,21 +486,23 @@ TInputManager::trackEvent(
   TInputState::DeviceId deviceId,
   TInputState::TouchId touchId,
   const TPointD &position,
-  const double *pressure,
-  const TPointD *tilt,
+  const double pressure,
+  const TPointD &tilt,
+  bool hasPressure,
+  bool hasTilt,
   bool final,
   TTimerTicks ticks )
 {
   ticks = fixTicks(ticks);
-  TTrackP track = getTrack(deviceId, touchId, ticks, (bool)pressure, (bool)tilt);
+  TTrackP track = getTrack(deviceId, touchId, ticks, hasPressure, hasTilt);
   if (!track->finished()) {
     ticks = fixTicks(ticks);
-    double time = (double)(ticks - track->ticks())*TToolTimer::step - track->timeOffset();
+    double time = (double)(ticks - track->ticks())*TToolTimer::step - track->rootTimeOffset;
     addTrackPoint(
       track,
       position,
-      pressure ? *pressure : 0.5,
-      tilt ? *tilt : TPointD(),
+      pressure,
+      tilt,
       time,
       final );
   }
diff --git a/toonz/sources/tnztools/modifiers/modifierassistants.cpp b/toonz/sources/tnztools/modifiers/modifierassistants.cpp
index 597e97d..3613446 100644
--- a/toonz/sources/tnztools/modifiers/modifierassistants.cpp
+++ b/toonz/sources/tnztools/modifiers/modifierassistants.cpp
@@ -20,20 +20,15 @@
 
 
 //*****************************************************************************************
-//    TModifierAssistants::Modifier implementation
+//    TModifierAssistants::Interpolator implementation
 //*****************************************************************************************
 
 
-TModifierAssistants::Modifier::Modifier(TTrackHandler &handler):
-  TTrackModifier(handler),
-  initialized()
-{ }
-
-
 TTrackPoint
-TModifierAssistants::Modifier::calcPoint(double originalIndex) {
-  TTrackPoint p = TTrackModifier::calcPoint(originalIndex);
-  return guidelines.empty() ? p : guidelines.front()->smoothTransformPoint(p);
+TModifierAssistants::Interpolator::interpolate(double index) {
+  TTrackPoint p = track.original ? track.calcPointFromOriginal(index)
+                                 : track.interpolateLinear(index);
+  return guidelines.empty() ? p : guidelines.front()->smoothTransformPoint(p, magnetism);
 }
 
 
@@ -42,8 +37,8 @@ TModifierAssistants::Modifier::calcPoint(double originalIndex) {
 //*****************************************************************************************
 
 
-TModifierAssistants::TModifierAssistants(bool drawOnly):
-  drawOnly(drawOnly),
+TModifierAssistants::TModifierAssistants(double magnetism):
+  magnetism(magnetism),
   sensitiveLength(50.0) { }
 
 
@@ -53,60 +48,22 @@ TModifierAssistants::scanAssistants(
   int positionsCount,
   TGuidelineList *outGuidelines,
   bool draw,
-  bool enabledOnly ) const
+  bool enabledOnly,
+  bool drawGuidelines ) const
 {
-  bool found = false;
   if (TInputManager *manager = getManager())
   if (TInputHandler *handler = manager->getHandler())
-  if (TTool *tool = handler->inputGetTool())
-  if (TToolViewer *viewer = tool->getViewer())
-  if (TApplication *application = tool->getApplication())
-  if (TXshLevelHandle *levelHandle = application->getCurrentLevel())
-  if (TXshLevel *level = levelHandle->getLevel())
-  if (TXshSimpleLevel *simpleLevel = level->getSimpleLevel())
-  if (TFrameHandle *frameHandle = application->getCurrentFrame())
-  if (TXsheetHandle *XsheetHandle = application->getCurrentXsheet())
-  if (TXsheet *Xsheet = XsheetHandle->getXsheet())
-  {
-    TPointD dpiScale = getCurrentDpiScale(simpleLevel, tool->getCurrentFid());
-    bool findGuidelines = (positions && positionsCount > 0 && outGuidelines);
-    bool doSomething = findGuidelines || draw;
-
-    int frame = frameHandle->getFrame();
-    int count = Xsheet->getColumnCount();
-    TAffine worldToTrack;
-    worldToTrack.a11 /= dpiScale.x;
-    worldToTrack.a22 /= dpiScale.y;
-
-    for(int i = 0; i < count; ++i)
-      if (TXshColumn *column = Xsheet->getColumn(i))
-      if (column->isCamstandVisible())
-      if (column->isPreviewVisible())
-      if (TImageP image = Xsheet->getCell(frame, i).getImage(false))
-      if (image->getType() == TImage::META)
-      if (TMetaImage *metaImage = dynamic_cast<TMetaImage*>(image.getPointer()))
-      {
-        TAffine imageToTrack = worldToTrack * tool->getColumnMatrix(i);
-        if (draw) { glPushMatrix(); tglMultMatrix(imageToTrack); }
-
-        TMetaImage::Reader reader(*metaImage);
-        for(TMetaObjectListCW::iterator i = reader->begin(); i != reader->end(); ++i)
-          if (*i)
-          if (const TAssistant *assistant = (*i)->getHandler<TAssistant>())
-          if (!enabledOnly || assistant->getEnabled())
-          {
-            found = true;
-            if (findGuidelines)
-              for(int i = 0; i < positionsCount; ++i)
-                assistant->getGuidelines(positions[i], imageToTrack, *outGuidelines);
-            if (draw) assistant->draw(viewer, !drawOnly);
-            if (!doSomething) return true;
-          }
-
-        if (draw) glPopMatrix();
-      }
-  }
-  return found;
+    return TAssistant::scanAssistants(
+      handler->inputGetTool(),
+      positions,
+      positionsCount,          
+      outGuidelines,
+      draw,
+      enabledOnly,
+      magnetism > 0,
+      drawGuidelines,
+      nullptr );
+  return false;
 }
 
 
@@ -116,29 +73,35 @@ TModifierAssistants::modifyTrack(
   TTrackList &outTracks )
 {
   if (!track.handler) {
-    track.handler = new TTrackHandler(track);
-    Modifier *modifier = new Modifier(*track.handler);
-    track.handler->tracks.push_back(new TTrack(modifier));
+    Handler *handler = new Handler();
+    track.handler = handler;
+    handler->track = new TTrack(track);
+    new Interpolator(*handler->track, magnetism);
   }
-
-  outTracks.push_back(track.handler->tracks.front());
-  TTrack &subTrack = *track.handler->tracks.front();
-  if (!track.changed())
+  
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return;
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+  Interpolator *intr = dynamic_cast<Interpolator*>(subTrack.getInterpolator().getPointer());
+  if (!intr)
     return;
-  Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer());
-  if (!modifier)
+  
+  if (!track.changed())
     return;
-    
+  
   // remove points
   int start = track.size() - track.pointsAdded;
   if (start < 0) start = 0;
 
-  if (!drawOnly && start <= 0) {
-    modifier->guidelines.clear();
-    scanAssistants(&track[0].position, 1, &modifier->guidelines, false, true);
+  if (intr->magnetism && start <= 0) {
+    intr->guidelines.clear();
+    scanAssistants(&track[0].position, 1, &intr->guidelines, false, true, false);
   }
   
-  bool fixed = subTrack.fixedSize() || modifier->guidelines.size() <= 1;
+  bool fixed = subTrack.fixedSize() || intr->guidelines.size() <= 1;
 
   // select guideline
   if (!fixed)
@@ -152,11 +115,11 @@ TModifierAssistants::modifyTrack(
         if (!objHandle->isSpline())
           trackToScreen *= TScale(viewer->getDpiScale().x, viewer->getDpiScale().y);
     trackToScreen *= viewer->get3dViewMatrix().get2d();
-    TGuidelineP guideline = TGuideline::findBest(modifier->guidelines, track, trackToScreen, fixed);
-    if (guideline != modifier->guidelines.front())
-      for(int i = 1; i < (int)modifier->guidelines.size(); ++i)
-        if (modifier->guidelines[i] == guideline) {
-          std::swap(modifier->guidelines[i], modifier->guidelines.front());
+    TGuidelineP guideline = TGuideline::findBest(intr->guidelines, track, trackToScreen, fixed);
+    if (guideline != intr->guidelines.front())
+      for(int i = 1; i < (int)intr->guidelines.size(); ++i)
+        if (intr->guidelines[i] == guideline) {
+          std::swap(intr->guidelines[i], intr->guidelines.front());
           start = 0;
           break;
         }
@@ -165,7 +128,7 @@ TModifierAssistants::modifyTrack(
   // add points
   subTrack.truncate(start);
   for(int i = start; i < track.size(); ++i)
-    subTrack.push_back( modifier->calcPoint(i), false );
+    subTrack.push_back( intr->interpolate(i), false );
   
   // fix points
   if (fixed || track.fixedFinished())
@@ -177,7 +140,7 @@ TModifierAssistants::modifyTrack(
 
 TRectD
 TModifierAssistants::calcDrawBounds(const TTrackList&, const THoverList&) {
-  if (scanAssistants(NULL, 0, NULL, false, false))
+  if (scanAssistants(NULL, 0, NULL, false, false, false))
     return TConsts::infiniteRectD;
   return TRectD();
 }
@@ -185,16 +148,20 @@ TModifierAssistants::calcDrawBounds(const TTrackList&, const THoverList&) {
 
 void
 TModifierAssistants::drawTrack(const TTrack &track) {
-  if (!track.handler) return;
-  TTrack &subTrack = *track.handler->tracks.front();
-  if (Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer())) {
-    const TGuidelineList &guidelines = modifier->guidelines;
-    if (!guidelines.empty()) {
-      guidelines.front()->draw(true);
-      for(TGuidelineList::const_iterator i = guidelines.begin() + 1; i != guidelines.end(); ++i)
-        (*i)->draw();
-    }
-  }
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler) return;
+  
+  TTrack &subTrack = *handler->track;
+  Interpolator *intr = dynamic_cast<Interpolator*>(subTrack.getInterpolator().getPointer());
+  if (!intr) return;
+  
+  const TGuidelineList &guidelines = intr->guidelines;
+  if (guidelines.empty())
+    return;
+  
+  guidelines.front()->draw(true);
+  for(TGuidelineList::const_iterator i = guidelines.begin() + 1; i != guidelines.end(); ++i)
+    (*i)->draw();
 }
 
 
@@ -205,21 +172,17 @@ TModifierAssistants::draw(const TTrackList &tracks, const THoverList &hovers) {
   if (tracks.empty()) // hide hovers if track exists
     allHovers.insert(allHovers.end(), hovers.begin(), hovers.end());
   for(TTrackList::const_iterator i = tracks.begin(); i != tracks.end(); ++i)
-    if ((*i)->handler && !(*i)->handler->tracks.empty() && !(*i)->handler->tracks.front()->empty())
-      allHovers.push_back( (*i)->handler->tracks.front()->back().position );
+    if (Handler *handler = dynamic_cast<Handler*>((*i)->handler.getPointer()))
+      allHovers.push_back( handler->track->back().position );
   
-  // draw assistants
-  TGuidelineList guidelines;
+  // draw assistants and guidelines
   scanAssistants(
     allHovers.empty() ? NULL : &allHovers.front(),
     (int)allHovers.size(),
-    &guidelines,
+    nullptr,
     true,
-    false );
-
-  // draw guidelines
-  for(TGuidelineList::const_iterator i = guidelines.begin(); i != guidelines.end(); ++i)
-    (*i)->draw(false, !drawOnly);
+    false,
+    true );
 
   // draw tracks
   TInputModifier::drawTracks(tracks);
diff --git a/toonz/sources/tnztools/modifiers/modifierline.cpp b/toonz/sources/tnztools/modifiers/modifierline.cpp
index e678914..382589a 100644
--- a/toonz/sources/tnztools/modifiers/modifierline.cpp
+++ b/toonz/sources/tnztools/modifiers/modifierline.cpp
@@ -2,10 +2,13 @@
 
 #include <tools/modifiers/modifierline.h>
 
+
+
 //*****************************************************************************************
 //    TModifierLine implementation
 //*****************************************************************************************
 
+
 static inline void calcFixedAngle(const TTrackPoint &p0, TTrackPoint &p1) {
   TPointD p = p1.position - p0.position;
   double l  = sqrt(p.x * p.x + p.y * p.y);
@@ -20,70 +23,51 @@ static inline void calcFixedAngle(const TTrackPoint &p0, TTrackPoint &p1) {
   p1.position = p0.position + p;
 }
 
-TTrackPoint TModifierLine::Modifier::calcPoint(double originalIndex) {
-  if (original.empty()) return TTrackPoint();
-  if (original.size() < 2) return original.front();
-  TTrackPoint p0 = original.front();
-  TTrackPoint p1 = original.back();
-  if (fixAngle) calcFixedAngle(p0, p1);
-  return TTrack::interpolationLinear(p0, p1,
-                                     originalIndex / (original.size() - 1));
-}
 
 void TModifierLine::modifyTrack(const TTrack &track,
                                 TTrackList &outTracks) {
   if (!track.handler) {
-    track.handler       = new TTrackHandler(track);
-    Modifier *modifier  = new Modifier(*track.handler);
-    track.handler->tracks.push_back(new TTrack(modifier));
+    Handler *handler  = new Handler();
+    track.handler     = handler;
+    handler->track    = new TTrack(track);
   }
 
-  if (!track.changed()) return;
-  if (track.handler->tracks.empty()) return;
-
-  TTrack &subTrack   = *track.handler->tracks.front();
-  Modifier *modifier = dynamic_cast<Modifier *>(subTrack.modifier.getPointer());
-  if (!modifier) {
-    track.resetChanges();
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return;
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+  
+  if (!track.changed())
     return;
-  }
 
-  bool fixAngle = track.getKeyState(track.back()).isPressed(TKey::control);
-  outTracks.push_back(track.handler->tracks.front());
+  subTrack.truncate(0);
 
+  // calc max pressure
   int i1             = track.size();
   int i0             = i1 - track.pointsAdded;
-  double maxPressure = modifier->maxPressure;
+  double maxPressure = handler->maxPressure;
   if (track.pointsRemoved) {
     maxPressure = 0;
     i0          = 0;
   }
-  for (int i = i0; i < i1; ++i) {
+  for(int i = i0; i < i1; ++i) {
     double p = track[i].pressure;
     if (maxPressure < p) maxPressure = p;
   }
-  modifier->maxPressure = maxPressure;
-  modifier->fixAngle    = fixAngle;
-
-  subTrack.truncate(0);
-
-  if (track.size() > 0) {
-    TTrackPoint p   = track.front();
-    p.originalIndex = 0;
-    p.pressure      = maxPressure;
-    p.tilt          = TPointD();
-    subTrack.push_back(p, false);
-  }
-
+  handler->maxPressure = maxPressure;
+  
+  if (track.size() > 0)
+    subTrack.push_back(subTrack.pointFromOriginal(0), false);
+  
   if (track.size() > 1) {
-    TTrackPoint p   = track.back();
-    p.originalIndex = track.size() - 1;
-    p.pressure      = maxPressure;
-    p.tilt          = TPointD();
-    if (fixAngle) calcFixedAngle(subTrack.front(), p);
+    TTrackPoint p = subTrack.pointFromOriginal(track.size() - 1);
+    if (track.getKeyState(track.back()).isPressed(TKey::control))
+      calcFixedAngle(subTrack.front(), p);
     subTrack.push_back(p, false);
   }
-
+    
   if (track.fixedFinished())
     subTrack.fix_all();
 
diff --git a/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp b/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp
index ba1232e..4718afc 100644
--- a/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp
+++ b/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp
@@ -42,7 +42,7 @@ TModifierSegmentation::addSegments(
   }
 
   --level;
-  TTrackPoint p = track.modifier->calcPoint(0.5*(p0.originalIndex + p1.originalIndex));
+  TTrackPoint p = track.calcPointFromOriginal(0.5*(p0.originalIndex + p1.originalIndex));
   addSegments(track, p0, p, level);
   addSegments(track, p, p1, level);
 }
@@ -54,21 +54,22 @@ TModifierSegmentation::modifyTrack(
   TTrackList &outTracks )
 {
   if (!track.handler) {
-    track.handler = new TTrackHandler(track);
-    track.handler->tracks.push_back(
-      new TTrack(
-        new TTrackModifier(*track.handler) ));
+    Handler *handler = new Handler();
+    track.handler = handler;
+    handler->track = new TTrack(track);
+    new Interpolator(*handler->track);
   }
 
-  if (track.handler->tracks.empty())
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
     return;
-
-  TTrack &subTrack = *track.handler->tracks.front();
-  outTracks.push_back(track.handler->tracks.front());
-
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+  
   if (!track.changed())
     return;
-
+  
   // remove points
   int start = track.size() - track.pointsAdded;
   if (start < 0) start = 0;
@@ -76,9 +77,9 @@ TModifierSegmentation::modifyTrack(
   subTrack.truncate(subStart);
 
   // add points
-  TTrackPoint p0 = subTrack.modifier->calcPoint(start - 1);
+  TTrackPoint p0 = subTrack.pointFromOriginal(start - 1);
   for(int i = start; i < track.size(); ++i) {
-    TTrackPoint p1 = subTrack.modifier->calcPoint(i);
+    TTrackPoint p1 = subTrack.pointFromOriginal(i);
     addSegments(subTrack, p0, p1, m_maxLevel);
     p0 = p1;
   }
diff --git a/toonz/sources/tnztools/modifiers/modifiersimplify.cpp b/toonz/sources/tnztools/modifiers/modifiersimplify.cpp
new file mode 100644
index 0000000..612af0b
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifiersimplify.cpp
@@ -0,0 +1,75 @@
+
+
+#include <tools/modifiers/modifiersimplify.h>
+#include <algorithm>
+
+
+//*****************************************************************************************
+//    TModifierSimplify implementation
+//*****************************************************************************************
+
+
+TModifierSimplify::TModifierSimplify(double step):
+  step(step) { }
+
+
+void
+TModifierSimplify::modifyTrack(
+  const TTrack &track,
+  TTrackList &outTracks )
+{
+  if (!track.handler) {
+    Handler *handler = new Handler();
+    track.handler = handler;
+    handler->track = new TTrack(track);
+    new Interpolator(*handler->track);
+  }
+
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return;
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+
+  if (!track.changed())
+    return;
+  
+  // remove points
+  int start = track.size() - track.pointsAdded;
+  if (start < 0) start = 0;
+  int subStart = subTrack.floorIndex(subTrack.indexByOriginalIndex(start));
+  if (subStart < 0) subStart = 0;
+  start = track.floorIndex(subTrack[subStart].originalIndex);
+  if (start < 0) start = 0;
+  subTrack.truncate(subStart);
+
+  // add points
+  double step2 = step*step;
+  TTrackPoint p0 = subTrack.back();
+  for(int i = start; i < track.size(); ++i) {
+    const TTrackPoint &p1 = subTrack.pointFromOriginal(i);
+    if (!subTrack.empty() && tdistance2(p1.position, p0.position) < step2) {
+      if (p0.pressure < p1.pressure) p0.pressure = p1.pressure;
+      if (i == track.size() - 1) p0.position = p1.position;
+      p0.tilt          = p1.tilt;
+      p0.time          = p1.time;
+      p0.final         = p1.final;
+      subTrack.pop_back();
+      subTrack.push_back(p0, false);
+    } else {
+      p0 = p1;
+      subTrack.push_back(p0, false);
+    }
+  }
+
+  // fix points
+  if (track.fixedFinished())
+    subTrack.fix_all();
+  else
+  if (track.fixedSize())
+    subTrack.fix_to(
+      subTrack.floorIndex( subTrack.indexByOriginalIndex(track.fixedSize()-1) ));
+  
+  track.resetChanges();
+}
diff --git a/toonz/sources/tnztools/modifiers/modifiersmooth.cpp b/toonz/sources/tnztools/modifiers/modifiersmooth.cpp
index 449623f..ac0b212 100644
--- a/toonz/sources/tnztools/modifiers/modifiersmooth.cpp
+++ b/toonz/sources/tnztools/modifiers/modifiersmooth.cpp
@@ -4,38 +4,14 @@
 #include <algorithm>
 
 
-//*****************************************************************************************
-//    TModifierSmooth::Modifier implementation
-//*****************************************************************************************
-
-
-TModifierSmooth::Modifier::Modifier(TTrackHandler &handler, int radius):
-  TTrackModifier(handler),
-  radius(std::max(radius, 0)),
-  smoothedTrack()
-{ }
-
-
-TTrackPoint
-TModifierSmooth::Modifier::calcPoint(double originalIndex) {
-  return smoothedTrack
-       ? smoothedTrack->interpolateLinear(originalIndex)
-       : TTrackModifier::calcPoint(originalIndex);
-}
-
 
 //*****************************************************************************************
 //    TModifierSmooth implementation
 //*****************************************************************************************
 
 
-TModifierSmooth::TModifierSmooth(int radius): m_radius()
-  { setRadius(radius); }
-
-
-void
-TModifierSmooth::setRadius(int radius)
-  { m_radius = std::max(0, radius); }
+TModifierSmooth::TModifierSmooth(int radius):
+  radius(radius) { }
 
 
 void
@@ -43,32 +19,25 @@ TModifierSmooth::modifyTrack(
   const TTrack &track,
   TTrackList &outTracks )
 {
-  if (!m_radius) {
-    TInputModifier::modifyTrack(track, outTracks);
-    return;
-  }
+  int radius = abs(this->radius);
   
-  if (!track.handler) {
-    track.handler = new TTrackHandler(track);
-    Modifier *modifier = new Modifier(*track.handler, m_radius);
-    modifier->smoothedTrack = new TTrack(modifier);
-    track.handler->tracks.push_back(modifier->smoothedTrack);
+  if (!track.handler && radius) {
+    Handler *handler = new Handler(radius);
+    track.handler = handler;
+    handler->track = new TTrack(track);
   }
 
-  if (track.handler->tracks.empty())
-    return;
-
-  TTrack &subTrack = *track.handler->tracks.front();
-  outTracks.push_back(track.handler->tracks.front());
-
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return TInputModifier::modifyTrack(track, outTracks);
+  
+  radius = handler->radius;
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+  
   if (!track.changed())
     return;
   
-  Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer());
-  if (!modifier)
-    return;
-  int radius = modifier->radius;
-
   // remove points
   int start = std::max(0, track.size() - track.pointsAdded);
   subTrack.truncate(start);
@@ -87,18 +56,23 @@ TModifierSmooth::modifyTrack(
     if (i < start)
       continue;
 
-    int oi = track.clampIndex(i - radius);
-    const TTrackPoint &p = track[oi];
-    subTrack.push_back(
-      TTrackPoint(
-        accum.position*k,
-        accum.pressure*k,
-        accum.tilt*k,
-        oi,
-        p.time,
-        0,
-        p.final ),
-      false );
+    double originalIndex;
+    if (i <= radius) {
+      originalIndex = i/(double)(radius + 1);
+    } else
+    if (i >= size - radius - 1) {
+      originalIndex = track.size() - 1 - (size - i - 1)/(double)(radius + 1);
+    } else {
+      originalIndex = i - radius;
+    }
+    
+    TTrackPoint p = subTrack.pointFromOriginal(i - radius);
+    p.position = accum.position*k;
+    p.pressure = accum.pressure*k;
+    p.tilt = accum.tilt*k;
+    p.originalIndex = originalIndex;
+    p.final = p.final && i == size - 1;
+    subTrack.push_back(p, false);
     
     const TTrackPoint &p0 = track[i - 2*radius];
     accum.position -= p0.position;
diff --git a/toonz/sources/tnztools/modifiers/modifiertangents.cpp b/toonz/sources/tnztools/modifiers/modifiertangents.cpp
index c7f11e0..e1cfcd7 100644
--- a/toonz/sources/tnztools/modifiers/modifiertangents.cpp
+++ b/toonz/sources/tnztools/modifiers/modifiertangents.cpp
@@ -4,22 +4,22 @@
 
 
 //*****************************************************************************************
-//    TModifierTangents::Modifier implementation
+//    TModifierTangents::Interpolator implementation
 //*****************************************************************************************
 
 
 TTrackPoint
-TModifierTangents::Modifier::calcPoint(double originalIndex) {
+TModifierTangents::Interpolator::interpolate(double index) {
   double frac;
-  int i0 = original.floorIndex(originalIndex, &frac);
+  int i0 = track.floorIndex(index, &frac);
   int i1 = i0 + 1;
 
   TTrackPoint p;
 
   // calculate tangent length to make monotonic subdivisions,
   // (because we don't have valid input time)
-  const TTrackPoint &p0 = original[i0];
-  const TTrackPoint &p1 = original[i1];
+  const TTrackPoint &p0 = track[i0];
+  const TTrackPoint &p1 = track[i1];
   TTrackTangent t0 = i0 >= 0 && i0 < (int)tangents.size() ? tangents[i0] : TTrackTangent();
   TTrackTangent t1 = i1 >= 0 && i1 < (int)tangents.size() ? tangents[i1] : TTrackTangent();
   double l = p1.length - p0.length;
@@ -36,9 +36,7 @@ TModifierTangents::Modifier::calcPoint(double originalIndex) {
   t1.tilt.x *= l;
   t1.tilt.y *= l;
   
-  p = TTrack::interpolationSpline(p0, p1, t0, t1, frac);
-  p.originalIndex = originalIndex;
-  return p;
+  return TTrack::interpolationSpline(p0, p1, t0, t1, frac);
 }
 
 
@@ -48,7 +46,7 @@ TModifierTangents::Modifier::calcPoint(double originalIndex) {
 
 
 TTrackTangent
-TModifierTangents::calcTangent(const TTrack &track, int index) const {
+TModifierTangents::calcTangent(const TTrack &track, int index) {
   if (index <= 0 || index >= track.size() - 1)
     return TTrackTangent();
   
@@ -60,7 +58,7 @@ TModifierTangents::calcTangent(const TTrack &track, int index) const {
   // instead of time when message dispatched
   //double k = p2.time - p0.time;
   
-  // calculate tangent based on length, util we have no valid times
+  // calculate tangent based on length, until we have no valid times
   double k = p2.length - p0.length;
   
   k = k > TConsts::epsilon ? 1/k : 0;
@@ -77,36 +75,39 @@ TModifierTangents::modifyTrack(
   TTrackList &outTracks )
 {
   if (!track.handler) {
-    track.handler = new TTrackHandler(track);
-    track.handler->tracks.push_back(
-      new TTrack(
-        new Modifier(*track.handler) ));
+    Handler *handler = new Handler();
+    track.handler = handler;
+    handler->track = new TTrack(track);
+    new Interpolator(*handler->track);
   }
-
-  if (track.handler->tracks.empty())
+  
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
     return;
-
-  TTrack &subTrack = *track.handler->tracks.front();
-  Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer());
-  if (!modifier)
+  
+  outTracks.push_back(handler->track);
+  TTrack &subTrack = *handler->track;
+  Interpolator *intr = dynamic_cast<Interpolator*>(subTrack.getInterpolator().getPointer());
+  if (!intr)
     return;
-
-  outTracks.push_back(track.handler->tracks.front());
+  
   if (!track.changed())
     return;
   
-  // update subTrack
   int start = track.size() - track.pointsAdded;
-  if (start > 1) --start;
   if (start < 0) start = 0;
-  subTrack.truncate(start);
-  for(int i = start; i < track.size(); ++i)
-    subTrack.push_back(track[i], false);
   
   // update tangents
-  modifier->tangents.resize(start);
+  int tangentStart = start - 1;
+  if (tangentStart < 0) tangentStart = 0;
+  intr->tangents.resize(tangentStart);
+  for(int i = tangentStart; i < track.size(); ++i)
+    intr->tangents.push_back(calcTangent(track, i));
+
+  // update subTrack
+  subTrack.truncate(start);
   for(int i = start; i < track.size(); ++i)
-    modifier->tangents.push_back(calcTangent(track, i));
+    subTrack.push_back(subTrack.pointFromOriginal(i), false);
   
   // fix points
   if (track.fixedFinished()) {
diff --git a/toonz/sources/tnztools/modifiers/modifiertest.cpp b/toonz/sources/tnztools/modifiers/modifiertest.cpp
index 7801f3b..ba33792 100644
--- a/toonz/sources/tnztools/modifiers/modifiertest.cpp
+++ b/toonz/sources/tnztools/modifiers/modifiertest.cpp
@@ -6,111 +6,118 @@
 // std includes
 #include <cmath>
 
+
 //*****************************************************************************************
-//    TModifierTest::Modifier implementation
+//    TModifierTest::Interpolator implementation
 //*****************************************************************************************
 
-TModifierTest::Modifier::Modifier(TTrackHandler &handler, double angle,
-                                  double radius, double speed)
-    : TTrackModifier(handler), angle(angle), radius(radius), speed(speed) {}
-
-TTrackPoint TModifierTest::Modifier::calcPoint(double originalIndex) {
-  TTrackPoint p = TTrackModifier::calcPoint(originalIndex);
-
-  if (p.length > TConsts::epsilon) {
-    double frac;
-    int i0 = original.floorIndex(originalIndex, &frac);
-    int i1 = original.ceilIndex(originalIndex);
-    if (i0 < 0) return p;
-
-    if (Handler *handler = dynamic_cast<Handler *>(&this->handler)) {
-      double angle = this->angle + speed * TTrack::interpolationLinear(
-                                               handler->angles[i0],
-                                               handler->angles[i1], frac);
-      double radius = 2.0 * this->radius * p.pressure;
-      double s      = sin(angle);
-      double c      = cos(angle);
-
-      TPointD tangent =
-          original.calcTangent(originalIndex, fabs(2.0 * this->radius / speed));
-      p.position.x -= tangent.y * s * radius;
-      p.position.y += tangent.x * s * radius;
-      p.pressure *= 1.0 - 0.5 * c;
-    }
-  } else {
-    p.pressure = 0.0;
-  }
+TTrackPoint TModifierTest::Interpolator::interpolateFromOriginal(double originalIndex) {
+  Handler *handler = track.original
+                   ? dynamic_cast<Handler*>(track.original->handler.getPointer())
+                   : nullptr;
+  if (!handler)
+    return track.interpolateLinear(track.indexByOriginalIndex(originalIndex));
+  
+  TTrackPoint p = track.calcPointFromOriginal(originalIndex);
+
+  double frac;
+  int i0 = track.original->floorIndex(originalIndex, &frac);
+  int i1 = track.original->ceilIndex(originalIndex);
+
+  double angle = TTrack::interpolationLinear(
+    handler->angles[i0], handler->angles[i1], frac );
+  double a = angle*speed + this->angle;
+
+  double kr = 1 - 1/(angle + 1);
+  double s = sin(a);
+  double r = radius*p.pressure*kr*s;
+  
+  double d = fabs(2.0*radius);
+  if (fabs(speed) > TConsts::epsilon)
+    d /= fabs(speed);
+
+  TPointD tangent = track.original->calcTangent(originalIndex, d);
+  p.position.x -= tangent.y * r;
+  p.position.y += tangent.x * r;
+  p.pressure *= fabs(s);
 
   return p;
 }
 
+
+TTrackPoint TModifierTest::Interpolator::interpolate(double index) {
+  return interpolateFromOriginal(track.originalIndexByIndex(index));
+}
+
+
 //*****************************************************************************************
 //    TModifierTest implementation
 //*****************************************************************************************
 
-TModifierTest::TModifierTest(int count, double radius)
-    : count(count), radius(radius) {}
+TModifierTest::TModifierTest(int count, double radius, double speed)
+    : count(count), radius(radius), speed(speed) {}
 
 void TModifierTest::modifyTrack(const TTrack &track,
                                 TTrackList &outTracks) {
   const double segmentSize = 2.0 * M_PI / 10.0;
 
-  if (!track.handler) {
-    if (track.getKeyState(track.front().time).isPressed(TKey(Qt::Key_Alt))) {
-      // TModifierTest::Handler for spiro
-      track.handler = new Handler(track);
-      for (int i = 0; i < count; ++i)
-        track.handler->tracks.push_back(new TTrack(new Modifier(
-            *track.handler, i * 2.0 * M_PI / (double)count, radius, 0.25)));
+  if ( !track.handler
+    && track.getKeyState(track.front().time).isPressed(TKey::alt) )
+  {
+    Handler *handler = new Handler(this->radius);
+    track.handler = handler;
+    for (int i = 0; i < count; ++i) {
+      handler->tracks.push_back(new TTrack(track));
+      TTrack &subTrack = *handler->tracks.back();
+      new Interpolator(subTrack, i*2*M_PI/(double)count, radius, 0.25);
     }
   }
-
-  Handler *handler = dynamic_cast<Handler *>(track.handler.getPointer());
-  if (!handler) {
-    TInputModifier::modifyTrack(track, outTracks);
+  
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler)
+    return TInputModifier::modifyTrack(track, outTracks);
+  
+  outTracks.insert(outTracks.end(), handler->tracks.begin(), handler->tracks.end());
+  if (!track.changed())
     return;
-  }
-
-  outTracks.insert(outTracks.end(), track.handler->tracks.begin(),
-                   track.handler->tracks.end());
-  if (!track.changed()) return;
 
+  double radius = handler->radius;
   int start = track.size() - track.pointsAdded;
   if (start < 0) start = 0;
 
   // remove angles
-  double lastAngle = start < (int)handler->angles.size()
-                         ? handler->angles[start]
-                     : handler->angles.empty() ? 0.0
-                                               : handler->angles.back();
-  handler->angles.resize(start, lastAngle);
-
+  handler->angles.resize(start);
+  
   // add angles
-  for (int i = start; i < track.size(); ++i) {
-    if (i > 0) {
-      double dl = track[i].length - track[i - 1].length;
-      double da = track[i].pressure > TConsts::epsilon
-                      ? dl / (radius * track[i].pressure)
-                      : 0.0;
-      handler->angles.push_back(handler->angles[i - 1] + da);
+  for(int i = start; i < track.size(); ++i) {
+    if (i) {
+      const TTrackPoint &p0 = track[i - 1];
+      const TTrackPoint &p1 = track[i];
+      double dl = p1.length - p0.length;
+      double da = p1.pressure > TConsts::epsilon
+                ? dl / (radius * p1.pressure)
+                : 0.0;
+      handler->angles.push_back(handler->angles.back() + da);
     } else {
       handler->angles.push_back(0.0);
     }
   }
-
+  
   // process sub-tracks
-  for (TTrackList::const_iterator ti = handler->tracks.begin();
-       ti != handler->tracks.end(); ++ti) {
-    TTrack &subTrack          = **ti;
+  for(TTrackList::const_iterator ti = handler->tracks.begin(); ti != handler->tracks.end(); ++ti) {
+    TTrack &subTrack = **ti;
+    Interpolator *intr = dynamic_cast<Interpolator*>(subTrack.getInterpolator().getPointer());
+    if (!intr)
+      continue;
+    
     double currentSegmentSize = segmentSize;
-    if (const Modifier *modifier =
-            dynamic_cast<const Modifier *>(subTrack.modifier.getPointer()))
-      if (fabs(modifier->speed) > TConsts::epsilon)
-        currentSegmentSize = segmentSize / fabs(modifier->speed);
+    if (fabs(intr->speed) > TConsts::epsilon)
+      currentSegmentSize /= fabs(intr->speed);
 
     // remove points
-    int subStart =
-        subTrack.floorIndex(subTrack.indexByOriginalIndex(start - 1)) + 1;
+    int subStart = start > 0
+                 ? subTrack.floorIndex(subTrack.indexByOriginalIndex(start - 1)) + 1
+                 : 0;
     subTrack.truncate(subStart);
 
     // add points
@@ -123,18 +130,20 @@ void TModifierTest::modifyTrack(const TTrack &track,
           double end  = 1.0 - 0.5 * step;
           for (double frac = step; frac < end; frac += step)
             subTrack.push_back(
-                subTrack.modifier->calcPoint((double)i - 1.0 + frac), false);
+                intr->interpolateFromOriginal(i - 1 + frac), false );
         }
       }
-      subTrack.push_back(subTrack.modifier->calcPoint(i), false);
+      subTrack.push_back(intr->interpolateFromOriginal(i), false);
     }
     
     // fix points
+    if (track.fixedFinished())
+      subTrack.fix_all();
     if (track.fixedSize())
       subTrack.fix_to(
         subTrack.floorIndex(subTrack.indexByOriginalIndex(track.fixedSize() - 1)) + 1 );
   }
-
+  
   track.resetChanges();
 }
 
diff --git a/toonz/sources/tnztools/tool.cpp b/toonz/sources/tnztools/tool.cpp
index 38434c5..0f730fe 100644
--- a/toonz/sources/tnztools/tool.cpp
+++ b/toonz/sources/tnztools/tool.cpp
@@ -179,6 +179,11 @@ TTool::TTool(std::string name)
 
 //-------------------------------------------------------------------
 
+unsigned int TTool::getToolHints() const
+  { return HintAssistants | HintGuidelines; }
+
+//-------------------------------------------------------------------
+
 TTool *TTool::getTool(std::string toolName, ToolTargetType targetType) {
   if (!toolTable) return 0;
 
diff --git a/toonz/sources/tnztools/toonzrasterbrushtool.cpp b/toonz/sources/tnztools/toonzrasterbrushtool.cpp
index 539368a..b4ba5d5 100644
--- a/toonz/sources/tnztools/toonzrasterbrushtool.cpp
+++ b/toonz/sources/tnztools/toonzrasterbrushtool.cpp
@@ -55,7 +55,7 @@ TEnv::DoubleVar RasterBrushHardness("RasterBrushHardness", 100);
 TEnv::DoubleVar RasterBrushModifierSize("RasterBrushModifierSize", 0);
 TEnv::StringVar RasterBrushPreset("RasterBrushPreset", "<custom>");
 TEnv::IntVar BrushLockAlpha("InknpaintBrushLockAlpha", 0);
-TEnv::IntVar RasterBrushAssistants("RasterBrushAssistants", 0);
+TEnv::IntVar RasterBrushAssistants("RasterBrushAssistants", 1);
 
 //-------------------------------------------------------------------
 #define CUSTOM_WSTR L"<custom>"
@@ -720,124 +720,6 @@ static void Smooth(std::vector<TThickPoint> &points, const int radius,
   }
 }
 
-//--------------------------------------------------------------------------------------------------
-
-void SmoothStroke::beginStroke(int smooth) {
-  m_smooth      = smooth;
-  m_outputIndex = 0;
-  m_readIndex   = -1;
-  m_rawPoints.clear();
-  m_outputPoints.clear();
-  m_resampledIndex = 0;
-  m_resampledPoints.clear();
-}
-
-//--------------------------------------------------------------------------------------------------
-
-void SmoothStroke::addPoint(const TThickPoint &point) {
-  if (m_rawPoints.size() > 0 && m_rawPoints.back().x == point.x &&
-      m_rawPoints.back().y == point.y) {
-    return;
-  }
-  m_rawPoints.push_back(point);
-  generatePoints();
-}
-
-//--------------------------------------------------------------------------------------------------
-
-void SmoothStroke::endStroke() {
-  generatePoints();
-  // force enable the output all segments
-  m_outputIndex = m_outputPoints.size() - 1;
-}
-
-//--------------------------------------------------------------------------------------------------
-
-void SmoothStroke::clearPoints() {
-  m_outputIndex = 0;
-  m_readIndex   = -1;
-  m_outputPoints.clear();
-  m_rawPoints.clear();
-  m_resampledIndex = 0;
-  m_resampledPoints.clear();
-}
-
-//--------------------------------------------------------------------------------------------------
-
-void SmoothStroke::getSmoothPoints(std::vector<TThickPoint> &smoothPoints) {
-  int n = m_outputPoints.size();
-  for (int i = m_readIndex + 1; i <= m_outputIndex && i < n; ++i) {
-    smoothPoints.push_back(m_outputPoints[i]);
-  }
-  m_readIndex = m_outputIndex;
-}
-
-//--------------------------------------------------------------------------------------------------
-
-void SmoothStroke::generatePoints() {
-  int n = (int)m_rawPoints.size();
-  if (n == 0) {
-    return;
-  }
-
-  // if m_smooth = 0, then skip whole smoothing process
-  if (m_smooth == 0) {
-    for (int i = m_outputIndex; i < (int)m_outputPoints.size(); ++i) {
-      if (m_outputPoints[i] != m_rawPoints[i]) {
-        break;
-      }
-      ++m_outputIndex;
-    }
-    m_outputPoints = m_rawPoints;
-    return;
-  }
-
-  std::vector<TThickPoint> smoothedPoints = m_resampledPoints;
-  // Add more stroke samples before applying the smoothing
-  // This is because the raw inputs points are too few to support smooth result,
-  // especially on stroke ends
-
-  int resampleStartId = m_resampledIndex;
-  for (int i = resampleStartId; i < n - 1; ++i) {
-    const TThickPoint &p1 = m_rawPoints[i];
-    const TThickPoint &p2 = m_rawPoints[i + 1];
-    const TThickPoint &p0 = i - 1 >= 0 ? m_rawPoints[i - 1] : p1;
-    const TThickPoint &p3 = i + 2 < n ? m_rawPoints[i + 2] : p2;
-
-    std::vector<TThickPoint> tmpResampled;
-    tmpResampled.push_back(p1);
-    // define subsample amount according to distance between points
-    int samples = std::min((int)tdistance(p1, p2), 8);
-    if (samples >= 1)
-      CatmullRomInterpolate(p0, p1, p2, p3, samples, tmpResampled);
-
-    if (i + 2 < n) {
-      m_resampledIndex = i + 1;
-      std::copy(tmpResampled.begin(), tmpResampled.end(),
-                std::back_inserter(m_resampledPoints));
-    }
-    std::copy(tmpResampled.begin(), tmpResampled.end(),
-              std::back_inserter(smoothedPoints));
-  }
-  smoothedPoints.push_back(m_rawPoints.back());
-  // Apply the 1D box filter
-  // Multiple passes result in better quality and fix the stroke ends break
-  // issue
-  // level is passed to define range where the points are smoothed
-  for (int level = 2; level >= 0; --level) {
-    Smooth(smoothedPoints, m_smooth, m_readIndex, level);
-  }
-  // Compare the new smoothed stroke with old one
-  // Enable the output for unchanged parts
-  int outputNum = (int)m_outputPoints.size();
-  for (int i = m_outputIndex; i < outputNum; ++i) {
-    if (m_outputPoints[i] != smoothedPoints[i]) {
-      break;
-    }
-    ++m_outputIndex;
-  }
-  m_outputPoints = smoothedPoints;
-}
 
 //===================================================================
 //
@@ -900,7 +782,7 @@ ToonzRasterBrushTool::ToonzRasterBrushTool(std::string name, int targetType)
   for(int i = 0; i < 3; ++i)
     m_modifierSmooth[i]        = new TModifierSmooth();
 #ifndef NDEBUG
-  m_modifierTest = new TModifierTest(5, 40);
+  m_modifierTest = new TModifierTest();
 #endif
 
   m_inputmanager.addModifier(
@@ -1158,15 +1040,15 @@ bool ToonzRasterBrushTool::askWrite(const TRect &rect) {
 
 bool ToonzRasterBrushTool::preLeftButtonDown() {
   int smoothRadius = (int)round(m_smooth.getValue());
-  m_modifierAssistants->drawOnly = !RasterBrushAssistants;
-  m_inputmanager.drawPreview     = false; //!m_modifierAssistants->drawOnly;
+  m_modifierAssistants->magnetism = m_assistants.getValue() ? 1 : 0;
+  m_inputmanager.drawPreview      = false; //!m_modifierAssistants->drawOnly;
 
   m_inputmanager.clearModifiers();
   m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
   if (smoothRadius > 0) {
     m_inputmanager.addModifier(TInputModifierP(m_modifierSmoothSegmentation.getPointer()));
     for(int i = 0; i < 3; ++i) {
-      m_modifierSmooth[i]->setRadius(smoothRadius);
+      m_modifierSmooth[i]->radius = smoothRadius;
       m_inputmanager.addModifier(TInputModifierP(m_modifierSmooth[i].getPointer()));
     }
   }
@@ -1198,7 +1080,7 @@ void ToonzRasterBrushTool::handleMouseEvent(MouseEventType type,
   bool control  = e.getModifiersMask() & TMouseEvent::CTRL_KEY;
 
   if (shift && type == ME_DOWN && e.button() == Qt::LeftButton && !m_painting.active) {
-    m_modifierAssistants->drawOnly = true;
+    m_modifierAssistants->magnetism = 0;
     m_inputmanager.clearModifiers();
     m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
     m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
@@ -1217,9 +1099,13 @@ void ToonzRasterBrushTool::handleMouseEvent(MouseEventType type,
     THoverList hovers(1, pos);
     m_inputmanager.hoverEvent(hovers);
   } else {
-    m_inputmanager.trackEvent(e.isTablet(), 0, pos,
-                              e.isTablet() ? &e.m_pressure : nullptr, nullptr,
-                              type == ME_UP, t);
+    int    deviceId    = e.isTablet() ? 1 : 0;
+    double defPressure = m_isMyPaintStyleSelected ? 0.5 : 1.0;
+    bool   hasPressure = e.isTablet();
+    double pressure    = hasPressure ? e.m_pressure : defPressure;
+    bool   final       = type == ME_UP;
+    m_inputmanager.trackEvent(
+      deviceId, 0, pos, pressure, TPointD(), hasPressure, false, final, t);
     m_inputmanager.processTracks();
   }
 }
@@ -1383,10 +1269,13 @@ void ToonzRasterBrushTool::inputPaintTrackPoint(const TTrackPoint &point, const 
   
   // first point must be without handler, following points must be with handler
   // other behaviour is possible bug and must be ignored
-  assert(firstPoint == !track.toolHandler);
-  if (firstPoint != !track.toolHandler)
+  assert(firstPoint == !track.handler);
+  if (firstPoint != !track.handler)
     return;
   
+  double defPressure = m_painting.myPaint.isActive ? 0.5 : 1.0;
+  double pressure    = m_pressure.getValue() ? point.pressure : defPressure;
+  
   if (m_painting.myPaint.isActive) {
     // mypaint case
     
@@ -1395,14 +1284,14 @@ void ToonzRasterBrushTool::inputPaintTrackPoint(const TTrackPoint &point, const 
     if (firstPoint) {
       handler = new MyPaintStroke(m_workRas, *this, m_painting.myPaint.baseBrush, false);
       handler->brush.beginStroke();
-      track.toolHandler = handler;
+      track.handler = handler;
     }
-    handler = dynamic_cast<MyPaintStroke*>(track.toolHandler.getPointer());
+    handler = dynamic_cast<MyPaintStroke*>(track.handler.getPointer());
     if (!handler) return;
     
     // paint stroke
     m_painting.myPaint.strokeSegmentRect.empty();
-    handler->brush.strokeTo( fixedPosition + rasCenter, point.pressure,
+    handler->brush.strokeTo( fixedPosition + rasCenter, pressure,
                              point.tilt, point.time - track.previous().time );
     if (lastPoint)
       handler->brush.endStroke();
@@ -1421,7 +1310,7 @@ void ToonzRasterBrushTool::inputPaintTrackPoint(const TTrackPoint &point, const 
     // pencil case
     
     // Pencilモードでなく、Hardness=100 の場合のブラシサイズを1段階下げる
-    double thickness = computeThickness(point.pressure, m_rasThickness)*2;
+    double thickness = computeThickness(pressure, m_rasThickness)*2;
     //if (!m_painting.pencil.realPencil && !m_modifierLine->getManager())
     //  thickness -= 1.0;
     TThickPoint thickPoint(fixedPosition + rasCenter, thickness);
@@ -1441,9 +1330,9 @@ void ToonzRasterBrushTool::inputPaintTrackPoint(const TTrackPoint &point, const 
         getAboveStyleIdSet(m_painting.styleId, ri->getPalette(), aboveStyleIds);
         handler->brush.setAboveStyleIds(aboveStyleIds);
       }
-      track.toolHandler = handler;
+      track.handler = handler;
     }
-    handler = dynamic_cast<PencilStroke*>(track.toolHandler.getPointer());
+    handler = dynamic_cast<PencilStroke*>(track.handler.getPointer());
     if (!handler) return;
 
     // paint stroke
@@ -1476,13 +1365,13 @@ void ToonzRasterBrushTool::inputPaintTrackPoint(const TTrackPoint &point, const 
         getAboveStyleIdSet(m_painting.styleId, ri->getPalette(), aboveStyleIds);
         handler->brush.setAboveStyleIds(aboveStyleIds);
       }
-      track.toolHandler = handler;
+      track.handler = handler;
     }
-    handler = dynamic_cast<BluredStroke*>(track.toolHandler.getPointer());
+    handler = dynamic_cast<BluredStroke*>(track.handler.getPointer());
     if (!handler) return;
 
     // paint stroke
-    double radius = computeThickness(point.pressure, m_rasThickness);
+    double radius = computeThickness(pressure, m_rasThickness);
     TThickPoint thickPoint(fixedPosition + rasCenter, radius*2);
     TRect strokeRect( tfloor(thickPoint.x - maxThick - 0.999),
                       tfloor(thickPoint.y - maxThick - 0.999),
diff --git a/toonz/sources/tnztools/toonzrasterbrushtool.h b/toonz/sources/tnztools/toonzrasterbrushtool.h
index 681230c..65bc306 100644
--- a/toonz/sources/tnztools/toonzrasterbrushtool.h
+++ b/toonz/sources/tnztools/toonzrasterbrushtool.h
@@ -92,42 +92,6 @@ public:
 };
 
 //************************************************************************
-//    Smooth Stroke declaration
-//    Brush stroke smoothing buffer.
-//************************************************************************
-class SmoothStroke {
-public:
-  SmoothStroke() {}
-  ~SmoothStroke() {}
-
-  // begin stroke
-  // smooth is smooth strength, from 0 to 100
-  void beginStroke(int smooth);
-  // add stroke point
-  void addPoint(const TThickPoint &point);
-  // end stroke
-  void endStroke();
-  // Get generated stroke points which has been smoothed.
-  // Both addPoint() and endStroke() generate new smoothed points.
-  // This method will removed generated points
-  void getSmoothPoints(std::vector<TThickPoint> &smoothPoints);
-  // Remove all points - used for straight lines
-  void clearPoints();
-
-private:
-  void generatePoints();
-
-private:
-  int m_smooth;
-  int m_outputIndex;
-  int m_readIndex;
-  std::vector<TThickPoint> m_rawPoints;
-  std::vector<TThickPoint> m_outputPoints;
-
-  int m_resampledIndex;
-  std::vector<TThickPoint> m_resampledPoints;
-};
-//************************************************************************
 //   Toonz Raster Brush Tool declaration
 //************************************************************************
 
@@ -142,8 +106,11 @@ class ToonzRasterBrushTool final : public TTool,
 public:
   ToonzRasterBrushTool(std::string name, int targetType);
 
-  ToolType getToolType() const override { return TTool::LevelWriteTool; }
-
+  ToolType getToolType() const override
+    { return TTool::LevelWriteTool; }
+  unsigned int getToolHints() const override
+    { return TTool::getToolHints() & ~HintAssistantsAll; }
+  
   ToolOptionsBox *createOptionsBox() override;
 
   void updateTranslation() override;
@@ -211,7 +178,7 @@ protected:
   TSmartPointerT<TModifierTest> m_modifierTest;
 #endif
 
-  class MyPaintStroke: public TTrackToolHandler {
+  class MyPaintStroke: public TTrackHandler {
   public:
     MyPaintToonzBrush brush;
     
@@ -225,7 +192,7 @@ protected:
     { }
   };
   
-  class PencilStroke: public TTrackToolHandler {
+  class PencilStroke: public TTrackHandler {
   public:
     RasterStrokeGenerator brush;
     
@@ -238,7 +205,7 @@ protected:
     { }
   };
 
-  class BluredStroke: public TTrackToolHandler {
+  class BluredStroke: public TTrackHandler {
   public:
     BluredBrush brush;
     
@@ -262,8 +229,6 @@ protected:
     TTileSaverCM32 *tileSaver = nullptr;
     TRect affectedRect;
     
-    SmoothStroke smoothStroke;
-    
     struct Pencil {
       bool isActive = false;
       bool realPencil = false;
diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.cpp b/toonz/sources/tnztools/toonzvectorbrushtool.cpp
index 2f46d02..1e01069 100644
--- a/toonz/sources/tnztools/toonzvectorbrushtool.cpp
+++ b/toonz/sources/tnztools/toonzvectorbrushtool.cpp
@@ -58,6 +58,7 @@ TEnv::IntVar V_BrushPressureSensitivity("InknpaintBrushPressureSensitivity", 1);
 TEnv::IntVar V_VectorBrushFrameRange("VectorBrushFrameRange", 0);
 TEnv::IntVar V_VectorBrushSnap("VectorBrushSnap", 0);
 TEnv::IntVar V_VectorBrushSnapSensitivity("VectorBrushSnapSensitivity", 0);
+TEnv::IntVar V_VectorBrushAssistants("VectorBrushAssistants", 1);
 TEnv::StringVar V_VectorBrushPreset("VectorBrushPreset", "<custom>");
 
 //-------------------------------------------------------------------
@@ -474,8 +475,9 @@ void getAboveStyleIdSet(int styleId, TPaletteP palette,
 //=========================================================================================================
 
 double computeThickness(double pressure, const TDoublePairProperty &property,
-                        bool isPath) {
+                        bool enablePressure, bool isPath ) {
   if (isPath) return 0.0;
+  if (!enablePressure) return property.getValue().second*0.5;
   double t      = pressure * pressure * pressure;
   double thick0 = property.getValue().first;
   double thick1 = property.getValue().second;
@@ -499,24 +501,32 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType)
     , m_preset("Preset:")
     , m_breakAngles("Break", true)
     , m_pressure("Pressure", true)
+    , m_snap("Snap", false)
+    , m_frameRange("Range:")
+    , m_snapSensitivity("Sensitivity:")
     , m_capStyle("Cap")
     , m_joinStyle("Join")
     , m_miterJoinLimit("Miter:", 0, 100, 4)
-    , m_rasterTrack(0)
-    , m_styleId(0)
-    , m_modifiedRegion()
-    , m_bluredBrush(0)
-    , m_active(false)
-    , m_enabled(false)
-    , m_isPrompting(false)
+    , m_assistants("Assistants", true)
+    , m_styleId()
+    , m_minThick()
+    , m_maxThick()
+    , m_col()
+    , m_firstFrame()
+    , m_veryFirstFrame()
+    , m_veryFirstCol()
+    , m_targetType(targetType)
+    , m_pixelSize()
+    , m_minDistance2()
+    , m_snapped()
+    , m_snappedSelf()
+    , m_active()
     , m_firstTime(true)
+    , m_isPath()
+    , m_presetsLoaded()
     , m_firstFrameRange(true)
-    , m_presetsLoaded(false)
-    , m_frameRange("Range:")
-    , m_snap("Snap", false)
-    , m_snapSensitivity("Sensitivity:")
-    , m_targetType(targetType)
-    , m_workingFrameId(TFrameId()) {
+    , m_propertyUpdating()
+{
   bind(targetType);
 
   m_thickness.setNonLinearSlider();
@@ -541,6 +551,8 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType)
   m_snapSensitivity.addValue(MEDIUM_WSTR);
   m_snapSensitivity.addValue(HIGH_WSTR);
 
+  m_prop[0].bind(m_assistants);
+
   m_prop[0].bind(m_preset);
   m_preset.addValue(CUSTOM_WSTR);
 
@@ -566,6 +578,23 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType)
   m_capStyle.setId("Cap");
   m_joinStyle.setId("Join");
   m_miterJoinLimit.setId("Miter");
+  m_assistants.setId("Assistants");
+
+  m_inputmanager.setHandler(this);
+  m_modifierLine               = new TModifierLine();
+  m_modifierTangents           = new TModifierTangents();
+  m_modifierAssistants         = new TModifierAssistants();
+  m_modifierSegmentation       = new TModifierSegmentation();
+  m_modifierSmoothSegmentation = new TModifierSegmentation(TPointD(1, 1), 3);
+  for(int i = 0; i < 3; ++i)
+    m_modifierSmooth[i]        = new TModifierSmooth();
+  m_modifierSimplify           = new TModifierSimplify();
+#ifndef NDEBUG
+  m_modifierTest = new TModifierTest();
+#endif
+
+  m_inputmanager.addModifier(
+      TInputModifierP(m_modifierAssistants.getPointer()));
 }
 
 //-------------------------------------------------------------------------------------------------------
@@ -593,6 +622,7 @@ void ToonzVectorBrushTool::updateTranslation() {
   m_frameRange.setQStringName(tr("Range:"));
   m_snap.setQStringName(tr("Snap"));
   m_snapSensitivity.setQStringName("");
+  m_assistants.setQStringName(tr("Assistants"));
   m_frameRange.setItemUIName(L"Off", tr("Off"));
   m_frameRange.setItemUIName(LINEAR_WSTR, tr("Linear"));
   m_frameRange.setItemUIName(EASEIN_WSTR, tr("In"));
@@ -638,421 +668,497 @@ void ToonzVectorBrushTool::onDeactivate() {
    * ---*/
 
   // End current stroke.
-  if (m_active && m_enabled) {
-    leftButtonUp(m_lastDragPos, m_lastDragEvent);
-  }
-
-  if (m_tileSaver && !m_isPath) {
-    m_enabled = false;
-  }
-  m_workRas   = TRaster32P();
-  m_backupRas = TRasterCM32P();
+  m_inputmanager.finishTracks();
   resetFrameRange();
 }
 
 //--------------------------------------------------------------------------------------------------
 
-bool ToonzVectorBrushTool::preLeftButtonDown() {
-  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
+void ToonzVectorBrushTool::inputMouseMove(
+  const TPointD &position, const TInputState &state )
+{
+  struct Locals {
+    ToonzVectorBrushTool *m_this;
 
-  touchImage();
-  if (m_isFrameCreated) {
-    // When the xsheet frame is selected, whole viewer will be updated from
-    // SceneViewer::onXsheetChanged() on adding a new frame.
-    // We need to take care of a case when the level frame is selected.
-    if (m_application->getCurrentFrame()->isEditingLevel()) invalidate();
-  }
-  return true;
-}
+    void setValue(TDoublePairProperty &prop,
+                  const TDoublePairProperty::Value &value) {
+      prop.setValue(value);
 
-//--------------------------------------------------------------------------------------------------
+      m_this->onPropertyChanged(prop.getName());
+      TTool::getApplication()->getCurrentTool()->notifyToolChanged();
+    }
 
-void ToonzVectorBrushTool::leftButtonDown(const TPointD &pos,
-                                          const TMouseEvent &e) {
-  TTool::Application *app = TTool::getApplication();
-  if (!app) return;
+    void addMinMax(TDoublePairProperty &prop, double min, double max) {
+      if (min == 0.0 && max == 0.0) return;
+      const TDoublePairProperty::Range &range = prop.getRange();
 
-  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) {
-    getViewer()->doPickGuideStroke(pos);
-    return;
-  }
+      TDoublePairProperty::Value value = prop.getValue();
+      value.first += min;
+      value.second += max;
+      if (value.first > value.second) value.first = value.second;
+      value.first  = tcrop(value.first, range.first, range.second);
+      value.second = tcrop(value.second, range.first, range.second);
 
-  int col   = app->getCurrentColumn()->getColumnIndex();
-  m_isPath  = app->getCurrentObject()->isSpline();
-  m_enabled = col >= 0 || m_isPath || app->getCurrentFrame()->isEditingLevel();
-  // todo: gestire autoenable
-  if (!m_enabled) return;
-  if (!m_isPath) {
-    m_currentColor = TPixel32::Black;
-    m_active       = !!getImage(true);
-    if (!m_active) {
-      m_active = !!touchImage();
-    }
-    if (!m_active) return;
-
-    if (m_active) {
-      // nel caso che il colore corrente sia un cleanup/studiopalette color
-      // oppure il colore di un colorfield
-      m_styleId       = app->getCurrentLevelStyleIndex();
-      TColorStyle *cs = app->getCurrentLevelStyle();
-      if (cs) {
-        TRasterStyleFx *rfx = cs ? cs->getRasterStyleFx() : 0;
-        m_active =
-            cs != 0 && (cs->isStrokeStyle() || (rfx && rfx->isInkStyle()));
-        m_currentColor   = cs->getAverageColor();
-        m_currentColor.m = 255;
-      } else {
-        m_styleId      = 1;
-        m_currentColor = TPixel32::Black;
-      }
+      setValue(prop, value);
     }
-  } else {
-    m_currentColor = TPixel32::Red;
-    m_active       = true;
-  }
 
-  TXshLevel *level = app->getCurrentLevel()->getLevel();
-  if (level == NULL && !m_isPath) {
-    m_active = false;
-    return;
-  }
+  } locals = {this};
 
-  // assert(0<=m_styleId && m_styleId<2);
-  m_track.clear();
-  double thickness = (m_pressure.getValue() || m_isPath)
-                         ? computeThickness(e.m_pressure, m_thickness, m_isPath)
-                         : m_thickness.getValue().second * 0.5;
-
-  /*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/
-  if (m_pressure.getValue() && e.m_pressure == 1.0)
-    thickness = m_thickness.getValue().first * 0.5;
-  m_currThickness = thickness;
-  m_smoothStroke.beginStroke(m_smooth.getValue());
-
-  if (m_foundFirstSnap) {
-    addTrackPoint(TThickPoint(m_firstSnapPoint, thickness),
-                  getPixelSize() * getPixelSize());
-  } else
-    addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
-  TRectD invalidateRect = m_track.getLastModifiedRegion();
-  invalidate(invalidateRect.enlarge(2));
+  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
+  TRectD invalidateRect(m_brushPos - halfThick, m_brushPos + halfThick);
 
-  // updating m_brushPos is needed to refresh viewer properly
-  m_brushPos = m_mousePos = pos;
-}
+  bool alt     = state.isKeyPressed(TInputState::Key::alt);
+  bool shift   = state.isKeyPressed(TInputState::Key::shift);
+  bool control = state.isKeyPressed(TInputState::Key::control);
+  
+  if ( alt && control && !shift
+    && Preferences::instance()->useCtrlAltToResizeBrushEnabled() )
+  {
+    // Resize the brush if CTRL+ALT is pressed and the preference is enabled.
+    const TPointD &diff = position - m_mousePos;
+    double max          = diff.x / 2;
+    double min          = diff.y / 2;
 
-//-------------------------------------------------------------------------------------------------------------
+    locals.addMinMax(m_thickness, min, max);
 
-void ToonzVectorBrushTool::leftButtonDrag(const TPointD &pos,
-                                          const TMouseEvent &e) {
-  if (!m_enabled || !m_active) {
-    m_brushPos = m_mousePos = pos;
-    return;
+    double radius = m_thickness.getValue().second * 0.5;
+    halfThick = TPointD(radius, radius);
+  } else {
+    m_brushPos = m_mousePos = position;
   }
-
-  m_lastDragPos   = pos;
-  m_lastDragEvent = e;
-
-  double thickness = (m_pressure.getValue() || m_isPath)
-                         ? computeThickness(e.m_pressure, m_thickness, m_isPath)
-                         : m_thickness.getValue().second * 0.5;
-
-  TRectD invalidateRect;
-  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
-  TPointD snapThick(6.0 * m_pixelSize, 6.0 * m_pixelSize);
-
-  // In order to clear the previous brush tip
+  
   invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
 
-  // In order to clear the previous snap indicator
-  if (m_foundLastSnap)
-    invalidateRect +=
-        TRectD(m_lastSnapPoint - snapThick, m_lastSnapPoint + snapThick);
-
-  m_currThickness = thickness;
-
-  m_mousePos       = pos;
-  m_lastSnapPoint  = pos;
-  m_foundLastSnap  = false;
-  m_foundFirstSnap = false;
-  m_snapSelf       = false;
-  m_altPressed     = e.isAltPressed() && !e.isCtrlPressed();
-
-  checkStrokeSnapping(false, m_altPressed);
-  checkGuideSnapping(false, m_altPressed);
-  m_brushPos = m_lastSnapPoint;
-
-  if (m_foundLastSnap)
-    invalidateRect +=
-        TRectD(m_lastSnapPoint - snapThick, m_lastSnapPoint + snapThick);
-
-  if (e.isShiftPressed() && e.isCtrlPressed()) {
-    TPointD m_firstPoint = m_track.getFirstPoint();
-
-    double denominator = m_lastSnapPoint.x - m_firstPoint.x;
-    if (denominator == 0) denominator = 0.001;
-    double slope = ((m_brushPos.y - m_firstPoint.y) / denominator);
-    double angle = std::atan(slope) * (180 / 3.14159);
-    if (abs(angle) > 67.5)
-      m_lastSnapPoint.x = m_firstPoint.x;
-    else if (abs(angle) < 22.5)
-      m_lastSnapPoint.y = m_firstPoint.y;
-    else {
-      double xDistance = m_lastSnapPoint.x - m_firstPoint.x;
-      double yDistance = m_lastSnapPoint.y - m_firstPoint.y;
-      if (abs(xDistance) > abs(yDistance)) {
-        if (abs(yDistance) == yDistance)
-          m_lastSnapPoint.y = m_firstPoint.y + abs(xDistance);
-        else
-          m_lastSnapPoint.y = m_firstPoint.y - abs(xDistance);
-      } else {
-        if (abs(xDistance) == xDistance)
-          m_lastSnapPoint.x = m_firstPoint.x + abs(yDistance);
-        else
-          m_lastSnapPoint.x = m_firstPoint.x - abs(yDistance);
-      }
-    }
-
-    m_smoothStroke.clearPoints();
-    m_track.add(TThickPoint(m_lastSnapPoint, thickness),
-                getPixelSize() * getPixelSize());
-    m_track.removeMiddlePoints();
-    invalidateRect += m_track.getModifiedRegion();
-  } else if (e.isShiftPressed()) {
-    m_smoothStroke.clearPoints();
-    m_track.add(TThickPoint(m_brushPos, thickness),
-                getPixelSize() * getPixelSize());
-    m_track.removeMiddlePoints();
-    invalidateRect += m_track.getModifiedRegion();
-  } else if (m_dragDraw) {
-    addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize());
-    invalidateRect += m_track.getLastModifiedRegion();
+  if (m_minThick == 0 && m_maxThick == 0) {
+    m_minThick = m_thickness.getValue().first;
+    m_maxThick = m_thickness.getValue().second;
   }
 
-  // In order to draw the current brush tip
-  invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
+  invalidate(invalidateRect.enlarge(2));
+}
 
-  if (!invalidateRect.isEmpty()) {
-    // for motion path, call the invalidate function directly to ignore dpi of
-    // the current level
-    if (m_isPath)
-      m_viewer->GLInvalidateRect(invalidateRect.enlarge(2));
-    else
-      invalidate(invalidateRect.enlarge(2));
-  }
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::deleteStrokes(StrokeList &strokes) {
+  for(StrokeList::iterator i = strokes.begin(); i != strokes.end(); ++i)
+    delete *i;
+  strokes.clear();
 }
 
-//---------------------------------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::copyStrokes(StrokeList &dst, const StrokeList &src) {
+  deleteStrokes(dst);
+  dst.reserve(src.size());
+  for(StrokeList::const_iterator i = src.begin(); i != src.end(); ++i)
+    dst.push_back(new TStroke(**i));
+}
 
-void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
-                                        const TMouseEvent &e) {
-  bool isValid = m_enabled && m_active;
-  m_enabled    = false;
+//--------------------------------------------------------------------------------------------------
 
-  if (!isValid) {
-    // in case the current frame is moved to empty cell while dragging
-    if (!m_track.isEmpty()) {
-      m_track.clear();
-      invalidate();
+void ToonzVectorBrushTool::inputSetBusy(bool busy) {
+  if (m_active == busy) return;
+  
+  if (busy) {
+    
+    // begin painting //////////////////////////
+    
+    m_styleId = 0;
+    m_tracks.clear();
+    
+    TTool::Application *app = TTool::getApplication();
+    if (!app)
+      return;
+
+    m_isPath = app->getCurrentObject()->isSpline();
+    if (m_isPath) {
+      m_currentColor = TPixel32::Red;
+      m_active = true;
+      return;
     }
-    return;
+    
+    // todo: gestire autoenable
+    if ( app->getCurrentColumn()->getColumnIndex() < 0
+      && !app->getCurrentFrame()->isEditingLevel() )
+      return;
+    if (!getImage(true) || !touchImage())
+      return;
+    if (!app->getCurrentLevel()->getLevel())
+      return;
+
+    // nel caso che il colore corrente sia un cleanup/studiopalette color
+    // oppure il colore di un colorfield
+    if (TColorStyle *cs = app->getCurrentLevelStyle()) {
+      TRasterStyleFx *rfx = cs->getRasterStyleFx();
+      if (!cs->isStrokeStyle() && (!rfx || !rfx->isInkStyle()))
+        return;
+      m_styleId = app->getCurrentLevelStyleIndex();
+      m_currentColor = cs->getAverageColor();
+      m_currentColor.m = 255;
+    } else {
+      m_styleId = 1;
+      m_currentColor = TPixel32::Black;
+    }
+    
+    m_active = true;
+    
+    return; // painting has begun
   }
-
+  
+  
+  // end painting //////////////////////////
+  
+  m_active = false;
+  
+  // clear tracks automatically when return from this function
+  struct Cleanup {
+    ToonzVectorBrushTool &owner;
+    inline ~Cleanup() { owner.m_tracks.clear(); owner.invalidate(); }
+  } cleanup = {*this};
+
+  // remove empty tracks
+  for(TrackList::iterator i = m_tracks.begin(); i != m_tracks.end(); )
+    if (i->isEmpty()) i = m_tracks.erase(i); else ++i;
+  
+  if (m_tracks.empty())
+    return;
+  
+  // make motion path (if need)
+    
   if (m_isPath) {
-    double error = 20.0 * getPixelSize();
+    double error = 20.0 * m_pixelSize;
 
-    TStroke *stroke;
-    if (e.isShiftPressed()) {
-      m_track.removeMiddlePoints();
-      stroke = m_track.makeStroke(0);
-    } else {
-      flushTrackPoint();
-      stroke = m_track.makeStroke(error);
-    }
-    int points = stroke->getControlPointCount();
+    m_tracks.resize(1);
+    TStroke *stroke = m_tracks.front().makeStroke(error);
 
     TVectorImageP vi = getImage(true);
-    struct Cleanup {
-      ToonzVectorBrushTool *m_this;
-      ~Cleanup() { m_this->m_track.clear(), m_this->invalidate(); }
-    } cleanup = {this};
 
     if (!isJustCreatedSpline(vi.getPointer())) {
-      m_isPrompting = true;
-
-      QString question("Are you sure you want to replace the motion path?");
-      int ret =
-          DVGui::MsgBox(question, QObject::tr("Yes"), QObject::tr("No"), 0);
-
-      m_isPrompting = false;
-
-      if (ret == 2 || ret == 0) return;
+      m_currentColor = TPixel32::Green;
+      invalidate();
+      int ret = DVGui::MsgBox(
+        QString("Are you sure you want to replace the motion path?"),
+        QObject::tr("Yes"), QObject::tr("No"), 0 );
+      if (ret != 1)
+        return; // 1 here means "Yes" button
     }
 
     QMutexLocker lock(vi->getMutex());
 
-    TUndo *undo =
-        new UndoPath(getXsheet()->getStageObject(getObjectId())->getSpline());
+    TUndo *undo = new UndoPath(
+      getXsheet()->getStageObject(getObjectId())->getSpline() );
 
-    while (vi->getStrokeCount() > 0) vi->deleteStroke(0);
+    while(vi->getStrokeCount() > 0) vi->deleteStroke(0);
     vi->addStroke(stroke, false);
 
     notifyImageChanged();
     TUndoManager::manager()->add(undo);
 
-    return;
+    return; // done with motion path
   }
-
+  
+  // paint regular strokes
+  
   TVectorImageP vi = getImage(true);
-  if (m_track.isEmpty()) {
-    m_styleId = 0;
-    m_track.clear();
-    return;
-  }
-
-  if (vi && (m_snap.getValue() != m_altPressed) && m_foundLastSnap) {
-    addTrackPoint(TThickPoint(m_lastSnapPoint, m_currThickness),
-                  getPixelSize() * getPixelSize());
-  }
-  m_strokeIndex1   = -1;
-  m_strokeIndex2   = -1;
-  m_w1             = -1;
-  m_w2             = -2;
-  m_foundFirstSnap = false;
-  m_foundLastSnap  = false;
-
-  m_track.filterPoints();
-  double error = 30.0 / (1 + 0.5 * m_accuracy.getValue());
-  error *= getPixelSize();
-
-  TStroke *stroke;
-  if (e.isShiftPressed()) {
-    m_track.removeMiddlePoints();
-    stroke = m_track.makeStroke(0);
-  } else {
-    flushTrackPoint();
-    stroke = m_track.makeStroke(error);
-  }
-  stroke->setStyle(m_styleId);
-  {
+  QMutexLocker lock(vi->getMutex());
+  TTool::Application *app = TTool::getApplication();
+  
+  // prepare strokes
+    
+  StrokeList strokes;
+  strokes.reserve(m_tracks.size());
+  for(TrackList::iterator i = m_tracks.begin(); i != m_tracks.end(); ++i) {
+    StrokeGenerator &track = *i;
+    
+    track.filterPoints();
+    double error = 30.0/(1 + 0.5 * m_accuracy.getValue())*m_pixelSize;
+    TStroke *stroke = track.makeStroke(error);
+    
+    stroke->setStyle(m_styleId);
+    
     TStroke::OutlineOptions &options = stroke->outlineOptions();
-    options.m_capStyle               = m_capStyle.getIndex();
-    options.m_joinStyle              = m_joinStyle.getIndex();
-    options.m_miterUpper             = m_miterJoinLimit.getValue();
+    options.m_capStyle   = m_capStyle.getIndex();
+    options.m_joinStyle  = m_joinStyle.getIndex();
+    options.m_miterUpper = m_miterJoinLimit.getValue();
+
+    if ( stroke->getControlPointCount() == 3
+      && stroke->getControlPoint(0) != stroke->getControlPoint(2) )
+                                        // gli stroke con solo 1 chunk vengono
+                                        // fatti dal tape tool...e devono venir
+                                        // riconosciuti come speciali di
+                                        // autoclose proprio dal fatto che
+                                        // hanno 1 solo chunk.
+      stroke->insertControlPoints(0.5);
+    
+    if (!m_frameRange.getIndex() && track.getLoop())
+      stroke->setSelfLoop(true);
+    
+    strokes.push_back(stroke);
   }
-  m_styleId = 0;
 
-  QMutexLocker lock(vi->getMutex());
-  if (stroke->getControlPointCount() == 3 &&
-      stroke->getControlPoint(0) !=
-          stroke->getControlPoint(2))  // gli stroke con solo 1 chunk vengono
-                                       // fatti dal tape tool...e devono venir
-                                       // riconosciuti come speciali di
-                                       // autoclose proprio dal fatto che
-                                       // hanno 1 solo chunk.
-    stroke->insertControlPoints(0.5);
+  // add stroke to image
+  
   if (m_frameRange.getIndex()) {
+    // frame range stroke
     if (m_firstFrameId == -1) {
-      m_firstStroke                   = new TStroke(*stroke);
-      m_firstFrameId                  = getFrameId();
-      TTool::Application *application = TTool::getApplication();
-      if (application) {
-        m_col        = application->getCurrentColumn()->getColumnIndex();
-        m_firstFrame = application->getCurrentFrame()->getFrame();
+      // remember strokes for first srame
+      copyStrokes(m_firstStrokes, strokes);
+      m_firstFrameId = getFrameId();
+      m_rangeTracks  = m_tracks;
+      
+      if (app) {
+        m_col        = app->getCurrentColumn()->getColumnIndex();
+        m_firstFrame = app->getCurrentFrame()->getFrame();
       }
-      m_rangeTrack = m_track;
+      
       if (m_firstFrameRange) {
         m_veryFirstCol     = m_col;
         m_veryFirstFrame   = m_firstFrame;
         m_veryFirstFrameId = m_firstFrameId;
       }
-    } else if (m_firstFrameId == getFrameId()) {
-      if (m_firstStroke) {
-        delete m_firstStroke;
-        m_firstStroke = 0;
-      }
-      m_firstStroke = new TStroke(*stroke);
-      m_rangeTrack  = m_track;
+    } else
+    if (m_firstFrameId == getFrameId()) {
+      // painted of first frame agein, so
+      // just replace the remembered strokes for first frame
+      copyStrokes(m_firstStrokes, strokes);
+      m_rangeTracks = m_tracks;
     } else {
+      // paint frame range strokes
       TFrameId currentId = getFrameId();
-      int curCol = 0, curFrame = 0;
-      TTool::Application *application = TTool::getApplication();
-      if (application) {
-        curCol   = application->getCurrentColumn()->getColumnIndex();
-        curFrame = application->getCurrentFrame()->getFrame();
+      int curCol   = app ? app->getCurrentColumn()->getColumnIndex() : 0;
+      int curFrame = app ? app->getCurrentFrame()->getFrame()        : 0;
+      
+      if (size_t count = std::min(m_firstStrokes.size(), strokes.size())) {
+        TUndoManager::manager()->beginBlock();
+        for(size_t i = 0; i < count; ++i)
+          doFrameRangeStrokes(
+              m_firstFrameId, m_firstStrokes[i], getFrameId(), strokes[i],
+              m_frameRange.getIndex(), m_breakAngles.getValue(), false, false,
+              m_firstFrameRange );
+        TUndoManager::manager()->endBlock();
       }
-      bool success = doFrameRangeStrokes(
-          m_firstFrameId, m_firstStroke, getFrameId(), stroke,
-          m_frameRange.getIndex(), m_breakAngles.getValue(), false, false,
-          m_firstFrameRange);
-      if (e.isCtrlPressed()) {
-        if (application) {
-          if (m_firstFrameId > currentId) {
-            if (application->getCurrentFrame()->isEditingScene()) {
-              application->getCurrentColumn()->setColumnIndex(curCol);
-              application->getCurrentFrame()->setFrame(curFrame);
-            } else
-              application->getCurrentFrame()->setFid(currentId);
+      
+      if (m_inputmanager.state.isKeyPressed(TInputState::Key::control)) {
+        if (app && m_firstFrameId > currentId) {
+          if (app->getCurrentFrame()->isEditingScene()) {
+            app->getCurrentColumn()->setColumnIndex(curCol);
+            app->getCurrentFrame()->setFrame(curFrame);
+          } else {
+            app->getCurrentFrame()->setFid(currentId);
           }
         }
+        
         resetFrameRange();
-        m_firstStroke     = new TStroke(*stroke);
-        m_rangeTrack      = m_track;
+        copyStrokes(m_firstStrokes, strokes);
+        m_rangeTracks     = m_tracks;
         m_firstFrameId    = currentId;
         m_firstFrameRange = false;
-      }
-
-      if (application && !e.isCtrlPressed()) {
-        if (application->getCurrentFrame()->isEditingScene()) {
-          application->getCurrentColumn()->setColumnIndex(m_veryFirstCol);
-          application->getCurrentFrame()->setFrame(m_veryFirstFrame);
-        } else
-          application->getCurrentFrame()->setFid(m_veryFirstFrameId);
-      }
-
-      if (!e.isCtrlPressed()) {
+      } else {
+        if (app) {
+          if (app->getCurrentFrame()->isEditingScene()) {
+            app->getCurrentColumn()->setColumnIndex(m_veryFirstCol);
+            app->getCurrentFrame()->setFrame(m_veryFirstFrame);
+          } else {
+            app->getCurrentFrame()->setFid(m_veryFirstFrameId);
+          }
+        }
         resetFrameRange();
       }
     }
-    invalidate();
   } else {
-    if (m_snapSelf) {
-      stroke->setSelfLoop(true);
-      m_snapSelf = false;
+    // regular paint strokes
+    TUndoManager::manager()->beginBlock();
+    for(StrokeList::iterator i = strokes.begin(); i != strokes.end(); ++i) {
+      TStroke *stroke = *i;
+      addStrokeToImage(app, vi, stroke, m_breakAngles.getValue(),
+                      false, false, m_isFrameCreated, m_isLevelCreated);
+
+      if ((Preferences::instance()->getGuidedDrawingType() == 1 ||
+          Preferences::instance()->getGuidedDrawingType() == 2) &&
+          Preferences::instance()->getGuidedAutoInbetween())
+      {
+        TFrameId fId = getFrameId();
+        doGuidedAutoInbetween(fId, vi, stroke, m_breakAngles.getValue(), false,
+                              false, false);
+        if (app->getCurrentFrame()->isEditingScene())
+          app->getCurrentFrame()->setFrame( app->getCurrentFrame()->getFrameIndex() );
+        else
+          app->getCurrentFrame()->setFid(fId);
+      }
     }
+    TUndoManager::manager()->endBlock();
+  }
+  
+  deleteStrokes(strokes);
+}
 
-    addStrokeToImage(getApplication(), vi, stroke, m_breakAngles.getValue(),
-                     false, false, m_isFrameCreated, m_isLevelCreated);
-    TRectD bbox = stroke->getBBox().enlarge(2) + m_track.getModifiedRegion();
+//--------------------------------------------------------------------------------------------------
 
-    invalidate();  // should use bbox?
+void ToonzVectorBrushTool::inputPaintTracks(const TTrackList &tracks) {
+  if (tracks.empty()) return;
 
-    if ((Preferences::instance()->getGuidedDrawingType() == 1 ||
-         Preferences::instance()->getGuidedDrawingType() == 2) &&
-        Preferences::instance()->getGuidedAutoInbetween()) {
-      int fidx     = getApplication()->getCurrentFrame()->getFrameIndex();
-      TFrameId fId = getFrameId();
+  TRectD invalidateRect;
 
-      doGuidedAutoInbetween(fId, vi, stroke, m_breakAngles.getValue(), false,
-                            false, false);
+  size_t count = m_isPath ? 1 : tracks.size();
+  m_tracks.resize(count);
+  for(size_t i = 0; i < count; ++i) {
+    const TTrack &track = *tracks[i];
+    StrokeGenerator &gen = m_tracks[i];
+    
+    while(track.pointsRemoved) {
+      gen.pop();
+      --track.pointsRemoved;
+    }
+    
+    while(track.pointsAdded) {
+      const TTrackPoint &p = track.current();
+      double t = computeThickness(p.pressure, m_thickness, m_pressure.getValue(), m_isPath);
+      gen.add(TThickPoint(p.position, t), 0);
+      --track.pointsAdded;
+    }
+    
+    bool loop = m_snappedSelf
+            && track.fixedFinished()
+            && !track.empty()
+            && areAlmostEqual(track.front().position, track.back().position);
+    gen.setLoop(loop);
+    
+    invalidateRect += gen.getLastModifiedRegion();
+    if (!i) {
+      TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
+      invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
+      m_brushPos = m_mousePos = track.current().position;
+      invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick);
+    }
+  }
+  
+  if (!invalidateRect.isEmpty()) {
+    if (m_isPath) {
+      if (getViewer()) getViewer()->GLInvalidateRect(invalidateRect);
+    } else {
+      invalidate(invalidateRect.enlarge(2));
+    }
+  }
+}
 
-      if (getApplication()->getCurrentFrame()->isEditingScene())
-        getApplication()->getCurrentFrame()->setFrame(fidx);
-      else
-        getApplication()->getCurrentFrame()->setFid(fId);
+//--------------------------------------------------------------------------------------------------
+
+bool ToonzVectorBrushTool::preLeftButtonDown() {
+  if (getViewer() && getViewer()->getGuidedStrokePickerMode()) return false;
+
+  m_pixelSize = getPixelSize();
+  int smoothRadius = (int)round(m_smooth.getValue());
+  m_modifierAssistants->magnetism = m_assistants.getValue() ? 1 : 0;
+  m_modifierSegmentation->setStep(TPointD(m_pixelSize, m_pixelSize));
+  m_modifierSmoothSegmentation->setStep(TPointD(2*m_pixelSize, 2*m_pixelSize));
+  m_modifierSimplify->step = 2*m_pixelSize;
+  m_inputmanager.drawPreview = false;
+
+  m_inputmanager.clearModifiers();
+  m_inputmanager.addModifier(TInputModifierP(m_modifierTangents.getPointer()));
+  if (smoothRadius > 0) {
+    m_inputmanager.addModifier(TInputModifierP(m_modifierSmoothSegmentation.getPointer()));
+    for(int i = 0; i < 3; ++i) {
+      m_modifierSmooth[i]->radius = smoothRadius;
+      m_inputmanager.addModifier(TInputModifierP(m_modifierSmooth[i].getPointer()));
     }
   }
-  assert(stroke);
-  m_track.clear();
-  m_altPressed = false;
+  m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
+#ifndef NDEBUG
+  m_inputmanager.addModifier(TInputModifierP(m_modifierTest.getPointer()));
+#endif
+  m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
+  m_inputmanager.addModifier(TInputModifierP(m_modifierSimplify.getPointer()));
+
+  touchImage();
+  if (m_isFrameCreated) {
+    // When the xsheet frame is selected, whole viewer will be updated from
+    // SceneViewer::onXsheetChanged() on adding a new frame.
+    // We need to take care of a case when the level frame is selected.
+    if (m_application->getCurrentFrame()->isEditingLevel()) invalidate();
+  }
+  return true;
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::handleMouseEvent(MouseEventType type,
+                                          const TPointD &pos,
+                                          const TMouseEvent &e)
+{
+  TTimerTicks t = TToolTimer::ticks();
+  bool alt      = e.getModifiersMask() & TMouseEvent::ALT_KEY;
+  bool shift    = e.getModifiersMask() & TMouseEvent::SHIFT_KEY;
+  bool control  = e.getModifiersMask() & TMouseEvent::CTRL_KEY;
+
+  if (shift && type == ME_DOWN && e.button() == Qt::LeftButton && !m_active) {
+    m_modifierAssistants->magnetism = 0;
+    m_inputmanager.clearModifiers();
+    m_inputmanager.addModifier(TInputModifierP(m_modifierLine.getPointer()));
+    m_inputmanager.addModifier(TInputModifierP(m_modifierAssistants.getPointer()));
+    m_inputmanager.addModifier(TInputModifierP(m_modifierSegmentation.getPointer()));
+    m_inputmanager.addModifier(TInputModifierP(m_modifierSimplify.getPointer()));
+    m_inputmanager.drawPreview = true;
+  }
+
+  if (alt != m_inputmanager.state.isKeyPressed(TKey::alt))
+    m_inputmanager.keyEvent(alt, TKey::alt, t, nullptr);
+  if (shift != m_inputmanager.state.isKeyPressed(TKey::shift))
+    m_inputmanager.keyEvent(shift, TKey::shift, t, nullptr);
+  if (control != m_inputmanager.state.isKeyPressed(TKey::control))
+    m_inputmanager.keyEvent(control, TKey::control, t, nullptr);
+
+  TPointD snappedPos = pos;
+  bool pickerMode = getViewer() && getViewer()->getGuidedStrokePickerMode();
+  bool snapInvert = alt && (!control || type == ME_MOVE || type == ME_DOWN);
+  bool snapEnabled = !pickerMode && (snapInvert != m_snap.getValue());
+  snap(pos, snapEnabled, m_active);
+  if (m_snapped)
+    snappedPos = m_snapPoint;
+  if (m_snappedSelf && type == ME_UP)
+    snappedPos = m_snapPointSelf;
+  
+  if (type == ME_MOVE) {
+    qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
+    THoverList hovers(1, snappedPos);
+    m_inputmanager.hoverEvent(hovers);
+  } else
+  if (pickerMode) {
+    if (type == ME_DOWN) getViewer()->doPickGuideStroke(pos);
+  } else {
+    int    deviceId    = e.isTablet() ? 1 : 0;
+    bool   hasPressure = e.isTablet();
+    double pressure    = hasPressure ? e.m_pressure : 1.0;
+    bool   final       = type == ME_UP;
+    m_inputmanager.trackEvent(
+      deviceId, 0, snappedPos, pressure, TPointD(), hasPressure, false, final, t);
+    m_inputmanager.processTracks();
+  }
+}
+
+//--------------------------------------------------------------------------------------------------
+
+void ToonzVectorBrushTool::leftButtonDown(const TPointD &pos,
+                                        const TMouseEvent &e) {
+  handleMouseEvent(ME_DOWN, pos, e);
+}
+void ToonzVectorBrushTool::leftButtonDrag(const TPointD &pos,
+                                        const TMouseEvent &e) {
+  handleMouseEvent(ME_DRAG, pos, e);
+}
+void ToonzVectorBrushTool::leftButtonUp(const TPointD &pos,
+                                      const TMouseEvent &e) {
+  handleMouseEvent(ME_UP, pos, e);
+}
+void ToonzVectorBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
+  handleMouseEvent(ME_MOVE, pos, e);
 }
 
 //--------------------------------------------------------------------------------------------------
 
 bool ToonzVectorBrushTool::keyDown(QKeyEvent *event) {
-  if (event->key() == Qt::Key_Escape) {
+  if (event->key() == Qt::Key_Escape)
     resetFrameRange();
-  }
   return false;
 }
 
@@ -1070,14 +1176,14 @@ bool ToonzVectorBrushTool::doFrameRangeStrokes(
   TVectorImageP firstImage = new TVectorImage();
   TVectorImageP lastImage  = new TVectorImage();
 
-  *first       = *firstStroke;
-  *last        = *lastStroke;
-  bool swapped = false;
-  if (firstFrameId > lastFrameId) {
+  bool swapped = firstFrameId > lastFrameId;
+  if (swapped) {
     std::swap(firstFrameId, lastFrameId);
-    *first  = *lastStroke;
-    *last   = *firstStroke;
-    swapped = true;
+    *first = *lastStroke;
+    *last  = *firstStroke;
+  } else {
+    *first = *firstStroke;
+    *last  = *lastStroke;
   }
 
   firstImage->addStroke(first, false);
@@ -1256,307 +1362,142 @@ bool ToonzVectorBrushTool::doGuidedAutoInbetween(
 
 //--------------------------------------------------------------------------------------------------
 
-void ToonzVectorBrushTool::addTrackPoint(const TThickPoint &point,
-                                         double pixelSize2) {
-  m_smoothStroke.addPoint(point);
-  std::vector<TThickPoint> pts;
-  m_smoothStroke.getSmoothPoints(pts);
-  for (size_t i = 0; i < pts.size(); ++i) {
-    m_track.add(pts[i], pixelSize2);
-  }
-}
-
-//--------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::flushTrackPoint() {
-  m_smoothStroke.endStroke();
-  std::vector<TThickPoint> pts;
-  m_smoothStroke.getSmoothPoints(pts);
-  double pixelSize2 = getPixelSize() * getPixelSize();
-  for (size_t i = 0; i < pts.size(); ++i) {
-    m_track.add(pts[i], pixelSize2);
-  }
-}
-
-//---------------------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) {
-  qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
-
-  struct Locals {
-    ToonzVectorBrushTool *m_this;
-
-    void setValue(TDoublePairProperty &prop,
-                  const TDoublePairProperty::Value &value) {
-      prop.setValue(value);
-
-      m_this->onPropertyChanged(prop.getName());
-      TTool::getApplication()->getCurrentTool()->notifyToolChanged();
-    }
-
-    void addMinMax(TDoublePairProperty &prop, double add) {
-      if (add == 0.0) return;
-      const TDoublePairProperty::Range &range = prop.getRange();
-
-      TDoublePairProperty::Value value = prop.getValue();
-      value.first  = tcrop(value.first + add, range.first, range.second);
-      value.second = tcrop(value.second + add, range.first, range.second);
-
-      setValue(prop, value);
-    }
-
-    void addMinMaxSeparate(TDoublePairProperty &prop, double min, double max) {
-      if (min == 0.0 && max == 0.0) return;
-      const TDoublePairProperty::Range &range = prop.getRange();
-
-      TDoublePairProperty::Value value = prop.getValue();
-      value.first += min;
-      value.second += max;
-      if (value.first > value.second) value.first = value.second;
-      value.first  = tcrop(value.first, range.first, range.second);
-      value.second = tcrop(value.second, range.first, range.second);
-
-      setValue(prop, value);
-    }
-
-  } locals = {this};
-
-  TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5);
-  TRectD invalidateRect(m_brushPos - halfThick, m_brushPos + halfThick);
-
-  if (e.isCtrlPressed() && e.isAltPressed() && !e.isShiftPressed() &&
-      Preferences::instance()->useCtrlAltToResizeBrushEnabled()) {
-    // Resize the brush if CTRL+ALT is pressed and the preference is enabled.
-    const TPointD &diff = pos - m_mousePos;
-    double max          = diff.x / 2;
-    double min          = diff.y / 2;
-
-    locals.addMinMaxSeparate(m_thickness, min, max);
-
-    double radius = m_thickness.getValue().second * 0.5;
-    invalidateRect += TRectD(m_brushPos - TPointD(radius, radius),
-                             m_brushPos + TPointD(radius, radius));
-
-  } else {
-    m_mousePos = pos;
-    m_brushPos = pos;
-
-    TPointD snapThick(6.0 * m_pixelSize, 6.0 * m_pixelSize);
-    // In order to clear the previous snap indicator
-    if (m_foundFirstSnap)
-      invalidateRect +=
-          TRectD(m_firstSnapPoint - snapThick, m_firstSnapPoint + snapThick);
-
-    m_firstSnapPoint = pos;
-    m_foundFirstSnap = false;
-    m_altPressed     = e.isAltPressed() && !e.isCtrlPressed();
-    checkStrokeSnapping(true, m_altPressed);
-    checkGuideSnapping(true, m_altPressed);
-    m_brushPos = m_firstSnapPoint;
-    // In order to draw the snap indicator
-    if (m_foundFirstSnap)
-      invalidateRect +=
-          TRectD(m_firstSnapPoint - snapThick, m_firstSnapPoint + snapThick);
-
-    invalidateRect += TRectD(pos - halfThick, pos + halfThick);
-  }
-
-  invalidate(invalidateRect.enlarge(2));
-
-  if (m_minThick == 0 && m_maxThick == 0) {
-    m_minThick = m_thickness.getValue().first;
-    m_maxThick = m_thickness.getValue().second;
-  }
-}
-
-//-------------------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::checkStrokeSnapping(bool beforeMousePress,
-                                               bool invertCheck) {
-  if (Preferences::instance()->getVectorSnappingTarget() == 1) return;
-
-  TVectorImageP vi(getImage(false));
-  bool checkSnap = m_snap.getValue();
-  if (invertCheck) checkSnap = !checkSnap;
-  m_dragDraw = true;
-  if (vi && checkSnap) {
+void ToonzVectorBrushTool::snap(const TPointD &pos, bool snapEnabled, bool withSelfSnap) {
+  bool oldSnapped      = m_snapped;
+  bool oldSnappedSelf  = m_snappedSelf;
+  TPointD oldPoint     = m_snapPoint;
+  TPointD oldPointSelf = m_snapPointSelf;
+  
+  m_snapped = m_snappedSelf = false;
+  
+  if (snapEnabled) { // snapping is active
     double minDistance2 = m_minDistance2;
-    if (beforeMousePress)
-      m_strokeIndex1 = -1;
-    else
-      m_strokeIndex2 = -1;
-    int i, strokeNumber = vi->getStrokeCount();
-    TStroke *stroke;
-    double distance2, outW;
-    bool snapFound = false;
-    TThickPoint point1;
-
-    for (i = 0; i < strokeNumber; i++) {
-      stroke = vi->getStroke(i);
-      if (stroke->getNearestW(m_mousePos, outW, distance2) &&
-          distance2 < minDistance2) {
-        minDistance2 = distance2;
-        beforeMousePress ? m_strokeIndex1 = i : m_strokeIndex2 = i;
-        if (areAlmostEqual(outW, 0.0, 1e-3))
-          beforeMousePress ? m_w1 = 0.0 : m_w2 = 0.0;
-        else if (areAlmostEqual(outW, 1.0, 1e-3))
-          beforeMousePress ? m_w1 = 1.0 : m_w2 = 1.0;
-        else
-          beforeMousePress ? m_w1 = outW : m_w2 = outW;
+    
+    // 0 - strokes, 1 - guides, 2 - all
+    int target = Preferences::instance()->getVectorSnappingTarget();
+
+    // snap to guides
+    if (target != 0) {
+      if (TToolViewer *viewer = getViewer()) {
+        // find nearest vertical guide
+        int cnt = viewer->getVGuideCount();
+        for(int i = 0; i < cnt; ++i) {
+          double guide = viewer->getVGuide(i);
+          double d2 = guide - pos.y;
+          d2 *= d2; // we work with square of the distance
+          if (d2 < minDistance2) {
+            m_snapped = true;
+            m_snapPoint.x = pos.x;
+            m_snapPoint.y = guide;
+            minDistance2 = d2;
+          }
+        }
 
-        beforeMousePress ? point1 = stroke->getPoint(m_w1)
-                         : point1 = stroke->getPoint(m_w2);
-        snapFound = true;
-      }
-    }
-    // compare to first point of current stroke
-    if (beforeMousePress && snapFound) {
-      m_firstSnapPoint = TPointD(point1.x, point1.y);
-      m_foundFirstSnap = true;
-    } else if (!beforeMousePress) {
-      if (!snapFound) {
-        TPointD tempPoint        = m_track.getFirstPoint();
-        double distanceFromStart = tdistance2(m_mousePos, tempPoint);
-
-        if (distanceFromStart < m_minDistance2) {
-          point1     = tempPoint;
-          distance2  = distanceFromStart;
-          snapFound  = true;
-          m_snapSelf = true;
+        // find nearest horizontal guide
+        cnt = viewer->getHGuideCount();
+        for(int i = 0; i < cnt; ++i) {
+          double guide = viewer->getHGuide(i);
+          double d2 = guide - pos.x;
+          d2 *= d2; // we work with square of the distance
+          if (d2 < minDistance2) {
+            m_snapped = true;
+            m_snapPoint.x = guide;
+            m_snapPoint.y = pos.y;
+            minDistance2 = d2;
+          }
         }
       }
-      if (snapFound) {
-        m_lastSnapPoint = TPointD(point1.x, point1.y);
-        m_foundLastSnap = true;
-        if (distance2 < 2.0) m_dragDraw = false;
-      }
     }
-  }
-}
 
-//-------------------------------------------------------------------------------------------------------------
-
-void ToonzVectorBrushTool::checkGuideSnapping(bool beforeMousePress,
-                                              bool invertCheck) {
-  if (Preferences::instance()->getVectorSnappingTarget() == 0) return;
-  bool foundSnap;
-  TPointD snapPoint;
-  beforeMousePress ? foundSnap = m_foundFirstSnap : foundSnap = m_foundLastSnap;
-  beforeMousePress ? snapPoint = m_firstSnapPoint : snapPoint = m_lastSnapPoint;
-
-  bool checkSnap = m_snap.getValue();
-  if (invertCheck) checkSnap = !checkSnap;
-
-  if (checkSnap) {
-    // check guide snapping
-    int vGuideCount = 0, hGuideCount = 0;
-    double guideDistance  = sqrt(m_minDistance2);
-    TToolViewer *viewer = getViewer();
-    if (viewer) {
-      vGuideCount = viewer->getVGuideCount();
-      hGuideCount = viewer->getHGuideCount();
-    }
-    double distanceToVGuide = -1.0, distanceToHGuide = -1.0;
-    double vGuide, hGuide;
-    bool useGuides = false;
-    if (vGuideCount) {
-      for (int j = 0; j < vGuideCount; j++) {
-        double guide        = viewer->getVGuide(j);
-        double tempDistance = std::abs(guide - m_mousePos.y);
-        if (tempDistance < guideDistance &&
-            (distanceToVGuide < 0 || tempDistance < distanceToVGuide)) {
-          distanceToVGuide = tempDistance;
-          vGuide           = guide;
-          useGuides        = true;
+    // snap to strokes
+    if (target != 1) {
+      if (TVectorImageP vi = getImage(false)) {
+        int count = vi->getStrokeCount();
+        for(int i = 0; i < count; ++i) {
+          double w, d2;
+          TStroke *stroke = vi->getStroke(i);
+          if (!stroke->getNearestW(pos, w, d2) || d2 >= minDistance2)
+            continue;
+          minDistance2 = d2;
+          w = w > 0.001 ? (w < 0.999 ? w : 1.0) : 0.0;
+          m_snapped = true;
+          m_snapPoint = stroke->getPoint(w);
         }
       }
-    }
-    if (hGuideCount) {
-      for (int j = 0; j < hGuideCount; j++) {
-        double guide        = viewer->getHGuide(j);
-        double tempDistance = std::abs(guide - m_mousePos.x);
-        if (tempDistance < guideDistance &&
-            (distanceToHGuide < 0 || tempDistance < distanceToHGuide)) {
-          distanceToHGuide = tempDistance;
-          hGuide           = guide;
-          useGuides        = true;
+    
+      // finally snap to first point of track (self snap)
+      if (withSelfSnap && !m_tracks.empty() && !m_tracks.front().isEmpty()) {
+        TPointD p = m_tracks.front().getFirstPoint();
+        double d2 = tdistance2(pos, p);
+        if (d2 < minDistance2) {
+          m_snappedSelf = true;
+          m_snapPointSelf = p;
         }
       }
     }
-    if (useGuides && foundSnap) {
-      double currYDistance = std::abs(snapPoint.y - m_mousePos.y);
-      double currXDistance = std::abs(snapPoint.x - m_mousePos.x);
-      double hypotenuse =
-          sqrt(pow(currYDistance, 2.0) + pow(currXDistance, 2.0));
-      if ((distanceToVGuide >= 0 && distanceToVGuide < hypotenuse) ||
-          (distanceToHGuide >= 0 && distanceToHGuide < hypotenuse)) {
-        useGuides  = true;
-        m_snapSelf = false;
-      } else
-        useGuides = false;
-    }
-    if (useGuides) {
-      assert(distanceToHGuide >= 0 || distanceToVGuide >= 0);
-      if (distanceToHGuide < 0 ||
-          (distanceToVGuide <= distanceToHGuide && distanceToVGuide >= 0)) {
-        snapPoint.y = vGuide;
-        snapPoint.x = m_mousePos.x;
+  } // snapping is active
+  
+  // invalidate rect
+  TRectD invalidateRect;
+  double radius = 8.0*m_pixelSize;
+  TPointD halfSize(radius, radius);
 
-      } else {
-        snapPoint.y = m_mousePos.y;
-        snapPoint.x = hGuide;
-      }
-      beforeMousePress ? m_foundFirstSnap = true : m_foundLastSnap = true;
-      beforeMousePress ? m_firstSnapPoint = snapPoint
-                       : m_lastSnapPoint  = snapPoint;
-    }
+  if ( oldSnapped != m_snapped
+    || !areAlmostEqual(oldPoint, m_snapPoint) )
+  {
+    if (oldSnapped) invalidateRect += TRectD(oldPoint - halfSize, oldPoint + halfSize);
+    if (m_snapped)  invalidateRect += TRectD(m_snapPoint - halfSize, m_snapPoint + halfSize);
   }
+  
+  if ( oldSnappedSelf != m_snappedSelf
+    || !areAlmostEqual(oldPointSelf, m_snapPointSelf) )
+  {
+    if (oldSnappedSelf) invalidateRect += TRectD(oldPointSelf - halfSize, oldPointSelf + halfSize);
+    if (m_snappedSelf)  invalidateRect += TRectD(m_snapPointSelf - halfSize, m_snapPointSelf + halfSize);
+  }
+  
+  if (!invalidateRect.isEmpty())
+    invalidate(invalidateRect);
 }
 
 //-------------------------------------------------------------------------------------------------------------
 
 void ToonzVectorBrushTool::draw() {
+  m_pixelSize = getPixelSize();
+  m_inputmanager.draw();
+  
   /*--ショートカットでのツール切り替え時に赤点が描かれるのを防止する--*/
   if (m_minThick == 0 && m_maxThick == 0 &&
       !Preferences::instance()->getShow0ThickLines())
     return;
 
-  TImageP img = getImage(false, 1);
-
-  // Draw track
-  tglColor(m_isPrompting ? TPixel32::Green : m_currentColor);
-  m_track.drawAllFragments();
-
-  // snapping
-  TVectorImageP vi = img;
-  if (m_snap.getValue() != m_altPressed) {
-    m_pixelSize  = getPixelSize();
-    double thick = 6.0 * m_pixelSize;
-    if (m_foundFirstSnap) {
-      tglColor(TPixelD(0.1, 0.9, 0.1));
-      tglDrawCircle(m_firstSnapPoint, thick);
-    }
-
-    TThickPoint point2;
-
-    if (m_foundLastSnap) {
-      tglColor(TPixelD(0.1, 0.9, 0.1));
-      tglDrawCircle(m_lastSnapPoint, thick);
-    }
+  // draw track
+  tglColor(m_currentColor);
+  for(TrackList::iterator i = m_tracks.begin(); i != m_tracks.end(); ++i)
+    i->drawAllFragments();
+  
+  // draw snapping
+  double snapMarkRadius = 6.0 * m_pixelSize;
+  if (m_snapped) {
+    tglColor(TPixelD(0.1, 0.9, 0.1));
+    tglDrawCircle(m_snapPoint, snapMarkRadius);
+  }
+  if (m_snappedSelf) {
+    tglColor(TPixelD(0.9, 0.9, 0.1));
+    tglDrawCircle(m_snapPointSelf, snapMarkRadius);
   }
 
   // frame range
-  if (m_firstStroke) {
+  for(TrackList::iterator i = m_rangeTracks.begin(); i != m_rangeTracks.end(); ++i) {
+    if (i->isEmpty()) continue;
+    TPointD offset1 = TPointD(5, 5);
+    TPointD offset2 = TPointD(-offset1.x, offset1.y);
+    TPointD point = i->getFirstPoint();
     glColor3d(1.0, 0.0, 0.0);
-    m_rangeTrack.drawAllFragments();
+    i->drawAllFragments();
     glColor3d(0.0, 0.6, 0.0);
-    TPointD firstPoint        = m_rangeTrack.getFirstPoint();
-    TPointD topLeftCorner     = TPointD(firstPoint.x - 5, firstPoint.y - 5);
-    TPointD topRightCorner    = TPointD(firstPoint.x + 5, firstPoint.y - 5);
-    TPointD bottomLeftCorner  = TPointD(firstPoint.x - 5, firstPoint.y + 5);
-    TPointD bottomRightCorner = TPointD(firstPoint.x + 5, firstPoint.y + 5);
-    tglDrawSegment(topLeftCorner, bottomRightCorner);
-    tglDrawSegment(topRightCorner, bottomLeftCorner);
+    tglDrawSegment(point - offset1, point + offset1);
+    tglDrawSegment(point - offset2, point + offset2);
   }
 
   if (getApplication()->getCurrentObject()->isSpline()) return;
@@ -1584,24 +1525,8 @@ void ToonzVectorBrushTool::draw() {
 //--------------------------------------------------------------------------------------------------------------
 
 void ToonzVectorBrushTool::onEnter() {
-  TImageP img = getImage(false);
-
   m_minThick = m_thickness.getValue().first;
   m_maxThick = m_thickness.getValue().second;
-
-  Application *app = getApplication();
-
-  m_styleId       = app->getCurrentLevelStyleIndex();
-  TColorStyle *cs = app->getCurrentLevelStyle();
-  if (cs) {
-    TRasterStyleFx *rfx = cs->getRasterStyleFx();
-    m_active            = cs->isStrokeStyle() || (rfx && rfx->isInkStyle());
-    m_currentColor      = cs->getAverageColor();
-    m_currentColor.m    = 255;
-  } else {
-    m_currentColor = TPixel32::Black;
-  }
-  m_active = img;
 }
 
 //----------------------------------------------------------------------------------------------------------
@@ -1615,19 +1540,15 @@ void ToonzVectorBrushTool::onLeave() {
 
 TPropertyGroup *ToonzVectorBrushTool::getProperties(int idx) {
   if (!m_presetsLoaded) initPresets();
-
   return &m_prop[idx];
 }
 
 //------------------------------------------------------------------
 
 void ToonzVectorBrushTool::resetFrameRange() {
-  m_rangeTrack.clear();
+  m_rangeTracks.clear();
   m_firstFrameId = -1;
-  if (m_firstStroke) {
-    delete m_firstStroke;
-    m_firstStroke = 0;
-  }
+  deleteStrokes(m_firstStrokes);
   m_firstFrameRange = true;
 }
 
@@ -1687,6 +1608,7 @@ bool ToonzVectorBrushTool::onPropertyChanged(std::string propertyName) {
   V_VectorBrushSnap            = m_snap.getValue();
   int snapSensitivityIndex     = m_snapSensitivity.getIndex();
   V_VectorBrushSnapSensitivity = snapSensitivityIndex;
+  V_VectorBrushAssistants      = m_assistants.getValue();
 
   // Recalculate/reset based on changed settings
   m_minThick = m_thickness.getValue().first;
@@ -1825,8 +1747,9 @@ void ToonzVectorBrushTool::loadLastBrush() {
 
   // Properties not tracked with preset
   m_frameRange.setIndex(V_VectorBrushFrameRange);
-  m_snap.setValue(V_VectorBrushSnap);
+  m_snap.setValue(V_VectorBrushSnap ? 1 : 0);
   m_snapSensitivity.setIndex(V_VectorBrushSnapSensitivity);
+  m_assistants.setValue(V_VectorBrushAssistants ? 1 : 0);
 
   // Recalculate based on prior values
   m_minThick = m_thickness.getValue().first;
diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.h b/toonz/sources/tnztools/toonzvectorbrushtool.h
index 5ded2d6..2aff0b4 100644
--- a/toonz/sources/tnztools/toonzvectorbrushtool.h
+++ b/toonz/sources/tnztools/toonzvectorbrushtool.h
@@ -3,17 +3,26 @@
 #ifndef TOONZVECTORBRUSHTOOL_H
 #define TOONZVECTORBRUSHTOOL_H
 
-#include "tgeometry.h"
-#include "tproperty.h"
-#include "trasterimage.h"
-#include "ttoonzimage.h"
-#include "tstroke.h"
-#include "toonz/strokegenerator.h"
-
-#include "tools/tool.h"
-#include "tools/cursors.h"
-
-#include "toonzrasterbrushtool.h"
+#include <tgeometry.h>
+#include <tproperty.h>
+#include <trasterimage.h>
+#include <ttoonzimage.h>
+#include <tstroke.h>
+#include <toonz/strokegenerator.h>
+
+#include <tools/tool.h>
+#include <tools/cursors.h>
+
+#include <tools/inputmanager.h>
+#include <tools/modifiers/modifierline.h>
+#include <tools/modifiers/modifiertangents.h>
+#include <tools/modifiers/modifierassistants.h>
+#include <tools/modifiers/modifiersegmentation.h>
+#include <tools/modifiers/modifiersimplify.h>
+#include <tools/modifiers/modifiersmooth.h>
+#ifndef NDEBUG
+#include <tools/modifiers/modifiertest.h>
+#endif
 
 #include <QCoreApplication>
 #include <QRadialGradient>
@@ -80,13 +89,18 @@ public:
 //    Brush Tool declaration
 //************************************************************************
 
-class ToonzVectorBrushTool final : public TTool {
+class ToonzVectorBrushTool final : public TTool,
+                                   public TInputHandler
+{
   Q_DECLARE_TR_FUNCTIONS(ToonzVectorBrushTool)
 
 public:
   ToonzVectorBrushTool(std::string name, int targetType);
 
-  ToolType getToolType() const override { return TTool::LevelWriteTool; }
+  ToolType getToolType() const override
+    { return TTool::LevelWriteTool; }
+  unsigned int getToolHints() const override
+    { return TTool::getToolHints() & ~HintAssistantsAll; }
 
   ToolOptionsBox *createOptionsBox() override;
 
@@ -102,6 +116,13 @@ public:
   void mouseMove(const TPointD &pos, const TMouseEvent &e) override;
   bool keyDown(QKeyEvent *event) override;
 
+  void inputMouseMove(const TPointD &position,
+                      const TInputState &state) override;
+  void inputSetBusy(bool busy) override;
+  void inputPaintTracks(const TTrackList &tracks) override;
+  void inputInvalidateRect(const TRectD &bounds) override { invalidate(bounds); }
+  TTool *inputGetTool() override { return this; };
+
   void draw() override;
 
   void onEnter() override;
@@ -128,22 +149,30 @@ public:
   // Tools.
   bool isPencilModeActive() override;
 
-  void addTrackPoint(const TThickPoint &point, double pixelSize2);
-  void flushTrackPoint();
   bool doFrameRangeStrokes(TFrameId firstFrameId, TStroke *firstStroke,
                            TFrameId lastFrameId, TStroke *lastStroke,
                            int interpolationType, bool breakAngles,
                            bool autoGroup = false, bool autoFill = false,
                            bool drawFirstStroke = true,
                            bool drawLastStroke = true, bool withUndo = true);
-  void checkGuideSnapping(bool beforeMousePress, bool invertCheck);
-  void checkStrokeSnapping(bool beforeMousePress, bool invertCheck);
   bool doGuidedAutoInbetween(TFrameId cFid, const TVectorImageP &cvi,
                              TStroke *cStroke, bool breakAngles,
                              bool autoGroup = false, bool autoFill = false,
                              bool drawStroke = true);
 
 protected:
+  typedef std::vector<StrokeGenerator> TrackList;
+  typedef std::vector<TStroke*> StrokeList;
+  void deleteStrokes(StrokeList &strokes);
+  void copyStrokes(StrokeList &dst, const StrokeList &src);
+
+  void snap(const TPointD &pos, bool snapEnabled, bool withSelfSnap = false);
+
+  enum MouseEventType { ME_DOWN, ME_DRAG, ME_UP, ME_MOVE };
+  void handleMouseEvent(MouseEventType type, const TPointD &pos,
+                        const TMouseEvent &e);
+
+protected:
   TPropertyGroup m_prop[2];
 
   TDoublePairProperty m_thickness;
@@ -158,58 +187,48 @@ protected:
   TEnumProperty m_capStyle;
   TEnumProperty m_joinStyle;
   TIntProperty m_miterJoinLimit;
-
-  StrokeGenerator m_track;
-  StrokeGenerator m_rangeTrack;
-  RasterStrokeGenerator *m_rasterTrack;
-  TStroke *m_firstStroke;
-  TTileSetCM32 *m_tileSet;
-  TTileSaverCM32 *m_tileSaver;
+  TBoolProperty m_assistants;
+
+  TInputManager m_inputmanager;
+  TSmartPointerT<TModifierLine> m_modifierLine;
+  TSmartPointerT<TModifierTangents> m_modifierTangents;
+  TSmartPointerT<TModifierAssistants> m_modifierAssistants;
+  TSmartPointerT<TModifierSegmentation> m_modifierSegmentation;
+  TSmartPointerT<TModifierSegmentation> m_modifierSmoothSegmentation;
+  TSmartPointerT<TModifierSmooth> m_modifierSmooth[3];
+  TSmartPointerT<TModifierSimplify> m_modifierSimplify;
+#ifndef NDEBUG
+  TSmartPointerT<TModifierTest> m_modifierTest;
+#endif
+  
+  TrackList m_tracks;
+  TrackList m_rangeTracks;
+  StrokeList m_firstStrokes;
   TFrameId m_firstFrameId, m_veryFirstFrameId;
   TPixel32 m_currentColor;
-  int m_styleId;
+  int m_styleId; // bwtodo: remove
   double m_minThick, m_maxThick;
 
   // for snapping and framerange
-  int m_strokeIndex1, m_strokeIndex2, m_col, m_firstFrame, m_veryFirstFrame,
+  int m_col, m_firstFrame, m_veryFirstFrame,
       m_veryFirstCol, m_targetType;
-  double m_w1, m_w2, m_pixelSize, m_currThickness, m_minDistance2;
-  bool m_foundFirstSnap = false, m_foundLastSnap = false, m_dragDraw = true,
-       m_altPressed = false, m_snapSelf = false;
-  TRectD m_modifiedRegion;
-  TPointD m_dpiScale,
-      m_mousePos,  //!< Current mouse position, in world coordinates.
-      m_brushPos,  //!< World position the brush will be painted at.
-      m_firstSnapPoint, m_lastSnapPoint;
-
-  BluredBrush *m_bluredBrush;
-  QRadialGradient m_brushPad;
-
-  TRasterCM32P m_backupRas;
-  TRaster32P m_workRas;
-
-  std::vector<TThickPoint> m_points;
-  TRect m_strokeRect, m_lastRect;
-
-  SmoothStroke m_smoothStroke;
+  double m_pixelSize, m_minDistance2;
+  
+  bool m_snapped;
+  bool m_snappedSelf;
+  TPointD m_snapPoint;
+  TPointD m_snapPointSelf;
+  
+  TPointD m_mousePos;  //!< Current mouse position, in world coordinates.
+  TPointD m_brushPos;  //!< World position the brush will be painted at.
 
   VectorBrushPresetManager
       m_presetsManager;  //!< Manager for presets of this tool instance
 
-  bool m_active, m_enabled,
-      m_isPrompting,  //!< Whether the tool is prompting for spline
-                      //! substitution.
-      m_firstTime, m_isPath, m_presetsLoaded, m_firstFrameRange;
-
-  /*---
-  ��ƒ���FrameId���N���b�N���ɕۑ����A�}�E�X�����[�X���iUndo�̓o�^���j�ɕʂ̃t���[����
-  �ړ����Ă����Ƃ��̕s����C������B---*/
-  TFrameId m_workingFrameId;
-
-  TPointD m_lastDragPos;        //!< Position where mouse was last dragged.
-  TMouseEvent m_lastDragEvent;  //!< Previous mouse-drag event.
+  bool m_active, m_firstTime, m_isPath,
+       m_presetsLoaded, m_firstFrameRange;
 
-  bool m_propertyUpdating = false;
+  bool m_propertyUpdating;
 };
 
 #endif  // TOONZVECTORBRUSHTOOL_H
diff --git a/toonz/sources/tnztools/track.cpp b/toonz/sources/tnztools/track.cpp
index 5af347a..2637216 100644
--- a/toonz/sources/tnztools/track.cpp
+++ b/toonz/sources/tnztools/track.cpp
@@ -11,14 +11,13 @@ TTrack::Id TTrack::m_lastId = 0;
 
 
 //*****************************************************************************************
-//    TTrackModifier implemantation
+//    TTrackIntrOrig implemantation
 //*****************************************************************************************
 
 TTrackPoint
-TTrackModifier::calcPoint(double originalIndex) {
-  TTrackPoint p = original.calcPoint(originalIndex);
-  p.originalIndex = originalIndex;
-  return p;
+TTrackIntrOrig::interpolate(double index) {
+  return track.original ? track.calcPointFromOriginal(track.originalIndexByIndex(index))
+                        : track.interpolateLinear(index);
 }
 
 
@@ -32,7 +31,8 @@ TTrack::TTrack(
   const TInputState::KeyHistory::Holder &keyHistory,
   const TInputState::ButtonHistory::Holder &buttonHistory,
   bool hasPressure,
-  bool hasTilt
+  bool hasTilt,
+  double timeOffset
 ):
   id(++m_lastId),
   deviceId(deviceId),
@@ -41,21 +41,26 @@ TTrack::TTrack(
   buttonHistory(buttonHistory),
   hasPressure(hasPressure),
   hasTilt(hasTilt),
+  original(),
+  timeOffset(timeOffset),
+  rootTimeOffset(timeOffset),
   pointsRemoved(),
   pointsAdded(),
   fixedPointsAdded(),
   m_pointsFixed()
   { }
 
-TTrack::TTrack(const TTrackModifierP &modifier):
+TTrack::TTrack(const TTrack &original, double timeOffset):
   id(++m_lastId),
-  deviceId(modifier->original.deviceId),
-  touchId(modifier->original.touchId),
-  keyHistory(modifier->original.keyHistory),
-  buttonHistory(modifier->original.buttonHistory),
-  hasPressure(modifier->original.hasPressure),
-  hasTilt(modifier->original.hasTilt),
-  modifier(modifier),
+  deviceId(original.deviceId),
+  touchId(original.touchId),
+  keyHistory(original.keyHistory),
+  buttonHistory(original.buttonHistory),
+  hasPressure(original.hasPressure),
+  hasTilt(original.hasTilt),
+  original(&original),
+  timeOffset(timeOffset),
+  rootTimeOffset(original.rootTimeOffset + timeOffset),
   pointsRemoved(),
   pointsAdded(),
   fixedPointsAdded(),
@@ -64,15 +69,15 @@ TTrack::TTrack(const TTrackModifierP &modifier):
 
 const TTrack*
 TTrack::root() const
-  { return original() ? original()->root() : this; }
+  { return original ? original->root() : this; }
 
 int
 TTrack::level() const
-  { return original() ? original()->level() + 1 : 0; }
+  { return original ? original->level() + 1 : 0; }
 
 int
 TTrack::floorIndex(double index, double *outFrac) const {
-  int i = (int)floor(index + TConsts::epsilon);
+  int i = floorIndexNoClamp(index);
   if (i > size() - 1) {
     if (outFrac) *outFrac = 0.0;
     return size() - 1;
@@ -87,10 +92,13 @@ TTrack::floorIndex(double index, double *outFrac) const {
 
 void
 TTrack::push_back(const TTrackPoint &point, bool fixed) {
+  assert(m_points.empty() || !m_points.back().final);
   m_points.push_back(point);
-  if (size() > 1) {
+  TTrackPoint &p = m_points.back();
+  if (m_points.size() <= 1) {
+    p.length = 0;
+  } else {
     const TTrackPoint &prev = *(m_points.rbegin() + 1);
-    TTrackPoint &p = m_points.back();
 
     // fix originalIndex
     if (p.originalIndex < prev.originalIndex)
@@ -100,8 +108,7 @@ TTrack::push_back(const TTrackPoint &point, bool fixed) {
     p.time = std::max(p.time, prev.time + TToolTimer::step);
 
     // calculate length
-    TPointD d = p.position - prev.position;
-    p.length = prev.length + sqrt(d.x*d.x + d.y*d.y);
+    p.length = prev.length + tdistance(p.position, prev.position);
   }
   ++pointsAdded;
   if (fixed) fix_all();
@@ -130,13 +137,6 @@ TTrack::fix_points(int count) {
 }
 
 
-TTrackPoint
-TTrack::calcPoint(double index) const {
-  return modifier
-       ? modifier->calcPoint( originalIndexByIndex(index) )
-       : interpolateLinear(index);
-}
-
 TPointD
 TTrack::calcTangent(double index, double distance) const {
   double minDistance = 10.0*TConsts::epsilon;
@@ -150,14 +150,14 @@ TTrack::calcTangent(double index, double distance) const {
 
 double
 TTrack::rootIndexByIndex(double index) const {
-  return modifier
-       ? modifier->original.rootIndexByIndex( originalIndexByIndex(index) )
+  return original
+       ? original->rootIndexByIndex( originalIndexByIndex(index) )
        : index;
 }
 
 TTrackPoint
 TTrack::calcRootPoint(double index) const {
-  return modifier
-       ? modifier->original.calcRootPoint( originalIndexByIndex(index) )
+  return original
+       ? original->calcRootPoint( originalIndexByIndex(index) )
        : calcPoint(index);
 }
diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp
index a7cfaa1..7b8dab6 100644
--- a/toonz/sources/toonz/sceneviewer.cpp
+++ b/toonz/sources/toonz/sceneviewer.cpp
@@ -23,6 +23,7 @@
 #include "tools/toolhandle.h"
 #include "tools/toolcommandids.h"
 #include "tools/toolutils.h"
+#include "tools/assistant.h"
 
 // TnzQt includes
 #include "toonzqt/icongenerator.h"
@@ -1831,7 +1832,25 @@ void SceneViewer::drawOverlay() {
         !app->getCurrentObject()->isSpline())
       glScaled(m_dpiScale.x, m_dpiScale.y, 1);
     m_pixelSize = sqrt(tglGetPixelSize2()) * getDevPixRatio();
+    
+    // draw assistans and guidelines
+    m_toolHasAssistants = false;
+    unsigned int hints = tool->getToolHints();
+    if (hints & TTool::HintAssistantsAll) {
+      m_toolHasAssistants = TAssistant::scanAssistants(
+        tool,                                 // tool
+        &m_toolPos, 1,                        // pointer positions
+        nullptr,                              // out guidelines
+        true,                                 // draw
+        false,                                // enabled only
+        hints & TTool::HintAssistantsEnabled, // mark enabled
+        true,                                 // draw guidelines
+        nullptr );                            // skip image
+    }
+    
+    // draw tool
     tool->draw();
+    
     glPopMatrix();
     // Used (only in the T_RGBPicker tool) to notify and set the currentColor
     // outside the draw() methods:
@@ -3307,6 +3326,7 @@ TAffine SceneViewer::getNormalZoomScale() {
 //-----------------------------------------------------------------------------
 
 void SceneViewer::invalidateToolStatus() {
+  m_toolHasAssistants = false;
   TTool *tool = TApp::instance()->getCurrentTool()->getTool();
   if (tool) {
     m_toolDisableReason = tool->updateEnabled();
diff --git a/toonz/sources/toonz/sceneviewer.h b/toonz/sources/toonz/sceneviewer.h
index e93cfd3..dcb22d7 100644
--- a/toonz/sources/toonz/sceneviewer.h
+++ b/toonz/sources/toonz/sceneviewer.h
@@ -71,6 +71,8 @@ class SceneViewer final : public GLWidgetForHighDpi,
   double m_pressure;
   QPointF m_lastMousePos;
   QPointF m_pos;
+  TPointD m_toolPos;
+  bool m_toolHasAssistants = false;
   Qt::MouseButton m_mouseButton;
   bool m_foregroundDrawing;
   bool m_tabletEvent, m_tabletMove;
diff --git a/toonz/sources/toonz/sceneviewerevents.cpp b/toonz/sources/toonz/sceneviewerevents.cpp
index fd8d085..d0a46dd 100644
--- a/toonz/sources/toonz/sceneviewerevents.cpp
+++ b/toonz/sources/toonz/sceneviewerevents.cpp
@@ -656,6 +656,12 @@ void SceneViewer::onMove(const TMouseEvent &event) {
     }
     if (!cursorSet) setToolCursor(this, tool->getCursorId());
 
+    if ( m_toolHasAssistants
+      && (tool->getToolHints() & TTool::HintGuidelines)
+      && !areAlmostEqual(m_toolPos, pos) )
+        invalidateAll();
+    m_toolPos = pos;
+    
 #ifdef WITH_CANON
     if (StopMotion::instance()->m_canon->m_pickLiveViewZoom)
       setToolCursor(this, ToolCursor::ZoomCursor);
diff --git a/toonz/sources/toonzlib/strokegenerator.cpp b/toonz/sources/toonzlib/strokegenerator.cpp
index 9a5c9c7..cac636b 100644
--- a/toonz/sources/toonzlib/strokegenerator.cpp
+++ b/toonz/sources/toonzlib/strokegenerator.cpp
@@ -26,32 +26,61 @@ bool StrokeGenerator::isEmpty() const { return m_points.empty(); }
 
 //-------------------------------------------------------------------
 
-void StrokeGenerator::add(const TThickPoint &point, double pixelSize2) {
+bool StrokeGenerator::add(const TThickPoint &point, double pixelSize2) {
   if (m_points.empty()) {
     double x = point.x, y = point.y, d = point.thick + 3;
     m_points.push_back(point);
     TRectD rect(x - d, y - d, x + d, y + d);
-    m_modifiedRegion     = rect;
-    m_lastPointRect      = rect;
-    m_lastModifiedRegion = rect;
+    m_modifiedRegion     += rect;
+    m_lastPointRect      += rect;
+    m_lastModifiedRegion += rect;
     m_p0 = m_p1 = point;
-  } else {
-    TThickPoint lastPoint = m_points.back();
-    if (tdistance2(lastPoint, point) >= 4 * pixelSize2) {
-      m_points.push_back(point);
+    return true;
+  }
+
+  TThickPoint lastPoint = m_points.back();
+  if (tdistance2(lastPoint, point) >= 4 * pixelSize2) {
+    m_points.push_back(point);
+    double d = std::max(point.thick, lastPoint.thick) + 3;
+    TRectD rect(TRectD(lastPoint, point).enlarge(d));
+    m_modifiedRegion += rect;
+    m_lastModifiedRegion += rect;
+    m_lastPointRect = rect;
+    return true;
+  }
+  
+  m_points.back().thick = std::max(m_points.back().thick, point.thick);
+  return false;
+}
+
+//-------------------------------------------------------------------
+
+void StrokeGenerator::pop() {
+  if (!m_points.empty()) {
+    TRectD rect;
+    TThickPoint point = m_points.back();
+    m_points.pop_back();
+    if (!m_points.empty()) {
+      const TThickPoint &lastPoint = m_points.back();
       double d = std::max(point.thick, lastPoint.thick) + 3;
-      TRectD rect(TRectD(lastPoint, point).enlarge(d));
-      m_modifiedRegion += rect;
-      m_lastModifiedRegion += rect;
-      m_lastPointRect = rect;
+      rect = TRectD(lastPoint, point).enlarge(d);
     } else {
-      m_points.back().thick = std::max(m_points.back().thick, point.thick);
+      double x = point.x, y = point.y, d = point.thick + 3;
+      rect = TRectD(x - d, y - d, x + d, y + d);
     }
+    m_modifiedRegion += rect;
+    m_lastModifiedRegion += rect;
+    m_lastPointRect = rect;
   }
 }
 
 //-------------------------------------------------------------------
 
+void StrokeGenerator::setLoop(bool loop)
+  { m_loop = loop; }
+
+//-------------------------------------------------------------------
+
 void StrokeGenerator::filterPoints() {
   if (m_points.size() < 10) return;
 
@@ -130,10 +159,10 @@ void StrokeGenerator::drawFragments(int first, int last) {
       if (b.thick == 0) b.thick = 0.1;
     }
     // m_p0 = m_p1 = b;
-    v          = a.thick * normalize(rotate90(b - a));
+    v          = a.thick * normalizeOrZero(rotate90(b - a));
     m_p0       = a + v;
     m_p1       = a - v;
-    v          = b.thick * normalize(rotate90(b - a));
+    v          = b.thick * normalizeOrZero(rotate90(b - a));
     TPointD p0 = b + v;
     TPointD p1 = b - v;
     glBegin(GL_POLYGON);
@@ -161,11 +190,11 @@ void StrokeGenerator::drawFragments(int first, int last) {
       if (c.thick == 0) c.thick = 0.1;
     }
     if (i - 1 == 0) {
-      v    = a.thick * normalize(rotate90(b - a));
+      v    = a.thick * normalizeOrZero(rotate90(b - a));
       m_p0 = a + v;
       m_p1 = a - v;
     }
-    v          = b.thick * normalize(rotate90(c - a));
+    v          = b.thick * normalizeOrZero(rotate90(c - a));
     TPointD p0 = b + v;
     TPointD p1 = b - v;
     glBegin(GL_POLYGON);
@@ -185,7 +214,7 @@ void StrokeGenerator::drawFragments(int first, int last) {
   }
   if (last < 2) return;
   v = m_points[last].thick *
-      normalize(rotate90(m_points[last] - m_points[last - 1]));
+      normalizeOrZero(rotate90(m_points[last] - m_points[last - 1]));
   TPointD p0 = m_points[last] + v;
   TPointD p1 = m_points[last] - v;
   glBegin(GL_POLYGON);
@@ -274,7 +303,7 @@ TPointD StrokeGenerator::getFirstPoint() { return m_points[0]; }
 
 //-------------------------------------------------------------------
 
-TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints) const {
+TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints, bool useLoop) const {
   if (onlyLastPoints == 0 || onlyLastPoints > m_points.size())
     return TStroke::interpolate(m_points, error);
 
@@ -283,7 +312,9 @@ TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints) const {
       m_points.begin() + (m_points.size() - onlyLastPoints);
   copy(first, m_points.end(), lastPoints.begin());
 
-  return TStroke::interpolate(lastPoints, error);
+  TStroke *stroke = TStroke::interpolate(lastPoints, error);
+  if (useLoop) stroke->setSelfLoop(m_loop);
+  return stroke;
 }
 
 //-------------------------------------------------------------------