| |
| |
| #include "tvectorgl.h" |
| #include "tgl.h" |
| #include "tpalette.h" |
| #include "tproperty.h" |
| #include "tthreadmessage.h" |
| #include "tvectorimage.h" |
| #include "drawutil.h" |
| #include "tcurveutil.h" |
| #include "tstroke.h" |
| #include "tstrokeutil.h" |
| #include "tvectorrenderdata.h" |
| #include "tstrokedeformations.h" |
| #include "tmathutil.h" |
| |
| |
| #include "toonz/tobjecthandle.h" |
| #include "toonz/txshlevelhandle.h" |
| |
| |
| #include "tools/tool.h" |
| #include "tools/toolutils.h" |
| #include "tools/cursors.h" |
| |
| |
| #include <QCoreApplication> // For Qt translation support |
| |
| using namespace ToolUtils; |
| |
| |
| |
| |
| |
| class PumpTool : public TTool |
| { |
| Q_DECLARE_TR_FUNCTIONS(PumpTool) |
| |
| int m_strokeStyleId, m_strokeIndex; |
| TStroke *m_inStroke, *m_outStroke; |
| std::vector<TStroke *> m_splitStrokes; |
| int m_stroke1Idx, m_stroke2Idx; |
| |
| TUndo *m_undo; |
| |
| double m_actionW; |
| double m_actionS1, m_actionS2; |
| double m_actionRadius; |
| |
| std::vector<double> m_splitPars; |
| std::vector<double> m_cpLenDiff1, m_cpLenDiff2; |
| |
| bool m_active; |
| bool m_enabled; |
| bool m_cursorEnabled; |
| bool m_draw; |
| |
| TPointD m_oldPoint, m_downPoint; |
| TThickPoint m_cursor; |
| int m_cursorId; |
| |
| double m_errorTol; |
| |
| TDoubleProperty m_toolSize; |
| TIntProperty m_accuracy; |
| TPropertyGroup m_prop; |
| |
| public: |
| PumpTool() |
| : TTool("T_Pump"), m_active(false), m_actionW(0), m_strokeIndex((std::numeric_limits<UINT>::max)()), m_inStroke(0), m_outStroke(0), m_stroke1Idx(-1), m_stroke2Idx(-1), m_cursorEnabled(false), m_cursorId(ToolCursor::PumpCursor), m_actionRadius(1), m_draw(false), m_undo(0), m_toolSize("Size:", 1, 100, 20), m_accuracy("Accuracy:", 0, 100, 40), m_enabled(false) |
| { |
| bind(TTool::VectorImage); |
| |
| m_splitPars.resize(2); |
| |
| m_prop.bind(m_toolSize); |
| m_prop.bind(m_accuracy); |
| } |
| |
| ToolType getToolType() const { return TTool::LevelWriteTool; } |
| |
| TPropertyGroup *getProperties(int targetType) { return &m_prop; } |
| |
| void updateTranslation() |
| { |
| m_toolSize.setQStringName(tr("Size:")); |
| m_accuracy.setQStringName(tr("Accuracy:")); |
| } |
| |
| void onEnter(); |
| void onLeave(); |
| |
| void draw(); |
| |
| void leftButtonDown(const TPointD &pos, const TMouseEvent &e); |
| void leftButtonDrag(const TPointD &pos, const TMouseEvent &e); |
| void leftButtonUp(const TPointD &pos, const TMouseEvent &e); |
| |
| void mouseMove(const TPointD &pos, const TMouseEvent &e); |
| bool moveCursor(const TPointD &pos); |
| |
| int getCursorId() const { return m_cursorId; } |
| void invalidateCursorArea(); |
| |
| void onDeactivate(); |
| |
| private: |
| double actionRadius(double strokeLength); |
| void splitStroke(TStroke *s); |
| TStroke *mergeStrokes(const std::vector<TStroke *> &strokes); |
| |
| } PumpToolInstance; |
| |
| |
| |
| |
| |
| void PumpTool::onEnter() |
| { |
| m_draw = true; |
| if (TTool::getApplication()->getCurrentObject()->isSpline() || |
| !(TVectorImageP)getImage(false)) { |
| m_enabled = false; |
| m_cursorId = ToolCursor::CURSOR_NO; |
| } else { |
| m_enabled = true; |
| m_cursorId = ToolCursor::PumpCursor; |
| } |
| } |
| |
| |
| |
| void PumpTool::draw() |
| { |
| if (!m_draw || !m_enabled) |
| return; |
| |
| TVectorImageP vi = TImageP(getImage(false)); |
| if (!vi) |
| return; |
| |
| QMutexLocker lock(vi->getMutex()); |
| |
| TPalette *palette = vi->getPalette(); |
| assert(palette); |
| if (m_active) { |
| |
| assert(m_outStroke); |
| |
| TRectD bboxD(m_outStroke->getBBox()); |
| TRect bbox(tfloor(bboxD.x0), tfloor(bboxD.y0), tceil(bboxD.x1) - 1, tceil(bboxD.y1) - 1); |
| |
| tglDraw(TVectorRenderData(TAffine(), bbox, palette, 0, true), m_outStroke); |
| } else { |
| |
| |
| double w, dist; |
| UINT index; |
| |
| if (m_cursorEnabled) { |
| |
| glColor3d(1.0, 0.0, 1.0); |
| if (m_cursor.thick > 0) |
| tglDrawCircle(m_cursor, m_cursor.thick); |
| tglDrawCircle(m_cursor, m_cursor.thick + 4 * getPixelSize()); |
| } |
| |
| if (vi->getNearestStroke(m_cursor, w, index, dist, true)) { |
| TStroke *stroke = vi->getStroke(index); |
| double totalLen = stroke->getLength(); |
| double actionLen = actionRadius(totalLen); |
| |
| tglColor(TPixel32::Red); |
| |
| if (totalLen < actionLen || (stroke->isSelfLoop() && totalLen < actionLen + actionLen)) |
| drawStrokeCenterline(*stroke, getPixelSize()); |
| else { |
| int i, chunckIndex1, chunckIndex2; |
| double t, t1, t2, w1, w2; |
| |
| double len = stroke->getLength(w); |
| |
| double len1 = len - actionLen; |
| if (len1 < 0) |
| if (stroke->isSelfLoop()) |
| len1 += totalLen; |
| else |
| len1 = 0; |
| |
| double len2 = len + actionLen; |
| if (len2 > totalLen) |
| if (stroke->isSelfLoop()) |
| len2 -= totalLen; |
| else |
| len2 = totalLen; |
| |
| w1 = stroke->getParameterAtLength(len1); |
| w2 = stroke->getParameterAtLength(len2); |
| |
| int chunkCount = stroke->getChunkCount(); |
| |
| stroke->getChunkAndT(w1, chunckIndex1, t1); |
| stroke->getChunkAndT(w2, chunckIndex2, t2); |
| double step; |
| |
| const TThickQuadratic *q = 0; |
| |
| glBegin(GL_LINE_STRIP); |
| |
| q = stroke->getChunk(chunckIndex1); |
| step = computeStep(*q, getPixelSize()); |
| |
| if (chunckIndex1 == chunckIndex2 && t1 < t2) { |
| for (t = t1; t < t2; t += step) |
| tglVertex(q->getPoint(t)); |
| |
| tglVertex(stroke->getPoint(w2)); |
| glEnd(); |
| return; |
| } |
| |
| for (t = t1; t < 1; t += step) |
| tglVertex(q->getPoint(t)); |
| |
| for (i = chunckIndex1 + 1; i != chunckIndex2; i++) { |
| if (i == chunkCount) |
| i = 0; |
| |
| if (i == chunckIndex2) |
| break; |
| |
| q = stroke->getChunk(i); |
| step = computeStep(*q, getPixelSize()); |
| for (t = 0; t < 1; t += step) |
| tglVertex(q->getPoint(t)); |
| } |
| |
| q = stroke->getChunk(chunckIndex2); |
| step = computeStep(*q, getPixelSize()); |
| for (t = 0; t < t2; t += step) |
| tglVertex(q->getPoint(t)); |
| |
| tglVertex(stroke->getPoint(w2)); |
| |
| glEnd(); |
| } |
| } |
| } |
| } |
| |
| |
| |
| void PumpTool::leftButtonDown(const TPointD &pos, const TMouseEvent &) |
| { |
| if (m_active || !m_enabled) |
| return; |
| |
| assert(m_undo == 0); |
| m_active = false; |
| |
| TVectorImageP vi(getImage(true)); |
| if (!vi) |
| return; |
| |
| QMutexLocker lock(vi->getMutex()); |
| |
| |
| m_oldPoint = pos; |
| m_downPoint = pos; |
| |
| m_inStroke = m_outStroke = 0; |
| m_stroke1Idx = m_stroke2Idx = -1; |
| m_splitPars[0] = m_splitPars[1] = -2; |
| m_actionW = 0; |
| |
| m_errorTol = (1.0 - 0.01 * m_accuracy.getValue()) * getPixelSize(); |
| |
| double dist2 = 0.0; |
| int cpCount; |
| int i; |
| UINT index; |
| |
| if (vi->getNearestStroke(pos, m_actionW, index, dist2)) { |
| |
| m_active = true; |
| m_strokeIndex = index; |
| |
| m_inStroke = vi->getStroke(m_strokeIndex); |
| m_outStroke = new TStroke(*m_inStroke); |
| |
| double totalLength = m_inStroke->getLength(); |
| TXshSimpleLevel *sl = TTool::getApplication()->getCurrentLevel()->getSimpleLevel(); |
| assert(sl); |
| TFrameId id = getCurrentFid(); |
| |
| |
| m_undo = new UndoModifyStrokeAndPaint(sl, id, m_strokeIndex); |
| |
| |
| |
| m_strokeStyleId = m_inStroke->getStyle(); |
| m_inStroke->setStyle(0); |
| |
| if (totalLength <= 0.0) { |
| |
| cpCount = m_inStroke->getControlPointCount(); |
| m_cpLenDiff1.resize(cpCount); |
| |
| for (i = 0; i < cpCount; i++) |
| m_cpLenDiff1[i] = 0.0; |
| |
| m_splitStrokes.resize(1); |
| m_splitStrokes[0] = new TStroke(*m_inStroke); |
| |
| m_stroke1Idx = 0; |
| } else |
| |
| splitStroke(m_inStroke); |
| } |
| |
| invalidate(); |
| } |
| |
| |
| |
| void PumpTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) |
| { |
| if (!m_active || !m_enabled) |
| return; |
| |
| TVectorImageP vi(getImage(true)); |
| if (!vi || !m_outStroke) |
| return; |
| |
| QMutexLocker lock(vi->getMutex()); |
| |
| |
| delete m_outStroke; |
| |
| |
| TPointD delta = TPointD(0, (pos - m_downPoint).y); |
| int deltaSign = tsign(delta.y); |
| if (deltaSign == 0) { |
| |
| m_outStroke = new TStroke(*m_inStroke); |
| m_outStroke->setStyle(m_strokeStyleId); |
| invalidate(); |
| return; |
| } |
| |
| |
| TStroke *stroke1 = 0, *stroke2 = 0; |
| |
| stroke1 = new TStroke(*m_splitStrokes[m_stroke1Idx]); |
| |
| |
| TStrokeThicknessDeformation deformer(stroke1, delta, m_actionS1, m_actionRadius, deltaSign); |
| modifyThickness(*stroke1, deformer, m_cpLenDiff1, deltaSign < 0); |
| |
| if (m_stroke2Idx >= 0) { |
| |
| stroke2 = new TStroke(*m_splitStrokes[m_stroke2Idx]); |
| |
| TStrokeThicknessDeformation deformer2(stroke2, delta, m_actionS2, m_actionRadius, deltaSign); |
| modifyThickness(*stroke2, deformer2, m_cpLenDiff2, deltaSign < 0); |
| } |
| |
| |
| std::vector<TStroke *> splitStrokesCopy(m_splitStrokes); |
| splitStrokesCopy[m_stroke1Idx] = stroke1; |
| if (stroke2) |
| splitStrokesCopy[m_stroke2Idx] = stroke2; |
| |
| m_outStroke = mergeStrokes(splitStrokesCopy); |
| |
| delete stroke1; |
| delete stroke2; |
| |
| invalidate(); |
| } |
| |
| |
| |
| void PumpTool::leftButtonUp(const TPointD &pos, const TMouseEvent &) |
| { |
| TVectorImageP vi; |
| |
| if (!m_active || !m_enabled) |
| goto cleanup; |
| |
| vi = TVectorImageP(getImage(true)); |
| if (!vi) |
| goto cleanup; |
| |
| { |
| m_active = false; |
| |
| QMutexLocker lock(vi->getMutex()); |
| |
| |
| double t; |
| UINT index; |
| double dist2; |
| if (vi->getNearestStroke(pos, t, index, dist2)) { |
| TStroke *nearestStroke = vi->getStroke(index); |
| if (nearestStroke) |
| m_cursor = nearestStroke->getThickPoint(t); |
| } |
| |
| if (m_outStroke && |
| !areAlmostEqual(m_downPoint, pos, PickRadius * getPixelSize())) { |
| |
| |
| |
| TStroke *oldStroke = new TStroke(*m_inStroke); |
| |
| m_outStroke->swap(*m_inStroke); |
| |
| m_inStroke->invalidate(); |
| |
| delete m_outStroke; |
| m_outStroke = 0; |
| |
| assert(m_undo); |
| TUndoManager::manager()->add(m_undo); |
| m_undo = 0; |
| |
| vi->notifyChangedStrokes(m_strokeIndex, oldStroke); |
| notifyImageChanged(); |
| |
| delete oldStroke; |
| } |
| } |
| |
| cleanup: |
| |
| if (m_inStroke) |
| m_inStroke->setStyle(m_strokeStyleId); |
| |
| m_strokeIndex = m_strokeStyleId = -1; |
| |
| clearPointerContainer(m_splitStrokes); |
| |
| delete m_outStroke; |
| m_inStroke = m_outStroke = 0; |
| |
| delete m_undo; |
| m_undo = 0; |
| |
| invalidate(); |
| } |
| |
| |
| |
| void PumpTool::invalidateCursorArea() |
| { |
| double r = m_cursor.thick + 6; |
| TPointD d(r, r); |
| invalidate(TRectD(m_cursor - d, m_cursor + d)); |
| } |
| |
| |
| |
| void PumpTool::mouseMove(const TPointD &pos, const TMouseEvent &e) |
| { |
| if (m_active || !m_enabled) |
| return; |
| |
| |
| if (tdistance2(pos, m_oldPoint) < 9.0 * sq(getPixelSize())) |
| return; |
| |
| if (!m_draw) |
| m_draw = true; |
| |
| m_oldPoint = pos; |
| |
| if (moveCursor(pos)) { |
| m_cursorEnabled = true; |
| invalidate(); |
| } else |
| m_cursorEnabled = false; |
| |
| invalidate(); |
| } |
| |
| |
| |
| bool PumpTool::moveCursor(const TPointD &pos) |
| { |
| TVectorImageP vi(getImage(false)); |
| if (vi) { |
| double t; |
| UINT index; |
| double dist2; |
| if (vi->getNearestStroke(pos, t, index, dist2)) { |
| TStroke *stroke = vi->getStroke(index); |
| if (stroke) { |
| m_cursor = stroke->getThickPoint(t); |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| |
| void PumpTool::onDeactivate() |
| { |
| m_draw = false; |
| if (m_active) { |
| m_active = false; |
| TVectorImageP vi(getImage(true)); |
| assert(!!vi && m_outStroke); |
| if (!vi || !m_outStroke) |
| return; |
| |
| clearPointerContainer(m_splitStrokes); |
| if (m_splitPars[0] == -1) { |
| delete m_outStroke; |
| m_outStroke = 0; |
| } |
| |
| |
| assert(m_strokeIndex >= 0); |
| if (m_strokeIndex >= 0) { |
| TStroke *stroke = vi->getStroke(m_strokeIndex); |
| stroke->setStyle(m_strokeStyleId); |
| } |
| |
| assert(m_undo); |
| delete m_undo; |
| m_undo = 0; |
| |
| invalidate(); |
| |
| m_strokeIndex = -1; |
| m_outStroke = 0; |
| } |
| } |
| |
| |
| |
| void PumpTool::onLeave() |
| { |
| if (!m_active) |
| m_draw = false; |
| } |
| |
| |
| |
| |
| |
| double PumpTool::actionRadius(double strokeLength) |
| { |
| double toolSize = std::max(m_toolSize.getValue(), 5.0); |
| double toolPercent = toolSize * 0.01; |
| double interpolationVal = pow(toolPercent, 5); |
| double indipendentValue = 7.0 * toolSize; |
| |
| double actionRadius = (indipendentValue) * (1.0 - interpolationVal) + |
| (strokeLength * toolPercent) * interpolationVal; |
| |
| return std::max(actionRadius, indipendentValue); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void PumpTool::splitStroke(TStroke *s) |
| { |
| assert(m_splitStrokes.empty()); |
| |
| TStroke *stroke1 = 0, *stroke2 = 0; |
| |
| |
| double totalLength = s->getLength(); |
| m_actionRadius = actionRadius(totalLength); |
| |
| |
| m_actionS1 = s->getLength(m_actionW); |
| double startLen = m_actionS1 - m_actionRadius; |
| double endLen = m_actionS1 + m_actionRadius; |
| |
| |
| int i, cpCount; |
| |
| if ((startLen <= 0 && endLen >= totalLength) || |
| (s->isSelfLoop() && totalLength < (m_actionRadius + m_actionRadius))) { |
| |
| m_splitStrokes.resize(1); |
| |
| m_splitPars[0] = -1; |
| |
| m_splitStrokes[0] = new TStroke(*s); |
| |
| m_stroke1Idx = 0; |
| stroke1 = m_splitStrokes[m_stroke1Idx]; |
| |
| TStrokeThicknessDeformation deformer(s, m_actionS1, m_actionRadius); |
| increaseControlPoints(*stroke1, deformer, getPixelSize()); |
| } else { |
| if (!s->isSelfLoop() || (startLen >= 0.0 && endLen <= totalLength)) { |
| |
| |
| |
| m_splitPars[0] = s->getParameterAtLength(std::max(startLen, 0.0)); |
| m_splitPars[1] = s->getParameterAtLength(std::min(endLen, totalLength)); |
| |
| if (m_splitPars[0] == 0.0) |
| { |
| m_splitStrokes.resize(2); |
| m_splitStrokes[0] = new TStroke; |
| m_splitStrokes[1] = new TStroke; |
| |
| s->split(m_splitPars[1], *(m_splitStrokes[0]), *(m_splitStrokes[1])); |
| |
| m_stroke1Idx = 0; |
| } else { |
| if (m_splitPars[1] == 1.0) { |
| m_splitStrokes.resize(2); |
| m_splitStrokes[0] = new TStroke; |
| m_splitStrokes[1] = new TStroke; |
| |
| s->split(m_splitPars[0], *(m_splitStrokes[0]), *(m_splitStrokes[1])); |
| } else |
| ::splitStroke(*s, m_splitPars, m_splitStrokes); |
| |
| m_stroke1Idx = 1; |
| |
| |
| m_actionS1 -= m_splitStrokes[0]->getLength(); |
| } |
| |
| stroke1 = m_splitStrokes[m_stroke1Idx]; |
| |
| |
| TStrokeThicknessDeformation deformer(stroke1, m_actionS1, m_actionRadius); |
| increaseControlPoints(*stroke1, deformer, getPixelSize()); |
| |
| m_actionS2 = 0; |
| } else { |
| |
| |
| |
| if (startLen < 0) |
| startLen += totalLength; |
| else { |
| endLen -= totalLength; |
| m_actionS1 -= totalLength; |
| } |
| |
| |
| |
| |
| m_splitPars[0] = s->getParameterAtLength(endLen); |
| m_splitPars[1] = s->getParameterAtLength(startLen); |
| |
| ::splitStroke(*s, m_splitPars, m_splitStrokes); |
| assert(m_splitStrokes.size() >= 3); |
| |
| m_stroke1Idx = 0; |
| m_stroke2Idx = 2; |
| |
| stroke1 = m_splitStrokes[m_stroke1Idx]; |
| stroke2 = m_splitStrokes[m_stroke2Idx]; |
| |
| m_actionS2 = m_actionS1 + stroke2->getLength(); |
| |
| TStrokeThicknessDeformation deformer(stroke1, m_actionS1, m_actionRadius); |
| increaseControlPoints(*stroke1, deformer, getPixelSize()); |
| TStrokeThicknessDeformation deformer2(stroke2, m_actionS2, m_actionRadius); |
| increaseControlPoints(*stroke2, deformer2, getPixelSize()); |
| |
| cpCount = stroke2->getControlPointCount(); |
| m_cpLenDiff2.resize(cpCount); |
| |
| for (i = 0; i < cpCount; ++i) |
| m_cpLenDiff2[i] = stroke2->getLengthAtControlPoint(i) - m_actionS2; |
| } |
| } |
| |
| cpCount = stroke1->getControlPointCount(); |
| m_cpLenDiff1.resize(cpCount); |
| |
| double diff; |
| for (i = 0; i < cpCount; i++) { |
| diff = stroke1->getLengthAtControlPoint(i) - m_actionS1; |
| m_cpLenDiff1[i] = (s->isSelfLoop() && stroke2 && totalLength - diff < diff) |
| ? totalLength - diff |
| : diff; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| TStroke *PumpTool::mergeStrokes(const std::vector<TStroke *> &strokes) |
| { |
| assert(strokes.size() > 0); |
| |
| TStroke *mergedStroke; |
| if (strokes.size() > 1) { |
| if (m_errorTol > 0.0) { |
| strokes[m_stroke1Idx]->reduceControlPoints(m_errorTol); |
| if (m_stroke2Idx >= 0) |
| strokes[m_stroke2Idx]->reduceControlPoints(m_errorTol); |
| } |
| |
| |
| mergedStroke = merge(strokes); |
| |
| |
| if (m_inStroke->isSelfLoop()) { |
| int cpCount = mergedStroke->getControlPointCount(); |
| |
| TThickPoint p1 = mergedStroke->getControlPoint(0); |
| TThickPoint p2 = mergedStroke->getControlPoint(cpCount - 1); |
| TThickPoint midP = 0.5 * (p1 + p2); |
| |
| mergedStroke->setControlPoint(0, midP); |
| mergedStroke->setControlPoint(cpCount - 1, midP); |
| mergedStroke->setSelfLoop(true); |
| } |
| |
| mergedStroke->outlineOptions() = strokes[0]->outlineOptions(); |
| } else { |
| mergedStroke = new TStroke(*strokes[0]); |
| if (m_errorTol > 0.0) |
| mergedStroke->reduceControlPoints(m_errorTol); |
| } |
| |
| mergedStroke->setStyle(m_strokeStyleId); |
| mergedStroke->invalidate(); |
| |
| return mergedStroke; |
| } |
| |