diff options
Diffstat (limited to 'apps/plugins/puzzles/src/penrose-legacy.c')
-rw-r--r-- | apps/plugins/puzzles/src/penrose-legacy.c | 506 |
1 files changed, 506 insertions, 0 deletions
diff --git a/apps/plugins/puzzles/src/penrose-legacy.c b/apps/plugins/puzzles/src/penrose-legacy.c new file mode 100644 index 0000000000..709d68d7f5 --- /dev/null +++ b/apps/plugins/puzzles/src/penrose-legacy.c | |||
@@ -0,0 +1,506 @@ | |||
1 | /* penrose-legacy.c: legacy Penrose tile generator. | ||
2 | * | ||
3 | * Works by choosing a small patch from a recursively expanded large | ||
4 | * region of tiling, using one of the two algorithms described at | ||
5 | * | ||
6 | * https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/aperiodic-tilings/ | ||
7 | * | ||
8 | * This method of generating Penrose tiling fragments is superseded by | ||
9 | * the completely different algorithm in penrose.c, using the other | ||
10 | * algorithm in that article (the 'combinatorial coordinates' one). We | ||
11 | * keep the legacy algorithm around only for interpreting Loopy game | ||
12 | * IDs generated by older versions of the code. | ||
13 | */ | ||
14 | |||
15 | #include <assert.h> | ||
16 | #include <string.h> | ||
17 | #ifdef NO_TGMATH_H | ||
18 | # include <math.h> | ||
19 | #else | ||
20 | # include <tgmath.h> | ||
21 | #endif | ||
22 | #include <stdio.h> | ||
23 | |||
24 | #include "puzzles.h" /* for malloc routines, and PI */ | ||
25 | #include "penrose-legacy.h" | ||
26 | |||
27 | /* ------------------------------------------------------- | ||
28 | * 36-degree basis vector arithmetic routines. | ||
29 | */ | ||
30 | |||
31 | /* Imagine drawing a | ||
32 | * ten-point 'clock face' like this: | ||
33 | * | ||
34 | * -E | ||
35 | * -D | A | ||
36 | * \ | / | ||
37 | * -C. \ | / ,B | ||
38 | * `-._\|/_,-' | ||
39 | * ,-' /|\ `-. | ||
40 | * -B' / | \ `C | ||
41 | * / | \ | ||
42 | * -A | D | ||
43 | * E | ||
44 | * | ||
45 | * In case the ASCII art isn't clear, those are supposed to be ten | ||
46 | * vectors of length 1, all sticking out from the origin at equal | ||
47 | * angular spacing (hence 36 degrees). Our basis vectors are A,B,C,D (I | ||
48 | * choose them to be symmetric about the x-axis so that the final | ||
49 | * translation into 2d coordinates will also be symmetric, which I | ||
50 | * think will avoid minor rounding uglinesses), so our vector | ||
51 | * representation sets | ||
52 | * | ||
53 | * A = (1,0,0,0) | ||
54 | * B = (0,1,0,0) | ||
55 | * C = (0,0,1,0) | ||
56 | * D = (0,0,0,1) | ||
57 | * | ||
58 | * The fifth vector E looks at first glance as if it needs to be | ||
59 | * another basis vector, but in fact it doesn't, because it can be | ||
60 | * represented in terms of the other four. Imagine starting from the | ||
61 | * origin and following the path -A, +B, -C, +D: you'll find you've | ||
62 | * traced four sides of a pentagram, and ended up one E-vector away | ||
63 | * from the origin. So we have | ||
64 | * | ||
65 | * E = (-1,1,-1,1) | ||
66 | * | ||
67 | * This tells us that we can rotate any vector in this system by 36 | ||
68 | * degrees: if we start with a*A + b*B + c*C + d*D, we want to end up | ||
69 | * with a*B + b*C + c*D + d*E, and we substitute our identity for E to | ||
70 | * turn that into a*B + b*C + c*D + d*(-A+B-C+D). In other words, | ||
71 | * | ||
72 | * rotate_one_notch_clockwise(a,b,c,d) = (-d, d+a, -d+b, d+c) | ||
73 | * | ||
74 | * and you can verify for yourself that applying that operation | ||
75 | * repeatedly starting with (1,0,0,0) cycles round ten vectors and | ||
76 | * comes back to where it started. | ||
77 | * | ||
78 | * The other operation that may be required is to construct vectors | ||
79 | * with lengths that are multiples of phi. That can be done by | ||
80 | * observing that the vector C-B is parallel to E and has length 1/phi, | ||
81 | * and the vector D-A is parallel to E and has length phi. So this | ||
82 | * tells us that given any vector, we can construct one which points in | ||
83 | * the same direction and is 1/phi or phi times its length, like this: | ||
84 | * | ||
85 | * divide_by_phi(vector) = rotate(vector, 2) - rotate(vector, 3) | ||
86 | * multiply_by_phi(vector) = rotate(vector, 1) - rotate(vector, 4) | ||
87 | * | ||
88 | * where rotate(vector, n) means applying the above | ||
89 | * rotate_one_notch_clockwise primitive n times. Expanding out the | ||
90 | * applications of rotate gives the following direct representation in | ||
91 | * terms of the vector coordinates: | ||
92 | * | ||
93 | * divide_by_phi(a,b,c,d) = (b-d, c+d-b, a+b-c, c-a) | ||
94 | * multiply_by_phi(a,b,c,d) = (a+b-d, c+d, a+b, c+d-a) | ||
95 | * | ||
96 | * and you can verify for yourself that those two operations are | ||
97 | * inverses of each other (as you'd hope!). | ||
98 | * | ||
99 | * Having done all of this, testing for equality between two vectors is | ||
100 | * a trivial matter of comparing the four integer coordinates. (Which | ||
101 | * it _wouldn't_ have been if we'd kept E as a fifth basis vector, | ||
102 | * because then (-1,1,-1,1,0) and (0,0,0,0,1) would have had to be | ||
103 | * considered identical. So leaving E out is vital.) | ||
104 | */ | ||
105 | |||
106 | struct vector { int a, b, c, d; }; | ||
107 | |||
108 | static vector v_origin(void) | ||
109 | { | ||
110 | vector v; | ||
111 | v.a = v.b = v.c = v.d = 0; | ||
112 | return v; | ||
113 | } | ||
114 | |||
115 | /* We start with a unit vector of B: this means we can easily | ||
116 | * draw an isoceles triangle centred on the X axis. */ | ||
117 | #ifdef TEST_VECTORS | ||
118 | |||
119 | static vector v_unit(void) | ||
120 | { | ||
121 | vector v; | ||
122 | |||
123 | v.b = 1; | ||
124 | v.a = v.c = v.d = 0; | ||
125 | return v; | ||
126 | } | ||
127 | |||
128 | #endif | ||
129 | |||
130 | #define COS54 0.5877852 | ||
131 | #define SIN54 0.8090169 | ||
132 | #define COS18 0.9510565 | ||
133 | #define SIN18 0.3090169 | ||
134 | |||
135 | /* These two are a bit rough-and-ready for now. Note that B/C are | ||
136 | * 18 degrees from the x-axis, and A/D are 54 degrees. */ | ||
137 | double penrose_legacy_vx(vector *vs, int i) | ||
138 | { | ||
139 | return (vs[i].a + vs[i].d) * COS54 + | ||
140 | (vs[i].b + vs[i].c) * COS18; | ||
141 | } | ||
142 | |||
143 | double penrose_legacy_vy(vector *vs, int i) | ||
144 | { | ||
145 | return (vs[i].a - vs[i].d) * SIN54 + | ||
146 | (vs[i].b - vs[i].c) * SIN18; | ||
147 | |||
148 | } | ||
149 | |||
150 | static vector v_trans(vector v, vector trans) | ||
151 | { | ||
152 | v.a += trans.a; | ||
153 | v.b += trans.b; | ||
154 | v.c += trans.c; | ||
155 | v.d += trans.d; | ||
156 | return v; | ||
157 | } | ||
158 | |||
159 | static vector v_rotate_36(vector v) | ||
160 | { | ||
161 | vector vv; | ||
162 | vv.a = -v.d; | ||
163 | vv.b = v.d + v.a; | ||
164 | vv.c = -v.d + v.b; | ||
165 | vv.d = v.d + v.c; | ||
166 | return vv; | ||
167 | } | ||
168 | |||
169 | static vector v_rotate(vector v, int ang) | ||
170 | { | ||
171 | int i; | ||
172 | |||
173 | assert((ang % 36) == 0); | ||
174 | while (ang < 0) ang += 360; | ||
175 | ang = 360-ang; | ||
176 | for (i = 0; i < (ang/36); i++) | ||
177 | v = v_rotate_36(v); | ||
178 | return v; | ||
179 | } | ||
180 | |||
181 | #ifdef TEST_VECTORS | ||
182 | |||
183 | static vector v_scale(vector v, int sc) | ||
184 | { | ||
185 | v.a *= sc; | ||
186 | v.b *= sc; | ||
187 | v.c *= sc; | ||
188 | v.d *= sc; | ||
189 | return v; | ||
190 | } | ||
191 | |||
192 | #endif | ||
193 | |||
194 | static vector v_growphi(vector v) | ||
195 | { | ||
196 | vector vv; | ||
197 | vv.a = v.a + v.b - v.d; | ||
198 | vv.b = v.c + v.d; | ||
199 | vv.c = v.a + v.b; | ||
200 | vv.d = v.c + v.d - v.a; | ||
201 | return vv; | ||
202 | } | ||
203 | |||
204 | static vector v_shrinkphi(vector v) | ||
205 | { | ||
206 | vector vv; | ||
207 | vv.a = v.b - v.d; | ||
208 | vv.b = v.c + v.d - v.b; | ||
209 | vv.c = v.a + v.b - v.c; | ||
210 | vv.d = v.c - v.a; | ||
211 | return vv; | ||
212 | } | ||
213 | |||
214 | #ifdef TEST_VECTORS | ||
215 | |||
216 | static const char *v_debug(vector v) | ||
217 | { | ||
218 | static char buf[255]; | ||
219 | sprintf(buf, | ||
220 | "(%d,%d,%d,%d)[%2.2f,%2.2f]", | ||
221 | v.a, v.b, v.c, v.d, v_x(&v,0), v_y(&v,0)); | ||
222 | return buf; | ||
223 | } | ||
224 | |||
225 | #endif | ||
226 | |||
227 | /* ------------------------------------------------------- | ||
228 | * Tiling routines. | ||
229 | */ | ||
230 | |||
231 | static vector xform_coord(vector vo, int shrink, vector vtrans, int ang) | ||
232 | { | ||
233 | if (shrink < 0) | ||
234 | vo = v_shrinkphi(vo); | ||
235 | else if (shrink > 0) | ||
236 | vo = v_growphi(vo); | ||
237 | |||
238 | vo = v_rotate(vo, ang); | ||
239 | vo = v_trans(vo, vtrans); | ||
240 | |||
241 | return vo; | ||
242 | } | ||
243 | |||
244 | |||
245 | #define XFORM(n,o,s,a) vs[(n)] = xform_coord(v_edge, (s), vs[(o)], (a)) | ||
246 | |||
247 | static int penrose_p2_small(penrose_legacy_state *state, int depth, int flip, | ||
248 | vector v_orig, vector v_edge); | ||
249 | |||
250 | static int penrose_p2_large(penrose_legacy_state *state, int depth, int flip, | ||
251 | vector v_orig, vector v_edge) | ||
252 | { | ||
253 | vector vv_orig, vv_edge; | ||
254 | |||
255 | #ifdef DEBUG_PENROSE | ||
256 | { | ||
257 | vector vs[3]; | ||
258 | vs[0] = v_orig; | ||
259 | XFORM(1, 0, 0, 0); | ||
260 | XFORM(2, 0, 0, -36*flip); | ||
261 | |||
262 | state->new_tile(state, vs, 3, depth); | ||
263 | } | ||
264 | #endif | ||
265 | |||
266 | if (flip > 0) { | ||
267 | vector vs[4]; | ||
268 | |||
269 | vs[0] = v_orig; | ||
270 | XFORM(1, 0, 0, -36); | ||
271 | XFORM(2, 0, 0, 0); | ||
272 | XFORM(3, 0, 0, 36); | ||
273 | |||
274 | state->new_tile(state, vs, 4, depth); | ||
275 | } | ||
276 | if (depth >= state->max_depth) return 0; | ||
277 | |||
278 | vv_orig = v_trans(v_orig, v_rotate(v_edge, -36*flip)); | ||
279 | vv_edge = v_rotate(v_edge, 108*flip); | ||
280 | |||
281 | penrose_p2_small(state, depth+1, flip, | ||
282 | v_orig, v_shrinkphi(v_edge)); | ||
283 | |||
284 | penrose_p2_large(state, depth+1, flip, | ||
285 | vv_orig, v_shrinkphi(vv_edge)); | ||
286 | penrose_p2_large(state, depth+1, -flip, | ||
287 | vv_orig, v_shrinkphi(vv_edge)); | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | static int penrose_p2_small(penrose_legacy_state *state, int depth, int flip, | ||
293 | vector v_orig, vector v_edge) | ||
294 | { | ||
295 | vector vv_orig; | ||
296 | |||
297 | #ifdef DEBUG_PENROSE | ||
298 | { | ||
299 | vector vs[3]; | ||
300 | vs[0] = v_orig; | ||
301 | XFORM(1, 0, 0, 0); | ||
302 | XFORM(2, 0, -1, -36*flip); | ||
303 | |||
304 | state->new_tile(state, vs, 3, depth); | ||
305 | } | ||
306 | #endif | ||
307 | |||
308 | if (flip > 0) { | ||
309 | vector vs[4]; | ||
310 | |||
311 | vs[0] = v_orig; | ||
312 | XFORM(1, 0, 0, -72); | ||
313 | XFORM(2, 0, -1, -36); | ||
314 | XFORM(3, 0, 0, 0); | ||
315 | |||
316 | state->new_tile(state, vs, 4, depth); | ||
317 | } | ||
318 | |||
319 | if (depth >= state->max_depth) return 0; | ||
320 | |||
321 | vv_orig = v_trans(v_orig, v_edge); | ||
322 | |||
323 | penrose_p2_large(state, depth+1, -flip, | ||
324 | v_orig, v_shrinkphi(v_rotate(v_edge, -36*flip))); | ||
325 | |||
326 | penrose_p2_small(state, depth+1, flip, | ||
327 | vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip))); | ||
328 | |||
329 | return 0; | ||
330 | } | ||
331 | |||
332 | static int penrose_p3_small(penrose_legacy_state *state, int depth, int flip, | ||
333 | vector v_orig, vector v_edge); | ||
334 | |||
335 | static int penrose_p3_large(penrose_legacy_state *state, int depth, int flip, | ||
336 | vector v_orig, vector v_edge) | ||
337 | { | ||
338 | vector vv_orig; | ||
339 | |||
340 | #ifdef DEBUG_PENROSE | ||
341 | { | ||
342 | vector vs[3]; | ||
343 | vs[0] = v_orig; | ||
344 | XFORM(1, 0, 1, 0); | ||
345 | XFORM(2, 0, 0, -36*flip); | ||
346 | |||
347 | state->new_tile(state, vs, 3, depth); | ||
348 | } | ||
349 | #endif | ||
350 | |||
351 | if (flip > 0) { | ||
352 | vector vs[4]; | ||
353 | |||
354 | vs[0] = v_orig; | ||
355 | XFORM(1, 0, 0, -36); | ||
356 | XFORM(2, 0, 1, 0); | ||
357 | XFORM(3, 0, 0, 36); | ||
358 | |||
359 | state->new_tile(state, vs, 4, depth); | ||
360 | } | ||
361 | if (depth >= state->max_depth) return 0; | ||
362 | |||
363 | vv_orig = v_trans(v_orig, v_edge); | ||
364 | |||
365 | penrose_p3_large(state, depth+1, -flip, | ||
366 | vv_orig, v_shrinkphi(v_rotate(v_edge, 180))); | ||
367 | |||
368 | penrose_p3_small(state, depth+1, flip, | ||
369 | vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip))); | ||
370 | |||
371 | vv_orig = v_trans(v_orig, v_growphi(v_edge)); | ||
372 | |||
373 | penrose_p3_large(state, depth+1, flip, | ||
374 | vv_orig, v_shrinkphi(v_rotate(v_edge, -144*flip))); | ||
375 | |||
376 | |||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | static int penrose_p3_small(penrose_legacy_state *state, int depth, int flip, | ||
381 | vector v_orig, vector v_edge) | ||
382 | { | ||
383 | vector vv_orig; | ||
384 | |||
385 | #ifdef DEBUG_PENROSE | ||
386 | { | ||
387 | vector vs[3]; | ||
388 | vs[0] = v_orig; | ||
389 | XFORM(1, 0, 0, 0); | ||
390 | XFORM(2, 0, 0, -36*flip); | ||
391 | |||
392 | state->new_tile(state, vs, 3, depth); | ||
393 | } | ||
394 | #endif | ||
395 | |||
396 | if (flip > 0) { | ||
397 | vector vs[4]; | ||
398 | |||
399 | vs[0] = v_orig; | ||
400 | XFORM(1, 0, 0, -36); | ||
401 | XFORM(3, 0, 0, 0); | ||
402 | XFORM(2, 3, 0, -36); | ||
403 | |||
404 | state->new_tile(state, vs, 4, depth); | ||
405 | } | ||
406 | if (depth >= state->max_depth) return 0; | ||
407 | |||
408 | /* NB these two are identical to the first two of p3_large. */ | ||
409 | vv_orig = v_trans(v_orig, v_edge); | ||
410 | |||
411 | penrose_p3_large(state, depth+1, -flip, | ||
412 | vv_orig, v_shrinkphi(v_rotate(v_edge, 180))); | ||
413 | |||
414 | penrose_p3_small(state, depth+1, flip, | ||
415 | vv_orig, v_shrinkphi(v_rotate(v_edge, -108*flip))); | ||
416 | |||
417 | return 0; | ||
418 | } | ||
419 | |||
420 | /* ------------------------------------------------------- | ||
421 | * Utility routines. | ||
422 | */ | ||
423 | |||
424 | double penrose_legacy_side_length(double start_size, int depth) | ||
425 | { | ||
426 | return start_size / pow(PHI, depth); | ||
427 | } | ||
428 | |||
429 | /* | ||
430 | * It turns out that an acute isosceles triangle with sides in ratio 1:phi:phi | ||
431 | * has an incentre which is conveniently 2*phi^-2 of the way from the apex to | ||
432 | * the base. Why's that convenient? Because: if we situate the incentre of the | ||
433 | * triangle at the origin, then we can place the apex at phi^-2 * (B+C), and | ||
434 | * the other two vertices at apex-B and apex-C respectively. So that's an acute | ||
435 | * triangle with its long sides of unit length, covering a circle about the | ||
436 | * origin of radius 1-(2*phi^-2), which is conveniently enough phi^-3. | ||
437 | * | ||
438 | * (later mail: this is an overestimate by about 5%) | ||
439 | */ | ||
440 | |||
441 | int penrose_legacy(penrose_legacy_state *state, int which, int angle) | ||
442 | { | ||
443 | vector vo = v_origin(); | ||
444 | vector vb = v_origin(); | ||
445 | |||
446 | vo.b = vo.c = -state->start_size; | ||
447 | vo = v_shrinkphi(v_shrinkphi(vo)); | ||
448 | |||
449 | vb.b = state->start_size; | ||
450 | |||
451 | vo = v_rotate(vo, angle); | ||
452 | vb = v_rotate(vb, angle); | ||
453 | |||
454 | if (which == PENROSE_P2) | ||
455 | return penrose_p2_large(state, 0, 1, vo, vb); | ||
456 | else | ||
457 | return penrose_p3_small(state, 0, 1, vo, vb); | ||
458 | } | ||
459 | |||
460 | /* | ||
461 | * We're asked for a MxN grid, which just means a tiling fitting into roughly | ||
462 | * an MxN space in some kind of reasonable unit - say, the side length of the | ||
463 | * two-arrow edges of the tiles. By some reasoning in a previous email, that | ||
464 | * means we want to pick some subarea of a circle of radius 3.11*sqrt(M^2+N^2). | ||
465 | * To cover that circle, we need to subdivide a triangle large enough that it | ||
466 | * contains a circle of that radius. | ||
467 | * | ||
468 | * Hence: start with those three vectors marking triangle vertices, scale them | ||
469 | * all up by phi repeatedly until the radius of the inscribed circle gets | ||
470 | * bigger than the target, and then recurse into that triangle with the same | ||
471 | * recursion depth as the number of times you scaled up. That will give you | ||
472 | * tiles of unit side length, covering a circle big enough that if you randomly | ||
473 | * choose an orientation and coordinates within the circle, you'll be able to | ||
474 | * get any valid piece of Penrose tiling of size MxN. | ||
475 | */ | ||
476 | #define INCIRCLE_RADIUS 0.22426 /* phi^-3 less 5%: see above */ | ||
477 | |||
478 | void penrose_legacy_calculate_size( | ||
479 | int which, int tilesize, int w, int h, | ||
480 | double *required_radius, int *start_size, int *depth) | ||
481 | { | ||
482 | double rradius, size; | ||
483 | int n = 0; | ||
484 | |||
485 | /* | ||
486 | * Fudge factor to scale P2 and P3 tilings differently. This | ||
487 | * doesn't seem to have much relevance to questions like the | ||
488 | * average number of tiles per unit area; it's just aesthetic. | ||
489 | */ | ||
490 | if (which == PENROSE_P2) | ||
491 | tilesize = tilesize * 3 / 2; | ||
492 | else | ||
493 | tilesize = tilesize * 5 / 4; | ||
494 | |||
495 | rradius = tilesize * 3.11 * sqrt((double)(w*w + h*h)); | ||
496 | size = tilesize; | ||
497 | |||
498 | while ((size * INCIRCLE_RADIUS) < rradius) { | ||
499 | n++; | ||
500 | size = size * PHI; | ||
501 | } | ||
502 | |||
503 | *start_size = (int)size; | ||
504 | *depth = n; | ||
505 | *required_radius = rradius; | ||
506 | } | ||