| #include "tenv.h" |
| #include "tproperty.h" |
| |
| #include "tools/tool.h" |
| #include "tools/toolutils.h" |
| #include "toonz/txsheethandle.h" |
| #include "tools/toolhandle.h" |
| #include "toonz/tframehandle.h" |
| #include "toonz/tcolumnhandle.h" |
| #include "toonz/txshlevelhandle.h" |
| #include "tools/strokeselection.h" |
| |
| #include "tmathutil.h" |
| #include "tstroke.h" |
| #include "tools/cursors.h" |
| #include "tundo.h" |
| #include "tvectorimage.h" |
| #include "tthreadmessage.h" |
| |
| #include "toonzqt/imageutils.h" |
| #include "toonzqt/tselectionhandle.h" |
| |
| #include "tgl.h" |
| |
| using namespace ToolUtils; |
| |
| TEnv::IntVar SnapAtIntersection("CutterToolSnapAtIntersection", 0); |
| |
| |
| namespace { |
| |
| |
| |
| |
| |
| class UndoCutter final : public ToolUtils::TToolUndo { |
| int m_newStrokeId1; |
| int m_newStrokeId2; |
| int m_pos; |
| |
| VIStroke *m_oldStroke; |
| |
| std::vector<TFilledRegionInf> *m_fillInformation; |
| std::vector<DoublePair> *m_sortedWRanges; |
| |
| int m_row; |
| int m_column; |
| |
| public: |
| UndoCutter(TXshSimpleLevel *level, const TFrameId &frameId, |
| VIStroke *oldStroke, int pos, int newStrokeId1, int newStrokeId2, |
| std::vector<TFilledRegionInf> *fillInformation, |
| std::vector<DoublePair> *sortedWRanges) |
| : TToolUndo(level, frameId) |
| , m_oldStroke(oldStroke) |
| , m_newStrokeId1(newStrokeId1) |
| , m_newStrokeId2(newStrokeId2) |
| , m_pos(pos) |
| , m_fillInformation(fillInformation) |
| , m_sortedWRanges(sortedWRanges) { |
| TTool::Application *app = TTool::getApplication(); |
| if (app) { |
| m_row = app->getCurrentFrame()->getFrame(); |
| m_column = app->getCurrentColumn()->getColumnIndex(); |
| } |
| } |
| |
| ~UndoCutter() { |
| deleteVIStroke(m_oldStroke); |
| delete m_sortedWRanges; |
| delete m_fillInformation; |
| } |
| |
| void undo() const override { |
| TTool::Application *app = TTool::getApplication(); |
| if (!app) return; |
| if (dynamic_cast<StrokeSelection *>( |
| TTool::getApplication()->getCurrentSelection()->getSelection())) |
| TTool::getApplication()->getCurrentSelection()->setSelection(0); |
| |
| if (app->getCurrentFrame()->isEditingScene()) { |
| app->getCurrentColumn()->setColumnIndex(m_column); |
| app->getCurrentFrame()->setFrame(m_row); |
| } else |
| app->getCurrentFrame()->setFid(m_frameId); |
| TVectorImageP image = m_level->getFrame(m_frameId, true); |
| assert(!!image); |
| if (!image) return; |
| QMutexLocker lock(image->getMutex()); |
| VIStroke *stroke; |
| |
| stroke = image->getStrokeById(m_newStrokeId1); |
| if (stroke) image->deleteStroke(stroke); |
| |
| stroke = image->getStrokeById(m_newStrokeId2); |
| if (stroke) image->deleteStroke(stroke); |
| |
| stroke = cloneVIStroke(m_oldStroke); |
| |
| image->insertStrokeAt(stroke, m_pos); |
| |
| UINT size = m_fillInformation->size(); |
| if (!size) { |
| app->getCurrentXsheet()->notifyXsheetChanged(); |
| notifyImageChanged(); |
| return; |
| } |
| |
| image->findRegions(); |
| TRegion *reg; |
| for (UINT i = 0; i < size; i++) { |
| reg = image->getRegion((*m_fillInformation)[i].m_regionId); |
| assert(reg); |
| if (reg) reg->setStyle((*m_fillInformation)[i].m_styleId); |
| } |
| app->getCurrentXsheet()->notifyXsheetChanged(); |
| notifyImageChanged(); |
| } |
| |
| void redo() const override { |
| TTool::Application *app = TTool::getApplication(); |
| if (!app) return; |
| if (app->getCurrentFrame()->isEditingScene()) { |
| app->getCurrentColumn()->setColumnIndex(m_column); |
| app->getCurrentFrame()->setFrame(m_row); |
| } else |
| app->getCurrentFrame()->setFid(m_frameId); |
| TVectorImageP image = m_level->getFrame(m_frameId, true); |
| assert(!!image); |
| if (!image) return; |
| QMutexLocker lock(image->getMutex()); |
| |
| bool isSelfLoop = image->getStroke(m_pos)->isSelfLoop(); |
| image->splitStroke(m_pos, *m_sortedWRanges); |
| |
| image->getStroke(m_pos)->setId(m_newStrokeId1); |
| if (!isSelfLoop && m_sortedWRanges->size() == 2) |
| image->getStroke(m_pos + 1)->setId(m_newStrokeId2); |
| |
| app->getCurrentXsheet()->notifyXsheetChanged(); |
| notifyImageChanged(); |
| } |
| |
| int getSize() const override { |
| return sizeof(*this) + |
| m_fillInformation->capacity() * sizeof(TFilledRegionInf) + 500; |
| } |
| |
| QString getToolName() override { return QString("Cutter Tool"); } |
| }; |
| |
| |
| |
| |
| |
| class CutterTool final : public TTool { |
| public: |
| bool m_mouseDown; |
| |
| TPointD m_vTan; |
| |
| TThickPoint m_cursor; |
| TPointD m_speed; |
| int m_cursorId; |
| double m_pW; |
| |
| int m_lockedStrokeIndex; |
| |
| TPropertyGroup m_prop; |
| TBoolProperty m_snapAtIntersection; |
| |
| CutterTool() |
| : TTool("T_Cutter") |
| , m_mouseDown(false) |
| , m_cursorId(ToolCursor::CutterCursor) |
| , m_snapAtIntersection("Snap At Intersection", false) { |
| bind(TTool::VectorImage); |
| m_prop.bind(m_snapAtIntersection); |
| m_snapAtIntersection.setId("Snap"); |
| } |
| |
| ToolType getToolType() const override { return TTool::LevelWriteTool; } |
| |
| void draw() override { |
| |
| |
| |
| |
| const double pixelSize = getPixelSize(); |
| |
| double len = m_cursor.thick + 15 * pixelSize; |
| |
| if (m_speed != TPointD(0, 0)) { |
| TPointD v = m_speed; |
| TPointD p = (TPointD)m_cursor; |
| |
| v = rotate90(v); |
| v = normalize(v); |
| |
| v = v * (len); |
| |
| tglColor(TPixelD(0.1, 0.9, 0.1)); |
| tglDrawSegment(p - v, p + v); |
| } |
| |
| } |
| |
| double getNearestSnapAtIntersection(TStroke *selfStroke, double w) { |
| TVectorImageP vi = TImageP(getImage(false)); |
| if (!vi) { |
| return w; |
| } |
| |
| std::vector<DoublePair> intersections; |
| int i, strokeNumber = vi->getStrokeCount(); |
| double diff; |
| double nearestW = 1000; |
| double minDiff = 1000; |
| |
| |
| intersect(selfStroke, selfStroke, intersections, false); |
| for (auto &intersection : intersections) { |
| if (areAlmostEqual(intersection.first, 0, 1e-6)) { |
| continue; |
| } |
| if (areAlmostEqual(intersection.second, 1, 1e-6)) { |
| continue; |
| } |
| |
| diff = std::abs(intersection.first - w); |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.first; |
| } |
| |
| diff = std::abs(intersection.second - w); |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.second; |
| } |
| |
| if (selfStroke->isSelfLoop()) { |
| diff = std::abs(1 - intersection.first) + w; |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.first; |
| } |
| |
| diff = intersection.first + std::abs(1 - w); |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.first; |
| } |
| |
| diff = std::abs(1 - intersection.second) + w; |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.second; |
| } |
| |
| diff = intersection.second + std::abs(1 - w); |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.second; |
| } |
| } |
| } |
| |
| for (i = 0; i < strokeNumber; ++i) { |
| TStroke *stroke = vi->getStroke(i); |
| if (stroke == selfStroke) { |
| continue; |
| } |
| |
| intersect(selfStroke, stroke, intersections, false); |
| for (auto &intersection : intersections) { |
| diff = std::abs(intersection.first - w); |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.first; |
| } |
| |
| if (selfStroke->isSelfLoop()) { |
| diff = std::abs(1 - intersection.first) + w; |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.first; |
| } |
| |
| diff = intersection.first + std::abs(1 - w); |
| if (diff < minDiff) { |
| minDiff = diff; |
| nearestW = intersection.first; |
| } |
| } |
| } |
| } |
| |
| if (nearestW >= 0 && nearestW <= 1) { |
| return nearestW; |
| } |
| return w; |
| } |
| |
| void leftButtonDown(const TPointD &pos, const TMouseEvent &e) override { |
| if (getViewer() && getViewer()->getGuidedStrokePickerMode()) { |
| getViewer()->doPickGuideStroke(pos); |
| return; |
| } |
| |
| TVectorImageP vi = TImageP(getImage(true)); |
| if (!vi) return; |
| QMutexLocker sl(vi->getMutex()); |
| |
| double dist, pW; |
| UINT strokeIndex; |
| |
| TStroke *strokeRef; |
| |
| if (getNearestStrokeWithLock(pos, pW, strokeIndex, dist, |
| e.isCtrlPressed()) && |
| pW >= 0 && pW <= 1) { |
| double w; |
| |
| strokeRef = vi->getStroke(strokeIndex); |
| |
| double hitPointLen = strokeRef->getLength(pW); |
| double totalLen = strokeRef->getLength(); |
| |
| double len = hitPointLen; |
| |
| if (!strokeRef->isSelfLoop()) { |
| if (len < TConsts::epsilon) |
| w = 0; |
| else |
| w = strokeRef->getParameterAtLength(len); |
| |
| if (len > totalLen - TConsts::epsilon) |
| w = 1; |
| else |
| w = strokeRef->getParameterAtLength(len); |
| } else { |
| if (len < 0) len += totalLen; |
| |
| if (len > totalLen) len -= totalLen; |
| |
| w = strokeRef->getParameterAtLength(len); |
| } |
| |
| if (m_snapAtIntersection.getValue()) { |
| w = getNearestSnapAtIntersection(strokeRef, w); |
| } |
| |
| std::vector<DoublePair> *sortedWRanges = new std::vector<DoublePair>; |
| |
| if (strokeRef->isSelfLoop()) { |
| sortedWRanges->push_back(std::make_pair(0, w)); |
| sortedWRanges->push_back(std::make_pair(w, 1)); |
| } else { |
| if (w == 0 || w == 1) |
| sortedWRanges->push_back(std::make_pair(0, 1)); |
| else { |
| sortedWRanges->push_back(std::make_pair(0, w)); |
| sortedWRanges->push_back(std::make_pair(w, 1)); |
| } |
| } |
| |
| std::vector<TFilledRegionInf> *fillInformation = |
| new std::vector<TFilledRegionInf>; |
| ImageUtils::getFillingInformationOverlappingArea(vi, *fillInformation, |
| strokeRef->getBBox()); |
| |
| VIStroke *oldStroke = cloneVIStroke(vi->getVIStroke(strokeIndex)); |
| bool isSelfLoop = vi->getStroke(strokeIndex)->isSelfLoop(); |
| vi->splitStroke(strokeIndex, *sortedWRanges); |
| |
| TUndo *nundo; |
| |
| TXshSimpleLevel *sl = |
| TTool::getApplication()->getCurrentLevel()->getSimpleLevel(); |
| assert(sl); |
| TFrameId id = getCurrentFid(); |
| if (isSelfLoop || sortedWRanges->size() == 1) { |
| nundo = new UndoCutter(sl, id, oldStroke, strokeIndex, |
| vi->getStroke(strokeIndex)->getId(), -1, |
| fillInformation, sortedWRanges); |
| } else { |
| assert(strokeIndex + 1 < vi->getStrokeCount()); |
| nundo = new UndoCutter(sl, id, oldStroke, strokeIndex, |
| vi->getStroke(strokeIndex)->getId(), |
| vi->getStroke(strokeIndex + 1)->getId(), |
| fillInformation, sortedWRanges); |
| } |
| |
| TUndoManager::manager()->add(nundo); |
| |
| invalidate(); |
| notifyImageChanged(); |
| } |
| invalidate(); |
| } |
| |
| void mouseMove(const TPointD &pos, const TMouseEvent &e) override { |
| TVectorImageP vi = TImageP(getImage(true)); |
| if (!vi) { |
| m_speed = TPointD(0, 0); |
| return; |
| } |
| |
| |
| double dist, pW; |
| UINT stroke; |
| |
| if (getNearestStrokeWithLock(pos, pW, stroke, dist, e.isCtrlPressed())) { |
| TStroke *strokeRef = vi->getStroke(stroke); |
| |
| if (m_snapAtIntersection.getValue()) { |
| pW = getNearestSnapAtIntersection(strokeRef, pW); |
| } |
| |
| m_speed = strokeRef->getSpeed(pW); |
| m_cursor = strokeRef->getThickPoint(pW); |
| m_pW = pW; |
| } else { |
| m_speed = TPointD(0, 0); |
| } |
| invalidate(); |
| } |
| |
| void onLeave() override { m_speed = TPointD(0, 0); } |
| |
| void onActivate() override { |
| m_snapAtIntersection.setValue(SnapAtIntersection ? 1 : 0); |
| } |
| void onEnter() override { |
| if ((TVectorImageP)getImage(false)) |
| m_cursorId = ToolCursor::CutterCursor; |
| else |
| m_cursorId = ToolCursor::CURSOR_NO; |
| } |
| |
| int getCursorId() const override { |
| if (m_viewer && m_viewer->getGuidedStrokePickerMode()) |
| return m_viewer->getGuidedStrokePickerCursor(); |
| return m_cursorId; |
| } |
| |
| void updateTranslation() override { |
| m_snapAtIntersection.setQStringName(QObject::tr("Snap At Intersection")); |
| } |
| |
| TPropertyGroup *getProperties(int targetType) override { return &m_prop; } |
| |
| bool onPropertyChanged(std::string propertyName) override { |
| SnapAtIntersection = (int)(m_snapAtIntersection.getValue()); |
| return true; |
| } |
| |
| bool getNearestStrokeWithLock(const TPointD &p, double &outW, |
| UINT &strokeIndex, double &dist2, bool lock) { |
| TVectorImageP vi = TImageP(getImage(false)); |
| if (!vi) return false; |
| |
| if (m_lockedStrokeIndex >= vi->getStrokeCount()) { |
| m_lockedStrokeIndex = -1; |
| } |
| |
| if (lock && m_lockedStrokeIndex >= 0) { |
| TStroke *stroke = vi->getStroke(m_lockedStrokeIndex); |
| strokeIndex = m_lockedStrokeIndex; |
| return stroke->getNearestW(p, outW, dist2); |
| } |
| |
| UINT index; |
| if (vi->getNearestStroke(p, outW, index, dist2)) { |
| m_lockedStrokeIndex = index; |
| strokeIndex = index; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } cutterTool; |
| |
| |
| } |
| |
| |
| |