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

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 <QPainterPath>
0011 #include <QDebug>
0012 #include "utilities.h"
0013 
0014 struct CairoCell {
0015     // corner : the border through the top left corner of the cell.
0016     GBClassicPlugParams corner;
0017     GBClassicPlugParams tl;
0018     GBClassicPlugParams tr;
0019     GBClassicPlugParams bl;
0020     GBClassicPlugParams br;
0021 
0022     // top and left tile of cell
0023     int id_top;
0024     int id_left;
0025 };
0026 
0027 void CairoMode::generateGrid(GoldbergEngine *e, int piece_count) const {
0028     int next_piece_id=0;
0029 
0030     int collision_tries = 10 * e->m_plug_size * e->m_plug_size;
0031     if (collision_tries < 5) collision_tries = 5;
0032     const qreal collision_shrink_factor = 0.95;
0033 
0034 
0035     // calculate piece counts
0036     const int width = e->get_image_width(), height = e->get_image_height();
0037     int xCount;
0038     int yCount;
0039     getBestFitExtended(xCount, yCount, 1.0 * width / height, piece_count, 2.0, 1., 1., 0.);
0040 
0041     qDebug() << "cell count x = " << xCount;
0042     qDebug() << "cell count y = " << yCount;
0043 
0044 
0045     const qreal cellWidth = 1.0 * width / xCount, cellHeight = 1.0 * height / yCount;
0046 
0047     // rationale: knobs should visually cover the same fraction of area as for the rect grid.
0048     e->m_length_base = sqrt(cellWidth * cellHeight * 0.5) * e->m_plug_size;
0049 
0050     // grid is made 1 unit larger in both dimensions, to store the right and bottom border cells.
0051     CairoCell** cells = new CairoCell*[xCount+1];
0052 
0053     qDebug() << "now generating edges";
0054  
0055     for (int x = 0; x < xCount+1; ++x) {
0056         cells[x] = new CairoCell[yCount + 1];
0057 
0058         for (int y = 0; y < yCount+1; ++y) {
0059 
0060             bool odd_cell = (x+y) % 2;
0061 
0062             // generate usual borders first, and cater for the "edge" cases afterwards.
0063             cells[x][y].corner = e->initEdge(false);
0064             cells[x][y].tr = e->initEdge(false);
0065             cells[x][y].tl = e->initEdge(false);
0066             cells[x][y].bl = e->initEdge(false);
0067             cells[x][y].br = e->initEdge(false);
0068 
0069             // determine border direction
0070             if (odd_cell) {
0071                 cells[x][y].tr.flipped ^= true;
0072                 cells[x][y].tl.flipped ^= true;
0073                 cells[x][y].bl.flipped ^= true;
0074                 cells[x][y].br.flipped ^= true;
0075             }
0076 
0077             cells[x][y].tr.flipped ^= e->m_alternate_flip;
0078             cells[x][y].br.flipped ^= e->m_alternate_flip;
0079             cells[x][y].bl.flipped ^= e->m_alternate_flip;
0080             cells[x][y].tl.flipped ^= e->m_alternate_flip;
0081 
0082             // set vector
0083             if (odd_cell) {
0084                 cells[x][y].corner.flipped ^= (y%2 == 1);
0085                 cells[x][y].corner.unit_x = QLineF((x-0.25) * cellWidth, y * cellHeight, (x+0.25) * cellWidth, y * cellHeight);
0086             }
0087             else {
0088                 cells[x][y].corner.flipped ^= (x%2 == 1);
0089                 cells[x][y].corner.unit_x = QLineF(x * cellWidth, (y-0.25) * cellHeight, x * cellWidth, (y + 0.25) * cellHeight);
0090             }
0091 
0092             cells[x][y].tl.unit_x = QLineF((x + (odd_cell?0.25:0.0 )) * cellWidth, 
0093                                            (y + (odd_cell?0.0 :0.25)) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
0094             cells[x][y].tr.unit_x = QLineF((x + (odd_cell?1.0 :0.75)) * cellWidth, 
0095                                            (y + (odd_cell?0.25:0.0 )) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
0096             cells[x][y].bl.unit_x = QLineF((x + (odd_cell?0.0 :0.25)) * cellWidth, 
0097                                            (y + (odd_cell?0.75:1.0 )) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
0098             cells[x][y].br.unit_x = QLineF((x + (odd_cell?0.75:1.0 )) * cellWidth, 
0099                                            (y + (odd_cell?1.0 :0.75)) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
0100 
0101             e->smooth_join(cells[x][y].tl, cells[x][y].br);
0102             e->smooth_join(cells[x][y].tr, cells[x][y].bl);
0103 
0104             // edges
0105             if (y==0) {
0106                 if (!odd_cell) {
0107                     cells[x][y].corner.unit_x = QLineF(x * cellWidth, y * cellHeight, x * cellWidth, (y+ 0.25) * cellHeight);
0108                 }
0109                 else {
0110                     cells[x][y].corner.is_straight = true;
0111                 }
0112             }
0113             if (x==0) {
0114                 if (odd_cell) {
0115                     cells[x][y].corner.unit_x = QLineF(x * cellWidth, y * cellHeight, (x+0.25) * cellWidth, y * cellHeight);
0116                 }
0117                 else {
0118                     cells[x][y].corner.is_straight = true;
0119                 }
0120             }
0121 
0122             if (y==yCount) {
0123                 if (!odd_cell) {
0124                     cells[x][y].corner.unit_x = QLineF(x * cellWidth, (y-0.25) * cellHeight, x * cellWidth, y * cellHeight);
0125                 }
0126                 else {
0127                     cells[x][y].corner.is_straight = true;
0128                 }
0129             }
0130             if (x==xCount) {
0131                 if (odd_cell) {
0132                     cells[x][y].corner.unit_x = QLineF((x-0.25) * cellWidth, y * cellHeight, x * cellWidth, y * cellHeight);
0133                 }
0134                 else {
0135                     cells[x][y].corner.is_straight = true;
0136                 }
0137             }
0138 
0139             // collision checking
0140             bool intersects;
0141             QList<GBClassicPlugParams*> offenders;
0142 
0143             // CORNER
0144             intersects = !cells[x][y].corner.is_straight;
0145             for (int i=0; i<collision_tries && intersects; i++) {
0146                 offenders.clear();
0147                 if (i>0 && intersects) {
0148                     //qDebug() << "collision: corner edge, x=" << x << ", y=" << y;
0149                     cells[x][y].corner.size_correction *= collision_shrink_factor;
0150                     e->reRandomizeEdge(cells[x][y].corner);
0151                 }
0152                 intersects = false;
0153                 if (x>0 && y>0) intersects |= e->plugsIntersect(cells[x][y].corner, cells[x-1][y-1].br, &offenders);
0154                 intersects |= (x==0) ? e->plugOutOfBounds(cells[x][y].corner) : e->plugsIntersect(cells[x][y].corner, cells[x-1][y].tr, &offenders);
0155                 intersects |= (y==0) ? e->plugOutOfBounds(cells[x][y].corner) : e->plugsIntersect(cells[x][y].corner, cells[x][y-1].bl, &offenders);
0156                 if (x==xCount || y==yCount) intersects |= e->plugOutOfBounds(cells[x][y].corner);
0157             }
0158             if (intersects) {
0159                 // give up and just make the colliding borders plugless.
0160                 e->makePlugless(cells[x][y].corner);
0161                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0162             }
0163 
0164 
0165             // TL
0166             // don't bother with the "outer" cells, they do not matter.
0167             intersects = (x<xCount && y < yCount);
0168             for (int i=0; i<collision_tries && intersects; i++) {
0169                 offenders.clear();
0170                 if (i>0 && intersects) {
0171                     //qDebug() << "collision: top left edge, x=" << x << ", y=" << y;
0172                     cells[x][y].tl.size_correction *= collision_shrink_factor;
0173                     e->reRandomizeEdge(cells[x][y].tl, true);
0174                 }
0175                 intersects = e->plugsIntersect(cells[x][y].tl, cells[x][y].corner, &offenders);
0176                 intersects |= (odd_cell ?
0177                                 ((y==0) ? e->plugOutOfBounds(cells[x][y].tl) : e->plugsIntersect(cells[x][y].tl, cells[x][y-1].bl, &offenders)) :
0178                                 ((x==0) ? e->plugOutOfBounds(cells[x][y].tl) : e->plugsIntersect(cells[x][y].tl, cells[x-1][y].tr, &offenders))
0179                             );
0180             }
0181             if (intersects) {
0182                 // give up and just make the colliding borders plugless.
0183                 e->makePlugless(cells[x][y].tl);
0184                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0185             }
0186 
0187             // TR
0188             // don't bother with the "outer" cells, they do not matter.
0189             intersects = (x<xCount && y < yCount);
0190             for (int i=0; i<collision_tries && intersects; i++) {
0191                 offenders.clear();
0192                 if (i>0 && intersects) {
0193                     //qDebug() << "collision: top right edge, x=" << x << ", y=" << y;
0194                     cells[x][y].tr.size_correction *= collision_shrink_factor;
0195                     e->reRandomizeEdge(cells[x][y].tr, true);
0196                 }
0197                 intersects = e->plugsIntersect(cells[x][y].tr, cells[x][y].tl, &offenders);
0198                 intersects |= (odd_cell ?
0199                                 ((x==xCount-1) ? e->plugOutOfBounds(cells[x][y].tr) : false) :
0200                                 ((y==0) ? e->plugOutOfBounds(cells[x][y].tr) : e->plugsIntersect(cells[x][y].tr, cells[x][y-1].br, &offenders))
0201                             );
0202             }
0203             if (intersects) {
0204                 // give up and just make the colliding borders plugless.
0205                 e->makePlugless(cells[x][y].tr);
0206                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0207             }
0208 
0209             // BL
0210             // don't bother with the "outer" cells, they do not matter.
0211             intersects = (x<xCount && y < yCount);
0212             for (int i=0; i<collision_tries && intersects; i++) {
0213                 offenders.clear();
0214                 if (i>0 && intersects) {
0215                     //qDebug() << "collision: bottom left edge, x=" << x << ", y=" << y;
0216                     cells[x][y].bl.size_correction *= collision_shrink_factor;
0217                     e->reRandomizeEdge(cells[x][y].bl, true);
0218                 }
0219                 intersects = e->plugsIntersect(cells[x][y].bl, cells[x][y].tl, &offenders);
0220                 intersects |= (odd_cell ?
0221                                 ((x==0) ? e->plugOutOfBounds(cells[x][y].bl) : e->plugsIntersect(cells[x][y].tr, cells[x-1][y].bl, &offenders)) :
0222                                 ((y==yCount-1) ? e->plugOutOfBounds(cells[x][y].bl) : false)
0223                             );
0224             }
0225             if (intersects) {
0226                 // give up and just make the colliding borders plugless.
0227                 e->makePlugless(cells[x][y].bl);
0228                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0229             }
0230 
0231             // BR
0232             // don't bother with the "outer" cells, they do not matter.
0233             intersects = (x<xCount && y < yCount);
0234             for (int i=0; i<collision_tries && intersects; i++) {
0235                 offenders.clear();
0236                 if (i>0 && intersects) {
0237                     //qDebug() << "collision: bottom right edge, x=" << x << ", y=" << y;
0238                     cells[x][y].br.size_correction *= collision_shrink_factor;
0239                     e->reRandomizeEdge(cells[x][y].br, true);
0240                 }
0241                 intersects = e->plugsIntersect(cells[x][y].br, cells[x][y].tr, &offenders);
0242                 intersects |= e->plugsIntersect(cells[x][y].br, cells[x][y].bl, &offenders);
0243                 intersects |= (odd_cell ?
0244                                 ((y==yCount-1) ? e->plugOutOfBounds(cells[x][y].br) : false) :
0245                                 ((x==xCount-1) ? e->plugOutOfBounds(cells[x][y].br) : false)
0246                             );
0247             }
0248             if (intersects) {
0249                 // give up and just make the colliding borders plugless.
0250                 e->makePlugless(cells[x][y].br);
0251                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0252             }
0253 
0254         } // end collision checking
0255     }
0256 
0257     qDebug() << "now creating pieces";
0258 
0259     for (int x = 0; x < xCount+1; ++x) {
0260         // checkerboard pattern as above
0261         for (int y = 0; y < yCount+1; ++y) {
0262             //create the mask path
0263             QPainterPath path;
0264             
0265             bool odd_cell = (x+y) % 2;
0266 
0267             // we start after the "corner" edge.
0268             path.moveTo(cells[x][y].corner.unit_x.p2());
0269 
0270             // TOP PIECE
0271             if (x < xCount) {
0272                 if (!odd_cell) e->addPlugToPath(path, true, cells[x][y].corner);
0273                 if (y==0) {
0274                     // half piece
0275                     path.lineTo(cells[x+1][y].corner.unit_x.p1());
0276                 }
0277                 else {
0278                     e->addPlugToPath(path, false, cells[x][y-1].bl);
0279                     e->addPlugToPath(path, true, cells[x][y-1].br);
0280                 }
0281                 if (odd_cell) e->addPlugToPath(path, false, cells[x+1][y].corner);
0282 
0283                 if (y==yCount) {
0284                     // half piece
0285                     path.lineTo(cells[x][y].corner.unit_x.p2());
0286                 }
0287                 else {
0288                     e->addPlugToPath(path, false, cells[x][y].tr);
0289                     e->addPlugToPath(path, true, cells[x][y].tl);
0290                 }
0291 
0292                 cells[x][y].id_top = next_piece_id++;
0293                 e->makePieceFromPath(cells[x][y].id_top, path);
0294             }
0295 
0296             // LEFT PIECE
0297             if (y < yCount) {
0298                 path = QPainterPath();
0299                 path.moveTo(cells[x][y].corner.unit_x.p2());
0300                 if (x==xCount) {
0301                     // half piece
0302                     path.lineTo(odd_cell ? cells[x-1][y].br.unit_x.p1() : cells[x][y+1].corner.unit_x.p2());
0303                 }
0304                 else {
0305                     e->addPlugToPath(path, false, cells[x][y].tl);
0306                     e->addPlugToPath(path, true, cells[x][y].bl);
0307                 }
0308                 if (!odd_cell) e->addPlugToPath(path, true, cells[x][y+1].corner);
0309 
0310                 if (x==0) {
0311                     // half piece
0312                     path.lineTo(odd_cell ? cells[x][y].corner.unit_x.p1() : cells[x][y].tl.unit_x.p1());
0313                 }
0314                 else {
0315                     e->addPlugToPath(path, false, cells[x-1][y].br);
0316                     e->addPlugToPath(path, true, cells[x-1][y].tr);
0317                 }
0318 
0319                 if (odd_cell) e->addPlugToPath(path, false, cells[x][y].corner);
0320                 
0321                 cells[x][y].id_left = next_piece_id++;
0322                 e->makePieceFromPath(cells[x][y].id_left, path);
0323             }
0324         }
0325     }
0326 
0327     qDebug() << "now adding relations";
0328 
0329     //create relations
0330     for (int x = 0; x < xCount+1; ++x) {
0331         for (int y = 0; y < yCount+1; ++y) {
0332             bool odd_cell = (x+y) % 2;
0333             // corner 
0334             if (odd_cell) {
0335                 if (y>0 && y < yCount) e->addRelation(cells[x][y].id_left, cells[x][y-1].id_left);
0336             }
0337             else {
0338                 if (x>0 && x < xCount) e->addRelation(cells[x][y].id_top, cells[x-1][y].id_top);
0339             }
0340             // inner-cell borders
0341             if (y < yCount && x < xCount) {
0342                 // tl
0343                 e->addRelation(cells[x][y].id_top, cells[x][y].id_left);
0344                 // tr
0345                 e->addRelation(cells[x][y].id_top, cells[x+1][y].id_left);
0346                 // bl
0347                 e->addRelation(cells[x][y].id_left, cells[x][y+1].id_top);
0348                 //br
0349                 e->addRelation(cells[x+1][y].id_left, cells[x][y+1].id_top);
0350             }
0351         }
0352     }
0353 
0354     //cleanup
0355     for (int x = 0; x < xCount+1; ++x) {
0356         delete[] cells[x];
0357     }
0358     delete[] cells;
0359 }