summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/src/penrose-legacy.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/src/penrose-legacy.c')
-rw-r--r--apps/plugins/puzzles/src/penrose-legacy.c506
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
106struct vector { int a, b, c, d; };
107
108static 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
119static 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. */
137double 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
143double 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
150static 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
159static 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
169static 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
183static 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
194static 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
204static 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
216static 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
231static 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
247static int penrose_p2_small(penrose_legacy_state *state, int depth, int flip,
248 vector v_orig, vector v_edge);
249
250static 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
292static 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
332static int penrose_p3_small(penrose_legacy_state *state, int depth, int flip,
333 vector v_orig, vector v_edge);
334
335static 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
380static 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
424double 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
441int 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
478void 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}