Git commit dad2c40a9445d0649d37f76d0d09e306a5daffb4 by Dmitry Kazakov. Committed on 08/01/2021 at 14:09. Pushed by dkazakov into branch 'master'. Implement a new free patch deform algorithm The algorithm should also move the nodes as well. The patch also implements a new Shift-mode: Shift+drag-patch --- drags the patch without modifying the handles The main idea of the algorithm is: it gest the offset budget from the user's drag and splits it among the following moves: * translate all four nodes if the patch * offset the nearest segment * translate the nearest node (or the two nodes of the nearest segment) CC:kimageshop@kde.org M +21 -0 libs/global/KisBezierMesh.h M +10 -0 libs/global/kis_algebra_2d.h M +98 -11 plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp https://invent.kde.org/graphics/krita/commit/dad2c40a9445d0649d37f76d0d09e3= 06a5daffb4 diff --git a/libs/global/KisBezierMesh.h b/libs/global/KisBezierMesh.h index f1cd7c35d9..db9d4a4d15 100644 --- a/libs/global/KisBezierMesh.h +++ b/libs/global/KisBezierMesh.h @@ -618,6 +618,15 @@ private: return m_mesh->find(ControlPointIndex(secondNodeIndex(), Mesh:= :ControlType::Node)); } = + QPointF pointAtParam(qreal t) const { + return KisBezierUtils::bezierCurve(p0(), p1(), p2(), p3(), t); + } + + qreal length() const { + const qreal eps =3D 1e-3; + return KisBezierUtils::curveLength(p0(), p1(), p2(), p3(), eps= ); + } + int degree() const { return KisBezierUtils::bezierDegree(p0(), p1(), p2(), p3()); } @@ -985,6 +994,10 @@ public: control_point_const_iterator find(const ControlPointIndex &index) cons= t { return find(*this, index); } control_point_const_iterator constFind(const ControlPointIndex &index)= const { return find(*this, index); } = + control_point_iterator find(const NodeIndex &index) { return find(*thi= s, index); } + control_point_const_iterator find(const NodeIndex &index) const { retu= rn find(*this, index); } + control_point_const_iterator constFind(const NodeIndex &index) const {= return find(*this, index); } + segment_iterator find(const SegmentIndex &index) { return find(*this, = index); } segment_const_iterator find(const SegmentIndex &index) const { return = find(*this, index); } segment_const_iterator constFind(const SegmentIndex &index) const { re= turn find(*this, index); } @@ -1193,6 +1206,14 @@ private: return it.isValid() ? it : mesh.endControlPoints(); } = + template ::value>> + static + IteratorType find(MeshType &mesh, const NodeIndex &index) { + IteratorType it(&mesh, index.x(), index.y(), Mesh::ControlType::No= de); + return it.isValid() ? it : mesh.endControlPoints(); + } + template ::value>> static diff --git a/libs/global/kis_algebra_2d.h b/libs/global/kis_algebra_2d.h index 8e78454a4d..d6c9d35952 100644 --- a/libs/global/kis_algebra_2d.h +++ b/libs/global/kis_algebra_2d.h @@ -683,6 +683,16 @@ std::pair KRITAGLOBAL_EXPORT tran= sformEllipse(const QPointF QPointF KRITAGLOBAL_EXPORT alignForZoom(const QPointF &pt, qreal zoom); = = +/** + * Linearly reshape function \p x so that in range [x0, x1] + * it would cross points (x0, y0) and (x1, y1). + */ +template +inline T linearReshapeFunc(T x, T x0, T x1, T y0, T y1) +{ + return y0 + (y1 - y0) * (x - x0) / (x1 - x0); +} + } = = diff --git a/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp = b/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp index 3fec4c55ac..a65b776080 100644 --- a/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp +++ b/plugins/tools/tool_transform2/kis_mesh_transform_strategy.cpp @@ -48,6 +48,7 @@ struct KisMeshTransformStrategy::Private OVER_SEGMENT, OVER_SEGMENT_SYMMETRIC, OVER_PATCH, + OVER_PATCH_LOCKED, SPLIT_SEGMENT, MULTIPLE_POINT_SELECTION, MOVE_MODE, @@ -153,7 +154,7 @@ void KisMeshTransformStrategy::setTransformFunction(con= st QPointF &mousePos, boo auto index =3D m_d->currentArgs.meshTransform()->hitTestPatch(mous= ePos, &localPatchPos); if (m_d->currentArgs.meshTransform()->isIndexValid(index)) { hoveredPatch =3D index; - mode =3D !shiftModifierActive ? Private::OVER_PATCH : Private:= :MOVE_MODE; + mode =3D !shiftModifierActive ? Private::OVER_PATCH : Private:= :OVER_PATCH_LOCKED; } } = @@ -318,6 +319,7 @@ QCursor KisMeshTransformStrategy::getCurrentCursor() co= nst case Private::OVER_POINT_SYMMETRIC: case Private::OVER_SEGMENT_SYMMETRIC: case Private::OVER_PATCH: + case Private::OVER_PATCH_LOCKED: cursor =3D KisCursor::meshCursorLocked(); break; case Private::SPLIT_SEGMENT: { @@ -511,7 +513,7 @@ bool KisMeshTransformStrategy::beginPrimaryAction(const= QPointF &pt) = retval =3D true; = - } else if (m_d->mode =3D=3D Private::OVER_PATCH) { + } else if (m_d->mode =3D=3D Private::OVER_PATCH || m_d->mode =3D=3D Pr= ivate::OVER_PATCH_LOCKED) { retval =3D true; = } else if (m_d->mode =3D=3D Private::SPLIT_SEGMENT) { @@ -597,36 +599,121 @@ void KisMeshTransformStrategy::continuePrimaryAction= (const QPointF &pt, bool shi smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().cont= rolIndex(), offsetP1, mode); smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().cont= rolIndex(), offsetP2, mode); = - } else if (m_d->mode =3D=3D Private::OVER_PATCH) { + } else if (m_d->mode =3D=3D Private::OVER_PATCH || m_d->mode =3D=3D Pr= ivate::OVER_PATCH_LOCKED) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->hoveredPatch); = - *m_d->currentArgs.meshTransform() =3D m_d->initialMeshState; + using KisAlgebra2D::linearReshapeFunc; + using Mesh =3D KisBezierTransformMesh; + + KisBezierTransformMesh &mesh =3D *m_d->currentArgs.meshTransform(); + mesh =3D m_d->initialMeshState; = auto patchIt =3D m_d->currentArgs.meshTransform()->find(*m_d->hove= redPatch); = - const QPointF offset =3D pt - m_d->mouseClickPos; + QPointF offset =3D pt - m_d->mouseClickPos; = auto offsetSegment =3D [this] (KisBezierTransformMesh::segment_iterator it, qreal t, - qreal distance, const QPointF &offset) { = QPointF offsetP1; QPointF offsetP2; = std::tie(offsetP1, offsetP2) =3D - KisBezierUtils::offsetSegment(t, (1.0 - distance) * offset= ); + KisBezierUtils::offsetSegment(t, offset); = = smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP1().= controlIndex(), offsetP1, KisSmartMoveMeshControlMode::MoveSymmetricLock); smartMoveControl(*m_d->currentArgs.meshTransform(), it.itP2().= controlIndex(), offsetP2, KisSmartMoveMeshControlMode::MoveSymmetricLock); }; = - offsetSegment(patchIt.segmentP(), m_d->localPatchPosition.x(), m_d= ->localPatchPosition.y(), offset); - offsetSegment(patchIt.segmentQ(), m_d->localPatchPosition.x(), 1.0= - m_d->localPatchPosition.y(), offset); - offsetSegment(patchIt.segmentR(), m_d->localPatchPosition.y(), m_d= ->localPatchPosition.x(), offset); - offsetSegment(patchIt.segmentS(), m_d->localPatchPosition.y(), 1.0= - m_d->localPatchPosition.x(), offset); + + const QPointF center =3D patchIt->localToGlobal(QPointF(0.5, 0.5)); + const qreal centerDistance =3D kisDistance(m_d->mouseClickPos, cen= ter); + + KisBezierTransformMesh::segment_iterator nearestSegment =3D mesh.e= ndSegments(); + qreal nearestSegmentSignificance =3D 0; + qreal nearestSegmentDistance =3D std::numeric_limits::max(); + qreal nearestSegmentDistanceSignificance =3D 0.0; + qreal nearestSegmentParam =3D 0.5; + + auto testSegment =3D + [&nearestSegment, + &nearestSegmentSignificance, + &nearestSegmentDistance, + &nearestSegmentDistanceSignificance, + &nearestSegmentParam, + centerDistance, + this] (KisBezierTransformMesh::segment_iterator it, qreal= param) { + + const QPointF movedPoint =3D KisBezierUtils::bezierCurve(it.p0= (), it.p1(), it.p2(), it.p3(), param); + const qreal distance =3D kisDistance(m_d->mouseClickPos, moved= Point); + + if (distance < nearestSegmentDistance) { + const qreal proportion =3D KisBezierUtils::curveProportion= ByParam(it.p0(), it.p1(), it.p2(), it.p3(), param, 0.1); + + qreal distanceSignificance =3D + centerDistance / (centerDistance + distance); + + if (distanceSignificance > 0.6) { + distanceSignificance =3D std::min(1.0, linearReshapeFu= nc(distanceSignificance, 0.6, 0.75, 0.6, 1.0)); + } + + const qreal directionSignificance =3D + 1.0 - std::min(1.0, std::abs(proportion - 0.5) / 0.4); + + nearestSegmentDistance =3D distance; + nearestSegment =3D it; + nearestSegmentParam =3D param; + nearestSegmentSignificance =3D m_d->mode !=3D Private::OVE= R_PATCH_LOCKED ? distanceSignificance * directionSignificance : 0; + nearestSegmentDistanceSignificance =3D distanceSignificanc= e; + } + }; + + testSegment(patchIt.segmentP(), m_d->localPatchPosition.x()); + testSegment(patchIt.segmentQ(), m_d->localPatchPosition.x()); + testSegment(patchIt.segmentR(), m_d->localPatchPosition.y()); + testSegment(patchIt.segmentS(), m_d->localPatchPosition.y()); + + KIS_SAFE_ASSERT_RECOVER_RETURN(nearestSegment !=3D mesh.endSegment= s()); + + const qreal translationOffsetCoeff =3D + qBound(0.0, + linearReshapeFunc(1.0 - nearestSegmentDistanceSignifica= nce, + 0.95, 0.75, 1.0, 0.0), + 1.0); + const QPointF translationOffset =3D translationOffsetCoeff * offse= t; + offset -=3D translationOffset; + + QPointF segmentOffset; + + if (nearestSegmentSignificance > 0) { + segmentOffset =3D nearestSegmentSignificance * offset; + offset -=3D segmentOffset; + } + + const qreal alpha =3D + 1.0 - KisBezierUtils::curveProportionByParam(nearestSegment.p0= (), + nearestSegment.p1= (), + nearestSegment.p2= (), + nearestSegment.p3= (), + nearestSegmentPar= am, 0.1); + + const qreal coeffN1 =3D + alpha > 0.5 ? std::max(0.0, linearReshapeFunc(alpha, 0.6, 0.75= , 1.0, 0.0)) : 1.0; + const qreal coeffN0 =3D + alpha < 0.5 ? std::max(0.0, linearReshapeFunc(alpha, 0.25, 0.4= , 0.0, 1.0)) : 1.0; + + nearestSegment.itP0().node().translate(offset * coeffN0); + nearestSegment.itP3().node().translate(offset * coeffN1); + + patchIt.nodeTopLeft().node().translate(translationOffset); + patchIt.nodeTopRight().node().translate(translationOffset); + patchIt.nodeBottomLeft().node().translate(translationOffset); + patchIt.nodeBottomRight().node().translate(translationOffset); + + offsetSegment(nearestSegment, nearestSegmentParam, segmentOffset); = } else if (m_d->mode =3D=3D Private::SPLIT_SEGMENT) { *m_d->currentArgs.meshTransform() =3D m_d->initialMeshState;