| |
| |
| #include "tools/tool.h" |
| #include "tools/toolutils.h" |
| #include "tools/cursors.h" |
| |
| #include "tstrokeutil.h" |
| #include "tmathutil.h" |
| #include "tgl.h" |
| #include "tstroke.h" |
| |
| #include "toonz/tobjecthandle.h" |
| #include "toonz/txshlevelhandle.h" |
| #include "toonz/tstageobject.h" |
| |
| #ifdef _DEBUG |
| #include "tcolorstyles.h" |
| #include "tsimplecolorstyles.h" |
| #include "tstrokeoutline.h" |
| #include "tpalette.h" |
| #endif |
| |
| using namespace ToolUtils; |
| |
| namespace |
| { |
| |
| const int MY_ERROR = -1; |
| |
| |
| |
| |
| |
| |
| inline bool isIncluded(int index, int left, int rigth) |
| { |
| if (left < rigth) { |
| return left <= index && index <= rigth; |
| } else { |
| return left <= index || index <= rigth; |
| } |
| } |
| |
| |
| |
| |
| |
| class IronTool : public TTool |
| { |
| |
| TStroke *m_strokeRef, *m_oldStroke; |
| TUndo *m_undo; |
| |
| DoublePair m_range; |
| |
| bool m_draw; |
| bool m_dragged; |
| |
| TThickPoint m_cursor; |
| int m_selectedStroke; |
| TPointD m_beginPoint; |
| |
| int m_cpIndexMin, m_cpIndexMax; |
| |
| bool m_active; |
| int m_cursorId; |
| |
| public: |
| IronTool() |
| : TTool("T_Iron"), m_strokeRef(0), m_draw(false), m_active(false), m_dragged(false), m_undo(0), m_cpIndexMin(-1), m_cpIndexMax(-1), m_oldStroke(0), m_cursorId(ToolCursor::IronCursor) |
| { |
| bind(TTool::Vectors); |
| } |
| |
| ToolType getToolType() const { return TTool::LevelWriteTool; } |
| |
| void draw() |
| { |
| if (m_draw && (TVectorImageP)getImage(false)) { |
| glColor3d(1, 0, 1); |
| if (m_cursor.thick > 0) |
| tglDrawCircle(m_cursor, m_cursor.thick); |
| tglDrawCircle(m_cursor, m_cursor.thick + 4 * getPixelSize()); |
| } |
| } |
| |
| void leftButtonDown(const TPointD &pos, const TMouseEvent &) |
| { |
| if (m_active) |
| return; |
| assert(m_undo == 0); |
| m_active = false; |
| |
| TVectorImageP vi = TImageP(getImage(true)); |
| if (!vi) |
| return; |
| |
| |
| double dist; |
| UINT stroke; |
| |
| if (!vi->getNearestStroke(pos, m_range.first, stroke, dist)) { |
| m_strokeRef = 0; |
| m_selectedStroke = MY_ERROR; |
| m_draw = false; |
| } else { |
| m_draw = true; |
| m_active = true; |
| m_strokeRef = vi->getStroke(stroke); |
| m_selectedStroke = stroke; |
| m_beginPoint = m_strokeRef->getPoint(m_range.first); |
| m_oldStroke = new TStroke(*vi->getStroke(stroke)); |
| |
| m_range.second = m_range.first; |
| |
| 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 UndoModifyStrokeAndPaint(sl, id, stroke); |
| } |
| } |
| |
| if (m_strokeRef) |
| m_cpIndexMin = m_strokeRef->getControlPointCount(); |
| m_cpIndexMax = -1; |
| |
| invalidate(); |
| } |
| |
| void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) |
| { |
| TVectorImageP vi(getImage(true)); |
| if (!m_active || !vi || !m_strokeRef) { |
| delete m_undo; |
| m_undo = 0; |
| return; |
| } |
| |
| m_range.second = m_strokeRef->getW(pos); |
| m_cursor = m_strokeRef->getThickPoint(m_range.second); |
| |
| double v0 = std::min(m_range.first, m_range.second); |
| double v1 = std::max(m_range.first, m_range.second); |
| const double eps = 0.005; |
| |
| if (v1 - v0 < eps && !(m_strokeRef->isSelfLoop() && 1 - (v1 - v0) < eps)) { |
| invalidate(); |
| return; |
| } |
| |
| TPointD point2 = m_strokeRef->getPoint(m_range.second); |
| double tdist2 = tdistance2(m_beginPoint, point2); |
| |
| double pixelSize = getPixelSize(); |
| if (tdist2 < 100 * pixelSize * pixelSize) { |
| invalidate(); |
| return; |
| } |
| |
| double draggedStrokeLen = m_strokeRef->getLength(v0, v1); |
| |
| double totalStrokeLen = m_strokeRef->getLength(); |
| |
| bool direction = !m_strokeRef->isSelfLoop() || |
| draggedStrokeLen < totalStrokeLen - draggedStrokeLen; |
| |
| if (!direction) { |
| draggedStrokeLen = totalStrokeLen - draggedStrokeLen; |
| tswap(v0, v1); |
| } |
| |
| |
| |
| |
| if ((draggedStrokeLen * draggedStrokeLen) > 16.0 * tdist2) { |
| m_beginPoint = point2; |
| return; |
| } |
| |
| int i; |
| int maxCP = m_strokeRef->getControlPointCount() - 1; |
| |
| |
| int iCP0 = (int)(maxCP * v0); |
| int iCP1 = m_strokeRef->getControlPointIndexAfterParameter(v1); |
| |
| if (iCP0 > maxCP) { |
| if (!direction) |
| iCP0 = maxCP; |
| else { |
| invalidate(); |
| return; |
| } |
| } |
| |
| if (iCP1 > maxCP) |
| iCP1 = maxCP; |
| |
| if (direction && (iCP1 - iCP0) < 2) { |
| invalidate(); |
| return; |
| } |
| |
| if (!direction && maxCP + 1 - (iCP1 - iCP0) < 3) { |
| invalidate(); |
| return; |
| } |
| |
| |
| m_dragged = true; |
| m_beginPoint = point2; |
| |
| if (!m_strokeRef->isSelfLoop()) { |
| if (m_cpIndexMin > iCP0) |
| m_cpIndexMin = iCP0; |
| |
| if (m_cpIndexMax < iCP1) |
| m_cpIndexMax = iCP1; |
| } else { |
| if (m_cpIndexMin == m_strokeRef->getControlPointCount() && m_cpIndexMax == -1) { |
| m_cpIndexMin = iCP0; |
| m_cpIndexMax = iCP1; |
| } else { |
| if (isIncluded(iCP0, m_cpIndexMin, m_cpIndexMax)) { |
| if (!isIncluded(iCP1, m_cpIndexMin, m_cpIndexMax)) { |
| m_cpIndexMax = iCP1; |
| } |
| } else if (isIncluded(iCP1, m_cpIndexMin, m_cpIndexMax)) { |
| assert(!isIncluded(iCP0, m_cpIndexMin, m_cpIndexMax)); |
| m_cpIndexMin = iCP0; |
| } else { |
| int sgn = tsign(m_range.second - m_range.first); |
| if (!direction) |
| sgn = -sgn; |
| |
| switch (sgn) { |
| case 1: |
| m_cpIndexMax = iCP1; |
| break; |
| case -1: |
| m_cpIndexMin = iCP0; |
| break; |
| case 0: |
| assert(0); |
| default: |
| assert(0); |
| } |
| } |
| } |
| } |
| |
| m_range.first = m_range.second; |
| |
| assert(m_cpIndexMin != m_cpIndexMax); |
| |
| v0 = m_strokeRef->getParameterAtControlPoint(iCP0); |
| v1 = m_strokeRef->getParameterAtControlPoint(iCP1); |
| |
| |
| |
| |
| |
| const double smoothFactor = 0.08; |
| |
| double wDistance = (direction) ? v1 - v0 : 1 - (v0 - v1); |
| |
| double smoothFP = smoothFactor / wDistance; |
| |
| TPointD pf0 = m_strokeRef->getControlPoint(iCP0) * smoothFP; |
| TPointD pf1 = m_strokeRef->getControlPoint(iCP1) * smoothFP; |
| |
| TPointD appDPoint; |
| TThickPoint appThickPoint; |
| |
| double oppSmoothFactor = 1.0 - smoothFactor; |
| double vp, v1vp, vpv0; |
| |
| i = (iCP0 == maxCP || (iCP0 == 0 && !direction)) ? 1 : iCP0 + 1; |
| |
| for (; i != iCP1;) { |
| vp = m_strokeRef->getParameterAtControlPoint(i); |
| |
| appThickPoint = m_strokeRef->getControlPoint(i); |
| appDPoint = appThickPoint; |
| |
| v1vp = v1 - vp; |
| if (v1vp < 0) { |
| if (!direction) |
| v1vp += 1; |
| else |
| assert(0); |
| } |
| |
| vpv0 = vp - v0; |
| if (vpv0 < 0) { |
| if (!direction) |
| vpv0 += 1; |
| else |
| assert(0); |
| } |
| |
| assert(isAlmostZero(v1vp + vpv0 - wDistance)); |
| |
| assert(isAlmostZero((smoothFP * v1vp + smoothFP * vpv0) + oppSmoothFactor - 1.0)); |
| |
| m_strokeRef->setControlPoint(i, TThickPoint( |
| pf0 * v1vp + |
| pf1 * vpv0 + |
| appDPoint * oppSmoothFactor, |
| appThickPoint.thick)); |
| |
| |
| |
| |
| |
| |
| |
| i++; |
| if (i == maxCP + 1) { |
| m_strokeRef->setControlPoint(0, m_strokeRef->getControlPoint(maxCP)); |
| i = 1; |
| } |
| } |
| |
| m_strokeRef->invalidate(); |
| invalidate(); |
| notifyImageChanged(); |
| } |
| |
| void leftButtonUp(const TPointD &pos, const TMouseEvent &) |
| { |
| if (!m_active) |
| return; |
| |
| m_active = false; |
| |
| invalidate(); |
| |
| TVectorImageP vi(getImage(true)); |
| if (!vi || !m_strokeRef || !m_dragged) { |
| delete m_undo; |
| m_undo = 0; |
| return; |
| } |
| QMutexLocker lock(vi->getMutex()); |
| |
| |
| |
| |
| |
| UINT cpCount = m_strokeRef->getControlPointCount(); |
| |
| m_cpIndexMin &= ~1; |
| |
| if (m_cpIndexMax & 1) |
| m_cpIndexMax++; |
| |
| int i = 0; |
| UINT count = 0; |
| |
| if ((m_cpIndexMin == 0 && (UINT)m_cpIndexMax == cpCount - 1) || |
| (m_cpIndexMin == m_cpIndexMax && m_strokeRef->isSelfLoop())) { |
| |
| |
| |
| |
| |
| |
| std::vector<int> corners; |
| corners.push_back(0); |
| detectCorners(m_strokeRef, 45, corners); |
| corners.push_back(m_strokeRef->getChunkCount()); |
| m_strokeRef->reduceControlPoints(2.0 * getPixelSize(), corners); |
| } else { |
| if (m_cpIndexMin < m_cpIndexMax) { |
| std::vector<TThickPoint> hitPoints(m_cpIndexMax - m_cpIndexMin + 1); |
| count = 0; |
| for (i = m_cpIndexMin; i <= m_cpIndexMax; i++) { |
| hitPoints[count++] = m_strokeRef->getControlPoint(i); |
| } |
| TStroke *newStroke = new TStroke(hitPoints); |
| |
| std::vector<int> corners; |
| corners.push_back(0); |
| detectCorners(newStroke, 45, corners); |
| corners.push_back(newStroke->getChunkCount()); |
| newStroke->reduceControlPoints(2.0 * getPixelSize(), corners); |
| |
| hitPoints.resize(m_cpIndexMin + newStroke->getControlPointCount() + cpCount - 1 - m_cpIndexMax); |
| |
| count = 0; |
| |
| for (i = 0; i < m_cpIndexMin; i++) { |
| hitPoints[count++] = m_strokeRef->getControlPoint(i); |
| } |
| |
| for (i = 0; i < newStroke->getControlPointCount(); i++) { |
| hitPoints[count++] = newStroke->getControlPoint(i); |
| } |
| |
| for (i = m_cpIndexMax + 1; i < (int)cpCount; i++) { |
| hitPoints[count++] = m_strokeRef->getControlPoint(i); |
| } |
| |
| m_strokeRef->reshape(&hitPoints[0], hitPoints.size()); |
| delete newStroke; |
| } else { |
| assert(m_cpIndexMin != m_cpIndexMax); |
| |
| std::vector<TThickPoint> hitPoints(cpCount - m_cpIndexMin); |
| count = 0; |
| for (i = m_cpIndexMin; i < (int)cpCount; i++) { |
| hitPoints[count++] = m_strokeRef->getControlPoint(i); |
| } |
| TStroke *newStroke1 = new TStroke(hitPoints); |
| |
| std::vector<int> corners; |
| corners.push_back(0); |
| detectCorners(newStroke1, 45, corners); |
| corners.push_back(newStroke1->getChunkCount()); |
| newStroke1->reduceControlPoints(2.0 * getPixelSize(), corners); |
| |
| hitPoints.resize(m_cpIndexMax + 1); |
| count = 0; |
| for (i = 0; i <= m_cpIndexMax; i++) { |
| hitPoints[count++] = m_strokeRef->getControlPoint(i); |
| } |
| TStroke *newStroke2 = new TStroke(hitPoints); |
| |
| corners.clear(); |
| corners.push_back(0); |
| detectCorners(newStroke2, 45, corners); |
| corners.push_back(newStroke2->getChunkCount()); |
| newStroke2->reduceControlPoints(2.0 * getPixelSize(), corners); |
| |
| hitPoints.resize( |
| newStroke1->getControlPointCount() + |
| newStroke2->getControlPointCount() + |
| m_cpIndexMin - m_cpIndexMax - 1); |
| |
| count = 0; |
| for (i = 0; i < newStroke2->getControlPointCount(); i++) { |
| hitPoints[count++] = newStroke2->getControlPoint(i); |
| } |
| for (i = m_cpIndexMax + 1; i < m_cpIndexMin; i++) { |
| hitPoints[count++] = m_strokeRef->getControlPoint(i); |
| } |
| for (i = 0; i < newStroke1->getControlPointCount(); i++) { |
| hitPoints[count++] = newStroke1->getControlPoint(i); |
| } |
| |
| m_strokeRef->reshape(&hitPoints[0], hitPoints.size()); |
| delete newStroke1; |
| delete newStroke2; |
| } |
| } |
| |
| invalidate(); |
| |
| m_dragged = false; |
| assert(m_undo); |
| |
| assert(m_oldStroke); |
| vi->notifyChangedStrokes(m_selectedStroke, m_oldStroke); |
| |
| TUndoManager::manager()->add(m_undo); |
| m_undo = 0; |
| delete m_oldStroke; |
| } |
| |
| void invalidateCursorArea() |
| { |
| double r = m_cursor.thick + 6; |
| TPointD d(r, r); |
| invalidate(TRectD(m_cursor - d, m_cursor + d)); |
| } |
| |
| void mouseMove(const TPointD &pos, const TMouseEvent &e) |
| { |
| TVectorImageP vi = TImageP(getImage(true)); |
| if (!vi) { |
| m_draw = false; |
| return; |
| } |
| |
| |
| double dist, pW; |
| UINT stroke; |
| |
| if (vi->getNearestStroke(pos, pW, stroke, dist)) { |
| m_draw = true; |
| TStroke *strokeRef = vi->getStroke(stroke); |
| m_cursor = strokeRef->getThickPoint(pW); |
| } else { |
| m_draw = false; |
| } |
| |
| invalidate(); |
| } |
| |
| bool moveCursor(const TPointD &pos) |
| { |
| return false; |
| } |
| |
| void onActivate() |
| { |
| |
| } |
| |
| void onLeave() |
| { |
| m_draw = false; |
| } |
| |
| int getCursorId() const { return m_cursorId; } |
| void onEnter() |
| { |
| m_draw = true; |
| if ((TVectorImageP)getImage(false)) |
| m_cursorId = ToolCursor::IronCursor; |
| else |
| m_cursorId = ToolCursor::CURSOR_NO; |
| } |
| |
| } ironTool; |
| |
| |
| |
| #ifdef _DEBUG |
| |
| void drawPoint(const TPointD &p, double pixelSize) |
| { |
| double sizeX = pixelSize; |
| double sizeY = pixelSize; |
| glBegin(GL_QUADS); |
| glVertex2d(p.x - sizeX, p.y - sizeY); |
| glVertex2d(p.x - sizeX, p.y + sizeY); |
| glVertex2d(p.x + sizeX, p.y + sizeY); |
| glVertex2d(p.x + sizeX, p.y - sizeY); |
| glEnd(); |
| return; |
| } |
| |
| |
| |
| void drawControlPoints(const TStroke *&stroke, double pixelSize) |
| { |
| int n = stroke->getChunkCount(); |
| int i = 0; |
| for (i = 0; i < n; ++i) { |
| const TThickQuadratic *chunk = stroke->getChunk(i); |
| TPointD p0 = chunk->getP0(); |
| TPointD p1 = chunk->getP1(); |
| glColor3d(1.0, 0.0, 0.0); |
| drawPoint(p0, pixelSize); |
| glColor3d(1.0, 1.0, 1.0); |
| drawPoint(p1, pixelSize); |
| } |
| const TThickQuadratic *chunk = stroke->getChunk(n - 1); |
| glColor3d(1.0, 0.0, 0.0); |
| TPointD p2 = chunk->getP2(); |
| drawPoint(p2, pixelSize); |
| return; |
| } |
| |
| #endif |
| |
| } |
| |
| |
| |