diff --git a/toonz/sources/tnztools/modifiers/modifiertest.cpp b/toonz/sources/tnztools/modifiers/modifiertest.cpp index 72f4774..ba33792 100644 --- a/toonz/sources/tnztools/modifiers/modifiertest.cpp +++ b/toonz/sources/tnztools/modifiers/modifiertest.cpp @@ -26,18 +26,19 @@ TTrackPoint TModifierTest::Interpolator::interpolateFromOriginal(double original double angle = TTrack::interpolationLinear( handler->angles[i0], handler->angles[i1], frac ); - angle = angle*speed + this->angle; - - double r = radius*p.pressure; - double s = sin(angle); + double a = angle*speed + this->angle; + + double kr = 1 - 1/(angle + 1); + double s = sin(a); + double r = radius*p.pressure*kr*s; double d = fabs(2.0*radius); if (fabs(speed) > TConsts::epsilon) d /= fabs(speed); TPointD tangent = track.original->calcTangent(originalIndex, d); - p.position.x -= tangent.y * s * r; - p.position.y += tangent.x * s * r; + p.position.x -= tangent.y * r; + p.position.y += tangent.x * r; p.pressure *= fabs(s); return p; diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.cpp b/toonz/sources/tnztools/toonzvectorbrushtool.cpp index b7eb5d6..1e01069 100644 --- a/toonz/sources/tnztools/toonzvectorbrushtool.cpp +++ b/toonz/sources/tnztools/toonzvectorbrushtool.cpp @@ -508,7 +508,6 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType) , m_joinStyle("Join") , m_miterJoinLimit("Miter:", 0, 100, 4) , m_assistants("Assistants", true) - , m_firstStroke() , m_styleId() , m_minThick() , m_maxThick() @@ -522,7 +521,6 @@ ToonzVectorBrushTool::ToonzVectorBrushTool(std::string name, int targetType) , m_snapped() , m_snappedSelf() , m_active() - , m_isPrompting() , m_firstTime(true) , m_isPath() , m_presetsLoaded() @@ -741,6 +739,23 @@ void ToonzVectorBrushTool::inputMouseMove( //-------------------------------------------------------------------------------------------------- +void ToonzVectorBrushTool::deleteStrokes(StrokeList &strokes) { + for(StrokeList::iterator i = strokes.begin(); i != strokes.end(); ++i) + delete *i; + strokes.clear(); +} + +//-------------------------------------------------------------------------------------------------- + +void ToonzVectorBrushTool::copyStrokes(StrokeList &dst, const StrokeList &src) { + deleteStrokes(dst); + dst.reserve(src.size()); + for(StrokeList::const_iterator i = src.begin(); i != src.end(); ++i) + dst.push_back(new TStroke(**i)); +} + +//-------------------------------------------------------------------------------------------------- + void ToonzVectorBrushTool::inputSetBusy(bool busy) { if (m_active == busy) return; @@ -748,6 +763,9 @@ void ToonzVectorBrushTool::inputSetBusy(bool busy) { // begin painting ////////////////////////// + m_styleId = 0; + m_tracks.clear(); + TTool::Application *app = TTool::getApplication(); if (!app) return; @@ -795,26 +813,32 @@ void ToonzVectorBrushTool::inputSetBusy(bool busy) { // clear tracks automatically when return from this function struct Cleanup { ToonzVectorBrushTool &owner; - inline ~Cleanup() { owner.m_track.clear(); owner.invalidate(); } + inline ~Cleanup() { owner.m_tracks.clear(); owner.invalidate(); } } cleanup = {*this}; - + + // remove empty tracks + for(TrackList::iterator i = m_tracks.begin(); i != m_tracks.end(); ) + if (i->isEmpty()) i = m_tracks.erase(i); else ++i; + + if (m_tracks.empty()) + return; // make motion path (if need) if (m_isPath) { double error = 20.0 * m_pixelSize; - TStroke *stroke = m_track.makeStroke(error); + m_tracks.resize(1); + TStroke *stroke = m_tracks.front().makeStroke(error); TVectorImageP vi = getImage(true); if (!isJustCreatedSpline(vi.getPointer())) { - m_isPrompting = true; + m_currentColor = TPixel32::Green; invalidate(); int ret = DVGui::MsgBox( QString("Are you sure you want to replace the motion path?"), QObject::tr("Yes"), QObject::tr("No"), 0 ); - m_isPrompting = false; if (ret != 1) return; // 1 here means "Yes" button } @@ -833,51 +857,54 @@ void ToonzVectorBrushTool::inputSetBusy(bool busy) { return; // done with motion path } - // paint regular strokes - - if (m_track.isEmpty()) - return; - - - // prepare stroke - - m_track.filterPoints(); - double error = 30.0/(1 + 0.5 * m_accuracy.getValue())*m_pixelSize; - TStroke *stroke = m_track.makeStroke(error); - - stroke->setStyle(m_styleId); - m_styleId = 0; - TStroke::OutlineOptions &options = stroke->outlineOptions(); - options.m_capStyle = m_capStyle.getIndex(); - options.m_joinStyle = m_joinStyle.getIndex(); - options.m_miterUpper = m_miterJoinLimit.getValue(); - - if ( stroke->getControlPointCount() == 3 - && stroke->getControlPoint(0) != stroke->getControlPoint(2) ) - // gli stroke con solo 1 chunk vengono - // fatti dal tape tool...e devono venir - // riconosciuti come speciali di - // autoclose proprio dal fatto che - // hanno 1 solo chunk. - stroke->insertControlPoints(0.5); - - - // lock image - TVectorImageP vi = getImage(true); QMutexLocker lock(vi->getMutex()); TTool::Application *app = TTool::getApplication(); - + // prepare strokes + + StrokeList strokes; + strokes.reserve(m_tracks.size()); + for(TrackList::iterator i = m_tracks.begin(); i != m_tracks.end(); ++i) { + StrokeGenerator &track = *i; + + track.filterPoints(); + double error = 30.0/(1 + 0.5 * m_accuracy.getValue())*m_pixelSize; + TStroke *stroke = track.makeStroke(error); + + stroke->setStyle(m_styleId); + + TStroke::OutlineOptions &options = stroke->outlineOptions(); + options.m_capStyle = m_capStyle.getIndex(); + options.m_joinStyle = m_joinStyle.getIndex(); + options.m_miterUpper = m_miterJoinLimit.getValue(); + + if ( stroke->getControlPointCount() == 3 + && stroke->getControlPoint(0) != stroke->getControlPoint(2) ) + // gli stroke con solo 1 chunk vengono + // fatti dal tape tool...e devono venir + // riconosciuti come speciali di + // autoclose proprio dal fatto che + // hanno 1 solo chunk. + stroke->insertControlPoints(0.5); + + if (!m_frameRange.getIndex() && track.getLoop()) + stroke->setSelfLoop(true); + + strokes.push_back(stroke); + } + // add stroke to image if (m_frameRange.getIndex()) { + // frame range stroke if (m_firstFrameId == -1) { - m_firstStroke = new TStroke(*stroke); + // remember strokes for first srame + copyStrokes(m_firstStrokes, strokes); m_firstFrameId = getFrameId(); - m_rangeTrack = m_track; + m_rangeTracks = m_tracks; if (app) { m_col = app->getCurrentColumn()->getColumnIndex(); @@ -891,18 +918,25 @@ void ToonzVectorBrushTool::inputSetBusy(bool busy) { } } else if (m_firstFrameId == getFrameId()) { - if (m_firstStroke) delete m_firstStroke; - m_firstStroke = new TStroke(*stroke); - m_rangeTrack = m_track; + // painted of first frame agein, so + // just replace the remembered strokes for first frame + copyStrokes(m_firstStrokes, strokes); + m_rangeTracks = m_tracks; } else { + // paint frame range strokes TFrameId currentId = getFrameId(); int curCol = app ? app->getCurrentColumn()->getColumnIndex() : 0; int curFrame = app ? app->getCurrentFrame()->getFrame() : 0; - doFrameRangeStrokes( - m_firstFrameId, m_firstStroke, getFrameId(), stroke, - m_frameRange.getIndex(), m_breakAngles.getValue(), false, false, - m_firstFrameRange ); + if (size_t count = std::min(m_firstStrokes.size(), strokes.size())) { + TUndoManager::manager()->beginBlock(); + for(size_t i = 0; i < count; ++i) + doFrameRangeStrokes( + m_firstFrameId, m_firstStrokes[i], getFrameId(), strokes[i], + m_frameRange.getIndex(), m_breakAngles.getValue(), false, false, + m_firstFrameRange ); + TUndoManager::manager()->endBlock(); + } if (m_inputmanager.state.isKeyPressed(TInputState::Key::control)) { if (app && m_firstFrameId > currentId) { @@ -915,8 +949,8 @@ void ToonzVectorBrushTool::inputSetBusy(bool busy) { } resetFrameRange(); - m_firstStroke = new TStroke(*stroke); - m_rangeTrack = m_track; + copyStrokes(m_firstStrokes, strokes); + m_rangeTracks = m_tracks; m_firstFrameId = currentId; m_firstFrameRange = false; } else { @@ -932,27 +966,30 @@ void ToonzVectorBrushTool::inputSetBusy(bool busy) { } } } else { - if (m_track.getLoop()) - stroke->setSelfLoop(true); - - addStrokeToImage(app, vi, stroke, m_breakAngles.getValue(), - false, false, m_isFrameCreated, m_isLevelCreated); - - if ((Preferences::instance()->getGuidedDrawingType() == 1 || - Preferences::instance()->getGuidedDrawingType() == 2) && - Preferences::instance()->getGuidedAutoInbetween()) - { - TFrameId fId = getFrameId(); - doGuidedAutoInbetween(fId, vi, stroke, m_breakAngles.getValue(), false, - false, false); - if (app->getCurrentFrame()->isEditingScene()) - app->getCurrentFrame()->setFrame( app->getCurrentFrame()->getFrameIndex() ); - else - app->getCurrentFrame()->setFid(fId); + // regular paint strokes + TUndoManager::manager()->beginBlock(); + for(StrokeList::iterator i = strokes.begin(); i != strokes.end(); ++i) { + TStroke *stroke = *i; + addStrokeToImage(app, vi, stroke, m_breakAngles.getValue(), + false, false, m_isFrameCreated, m_isLevelCreated); + + if ((Preferences::instance()->getGuidedDrawingType() == 1 || + Preferences::instance()->getGuidedDrawingType() == 2) && + Preferences::instance()->getGuidedAutoInbetween()) + { + TFrameId fId = getFrameId(); + doGuidedAutoInbetween(fId, vi, stroke, m_breakAngles.getValue(), false, + false, false); + if (app->getCurrentFrame()->isEditingScene()) + app->getCurrentFrame()->setFrame( app->getCurrentFrame()->getFrameIndex() ); + else + app->getCurrentFrame()->setFid(fId); + } } + TUndoManager::manager()->endBlock(); } - delete stroke; + deleteStrokes(strokes); } //-------------------------------------------------------------------------------------------------- @@ -961,32 +998,39 @@ void ToonzVectorBrushTool::inputPaintTracks(const TTrackList &tracks) { if (tracks.empty()) return; TRectD invalidateRect; - - const TTrack &track = *tracks.front(); - while(track.pointsRemoved) { - m_track.pop(); - --track.pointsRemoved; - } - - while(track.pointsAdded) { - const TTrackPoint &p = track.current(); - double t = computeThickness(p.pressure, m_thickness, m_pressure.getValue(), m_isPath); - m_track.add(TThickPoint(p.position, t), 0); - --track.pointsAdded; - } - - bool loop = m_snappedSelf - && track.fixedFinished() - && !track.empty() - && areAlmostEqual(track.front().position, track.back().position); - m_track.setLoop(loop); - - invalidateRect += m_track.getLastModifiedRegion(); - TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5); - invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick); - m_brushPos = m_mousePos = track.current().position; - invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick); + size_t count = m_isPath ? 1 : tracks.size(); + m_tracks.resize(count); + for(size_t i = 0; i < count; ++i) { + const TTrack &track = *tracks[i]; + StrokeGenerator &gen = m_tracks[i]; + + while(track.pointsRemoved) { + gen.pop(); + --track.pointsRemoved; + } + + while(track.pointsAdded) { + const TTrackPoint &p = track.current(); + double t = computeThickness(p.pressure, m_thickness, m_pressure.getValue(), m_isPath); + gen.add(TThickPoint(p.position, t), 0); + --track.pointsAdded; + } + + bool loop = m_snappedSelf + && track.fixedFinished() + && !track.empty() + && areAlmostEqual(track.front().position, track.back().position); + gen.setLoop(loop); + + invalidateRect += gen.getLastModifiedRegion(); + if (!i) { + TPointD halfThick(m_maxThick * 0.5, m_maxThick * 0.5); + invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick); + m_brushPos = m_mousePos = track.current().position; + invalidateRect += TRectD(m_brushPos - halfThick, m_brushPos + halfThick); + } + } if (!invalidateRect.isEmpty()) { if (m_isPath) { @@ -1080,7 +1124,7 @@ void ToonzVectorBrushTool::handleMouseEvent(MouseEventType type, m_inputmanager.hoverEvent(hovers); } else if (pickerMode) { - if (type == ME_DOWN) getViewer()->doPickGuideStroke(snappedPos); + if (type == ME_DOWN) getViewer()->doPickGuideStroke(pos); } else { int deviceId = e.isTablet() ? 1 : 0; bool hasPressure = e.isTablet(); @@ -1382,8 +1426,8 @@ void ToonzVectorBrushTool::snap(const TPointD &pos, bool snapEnabled, bool withS } // finally snap to first point of track (self snap) - if (withSelfSnap && !m_track.isEmpty()) { - TPointD p = m_track.getFirstPoint(); + if (withSelfSnap && !m_tracks.empty() && !m_tracks.front().isEmpty()) { + TPointD p = m_tracks.front().getFirstPoint(); double d2 = tdistance2(pos, p); if (d2 < minDistance2) { m_snappedSelf = true; @@ -1428,8 +1472,9 @@ void ToonzVectorBrushTool::draw() { return; // draw track - tglColor(m_isPrompting ? TPixel32::Green : m_currentColor); - m_track.drawAllFragments(); + tglColor(m_currentColor); + for(TrackList::iterator i = m_tracks.begin(); i != m_tracks.end(); ++i) + i->drawAllFragments(); // draw snapping double snapMarkRadius = 6.0 * m_pixelSize; @@ -1443,13 +1488,14 @@ void ToonzVectorBrushTool::draw() { } // frame range - if (m_firstStroke) { - glColor3d(1.0, 0.0, 0.0); - m_rangeTrack.drawAllFragments(); - glColor3d(0.0, 0.6, 0.0); + for(TrackList::iterator i = m_rangeTracks.begin(); i != m_rangeTracks.end(); ++i) { + if (i->isEmpty()) continue; TPointD offset1 = TPointD(5, 5); TPointD offset2 = TPointD(-offset1.x, offset1.y); - TPointD point = m_rangeTrack.getFirstPoint(); + TPointD point = i->getFirstPoint(); + glColor3d(1.0, 0.0, 0.0); + i->drawAllFragments(); + glColor3d(0.0, 0.6, 0.0); tglDrawSegment(point - offset1, point + offset1); tglDrawSegment(point - offset2, point + offset2); } @@ -1500,12 +1546,9 @@ TPropertyGroup *ToonzVectorBrushTool::getProperties(int idx) { //------------------------------------------------------------------ void ToonzVectorBrushTool::resetFrameRange() { - m_rangeTrack.clear(); + m_rangeTracks.clear(); m_firstFrameId = -1; - if (m_firstStroke) { - delete m_firstStroke; - m_firstStroke = nullptr; - } + deleteStrokes(m_firstStrokes); m_firstFrameRange = true; } diff --git a/toonz/sources/tnztools/toonzvectorbrushtool.h b/toonz/sources/tnztools/toonzvectorbrushtool.h index 1ae9926..579894a 100644 --- a/toonz/sources/tnztools/toonzvectorbrushtool.h +++ b/toonz/sources/tnztools/toonzvectorbrushtool.h @@ -158,6 +158,11 @@ public: bool drawStroke = true); protected: + typedef std::vector<StrokeGenerator> TrackList; + typedef std::vector<TStroke*> StrokeList; + void deleteStrokes(StrokeList &strokes); + void copyStrokes(StrokeList &dst, const StrokeList &src); + void snap(const TPointD &pos, bool snapEnabled, bool withSelfSnap = false); enum MouseEventType { ME_DOWN, ME_DRAG, ME_UP, ME_MOVE }; @@ -192,10 +197,10 @@ protected: #ifndef NDEBUG TSmartPointerT<TModifierTest> m_modifierTest; #endif - - StrokeGenerator m_track; - StrokeGenerator m_rangeTrack; - TStroke *m_firstStroke; + + TrackList m_tracks; + TrackList m_rangeTracks; + StrokeList m_firstStrokes; TFrameId m_firstFrameId, m_veryFirstFrameId; TPixel32 m_currentColor; int m_styleId; // bwtodo: remove @@ -217,10 +222,8 @@ protected: VectorBrushPresetManager m_presetsManager; //!< Manager for presets of this tool instance - bool m_active, - m_isPrompting, //!< Whether the tool is prompting for spline - //! substitution. - m_firstTime, m_isPath, m_presetsLoaded, m_firstFrameRange; + bool m_active, m_firstTime, m_isPath, + m_presetsLoaded, m_firstFrameRange; bool m_propertyUpdating; };