1 /*
2     SPDX-FileCopyrightText: 2010 Johannes Loehnert <loehnert.kde@gmx.de>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "grid.h"
8 
9 #include <cmath>
10 #include <QPainterPath>
11 #include <QDebug>
12 #include "utilities.h"
13 
14 struct CairoCell {
15     // corner : the border through the top left corner of the cell.
16     GBClassicPlugParams corner;
17     GBClassicPlugParams tl;
18     GBClassicPlugParams tr;
19     GBClassicPlugParams bl;
20     GBClassicPlugParams br;
21 
22     // top and left tile of cell
23     int id_top;
24     int id_left;
25 };
26 
generateGrid(GoldbergEngine * e,int piece_count) const27 void CairoMode::generateGrid(GoldbergEngine *e, int piece_count) const {
28     int next_piece_id=0;
29 
30     int collision_tries = 10 * e->m_plug_size * e->m_plug_size;
31     if (collision_tries < 5) collision_tries = 5;
32     const qreal collision_shrink_factor = 0.95;
33 
34 
35     // calculate piece counts
36     const int width = e->get_image_width(), height = e->get_image_height();
37     int xCount;
38     int yCount;
39     getBestFitExtended(xCount, yCount, 1.0 * width / height, piece_count, 2.0, 1., 1., 0.);
40 
41     qDebug() << "cell count x = " << xCount;
42     qDebug() << "cell count y = " << yCount;
43 
44 
45     const qreal cellWidth = 1.0 * width / xCount, cellHeight = 1.0 * height / yCount;
46 
47     // rationale: knobs should visually cover the same fraction of area as for the rect grid.
48     e->m_length_base = sqrt(cellWidth * cellHeight * 0.5) * e->m_plug_size;
49 
50     // grid is made 1 unit larger in both dimensions, to store the right and bottom border cells.
51     CairoCell** cells = new CairoCell*[xCount+1];
52 
53     qDebug() << "now generating edges";
54 
55     for (int x = 0; x < xCount+1; ++x) {
56         cells[x] = new CairoCell[yCount + 1];
57 
58         for (int y = 0; y < yCount+1; ++y) {
59 
60             bool odd_cell = (x+y) % 2;
61 
62             // generate usual borders first, and cater for the "edge" cases afterwards.
63             cells[x][y].corner = e->initEdge(false);
64             cells[x][y].tr = e->initEdge(false);
65             cells[x][y].tl = e->initEdge(false);
66             cells[x][y].bl = e->initEdge(false);
67             cells[x][y].br = e->initEdge(false);
68 
69             // determine border direction
70             if (odd_cell) {
71                 cells[x][y].tr.flipped ^= true;
72                 cells[x][y].tl.flipped ^= true;
73                 cells[x][y].bl.flipped ^= true;
74                 cells[x][y].br.flipped ^= true;
75             }
76 
77             cells[x][y].tr.flipped ^= e->m_alternate_flip;
78             cells[x][y].br.flipped ^= e->m_alternate_flip;
79             cells[x][y].bl.flipped ^= e->m_alternate_flip;
80             cells[x][y].tl.flipped ^= e->m_alternate_flip;
81 
82             // set vector
83             if (odd_cell) {
84                 cells[x][y].corner.flipped ^= (y%2 == 1);
85                 cells[x][y].corner.unit_x = QLineF((x-0.25) * cellWidth, y * cellHeight, (x+0.25) * cellWidth, y * cellHeight);
86             }
87             else {
88                 cells[x][y].corner.flipped ^= (x%2 == 1);
89                 cells[x][y].corner.unit_x = QLineF(x * cellWidth, (y-0.25) * cellHeight, x * cellWidth, (y + 0.25) * cellHeight);
90             }
91 
92             cells[x][y].tl.unit_x = QLineF((x + (odd_cell?0.25:0.0 )) * cellWidth,
93                                            (y + (odd_cell?0.0 :0.25)) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
94             cells[x][y].tr.unit_x = QLineF((x + (odd_cell?1.0 :0.75)) * cellWidth,
95                                            (y + (odd_cell?0.25:0.0 )) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
96             cells[x][y].bl.unit_x = QLineF((x + (odd_cell?0.0 :0.25)) * cellWidth,
97                                            (y + (odd_cell?0.75:1.0 )) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
98             cells[x][y].br.unit_x = QLineF((x + (odd_cell?0.75:1.0 )) * cellWidth,
99                                            (y + (odd_cell?1.0 :0.75)) * cellHeight, (x+0.5) * cellWidth, (y+0.5) * cellHeight);
100 
101             e->smooth_join(cells[x][y].tl, cells[x][y].br);
102             e->smooth_join(cells[x][y].tr, cells[x][y].bl);
103 
104             // edges
105             if (y==0) {
106                 if (!odd_cell) {
107                     cells[x][y].corner.unit_x = QLineF(x * cellWidth, y * cellHeight, x * cellWidth, (y+ 0.25) * cellHeight);
108                 }
109                 else {
110                     cells[x][y].corner.is_straight = true;
111                 }
112             }
113             if (x==0) {
114                 if (odd_cell) {
115                     cells[x][y].corner.unit_x = QLineF(x * cellWidth, y * cellHeight, (x+0.25) * cellWidth, y * cellHeight);
116                 }
117                 else {
118                     cells[x][y].corner.is_straight = true;
119                 }
120             }
121 
122             if (y==yCount) {
123                 if (!odd_cell) {
124                     cells[x][y].corner.unit_x = QLineF(x * cellWidth, (y-0.25) * cellHeight, x * cellWidth, y * cellHeight);
125                 }
126                 else {
127                     cells[x][y].corner.is_straight = true;
128                 }
129             }
130             if (x==xCount) {
131                 if (odd_cell) {
132                     cells[x][y].corner.unit_x = QLineF((x-0.25) * cellWidth, y * cellHeight, x * cellWidth, y * cellHeight);
133                 }
134                 else {
135                     cells[x][y].corner.is_straight = true;
136                 }
137             }
138 
139             // collision checking
140             bool intersects;
141             QList<GBClassicPlugParams*> offenders;
142 
143             // CORNER
144             intersects = !cells[x][y].corner.is_straight;
145             for (int i=0; i<collision_tries && intersects; i++) {
146                 offenders.clear();
147                 if (i>0 && intersects) {
148                     //qDebug() << "collision: corner edge, x=" << x << ", y=" << y;
149                     cells[x][y].corner.size_correction *= collision_shrink_factor;
150                     e->reRandomizeEdge(cells[x][y].corner);
151                 }
152                 intersects = false;
153                 if (x>0 && y>0) intersects |= e->plugsIntersect(cells[x][y].corner, cells[x-1][y-1].br, &offenders);
154                 intersects |= (x==0) ? e->plugOutOfBounds(cells[x][y].corner) : e->plugsIntersect(cells[x][y].corner, cells[x-1][y].tr, &offenders);
155                 intersects |= (y==0) ? e->plugOutOfBounds(cells[x][y].corner) : e->plugsIntersect(cells[x][y].corner, cells[x][y-1].bl, &offenders);
156                 if (x==xCount || y==yCount) intersects |= e->plugOutOfBounds(cells[x][y].corner);
157             }
158             if (intersects) {
159                 // give up and just make the colliding borders plugless.
160                 e->makePlugless(cells[x][y].corner);
161                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
162             }
163 
164 
165             // TL
166             // don't bother with the "outer" cells, they do not matter.
167             intersects = (x<xCount && y < yCount);
168             for (int i=0; i<collision_tries && intersects; i++) {
169                 offenders.clear();
170                 if (i>0 && intersects) {
171                     //qDebug() << "collision: top left edge, x=" << x << ", y=" << y;
172                     cells[x][y].tl.size_correction *= collision_shrink_factor;
173                     e->reRandomizeEdge(cells[x][y].tl, true);
174                 }
175                 intersects = e->plugsIntersect(cells[x][y].tl, cells[x][y].corner, &offenders);
176                 intersects |= (odd_cell ?
177                                 ((y==0) ? e->plugOutOfBounds(cells[x][y].tl) : e->plugsIntersect(cells[x][y].tl, cells[x][y-1].bl, &offenders)) :
178                                 ((x==0) ? e->plugOutOfBounds(cells[x][y].tl) : e->plugsIntersect(cells[x][y].tl, cells[x-1][y].tr, &offenders))
179                             );
180             }
181             if (intersects) {
182                 // give up and just make the colliding borders plugless.
183                 e->makePlugless(cells[x][y].tl);
184                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
185             }
186 
187             // TR
188             // don't bother with the "outer" cells, they do not matter.
189             intersects = (x<xCount && y < yCount);
190             for (int i=0; i<collision_tries && intersects; i++) {
191                 offenders.clear();
192                 if (i>0 && intersects) {
193                     //qDebug() << "collision: top right edge, x=" << x << ", y=" << y;
194                     cells[x][y].tr.size_correction *= collision_shrink_factor;
195                     e->reRandomizeEdge(cells[x][y].tr, true);
196                 }
197                 intersects = e->plugsIntersect(cells[x][y].tr, cells[x][y].tl, &offenders);
198                 intersects |= (odd_cell ?
199                                 ((x==xCount-1) ? e->plugOutOfBounds(cells[x][y].tr) : false) :
200                                 ((y==0) ? e->plugOutOfBounds(cells[x][y].tr) : e->plugsIntersect(cells[x][y].tr, cells[x][y-1].br, &offenders))
201                             );
202             }
203             if (intersects) {
204                 // give up and just make the colliding borders plugless.
205                 e->makePlugless(cells[x][y].tr);
206                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
207             }
208 
209             // BL
210             // don't bother with the "outer" cells, they do not matter.
211             intersects = (x<xCount && y < yCount);
212             for (int i=0; i<collision_tries && intersects; i++) {
213                 offenders.clear();
214                 if (i>0 && intersects) {
215                     //qDebug() << "collision: bottom left edge, x=" << x << ", y=" << y;
216                     cells[x][y].bl.size_correction *= collision_shrink_factor;
217                     e->reRandomizeEdge(cells[x][y].bl, true);
218                 }
219                 intersects = e->plugsIntersect(cells[x][y].bl, cells[x][y].tl, &offenders);
220                 intersects |= (odd_cell ?
221                                 ((x==0) ? e->plugOutOfBounds(cells[x][y].bl) : e->plugsIntersect(cells[x][y].tr, cells[x-1][y].bl, &offenders)) :
222                                 ((y==yCount-1) ? e->plugOutOfBounds(cells[x][y].bl) : false)
223                             );
224             }
225             if (intersects) {
226                 // give up and just make the colliding borders plugless.
227                 e->makePlugless(cells[x][y].bl);
228                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
229             }
230 
231             // BR
232             // don't bother with the "outer" cells, they do not matter.
233             intersects = (x<xCount && y < yCount);
234             for (int i=0; i<collision_tries && intersects; i++) {
235                 offenders.clear();
236                 if (i>0 && intersects) {
237                     //qDebug() << "collision: bottom right edge, x=" << x << ", y=" << y;
238                     cells[x][y].br.size_correction *= collision_shrink_factor;
239                     e->reRandomizeEdge(cells[x][y].br, true);
240                 }
241                 intersects = e->plugsIntersect(cells[x][y].br, cells[x][y].tr, &offenders);
242                 intersects |= e->plugsIntersect(cells[x][y].br, cells[x][y].bl, &offenders);
243                 intersects |= (odd_cell ?
244                                 ((y==yCount-1) ? e->plugOutOfBounds(cells[x][y].br) : false) :
245                                 ((x==xCount-1) ? e->plugOutOfBounds(cells[x][y].br) : false)
246                             );
247             }
248             if (intersects) {
249                 // give up and just make the colliding borders plugless.
250                 e->makePlugless(cells[x][y].br);
251                 for (int i=0; i<offenders.size(); i++) e->makePlugless(*(offenders.at(i)));
252             }
253 
254         } // end collision checking
255     }
256 
257     qDebug() << "now creating pieces";
258 
259     for (int x = 0; x < xCount+1; ++x) {
260         // checkerboard pattern as above
261         for (int y = 0; y < yCount+1; ++y) {
262             //create the mask path
263             QPainterPath path;
264 
265             bool odd_cell = (x+y) % 2;
266 
267             // we start after the "corner" edge.
268             path.moveTo(cells[x][y].corner.unit_x.p2());
269 
270             // TOP PIECE
271             if (x < xCount) {
272                 if (!odd_cell) e->addPlugToPath(path, true, cells[x][y].corner);
273                 if (y==0) {
274                     // half piece
275                     path.lineTo(cells[x+1][y].corner.unit_x.p1());
276                 }
277                 else {
278                     e->addPlugToPath(path, false, cells[x][y-1].bl);
279                     e->addPlugToPath(path, true, cells[x][y-1].br);
280                 }
281                 if (odd_cell) e->addPlugToPath(path, false, cells[x+1][y].corner);
282 
283                 if (y==yCount) {
284                     // half piece
285                     path.lineTo(cells[x][y].corner.unit_x.p2());
286                 }
287                 else {
288                     e->addPlugToPath(path, false, cells[x][y].tr);
289                     e->addPlugToPath(path, true, cells[x][y].tl);
290                 }
291 
292                 cells[x][y].id_top = next_piece_id++;
293                 e->makePieceFromPath(cells[x][y].id_top, path);
294             }
295 
296             // LEFT PIECE
297             if (y < yCount) {
298                 path = QPainterPath();
299                 path.moveTo(cells[x][y].corner.unit_x.p2());
300                 if (x==xCount) {
301                     // half piece
302                     path.lineTo(odd_cell ? cells[x-1][y].br.unit_x.p1() : cells[x][y+1].corner.unit_x.p2());
303                 }
304                 else {
305                     e->addPlugToPath(path, false, cells[x][y].tl);
306                     e->addPlugToPath(path, true, cells[x][y].bl);
307                 }
308                 if (!odd_cell) e->addPlugToPath(path, true, cells[x][y+1].corner);
309 
310                 if (x==0) {
311                     // half piece
312                     path.lineTo(odd_cell ? cells[x][y].corner.unit_x.p1() : cells[x][y].tl.unit_x.p1());
313                 }
314                 else {
315                     e->addPlugToPath(path, false, cells[x-1][y].br);
316                     e->addPlugToPath(path, true, cells[x-1][y].tr);
317                 }
318 
319                 if (odd_cell) e->addPlugToPath(path, false, cells[x][y].corner);
320 
321                 cells[x][y].id_left = next_piece_id++;
322                 e->makePieceFromPath(cells[x][y].id_left, path);
323             }
324         }
325     }
326 
327     qDebug() << "now adding relations";
328 
329     //create relations
330     for (int x = 0; x < xCount+1; ++x) {
331         for (int y = 0; y < yCount+1; ++y) {
332             bool odd_cell = (x+y) % 2;
333             // corner
334             if (odd_cell) {
335                 if (y>0 && y < yCount) e->addRelation(cells[x][y].id_left, cells[x][y-1].id_left);
336             }
337             else {
338                 if (x>0 && x < xCount) e->addRelation(cells[x][y].id_top, cells[x-1][y].id_top);
339             }
340             // inner-cell borders
341             if (y < yCount && x < xCount) {
342                 // tl
343                 e->addRelation(cells[x][y].id_top, cells[x][y].id_left);
344                 // tr
345                 e->addRelation(cells[x][y].id_top, cells[x+1][y].id_left);
346                 // bl
347                 e->addRelation(cells[x][y].id_left, cells[x][y+1].id_top);
348                 //br
349                 e->addRelation(cells[x+1][y].id_left, cells[x][y+1].id_top);
350             }
351         }
352     }
353 
354     //cleanup
355     for (int x = 0; x < xCount+1; ++x) {
356         delete[] cells[x];
357     }
358     delete[] cells;
359 }
360