diff --git a/src/app/geometry/vspline.cpp b/src/app/geometry/vspline.cpp index 00e7ca219..8985f9c56 100644 --- a/src/app/geometry/vspline.cpp +++ b/src/app/geometry/vspline.cpp @@ -744,3 +744,134 @@ void VSpline::SetKcurve(qreal factor) d->kCurve = factor; } } + +//--------------------------------------------------------------------------------------------------------------------- +int VSpline::Sign(long double ld) const +{ + if(qAbs(ld)<0.00000000001) + { + return 0; + } + return (ld>0) ? 1 : -1; +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * @brief Cubic Cubic equation solution. Real coefficients case. + * + * This method use method Vieta-Cardano for eval cubic equations. + * Cubic equation write in form x3+a*x2+b*x+c=0. + * + * Output: + * 3 real roots -> then x is filled with them; + * 1 real + 2 complex -> x[0] is real, x[1] is real part of complex roots, x[2] - non-negative imaginary part. + * + * @param x solution array (size 3). + * @param a coefficient + * @param b coefficient + * @param c coefficient + * @return 3 - 3 real roots; + * 1 - 1 real root + 2 complex; + * 2 - 1 real root + complex roots imaginary part is zero (i.e. 2 real roots). + */ +qint32 VSpline::Cubic(QVector &x, qreal a, qreal b, qreal c) const +{ + //To find cubic equation roots in the case of real coefficients, calculated at the beginning + const qreal q = (pow(a, 2) - 3*b)/9.; + const qreal r = (2*pow(a, 3) - 9*a*b + 27.*c)/54.; + if (pow(r, 2) < pow(q, 3)) + { // equation has three real roots, use formula Vieta + const qreal t = acos(r/sqrt(pow(q, 3)))/3.; + x.insert(0, -2.*sqrt(q)*cos(t)-a/3); + x.insert(1, -2.*sqrt(q)*cos(t + (2*M_2PI/3.)) - a/3.); + x.insert(2, -2.*sqrt(q)*cos(t - (2*M_2PI/3.)) - a/3.); + return(3); + } + else + { // 1 real root + 2 complex + //Formula Cardano + const qreal aa = -Sign(r)*pow(fabs(r)+sqrt(pow(r, 2)-pow(q, 3)), 1./3.); + const qreal bb = Sign(aa) == 0 ? 0 : q/aa; + + x.insert(0, aa+bb-a/3.); // Real root + x.insert(1, (-0.5)*(aa+bb)-a/3.); //Complex root + x.insert(2, (sqrt(3.)*0.5)*fabs(aa-bb)); // Complex root + if (qFuzzyCompare(x.at(2) + 1, 0. + 1)) + { + return(2); + } + return(1); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +QVector VSpline::CalcT (qreal curveCoord1, qreal curveCoord2, qreal curveCoord3, + qreal curveCoord4, qreal pointCoord) const +{ + const qreal a = -curveCoord1 + 3*curveCoord2 - 3*curveCoord3 + curveCoord4; + const qreal b = 3*curveCoord1 - 6*curveCoord2 + 3*curveCoord3; + const qreal c = -3*curveCoord1 + 3*curveCoord2; + const qreal d = -pointCoord + curveCoord1; + + QVector t = QVector(3, -1); + Cubic(t, b/a, c/a, d/a); + + QVector retT; + for (int i=0; i < t.size(); ++i) + { + if ( t.at(i) >= 0 && t.at(i) <= 1 ) + { + retT.append(t.at(i)); + } + } + + return retT; +} + +//--------------------------------------------------------------------------------------------------------------------- +/** + * @brief VSpline::ParamT calculate t coeffient that reprezent point on curve. + * + * Each point that belongs to Cubic Bézier curve can be shown by coefficient in interval [0; 1]. + * + * @param pBt point on curve + * @return t coeffient that reprezent this point on curve. Return -1 if point doesn't belongs to curve. + */ +qreal VSpline::ParamT (const QPointF &pBt) const +{ + QVector ts; + // Calculate t coefficient for each axis + ts += CalcT (GetP1().toQPointF().x(), d->p2.x(), d->p3.x(), GetP4().toQPointF().x(), pBt.x()); + ts += CalcT (GetP1().toQPointF().y(), d->p2.y(), d->p3.y(), GetP4().toQPointF().y(), pBt.y()); + + if(ts.isEmpty()) + { + return -1; // We don't have candidates + } + + qreal tx = -1; + qreal eps = 3; // Error calculation + + // In morst case we will have 6 result in interval [0; 1]. + // Here we try find closest to our point. + for (int i=0; i< ts.size(); ++i) + { + const qreal t = ts.at(i); + const QPointF p0 = GetP1().toQPointF(); + const QPointF p1 = d->p2; + const QPointF p2 = d->p3; + const QPointF p3 = GetP4().toQPointF(); + //The explicit form of the Cubic Bézier curve + const qreal pointX = pow(1-t, 3)*p0.x() + 3*pow(1-t, 2)*t*p1.x() + 3*(1-t)*pow(t, 2)*p2.x() + pow(t, 3)*p3.x(); + const qreal pointY = pow(1-t, 3)*p0.y() + 3*pow(1-t, 2)*t*p1.y() + 3*(1-t)*pow(t, 2)*p2.y() + pow(t, 3)*p3.y(); + + const QLineF line(pBt, QPointF(pointX, pointY)); + if (line.length() <= eps) + { + tx = t; + eps = line.length(); //Next point should be even closest + } + } + + return tx; +} diff --git a/src/app/geometry/vspline.h b/src/app/geometry/vspline.h index d363f8b0f..c01be8c88 100644 --- a/src/app/geometry/vspline.h +++ b/src/app/geometry/vspline.h @@ -71,6 +71,7 @@ public: // cppcheck-suppress unusedFunction static QVector SplinePoints(const QPointF &p1, const QPointF &p4, qreal angle1, qreal angle2, qreal kAsm1, qreal kAsm2, qreal kCurve); + qreal ParamT(const QPointF &pBt) const; protected: static QVector GetPoints (const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4 ); private: @@ -80,6 +81,10 @@ private: qint16 level, QVector &px, QVector &py); static qreal CalcSqDistance ( qreal x1, qreal y1, qreal x2, qreal y2); void CreateName(); + QVector CalcT(qreal curveCoord1, qreal curveCoord2, qreal curveCoord3, qreal curveCoord4, + qreal pointCoord) const; + qint32 Cubic(QVector &x, qreal a, qreal b, qreal c) const; + int Sign(long double ld) const; }; #endif // VSPLINE_H diff --git a/src/app/tools/drawTools/vtoolspline.cpp b/src/app/tools/drawTools/vtoolspline.cpp index ce50c27a0..99415a3ae 100644 --- a/src/app/tools/drawTools/vtoolspline.cpp +++ b/src/app/tools/drawTools/vtoolspline.cpp @@ -31,6 +31,7 @@ #include "../../dialogs/tools/dialogspline.h" #include "../../undocommands/movespline.h" #include "../../visualization/vistoolspline.h" +#include const QString VToolSpline::ToolType = QStringLiteral("simple"); @@ -45,7 +46,7 @@ const QString VToolSpline::ToolType = QStringLiteral("simple"); */ VToolSpline::VToolSpline(VPattern *doc, VContainer *data, quint32 id, const QString color, const Source &typeCreation, QGraphicsItem *parent) - :VAbstractSpline(doc, data, id, parent) + :VAbstractSpline(doc, data, id, parent), oldPosition() { sceneType = SceneObject::Spline; lineColor = color; @@ -53,6 +54,8 @@ VToolSpline::VToolSpline(VPattern *doc, VContainer *data, quint32 id, const QStr this->setPen(QPen(Qt::black, qApp->toPixel(qApp->widthHairLine())/factor)); this->setFlag(QGraphicsItem::ItemIsSelectable, true); this->setFlag(QGraphicsItem::ItemIsFocusable, true); + this->setFlag(QGraphicsItem::ItemIsMovable, true); + //this->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); this->setAcceptHoverEvents(true); this->setPath(ToolPath()); @@ -355,6 +358,71 @@ void VToolSpline::SaveOptions(QDomElement &tag, QSharedPointer &obj) doc->SetAttribute(tag, AttrColor, lineColor); } +//--------------------------------------------------------------------------------------------------------------------- +void VToolSpline::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + oldPosition = event->scenePos(); + event->accept(); + } + VAbstractSpline::mousePressEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VToolSpline::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + // Don't need check if left mouse button was pressed. According to the Qt documentation "If you do receive this + // event, you can be certain that this item also received a mouse press event, and that this item is the current + // mouse grabber.". + + // Magic Bezier Drag Equations follow! + // "weight" describes how the influence of the drag should be distributed + // among the handles; 0 = front handle only, 1 = back handle only. + + const QSharedPointer spline = VAbstractTool::data.GeometricObject(id); + const qreal t = spline->ParamT(oldPosition); + + if (qFloor(t) == -1) + { + return; + } + + double weight; + if (t <= 1.0 / 6.0) + { + weight = 0; + } + else if (t <= 0.5) + { + weight = (pow((6 * t - 1) / 2.0, 3)) / 2; + } + else if (t <= 5.0 / 6.0) + { + weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5; + } + else + { + weight = 1; + } + + const QPointF delta = event->scenePos() - oldPosition; + const QPointF offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta; + const QPointF offset1 = (weight/(3*t*t*(1-t))) * delta; + + const QPointF p2 = spline->GetP2() + offset0; + const QPointF p3 = spline->GetP3() + offset1; + + oldPosition = event->scenePos(); // Now mouse here + + VSpline spl = VSpline(spline->GetP1(), p2, p3, spline->GetP4(), spline->GetKcurve()); + + MoveSpline *moveSpl = new MoveSpline(doc, spline.data(), spl, id, this->scene()); + connect(moveSpl, &MoveSpline::NeedLiteParsing, doc, &VPattern::LiteParseTree); + qApp->getUndoStack()->push(moveSpl); + +} + //--------------------------------------------------------------------------------------------------------------------- /** * @brief RefreshGeometry refresh item on scene. diff --git a/src/app/tools/drawTools/vtoolspline.h b/src/app/tools/drawTools/vtoolspline.h index b10345fd6..ba17cc7cc 100644 --- a/src/app/tools/drawTools/vtoolspline.h +++ b/src/app/tools/drawTools/vtoolspline.h @@ -63,9 +63,12 @@ protected: virtual void RemoveReferens(); virtual void SaveDialog(QDomElement &domElement); virtual void SaveOptions(QDomElement &tag, QSharedPointer &obj); + virtual void mousePressEvent(QGraphicsSceneMouseEvent * event); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent * event); private: Q_DISABLE_COPY(VToolSpline) void RefreshGeometry (); + QPointF oldPosition; }; #endif // VTOOLSPLINE_H