File indexing completed on 2024-05-26 04:32:06

0001 /*
0002  * SPDX-FileCopyrightText: 2022 Agata Cacko <cacko.azh@gmail.com>
0003  */
0004 
0005 #include "PerspectiveBasedAssistantHelper.h"
0006 
0007 #include <klocalizedstring.h>
0008 #include "kis_debug.h"
0009 #include <QPainter>
0010 #include <QPainterPath>
0011 #include <QLinearGradient>
0012 #include <QTransform>
0013 
0014 #include <kis_canvas2.h>
0015 #include <kis_coordinates_converter.h>
0016 #include "kis_algebra_2d.h"
0017 #include <Eigen/Eigenvalues>
0018 
0019 #include <math.h>
0020 #include<QDebug>
0021 #include <QtMath>
0022 
0023 #include <functional>
0024 
0025 
0026 
0027 bool PerspectiveBasedAssistantHelper::getTetragon(const QList<KisPaintingAssistantHandleSP>& handles, bool isAssistantComplete, QPolygonF& outPolygon)
0028 {
0029     outPolygon.clear();
0030     for (int i = 0; i < handles.size(); ++i) {
0031         outPolygon.push_back(*handles[i]);
0032     }
0033 
0034     if (!isAssistantComplete) {
0035         return false;
0036     }
0037 
0038     int sum = 0;
0039     int signs[4];
0040 
0041     for (int i = 0; i < 4; ++i) {
0042         int j = (i == 3) ? 0 : (i + 1);
0043         int k = (j == 3) ? 0 : (j + 1);
0044         signs[i] = KisAlgebra2D::signZZ(pdot(outPolygon[j] - outPolygon[i], outPolygon[k] - outPolygon[j]));
0045         sum += signs[i];
0046     }
0047 
0048     if (sum == 0) {
0049         // complex (crossed)
0050         for (int i = 0; i < 4; ++i) {
0051             int j = (i == 3) ? 0 : (i + 1);
0052             if (signs[i] * signs[j] == -1) {
0053                 // opposite signs: uncross
0054                 std::swap(outPolygon[i], outPolygon[j]);
0055                 return true;
0056             }
0057         }
0058         // okay, maybe it's just a line
0059         return false;
0060     } else if (sum != 4 && sum != -4) {
0061         // concave, or a triangle
0062         if (sum == 2 || sum == -2) {
0063             // concave, let's return a triangle instead
0064             for (int i = 0; i < 4; ++i) {
0065                 int j = (i == 3) ? 0 : (i + 1);
0066                 if (signs[i] != KisAlgebra2D::signZZ(sum)) {
0067                     // wrong sign: drop the inside node
0068                     outPolygon.remove(j);
0069                     return false;
0070                 }
0071             }
0072         }
0073         return false;
0074     }
0075     // convex
0076     return true;
0077 }
0078 
0079 QPolygonF PerspectiveBasedAssistantHelper::getAllConnectedTetragon(const QList<KisPaintingAssistantHandleSP>& handles)
0080 {
0081     QPolygonF polyAllConnected;
0082     if (handles.size() < 4) {
0083         return polyAllConnected;
0084     }
0085     polyAllConnected << *handles[0] << *handles[1] << *handles[2] << *handles[3] << *handles[0] << *handles[2] << *handles[1] << *handles[3];
0086     return polyAllConnected;
0087 }
0088 
0089 qreal PerspectiveBasedAssistantHelper::localScale(const QTransform &transform, QPointF pt)
0090 {
0091     //    const qreal epsilon = 1e-5, epsilonSquared = epsilon * epsilon;
0092     //    qreal xSizeSquared = lengthSquared(transform.map(pt + QPointF(epsilon, 0.0)) - orig) / epsilonSquared;
0093     //    qreal ySizeSquared = lengthSquared(transform.map(pt + QPointF(0.0, epsilon)) - orig) / epsilonSquared;
0094     //    xSizeSquared /= lengthSquared(transform.map(QPointF(0.0, pt.y())) - transform.map(QPointF(1.0, pt.y())));
0095     //    ySizeSquared /= lengthSquared(transform.map(QPointF(pt.x(), 0.0)) - transform.map(QPointF(pt.x(), 1.0)));
0096     //  when taking the limit epsilon->0:
0097     //  xSizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4
0098     //  ySizeSquared=((m23*y+m33)^2*(m23*y+m33+m13)^2)/(m23*y+m13*x+m33)^4
0099     //  xSize*ySize=(abs(m13*x+m33)*abs(m13*x+m33+m23)*abs(m23*y+m33)*abs(m23*y+m33+m13))/(m23*y+m13*x+m33)^4
0100     const qreal x = transform.m13() * pt.x(),
0101             y = transform.m23() * pt.y(),
0102             a = x + transform.m33(),
0103             b = y + transform.m33(),
0104             c = x + y + transform.m33(),
0105             d = c * c;
0106     return fabs(a*(a + transform.m23())*b*(b + transform.m13()))/(d * d);
0107 }
0108 
0109 qreal PerspectiveBasedAssistantHelper::inverseMaxLocalScale(const QTransform &transform)
0110 {
0111     const qreal a = fabs((transform.m33() + transform.m13()) * (transform.m33() + transform.m23())),
0112             b = fabs((transform.m33()) * (transform.m13() + transform.m33() + transform.m23())),
0113             d00 = transform.m33() * transform.m33(),
0114             d11 = (transform.m33() + transform.m23() + transform.m13())*(transform.m33() + transform.m23() + transform.m13()),
0115             s0011 = qMin(d00, d11) / a,
0116             d10 = (transform.m33() + transform.m13()) * (transform.m33() + transform.m13()),
0117             d01 = (transform.m33() + transform.m23()) * (transform.m33() + transform.m23()),
0118             s1001 = qMin(d10, d01) / b;
0119     return qMin(s0011, s1001);
0120 }
0121 
0122 qreal PerspectiveBasedAssistantHelper::distanceInGrid(const QList<KisPaintingAssistantHandleSP>& handles, bool isAssistantComplete, const QPointF &point)
0123 {
0124     // TODO: make it not calculate the poly max distance over and over
0125     qreal defaultValue = 1;
0126     int vertexCount = 4;
0127 
0128     QPolygonF poly;
0129     if (!PerspectiveBasedAssistantHelper::getTetragon(handles, isAssistantComplete, poly)) {
0130         return defaultValue;
0131     }
0132 
0133     boost::optional<QPointF> vp1;
0134     boost::optional<QPointF> vp2;
0135 
0136     PerspectiveBasedAssistantHelper::getVanishingPointsOptional(poly, vp1, vp2);
0137     if (!vp1 && !vp2) {
0138         return defaultValue; // possibly wrong shape
0139     } else if (!vp1 || !vp2) {
0140         // result should be:
0141         // dist from horizon / max dist from horizon
0142         // horizon is parallel to the parallel sides of the tetragon (two must be parallel if there is only one vp)
0143         QLineF horizon;
0144         if (vp1) {
0145             // that means the 0-1 line is the horizon parallel line
0146             horizon = QLineF(vp1.get(), vp1.get() + poly[1] - poly[0]);
0147         } else {
0148             horizon = QLineF(vp2.get(), vp2.get() + poly[2] - poly[1]);
0149         }
0150 
0151         qreal dist = kisDistanceToLine(point, horizon);
0152         qreal distMax = 0;
0153         for (int i = 0; i < vertexCount; i++) {
0154             qreal vertexDist = kisDistanceToLine(poly[i], horizon);
0155             if (vertexDist > distMax) {
0156                 distMax = vertexDist;
0157             }
0158         }
0159         if (distMax == 0) {
0160             return defaultValue;
0161         }
0162         return dist/distMax;
0163     } else if (vp1 && vp2) {
0164         // should be:
0165         // dist from vp-line / max dist from vp-line
0166         QLineF horizon = QLineF(vp1.get(), vp2.get());
0167 
0168         qreal dist = kisDistanceToLine(point, horizon);
0169         qreal distMax = 0;
0170         for (int i = 0; i < vertexCount; i++) {
0171             qreal vertexDist = kisDistanceToLine(poly[i], horizon);
0172             if (vertexDist > distMax) {
0173                 distMax = vertexDist;
0174             }
0175         }
0176         if (distMax == 0) {
0177             return defaultValue;
0178         }
0179         return dist/distMax;
0180     }
0181 
0182     return defaultValue;
0183 
0184 }
0185 
0186 qreal PerspectiveBasedAssistantHelper::distanceInGrid(const PerspectiveBasedAssistantHelper::CacheData &cache, const QPointF& point)
0187 {
0188     qreal defaultValue = 1;
0189     if (cache.maxDistanceFromPoint == 0.0) {
0190         return defaultValue;
0191     }
0192 
0193     if (!cache.vanishingPoint1 && !cache.vanishingPoint2) {
0194         return defaultValue; // possibly wrong shape
0195     } else if (!cache.vanishingPoint1 || !cache.vanishingPoint2) {
0196         // result should be:
0197         // dist from horizon / max dist from horizon
0198         // horizon is parallel to the parallel sides of the tetragon (two must be parallel if there is only one vp)
0199         qreal dist = kisDistanceToLine(point, cache.horizon);
0200         return dist/cache.maxDistanceFromPoint;
0201     } else if (cache.vanishingPoint1 && cache.vanishingPoint2) {
0202         // should be:
0203         // dist from vp-line / max dist from vp-line
0204         qreal dist = kisDistanceToLine(point, cache.horizon);
0205         return dist/cache.maxDistanceFromPoint;
0206     }
0207 
0208     return defaultValue;
0209 }
0210 
0211 void PerspectiveBasedAssistantHelper::updateCacheData(PerspectiveBasedAssistantHelper::CacheData &cache, const QPolygonF &poly)
0212 {
0213     cache.polygon = poly;
0214     bool r = PerspectiveBasedAssistantHelper::getVanishingPointsOptional(poly, cache.vanishingPoint1, cache.vanishingPoint2);
0215     Q_UNUSED(r);
0216 
0217     if (cache.vanishingPoint1 && cache.vanishingPoint2) {
0218         cache.type = CacheData::TwoVps;
0219     } else if (cache.vanishingPoint1 || cache.vanishingPoint2) {
0220         cache.type = CacheData::OneVp;
0221     } else {
0222         cache.type = CacheData::None;
0223         cache.horizon = QLineF();
0224         cache.distancesFromPoints = QVector<qreal>();
0225         cache.maxDistanceFromPoint = 0.0;
0226         return;
0227     }
0228 
0229     if (cache.type == CacheData::TwoVps) {
0230         cache.horizon = QLineF(cache.vanishingPoint1.get(), cache.vanishingPoint2.get());
0231     } else if (cache.type == CacheData::OneVp) {
0232         if (cache.vanishingPoint1) {
0233             // that means the 0-1 line is the horizon parallel line
0234             cache.horizon = QLineF(cache.vanishingPoint1.get(), cache.vanishingPoint1.get() + cache.polygon[1] - cache.polygon[0]);
0235         } else { // the other vp
0236             cache.horizon = QLineF(cache.vanishingPoint2.get(), cache.vanishingPoint2.get() + cache.polygon[2] - cache.polygon[1]);
0237         }
0238     }
0239 
0240     int vertexCount = 4;
0241     cache.distancesFromPoints.fill(0.0, vertexCount);
0242     for (int i = 0; i < vertexCount; i++) {
0243         qreal vertexDist = kisDistanceToLine(cache.polygon[i], cache.horizon);
0244         if (vertexDist > cache.maxDistanceFromPoint) {
0245             cache.maxDistanceFromPoint = vertexDist;
0246         }
0247         cache.distancesFromPoints[i] = vertexDist;
0248     }
0249 
0250 }
0251 
0252 bool PerspectiveBasedAssistantHelper::getVanishingPointsOptional(const QPolygonF &poly, boost::optional<QPointF> &vp1, boost::optional<QPointF> &vp2)
0253 {
0254     bool either = false;
0255     vp1 = boost::none;
0256     vp2 = boost::none;
0257 
0258     if (poly.size() < 4) { // four points are required for a tetragon
0259         return false;
0260     }
0261 
0262     QPointF intersection(0, 0);
0263     // note: in code it seems like vp1 and vp2 are swapped, but it's all correct if you read carefully
0264 
0265     if (fmod(QLineF(poly[0], poly[1]).angle(), 180.0)>=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)+2.0
0266             || fmod(QLineF(poly[0], poly[1]).angle(), 180.0)<=fmod(QLineF(poly[2], poly[3]).angle(), 180.0)-2.0) {
0267         if (QLineF(poly[0], poly[1]).intersect(QLineF(poly[2], poly[3]), &intersection) != QLineF::NoIntersection) {
0268             vp2 = intersection;
0269             either = true;
0270         }
0271     }
0272     if (fmod(QLineF(poly[1], poly[2]).angle(), 180.0)>=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)+2.0
0273             || fmod(QLineF(poly[1], poly[2]).angle(), 180.0)<=fmod(QLineF(poly[3], poly[0]).angle(), 180.0)-2.0){
0274         if (QLineF(poly[1], poly[2]).intersect(QLineF(poly[3], poly[0]), &intersection) != QLineF::NoIntersection) {
0275             vp1 = intersection;
0276             either = true;
0277         }
0278     }
0279     return either;
0280 }
0281 
0282 qreal PerspectiveBasedAssistantHelper::pdot(const QPointF &a, const QPointF &b)
0283 {
0284     return a.x() * b.y() - a.y() * b.x();
0285 }
0286 
0287