#include "tools/tool.h"
#include "tools/toolutils.h"
#include "tools/cursors.h"
#include "tstroke.h"
#include "tstrokeutil.h"
#include "tstrokedeformations.h"
#include "tmathutil.h"
#include "tproperty.h"
#include "drawutil.h"
#include "tcurveutil.h"
#include "toonz/tobjecthandle.h"
#include "toonz/txshlevelhandle.h"
#include "toonz/tstageobject.h"
using namespace ToolUtils;
// For Qt translation support
#include <QCoreApplication>
//=============================================================================
namespace {
/*
Controlla se il primo punto della prima sotto_stroke
e' interno al cerchio, nel caso la stroke successiva
deve essere quella esterna e cosi' via, basta allora
fissare un contatore in modo da prendere le stroke
alternativamente (interne/esterne).
*/
void selectStrokeToMove(const ArrayOfStroke &v, const TPointD ¢er,
double radius, ArrayOfStroke &toMove) {
if (v.empty()) return;
UINT counter;
if (tdistance2(v.front()->getPoint(0), center) < sq(radius))
counter = 0;
else
counter = 1;
do {
toMove.push_back(v[counter]);
counter += 2;
} while (counter < v.size());
}
//=============================================================================
/*
inline double computeStep(const TQuadratic& quad, double invOfPixelSize)
{
double step = std::numeric_limits<double>::max();
TPointD A = quad.getP0() - 2.0*quad.getP1() + quad.getP2(); // 2*A is the
acceleration of the curve
double A_len = norm(A);
if (A_len > 0) step = sqrt(2 * invOfPixelSize / A_len);
return step;
}
*/
//-----------------------------------------------------------------------------
void drawQuadratic(const TQuadratic &quad, double pixelSize) {
double m_min_step_at_normal_size = computeStep(quad, pixelSize);
// It draws the curve as a linear piecewise approximation
double invSqrtScale = 1.0;
// First of all, it computes the control circles of the curve in screen
// coordinates
TPointD scP0 = quad.getP0();
TPointD scP1 = quad.getP1();
TPointD scP2 = quad.getP2();
TPointD A = scP0 - 2 * scP1 + scP2;
TPointD B = scP0 - scP1;
double h;
h = invSqrtScale * m_min_step_at_normal_size;
double h2 = h * h;
TPointD P = scP0, D2 = 2 * h2 * A, D1 = A * h2 - 2 * B * h;
if (h < 0 || isAlmostZero(h)) return;
// It draws the whole curve, using forward differencing
glBegin(GL_LINE_STRIP); // The curve starts from scP0
glVertex2d(scP0.x, scP0.y);
for (double t = h; t < 1; t = t + h) {
P = P + D1;
D1 = D1 + D2;
glVertex2d(P.x, P.y);
}
glVertex2d(scP2.x, scP2.y); // The curve ends in scP2
glEnd();
}
} // namespace
//=============================================================================
// MagnetTool
//-----------------------------------------------------------------------------
class MagnetTool final : public TTool {
Q_DECLARE_TR_FUNCTIONS(MagnetTool)
bool m_active;
TPointD m_startingPos;
TPointD m_oldPos, m_pointAtMouseDown, m_pointAtMove;
DoublePair m_extremes;
int m_cursorId;
double m_pointSize;
TUndo *m_undo;
typedef struct {
TStroke *m_parent;
ArrayOfStroke m_splitted;
ArrayOfStroke m_splittedToMove;
} strokeCollection;
std::vector<strokeCollection> m_strokeToModify;
// vector<StrokeInfo> m_info;
std::vector<TStroke *> m_strokeHit, m_oldStrokesArray;
std::vector<int> m_changedStrokes;
std::vector<std::vector<int> *> m_hitStrokeCorners, m_strokeToModifyCorners;
TDoubleProperty m_toolSize;
TPropertyGroup m_prop;
public:
MagnetTool()
: TTool("T_Magnet")
, m_active(false)
, m_pointSize(-1)
, m_oldStrokesArray()
, m_toolSize("Size:", 0, 100, 20) // W_ToolOptions_MagnetTool
{
bind(TTool::Vectors);
m_prop.bind(m_toolSize);
}
ToolType getToolType() const override { return TTool::LevelWriteTool; }
void updateTranslation() override { m_toolSize.setQStringName(tr("Size:")); }
void onEnter() override {
if ((TVectorImageP)getImage(false))
m_cursorId = ToolCursor::MagnetCursor;
else
m_cursorId = ToolCursor::CURSOR_NO;
updatePointSize();
}
void onLeave() override { m_pointSize = -1; }
void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override {
TPointD p(pos);
m_oldPos = pos;
m_pointAtMouseDown = p;
m_startingPos = p;
m_active = false;
TVectorImageP vi = TImageP(getImage(true));
if (!vi) return;
QMutexLocker lock(vi->getMutex());
m_active = true;
m_pointAtMove = m_pointAtMouseDown = p;
m_strokeHit.clear();
m_changedStrokes.clear();
m_strokeToModify.clear();
std::vector<TStroke *> strokeUndo;
TStroke *ref;
m_hitStrokeCorners.clear();
m_strokeToModifyCorners.clear();
UINT i = 0;
for (; i < vi->getStrokeCount(); ++i) {
if (!vi->inCurrentGroup(i)) continue;
TStroke *stroke = vi->getStroke(i);
ref = stroke;
// calcola le intersezioni
std::vector<double> intersections;
intersect(*ref, p, m_pointSize, intersections);
if (intersections.empty()) {
if (increaseControlPoints(*ref,
TStrokePointDeformation(p, m_pointSize))) {
m_changedStrokes.push_back(i);
m_strokeHit.push_back(ref);
std::vector<int> *corners = new std::vector<int>;
corners->push_back(0);
detectCorners(ref, 20, *corners);
corners->push_back(ref->getChunkCount());
m_hitStrokeCorners.push_back(corners);
ref->disableComputeOfCaches();
strokeUndo.push_back(ref);
}
} else {
strokeUndo.push_back(ref);
MagnetTool::strokeCollection sc;
sc.m_parent = ref;
splitStroke(*sc.m_parent, intersections, sc.m_splitted);
selectStrokeToMove(sc.m_splitted, p, m_pointSize, sc.m_splittedToMove);
for (UINT ii = 0; ii < sc.m_splittedToMove.size(); ++ii) {
TStroke *temp = sc.m_splittedToMove[ii];
bool test = increaseControlPoints(
*temp, TStrokePointDeformation(p, m_pointSize));
assert(test);
std::vector<int> *corners = new std::vector<int>;
corners->push_back(0);
detectCorners(temp, 20, *corners);
corners->push_back(temp->getChunkCount());
m_strokeToModifyCorners.push_back(corners);
}
m_strokeToModify.push_back(sc);
m_changedStrokes.push_back(i);
}
}
m_oldStrokesArray.resize(m_changedStrokes.size());
for (i = 0; i < m_changedStrokes.size(); i++)
m_oldStrokesArray[i] = new TStroke(*(vi->getStroke(m_changedStrokes[i])));
if (!strokeUndo.empty()) {
if (TTool::getApplication()->getCurrentObject()->isSpline())
m_undo = new UndoPath(
getXsheet()->getStageObject(getObjectId())->getSpline());
else {
TXshSimpleLevel *sl =
TTool::getApplication()->getCurrentLevel()->getSimpleLevel();
assert(sl);
TFrameId id = getCurrentFid();
m_undo = new UndoModifyListStroke(sl, id, strokeUndo);
}
}
invalidate();
// vi->validateRegionEdges(ref, true);
};
void leftButtonDrag(const TPointD &p, const TMouseEvent &) override {
if (!m_active) return;
// double dx = p.x - m_pointAtMouseDown.x;
double pixelSize = getPixelSize();
if (tdistance2(p, m_oldPos) < 9.0 * pixelSize * pixelSize) return;
m_oldPos = p;
m_pointAtMouseDown = p;
// double sc = exp(0.001 * (double)dx);
TVectorImageP vi = TImageP(getImage(true));
if (!vi) return;
QMutexLocker lock(vi->getMutex());
TPointD offset = p - m_pointAtMove;
/*
if( tdistance2(m_pointAtMouseDown, p ) > sq(m_pointSize * 0.5) ) // reincremento
{
leftButtonUp(p);
lefrightButtonDown(p);
}
*/
UINT i, j;
for (i = 0; i < m_strokeHit.size(); ++i)
modifyControlPoints(*m_strokeHit[i],
TStrokePointDeformation(offset, m_pointAtMouseDown,
m_pointSize * 0.7));
for (i = 0; i < m_strokeToModify.size(); ++i)
for (j = 0; j < m_strokeToModify[i].m_splittedToMove.size(); ++j) {
TStroke *temp = m_strokeToModify[i].m_splittedToMove[j];
modifyControlPoints(*temp,
TStrokePointDeformation(offset, m_pointAtMouseDown,
m_pointSize * 0.7));
}
m_pointAtMove = p;
invalidate();
};
void mouseMove(const TPointD &p, const TMouseEvent &e) override {
m_pointAtMove = p;
double pixelSize = getPixelSize();
if (tdistance2(p, m_oldPos) < 9.0 * pixelSize * pixelSize) return;
m_oldPos = p;
invalidate();
}
void leftButtonUp(const TPointD &, const TMouseEvent &) override {
if (!m_active) return;
m_active = false;
m_pointAtMouseDown = m_pointAtMove = TConsts::napd;
TStroke *ref;
TVectorImageP vi = TImageP(getImage(true));
if (!vi) return;
QMutexLocker lock(vi->getMutex());
UINT i, j;
for (i = 0; i < m_strokeHit.size(); ++i) {
ref = m_strokeHit[i];
ref->enableComputeOfCaches();
ref->reduceControlPoints(getPixelSize() * ReduceControlPointCorrection,
*(m_hitStrokeCorners[i]));
// vi->validateRegionEdges(ref, false);
}
clearPointerContainer(m_hitStrokeCorners);
m_hitStrokeCorners.clear();
UINT count = 0;
for (i = 0; i < m_strokeToModify.size(); ++i) {
// recupero la stroke collection
MagnetTool::strokeCollection &sc = m_strokeToModify[i];
for (j = 0; j < sc.m_splittedToMove.size(); ++j) {
ref = sc.m_splittedToMove[j];
ref->enableComputeOfCaches();
ref->reduceControlPoints(getPixelSize() * ReduceControlPointCorrection,
*(m_strokeToModifyCorners[count++]));
}
// ricostruisco una stroke con quella data
ref = merge(sc.m_splitted);
if (sc.m_parent->isSelfLoop()) {
int cpCount = ref->getControlPointCount();
TThickPoint p1 = ref->getControlPoint(0);
TThickPoint p2 = ref->getControlPoint(cpCount - 1);
TThickPoint midP = (p1 + p2) * 0.5;
ref->setControlPoint(0, midP);
ref->setControlPoint(cpCount - 1, midP);
ref->setSelfLoop(true);
}
sc.m_parent->swapGeometry(*ref);
delete ref; // elimino la curva temporanea
clearPointerContainer(
sc.m_splitted); // pulisco le stroke trovate con lo split
sc.m_splittedToMove.clear(); // pulisco il contenitore ( le stroke
// che erano contenute qua sono state
// eliminate nella clearPointer....
}
clearPointerContainer(m_strokeToModifyCorners);
m_strokeToModifyCorners.clear();
for (i = 0; i < vi->getStrokeCount(); ++i) {
ref = vi->getStroke(i);
ref->invalidate();
}
vi->notifyChangedStrokes(m_changedStrokes, m_oldStrokesArray);
notifyImageChanged();
if (m_undo) TUndoManager::manager()->add(m_undo);
m_undo = 0;
clearPointerContainer(m_oldStrokesArray);
m_oldStrokesArray.clear();
invalidate();
};
void draw() override {
TVectorImageP vi = TImageP(getImage(true));
if (!vi) return;
// TAffine viewMatrix = getViewer()->getViewMatrix();
// glPushMatrix();
// tglMultMatrix(viewMatrix);
if (m_pointSize > 0) {
tglColor(TPixel32::Red);
tglDrawCircle(m_pointAtMove, m_pointSize);
}
if (!m_active) {
// glPopMatrix();
return;
}
TPointD delta = m_pointAtMouseDown - m_startingPos;
// glPushMatrix();
// tglMultMatrix(m_referenceMatrix);
if (!m_strokeHit.empty())
for (UINT i = 0; i < m_strokeHit.size(); ++i)
drawStrokeCenterline(*m_strokeHit[i], getPixelSize());
tglColor(TPixel32::Red);
UINT i, j;
for (i = 0; i < m_strokeToModify.size(); ++i)
for (j = 0; j < m_strokeToModify[i].m_splittedToMove.size(); ++j) {
TStroke *temp = m_strokeToModify[i].m_splittedToMove[j];
drawStrokeCenterline(*temp, getPixelSize());
}
// glPopMatrix();
// glPopMatrix();
}
void onActivate() override {
// getApplication()->editImageOrSpline();
}
TPropertyGroup *getProperties(int targetType) override { return &m_prop; }
int getCursorId() const override { return m_cursorId; }
bool onPropertyChanged(std::string propertyName) override
{
if(propertyName == m_toolSize.getName()) {
updatePointSize();
invalidate();
}
return true;
}
private:
/// Update point size based on property.
void updatePointSize()
{
double x = m_toolSize.getValue();
double minRange = 1;
double maxRange = 100;
double minSize = 10;
double maxSize = 100;
m_pointSize =
(x - minRange) / (maxRange - minRange) * (maxSize - minSize) + minSize;
}
} magnetTool;
// TTool *getMagnetTool() {return &magnetTool;}