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 
0015 // see rotrex-grid.svg to understand everything.
0016 struct RotrexCell {
0017     GBClassicPlugParams horiz;
0018     GBClassicPlugParams vert;
0019     GBClassicPlugParams tl;
0020     GBClassicPlugParams tr;
0021     GBClassicPlugParams bl;
0022     GBClassicPlugParams br;
0023 
0024     // piece ids...
0025     int id_corner; // of the piece centered in the top left cell corner (hex or rect)
0026     int id_tri;    // of the triangular piece on the top cell corner
0027     int id_rect;   // of the rect piece fully contained in the cell
0028 };
0029 
0030 void RotrexMode::generateGrid(GoldbergEngine *e, int piece_count) const {
0031     // fixed offsets in relative units of cell size. see image.
0032     // set as constants so we don't have to throw magic numbers around all the time.
0033     // square root of three - helper number
0034     const qreal _R3 = sqrt(3.0);
0035     const qreal _xunit = 1.5 + _R3;
0036     const qreal x1 = 0.5 / _xunit, x2 = 1.0 / _xunit, x3 = (0.5 + _R3) / _xunit, x4 = (1 + _R3) / _xunit;
0037     const qreal _yunit = 1.0 + 0.5*_R3;
0038     const qreal y1 = (0.5*_R3) / _yunit, y2 = 1.0 / _yunit;
0039 
0040 
0041     int next_piece_id = 0;
0042 
0043     int collision_tries = 10 * e->m_plug_size * e->m_plug_size;
0044     if (collision_tries < 5) collision_tries = 5;
0045     const qreal collision_shrink_factor = 0.95;
0046 
0047     // calculate piece counts
0048     const int width = e->get_image_width(), height = e->get_image_height();
0049     int xCount;
0050     int yCount;
0051     getBestFitExtended(xCount, yCount, 1.0*width / height * _yunit / _xunit, piece_count, 3.0, 1.0, 2.0, 1.0);
0052 
0053     qDebug() << "cell count x = " << xCount;
0054     qDebug() << "cell count y = " << yCount;
0055 
0056     const qreal cellWidth = 1.0 * width / xCount, cellHeight = 1.0 * height / yCount;
0057 
0058 
0059     e->m_length_base = sqrt(cellWidth * cellHeight / 3.0) * e->m_plug_size;
0060 
0061     // GENERATE BORDERS
0062     // grid is made 1 unit larger in both dimensions, to store the right and bottom borders.
0063     RotrexCell** cells = new RotrexCell*[xCount+1];
0064 
0065     for (int x=0; x<xCount+1; x++) {
0066         cells[x] = new RotrexCell[yCount+1];
0067         for (int y=0; y<yCount+1; y++) {
0068             bool odd_cell = (x+y) % 2;
0069 
0070             // generate usual borders first, and cater for the "edge" cases afterwards.
0071             cells[x][y].horiz = e->initEdge(false);
0072             cells[x][y].vert = e->initEdge(false);
0073             cells[x][y].tl = e->initEdge(false);
0074             cells[x][y].tr = e->initEdge(false);
0075             cells[x][y].bl = e->initEdge(false);
0076             cells[x][y].br = e->initEdge(false);
0077 
0078             // determine border direction
0079             cells[x][y].horiz.flipped ^= (!odd_cell) ^ e->m_alternate_flip;
0080             cells[x][y].vert.flipped ^= (!odd_cell) ^ e->m_alternate_flip;
0081             cells[x][y].tl.flipped ^= odd_cell ^ e->m_alternate_flip;
0082             cells[x][y].tr.flipped ^= odd_cell ^ e->m_alternate_flip;
0083             cells[x][y].bl.flipped ^= (!odd_cell) ^ e->m_alternate_flip;
0084             cells[x][y].br.flipped ^= (!odd_cell) ^ e->m_alternate_flip;
0085 
0086             // now for the mad sh**. Set edge vectors.
0087             cells[x][y].horiz.unit_x = QLineF(
0088                         (x - x1) * cellWidth,
0089                         (y + (odd_cell ? y2 : y1)) * cellHeight,
0090                         (x + x1) * cellWidth,
0091                         (y + (odd_cell ? y2 : y1)) * cellHeight);
0092             cells[x][y].vert.unit_x = QLineF(
0093                         (x + (odd_cell ? x1 : x4)) * cellWidth,
0094                         (y - y2) * cellHeight,
0095                         (x + (odd_cell ? x1 : x4)) * cellWidth,
0096                         (y + y2) * cellHeight);
0097             cells[x][y].tl.unit_x = QLineF(
0098                         (x + (odd_cell ? x3 : x2)) * cellWidth,
0099                         y * cellHeight,
0100                         (x + x1) * cellWidth,
0101                         (y + (odd_cell ? y2 : y1)) * cellHeight);
0102             cells[x][y].tr.unit_x = QLineF(
0103                         (x + (odd_cell ? x3 : x2)) * cellWidth,
0104                         y * cellHeight,
0105                         (x + x4) * cellWidth,
0106                         (y + (odd_cell ? y1 : y2)) * cellHeight);
0107             cells[x][y].bl.unit_x = QLineF(
0108                         (x + x1) * cellWidth,
0109                         (y + (odd_cell ? y2 : y1)) * cellHeight,
0110                         (x + (odd_cell ? x2 : x3)) * cellWidth,
0111                         (y + 1) * cellHeight);
0112             cells[x][y].br.unit_x = QLineF(
0113                         (x + x4) * cellWidth,
0114                         (y + (odd_cell ? y1 : y2)) * cellHeight,
0115                         (x + (odd_cell ? x2 : x3)) * cellWidth,
0116                         (y + 1) * cellHeight);
0117 
0118             // pieces at frame
0119             // top edge
0120             if (y==0) {
0121                 cells[x][y].vert.unit_x.setP1(QPointF(
0122                             cells[x][y].vert.unit_x.x1(),
0123                             y * cellHeight));
0124             }
0125             // left edge
0126             if (x==0) {
0127                 cells[x][y].horiz.unit_x.setP1(QPointF(
0128                             x * cellWidth,
0129                             cells[x][y].horiz.unit_x.y1()));
0130             }
0131             // right edge
0132             if (x==xCount) {
0133                 cells[x][y].horiz.unit_x.setP2(QPointF(
0134                             x * cellWidth,
0135                             cells[x][y].horiz.unit_x.y2()));
0136             }
0137 
0138             // bottom edge
0139             if (y == yCount) {
0140                 cells[x][y].vert.unit_x.setP2(QPointF(
0141                             cells[x][y].vert.unit_x.x2(),
0142                             y * cellHeight));
0143             }
0144 
0145             // COLLISION CHECKS
0146             bool intersects;
0147             QList<GBClassicPlugParams*> offenders;
0148 
0149             // horiz
0150             intersects = (y < yCount);
0151             for (int i=0; i<collision_tries && intersects; i++) {
0152                 offenders.clear();
0153                 if (i>0 && intersects) {
0154                     cells[x][y].horiz.size_correction *= collision_shrink_factor;
0155                     e->reRandomizeEdge(cells[x][y].horiz);
0156                 }
0157                 if (x==0) {
0158                     intersects = e->plugOutOfBounds(cells[x][y].horiz);
0159                 }
0160                 else {
0161                     if (!odd_cell) {
0162                         intersects = e->plugsIntersect(cells[x][y].horiz, cells[x-1][y].tr, &offenders)
0163                                     || e->plugsIntersect(cells[x][y].horiz, cells[x-1][y+1].vert, &offenders);
0164                     }
0165                     else {
0166                         intersects = e->plugsIntersect(cells[x][y].horiz, cells[x-1][y].br, &offenders)
0167                                     || e->plugsIntersect(cells[x][y].horiz, cells[x-1][y].vert, &offenders);
0168                     }
0169                 }
0170                 if (x==xCount) intersects |= e->plugOutOfBounds(cells[x][y].horiz);
0171             }
0172             if (intersects) {
0173                 // give up and make colliding borders plugless.
0174                 e->makePlugless(cells[x][y].horiz);
0175                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0176             }
0177 
0178             // vert
0179             intersects = (x < xCount);
0180             for (int i=0; i<collision_tries && intersects; i++) {
0181                 offenders.clear();
0182                 if (i>0 && intersects) {
0183                     cells[x][y].vert.size_correction *= collision_shrink_factor;
0184                     e->reRandomizeEdge(cells[x][y].vert);
0185                 }
0186                 intersects = false;
0187                 if (y==0) {
0188                     intersects |= e->plugOutOfBounds(cells[x][y].vert);
0189                 }
0190                 else {
0191                     if (!odd_cell) {
0192                         intersects |= e->plugsIntersect(cells[x][y].vert, cells[x][y-1].br, &offenders);
0193                     }
0194                     else {
0195                         intersects |= e->plugsIntersect(cells[x][y].vert, cells[x][y-1].bl, &offenders);
0196                         intersects |= e->plugsIntersect(cells[x][y].vert, cells[x][y-1].horiz, &offenders);
0197                     }
0198                 }
0199                 if (odd_cell) intersects |= e->plugsIntersect(cells[x][y].vert, cells[x][y].horiz, &offenders);
0200                 if (y==yCount) intersects |= e->plugOutOfBounds(cells[x][y].vert);
0201             }
0202             if (intersects) {
0203                 // give up and make colliding borders plugless.
0204                 e->makePlugless(cells[x][y].vert);
0205                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0206             }
0207 
0208             if (x<xCount && y<yCount) {
0209                 // tl
0210                 intersects = true;
0211                 for (int i=0; i<collision_tries && intersects; i++) {
0212                     offenders.clear();
0213                     if (i>0 && intersects) {
0214                         cells[x][y].tl.size_correction *= collision_shrink_factor;
0215                         e->reRandomizeEdge(cells[x][y].tl);
0216                     }
0217                     intersects = (y>0 ? 
0218                             e->plugsIntersect(cells[x][y].tl, cells[x][y-1].bl, &offenders) :
0219                             e->plugOutOfBounds(cells[x][y].tl));
0220                     intersects |= (odd_cell ?
0221                             e->plugsIntersect(cells[x][y].tl, cells[x][y].vert, &offenders) :
0222                             e->plugsIntersect(cells[x][y].tl, cells[x][y].horiz, &offenders));
0223                 }
0224                 if (intersects) {
0225                     // give up and make colliding borders plugless.
0226                     e->makePlugless(cells[x][y].tl);
0227                     for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0228                 }
0229 
0230                 // tr
0231                 intersects = true;
0232                 for (int i=0; i<collision_tries && intersects; i++) {
0233                     offenders.clear();
0234                     if (i>0 && intersects) {
0235                         cells[x][y].tr.size_correction *= collision_shrink_factor;
0236                         e->reRandomizeEdge(cells[x][y].tr);
0237                     }
0238                     intersects = (y>0 ?
0239                             e->plugsIntersect(cells[x][y].tr, cells[x][y-1].br, &offenders) :
0240                             e->plugOutOfBounds(cells[x][y].tr));
0241                     intersects |= e->plugsIntersect(cells[x][y].tr, cells[x][y].tl, &offenders);
0242                     if (!odd_cell) intersects |= e->plugsIntersect(cells[x][y].tr, cells[x][y].vert, &offenders);
0243                 }
0244                 if (intersects) {
0245                     // give up and make colliding borders plugless.
0246                     e->makePlugless(cells[x][y].tr);
0247                     for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0248                 }
0249 
0250                 // bl
0251                 intersects = true;
0252                 for (int i=0; i<collision_tries && intersects; i++) {
0253                     offenders.clear();
0254                     if (i>0 && intersects) {
0255                         cells[x][y].bl.size_correction *= collision_shrink_factor;
0256                         e->reRandomizeEdge(cells[x][y].bl);
0257                     }
0258                     intersects = e->plugsIntersect(cells[x][y].bl, cells[x][y].tl, &offenders);
0259                     if (odd_cell) intersects |= e->plugsIntersect(cells[x][y].bl, cells[x][y].horiz, &offenders);
0260                     if (y==yCount-1) intersects |= e->plugOutOfBounds(cells[x][y].bl);
0261                 }
0262                 if (intersects) {
0263                     // give up and make colliding borders plugless.
0264                     e->makePlugless(cells[x][y].bl);
0265                     for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0266                 }
0267 
0268                 // br
0269                 intersects = true;
0270                 for (int i=0; i<collision_tries && intersects; i++) {
0271                     offenders.clear();
0272                     if (i>0 && intersects) {
0273                         cells[x][y].br.size_correction *= collision_shrink_factor;
0274                         e->reRandomizeEdge(cells[x][y].br);
0275                     }
0276                     intersects = e->plugsIntersect(cells[x][y].br, cells[x][y].bl, &offenders);
0277                     intersects |= e->plugsIntersect(cells[x][y].br, cells[x][y].tr, &offenders);
0278                     if (y==yCount-1) intersects |= e->plugOutOfBounds(cells[x][y].br);
0279                 }
0280                 if (intersects) {
0281                     // give up and make colliding borders plugless.
0282                     e->makePlugless(cells[x][y].br);
0283                     for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
0284                 }
0285             }
0286 
0287         }
0288     }
0289 
0290 
0291     // CREATE PIECES
0292     for (int x=0; x<xCount+1; x++) {
0293         for (int y=0; y<yCount+1; y++) {
0294             QPainterPath path;
0295             bool odd_cell = (x+y) % 2;
0296 
0297             // separate logic for even and odd cell (easier...)
0298             // the inner rect piece is common for both
0299             if (!odd_cell) {
0300                 // corner piece is hexagonal.
0301                 // this is a real beast since it might be halved or even quartered.
0302                 path = QPainterPath();
0303 
0304                 // upper half
0305                 path.moveTo((x + (x==0 ? 0.0 : -x2)) * cellWidth, y * cellHeight);
0306 
0307                 if (y==0) {
0308                     path.lineTo((x + (x==xCount ? 0.0 : x2)) * cellWidth, 0.0);
0309                 }
0310                 else {
0311                     if (x==0) {
0312                         path.lineTo(cells[x][y-1].horiz.unit_x.p1());
0313                     }
0314                     else {
0315                         e->addPlugToPath(path, true, cells[x-1][y-1].br);
0316                     }
0317                     e->addPlugToPath(path, false, cells[x][y-1].horiz);
0318                     if (x==xCount) {
0319                         path.lineTo(xCount * cellWidth, y * cellHeight);
0320                     }
0321                     else {
0322                         e->addPlugToPath(path, false, cells[x][y-1].bl);
0323                     }
0324                 }
0325                 // lower half
0326                 if (y==yCount) {
0327                     path.lineTo((x + (x==0 ? 0.0 : -x2)) * cellWidth, y * cellHeight);
0328                 }
0329                 else {
0330                     if (x==xCount) {
0331                         path.lineTo(cells[x][y].horiz.unit_x.p2());
0332                     }
0333                     else {
0334                         e->addPlugToPath(path, false, cells[x][y].tl);
0335                     }
0336                     e->addPlugToPath(path, true, cells[x][y].horiz);
0337                     if (x==0) {
0338                         path.lineTo(x * cellWidth, y * cellHeight);
0339                     }
0340                     else {
0341                         e->addPlugToPath(path, true, cells[x-1][y].tr);
0342                     }
0343                 }
0344 
0345                 cells[x][y].id_corner = next_piece_id++;
0346                 e->makePieceFromPath(cells[x][y].id_corner, path);
0347 
0348                 // triangle piece
0349                 if (x < xCount) {
0350                     path = QPainterPath();
0351                     path.moveTo(cells[x][y].tl.unit_x.p1());
0352 
0353                     if (y==0) {
0354                         path.lineTo((x+x4) * cellWidth, 0.0);
0355                     }
0356                     else {
0357                         e->addPlugToPath(path, true, cells[x][y-1].br);
0358                     }
0359                     e->addPlugToPath(path, false, cells[x][y].vert);
0360                     if (y==yCount) {
0361                         path.lineTo(cells[x][y].tl.unit_x.p1());
0362                     }
0363                     else {
0364                         e->addPlugToPath(path, true, cells[x][y].tr);
0365                     }
0366 
0367                     cells[x][y].id_tri = next_piece_id++;
0368                     e->makePieceFromPath(cells[x][y].id_tri, path);
0369                 }
0370 
0371             }
0372             else {
0373                 // rect piece
0374                 // might be halved or quartered.
0375                 path = QPainterPath();
0376                 path.moveTo((x + (x==0 ? 0.0 : -x1)) * cellWidth, (y + (y==0 ? 0.0 : -y2)) * cellHeight);
0377 
0378                 if (y==0) {
0379                     path.lineTo((x + (x==xCount ? 0.0 : x1)) * cellWidth, 0.0);
0380                 }
0381                 else {
0382                     e->addPlugToPath(path, false, cells[x][y-1].horiz);
0383                 }
0384                 if (x==xCount) {
0385                     path.lineTo(xCount * cellWidth, (y + (y==yCount ? 0.0 : y2)) * cellHeight);
0386                 }
0387                 else {
0388                     e->addPlugToPath(path, false, cells[x][y].vert);
0389                 }
0390                 if (y==yCount) {
0391                     path.lineTo((x + (x==0 ? 0 : -x1)) * cellWidth, yCount * cellHeight);
0392                 }
0393                 else {
0394                     e->addPlugToPath(path, true, cells[x][y].horiz);
0395                 }
0396                 if (x==0) {
0397                     path.lineTo(0.0, (y + (y==0 ? 0.0 : -y2)) * cellHeight);
0398                 }
0399                 else {
0400                     e->addPlugToPath(path, true, cells[x-1][y].vert);
0401                 }
0402 
0403                 cells[x][y].id_corner = next_piece_id++;
0404                 e->makePieceFromPath(cells[x][y].id_corner, path);
0405 
0406                 // trigonal piece
0407                 if (x < xCount) {
0408                     path = QPainterPath();
0409                     path.moveTo(cells[x][y].tl.unit_x.p1());
0410 
0411                     if (y==yCount) {
0412                         path.lineTo((x+x1) * cellWidth, yCount * cellHeight);
0413                     }
0414                     else {
0415                         e->addPlugToPath(path, false, cells[x][y].tl);
0416                     }
0417 
0418                     e->addPlugToPath(path, true, cells[x][y].vert);
0419 
0420                     if (y==0) {
0421                         path.lineTo(cells[x][y].tl.unit_x.p1());
0422                     }
0423                     else {
0424                         e->addPlugToPath(path, false, cells[x][y-1].bl);
0425                     }
0426                     cells[x][y].id_tri = next_piece_id++;
0427                     e->makePieceFromPath(cells[x][y].id_tri, path);
0428                 }
0429             }
0430             // inner rect piece
0431             if (x<xCount && y<yCount) {
0432                 path = QPainterPath();
0433                 path.moveTo(cells[x][y].tr.unit_x.p1());
0434                 e->addPlugToPath(path, false, cells[x][y].tr);
0435                 e->addPlugToPath(path, false, cells[x][y].br);
0436                 e->addPlugToPath(path, true, cells[x][y].bl);
0437                 e->addPlugToPath(path, true, cells[x][y].tl);
0438 
0439                 cells[x][y].id_rect = next_piece_id++;
0440                 e->makePieceFromPath(cells[x][y].id_rect, path);
0441             }
0442         }
0443     }
0444 
0445     // RELATIONS
0446     for (int x=0; x<xCount+1; x++) {
0447         for (int y=0; y<yCount+1; y++) {
0448             bool odd_cell = (x+y) % 2;
0449 
0450             // Each cell takes care of the relations corresponding to the borders it contains.
0451             // horiz
0452             if (y<yCount) e->addRelation(cells[x][y].id_corner, cells[x][y+1].id_corner);
0453             // vert
0454             if (x<xCount) e->addRelation(cells[x][y].id_tri, cells[x + (odd_cell ? 0 : 1)][y].id_corner);
0455 
0456             if (x<xCount && y<yCount) {
0457                 e->addRelation(cells[x][y].id_tri, cells[x][y].id_rect);
0458                 e->addRelation(cells[x][y].id_rect, cells[x][y+1].id_tri);
0459                 e->addRelation(cells[x][y].id_rect, cells[x  ][y + (odd_cell ? 1 : 0)].id_corner);
0460                 e->addRelation(cells[x][y].id_rect, cells[x+1][y + (odd_cell ? 0 : 1)].id_corner);
0461             }
0462         }
0463     }
0464 
0465     // CLEANUP
0466     for (int x=0; x<xCount+1; x++) {
0467         delete[] cells[x];
0468     }
0469     delete[] cells;
0470 
0471 }