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