From 9a49d4d69d2da82cbf57a53cc59c3a509b3567a9 Mon Sep 17 00:00:00 2001 From: Ivan Mahonin Date: May 01 2023 08:50:50 +0000 Subject: #assistants: TAssistantEllipse --- diff --git a/toonz/sources/common/tgeometry/tgeometry.cpp b/toonz/sources/common/tgeometry/tgeometry.cpp index 882bc68..cbd9367 100644 --- a/toonz/sources/common/tgeometry/tgeometry.cpp +++ b/toonz/sources/common/tgeometry/tgeometry.cpp @@ -95,12 +95,17 @@ bool TAffine::operator!=(const TAffine &a) const { //-------------------------------------------------------------------------------------------------- bool TAffine::isIdentity(double err) const { return ((a11 - 1.0) * (a11 - 1.0) + (a22 - 1.0) * (a22 - 1.0) + a12 * a12 + - a13 * a13 + a21 * a21 + a23 * a23) < err; + a13 * a13 + a21 * a21 + a23 * a23) <+ err; +} +//-------------------------------------------------------------------------------------------------- +bool TAffine::isZero(double err) const { + return ( a11*a11 + a12*a12 + a13*a13 + + a21*a21 + a22*a22 + a23*a23 ) <= err; } //-------------------------------------------------------------------------------------------------- bool TAffine::isTranslation(double err) const { return ((a11 - 1.0) * (a11 - 1.0) + (a22 - 1.0) * (a22 - 1.0) + a12 * a12 + - a21 * a21) < err; + a21 * a21) <= err; } //-------------------------------------------------------------------------------------------------- bool TAffine::isIsotropic(double err) const { @@ -147,6 +152,14 @@ TAffine TAffine::place(const TPointD &pIn, const TPointD &pOut) const { pOut.y - (a21 * pIn.x + a22 * pIn.y)); } +//-------------------------------------------------------------------------------------------------- + +TAffine TAffine::rotation(double angle) { + double s = sin(angle); + double c = cos(angle); + return TAffine(c, -s, 0, s, c, 0); +} + //================================================================================================== TRotation::TRotation(double degrees) { diff --git a/toonz/sources/include/tgeometry.h b/toonz/sources/include/tgeometry.h index d22a047..6faae14 100644 --- a/toonz/sources/include/tgeometry.h +++ b/toonz/sources/include/tgeometry.h @@ -1042,6 +1042,8 @@ a12*a12+a13*a13+a21*a21+a23*a23) < err; identity matrix. */ + bool isZero(double err = 1.e-8) const; + bool isTranslation(double err = 1.e-8) const; /*Sposto in tgeometry.cpp { @@ -1094,6 +1096,32 @@ return TPointD(p.x*a11+p.y*a12+a13, p.x*a21+p.y*a22+a23); See above. */ TAffine place(const TPointD &pIn, const TPointD &pOut) const; + + inline static TAffine identity() + { return TAffine(); } + inline static TAffine zero() + { return TAffine(0, 0, 0, 0, 0, 0); } + + inline static TAffine translation(double x, double y) + { return TAffine(1, 0, x, 0, 1, y); } + inline static TAffine translation(const TPointD &p) + { return translation(p.x, p.y); } + + inline static TAffine scale(double sx, double sy) + { return TAffine(sx, 0, 0, 0, sy, 0); } + inline static TAffine scale(double s) + { return scale(s, s); } + inline static TAffine scale(const TPointD ¢er, double sx, double sy) + { return translation(center)*scale(sx, sy)*translation(-center); } + inline static TAffine scale(const TPointD ¢er, double s) + { return scale(center, s, s); } + + static TAffine rotation(double angle); + inline static TAffine rotation(const TPointD ¢er, double angle) + { return translation(center)*rotation(angle)*translation(-center); } + + inline static TAffine shear(double sx, double sy) + { return TAffine(1, sx, 0, sy, 1, 0); } }; //----------------------------------------------------------------------------- @@ -1306,9 +1334,11 @@ public: //! This class performs binary manipulations with angle ranges +typedef unsigned int TAngleI; + class DVAPI TAngleRangeSet { public: - typedef unsigned int Type; + typedef TAngleI Type; typedef std::vector List; static const Type max = Type() - Type(1); diff --git a/toonz/sources/include/tools/assistant.h b/toonz/sources/include/tools/assistant.h index 52b7247..50c5840 100644 --- a/toonz/sources/include/tools/assistant.h +++ b/toonz/sources/include/tools/assistant.h @@ -321,6 +321,17 @@ public: virtual void draw(TToolViewer *viewer, bool enabled) const; void draw(TToolViewer *viewer) const { draw(viewer, true); } virtual void drawEdit(TToolViewer *viewer) const; + + static bool calcPerspectiveStep( + double minStep, + double minX, + double maxX, + double x0, + double x1, + double x2, + double &outK, + double &outMin, + double &outMax ); }; diff --git a/toonz/sources/tnztools/CMakeLists.txt b/toonz/sources/tnztools/CMakeLists.txt index 2deca95..51f2e70 100644 --- a/toonz/sources/tnztools/CMakeLists.txt +++ b/toonz/sources/tnztools/CMakeLists.txt @@ -56,6 +56,7 @@ set(HEADERS ../include/tools/modifiers/modifiersegmentation.h ../include/tools/modifiers/modifierassistants.h assistants/guidelineline.h + assistants/guidelineellipse.h ) set(SOURCES @@ -130,8 +131,10 @@ set(SOURCES modifiers/modifiertest.cpp modifiers/modifiersegmentation.cpp assistants/guidelineline.cpp + assistants/guidelineellipse.cpp assistants/assistantvanishingpoint.cpp assistants/assistantline.cpp + assistants/assistantellipse.cpp editassistantstool.cpp ) diff --git a/toonz/sources/tnztools/assistant.cpp b/toonz/sources/tnztools/assistant.cpp index 2756531..50f3b91 100644 --- a/toonz/sources/tnztools/assistant.cpp +++ b/toonz/sources/tnztools/assistant.cpp @@ -539,3 +539,36 @@ TAssistant::drawEdit(TToolViewer *viewer) const { } //--------------------------------------------------------------------------------------------------- + +bool +TAssistant::calcPerspectiveStep( + double minStep, + double minX, + double maxX, + double x0, + double x1, + double x2, + double &outK, + double &outMin, + double &outMax ) +{ + 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; +} diff --git a/toonz/sources/tnztools/assistants/assistantellipse.cpp b/toonz/sources/tnztools/assistants/assistantellipse.cpp new file mode 100644 index 0000000..1a8666f --- /dev/null +++ b/toonz/sources/tnztools/assistants/assistantellipse.cpp @@ -0,0 +1,503 @@ + + +#include "guidelineline.h" +#include "guidelineellipse.h" + +// TnzTools includes +#include + +// TnzCore includes +#include + +// std includes +#include + + +//***************************************************************************************** +// TAssistantEllipse implementation +//***************************************************************************************** + +class DVAPI TAssistantEllipse final : public TAssistant { + Q_DECLARE_TR_FUNCTIONS(TAssistantEllipse) +public: + const TStringId m_idRestricktA; + const TStringId m_idRestricktB; + const TStringId m_idRepeat; + const TStringId m_idGrid; + const TStringId m_idPerspective; + +protected: + TAssistantPoint &m_center; + TAssistantPoint &m_a; + TAssistantPoint &m_b; + TAssistantPoint &m_grid0; + TAssistantPoint &m_grid1; + +public: + TAssistantEllipse(TMetaObject &object): + TAssistant(object), + m_idRestricktA("restrictA"), + m_idRestricktB("restrictB"), + m_idRepeat("repeat"), + m_idGrid("grid"), + m_idPerspective("perspective"), + m_center( addPoint("center", TAssistantPoint::CircleCross) ), + m_a( addPoint("a", TAssistantPoint::CircleFill, TPointD(100.0, 0.0)) ), + m_b( addPoint("b", TAssistantPoint::Circle, TPointD(0.0, 50.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_idRepeat.str(), getRepeat()) ); + addProperty( new TBoolProperty(m_idGrid.str(), getGrid()) ); + addProperty( new TBoolProperty(m_idPerspective.str(), getPerspective()) ); + } + + static QString getLocalName() + { return tr("Ellipse"); } + + void updateTranslation() const override { + setTranslation(m_idRestricktA, tr("Restrict A")); + setTranslation(m_idRestricktB, tr("Restrict B")); + setTranslation(m_idRepeat, tr("Repeat")); + 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 getRepeat() const + { return data()[m_idRepeat].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 = m_grid1.visible = getGrid(); + } + +private: + void fixBAndGgid1(const TPointD &previousCenter, const TPointD &previousA) { + TPointD dx = previousA - previousCenter; + double l = norm2(dx); + if (l <= TConsts::epsilon*TConsts::epsilon) return; + dx = dx*(1.0/sqrt(l)); + TPointD dy(-dx.y, dx.x); + + double r2 = dy*(m_b.position - m_center.position); + + TPointD g1 = m_grid1.position - m_grid0.position; + g1 = TPointD(dx*g1, dy*g1); + + dx = m_a.position - m_center.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; + m_b.position = m_center.position + dy*r2; + } + +public: + void onMovePoint(TAssistantPoint &point, const TPointD &position) override { + TPointD previousCenter = m_center.position; + TPointD previousA = m_a.position; + point.position = position; + if (&point == &m_center) { + m_a.position += m_center.position - previousCenter; + m_b.position += m_center.position - previousCenter; + } else + if (&point == &m_a || &point == &m_b) + fixBAndGgid1(previousCenter, previousA); + } + + TAffine calcEllipseMatrix() const { + TPointD da = m_a.position - m_center.position; + TPointD db = m_b.position - m_center.position; + double r1 = norm(da); + if (r1 <= TConsts::epsilon) return TAffine::zero(); + double r2 = fabs( (rotate90(da)*db)*(1.0/r1) ); + if (r2 <= TConsts::epsilon) return TAffine::zero(); + return TAffine::translation(m_center.position) + * TAffine::rotation(atan(da)) + * TAffine::scale(r1, r2); + } + + void getGuidelines( + const TPointD &position, + const TAffine &toTool, + TGuidelineList &outGuidelines ) const override + { + bool restrictA = getRestrictA(); + bool restrictB = getRestrictB(); + bool repeat = getRepeat(); + + TAffine matrix = calcEllipseMatrix(); + if (matrix.isZero()) return; + if (!restrictA && restrictB) { + std::swap(matrix.a11, matrix.a12); + std::swap(matrix.a21, matrix.a22); + std::swap(restrictA, restrictB); + } + + matrix = toTool*matrix; + TAffine matrixInv = matrix.inv(); + + if (restrictA && restrictB) { + // ellipse + outGuidelines.push_back(TGuidelineP( + new TGuidelineEllipse( + getEnabled(), + getMagnetism(), + matrix, + matrixInv ))); + } else + if (!restrictA && !restrictB) { + // scaled ellipse + TPointD p = matrixInv*position; + double l = norm(p); + outGuidelines.push_back(TGuidelineP( + new TGuidelineEllipse( + getEnabled(), + getMagnetism(), + matrix * TAffine::scale(l) ))); + } else { // restrictA + TPointD p = matrixInv*position; + if (repeat) { + double ox = round(0.5*p.x)*2.0; + p.x -= ox; + matrix *= TAffine::translation(ox, 0.0); + } + + // scale by Y + if (p.x <= TConsts::epsilon - 1.0) { + // line x = -1 + outGuidelines.push_back(TGuidelineP( + new TGuidelineInfiniteLine( + getEnabled(), + getMagnetism(), + matrix*TPointD(-1.0, 0.0), + matrix*TPointD(-1.0, 1.0) ))); + } else + if (p.x >= 1.0 - TConsts::epsilon) { + // line x = 1 + outGuidelines.push_back(TGuidelineP( + new TGuidelineInfiniteLine( + getEnabled(), + getMagnetism(), + matrix*TPointD(1.0, 0.0), + matrix*TPointD(1.0, 1.0) ))); + } else { + // ellipse scaled by Y + double k = fabs(p.y/sqrt(1.0 - p.x*p.x)); + outGuidelines.push_back(TGuidelineP( + new TGuidelineEllipse( + getEnabled(), + getMagnetism(), + matrix * TAffine::scale(1.0, k) ))); + } + } + } + +private: + void drawEllipseRanges( + const TAngleRangeSet &ranges, + const TAffine &ellipseMatrix, + const TAffine &screenMatrixInv, + double pixelSize, + double alpha ) const + { + assert(ranges.check()); + TAngleRangeSet actualRanges(ranges); + const TRectD oneBox(-1.0, -1.0, 1.0, 1.0); + if (!TGuidelineEllipse::truncateEllipse(actualRanges, ellipseMatrix.inv()*screenMatrixInv, oneBox)) + return; + assert(actualRanges.check()); + + int segments = TGuidelineEllipse::calcSegmentsCount(ellipseMatrix, pixelSize); + double da = M_2PI/segments; + double s = sin(da); + double c = cos(da); + + for(TAngleRangeSet::Iterator i(actualRanges); i; ++i) { + double a0 = i.d0(); + double a1 = i.d1greater(); + int cnt = (int)floor((a1 - a0)/da); + TPointD r(cos(a0), sin(a0)); + TPointD p0 = ellipseMatrix*r; + for(int j = 0; j < cnt; ++j) { + r = TPointD(r.x*c - r.y*s, r.y*c + r.x*s); + TPointD p1 = ellipseMatrix*r; + drawSegment(p0, p1, pixelSize, alpha); + p0 = p1; + } + drawSegment(p0, ellipseMatrix*TPointD(cos(a1), sin(a1)), pixelSize, alpha); + } + } + + void drawEllipse( + const TAffine &ellipseMatrix, + const TAffine &screenMatrixInv, + double pixelSize, + double alpha ) const + { drawEllipseRanges(TAngleRangeSet(true), ellipseMatrix, screenMatrixInv, pixelSize, alpha); } + + void drawRuler(const TAffine &ellipseMatrix, double pixelSize) const { + double minStep = 10.0*pixelSize; + double alpha = getDrawingGridAlpha(); + + const TAffine &em = ellipseMatrix; + double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22)))); + double actualMinStep = minStep/r; + TAffine ellipseMatrixInv = ellipseMatrix.inv(); + TPointD g0 = ellipseMatrixInv*m_grid0.position; + TPointD g1 = ellipseMatrixInv*m_grid1.position; + if (norm2(g0) <= TConsts::epsilon*TConsts::epsilon) return; + if (norm2(g1) <= TConsts::epsilon*TConsts::epsilon) return; + double ga0 = atan(g0); + double ga1 = atan(g1); + + if (getPerspective()) { + // draw perspective + if (ga0 < 0.0) { if (ga1 > 0.0) ga1 -= M_2PI; } + else { if (ga1 < 0.0) ga1 += M_2PI; } + double k = 0.0, begin = 0.0, end = 0.0; + if (!calcPerspectiveStep(actualMinStep, 0.0, M_2PI, 0.0, fabs(ga0), ga1, k, begin, end)) return; + for(double x = begin; fabs(x) < fabs(end); x *= k) + drawDot(ellipseMatrix * TPointD(cos(x), (ga0 < 0.0 ? -1.0 : 1.0)*sin(x))); + } else { + // draw linear + double da = ga1 - ga0; + if (da < 0.0) { da = -da; std::swap(ga0, ga1); } + if (ga1 - ga0 > M_PI) { da = M_2PI - da; std::swap(ga0, ga1); } + if (da < actualMinStep) return; + for(double a = ga0 - floor(M_PI/da)*da; a < ga0 + M_PI; a += da) + drawDot(ellipseMatrix * TPointD(cos(a), sin(a))); + } + } + + void drawConcentricGrid( + const TAffine &ellipseMatrix, + const TAffine &screenMatrixInv, + double pixelSize ) const + { + double minStep = 20.0*pixelSize; + double alpha = getDrawingGridAlpha(); + TAffine ellipseMatrixInv = ellipseMatrix.inv(); + + // calculate bounds + TAffine matrixInv = ellipseMatrixInv * screenMatrixInv; + TPointD o = matrixInv * TPointD(-1.0, -1.0); + TPointD dx = matrixInv.transformDirection( TPointD(2.0, 0.0) ); + TPointD dy = matrixInv.transformDirection( TPointD(0.0, 2.0) ); + double max = 0.0; + double min = std::numeric_limits::infinity(); + + // distance to points + TPointD corners[] = { o, o+dx, o+dx+dy, o+dy }; + for(int i = 0; i < 4; ++i) { + double k = norm(corners[i]); + if (k < min) min = k; + if (k > max) max = k; + } + + // distance to sides + TPointD lines[] = { dx, dy, -1.0*dx, -1.0*dy }; + int positive = 0, negative = 0; + for(int i = 0; i < 4; ++i) { + double len2 = norm2(lines[i]); + if (len2 <= TConsts::epsilon*TConsts::epsilon) continue; + double k = (corners[i]*rotate90(lines[i]))/sqrt(len2); + if (k > TConsts::epsilon) ++positive; + if (k < TConsts::epsilon) ++negative; + double l = -(corners[i]*lines[i]); + if (l <= TConsts::epsilon || l >= len2 - TConsts::epsilon) continue; + k = fabs(k); + if (k < min) min = k; + if (k > max) max = k; + } + + // if center is inside bounds + if (min < 0.0 || positive == 0 || negative == 0) min = 0.0; + if (max <= min) return; + + // draw + const TAffine &em = ellipseMatrix; + double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22)))); + double actualMinStep = minStep/r; + double gs0 = norm(ellipseMatrixInv*m_grid0.position); + double gs1 = norm(ellipseMatrixInv*m_grid1.position); + if (gs0 <= TConsts::epsilon*TConsts::epsilon) return; + if (gs1 <= TConsts::epsilon*TConsts::epsilon) return; + + if (getPerspective()) { + // draw perspective + double k = 0.0, begin = 0.0, end = 0.0; + if (!calcPerspectiveStep(actualMinStep, min, max, 0.0, gs0, gs1, k, begin, end)) return; + for(double x = begin; fabs(x) < fabs(end); x *= k) + drawEllipse(ellipseMatrix * TAffine::scale(x), screenMatrixInv, pixelSize, alpha); + } else { + // draw linear + double dx = fabs(gs1 - gs0); + if (dx*r < minStep) return; + for(double x = gs0 + ceil((min - gs0)/dx)*dx; x < max; x += dx) + drawEllipse(ellipseMatrix * TAffine::scale(x), screenMatrixInv, pixelSize, alpha); + } + } + + void drawParallelGrid( + const TAffine &ellipseMatrix, + const TAffine &screenMatrixInv, + double pixelSize ) const + { + double minStep = 10.0*pixelSize; + double alpha = getDrawingGridAlpha(); + TAffine ellipseMatrixInv = ellipseMatrix.inv(); + + const TAffine &em = ellipseMatrix; + double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22)))); + double actualMinStep = minStep/r; + TPointD g0 = ellipseMatrixInv*m_grid0.position; + TPointD g1 = ellipseMatrixInv*m_grid1.position; + if (getRepeat()) + { g0.x -= round(0.5*g0.x)*2.0; g1.x -= round(0.5*g1.x)*2.0; } + if (fabs(g0.x) >= 1.0 - TConsts::epsilon) return; + if (fabs(g1.x) >= 1.0 - TConsts::epsilon) return; + double gs0 = g0.y/sqrt(1.0 - g0.x*g0.x); + double gs1 = g1.y/sqrt(1.0 - g1.x*g1.x); + if (fabs(gs0) >= 1.0 - TConsts::epsilon) return; + if (fabs(gs1) >= 1.0 - TConsts::epsilon) return; + + TAngleRangeSet ranges; + ranges.add( TAngleRangeSet::fromDouble(0.0), TAngleRangeSet::fromDouble(M_PI) ); + + if (getPerspective()) { + // draw perspective (actually angular) + double k = 0.0, begin = 0.0, end = 0.0; + double a0 = asin(gs0); + double a1 = asin(gs1); + double da = fabs(a1 - a0); + if (fabs(sin(da)) < 2.0*actualMinStep) return; + for(double a = a0 + ceil((-M_PI_2 - a0)/da)*da; a < M_PI_2; a += da) + drawEllipseRanges( + ranges, + ellipseMatrix*TAffine::scale(a < 0.0 ? -1.0 : 1.0, sin(a)), + screenMatrixInv, + pixelSize, + alpha ); + } else { + // draw linear + double dx = fabs(gs1 - gs0); + if (dx < actualMinStep) return; + for(double x = gs0 + ceil((-1.0 - gs0)/dx)*dx; x < 1.0; x += dx) + drawEllipseRanges( + ranges, + ellipseMatrix*TAffine::scale(x < 0.0 ? -1.0 : 1.0, x), + screenMatrixInv, + pixelSize, + alpha ); + } + } + + void draw( + const TAffine &ellipseMatrix, + const TAffine &screenMatrixInv, + double ox, + double pixelSize, + bool enabled ) const + { + const double crossSize = 0.1; + + double alpha = getDrawingAlpha(enabled); + bool grid = getGrid(); + bool ruler = getRestrictA() && getRestrictB(); + bool concentric = !getRestrictA() && !getRestrictB(); + bool perspective = getPerspective(); + + drawSegment( ellipseMatrix*TPointD(-crossSize, 0.0), + ellipseMatrix*TPointD( crossSize, 0.0), pixelSize, alpha); + drawSegment( ellipseMatrix*TPointD(0.0, -crossSize), + ellipseMatrix*TPointD(0.0, crossSize), pixelSize, alpha); + drawEllipse(ellipseMatrix, screenMatrixInv, pixelSize, alpha); + if (ox > 1.0) + drawSegment( ellipseMatrix*TPointD(-1.0, -1.0), + ellipseMatrix*TPointD(-1.0, 1.0), pixelSize, alpha); + else if (ox < -1.0) + drawSegment( ellipseMatrix*TPointD( 1.0, -1.0), + ellipseMatrix*TPointD( 1.0, 1.0), pixelSize, alpha); + + if (!grid) return; + + if (ruler) { + drawRuler(ellipseMatrix, pixelSize); + } else + if (concentric) { + drawConcentricGrid(ellipseMatrix, screenMatrixInv, pixelSize); + } else { + drawParallelGrid(ellipseMatrix, screenMatrixInv, pixelSize); + } + } + +public: + void draw(TToolViewer *viewer, bool enabled) const override { + bool restrictA = getRestrictA(); + bool restrictB = getRestrictB(); + bool repeat = getRepeat(); + double minStep = 30.0; + + TAffine ellipseMatrix = calcEllipseMatrix(); + if (ellipseMatrix.isZero()) return; + if (!restrictA && restrictB) { + std::swap(ellipseMatrix.a11, ellipseMatrix.a12); + std::swap(ellipseMatrix.a21, ellipseMatrix.a22); + } + + // 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()); + + if (!repeat || restrictA == restrictB || norm(TPointD(ellipseMatrix.a11, ellipseMatrix.a21)) < minStep*pixelSize) { + draw(ellipseMatrix, matrixInv, 0.0, pixelSize, enabled); + } else { + // calculate bounds + TPointD o(ellipseMatrix.a13, ellipseMatrix.a23); + TPointD proj(ellipseMatrix.a11, ellipseMatrix.a21); + proj = proj * (1.0/norm2(proj)); + 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 = proj * (matrixInv*corners[i] - o); + if (i == 0 || x < minX) minX = x; + if (i == 0 || x > maxX) maxX = x; + } + if (maxX <= minX) return; + + // draw + for(double ox = round(0.5*minX)*2.0; ox - 1.0 < maxX; ox += 2.0) + draw(ellipseMatrix*TAffine::translation(ox, 0.0), matrixInv, ox, pixelSize, enabled); + } + } +}; + + +//***************************************************************************************** +// Registration +//***************************************************************************************** + +static TAssistantTypeT assistantEllipse("assistantEllipse"); + diff --git a/toonz/sources/tnztools/assistants/assistantline.cpp b/toonz/sources/tnztools/assistants/assistantline.cpp index 642fb53..01aaaa7 100644 --- a/toonz/sources/tnztools/assistants/assistantline.cpp +++ b/toonz/sources/tnztools/assistants/assistantline.cpp @@ -157,40 +157,8 @@ public: } 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; + void drawRuler(const TPointD &a, const TPointD &b, double pixelSize, bool perspective) const { + double minStep = 10.0*pixelSize; double alpha = getDrawingGridAlpha(); TPointD direction = b - a; @@ -246,7 +214,7 @@ private: bool restrictB, bool perspective ) const { - const double minStep = 10.0; + double minStep = 10.0*pixelSize; double alpha = getDrawingGridAlpha(); TPointD a = m_a.position; @@ -363,11 +331,11 @@ public: if (restrictB) drawDot(m_b.position); - if (getGrid()) { + if (grid) { if (getParallel()) { drawGrid(matrix, matrixInv, pixelSize, restrictA, restrictB, perspective); } else { - drawRuler(a, b, perspective); + drawRuler(a, b, pixelSize, perspective); } } } diff --git a/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp b/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp index fb50040..163e3ab 100644 --- a/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp +++ b/toonz/sources/tnztools/assistants/assistantvanishingpoint.cpp @@ -187,9 +187,10 @@ public: } void drawSimpleGrid() const { - const double minStep = 5.0; double alpha = getDrawingGridAlpha(); const TPointD &p = m_center.position; + double pixelSize = sqrt(tglGetPixelSize2()); + double minStep = 5.0*pixelSize; // calculate rays count and step TPointD d0 = m_grid0.position - p; @@ -217,7 +218,6 @@ public: glGetDoublev(GL_PROJECTION_MATRIX, projection.a); TAffine matrix = (projection*modelview).get2d(); TAffine matrixInv = matrix.inv(); - double pixelSize = sqrt(tglGetPixelSize2()); // calculate range if (!(matrixInv*oneBox).contains(p)) { @@ -262,9 +262,10 @@ public: void drawPerspectiveGrid() const { // initial calculations - const double minStep = 5.0; double alpha = getDrawingGridAlpha(); const TPointD ¢er = m_center.position; + double pixelSize = sqrt(tglGetPixelSize2()); + double minStep = 5.0*pixelSize; TPointD step = m_grid1.position - m_grid0.position; double stepLen2 = norm2(step); @@ -293,7 +294,6 @@ public: glGetDoublev(GL_PROJECTION_MATRIX, projection.a); TAffine matrix = (projection*modelview).get2d(); TAffine matrixInv = matrix.inv(); - double pixelSize = sqrt(tglGetPixelSize2()); // calculate bounds bool found = false; diff --git a/toonz/sources/tnztools/assistants/guidelineellipse.cpp b/toonz/sources/tnztools/assistants/guidelineellipse.cpp new file mode 100644 index 0000000..068a68b --- /dev/null +++ b/toonz/sources/tnztools/assistants/guidelineellipse.cpp @@ -0,0 +1,156 @@ + + +#include "guidelineellipse.h" + +// TnzCore includes +#include "tgl.h" + + +//***************************************************************************************** +// TGuidelineEllipse implementation +//***************************************************************************************** + +TGuidelineEllipse::TGuidelineEllipse( + bool enabled, + double magnetism, + TAffine matrix +): + TGuideline(enabled, magnetism), + matrix(matrix), + matrixInv(matrix.inv()) { } + + +TGuidelineEllipse::TGuidelineEllipse( + bool enabled, + double magnetism, + TAffine matrix, + TAffine matrixInv +): + TGuideline(enabled, magnetism), + matrix(matrix), + matrixInv(matrixInv) { } + + +TTrackPoint +TGuidelineEllipse::transformPoint(const TTrackPoint &point) const +{ + TTrackPoint p = point; + TPointD pp = matrixInv*p.position; + double l2 = norm2(pp); + if (l2 > TConsts::epsilon*TConsts::epsilon) + p.position = matrix*(pp*(1.0/sqrt(l2))); + return p; +} + + +bool +TGuidelineEllipse::truncateEllipse( + TAngleRangeSet &ranges, + const TAffine &ellipseMatrixInv, + const TRectD &bounds ) +{ + if (ranges.isEmpty()) return false; + if (bounds.isEmpty()) { ranges.clear(); return false; } + + TPointD o = ellipseMatrixInv*bounds.getP00(); + TPointD dx = ellipseMatrixInv.transformDirection(TPointD(bounds.getLx(), 0.0)); + TPointD dy = ellipseMatrixInv.transformDirection(TPointD(0.0, bounds.getLy())); + double lx2 = norm2(dx); + double ly2 = norm2(dy); + if ( lx2 < TConsts::epsilon*TConsts::epsilon + || ly2 < TConsts::epsilon*TConsts::epsilon ) + { ranges.clear(); return false; } + TPointD nx = rotate90(dx)*(1.0/sqrt(lx2)); + TPointD ny = rotate90(dy)*(1.0/sqrt(ly2)); + + TAngleI ax = TAngleRangeSet::fromDouble(atan(dx)); + TAngleI ay = TAngleRangeSet::fromDouble(atan(dy)); + + double sign = nx*dy; + if (fabs(sign) <= TConsts::epsilon) + { ranges.clear(); return false; } + + if (sign < 0.0) { + nx = -nx; ny = -ny; + ax ^= TAngleRangeSet::half; + ay ^= TAngleRangeSet::half; + } + + TAngleI angles[] = { + ax, + ax^TAngleRangeSet::half, + ay, + ay^TAngleRangeSet::half }; + + double heights[] = { + o*nx, + -((o+dx+dy)*nx), + (o+dx)*ny, + -((o+dy)*ny) }; + + for(int i = 0; i < 4; ++i) { + double h = heights[i]; + if (heights[i] <= TConsts::epsilon - 1.0) + continue; + if (h >= 1.0 - TConsts::epsilon) + { ranges.clear(); return false; } + TAngleI a = TAngleRangeSet::fromDouble(asin(h)); + TAngleI da = angles[i]; + ranges.subtract(da - a, (da + a)^TAngleRangeSet::half ); + if (ranges.isEmpty()) return false; + } + + return true; +} + + +int +TGuidelineEllipse::calcSegmentsCount(const TAffine &ellipseMatrix, double pixelSize) { + const TAffine &em = ellipseMatrix; + const int min = 4, max = 1000; + double r = sqrt(0.5*(norm2(TPointD(em.a11, em.a21)) + norm2(TPointD(em.a12, em.a22)))); + double h = 0.5*pixelSize/r; + if (h <= TConsts::epsilon) return max; + if (h >= 1.0 - TConsts::epsilon) return min; + double segments = round(M_2PI/acos(1.0 - h)); + return segments <= (double)min ? min + : segments >= (double)max ? max + : (int)segments; +} + + +void +TGuidelineEllipse::draw(bool active, bool enabled) const { + TAffine4 modelview, projection; + glGetDoublev(GL_MODELVIEW_MATRIX, modelview.a); + glGetDoublev(GL_PROJECTION_MATRIX, projection.a); + TAffine screenMatrix = (projection*modelview).get2d(); + TAffine screenMatrixInv = screenMatrix.inv(); + double pixelSize = sqrt(tglGetPixelSize2()); + + const TRectD oneBox(-1.0, -1.0, 1.0, 1.0); + TAngleRangeSet ranges(true); + if (!truncateEllipse(ranges, matrixInv*screenMatrixInv, oneBox)) + return; + + int segments = calcSegmentsCount(matrix, pixelSize); + double da = M_2PI/segments; + double s = sin(da); + double c = cos(da); + + for(TAngleRangeSet::Iterator i(ranges); i; ++i) { + double a0 = i.d0(); + double a1 = i.d1greater(); + TPointD r(cos(a0), sin(a0)); + TPointD p0 = matrix*r; + int cnt = (int)floor((a1 - a0)/da); + for(int j = 0; j < cnt; ++j) { + r = TPointD(r.x*c - r.y*s, r.y*c + r.x*s); + TPointD p1 = matrix*r; + drawSegment(p0, p1, pixelSize, active, enabled); + p0 = p1; + } + drawSegment(p0, matrix*TPointD(cos(a1), sin(a1)), pixelSize, active, enabled); + } +} + diff --git a/toonz/sources/tnztools/assistants/guidelineellipse.h b/toonz/sources/tnztools/assistants/guidelineellipse.h new file mode 100644 index 0000000..c8b1607 --- /dev/null +++ b/toonz/sources/tnztools/assistants/guidelineellipse.h @@ -0,0 +1,55 @@ +#pragma once + +#ifndef GUIDELINEELLIPSE_INCLUDED +#define GUIDELINEELLIPSE_INCLUDED + +// TnzTools includes +#include + + +#undef DVAPI +#undef DVVAR +#ifdef TNZTOOLS_EXPORTS +#define DVAPI DV_EXPORT_API +#define DVVAR DV_EXPORT_VAR +#else +#define DVAPI DV_IMPORT_API +#define DVVAR DV_IMPORT_VAR +#endif + + +//============================================================== + +//***************************************************************************************** +// TGuidelineEllipse definition +//***************************************************************************************** + +class DVAPI TGuidelineEllipse : public TGuideline { +public: + const TAffine matrix; + const TAffine matrixInv; + + TGuidelineEllipse( + bool enabled, + double magnetism, + TAffine matrix ); + + TGuidelineEllipse( + bool enabled, + double magnetism, + TAffine matrix, + TAffine matrixInv ); + + //! returns false when ellipse is invisible + static bool truncateEllipse( + TAngleRangeSet &ranges, + const TAffine &ellipseMatrixInv, + const TRectD &bounds ); + + static int calcSegmentsCount(const TAffine &ellipseMatrix, double pixelSize); + + TTrackPoint transformPoint(const TTrackPoint &point) const override; + void draw(bool active, bool enabled) const override; +}; + +#endif diff --git a/toonz/sources/tnztools/assistants/guidelineline.h b/toonz/sources/tnztools/assistants/guidelineline.h index e2954e4..7373be9 100644 --- a/toonz/sources/tnztools/assistants/guidelineline.h +++ b/toonz/sources/tnztools/assistants/guidelineline.h @@ -6,8 +6,6 @@ // TnzTools includes #include -#include - #undef DVAPI #undef DVVAR