File indexing completed on 2024-05-12 04:06:21

0001 /*
0002     SPDX-FileCopyrightText: 2010 Johannes Loehnert <loehnert.kde@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "grid.h"
0008 
0009 #include <cmath>
0010 #include <QtMath>
0011 #include <QDebug>
0012 #include <QPainterPath>
0013 #include <QProcess>
0014 #include <QRandomGenerator>
0015 #include "pointfinder.h"
0016 #include "utilities.h"
0017 
0018 // auxiliary functions for serializing / unserializing
0019 
0020 /// serialize a list of floats into their space-separated ascii representation
0021 QByteArray serializeLine(QList<qreal> input) {
0022     QStringList result;
0023     for (int i=0; i<input.size(); ++i) {
0024         result.append(QString::number(input[i]));
0025     }
0026     return result.join( QStringLiteral( " " )).toLatin1();
0027 }
0028 
0029 /// unserializes the first item of the input into a list of space-separated ints and removes it.
0030 QList<int> popIntLine(QList<QByteArray> &input) {
0031     QList<int> result;
0032     if (input.size() == 0) return result;
0033 
0034     const QStringList parts = QString::fromUtf8(input.takeFirst()).split(QLatin1Char(' '), Qt::SkipEmptyParts);
0035     bool ok;
0036 
0037     for (int i=0; i<parts.size(); ++i) {
0038         int n = parts[i].toInt(&ok);
0039         if (ok) {
0040             result.append(n);
0041         }
0042         else {
0043             qDebug() << "Failure converting to integer:" << parts[i];
0044         }
0045     }
0046     return result;
0047 }
0048 
0049 /// unserializes the first item of the input into a list of space-separated floats and removes it.
0050 QList<qreal> popFloatLine(QList<QByteArray> &input) {
0051     QList<qreal> result;
0052     if (input.size() == 0) return result;
0053 
0054     const QStringList parts = QString::fromUtf8(input.takeFirst()).split(QLatin1Char(' '), Qt::SkipEmptyParts);
0055     bool ok;
0056 
0057     for (int i=0; i<parts.size(); ++i) {
0058         qreal x = parts[i].toDouble(&ok);
0059         if (ok) {
0060             result.append(x);
0061         }
0062         else {
0063             qDebug() << "Failure converting to float:" << parts[i];
0064         }
0065     }
0066     return result;
0067 }
0068 
0069 /** if part of the line from p1 to p2 is out of bounds, shorten it  
0070 so that it is totally contained in the image frame. returns false
0071 if no part of the line is within the bounds.
0072 */
0073 bool crop_endpoints_to_frame(QPointF *p1, QPointF *p2, int width, int height) {
0074     QRectF frame = QRectF(0.0, 0.0, width, height);
0075     QLineF ridge = QLineF(*p1, *p2);
0076 
0077     // first check if line is completely within frame.
0078     bool p1_contained = frame.contains(*p1);
0079     bool p2_contained = frame.contains(*p2);
0080     if (p1_contained && p2_contained) return true;
0081 
0082     // no it is not, cropping is necessary.
0083     // determine intersection points with frame
0084     QPointF new_p1, new_p2;
0085     int new_points_found = 0;
0086 
0087     for (int i=0; i<4; i++) {
0088         QLineF border;
0089         switch (i) {
0090             case 0: border = QLineF(0, 0, width, 0); break;
0091             case 1: border = QLineF(0, 0, 0, height); break;
0092             case 2: border = QLineF(width, 0, width, height); break;
0093             case 3: border = QLineF(0, height, width, height); break;
0094         }
0095         if (new_points_found == 0) {
0096             if (QLineF::BoundedIntersection == border.intersects(ridge, &new_p1)) {
0097                 // if one point is inside, there will be only 1 intersection point.
0098                 // But, if one point is on the frame, we might have found it. check that.
0099                 new_points_found = 1;
0100                 if (p1_contained || p2_contained) {
0101                     if (new_p1!=*p1 && new_p1!=*p2) {
0102                         break;
0103                     }
0104                     else {
0105                         // pretend that nothing happened
0106                         new_points_found = 0;
0107                     }
0108                 }
0109             }
0110         }
0111         else {
0112             if (QLineF::BoundedIntersection == border.intersects(ridge, &new_p2)) {
0113                 // We have to set new_points_found > 1, in order to get
0114                 // endpoints cropped correctly in the case where both points are
0115                 // outside the frame, but the ridge passes through the frame.
0116                 // The lack of the next line was probably the cause of the
0117                 // crashes mentioned below
0118                 new_points_found = 2;
0119                 // no need to search further
0120                 break;
0121             }
0122         }
0123     }
0124 
0125     // border completely oob?
0126     if (new_points_found == 0) return false;
0127 
0128     if (new_points_found == 1) {
0129         // either p1 or p2 is contained. modify the uncontained point.
0130         if (p1_contained) {
0131             *p2 = new_p1; 
0132         }
0133         else {
0134             *p1 = new_p1;
0135         }
0136         // Sometimes there is a crash which probably comes from bad cropping.
0137         // Hoping to catch it with this.
0138         // Problem should be fixed now - SB
0139         qDebug() << "p1contained:" << p1_contained << " p1:" << *p1 << " p2:" << *p2;
0140     }
0141     else {
0142         // modify so that the direction of the line remains unchanged.
0143         qreal l1 = QLineF(*p1, new_p1).length();
0144         qreal l2 = QLineF(*p1, new_p2).length();
0145         if (l1 <= l2) {
0146             *p1 = new_p1;
0147             *p2 = new_p2;
0148         }
0149         else {
0150             *p1 = new_p2;
0151             *p2 = new_p1;
0152         }
0153     }
0154 
0155     return true;
0156 }
0157 
0158 void add_frame_segment(QPainterPath &path, QPointF from, QPointF to, int width, int height) {
0159     // find out on which segments of the frame the points lie.
0160     // 0 = top, 1 = right, 2 = bottom, 3 = left
0161     int seg_from = -1, seg_to = -1;
0162 
0163     if (from.y() == 0) seg_from = 0;
0164     if (from.x() == width) seg_from = 1;
0165     if (from.y() == height) seg_from = 2;
0166     if (from.x() == 0) seg_from = 3;
0167 
0168     if (to.y() == 0) seg_to = 0;
0169     if (to.x() == width) seg_to = 1;
0170     if (to.y() == height) seg_to = 2;
0171     if (to.x() == 0) seg_to = 3;
0172 
0173     if (seg_from == -1 || seg_to == -1) {
0174         qDebug() << "add_frame_segment: one of the points is not on the frame!";
0175         qDebug() << "from" << from << "to" << to;
0176     }
0177 
0178     while (seg_from != seg_to) {
0179         switch (seg_from) {
0180             case 0: path.lineTo(QPointF(width, 0)); break;
0181             case 1: path.lineTo(QPointF(width, height)); break;
0182             case 2: path.lineTo(QPointF(0, height)); break;
0183             case 3: path.lineTo(QPointF(0, 0)); break;
0184         }
0185         seg_from ++;
0186         if (seg_from>3) seg_from = 0;
0187     }
0188     path.lineTo(to);
0189 }
0190 
0191 // end auxiliary functions
0192 
0193 void IrregularMode::generateGrid(GoldbergEngine *e, int piece_count) const {
0194     PointFinder *pfinder, *new_pfinder;
0195     int width = e->get_image_width();
0196     int height = e->get_image_height();
0197     int steps = e->m_irregular_relaxation_steps;
0198     qreal radius = 1.5 * sqrt(1.0 * width * height / piece_count);
0199 
0200     if (piece_count < 2) piece_count = 2;
0201 
0202     pfinder = new PointFinder(width, height, radius);
0203     auto *generator = QRandomGenerator::global();
0204     for (int i=0; i<piece_count; ++i) {
0205         qreal x = 0.000001 * qreal(generator->bounded(1000000)) * width;
0206         qreal y = 0.000001 * qreal(generator->bounded(1000000)) * height;
0207         pfinder->append(QPointF(x, y));
0208     }
0209 
0210     // If you just take random points, you will find that the voronoi cells
0211     // are of very different sizes. For a jigsaw puzzle, this is not so nice.
0212     // To make the cells roughly equal size, we let the points push each
0213     // other out of the way (much like rubber balls in a box). By doing more
0214     // or less steps, one arrives at more or less equal cell sizes.
0215     for (int step = 0; step < steps; step++) {
0216         qreal step_factor;
0217         // step_factor determines the size of the step. it is
0218         // chosen so that the cells move roughly the same distance
0219         // in each step. The initial steps are smaller since the
0220         // "forces" are higher in unrelaxed state.
0221         if (step>=19) {
0222             step_factor = 0.5;
0223         }
0224         else {
0225             step_factor = 1.0/(20-step);
0226         }
0227 
0228         new_pfinder = new PointFinder(width, height, radius);
0229         QList<QPointF> points = pfinder->points();
0230         for (int i=0; i < points.size(); i++) {
0231             qreal x = points[i].x(), y = points[i].y();
0232             QList<QPointF> others = pfinder->find_neighbours(points[i]);
0233             QPointF force = QPointF(0.0, 0.0);
0234             for (int j=0; j<others.size(); j++) {
0235                 qreal dist = QLineF(points[i], others[j]).length();
0236                 // at 0 distance, force is 1; it shrinks linearly until
0237                 // it reaches zero at "radius" distance.
0238                 force += (points[i] - others[j]) / dist * (1.0 - dist/radius);
0239             }
0240             // repulsive walls
0241             if (x < 0.5*radius) force += QPointF(1.0 - 2*x/radius, 0.0);
0242             if (x > width - 0.5*radius) force -= QPointF(1.0 - 2*(width-x)/radius, 0.0);
0243             if (y < 0.5*radius) force += QPointF(0.0, 1.0 - 2*y/radius);
0244             if (y > height - 0.5*radius) force -= QPointF(0.0, 1.0 - 2*(height-y)/radius);
0245 
0246             // 0.5 : newtons 3rd law (force acts on both partners)
0247             force *= 0.5 * radius * step_factor;
0248             x += force.x();
0249             y += force.y();
0250             if (x<0.0) x = 0.0;
0251             if (y<0.0) y = 0.0;
0252             if (x>width) x = width;
0253             if (y>height) y = height;
0254             new_pfinder->append(QPointF(x, y));
0255         }
0256         delete pfinder;
0257         pfinder = new_pfinder;
0258         new_pfinder = nullptr;
0259     }
0260 
0261 
0262     generateVoronoiGrid(e, pfinder->points());
0263     delete pfinder;
0264 }
0265 
0266 
0267 bool IrregularMode::checkForQVoronoi() {
0268     QProcess process;
0269 
0270     process.start(QStringLiteral("qvoronoi"), QStringList());
0271     process.waitForStarted();
0272     if (process.error() == QProcess::FailedToStart) {
0273         return false;
0274     }
0275     process.close();
0276     return true;
0277 
0278 }
0279 
0280 struct VoronoiVertex {
0281     QPointF position;
0282     QList<GBClassicPlugParams*> connected_borders;
0283 };
0284     
0285 struct VoronoiCell {
0286     QPointF center;
0287     QList<int> neighbours;
0288     QList<GBClassicPlugParams*> borders;
0289     QList<int> border_from;
0290     QList<int> border_to;
0291 };
0292 
0293 void IrregularMode::generateVoronoiGrid(GoldbergEngine *e, QList<QPointF> cell_centers) const {
0294     QList<VoronoiVertex> cell_corners;
0295     QList<VoronoiCell> cells;
0296     QList<GBClassicPlugParams*> borders;
0297     int width = e->get_image_width();
0298     int height = e->get_image_height();
0299 
0300     int collision_tries = 10 * e->m_plug_size * e->m_plug_size;
0301     if (collision_tries < 5) collision_tries = 5;
0302     const qreal collision_shrink_factor = 0.95;
0303 
0304     e->m_length_base = qSqrt(width * height / cell_centers.size());
0305 
0306     QList<int> int_line;
0307 
0308     // prepare list of pieces to create
0309     for (int n=0; n<cell_centers.size(); n++) {
0310         VoronoiCell c = VoronoiCell();
0311         c.center = cell_centers[n];
0312         cells.append(c);
0313     }
0314 
0315     // convert the cell center list into ASCII
0316     QByteArray qvoronoi_input;
0317     qvoronoi_input.append("2\n"); // dimension
0318     // append a large box so that all ridges we care about are bounded.
0319     // cell_centers is not used afterwards, so we can modify it.
0320     cell_centers.append(QPointF(-width, -height));
0321     cell_centers.append(QPointF(-width, 2 * height));
0322     cell_centers.append(QPointF(2 * width, -height));
0323     cell_centers.append(QPointF(2 * width, 2 * height));
0324     qvoronoi_input.append(QString::number(cell_centers.size()).toLatin1()).append("\n");
0325     for (int n=0; n<cell_centers.size(); n++) {
0326         QList<qreal> coords;
0327         coords.append(cell_centers[n].x());
0328         coords.append(cell_centers[n].y());
0329         qvoronoi_input.append(serializeLine(coords)).append("\n");
0330     }
0331 
0332     //qDebug() << "INPUT for qvoronoi: " << qvoronoi_input;
0333 
0334     // shellout to qvoronoi, and ask it to return voronoi vertices (p) and ridges (Fv) for possibly degenerate (Qz) input.
0335     QProcess process;
0336     process.start(QStringLiteral("qvoronoi"), QStringList() << QStringLiteral("Qz") << QStringLiteral("p") << QStringLiteral("Fv"));
0337     process.waitForStarted();
0338     process.write(qvoronoi_input);
0339     process.closeWriteChannel();
0340     process.waitForFinished();
0341     QByteArray qvoronoi_output = process.readAll();
0342     //qDebug() << "OUTPUT of qvoronoi: " << qvoronoi_output;
0343     QList<QByteArray> qvoronoi_output_lines = qvoronoi_output.split('\n');
0344 
0345 
0346     // read list of voronoi vertices
0347     // first line is the dimension again, which we already know
0348     popIntLine(qvoronoi_output_lines); 
0349     // get corner count
0350     int_line = popIntLine(qvoronoi_output_lines);
0351     if (int_line.size() == 0) return; // bad output
0352     int n_corners = int_line[0];
0353 
0354     // read them
0355     for (int n=0; n<n_corners; n++) {
0356         cell_corners.append(VoronoiVertex());
0357         QList<qreal> vertex_coords = popFloatLine(qvoronoi_output_lines);
0358         if (vertex_coords.size() < 2) return; // bad output
0359         cell_corners.last().position = QPointF(vertex_coords[0], vertex_coords[1]);
0360     }
0361 
0362     // GENERATE BORDERS by reading ridge list
0363 
0364     int_line = popIntLine(qvoronoi_output_lines);
0365     if (int_line.size() == 0) return; // bad output
0366     int ridge_count = int_line[0];
0367 
0368     for (int n=0; n<ridge_count; n++) {
0369         // read ridge line
0370         int_line = popIntLine(qvoronoi_output_lines);
0371         if (int_line.size() < 5) return; // bad output
0372         // elements: 0: always 4; 1, 2 cell ids; 3, 4: vertices
0373         int cell1 = int_line[1];
0374         int cell2 = int_line[2];
0375         int vert1 = int_line[3] - 1;
0376         int vert2 = int_line[4] - 1;
0377 
0378         if (vert1 == -1 || vert2 == -1) continue;
0379 
0380         // get vertice coordinates 1 and 2
0381         QPointF p1, p2;
0382         p1 = cell_corners[vert1].position;
0383         p2 = cell_corners[vert2].position;
0384 
0385         // crop OOB endpoints to frame
0386         //qDebug() << "before crop: " << p1 << p2;
0387         bool ridge_oob = !crop_endpoints_to_frame(&p1, &p2, width, height);
0388         //qDebug() << "after crop: " << p1 << p2;
0389 
0390         // Add the cell with lower id to the neighbour list of the cell with
0391         // higher id, but only if none of both belongs to the "safety box"
0392         // and only if the ridge is not out of bounds.
0393         if ( ! ridge_oob && cell1 < cells.size() && cell2 < cells.size()) {
0394             if (cell1 < cell2) {
0395                 cells[cell2].neighbours.append(cell1);
0396             }
0397             else {
0398                 cells[cell1].neighbours.append(cell2);
0399             }
0400         }
0401 
0402         GBClassicPlugParams *p_plug;
0403 
0404         if (!ridge_oob) {
0405             // create a border for the ridge
0406             GBClassicPlugParams plug = e->initEdge(false);
0407             plug.unit_x = QLineF(p1, p2);
0408             // if border is short, make it plugless...
0409             if (plug.unit_x.length() < e->m_length_base * 0.3) e->makePlugless(plug);
0410             // and if it is *very* short, make it straight.
0411             if (plug.unit_x.length() < e->m_length_base * 0.15) plug.is_straight = true;
0412 
0413             // collision-check that border against all borders already connected to both endpoints
0414             // but only if it is visible
0415             if (!ridge_oob) {
0416                 bool intersects = true;
0417                 QList<GBClassicPlugParams*> offenders;
0418 
0419                 for (int i=0; i<collision_tries && intersects; i++) {
0420                     offenders.clear();
0421                     if (i>0 && intersects) {
0422                         plug.size_correction *= collision_shrink_factor;
0423                         e->reRandomizeEdge(plug, false);
0424                     }
0425                     intersects = false;
0426                     if (cell1 < cells.size()) {
0427                         for (int j=0; j<cells[cell1].borders.size(); j++) {
0428                             if (cells[cell1].borders[j] == NULL) {
0429                                 intersects |= e->plugOutOfBounds(plug);
0430                             }
0431                             else {
0432                                 intersects |= e->plugsIntersect(plug, *(cells[cell1].borders[j]), &offenders);
0433                             }
0434                         }
0435                     }
0436                     if (cell2 < cells.size()) {
0437                         for (int j=0; j<cells[cell2].borders.size(); j++) {
0438                             if (cells[cell2].borders[j] == NULL) {
0439                                 intersects |= e->plugOutOfBounds(plug);
0440                             }
0441                             else {
0442                                 intersects |= e->plugsIntersect(plug, *(cells[cell2].borders[j]), &offenders);
0443                             }
0444                         }
0445                     }
0446                 }
0447                 if (intersects) {
0448                     // make the colliding borders plugless
0449                     e->makePlugless(plug);
0450                     for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0451                 }
0452             }
0453 
0454             p_plug = new GBClassicPlugParams();
0455             *p_plug = plug;
0456             borders.append(p_plug);
0457 
0458             // add border to both VVertices' "connected" list
0459             cell_corners[vert1].connected_borders.append(p_plug);
0460             cell_corners[vert2].connected_borders.append(p_plug);
0461         }
0462         else {
0463             // invisible ridge: no border definition
0464             p_plug = nullptr;
0465         }
0466 
0467         // add the border to the cells
0468         if (cell1 < cells.size()) {
0469             cells[cell1].borders.append(p_plug);
0470             cells[cell1].border_from.append(vert1);
0471             cells[cell1].border_to.append(vert2);
0472         }
0473         
0474         if (cell2 < cells.size()) {
0475             cells[cell2].borders.append(p_plug);
0476             cells[cell2].border_from.append(vert1);
0477             cells[cell2].border_to.append(vert2);
0478         }
0479     }
0480 
0481     // CREATE PIECES
0482 
0483     for (int n=0; n<cells.size(); n++) {
0484         int first_plug = 0;
0485         while (first_plug < cells[n].borders.size() && cells[n].borders[first_plug] == NULL) first_plug++;
0486 
0487         if (first_plug >= cells[n].borders.size()) {
0488             //qDebug() << "piece" << n << "has no visible borders, skipping it";
0489             continue;
0490             // this will probably lead to problems :-( (missing piece)
0491         }
0492 
0493         GBClassicPlugParams *p_plug;
0494         p_plug = cells[n].borders[first_plug];
0495 
0496         // calculate angle for p1()-center and p2()-center of first border 
0497         QLineF l1 = QLineF(cells[n].center, p_plug->unit_x.p1());
0498         QLineF l2 = QLineF(cells[n].center, p_plug->unit_x.p2());
0499         qreal anglediff = l1.angleTo(l2);
0500 
0501         QList<int> order = QList<int>();
0502         QList<bool> reversed = QList<bool>();
0503         QList<QLineF> vectors = QList<QLineF>();
0504 
0505         // we want to add the borders clockwise!!
0506         if (!(anglediff >= 0 && anglediff <= 360)) qDebug() << "anglediff out of expected range:" << anglediff;
0507         order.append(first_plug);
0508         reversed.append(anglediff < 180);
0509         if (reversed.first()) {
0510             vectors.append(QLineF(cells[n].borders[first_plug]->unit_x.p2(), cells[n].borders[first_plug]->unit_x.p1()));
0511         }
0512         else {
0513             vectors.append(cells[n].borders[first_plug]->unit_x);
0514         }
0515 
0516         // argsort the borders by connecting border_from and border_to, and mark reversions
0517         // this is a dumb O(N2) sort, but we are talking about, say, 6 listitems here.
0518         //qDebug() << "sorting borders for cell " << n;
0519 
0520         int current_border = first_plug;
0521         int first_vertex = reversed.last() ? cells[n].border_to[current_border] : cells[n].border_from[current_border];
0522         int current_vertex = reversed.last() ? cells[n].border_from[current_border] : cells[n].border_to[current_border];
0523         while (current_vertex != first_vertex) {
0524             for (int i=0; i<cells[n].borders.size(); i++) {
0525                 if (i == current_border) continue;
0526                 if (cells[n].border_from[i] == current_vertex) {
0527                     // skip "invisible" borders
0528                     if (cells[n].borders[i] != NULL) {
0529                         order.append(i);
0530                         reversed.append(false);
0531                         vectors.append(cells[n].borders[i]->unit_x);
0532                     }
0533                     current_border = i;
0534                     current_vertex = cells[n].border_to[i];
0535                     break;
0536                 }
0537                 if (cells[n].border_to[i] == current_vertex) {
0538                     if (cells[n].borders[i] != NULL) {
0539                         order.append(i);
0540                         reversed.append(true);
0541                         vectors.append(QLineF(cells[n].borders[i]->unit_x.p2(), cells[n].borders[i]->unit_x.p1()));
0542                     }
0543                     current_border = i;
0544                     current_vertex = cells[n].border_from[i];
0545                     break;
0546                 }
0547             }
0548         }
0549 
0550         //qDebug() << "drawing path";
0551         // init path
0552         QPainterPath path;
0553         path.moveTo(vectors[0].p1());
0554 
0555         // iterate over the sorted borders
0556         for (int i=0; i<order.size(); i++) {
0557             e->addPlugToPath(path, reversed[i], *cells[n].borders[order[i]]);
0558             //qDebug() << "add border from" << vectors[i].p1() << "to" << vectors[i].p2();
0559 
0560             // if startpoint of next border != endpoint of last border, there must be a frame segment inbetween - add it
0561             if (vectors[i].p2() != vectors[(i+1) % order.size()].p1()) {
0562                 add_frame_segment(path, vectors[i].p2(), vectors[(i+1) % order.size()].p1(), width, height);
0563                 //qDebug() << "add frame segment";
0564             }
0565         }
0566         // render piece
0567         e->makePieceFromPath(n, path);
0568          
0569         // RELATIONS: iterate neighbour list and add relations. 
0570         for (int i=0; i<cells[n].neighbours.size(); i++) {
0571             e->addRelation(n, cells[n].neighbours[i]);
0572         }
0573     }
0574 
0575     // cleanup
0576     for (int i = 0; i < borders.size(); i++) {
0577         if (borders[i]!=NULL) delete borders[i];
0578     }
0579 }
0580