From 9cf8be95e8b2ec8650670c1b7be2e6bb131c5c4d Mon Sep 17 00:00:00 2001
From: Ivan Mahonin <bh@icystar.com>
Date: May 01 2023 07:52:44 +0000
Subject: #assistants: TModifierAssistants


---

diff --git a/toonz/sources/common/tmetaimage/tmetaimage.cpp b/toonz/sources/common/tmetaimage/tmetaimage.cpp
index 0c762b6..8a0c5b4 100644
--- a/toonz/sources/common/tmetaimage/tmetaimage.cpp
+++ b/toonz/sources/common/tmetaimage/tmetaimage.cpp
@@ -39,7 +39,7 @@ TMetaObject::setType(const TStringId &name) {
 
 void
 TMetaObject::onVariantChanged(const TVariant &value)
-  { if (m_handler) m_handler->onDataChanged(value); }
+  { if (m_handler) m_handler->dataChanged(value); }
 
 //---------------------------------------------------------
 
diff --git a/toonz/sources/common/tvariant.cpp b/toonz/sources/common/tvariant.cpp
index 2559103..2716709 100644
--- a/toonz/sources/common/tvariant.cpp
+++ b/toonz/sources/common/tvariant.cpp
@@ -123,7 +123,7 @@ const TVariant&
 TVariant::byPath(const TVariantPath &path, int begin, int end) const {
   if ((int)path.size() <= begin || begin >= end) return *this;
   if (isNone()) return blank();
-  return byPath(path[begin]).byPath(path, begin + 1, end);
+  return (*this)[path[begin]].byPath(path, begin + 1, end);
 }
 
 //---------------------------------------------------------
@@ -131,29 +131,41 @@ TVariant::byPath(const TVariantPath &path, int begin, int end) const {
 TVariant&
 TVariant::byPath(const TVariantPath &path, int begin, int end) {
   if ((int)path.size() <= begin || begin >= end) return *this;
-  return byPath(path[begin]).byPath(path, begin + 1, end);
+  return (*this)[path[begin]].byPath(path, begin + 1, end);
 }
 
 //---------------------------------------------------------
 
 int
-TVariant::getPathSize() const {
-  const TVariant *a = this->m_parent;
+TVariant::getParentPathSize(const TVariant &parent) const {
   int ac = 0;
-  while(a) a = a->m_parent, ++ac;
-  return ac;
+  for(const TVariant *a = this; a; a = a->parent(), ++ac)
+    if (a == &parent) return ac;
+  return -1;
 }
 
 //---------------------------------------------------------
 
-void
-TVariant::getParentPath(TVariantPath &outPath) const {
-  if (m_parent) {
-    m_parent->getParentPath(outPath);
-    outPath.push_back(parentPathEntry());
-  } else {
-    outPath.clear();
-  }
+bool
+TVariant::getParentPath(TVariantPath &outPath, const TVariant &parent) const {
+  if (!m_parent)
+    { outPath.clear(); return false; }
+  if (m_parent == this)
+    { outPath.clear(); return true; }
+  if (m_parent->getParentPath(outPath))
+    { outPath.push_back(parentPathEntry()); return true; }
+  return false;
+}
+
+//---------------------------------------------------------
+
+bool
+TVariant::getChildPathEntry(const TVariant &child, TVariantPathEntry &outEntry) const {
+  for(const TVariant *a = &child; a->parent(); a = a->parent())
+    if (a->parent() == this)
+      { outEntry = a->parentPathEntry(); return true; }
+  outEntry = TVariantPathEntry();
+  return false;
 }
 
 //---------------------------------------------------------
diff --git a/toonz/sources/include/tmetaimage.h b/toonz/sources/include/tmetaimage.h
index 8504441..75a876e 100644
--- a/toonz/sources/include/tmetaimage.h
+++ b/toonz/sources/include/tmetaimage.h
@@ -30,7 +30,6 @@ class TMetaObject;
 class TMetaObjectHandler;
 typedef TSmartPointerT<TMetaObject> TMetaObjectP;
 typedef TSmartRefT<TMetaObject> TMetaObjectR;
-typedef std::vector<TMetaObjectP> TMetaObjectList;
 typedef std::vector<TMetaObjectR> TMetaObjectRefList;
 
 //-------------------------------------------------------------------
@@ -98,6 +97,7 @@ public:
 class DVAPI TMetaObjectHandler {
 private:
   TMetaObject &m_object;
+  TAtomicVar m_fixindData;
 
 public:
   TMetaObjectHandler(TMetaObject &object):
@@ -113,8 +113,19 @@ public:
   inline TVariant& data()
     { return object().data(); }
 
+protected:
   virtual void onDataChanged(const TVariant &value) { }
-  virtual void fixData() { }
+  virtual void onFixData() { }
+
+public:
+  void dataChanged(const TVariant &value)
+    { if (m_fixindData != 0) onFixData(); }
+
+  void fixData() {
+    ++m_fixindData;
+    if (m_fixindData == 1) onFixData();
+    --m_fixindData;
+  }
 };
 
 //-------------------------------------------------------------------
diff --git a/toonz/sources/include/tools/assistant.h b/toonz/sources/include/tools/assistant.h
index a6b7192..625e66f 100644
--- a/toonz/sources/include/tools/assistant.h
+++ b/toonz/sources/include/tools/assistant.h
@@ -36,10 +36,12 @@
 
 class TToolViewer;
 class TAssistant;
+class TAssistantPoint;
 class TGuideline;
 
 typedef TSmartPointerT<TGuideline> TGuidelineP;
 typedef std::vector<TGuidelineP> TGuidelineList;
+typedef std::vector<TAssistantPoint> TAssistantPointList;
 
 //===================================================================
 
@@ -51,28 +53,89 @@ class DVAPI TGuideline final : public TSmartObject {
 public:
   virtual TTrackPoint transformPoint(const TTrackPoint &point) const
     { return point; }
-  virtual void draw(TToolViewer *viewer, bool active) const
+  virtual void draw(bool active) const
     { }
-  void draw(TToolViewer *viewer) const
-    { draw(viewer, false); }
+  void draw() const
+    { draw(false); }
 
-  double calcTrackWeight(const TTrack &track, const TAffine &affine) const;
-  static TGuidelineP findBest(const TGuidelineList &guidelines, const TTrack &track, const TAffine &affine);
+  double calcTrackWeight(const TTrack &track, const TAffine &toScreen, bool &outLongEnough) const;
+  static TGuidelineP findBest(const TGuidelineList &guidelines, const TTrack &track, const TAffine &toScreen, bool &outLongEnough);
 };
 
 
 //*****************************************************************************************
+//    TAssistantPoint definition
+//*****************************************************************************************
+
+class DVAPI TAssistantPoint {
+public:
+  enum Type {
+    Circle,
+    CircleFill,
+    CircleCross
+  };
+
+  Type type;
+  TPointD position;
+  bool selected;
+
+  inline explicit TAssistantPoint(Type type = Circle, const TPointD &position = TPointD()):
+    type(Circle), position(position), selected() { }
+};
+
+//*****************************************************************************************
 //    TAssistant definition
 //*****************************************************************************************
 
 class DVAPI TAssistant final : public TMetaObjectHandler {
+protected:
+  const TStringId m_idPoints;
+  const TStringId m_idX;
+  const TStringId m_idY;
+
+  TAssistantPointList m_points;
+
 public:
-  // TODO: handle data changes
+  TAssistant(TMetaObject &object);
+
+  static const TPointD& blank();
+
+  inline const TAssistantPointList& points() const
+    { return m_points; }
+  inline const int pointsCount() const
+    { return (int)m_points.size(); }
+
+  void fixPoints(int index, const TPointD &position);
+  void movePoint(int index, const TPointD &position);
+  void setPointSelection(int index, bool selected);
+
+  inline void selectPoint(int index)
+    { setPointSelection(index, true); }
+  inline void deselectPoint(int index)
+    { setPointSelection(index, false); }
+  inline void selectAll()
+    { for(int i = 0; i < pointsCount(); ++i) setPointSelection(i, false); }
+  inline void deselectAll()
+    { for(int i = 0; i < pointsCount(); ++i) setPointSelection(i, false); }
+
+protected:
+  //! called when part of variant data changed
+  void onDataChanged(const TVariant &value) override;
+  //! load object data from variant
+  virtual void onAllDataChanged();
+  //! fix positions of all points
+  virtual void onFixPoints();
+  //! try to move point
+  virtual void onMovePoint(int index, const TPointD &position);
+  //! save object data to variant
+  virtual void onFixData();
+
+  void drawPoint(const TAssistantPoint &point, double pixelSize) const;
 
-  virtual void getGuidelines(const TPointD &position, TGuidelineList &outGuidelines) { }
-  virtual void draw(TToolViewer *viewer) { }
-  virtual void drawEdit(TToolViewer *viewer, int currentPointIndex) { }
+public:
+  virtual void getGuidelines(const TPointD &position, const TAffine &toTool, TGuidelineList &outGuidelines) const;
+  virtual void draw(TToolViewer *viewer) const;
+  virtual void drawEdit(TToolViewer *viewer) const;
 };
 
-
 #endif
diff --git a/toonz/sources/include/tools/inputmanager.h b/toonz/sources/include/tools/inputmanager.h
index 4696042..5b6d677 100644
--- a/toonz/sources/include/tools/inputmanager.h
+++ b/toonz/sources/include/tools/inputmanager.h
@@ -10,6 +10,7 @@
 
 // TnzCore includes
 #include <tcommon.h>
+#include <tgeometry.h>
 #include <tsmartpointer.h>
 
 // Qt includes
@@ -36,6 +37,7 @@
 
 //  Forward declarations
 
+class TTool;
 class TInputModifier;
 class TInputManager;
 
@@ -178,8 +180,10 @@ public:
   virtual TRectD calcDrawBoundsTrack(const TTrack &track) { return TRectD(); }
   virtual TRectD calcDrawBounds(const TTrackList &tracks, const THoverList &hovers);
 
-  virtual void drawHover(const TPointD &hover) { }
   virtual void drawTrack(const TTrack &track) { }
+  virtual void drawHover(const TPointD &hover) { }
+  virtual void drawTracks(const TTrackList &tracks);
+  virtual void drawHovers(const THoverList &hovers);
   virtual void draw(const TTrackList &tracks, const THoverList &hovers);
 
   virtual void deactivate() { }
@@ -240,6 +244,9 @@ public:
   virtual void inputPaintPop(int count) { }
   
   virtual void inputInvalidateRect(const TRectD &bounds) { }
+  
+  virtual TAffine toWorld() { return TAffine(); };
+  virtual TTool* getTool() { return nullptr; };
 };
 
 
diff --git a/toonz/sources/include/tools/modifiers/modifierassistants.h b/toonz/sources/include/tools/modifiers/modifierassistants.h
new file mode 100644
index 0000000..e0f8a76
--- /dev/null
+++ b/toonz/sources/include/tools/modifiers/modifierassistants.h
@@ -0,0 +1,57 @@
+#pragma once
+
+#ifndef MODIFIERASSISTANTS_INCLUDED
+#define MODIFIERASSISTANTS_INCLUDED
+
+// TnzTools includes
+#include <tools/assistant.h>
+#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
+
+
+//===================================================================
+
+//*****************************************************************************************
+//    TModifierAssistants definition
+//*****************************************************************************************
+
+class TModifierAssistants: public TInputModifier {
+public:
+  class Modifier: public TTrackModifier {
+  public:
+    bool initialized;
+    TInputSavePoint::Holder savePoint;
+    TGuidelineList guidelines;
+
+    Modifier(TTrackHandler &handler);
+    TTrackPoint calcPoint(double originalIndex) override;
+  };
+
+public:
+  const double sensitiveLength;
+
+  TModifierAssistants();
+
+  void findGuidelines(
+    const TPointD &position,
+    TGuidelineList &outGuidelines ) const;
+
+  void modifyTrack(
+    const TTrack &track,
+    const TInputSavePoint::Holder &savePoint,
+    TTrackList &outTracks ) override;
+  void drawHover(const TPointD &hover) override;
+  void drawTrack(const TTrack &track) override;
+};
+
+
+#endif
diff --git a/toonz/sources/include/tvariant.h b/toonz/sources/include/tvariant.h
index 4934276..224e083 100644
--- a/toonz/sources/include/tvariant.h
+++ b/toonz/sources/include/tvariant.h
@@ -68,6 +68,11 @@ public:
 
 class DVAPI TVariantPath: public std::vector<TVariantPathEntry> {
 public:
+  inline TVariantPath& append(const TVariantPathEntry &x)
+    { push_back(x); return *this; }
+  inline TVariantPath& append(const TVariantPath &x)
+    { insert(end(), x.begin(), x.end()); return *this; }
+
   inline bool isSubPathOf(const TVariantPath &other) const
     { return compare(*this, 0, other, 0, (int)size()); }
   inline bool isBasePathOf(const TVariantPath &other) const
@@ -289,12 +294,12 @@ public:
   // path methods
   const TVariant& byPath(const TVariantPath &path, int begin, int end) const;
   TVariant& byPath(const TVariantPath &path, int begin, int end);
-  inline const TVariant& byPath(const TVariantPathEntry &entry) const {
+  inline const TVariant& operator[] (const TVariantPathEntry &entry) const {
     return entry.isIndex()
          ? (m_type == List ? (*this)[entry.index()] : blank())
          : (m_type == Map  ? (*this)[entry.field()] : blank());
   }
-  inline TVariant& byPath(const TVariantPathEntry &entry)
+  inline TVariant& operator[] (const TVariantPathEntry &entry)
     { return entry.isIndex() ? (*this)[entry.index()] : (*this)[entry.field()]; }
   inline const TVariant& byPath(const TVariantPath &path, int begin = 0) const
     { return byPath(path, begin, (int)path.size()); }
@@ -325,15 +330,23 @@ public:
   inline bool isRoot() const
     { return this == m_root; }
 
-  int getPathSize() const;
-  void getParentPath(TVariantPath &outPath) const;
+  int getParentPathSize(const TVariant &parent) const;
+  bool getParentPath(TVariantPath &outPath, const TVariant &parent) const;
   inline TVariantPathEntry parentPathEntry() const {
     return !m_parent               ? TVariantPathEntry()
          : m_parent->m_type == Map ? TVariantPathEntry(m_parentField)
          : TVariantPathEntry( this - &m_parent->m_list.front() );
   }
-  inline TVariantPath getParentPath() const
-    { TVariantPath path; getParentPath(path); return path; }
+  inline int getParentPathSize() const
+    { return getParentPathSize(*m_root); }
+  inline bool getParentPath(TVariantPath &outPath) const
+    { return getParentPath(outPath, *m_root); }
+
+  inline int getChildPathSize(const TVariant &child) const
+    { return child.getParentPathSize(*this); }
+  inline bool getChildPath(TVariantPath &outPath, const TVariant &child) const
+    { return child.getParentPath(outPath, *this); }
+  bool getChildPathEntry(const TVariant &child, TVariantPathEntry &outEntry) const;
 
   bool isChildOf(const TVariant &other) const;
   bool isChildOrEqual(const TVariant &other) const;
diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt
index d06bc2d..b272a75 100644
--- a/toonz/sources/tnztools/CMakeLists.txt
+++ b/toonz/sources/tnztools/CMakeLists.txt
@@ -48,10 +48,11 @@ set(HEADERS
     ../include/tools/inputstate.h
     ../include/tools/track.h
     ../include/tools/inputmanager.h
+    ../include/tools/assistant.h
     ../include/tools/modifiers/modifiertest.h
     ../include/tools/modifiers/modifiertangents.h
     ../include/tools/modifiers/modifiersegmentation.h
-    ../include/tools/assistant.h
+    ../include/tools/modifiers/modifierassistants.h
 )
 
 set(SOURCES
@@ -119,10 +120,11 @@ set(SOURCES
     inputstate.cpp
     track.cpp
     inputmanager.cpp
-    modifiertest.cpp
-    modifiertangents.cpp
-    modifiersegmentation.cpp
     assistant.cpp
+    modifiers/modifiertest.cpp
+    modifiers/modifiertangents.cpp
+    modifiers/modifiersegmentation.cpp
+    modifiers/modifierassistants.cpp
 )
 
 set(RESOURCES tnztools.qrc)
diff --git a/toonz/sources/tnztools/assistant.cpp b/toonz/sources/tnztools/assistant.cpp
index 80510cd..eda9f1e 100644
--- a/toonz/sources/tnztools/assistant.cpp
+++ b/toonz/sources/tnztools/assistant.cpp
@@ -1,6 +1,8 @@
 
 #include <tools/assistant.h>
 
+#include <tgl.h>
+
 #include <limits>
 
 
@@ -9,22 +11,23 @@
 //************************************************************************
 
 double
-TGuideline::calcTrackWeight(const TTrack &track, const TAffine &affine) const {
-  if (track.empty() < 1)
+TGuideline::calcTrackWeight(const TTrack &track, const TAffine &toScreen, bool &outLongEnough) const {
+  outLongEnough = false;
+  if (track.size() < 2)
     return std::numeric_limits<double>::infinity();
 
   const double snapLenght = 20.0;
   const double snapScale = 1.0;
-  const double maxLenght = 20.0*snapLenght*snapScale;
+  const double maxLength = 20.0*snapLenght*snapScale;
 
   double sumWeight = 0.0;
   double sumLength = 0.0;
   double sumDeviation = 0.0;
 
-  TPointD prev = affine*track[0].position;
+  TPointD prev = toScreen*track[0].position;
   for(int i = 0; i < track.size(); ++i) {
     const TTrackPoint &tp = track[i];
-    TPointD p = affine*tp.position;
+    TPointD p = toScreen*tp.position;
     double length = tdistance(p, prev);
     sumLength += length;
 
@@ -34,24 +37,28 @@ TGuideline::calcTrackWeight(const TTrack &track, const TAffine &affine) const {
       sumWeight += weight;
 
       TTrackPoint ntp = transformPoint(tp);
-      double deviation = tdistance(affine*ntp.position, p);
+      double deviation = tdistance(toScreen*ntp.position, p);
       sumDeviation += weight*deviation;
     }
     prev = p;
+
+    if (sumLength >= maxLength)
+      { outLongEnough = true; break; }
   }
-  if (sumWeight < TTrack::epsilon)
-    return std::numeric_limits<double>::infinity();
-  return sumDeviation/sumWeight;
+  return sumWeight > TTrack::epsilon
+       ? sumDeviation/sumWeight
+       : std::numeric_limits<double>::infinity();
 }
 
 //---------------------------------------------------------------------------------------------------
 
 TGuidelineP
-TGuideline::findBest(const TGuidelineList &guidelines, const TTrack &track, const TAffine &affine) {
+TGuideline::findBest(const TGuidelineList &guidelines, const TTrack &track, const TAffine &toScreen, bool &outLongEnough) {
+  outLongEnough = true;
   double bestWeight = 0.0;
   TGuidelineP best;
   for(TGuidelineList::const_iterator i = guidelines.begin(); i != guidelines.end(); ++i) {
-    double weight = (*i)->calcTrackWeight(track, affine);
+    double weight = (*i)->calcTrackWeight(track, toScreen, outLongEnough);
     if (!best || weight < bestWeight)
       { bestWeight = weight; best = *i; }
   }
@@ -63,4 +70,165 @@ TGuideline::findBest(const TGuidelineList &guidelines, const TTrack &track, cons
 //    TAssistant implementation
 //************************************************************************
 
-// TODO:
+TAssistant::TAssistant(TMetaObject &object):
+  TMetaObjectHandler(object),
+  m_idPoints("points"),
+  m_idX("x"),
+  m_idY("y")
+{ }
+
+//---------------------------------------------------------------------------------------------------
+
+const TPointD&
+TAssistant::blank() {
+  static TPointD point;
+  return point;
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::fixPoints(int index, const TPointD &position)
+  { onFixPoints(); }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::movePoint(int index, const TPointD &position)
+  { if (index >= 0 && index < (int)m_points.size()) onMovePoint(index, position); }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::setPointSelection(int index, bool selected) {
+  if (index >= 0 && index < pointsCount())
+    m_points[index].selected = selected;
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::onDataChanged(const TVariant &value) {
+  const TVariant& pointsData = data()[m_idPoints];
+  TVariantPathEntry entry;
+
+  if (&value == &data() || &value == &pointsData)
+    onAllDataChanged();
+  else
+  if (pointsData.getChildPathEntry(value, entry) && entry.isIndex()) {
+    const TVariant& pointData = pointsData[entry];
+    TPointD position = TPointD(
+      pointData[m_idX].getDouble(),
+      pointData[m_idY].getDouble() );
+    movePoint(entry.index(), position);
+  }
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::onAllDataChanged() {
+  const TVariant& pointsData = data()[m_idPoints];
+  for(int i = 0; i < pointsCount(); ++i) {
+    const TVariant& pointData = pointsData[i];
+    m_points[i].position = TPointD(
+      pointData[m_idX].getDouble(),
+      pointData[m_idY].getDouble() );
+  }
+}
+
+//---------------------------------------------------------------------------------------------------
+
+//! fix positions of all points
+void
+TAssistant::onFixPoints()
+  { }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::onMovePoint(int index, const TPointD &position)
+  { m_points[index].position = position; }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::onFixData() {
+  TVariant& pointsData = data()[m_idPoints];
+  for(int i = 0; i < pointsCount(); ++i) {
+    TVariant& pointData = pointsData[i];
+    pointData[m_idX].setDouble( m_points[i].position.x );
+    pointData[m_idY].setDouble( m_points[i].position.y );
+  }
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::drawPoint(const TAssistantPoint &point, double pixelSize) const {
+  double radius = 10.0;
+  double crossSize = 1.2*radius;
+
+  double colorBlack[4] = { 0.0, 0.0, 0.0, 0.5 };
+  double colorGray[4]  = { 0.5, 0.5, 0.5, 0.5 };
+  double colorWhite[4] = { 1.0, 1.0, 1.0, 0.5 };
+
+  if (point.selected) {
+    colorBlack[2] = 1.0;
+    colorGray[2] = 1.0;
+  }
+
+  glPushAttrib(GL_ALL_ATTRIB_BITS);
+
+  tglEnableBlending();
+  tglEnableLineSmooth(true, 0.5);
+
+  if (point.type == TAssistantPoint::CircleFill) {
+    glColor4dv(colorGray);
+    tglDrawDisk(point.position, radius*pixelSize);
+  }
+
+  if (point.type == TAssistantPoint::CircleCross) {
+    TPointD dp(0.5*pixelSize, 0.5*pixelSize);
+    TPointD dx(pixelSize*crossSize, 0.0);
+    TPointD dy(0.0, pixelSize*crossSize);
+
+    glColor4dv(colorWhite);
+    tglDrawSegment(point.position - dx + dp, point.position + dx + dp);
+    tglDrawSegment(point.position - dy + dp, point.position + dy + dp);
+    glColor4dv(colorBlack);
+    tglDrawSegment(point.position - dx - dp, point.position + dx - dp);
+    tglDrawSegment(point.position - dy - dp, point.position + dy - dp);
+  }
+
+  glColor4dv(colorWhite);
+  tglDrawCircle(point.position, (radius + 0.5)*pixelSize);
+  glColor4dv(colorBlack);
+  tglDrawCircle(point.position, (radius - 0.5)*pixelSize);
+
+  glPopAttrib();
+}
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::getGuidelines(const TPointD &position, const TAffine &toTool, TGuidelineList &outGuidelines) const
+  { }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::draw(TToolViewer *viewer) const
+  { }
+
+//---------------------------------------------------------------------------------------------------
+
+void
+TAssistant::drawEdit(TToolViewer *viewer) const {
+  // paint all points
+  double pixelSize = sqrt(tglGetPixelSize2());
+  for(int i = 0; i < pointsCount(); ++i)
+    drawPoint(m_points[i], pixelSize);
+}
+
+//---------------------------------------------------------------------------------------------------
diff --git a/toonz/sources/tnztools/inputmanager.cpp b/toonz/sources/tnztools/inputmanager.cpp
index 0cb6bc7..b65a2f5 100644
--- a/toonz/sources/tnztools/inputmanager.cpp
+++ b/toonz/sources/tnztools/inputmanager.cpp
@@ -98,14 +98,26 @@ TInputModifier::calcDrawBounds(const TTrackList &tracks, const THoverList &hover
 
 
 void
-TInputModifier::draw(const TTrackList &tracks, const THoverList &hovers) {
+TInputModifier::drawTracks(const TTrackList &tracks) {
   for(TTrackList::const_iterator i = tracks.begin(); i != tracks.end(); ++i)
     drawTrack(**i);
-  for(THoverList::const_iterator i = hovers.begin(); i != hovers.end(); ++i)
+}
+
+
+void
+TInputModifier::drawHovers(const std::vector<TPointD> &hovers) {
+  for(std::vector<TPointD>::const_iterator i = hovers.begin(); i != hovers.end(); ++i)
     drawHover(*i);
 }
 
 
+void
+TInputModifier::draw(const TTrackList &tracks, const std::vector<TPointD> &hovers) {
+  drawTracks(tracks);
+  drawHovers(hovers);
+}
+
+
 //*****************************************************************************************
 //    TInputHandler implementation
 //*****************************************************************************************
diff --git a/toonz/sources/tnztools/modifiers/modifierassistants.cpp b/toonz/sources/tnztools/modifiers/modifierassistants.cpp
new file mode 100644
index 0000000..acd24da
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifierassistants.cpp
@@ -0,0 +1,159 @@
+
+
+#include <tools/modifiers/modifierassistants.h>
+
+// TnzTools includes
+#include <tools/tool.h>
+
+// TnzLib includes
+#include <toonz/tapplication.h>
+#include <toonz/txshlevelhandle.h>
+#include <toonz/txsheethandle.h>
+#include <toonz/txsheet.h>
+#include <toonz/tframehandle.h>
+
+// TnzCore includes
+#include <tmetaimage.h>
+
+
+//*****************************************************************************************
+//    TModifierAssistants::Modifier implementation
+//*****************************************************************************************
+
+
+TModifierAssistants::Modifier::Modifier(TTrackHandler &handler):
+  TTrackModifier(handler),
+  initialized()
+{ }
+
+
+TTrackPoint
+TModifierAssistants::Modifier::calcPoint(double originalIndex) {
+  TTrackPoint p = TTrackModifier::calcPoint(originalIndex);
+  return guidelines.empty() > 0 ? p : guidelines.front()->transformPoint(p);
+}
+
+
+//*****************************************************************************************
+//    TModifierAssistants implementation
+//*****************************************************************************************
+
+
+TModifierAssistants::TModifierAssistants():
+  sensitiveLength(50.0) { }
+
+
+void
+TModifierAssistants::findGuidelines(const TPointD &position, TGuidelineList &outGuidelines) const {
+  if (TInputManager *manager = getManager())
+  if (TInputHandler *handler = manager->getHandler())
+  if (TTool *tool = handler->getTool())
+  if (TToolViewer *viewer = tool->getViewer())
+  if (TApplication *application = tool->getApplication())
+  if (TFrameHandle *frameHandle = application->getCurrentFrame())
+  if (TXsheetHandle *XsheetHandle = application->getCurrentXsheet())
+  if (TXsheet *Xsheet = XsheetHandle->getXsheet())
+  {
+    int frame = frameHandle->getFrame();
+    int count = Xsheet->getColumnCount();
+    TAffine worldToTrack = viewer->getViewMatrix();
+
+    for(int i = 0; i < count; ++i)
+      if (TImageP image = Xsheet->getCell(frame, i).getImage(false))
+      if (image->getType() == TImage::META)
+      if (TMetaImage *metaImage = dynamic_cast<TMetaImage*>(image.getPointer()))
+      {
+        TAffine imageToTrack = tool->getColumnMatrix(i)
+                             * worldToTrack;
+        TMetaImage::Reader reader(*metaImage);
+
+        for(TMetaObjectRefList::const_iterator i = reader->begin(); i != reader->end(); ++i)
+          if (*i)
+          if (const TAssistant *assistant = (*i)->getHandler<TAssistant>())
+            assistant->getGuidelines(position, imageToTrack, outGuidelines);
+      }
+  }
+}
+
+
+void
+TModifierAssistants::modifyTrack(
+  const TTrack &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  if (!track.handler) {
+    track.handler = new TTrackHandler(track);
+    Modifier *modifier = new Modifier(*track.handler);
+    findGuidelines(track[0].position, modifier->guidelines);
+
+    track.handler->tracks.push_back(new TTrack(modifier));
+
+    if ((int)modifier->guidelines.size() > 1) {
+      modifier->savePoint = savePoint;
+      outTracks.push_back(track.handler->tracks.front());
+      return;
+    }
+  }
+
+  outTracks.push_back(track.handler->tracks.front());
+  TTrack &subTrack = *track.handler->tracks.front();
+  if (!track.changed()) return;
+  if (Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer())) {
+    // remove points
+    int start = track.size() - track.pointsAdded;
+    if (start < 0) start = 0;
+
+    if ((int)modifier->guidelines.size() > 1 && modifier->savePoint.available()) {
+      // select guideline
+      bool longEnough = false;
+      if (TInputManager *manager = getManager()) {
+        if (TToolViewer *viewer = manager->getViewer()) {
+          TAffine trackToScreen = manager->toolToWorld()
+                                * viewer->get3dViewMatrix().get2d().inv();
+          TGuidelineP guideline = TGuideline::findBest(modifier->guidelines, track, trackToScreen, longEnough);
+          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());
+                start = 0;
+                break;
+              }
+        }
+      }
+      modifier->savePoint.setLock(!longEnough);
+    } else {
+      modifier->savePoint.reset();
+    }
+
+    // add points
+    subTrack.truncate(start);
+    for(int i = start; i < track.size(); ++i)
+      subTrack.push_back( modifier->calcPoint(i) );
+  }
+  track.resetChanges();
+}
+
+
+void
+TModifierAssistants::drawHover(const TPointD &hover) {
+  TGuidelineList guidelines;
+  findGuidelines(hover, guidelines);
+  for(TGuidelineList::const_iterator i = guidelines.begin(); i != guidelines.end(); ++i)
+    (*i)->draw();
+}
+
+
+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();
+    }
+  }
+}
diff --git a/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp b/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp
new file mode 100644
index 0000000..dc00b39
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifiersegmentation.cpp
@@ -0,0 +1,73 @@
+
+
+#include <tools/modifiers/modifiersegmentation.h>
+#include <algorithm>
+
+
+//*****************************************************************************************
+//    TModifierSegmentation implementation
+//*****************************************************************************************
+
+
+TModifierSegmentation::TModifierSegmentation(double precision):
+  precision(std::max(TTrack::epsilon, precision)),
+  precisionSqr(this->precision * this->precision)
+{ }
+
+
+void
+TModifierSegmentation::addSegments(
+  TTrack &track,
+  const TTrackPoint &p0,
+  const TTrackPoint &p1,
+  int level)
+{
+  static const int maxRecursion = 10;
+  TPointD d = p1.position - p0.position;
+
+  if (level >= maxRecursion || d.x*d.x + d.y*d.y <= precisionSqr) {
+    track.push_back(p1);
+    return;
+  }
+
+  TTrackPoint p = track.modifier->calcPoint(0.5*(p0.originalIndex + p1.originalIndex));
+  addSegments(track, p0, p, level + 1);
+  addSegments(track, p, p1, level + 1);
+}
+
+
+void
+TModifierSegmentation::modifyTrack(
+  const TTrack &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  if (!track.handler) {
+    track.handler = new TTrackHandler(track);
+    track.handler->tracks.push_back(
+      new TTrack(
+        new TTrackModifier(*track.handler) ));
+  }
+
+  if (!track.changed() || track.handler->tracks.empty())
+    return;
+
+  TTrack &subTrack = *track.handler->tracks.front();
+  outTracks.push_back(track.handler->tracks.front());
+
+  // remove points
+  int start = track.size() - track.pointsAdded;
+  if (start < 0) start = 0;
+  int subStart = subTrack.ceilIndex(subTrack.indexByOriginalIndex(start-1)) + 1;
+  subTrack.truncate(subStart);
+
+  // add points
+  TTrackPoint p0 = subTrack.modifier->calcPoint(start - 1);
+  for(int i = start; i < track.size(); ++i) {
+    TTrackPoint p1 = subTrack.modifier->calcPoint(i);
+    addSegments(subTrack, p0, p1);
+    p0 = p1;
+  }
+
+  track.resetChanges();
+}
diff --git a/toonz/sources/tnztools/modifiers/modifiertangents.cpp b/toonz/sources/tnztools/modifiers/modifiertangents.cpp
new file mode 100644
index 0000000..1dab9cc
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifiertangents.cpp
@@ -0,0 +1,118 @@
+
+
+#include <tools/modifiers/modifiertangents.h>
+
+
+//*****************************************************************************************
+//    TModifierTangents::Modifier implementation
+//*****************************************************************************************
+
+
+TTrackPoint
+TModifierTangents::Modifier::calcPoint(double originalIndex) {
+  double frac;
+  int i0 = original.floorIndex(originalIndex, &frac);
+  int i1 = original.ceilIndex(originalIndex);
+  TTrackPoint p = i0 < 0 ? TTrackPoint()
+    : TTrack::interpolationSpline(
+        original[i0],
+        original[i1],
+        i0 < (int)tangents.size() ? tangents[i0] : TTrackTangent(),
+        i1 < (int)tangents.size() ? tangents[i1] : TTrackTangent(),
+        frac );
+  p.originalIndex = originalIndex;
+  return p;
+}
+
+
+//*****************************************************************************************
+//    TModifierTangents implementation
+//*****************************************************************************************
+
+
+void
+TModifierTangents::modifyTrack(
+  const TTrack &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  if (!track.handler) {
+    track.handler = new TTrackHandler(track);
+    track.handler->tracks.push_back(
+      new TTrack(
+        new Modifier(*track.handler) ));
+  }
+
+  if (track.handler->tracks.empty())
+    return;
+
+  TTrack &subTrack = *track.handler->tracks.front();
+  Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer());
+  if (!modifier)
+    return;
+
+  outTracks.push_back(track.handler->tracks.front());
+
+  if ( !track.changed()
+    && track.size() == subTrack.size()
+    && track.size() == (int)modifier->tangents.size() )
+      return;
+
+  if (!track.changed() && subTrack.size() == track.size() - 1) {
+    // add temporary point
+    modifier->tangents.push_back(TTrackTangent());
+    subTrack.push_back(track.back());
+  } else {
+    // apply permanent changes
+
+    // remove points
+    int start = track.size() - track.pointsAdded;
+    if (start < 0) start = 0;
+    if (start > 1) --start;
+    subTrack.truncate(start);
+    TTrackTangent lastTangent =
+        start < (int)modifier->tangents.size() ? modifier->tangents[start]
+      : modifier->tangents.empty() ? TTrackTangent()
+      : modifier->tangents.back();
+    modifier->tangents.resize(start, lastTangent);
+
+    // add first point
+    int index = start;
+    if (index == 0) {
+      modifier->tangents.push_back(TTrackTangent());
+      subTrack.push_back(track.back());
+      ++index;
+    }
+
+    // add points with tangents
+    while(index < track.size() - 1) {
+      const TTrackPoint &p0 = track[index-1];
+      const TTrackPoint &p1 = track[index];
+      const TTrackPoint &p2 = track[index+1];
+      double dt = p2.time - p0.time;
+      double k = dt > TTrack::epsilon ? (p1.time - p0.time)/dt : 0.5;
+      TTrackTangent tangent(
+        (p2.position - p0.position)*k,
+        (p2.pressure - p0.pressure)*k,
+        (p2.tilt - p0.tilt)*k );
+      modifier->tangents.push_back(tangent);
+      subTrack.push_back(p1);
+      ++index;
+    }
+
+    track.resetChanges();
+
+    // release previous key point
+    modifier->savePoint.reset();
+
+    if (track.finished()) {
+      // finish
+      modifier->tangents.push_back(TTrackTangent());
+      subTrack.push_back(track.back());
+    } else {
+      // save key point
+      modifier->savePoint = savePoint;
+    }
+  }
+}
+
diff --git a/toonz/sources/tnztools/modifiers/modifiertest.cpp b/toonz/sources/tnztools/modifiers/modifiertest.cpp
new file mode 100644
index 0000000..75090d6
--- /dev/null
+++ b/toonz/sources/tnztools/modifiers/modifiertest.cpp
@@ -0,0 +1,153 @@
+
+
+#include <tools/modifiers/modifiertest.h>
+
+// std includes
+#include <cmath>
+
+//*****************************************************************************************
+//    TModifierTest::Modifier 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 > 2.0) {
+    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 = TPointD(1.0, 0.0); // original.calcTangent(originalIndex, fabs(2.0*this->radius/speed));
+      p.position.x += radius*(c*tangent.x - s*tangent.y);
+      p.position.y += radius*(s*tangent.x + c*tangent.y);
+      p.pressure   *= 0.5*(1.0 + c);
+    }
+  } else {
+    p.pressure = 0.0;
+  }
+
+  return p;
+}
+
+
+//*****************************************************************************************
+//    TModifierTest implementation
+//*****************************************************************************************
+
+
+TModifierTest::TModifierTest():
+  count(1),
+  radius(40.0)
+{ }
+
+
+void
+TModifierTest::modifyTrack(
+  const TTrack &track,
+  const TInputSavePoint::Holder &savePoint,
+  TTrackList &outTracks )
+{
+  const double segmentSize = M_PI/180.0*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,
+              2.0 )));
+    }
+  }
+
+  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
+  if (!handler) {
+    TInputModifier::modifyTrack(track, savePoint, outTracks);
+    return;
+  }
+
+  outTracks.insert(
+    outTracks.end(),
+    track.handler->tracks.begin(),
+    track.handler->tracks.end() );
+  if (!track.changed())
+    return;
+
+  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);
+
+  // 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 > TTrack::epsilon
+                ? dl/(2.0*radius*track[i].pressure) : 0.0;
+      handler->angles.push_back(handler->angles[i-1] + 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;
+
+    // remove points
+    int subStart = subTrack.floorIndex(subTrack.indexByOriginalIndex(start));
+    if (subStart < 0) subStart = 0;
+    if (subStart < subTrack.size() && subTrack[subStart].originalIndex + TTrack::epsilon < start)
+      ++subStart;
+    subTrack.truncate(subStart);
+
+    // add points
+    for(int i = start; i < track.size(); ++i) {
+      if (i > 0) {
+        double prevAngle = handler->angles[i-1];
+        double nextAngle = handler->angles[i];
+        if (fabs(nextAngle - prevAngle) > 1.5*segmentSize) {
+          double step = segmentSize/fabs(nextAngle - prevAngle);
+          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) );
+        }
+      }
+      subTrack.push_back( subTrack.modifier->calcPoint(i) );
+    }
+  }
+
+  track.resetChanges();
+}
+
diff --git a/toonz/sources/tnztools/modifiersegmentation.cpp b/toonz/sources/tnztools/modifiersegmentation.cpp
deleted file mode 100644
index dc00b39..0000000
--- a/toonz/sources/tnztools/modifiersegmentation.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-#include <tools/modifiers/modifiersegmentation.h>
-#include <algorithm>
-
-
-//*****************************************************************************************
-//    TModifierSegmentation implementation
-//*****************************************************************************************
-
-
-TModifierSegmentation::TModifierSegmentation(double precision):
-  precision(std::max(TTrack::epsilon, precision)),
-  precisionSqr(this->precision * this->precision)
-{ }
-
-
-void
-TModifierSegmentation::addSegments(
-  TTrack &track,
-  const TTrackPoint &p0,
-  const TTrackPoint &p1,
-  int level)
-{
-  static const int maxRecursion = 10;
-  TPointD d = p1.position - p0.position;
-
-  if (level >= maxRecursion || d.x*d.x + d.y*d.y <= precisionSqr) {
-    track.push_back(p1);
-    return;
-  }
-
-  TTrackPoint p = track.modifier->calcPoint(0.5*(p0.originalIndex + p1.originalIndex));
-  addSegments(track, p0, p, level + 1);
-  addSegments(track, p, p1, level + 1);
-}
-
-
-void
-TModifierSegmentation::modifyTrack(
-  const TTrack &track,
-  const TInputSavePoint::Holder &savePoint,
-  TTrackList &outTracks )
-{
-  if (!track.handler) {
-    track.handler = new TTrackHandler(track);
-    track.handler->tracks.push_back(
-      new TTrack(
-        new TTrackModifier(*track.handler) ));
-  }
-
-  if (!track.changed() || track.handler->tracks.empty())
-    return;
-
-  TTrack &subTrack = *track.handler->tracks.front();
-  outTracks.push_back(track.handler->tracks.front());
-
-  // remove points
-  int start = track.size() - track.pointsAdded;
-  if (start < 0) start = 0;
-  int subStart = subTrack.ceilIndex(subTrack.indexByOriginalIndex(start-1)) + 1;
-  subTrack.truncate(subStart);
-
-  // add points
-  TTrackPoint p0 = subTrack.modifier->calcPoint(start - 1);
-  for(int i = start; i < track.size(); ++i) {
-    TTrackPoint p1 = subTrack.modifier->calcPoint(i);
-    addSegments(subTrack, p0, p1);
-    p0 = p1;
-  }
-
-  track.resetChanges();
-}
diff --git a/toonz/sources/tnztools/modifiertangents.cpp b/toonz/sources/tnztools/modifiertangents.cpp
deleted file mode 100644
index 1dab9cc..0000000
--- a/toonz/sources/tnztools/modifiertangents.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-#include <tools/modifiers/modifiertangents.h>
-
-
-//*****************************************************************************************
-//    TModifierTangents::Modifier implementation
-//*****************************************************************************************
-
-
-TTrackPoint
-TModifierTangents::Modifier::calcPoint(double originalIndex) {
-  double frac;
-  int i0 = original.floorIndex(originalIndex, &frac);
-  int i1 = original.ceilIndex(originalIndex);
-  TTrackPoint p = i0 < 0 ? TTrackPoint()
-    : TTrack::interpolationSpline(
-        original[i0],
-        original[i1],
-        i0 < (int)tangents.size() ? tangents[i0] : TTrackTangent(),
-        i1 < (int)tangents.size() ? tangents[i1] : TTrackTangent(),
-        frac );
-  p.originalIndex = originalIndex;
-  return p;
-}
-
-
-//*****************************************************************************************
-//    TModifierTangents implementation
-//*****************************************************************************************
-
-
-void
-TModifierTangents::modifyTrack(
-  const TTrack &track,
-  const TInputSavePoint::Holder &savePoint,
-  TTrackList &outTracks )
-{
-  if (!track.handler) {
-    track.handler = new TTrackHandler(track);
-    track.handler->tracks.push_back(
-      new TTrack(
-        new Modifier(*track.handler) ));
-  }
-
-  if (track.handler->tracks.empty())
-    return;
-
-  TTrack &subTrack = *track.handler->tracks.front();
-  Modifier *modifier = dynamic_cast<Modifier*>(subTrack.modifier.getPointer());
-  if (!modifier)
-    return;
-
-  outTracks.push_back(track.handler->tracks.front());
-
-  if ( !track.changed()
-    && track.size() == subTrack.size()
-    && track.size() == (int)modifier->tangents.size() )
-      return;
-
-  if (!track.changed() && subTrack.size() == track.size() - 1) {
-    // add temporary point
-    modifier->tangents.push_back(TTrackTangent());
-    subTrack.push_back(track.back());
-  } else {
-    // apply permanent changes
-
-    // remove points
-    int start = track.size() - track.pointsAdded;
-    if (start < 0) start = 0;
-    if (start > 1) --start;
-    subTrack.truncate(start);
-    TTrackTangent lastTangent =
-        start < (int)modifier->tangents.size() ? modifier->tangents[start]
-      : modifier->tangents.empty() ? TTrackTangent()
-      : modifier->tangents.back();
-    modifier->tangents.resize(start, lastTangent);
-
-    // add first point
-    int index = start;
-    if (index == 0) {
-      modifier->tangents.push_back(TTrackTangent());
-      subTrack.push_back(track.back());
-      ++index;
-    }
-
-    // add points with tangents
-    while(index < track.size() - 1) {
-      const TTrackPoint &p0 = track[index-1];
-      const TTrackPoint &p1 = track[index];
-      const TTrackPoint &p2 = track[index+1];
-      double dt = p2.time - p0.time;
-      double k = dt > TTrack::epsilon ? (p1.time - p0.time)/dt : 0.5;
-      TTrackTangent tangent(
-        (p2.position - p0.position)*k,
-        (p2.pressure - p0.pressure)*k,
-        (p2.tilt - p0.tilt)*k );
-      modifier->tangents.push_back(tangent);
-      subTrack.push_back(p1);
-      ++index;
-    }
-
-    track.resetChanges();
-
-    // release previous key point
-    modifier->savePoint.reset();
-
-    if (track.finished()) {
-      // finish
-      modifier->tangents.push_back(TTrackTangent());
-      subTrack.push_back(track.back());
-    } else {
-      // save key point
-      modifier->savePoint = savePoint;
-    }
-  }
-}
-
diff --git a/toonz/sources/tnztools/modifiertest.cpp b/toonz/sources/tnztools/modifiertest.cpp
deleted file mode 100644
index 75090d6..0000000
--- a/toonz/sources/tnztools/modifiertest.cpp
+++ /dev/null
@@ -1,153 +0,0 @@
-
-
-#include <tools/modifiers/modifiertest.h>
-
-// std includes
-#include <cmath>
-
-//*****************************************************************************************
-//    TModifierTest::Modifier 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 > 2.0) {
-    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 = TPointD(1.0, 0.0); // original.calcTangent(originalIndex, fabs(2.0*this->radius/speed));
-      p.position.x += radius*(c*tangent.x - s*tangent.y);
-      p.position.y += radius*(s*tangent.x + c*tangent.y);
-      p.pressure   *= 0.5*(1.0 + c);
-    }
-  } else {
-    p.pressure = 0.0;
-  }
-
-  return p;
-}
-
-
-//*****************************************************************************************
-//    TModifierTest implementation
-//*****************************************************************************************
-
-
-TModifierTest::TModifierTest():
-  count(1),
-  radius(40.0)
-{ }
-
-
-void
-TModifierTest::modifyTrack(
-  const TTrack &track,
-  const TInputSavePoint::Holder &savePoint,
-  TTrackList &outTracks )
-{
-  const double segmentSize = M_PI/180.0*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,
-              2.0 )));
-    }
-  }
-
-  Handler *handler = dynamic_cast<Handler*>(track.handler.getPointer());
-  if (!handler) {
-    TInputModifier::modifyTrack(track, savePoint, outTracks);
-    return;
-  }
-
-  outTracks.insert(
-    outTracks.end(),
-    track.handler->tracks.begin(),
-    track.handler->tracks.end() );
-  if (!track.changed())
-    return;
-
-  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);
-
-  // 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 > TTrack::epsilon
-                ? dl/(2.0*radius*track[i].pressure) : 0.0;
-      handler->angles.push_back(handler->angles[i-1] + 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;
-
-    // remove points
-    int subStart = subTrack.floorIndex(subTrack.indexByOriginalIndex(start));
-    if (subStart < 0) subStart = 0;
-    if (subStart < subTrack.size() && subTrack[subStart].originalIndex + TTrack::epsilon < start)
-      ++subStart;
-    subTrack.truncate(subStart);
-
-    // add points
-    for(int i = start; i < track.size(); ++i) {
-      if (i > 0) {
-        double prevAngle = handler->angles[i-1];
-        double nextAngle = handler->angles[i];
-        if (fabs(nextAngle - prevAngle) > 1.5*segmentSize) {
-          double step = segmentSize/fabs(nextAngle - prevAngle);
-          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) );
-        }
-      }
-      subTrack.push_back( subTrack.modifier->calcPoint(i) );
-    }
-  }
-
-  track.resetChanges();
-}
-