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