diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt index 5d6d83c..2deca95 100644 --- a/toonz/sources/tnztools/CMakeLists.txt +++ b/toonz/sources/tnztools/CMakeLists.txt @@ -131,6 +131,7 @@ set(SOURCES modifiers/modifiersegmentation.cpp assistants/guidelineline.cpp assistants/assistantvanishingpoint.cpp + assistants/assistantline.cpp editassistantstool.cpp ) diff --git a/toonz/sources/tnztools/assistants/assistantline.cpp b/toonz/sources/tnztools/assistants/assistantline.cpp new file mode 100644 index 0000000..642fb53 --- /dev/null +++ b/toonz/sources/tnztools/assistants/assistantline.cpp @@ -0,0 +1,381 @@ + + +#include "guidelineline.h" + +// TnzTools includes +#include <tools/assistant.h> + +// TnzCore includes +#include <tgl.h> + + +//***************************************************************************************** +// TAssistantLine implementation +//***************************************************************************************** + +class DVAPI TAssistantLine final : public TAssistant { + Q_DECLARE_TR_FUNCTIONS(TAssistantVanishingPoint) +public: + const TStringId m_idRestricktA; + const TStringId m_idRestricktB; + const TStringId m_idParallel; + const TStringId m_idGrid; + const TStringId m_idPerspective; + +protected: + TAssistantPoint &m_a; + TAssistantPoint &m_b; + TAssistantPoint &m_grid0; + TAssistantPoint &m_grid1; + +public: + TAssistantLine(TMetaObject &object): + TAssistant(object), + m_idRestricktA("restrictA"), + m_idRestricktB("restrictB"), + m_idParallel("parallel"), + m_idGrid("grid"), + m_idPerspective("perspective"), + m_a( addPoint("a", TAssistantPoint::CircleCross) ), + m_b( addPoint("b", TAssistantPoint::Circle, TPointD(100.0, 0.0)) ), + m_grid0( addPoint("grid0", TAssistantPoint::CircleDoubleDots, TPointD( 0.0,-50.0)) ), + m_grid1( addPoint("grid1", TAssistantPoint::CircleDots, TPointD( 25.0,-75.0)) ) + { + addProperty( new TBoolProperty(m_idRestricktA.str(), getRestrictA()) ); + addProperty( new TBoolProperty(m_idRestricktB.str(), getRestrictB()) ); + addProperty( new TBoolProperty(m_idParallel.str(), getParallel()) ); + addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) ); + addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) ); + } + + static QString getLocalName() + { return tr("Line"); } + + void updateTranslation() const override { + setTranslation(m_idRestricktA, tr("Restrict A")); + setTranslation(m_idRestricktB, tr("Restrict B")); + setTranslation(m_idParallel, tr("Parallel")); + setTranslation(m_idGrid, tr("Grid")); + setTranslation(m_idPerspective, tr("Perspective")); + } + + inline bool getRestrictA() const + { return data()[m_idRestricktA].getBool(); } + inline bool getRestrictB() const + { return data()[m_idRestricktB].getBool(); } + inline bool getParallel() const + { return data()[m_idParallel].getBool(); } + inline bool getGrid() const + { return data()[m_idGrid].getBool(); } + inline bool getPerspective() const + { return data()[m_idPerspective].getBool(); } + + void onDataChanged(const TVariant &value) override { + TAssistant::onDataChanged(value); + m_grid0.visible = getGrid() + || (getParallel() && (getRestrictA() || getRestrictB())); + m_grid1.visible = getGrid(); + } + +private: + void fixGrid1(const TPointD &previousA, const TPointD &previousB) { + TPointD dx = previousB - previousA; + double l = norm2(dx); + if (l <= TConsts::epsilon*TConsts::epsilon) return; + dx = dx*(1.0/sqrt(l)); + TPointD dy(-dx.y, dx.x); + + TPointD g1 = m_grid1.position - m_grid0.position; + g1 = TPointD(dx*g1, dy*g1); + + dx = m_b.position - m_a.position; + l = norm2(dx); + if (l <= TConsts::epsilon*TConsts::epsilon) return; + dx = dx*(1.0/sqrt(l)); + dy = TPointD(-dx.y, dx.x); + + m_grid1.position = m_grid0.position + dx*g1.x + dy*g1.y; + } + +public: + void onMovePoint(TAssistantPoint &point, const TPointD &position) override { + TPointD previousA = m_a.position; + TPointD previousB = m_b.position; + point.position = position; + if (&point != &m_grid1) + fixGrid1(previousA, previousB); + } + + void getGuidelines( + const TPointD &position, + const TAffine &toTool, + TGuidelineList &outGuidelines ) const override + { + bool restrictA = getRestrictA(); + bool restrictB = getRestrictB(); + bool parallel = getParallel(); + bool perspective = getPerspective(); + + TPointD a = toTool*m_a.position; + TPointD b = toTool*m_b.position; + TPointD ab = b - a; + double abLen2 = norm2(ab); + if (abLen2 < TConsts::epsilon*TConsts::epsilon) return; + + if (parallel) { + TPointD abp = rotate90(ab); + TPointD ag = toTool*m_grid0.position - a; + double k = abp*ag; + if (fabs(k) <= TConsts::epsilon) { + if (restrictA || restrictB) return; + a = position; + } else { + k = (abp*(position - a))/k; + a = a + ag*k; + } + if (perspective && (restrictA || restrictB)) + ab = ab*k; + b = a + ab; + } + + if (restrictA && restrictB) + outGuidelines.push_back(TGuidelineP( + new TGuidelineLine( + getEnabled(), getMagnetism(), a, b ))); + else if (restrictA) + outGuidelines.push_back(TGuidelineP( + new TGuidelineRay( + getEnabled(), getMagnetism(), a, b ))); + else if (restrictB) + outGuidelines.push_back(TGuidelineP( + new TGuidelineRay( + getEnabled(), getMagnetism(), b, a ))); // b first + else + outGuidelines.push_back(TGuidelineP( + new TGuidelineInfiniteLine( + getEnabled(), getMagnetism(), a, b ))); + } + +private: + bool calcPerspectiveStep( + double minStep, + double minX, + double maxX, + double x0, + double x1, + double x2, + double &outK, + double &outMin, + double &outMax ) const + { + outK = outMin = outMax = 0.0; + + double dx1 = x1 - x0; + double dx2 = x2 - x0; + if (fabs(dx1) <= TConsts::epsilon) return false; + if (fabs(dx2) <= TConsts::epsilon) return false; + if ((dx1 < 0.0) != (dx2 < 0.0)) dx2 = -dx2; + if (fabs(dx2 - dx1) <= minStep) return false; + if (fabs(dx2) < fabs(dx1)) std::swap(dx1, dx2); + + if (x0 <= minX + TConsts::epsilon && dx1 < 0.0) return false; + if (x0 >= maxX - TConsts::epsilon && dx1 > 0.0) return false; + + outK = dx2/dx1; + double minI = log(minStep/fabs(dx1*(1.0 - 1.0/outK)))/log(outK); + outMin = dx1*pow(outK, floor(minI - TConsts::epsilon)); + if (fabs(outMin) < TConsts::epsilon) return false; + outMax = (dx1 > 0.0 ? maxX : minX) - x0; + return true; + } + + void drawRuler(const TPointD &a, const TPointD &b, bool perspective) const { + const double minStep = 10.0; + double alpha = getDrawingGridAlpha(); + + TPointD direction = b - a; + double l2 = norm2(direction); + if (l2 <= TConsts::epsilon*TConsts::epsilon) return; + double dirLen = sqrt(l2); + TPointD dirProj = direction*(1.0/l2); + + double xg0 = dirProj*(m_grid0.position - a); + double xg1 = dirProj*(m_grid1.position - a); + + if (perspective) { + // draw perspective + double xa0 = dirProj*(m_a.position - a); + double k = 0.0, begin = 0.0, end = 0.0; + if (!calcPerspectiveStep(minStep/dirLen, 0.0, 1.0, xa0, xg0, xg1, k, begin, end)) return; + for(double x = begin; fabs(x) < fabs(end); x *= k) + drawDot(a + direction*(xa0 + x), alpha); + } else { + // draw linear + double dx = fabs(xg1 - xg0); + if (dx*dirLen < minStep) return; + for(double x = xg0 - floor(xg0/dx)*dx; x < 1.0; x += dx) + drawDot(a + direction*x, alpha); + } + } + + void drawLine( + const TAffine &matrix, + const TAffine &matrixInv, + double pixelSize, + const TPointD &a, + const TPointD &b, + bool restrictA, + bool restrictB, + double alpha ) const + { + const TRectD oneBox(-1.0, -1.0, 1.0, 1.0); + TPointD aa = matrix*a; + TPointD bb = matrix*b; + if ( restrictA && restrictB ? TGuidelineLineBase::truncateLine(oneBox, aa, bb) + : restrictA ? TGuidelineLineBase::truncateRay (oneBox, aa, bb) + : restrictB ? TGuidelineLineBase::truncateRay (oneBox, bb, aa) // aa first + : TGuidelineLineBase::truncateInfiniteLine(oneBox, aa, bb) ) + drawSegment(matrixInv*aa, matrixInv*bb, pixelSize, alpha); + } + + void drawGrid( + const TAffine &matrix, + const TAffine &matrixInv, + double pixelSize, + bool restrictA, + bool restrictB, + bool perspective ) const + { + const double minStep = 10.0; + + double alpha = getDrawingGridAlpha(); + TPointD a = m_a.position; + TPointD b = m_b.position; + TPointD ab = b - a; + double abLen2 = norm2(ab); + if (abLen2 < TConsts::epsilon*TConsts::epsilon) return; + double abLen = sqrt(abLen2); + + TPointD g0 = m_grid0.position; + TPointD g1 = m_grid1.position; + + TPointD abp = rotate90(ab); + TPointD ag = g0 - a; + if (fabs(abp*ag) <= TConsts::epsilon) { + if (restrictA || restrictB) return; + ag = abp; + } + double agLen2 = norm2(ag); + if (agLen2 < TConsts::epsilon*TConsts::epsilon) return; + double agLen = sqrt(agLen2); + double abpAgK = 1.0/(abp*ag); + TPointD abpAgProj = abp*abpAgK; + + // draw restriction lines + if (perspective) { + if (restrictA) drawLine(matrix, matrixInv, pixelSize, a, a + ag, false, false, alpha); + if (restrictB) drawLine(matrix, matrixInv, pixelSize, a, a + ag + ab, false, false, alpha); + } else { + if (restrictA) drawLine(matrix, matrixInv, pixelSize, a, a + ag, false, false, alpha); + if (restrictB) drawLine(matrix, matrixInv, pixelSize, b, b + ag, false, false, alpha); + } + + double minStepX = fabs(minStep*abLen*abpAgK); + if (minStepX <= TConsts::epsilon) return; + + // calculate bounds + const TRectD oneBox(-1.0, -1.0, 1.0, 1.0); + TPointD corners[4] = { + TPointD(oneBox.x0, oneBox.y0), + TPointD(oneBox.x0, oneBox.y1), + TPointD(oneBox.x1, oneBox.y0), + TPointD(oneBox.x1, oneBox.y1) }; + double minX = 0.0, maxX = 0.0; + for(int i = 0; i < 4; ++i) { + double x = abpAgProj * (matrixInv*corners[i] - a); + if (i == 0 || x < minX) minX = x; + if (i == 0 || x > maxX) maxX = x; + } + if (maxX <= minX) return; + + double x0 = abpAgProj*(g0 - a); + double x1 = abpAgProj*(g1 - a); + + if (perspective) { + double k = 0.0, begin = 0.0, end = 0.0; + if (!calcPerspectiveStep(minStepX, minX, maxX, 0.0, x0, x1, k, begin, end)) return; + double abk = 1.0/fabs(x0); + for(double x = begin; fabs(x) < fabs(end); x *= k) { + TPointD ca = a + ag*x; + TPointD cb = ca + ab*(abk*x); + drawLine(matrix, matrixInv, pixelSize, ca, cb, restrictA, restrictB, alpha); + } + } else { + double dx = fabs(x1 - x0); + if (dx < minStepX) return; + for(double x = x0 + ceil((minX - x0)/dx)*dx; x < maxX; x += dx) { + TPointD ca = a + ag*x; + drawLine(matrix, matrixInv, pixelSize, ca, ca + ab, restrictA, restrictB, alpha); + } + } + } + +public: + void draw(TToolViewer *viewer, bool enabled) const override { + double alpha = getDrawingAlpha(enabled); + bool restrictA = getRestrictA(); + bool restrictB = getRestrictB(); + bool parallel = getParallel(); + bool grid = getGrid(); + bool perspective = getPerspective(); + + // common data about viewport + const TRectD oneBox(-1.0, -1.0, 1.0, 1.0); + TAffine4 modelview, projection; + glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a); + glGetDoublev(GL_PROJECTION_MATRIX, projection.a); + TAffine matrix = (projection*modelview).get2d(); + TAffine matrixInv = matrix.inv(); + double pixelSize = sqrt(tglGetPixelSize2()); + + // calculate range + TPointD aa = matrix*m_a.position; + TPointD bb = matrix*m_b.position; + bool success = false; + if (restrictA && restrictB) + success = TGuidelineLineBase::truncateLine(oneBox, aa, bb); + else if (restrictA) + success = TGuidelineLineBase::truncateRay(oneBox, aa, bb); + else if (restrictB) + success = TGuidelineLineBase::truncateRay(oneBox, bb, aa); + else + success = TGuidelineLineBase::truncateInfiniteLine(oneBox, aa, bb); + if (!success) return; + TPointD a = matrixInv*aa; + TPointD b = matrixInv*bb; + + // draw line + drawSegment(a, b, pixelSize, alpha); + + // draw restriction marks + if (restrictA || (!parallel && grid && perspective)) + drawDot(m_a.position); + if (restrictB) + drawDot(m_b.position); + + if (getGrid()) { + if (getParallel()) { + drawGrid(matrix, matrixInv, pixelSize, restrictA, restrictB, perspective); + } else { + drawRuler(a, b, perspective); + } + } + } +}; + + +//***************************************************************************************** +// Registration +//***************************************************************************************** + +static TAssistantTypeT<TAssistantLine> assistantLine("assistantLine");