mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
Files

The new generator works on the same 'combinatorial coordinates' system as the more recently written Hats and Spectre generators. When I came up with that technique for the Hats tiling, I was already tempted to rewrite the Penrose generator on the same principle, because it has a lot of advantages over the previous technique of picking a randomly selected patch out of the subdivision of a huge starting tile. It generates the exact limiting distribution of possible tiling patches rather than an approximation based on how big a tile you subdivided; it doesn't use any overly large integers or overly precise floating point to suffer overflow or significance loss, because it never has to even _think_ about the coordinates of any point not in the actual output area. But I resisted the temptation to throw out our existing Penrose generator and move to the shiny new algorithm, for just one reason: backwards compatibility. There's no sensible way to translate existing Loopy game IDs into the new notation, so they would stop working, unless we kept the old generator around as well to interpret the previous system. And although _random seeds_ aren't guaranteed to generate the same result from one build of Puzzles to the next, I do try to keep existing descriptive game IDs working. So if we had to keep the old generator around anyway, it didn't seem worth writing a new one... ... at least, until we discovered that the old generator has a latent bug. The function that decides whether each tile is within the target area, and hence whether to make it part of the output grid, is based on floating-point calculation of the tile's vertices. So a change in FP rounding behaviour between two platforms could cause them to interpret the same grid description differently, invalidating a Loopy game on that grid. This is _unlikely_, as long as everyone uses IEEE 754 double, but the C standard doesn't actually guarantee that. We found this out when I started investigating a slight distortion in large instances of Penrose Loopy. For example, the Loopy random seed "40x40t11#12345", as of just before this commit, generates a game description beginning with the Penrose grid string "G-4944,5110,108", in which you can see a star shape of five darts a few tiles down the left edge, where two of the radii of the star don't properly line up with edges in the surrounding shell of kites that they should be collinear with. This turns out to be due to FP error: not because _double precision_ ran out, but because the definitions of COS54, SIN54, COS18 and SIN18 in penrose.c were specified to only 7 digits of precision. And when you expand a patch of tiling that large, you end up with integer combinations of those numbers with coefficients about 7 digits long, mostly cancelling out to leave a much smaller output value, and the inaccuracies in those constants start to be noticeable. But having noticed that, it then became clear that these badly computed values were also critical to _correctness_ of the grid. So they can't be fixed without invalidating old game IDs. _And_ then we realised that this also means existing platforms might disagree on a game ID's validity. So if we have to break compatibility anyway, we should go all the way, and instead of fixing the old system, introduce the shiny new system that gets all of this right. Therefore, the new penrose.c uses the more reliable combinatorial-coordinates system which doesn't deal in integers that large in the first place. Also, there's no longer any floating point at all in the calculation of tile vertex coordinates: the combinations of 1 and sqrt(5) are computed exactly by the n_times_root_k function. So now a large Penrose grid should never have obvious failures of alignment like that. The old system is kept for backwards compatibility. It's not fully reliable, because that bug hasn't been fixed - but any Penrose Loopy game ID that was working before on _some_ platform should still work on that one. However, new Penrose Loopy game IDs are based on combinatorial coordinates, computed in exact arithmetic, and should be properly stable. The new code looks suspiciously like the Spectre code (though considerably simpler - the Penrose coordinate maps are easy enough that I just hand-typed them rather than having an elaborate auxiliary data-generation tool). That's because they were similar enough in concept to make it possible to clone and hack. But there are enough divergences that it didn't seem like a good idea to try to merge them again afterwards - in particular, the fact that output Penrose tiles are generated by merging two triangular metatiles while Spectres are subdivisions of their metatiles.
895 lines
26 KiB
C
895 lines
26 KiB
C
/*
|
|
* Generate Penrose tilings via combinatorial coordinates.
|
|
*
|
|
* For general explanation of the algorithm:
|
|
* https://www.chiark.greenend.org.uk/~sgtatham/quasiblog/aperiodic-tilings/
|
|
*
|
|
* I use exactly the same indexing system here that's described in the
|
|
* article. For the P2 tiling, acute isosceles triangles (half-kites)
|
|
* are assigned letters A,B, and obtuse ones (half-darts) U,V; for P3,
|
|
* acute triangles (half of a thin rhomb) are C,D and obtuse ones
|
|
* (half a thick rhomb) are X,Y. Edges of all triangles are indexed
|
|
* anticlockwise around the triangle, with 0 being the base and 1,2
|
|
* being the two equal legs.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "puzzles.h"
|
|
#include "penrose.h"
|
|
#include "penrose-internal.h"
|
|
#include "tree234.h"
|
|
|
|
bool penrose_valid_letter(char c, int which)
|
|
{
|
|
switch (c) {
|
|
case 'A': case 'B': case 'U': case 'V':
|
|
return which == PENROSE_P2;
|
|
case 'C': case 'D': case 'X': case 'Y':
|
|
return which == PENROSE_P3;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Result of attempting a transition within the coordinate system.
|
|
* INTERNAL means we've moved to a different child of the same parent,
|
|
* so the 'internal' substructure gives the type of the new triangle
|
|
* and which edge of it we came in through; EXTERNAL means we've moved
|
|
* out of the parent entirely, and the 'external' substructure tells
|
|
* us which edge of the parent triangle we left by, and if it's
|
|
* divided in two, which end of that edge (-1 for the left end or +1
|
|
* for the right end). If the parent edge is undivided, end == 0.
|
|
*
|
|
* The type FAIL _shouldn't_ ever come up! It occurs if you try to
|
|
* compute an incoming transition with an illegal value of 'end' (i.e.
|
|
* having the wrong idea of whether the edge is divided), or if you
|
|
* refer to a child triangle type that doesn't exist in that parent.
|
|
* If it ever happens in the production code then an assertion will
|
|
* fail. But it might be useful to other users of the same code.
|
|
*/
|
|
typedef struct TransitionResult {
|
|
enum { INTERNAL, EXTERNAL, FAIL } type;
|
|
union {
|
|
struct {
|
|
char new_child;
|
|
unsigned char new_edge;
|
|
} internal;
|
|
struct {
|
|
unsigned char parent_edge;
|
|
signed char end;
|
|
} external;
|
|
} u;
|
|
} TransitionResult;
|
|
|
|
/* Construction function to make an INTERNAL-type TransitionResult */
|
|
static inline TransitionResult internal(char new_child, unsigned new_edge)
|
|
{
|
|
TransitionResult tr;
|
|
tr.type = INTERNAL;
|
|
tr.u.internal.new_child = new_child;
|
|
tr.u.internal.new_edge = new_edge;
|
|
return tr;
|
|
}
|
|
|
|
/* Construction function to make an EXTERNAL-type TransitionResult */
|
|
static inline TransitionResult external(unsigned parent_edge, int end)
|
|
{
|
|
TransitionResult tr;
|
|
tr.type = EXTERNAL;
|
|
tr.u.external.parent_edge = parent_edge;
|
|
tr.u.external.end = end;
|
|
return tr;
|
|
}
|
|
|
|
/* Construction function to make a FAIL-type TransitionResult */
|
|
static inline TransitionResult fail(void)
|
|
{
|
|
TransitionResult tr;
|
|
tr.type = FAIL;
|
|
return tr;
|
|
}
|
|
|
|
/*
|
|
* Compute a transition out of a triangle. Can return either INTERNAL
|
|
* or EXTERNAL types (or FAIL if it gets invalid data).
|
|
*/
|
|
static TransitionResult transition(char parent, char child, unsigned edge)
|
|
{
|
|
switch (parent) {
|
|
case 'A':
|
|
switch (child) {
|
|
case 'A':
|
|
switch (edge) {
|
|
case 0: return external(2, -1);
|
|
case 1: return external(0, 0);
|
|
case 2: return internal('B', 1);
|
|
}
|
|
case 'B':
|
|
switch (edge) {
|
|
case 0: return internal('U', 1);
|
|
case 1: return internal('A', 2);
|
|
case 2: return external(1, +1);
|
|
}
|
|
case 'U':
|
|
switch (edge) {
|
|
case 0: return external(2, +1);
|
|
case 1: return internal('B', 0);
|
|
case 2: return external(1, -1);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'B':
|
|
switch (child) {
|
|
case 'A':
|
|
switch (edge) {
|
|
case 0: return internal('V', 2);
|
|
case 1: return external(2, -1);
|
|
case 2: return internal('B', 1);
|
|
}
|
|
case 'B':
|
|
switch (edge) {
|
|
case 0: return external(1, +1);
|
|
case 1: return internal('A', 2);
|
|
case 2: return external(0, 0);
|
|
}
|
|
case 'V':
|
|
switch (edge) {
|
|
case 0: return external(1, -1);
|
|
case 1: return external(2, +1);
|
|
case 2: return internal('A', 0);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'U':
|
|
switch (child) {
|
|
case 'B':
|
|
switch (edge) {
|
|
case 0: return internal('U', 1);
|
|
case 1: return external(2, 0);
|
|
case 2: return external(0, +1);
|
|
}
|
|
case 'U':
|
|
switch (edge) {
|
|
case 0: return external(1, 0);
|
|
case 1: return internal('B', 0);
|
|
case 2: return external(0, -1);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'V':
|
|
switch (child) {
|
|
case 'A':
|
|
switch (edge) {
|
|
case 0: return internal('V', 2);
|
|
case 1: return external(0, -1);
|
|
case 2: return external(1, 0);
|
|
}
|
|
case 'V':
|
|
switch (edge) {
|
|
case 0: return external(2, 0);
|
|
case 1: return external(0, +1);
|
|
case 2: return internal('A', 0);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'C':
|
|
switch (child) {
|
|
case 'C':
|
|
switch (edge) {
|
|
case 0: return external(1, +1);
|
|
case 1: return internal('Y', 1);
|
|
case 2: return external(0, 0);
|
|
}
|
|
case 'Y':
|
|
switch (edge) {
|
|
case 0: return external(2, 0);
|
|
case 1: return internal('C', 1);
|
|
case 2: return external(1, -1);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'D':
|
|
switch (child) {
|
|
case 'D':
|
|
switch (edge) {
|
|
case 0: return external(2, -1);
|
|
case 1: return external(0, 0);
|
|
case 2: return internal('X', 2);
|
|
}
|
|
case 'X':
|
|
switch (edge) {
|
|
case 0: return external(1, 0);
|
|
case 1: return external(2, +1);
|
|
case 2: return internal('D', 2);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'X':
|
|
switch (child) {
|
|
case 'C':
|
|
switch (edge) {
|
|
case 0: return external(2, +1);
|
|
case 1: return internal('Y', 1);
|
|
case 2: return internal('X', 1);
|
|
}
|
|
case 'X':
|
|
switch (edge) {
|
|
case 0: return external(1, 0);
|
|
case 1: return internal('C', 2);
|
|
case 2: return external(0, -1);
|
|
}
|
|
case 'Y':
|
|
switch (edge) {
|
|
case 0: return external(0, +1);
|
|
case 1: return internal('C', 1);
|
|
case 2: return external(2, -1);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'Y':
|
|
switch (child) {
|
|
case 'D':
|
|
switch (edge) {
|
|
case 0: return external(1, -1);
|
|
case 1: return internal('Y', 2);
|
|
case 2: return internal('X', 2);
|
|
}
|
|
case 'X':
|
|
switch (edge) {
|
|
case 0: return external(0, -1);
|
|
case 1: return external(1, +1);
|
|
case 2: return internal('D', 2);
|
|
}
|
|
case 'Y':
|
|
switch (edge) {
|
|
case 0: return external(2, 0);
|
|
case 1: return external(0, +1);
|
|
case 2: return internal('D', 1);
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Compute a transition into a parent triangle, after the above
|
|
* function reported an EXTERNAL transition out of a neighbouring
|
|
* parent and we had to recurse. Because we're coming inwards, this
|
|
* should always return an INTERNAL TransitionResult (or FAIL if it
|
|
* gets invalid data).
|
|
*/
|
|
static TransitionResult transition_in(char parent, unsigned edge, int end)
|
|
{
|
|
#define EDGEEND(edge, end) (3 * (edge) + 1 + (end))
|
|
|
|
switch (parent) {
|
|
case 'A':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, 0): return internal('A', 1);
|
|
case EDGEEND(1, -1): return internal('B', 2);
|
|
case EDGEEND(1, +1): return internal('U', 2);
|
|
case EDGEEND(2, -1): return internal('U', 0);
|
|
case EDGEEND(2, +1): return internal('A', 0);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'B':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, 0): return internal('B', 2);
|
|
case EDGEEND(1, -1): return internal('B', 0);
|
|
case EDGEEND(1, +1): return internal('V', 0);
|
|
case EDGEEND(2, -1): return internal('V', 1);
|
|
case EDGEEND(2, +1): return internal('A', 1);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'U':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, -1): return internal('B', 2);
|
|
case EDGEEND(0, +1): return internal('U', 2);
|
|
case EDGEEND(1, 0): return internal('U', 0);
|
|
case EDGEEND(2, 0): return internal('B', 1);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'V':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, -1): return internal('V', 1);
|
|
case EDGEEND(0, +1): return internal('A', 1);
|
|
case EDGEEND(1, 0): return internal('A', 2);
|
|
case EDGEEND(2, 0): return internal('V', 0);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'C':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, 0): return internal('C', 2);
|
|
case EDGEEND(1, -1): return internal('C', 0);
|
|
case EDGEEND(1, +1): return internal('Y', 2);
|
|
case EDGEEND(2, 0): return internal('Y', 0);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'D':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, 0): return internal('D', 1);
|
|
case EDGEEND(1, 0): return internal('X', 0);
|
|
case EDGEEND(2, -1): return internal('X', 1);
|
|
case EDGEEND(2, +1): return internal('D', 0);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'X':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, -1): return internal('Y', 0);
|
|
case EDGEEND(0, +1): return internal('X', 2);
|
|
case EDGEEND(1, 0): return internal('X', 0);
|
|
case EDGEEND(2, -1): return internal('C', 0);
|
|
case EDGEEND(2, +1): return internal('Y', 2);
|
|
default:
|
|
return fail();
|
|
}
|
|
case 'Y':
|
|
switch (EDGEEND(edge, end)) {
|
|
case EDGEEND(0, +1): return internal('X', 0);
|
|
case EDGEEND(0, -1): return internal('Y', 1);
|
|
case EDGEEND(1, -1): return internal('X', 1);
|
|
case EDGEEND(1, +1): return internal('D', 0);
|
|
case EDGEEND(2, 0): return internal('Y', 0);
|
|
default:
|
|
return fail();
|
|
}
|
|
default:
|
|
return fail();
|
|
}
|
|
|
|
#undef EDGEEND
|
|
}
|
|
|
|
PenroseCoords *penrose_coords_new(void)
|
|
{
|
|
PenroseCoords *pc = snew(PenroseCoords);
|
|
pc->nc = pc->csize = 0;
|
|
pc->c = NULL;
|
|
return pc;
|
|
}
|
|
|
|
void penrose_coords_free(PenroseCoords *pc)
|
|
{
|
|
if (pc) {
|
|
sfree(pc->c);
|
|
sfree(pc);
|
|
}
|
|
}
|
|
|
|
void penrose_coords_make_space(PenroseCoords *pc, size_t size)
|
|
{
|
|
if (pc->csize < size) {
|
|
pc->csize = pc->csize * 5 / 4 + 16;
|
|
if (pc->csize < size)
|
|
pc->csize = size;
|
|
pc->c = sresize(pc->c, pc->csize, char);
|
|
}
|
|
}
|
|
|
|
PenroseCoords *penrose_coords_copy(PenroseCoords *pc_in)
|
|
{
|
|
PenroseCoords *pc_out = penrose_coords_new();
|
|
penrose_coords_make_space(pc_out, pc_in->nc);
|
|
memcpy(pc_out->c, pc_in->c, pc_in->nc * sizeof(*pc_out->c));
|
|
pc_out->nc = pc_in->nc;
|
|
return pc_out;
|
|
}
|
|
|
|
/*
|
|
* The main recursive function for computing the next triangle's
|
|
* combinatorial coordinates.
|
|
*/
|
|
static void penrosectx_step_recurse(
|
|
PenroseContext *ctx, PenroseCoords *pc, size_t depth,
|
|
unsigned edge, unsigned *outedge)
|
|
{
|
|
TransitionResult tr;
|
|
|
|
penrosectx_extend_coords(ctx, pc, depth+2);
|
|
|
|
/* Look up the transition out of the starting triangle */
|
|
tr = transition(pc->c[depth+1], pc->c[depth], edge);
|
|
|
|
/* If we've left the parent triangle, recurse to find out what new
|
|
* triangle we've landed in at the next size up, and then call
|
|
* transition_in to find out which child of that parent we're
|
|
* going to */
|
|
if (tr.type == EXTERNAL) {
|
|
unsigned parent_outedge;
|
|
penrosectx_step_recurse(
|
|
ctx, pc, depth+1, tr.u.external.parent_edge, &parent_outedge);
|
|
tr = transition_in(pc->c[depth+1], parent_outedge, tr.u.external.end);
|
|
}
|
|
|
|
/* Now we should definitely have ended up in a child of the
|
|
* (perhaps rewritten) parent triangle */
|
|
assert(tr.type == INTERNAL);
|
|
pc->c[depth] = tr.u.internal.new_child;
|
|
*outedge = tr.u.internal.new_edge;
|
|
}
|
|
|
|
void penrosectx_step(PenroseContext *ctx, PenroseCoords *pc,
|
|
unsigned edge, unsigned *outedge)
|
|
{
|
|
/* Allow outedge to be NULL harmlessly, just in case */
|
|
unsigned dummy_outedge;
|
|
if (!outedge)
|
|
outedge = &dummy_outedge;
|
|
|
|
penrosectx_step_recurse(ctx, pc, 0, edge, outedge);
|
|
}
|
|
|
|
static Point penrose_triangle_post_edge(char c, unsigned edge)
|
|
{
|
|
static const Point acute_post_edge[3] = {
|
|
{{-1, 1, 0, 1}}, /* phi * t^3 */
|
|
{{-1, 1, -1, 1}}, /* t^4 */
|
|
{{-1, 1, 0, 0}}, /* 1/phi * t^3 */
|
|
};
|
|
static const Point obtuse_post_edge[3] = {
|
|
{{0, -1, 1, 0}}, /* 1/phi * t^4 */
|
|
{{0, 0, 1, 0}}, /* t^2 */
|
|
{{-1, 0, 0, 1}}, /* phi * t^4 */
|
|
};
|
|
|
|
switch (c) {
|
|
case 'A': case 'B': case 'C': case 'D':
|
|
return acute_post_edge[edge];
|
|
default: /* case 'U': case 'V': case 'X': case 'Y': */
|
|
return obtuse_post_edge[edge];
|
|
}
|
|
}
|
|
|
|
void penrose_place(PenroseTriangle *tri, Point u, Point v, int index_of_u)
|
|
{
|
|
unsigned i;
|
|
Point here = u, delta = point_sub(v, u);
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
unsigned edge = (index_of_u + i) % 3;
|
|
tri->vertices[edge] = here;
|
|
here = point_add(here, delta);
|
|
delta = point_mul(delta, penrose_triangle_post_edge(
|
|
tri->pc->c[0], edge));
|
|
}
|
|
}
|
|
|
|
void penrose_free(PenroseTriangle *tri)
|
|
{
|
|
penrose_coords_free(tri->pc);
|
|
sfree(tri);
|
|
}
|
|
|
|
static bool penrose_relative_probability(char c)
|
|
{
|
|
/* Penrose tile probability ratios are always phi, so we can
|
|
* approximate that very well using two consecutive Fibonacci
|
|
* numbers */
|
|
switch (c) {
|
|
case 'A': case 'B': case 'X': case 'Y':
|
|
return 165580141;
|
|
case 'C': case 'D': case 'U': case 'V':
|
|
return 102334155;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static char penrose_choose_random(const char *possibilities, random_state *rs)
|
|
{
|
|
const char *p;
|
|
unsigned long value, limit = 0;
|
|
|
|
for (p = possibilities; *p; p++)
|
|
limit += penrose_relative_probability(*p);
|
|
value = random_upto(rs, limit);
|
|
for (p = possibilities; *p; p++) {
|
|
unsigned long curr = penrose_relative_probability(*p);
|
|
if (value < curr)
|
|
return *p;
|
|
value -= curr;
|
|
}
|
|
assert(false && "Probability overflow!");
|
|
return possibilities[0];
|
|
}
|
|
|
|
static const char *penrose_starting_tiles(int which)
|
|
{
|
|
return which == PENROSE_P2 ? "ABUV" : "CDXY";
|
|
}
|
|
|
|
static const char *penrose_valid_parents(char tile)
|
|
{
|
|
switch (tile) {
|
|
case 'A': return "ABV";
|
|
case 'B': return "ABU";
|
|
case 'U': return "AU";
|
|
case 'V': return "BV";
|
|
case 'C': return "CX";
|
|
case 'D': return "DY";
|
|
case 'X': return "DXY";
|
|
case 'Y': return "CXY";
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
void penrosectx_init_random(PenroseContext *ctx, random_state *rs, int which)
|
|
{
|
|
ctx->rs = rs;
|
|
ctx->must_free_rs = false;
|
|
ctx->prototype = penrose_coords_new();
|
|
penrose_coords_make_space(ctx->prototype, 1);
|
|
ctx->prototype->c[0] = penrose_choose_random(
|
|
penrose_starting_tiles(which), rs);
|
|
ctx->prototype->nc = 1;
|
|
ctx->start_vertex = random_upto(rs, 3);
|
|
ctx->orientation = random_upto(rs, 10);
|
|
}
|
|
|
|
void penrosectx_init_from_params(
|
|
PenroseContext *ctx, const struct PenrosePatchParams *ps)
|
|
{
|
|
size_t i;
|
|
|
|
ctx->rs = NULL;
|
|
ctx->must_free_rs = false;
|
|
ctx->prototype = penrose_coords_new();
|
|
penrose_coords_make_space(ctx->prototype, ps->ncoords);
|
|
for (i = 0; i < ps->ncoords; i++)
|
|
ctx->prototype->c[i] = ps->coords[i];
|
|
ctx->prototype->nc = ps->ncoords;
|
|
ctx->start_vertex = ps->start_vertex;
|
|
ctx->orientation = ps->orientation;
|
|
}
|
|
|
|
void penrosectx_cleanup(PenroseContext *ctx)
|
|
{
|
|
if (ctx->must_free_rs)
|
|
random_free(ctx->rs);
|
|
penrose_coords_free(ctx->prototype);
|
|
}
|
|
|
|
PenroseCoords *penrosectx_initial_coords(PenroseContext *ctx)
|
|
{
|
|
return penrose_coords_copy(ctx->prototype);
|
|
}
|
|
|
|
void penrosectx_extend_coords(PenroseContext *ctx, PenroseCoords *pc,
|
|
size_t n)
|
|
{
|
|
if (ctx->prototype->nc < n) {
|
|
penrose_coords_make_space(ctx->prototype, n);
|
|
while (ctx->prototype->nc < n) {
|
|
if (!ctx->rs) {
|
|
/*
|
|
* For safety, similarly to spectre.c, we respond to a
|
|
* lack of available random_state by making a
|
|
* deterministic one.
|
|
*/
|
|
ctx->rs = random_new("dummy", 5);
|
|
ctx->must_free_rs = true;
|
|
}
|
|
|
|
ctx->prototype->c[ctx->prototype->nc] = penrose_choose_random(
|
|
penrose_valid_parents(ctx->prototype->c[ctx->prototype->nc-1]),
|
|
ctx->rs);
|
|
ctx->prototype->nc++;
|
|
}
|
|
}
|
|
|
|
penrose_coords_make_space(pc, n);
|
|
while (pc->nc < n) {
|
|
pc->c[pc->nc] = ctx->prototype->c[pc->nc];
|
|
pc->nc++;
|
|
}
|
|
}
|
|
|
|
static Point penrose_triangle_edge_0_length(char c)
|
|
{
|
|
static const Point one = {{ 1, 0, 0, 0 }};
|
|
static const Point phi = {{ 1, 0, 1, -1 }};
|
|
static const Point invphi = {{ 0, 0, 1, -1 }};
|
|
|
|
switch (c) {
|
|
/* P2 tiling: unit-length edges are the long edges, i.e. edges
|
|
* 1,2 of AB and edge 0 of UV. So AB have edge 0 short. */
|
|
case 'A': case 'B':
|
|
return invphi;
|
|
case 'U': case 'V':
|
|
return one;
|
|
|
|
/* P3 tiling: unit-length edges are edges 1,2 of everything,
|
|
* so CD have edge 0 short and XY have it long. */
|
|
case 'C': case 'D':
|
|
return invphi;
|
|
default: /* case 'X': case 'Y': */
|
|
return phi;
|
|
}
|
|
}
|
|
|
|
PenroseTriangle *penrose_initial(PenroseContext *ctx)
|
|
{
|
|
char type = ctx->prototype->c[0];
|
|
Point origin = {{ 0, 0, 0, 0 }};
|
|
Point edge0 = penrose_triangle_edge_0_length(type);
|
|
Point negoffset;
|
|
size_t i;
|
|
PenroseTriangle *tri = snew(PenroseTriangle);
|
|
|
|
/* Orient the triangle by deciding what vector edge #0 should traverse */
|
|
edge0 = point_mul(edge0, point_rot(ctx->orientation));
|
|
|
|
/* Place the triangle at an arbitrary position, in that orientation */
|
|
tri->pc = penrose_coords_copy(ctx->prototype);
|
|
penrose_place(tri, origin, edge0, 0);
|
|
|
|
/* Now translate so that the appropriate vertex is at the origin */
|
|
negoffset = tri->vertices[ctx->start_vertex];
|
|
for (i = 0; i < 3; i++)
|
|
tri->vertices[i] = point_sub(tri->vertices[i], negoffset);
|
|
|
|
return tri;
|
|
}
|
|
|
|
PenroseTriangle *penrose_adjacent(PenroseContext *ctx,
|
|
const PenroseTriangle *src_tri,
|
|
unsigned src_edge, unsigned *dst_edge_out)
|
|
{
|
|
unsigned dst_edge;
|
|
PenroseTriangle *dst_tri = snew(PenroseTriangle);
|
|
dst_tri->pc = penrose_coords_copy(src_tri->pc);
|
|
penrosectx_step(ctx, dst_tri->pc, src_edge, &dst_edge);
|
|
penrose_place(dst_tri, src_tri->vertices[(src_edge+1) % 3],
|
|
src_tri->vertices[src_edge], dst_edge);
|
|
if (dst_edge_out)
|
|
*dst_edge_out = dst_edge;
|
|
return dst_tri;
|
|
}
|
|
|
|
static int penrose_cmp(void *av, void *bv)
|
|
{
|
|
PenroseTriangle *a = (PenroseTriangle *)av, *b = (PenroseTriangle *)bv;
|
|
size_t i, j;
|
|
|
|
/* We should only ever need to compare the first two vertices of
|
|
* any triangle, because those force the rest */
|
|
for (i = 0; i < 2; i++) {
|
|
for (j = 0; j < 4; j++) {
|
|
int ac = a->vertices[i].coeffs[j], bc = b->vertices[i].coeffs[j];
|
|
if (ac < bc)
|
|
return -1;
|
|
if (ac > bc)
|
|
return +1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned penrose_sibling_edge_index(char c)
|
|
{
|
|
switch (c) {
|
|
case 'A': case 'U': return 2;
|
|
case 'B': case 'V': return 1;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
void penrosectx_generate(
|
|
PenroseContext *ctx,
|
|
bool (*inbounds)(void *inboundsctx,
|
|
const PenroseTriangle *tri), void *inboundsctx,
|
|
void (*tile)(void *tilectx, const Point *vertices), void *tilectx)
|
|
{
|
|
tree234 *placed = newtree234(penrose_cmp);
|
|
PenroseTriangle *qhead = NULL, *qtail = NULL;
|
|
|
|
{
|
|
PenroseTriangle *tri = penrose_initial(ctx);
|
|
|
|
add234(placed, tri);
|
|
|
|
tri->next = NULL;
|
|
tri->reported = false;
|
|
|
|
if (inbounds(inboundsctx, tri))
|
|
qhead = qtail = tri;
|
|
}
|
|
|
|
while (qhead) {
|
|
PenroseTriangle *tri = qhead;
|
|
unsigned edge;
|
|
unsigned sibling_edge = penrose_sibling_edge_index(tri->pc->c[0]);
|
|
|
|
for (edge = 0; edge < 3; edge++) {
|
|
PenroseTriangle *new_tri, *found_tri;
|
|
|
|
new_tri = penrose_adjacent(ctx, tri, edge, NULL);
|
|
|
|
if (!inbounds(inboundsctx, new_tri)) {
|
|
penrose_free(new_tri);
|
|
continue;
|
|
}
|
|
|
|
found_tri = find234(placed, new_tri, NULL);
|
|
if (found_tri) {
|
|
if (edge == sibling_edge && !tri->reported &&
|
|
!found_tri->reported) {
|
|
/*
|
|
* found_tri and tri are opposite halves of the
|
|
* same tile; both are in the tree, and haven't
|
|
* yet been reported as a completed tile.
|
|
*/
|
|
unsigned new_sibling_edge = penrose_sibling_edge_index(
|
|
found_tri->pc->c[0]);
|
|
Point tilevertices[4] = {
|
|
tri->vertices[(sibling_edge + 1) % 3],
|
|
tri->vertices[(sibling_edge + 2) % 3],
|
|
found_tri->vertices[(new_sibling_edge + 1) % 3],
|
|
found_tri->vertices[(new_sibling_edge + 2) % 3],
|
|
};
|
|
tile(tilectx, tilevertices);
|
|
|
|
tri->reported = true;
|
|
found_tri->reported = true;
|
|
}
|
|
|
|
penrose_free(new_tri);
|
|
continue;
|
|
}
|
|
|
|
add234(placed, new_tri);
|
|
qtail->next = new_tri;
|
|
qtail = new_tri;
|
|
new_tri->next = NULL;
|
|
new_tri->reported = false;
|
|
}
|
|
|
|
qhead = qhead->next;
|
|
}
|
|
|
|
{
|
|
PenroseTriangle *tri;
|
|
while ((tri = delpos234(placed, 0)) != NULL)
|
|
penrose_free(tri);
|
|
freetree234(placed);
|
|
}
|
|
}
|
|
|
|
const char *penrose_tiling_params_invalid(
|
|
const struct PenrosePatchParams *params, int which)
|
|
{
|
|
size_t i;
|
|
|
|
if (params->ncoords == 0)
|
|
return "expected at least one coordinate";
|
|
|
|
for (i = 0; i < params->ncoords; i++) {
|
|
if (!penrose_valid_letter(params->coords[i], which))
|
|
return "invalid coordinate letter";
|
|
if (i > 0 && !strchr(penrose_valid_parents(params->coords[i-1]),
|
|
params->coords[i]))
|
|
return "invalid pair of consecutive coordinates";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct PenroseOutputCtx {
|
|
int xoff, yoff;
|
|
Coord xmin, xmax, ymin, ymax;
|
|
|
|
penrose_tile_callback_fn external_cb;
|
|
void *external_cbctx;
|
|
};
|
|
|
|
static bool inbounds(void *vctx, const PenroseTriangle *tri)
|
|
{
|
|
struct PenroseOutputCtx *octx = (struct PenroseOutputCtx *)vctx;
|
|
size_t i;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
Coord x = point_x(tri->vertices[i]);
|
|
Coord y = point_y(tri->vertices[i]);
|
|
|
|
if (coord_cmp(x, octx->xmin) < 0 || coord_cmp(x, octx->xmax) > 0 ||
|
|
coord_cmp(y, octx->ymin) < 0 || coord_cmp(y, octx->ymax) > 0)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void null_output_tile(void *vctx, const Point *vertices)
|
|
{
|
|
}
|
|
|
|
static void really_output_tile(void *vctx, const Point *vertices)
|
|
{
|
|
struct PenroseOutputCtx *octx = (struct PenroseOutputCtx *)vctx;
|
|
size_t i;
|
|
int coords[16];
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
Coord x = point_x(vertices[i]);
|
|
Coord y = point_y(vertices[i]);
|
|
|
|
coords[4*i + 0] = x.c1 + octx->xoff;
|
|
coords[4*i + 1] = x.cr5;
|
|
coords[4*i + 2] = y.c1 + octx->yoff;
|
|
coords[4*i + 3] = y.cr5;
|
|
}
|
|
|
|
octx->external_cb(octx->external_cbctx, coords);
|
|
}
|
|
|
|
static void penrose_set_bounds(struct PenroseOutputCtx *octx, int w, int h)
|
|
{
|
|
octx->xoff = w/2;
|
|
octx->yoff = h/2;
|
|
octx->xmin.c1 = -octx->xoff;
|
|
octx->xmax.c1 = -octx->xoff + w;
|
|
octx->ymin.c1 = octx->yoff - h;
|
|
octx->ymax.c1 = octx->yoff;
|
|
octx->xmin.cr5 = 0;
|
|
octx->xmax.cr5 = 0;
|
|
octx->ymin.cr5 = 0;
|
|
octx->ymax.cr5 = 0;
|
|
}
|
|
|
|
void penrose_tiling_randomise(struct PenrosePatchParams *params, int which,
|
|
int w, int h, random_state *rs)
|
|
{
|
|
PenroseContext ctx[1];
|
|
struct PenroseOutputCtx octx[1];
|
|
|
|
penrose_set_bounds(octx, w, h);
|
|
|
|
penrosectx_init_random(ctx, rs, which);
|
|
penrosectx_generate(ctx, inbounds, octx, null_output_tile, NULL);
|
|
|
|
params->orientation = ctx->orientation;
|
|
params->start_vertex = ctx->start_vertex;
|
|
params->ncoords = ctx->prototype->nc;
|
|
params->coords = snewn(params->ncoords, char);
|
|
memcpy(params->coords, ctx->prototype->c, params->ncoords);
|
|
|
|
penrosectx_cleanup(ctx);
|
|
}
|
|
|
|
void penrose_tiling_generate(
|
|
const struct PenrosePatchParams *params, int w, int h,
|
|
penrose_tile_callback_fn cb, void *cbctx)
|
|
{
|
|
PenroseContext ctx[1];
|
|
struct PenroseOutputCtx octx[1];
|
|
|
|
penrose_set_bounds(octx, w, h);
|
|
octx->external_cb = cb;
|
|
octx->external_cbctx = cbctx;
|
|
|
|
penrosectx_init_from_params(ctx, params);
|
|
penrosectx_generate(ctx, inbounds, octx, really_output_tile, octx);
|
|
penrosectx_cleanup(ctx);
|
|
}
|