Files
puzzles/aux/hatgen.c
Simon Tatham 8d6647548f Loopy / grid.c: new grid type, 'Hats'.
The big mathematical news this month is that a polygon has been
discovered that will tile the plane but only aperiodically. Penrose
tiles achieve this with two tile types; it's been an open question for
decades whether you could do it with only one tile. Now someone has
announced the discovery of such a thing, so _obviously_ this
mathematically exciting tiling ought to be one of the Loopy grid
options!

The polygon, named a 'hat' by its discoverers, consists of the union
of eight cells of the 'Kites' periodic tiling that Loopy already
implements. So all the vertex coordinates of the whole tiling are
vertices of the Kites grid, which makes handling the coordinates in an
exact manner a lot easier than Penrose tilings.

What's _harder_ than Penrose tilings is that, although this tiling can
be generated by a vaguely similar system of recursive expansion, the
expansion is geometrically distorting, which means you can't easily
figure out which tiles can be discarded early to save CPU. Instead
I've come up with a completely different system for generating a patch
of tiling, by using a hierarchical coordinate system to track a
location within many levels of the expansion process without ever
simulating the process as a whole. I'm really quite pleased with that
technique, and am tempted to try switching the Penrose generator over
to it too - except that we'd have to keep the old generator around to
stop old game ids being invalidated, and also, I think it would be
slightly trickier without an underlying fixed grid and without
overlaps in the tile expansion system.

However, before coming up with that, I got most of the way through
implementing the more obvious system of actually doing the expansions.
The result worked, but was very slow (because I changed approach
rather than try to implement tree-pruning under distortion). But the
code was reusable for two other useful purposes: it generated the
lookup tables needed for the production code, and it also generated a
lot of useful diagrams. So I've committed it anyway as a supporting
program, in a new 'aux' source subdirectory, and in aux/doc is a
writeup of the coordinate system's concepts, with all those diagrams.
(That's the kind of thing I'd normally put in a huge comment at the
top of the file, but doing all those diagrams in ASCII art would be
beyond miserable.)

From a gameplay perspective: the hat polygon has 13 edges, but one of
them has a vertex of the Kites tiling in the middle, and sometimes two
other tile boundaries meet at that vertex. I've chosen to represent
every hat as having degree 14 for Loopy purposes, because if you only
included that extra vertex when it was needed, then people would be
forever having to check whether this was a 13-hat or a 14-hat and it
would be nightmarish to play.

Even so, there's a lot of clicking involved to turn all those fiddly
individual edges on or off. This grid is noticeably nicer to play in
'autofollow' mode, by setting LOOPY_AUTOFOLLOW in the environment to
either 'fixed' or 'adaptive'. I'm tempted to make 'fixed' the default,
except that I think it would confuse players of ordinary square Loopy!
2023-03-26 20:32:38 +01:00

1714 lines
54 KiB
C

/*
* Generate patches of tiling by the 'hat' aperiodic monotile
* discovered in 2023.
*
* This implementation of hat-tiling generation was intended to be the
* basis for generating hat grids for Loopy. However, it turned out
* that I found a better strategy, so this source file isn't used by
* the main puzzle system. I've kept it anyway because I ended up
* adapting it to generate the file hat-tables.h containing the lookup
* tables for the algorithm I _did_ end up using. It also generates
* diagrams that are useful for understanding that algorithm, and for
* debugging it if anything still turns out to be wrong with it.
*
* Discoverers' website: https://cs.uwaterloo.ca/~csk/hat/
* Preprint of paper: https://arxiv.org/abs/2303.10798
*/
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "puzzles.h"
#include "tree234.h"
#include "hat.h"
/*
* General strategy:
*
* We construct the hat tiling by means of a substitution system of
* 'metatiles' which come in four types, called H,T,P,F. A (valid)
* tiling of these metatiles can be expanded to a larger one by a set
* of recursive subdivision rules. Once we have a large enough patch
* of metatiles, we apply a final transformation that converts each
* metatile into 1, 2 or 4 instances of the aperiodically tiling
* 'hat'.
*
* Unlike the similar substitution system for Penrose tilings, the
* expansion rules are not geometrically precise: the larger versions
* of each metatile fit together combinatorially in the same way, but
* their shapes are distorted slightly. So we must construct our
* expanded meta-tiling by breadth-first search out from a starting
* metatile, because we won't quite know the coordinates of the
* expanded version of each metatile until we know one of the ones
* next to it.
*/
/*
* Coordinate system:
*
* Everything in this code lives on the tiling known to grid.c as
* 'Kites', which can be viewed as a tiling of hexagons each of which
* is subdivided into six kites sharing their pointy vertex, or
* (equivalently) a tiling of equilateral triangles each subdivided
* into three kits sharing their blunt vertex.
*
* We express coordinates in this system relative to the basis (1, r)
* where r = (1 + sqrt(3)i) / 2 is a primitive 6th root of unity. This
* gives us a system in which two integer coordinates can address any
* grid point, provided we scale up so that the side length of the
* equilateral triangles in the tiling is 6.
*/
typedef struct Point {
int x, y; /* represents x + yr */
} Point;
static inline Point left6(Point p)
{
/* r satisfies the equation r^2 = r-1. Hence, multiplying by r
* (achieving a rotation anticlockwise by 1/6 turn) transforms
* x+yr into xr+yr^2 = xr+y(r-1) = (-y) + (x+y)r.
*
* It's easy to check that iterating this transformation six times
* gives you back the same coordinates you started with. */
Point q = { -p.y, p.x + p.y };
return q;
}
static inline Point right6(Point p)
{
/* Conversely, 1/r = 1 - r, so dividing by r turns x+yr into x/r+y
* = x(1-r) + y = (x+y) + (-x)r. */
Point q = { p.x + p.y, -p.x };
return q;
}
typedef enum MetatileType { MT_H, MT_T, MT_P, MT_F } MetatileType;
typedef struct Metatile Metatile;
typedef struct MetaCoord {
Metatile *parent;
int index;
} MetaCoord;
struct Metatile {
/* Data fields describing the metatile and its position. */
MetatileType type;
Point start, orientation;
MetaCoord coords[4];
size_t ncoords;
/* Auxiliary fields used to store the progress of the expansion
* algorithm. */
bool queued;
Metatile *qnext;
};
#define MT_MAXVERT 6 /* largest number of vertices of any metatile */
#define MT_MAXVDEGREE 3 /* largest degree of any vertex of a meta-tiling */
#define MT_MAXEXPAND 13 /* largest number of metatiles in any expansion */
#define MT_MAXHAT 4 /* largest number of hats in a metatile */
#define HAT_NVERT 14 /* vertices of a single hat (counting the straight one) */
#define HAT_NKITE 8 /* kites in a single hat */
static int metatile_cmp(void *av, void *bv)
{
Metatile *a = (Metatile *)av, *b = (Metatile *)bv;
if (a->type < b->type) return -1;
if (a->type > b->type) return +1;
if (a->start.x < b->start.x) return -1;
if (a->start.x > b->start.x) return +1;
if (a->start.y < b->start.y) return -1;
if (a->start.y > b->start.y) return +1;
if (a->orientation.x < b->orientation.x) return -1;
if (a->orientation.x > b->orientation.x) return +1;
if (a->orientation.y < b->orientation.y) return -1;
if (a->orientation.y > b->orientation.y) return +1;
return 0;
}
/*
* Return the coordinates of the vertices of a metatile, given the
* coordinates of the vertex we deem to be distinguished, and a vector
* of Euclidean length 1 showing its direction.
*
* If 'expanded' is true, we instead return the coordinates of the
* corresponding vertices of the expanded version of the same
* metatile.
*/
static size_t metatile_vertices(Metatile m, Point *out, bool expanded)
{
static const Point vertices_H[] = {
{0, 0}, {4, -2}, {12, 6}, {10, 10}, {-6, 18}, {-8, 16},
};
static const Point vertices_T[] = {
{0, 0}, {6, 6}, {-6, 12},
};
static const Point vertices_P[] = {
{0, 0}, {4, 4}, {-4, 20}, {-8, 16},
};
static const Point vertices_F[] = {
{0, 0}, {4, -2}, {6, 0}, {-2, 16}, {-6, 12},
};
static const Point expanded_H[] = {
{0, 0}, {12, -6}, {30, 12}, {24, 24}, {-12, 42}, {-18, 36},
};
static const Point expanded_T[] = {
{0, 0}, {12, 12}, {-12, 24},
};
static const Point expanded_P[] = {
{0, 0}, {14, 8}, {-4, 44}, {-18, 36},
};
static const Point expanded_F[] = {
{0, 0}, {14, -4}, {18, 6}, {0, 42}, {-14, 34},
};
const Point *vertices;
size_t nvertices;
size_t i;
switch (m.type) {
case MT_H:
vertices = expanded ? expanded_H : vertices_H;
nvertices = lenof(vertices_H);
break;
case MT_T:
vertices = expanded ? expanded_T : vertices_T;
nvertices = lenof(vertices_T);
break;
case MT_P:
vertices = expanded ? expanded_P : vertices_P;
nvertices = lenof(vertices_P);
break;
default /* case MT_F */:
vertices = expanded ? expanded_F : vertices_F;
nvertices = lenof(vertices_F);
break;
}
assert(nvertices <= MT_MAXVERT);
Point orientation_r = left6(m.orientation);
for (i = 0; i < nvertices; i++) {
Point v = vertices[i];
out[i].x = m.start.x + v.x * m.orientation.x + v.y * orientation_r.x;
out[i].y = m.start.y + v.x * m.orientation.y + v.y * orientation_r.y;
}
return nvertices;
}
/*
* Return a list of metatiles that arise from expanding a given tile.
*/
static size_t metatile_expand(Metatile m, Metatile *out)
{
static const Metatile tiles_H[] = {
{MT_H, {-4, 20}, {1, 0}},
{MT_H, {2, 2}, {1, 0}},
{MT_H, {8, 26}, {0, -1}},
{MT_T, {6, 24}, {-1, 0}},
{MT_P, {-8, 16}, {1, 0}},
{MT_P, {4, 34}, {0, -1}},
{MT_P, {6, 0}, {1, -1}},
{MT_F, {-10, 38}, {-1, 1}},
{MT_F, {-10, 44}, {0, -1}},
{MT_F, {-4, 2}, {1, 0}},
{MT_F, {2, 2}, {0, -1}},
{MT_F, {26, 14}, {1, 0}},
{MT_F, {32, 8}, {-1, 1}},
};
static const Metatile tiles_T[] = {
{MT_H, {10, 10}, {-1, 1}},
{MT_P, {-6, 0}, {1, 0}},
{MT_P, {8, 14}, {0, 1}},
{MT_P, {18, 6}, {-1, 1}},
{MT_F, {-14, 34}, {-1, 0}},
{MT_F, {-8, -2}, {1, -1}},
{MT_F, {22, 4}, {0, 1}},
};
static const Metatile tiles_P[] = {
{MT_H, {4, 22}, {0, 1}},
{MT_H, {10, 10}, {-1, 1}},
{MT_P, {-6, 0}, {1, 0}},
{MT_P, {6, 24}, {1, 0}},
{MT_P, {8, 14}, {0, 1}},
{MT_F, {-20, 40}, {1, -1}},
{MT_F, {-14, 34}, {-1, 0}},
{MT_F, {-8, -2}, {1, -1}},
{MT_F, {4, 46}, {-1, 1}},
{MT_F, {10, 10}, {1, 0}},
{MT_F, {16, 4}, {-1, 1}},
};
static const Metatile tiles_F[] = {
{MT_H, {8, 20}, {0, 1}},
{MT_H, {14, 8}, {-1, 1}},
{MT_P, {10, 22}, {1, 0}},
{MT_P, {12, 12}, {0, 1}},
{MT_F, {-16, 38}, {1, -1}},
{MT_F, {-10, 32}, {-1, 0}},
{MT_F, {-4, 2}, {1, 0}},
{MT_F, {2, 2}, {0, -1}},
{MT_F, {8, 44}, {-1, 1}},
{MT_F, {14, 8}, {1, 0}},
{MT_F, {20, 2}, {-1, 1}},
};
const Metatile *tiles;
size_t ntiles;
size_t i;
switch (m.type) {
case MT_H:
tiles = tiles_H;
ntiles = lenof(tiles_H);
break;
case MT_T:
tiles = tiles_T;
ntiles = lenof(tiles_T);
break;
case MT_P:
tiles = tiles_P;
ntiles = lenof(tiles_P);
break;
default /* case MT_F */:
tiles = tiles_F;
ntiles = lenof(tiles_F);
break;
}
assert(ntiles <= MT_MAXEXPAND);
Point orientation_r = left6(m.orientation);
for (i = 0; i < ntiles; i++) {
Metatile t = tiles[i];
out[i].type = t.type;
out[i].start.x = (m.start.x + t.start.x * m.orientation.x +
t.start.y * orientation_r.x);
out[i].start.y = (m.start.y + t.start.x * m.orientation.y +
t.start.y * orientation_r.y);
out[i].orientation.x = (t.orientation.x * m.orientation.x +
t.orientation.y * orientation_r.x);
out[i].orientation.y = (t.orientation.x * m.orientation.y +
t.orientation.y * orientation_r.y);
}
return ntiles;
}
/* Store data about each vertex during an expansion. */
typedef struct VertexMapping {
Point in;
/* Metatiles sharing this vertex */
Metatile *tiles[MT_MAXVDEGREE];
size_t ntiles;
/* The expanded coordinates of this vertex, if known */
bool mapped;
Point out;
} VertexMapping;
static int vertexmapping_cmp(void *av, void *bv)
{
VertexMapping *a = (VertexMapping *)av, *b = (VertexMapping *)bv;
if (a->in.x < b->in.x) return -1;
if (a->in.x > b->in.x) return +1;
if (a->in.y < b->in.y) return -1;
if (a->in.y > b->in.y) return +1;
return 0;
}
static int vertexmapping_find(void *av, void *bv)
{
Point *a = (Point *)av;
VertexMapping *b = (VertexMapping *)bv;
if (a->x < b->in.x) return -1;
if (a->x > b->in.x) return +1;
if (a->y < b->in.y) return -1;
if (a->y > b->in.y) return +1;
return 0;
}
typedef struct MetatileSet {
/* The tiles in the set */
tree234 *tiles;
/*
* Bounding box of a rectangular region within the original single
* tile this set was expanded from. We need this in order to pick
* a random chunk out of the tiling to return to our client: this
* box is the limit of where we might select our chunk from.
*
* The box is obtained by starting from the two obtuse vertices of
* the starting P metatile, and then mapping those two vertices
* through each expansion pass. This wouldn't work for the _other_
* two vertices of the P metatile, which end up in the middle of
* another metatile after the first expansion, so that the next
* expansion wouldn't find that point in its VertexMapping. But
* luckily the two inner P vertices do continue working: they
* alternate in subsequent expansions between vertex 1 and vertex
* 4 of an F metatile. And those are the ones we need to define a
* reliable bounding box - phew!
*/
Point vertices[2];
size_t nvertices;
} MetatileSet;
static MetatileSet *metatile_initial_set(MetatileType type)
{
MetatileSet *s;
Metatile *m;
Point vertices[MT_MAXVERT];
size_t nv;
s = snew(MetatileSet);
s->tiles = newtree234(metatile_cmp);
m = snew(Metatile);
m->type = type;
m->start.x = 0;
m->start.y = 0;
m->orientation.x = 1;
m->orientation.y = 0;
m->ncoords = 0;
add234(s->tiles, m);
if (type == MT_P) {
nv = metatile_vertices(*m, vertices, false);
assert(nv == 4);
s->vertices[0] = vertices[1];
s->vertices[1] = vertices[3];
s->nvertices = 2;
} else {
s->nvertices = 0;
}
return s;
}
static void metatile_free_set(MetatileSet *s)
{
Metatile *m;
while ((m = delpos234(s->tiles, 0)) != NULL)
sfree(m);
freetree234(s->tiles);
sfree(s);
}
typedef struct Queue {
Metatile *head, *tail;
} Queue;
static void map_vertex(VertexMapping *vm, Point out, Queue *queue)
{
size_t i;
debug(("map_vertex %d,%d -> %d,%d", vm->in.x, vm->in.y, out.x, out.y));
if (vm->mapped) {
debug((" (already done)\n"));
return;
}
debug(("\n"));
vm->mapped = true;
vm->out = out;
for (i = 0; i < vm->ntiles; i++) {
Metatile *t = vm->tiles[i];
if (!t->queued) {
t->queued = true;
t->qnext = NULL;
if (queue->tail)
queue->tail->qnext = t;
else
queue->head = t;
queue->tail = t;
debug(("queued %c @ %d,%d d=%d,%d\n", "HTPF"[t->type], t->start.x,
t->start.y, t->orientation.x, t->orientation.y));
}
}
}
/*
* Expand a set of metatiles into its next-generation set. Returns the
* new set. The old set is not freed, but the auxiliary fields of its
* Metatile structures will be used as intermediate storage.
*/
static MetatileSet *metatile_set_expand(MetatileSet *si)
{
tree234 *vmap;
VertexMapping *vm;
Metatile *m;
Queue queue = { NULL, NULL };
size_t i, j;
MetatileSet *so = snew(MetatileSet);
so->tiles = newtree234(metatile_cmp);
/*
* Enumerate all the vertices in our tiling, and store the set of
* tiles they belong to.
*/
vmap = newtree234(vertexmapping_cmp);
for (i = 0; (m = index234(si->tiles, i)) != NULL; i++) {
Point vertices[MT_MAXVERT];
size_t nv = metatile_vertices(*m, vertices, false);
for (j = 0; j < nv; j++) {
VertexMapping *newvm = snew(VertexMapping);
newvm->in = vertices[j];
newvm->ntiles = 0;
newvm->mapped = false;
vm = add234(vmap, newvm);
if (vm != newvm)
sfree(newvm);
assert(vm->ntiles < MT_MAXVDEGREE);
vm->tiles[vm->ntiles++] = m;
}
m->queued = false;
}
for (i = 0; (vm = index234(vmap, i)) != NULL; i++) {
debug(("vertex @ %d,%d {", vm->in.x, vm->in.y));
for (j = 0; j < vm->ntiles; j++) {
m = vm->tiles[j];
debug(("%s%c @ %d,%d d=%d,%d", j?", ":"", "HTPF"[m->type],
m->start.x, m->start.y, m->orientation.x,
m->orientation.y));
}
debug(("}\n"));
}
/*
* Initialise an arbitrary vertex to a known location.
*/
{
Point p = {0, 0};
m = index234(si->tiles, 0);
vm = find234(vmap, &m->start, vertexmapping_find);
map_vertex(vm, p, &queue);
}
/*
* Now process the queue of tiles to be expanded.
*/
debug(("-- start\n"));
while (queue.head) {
Metatile *m, m_moved;
Metatile t[MT_MAXEXPAND];
Point vi[MT_MAXVERT], vo[MT_MAXVERT];
size_t nv, nt;
m = queue.head;
queue.head = queue.head->qnext;
if (!queue.head)
queue.tail = NULL;
debug(("unqueued %c @ %d,%d d=%d,%d\n", "HTPF"[m->type],
m->start.x, m->start.y, m->orientation.x, m->orientation.y));
nv = metatile_vertices(*m, vi, false);
metatile_vertices(*m, vo, true);
/* Find a vertex of this tile that's already mapped, and use
* it to determine the placement. */
int dx, dy;
for (i = 0; i < nv; i++) {
vm = find234(vmap, &vi[i], vertexmapping_find);
assert(vm);
if (vm->mapped) {
dx = vm->out.x - vo[i].x;
dy = vm->out.y - vo[i].y;
debug(("found mapped vertex %d,%d -> %d,%d: "
"offset=%d,%d\n",
vm->in.x, vm->in.y, vm->out.x, vm->out.y, dx, dy));
break;
}
}
assert(i < nv && "Why was this tile queued without a mapped vertex?");
/* Now map all the rest of the vertices of the tile. */
for (i = 0; i < nv; i++) {
vm = find234(vmap, &vi[i], vertexmapping_find);
vo[i].x += dx;
vo[i].y += dy;
map_vertex(vm, vo[i], &queue);
}
/* And expand it, substituting in its new starting coordinate. */
m_moved = *m; /* structure copy */
m_moved.start = vo[0];
nt = metatile_expand(m_moved, t);
for (i = 0; i < nt; i++) {
Metatile *newmt = snew(Metatile);
*newmt = t[i]; /* structure copy */
newmt->ncoords = 0;
debug(("expanded %c @ %d,%d d=%d,%d\n", "HTPF"[newmt->type],
newmt->start.x, newmt->start.y, newmt->orientation.x,
newmt->orientation.y));
Metatile *added = add234(so->tiles, newmt);
if (added != newmt)
sfree(newmt);
assert(added->ncoords < lenof(added->coords));
added->coords[added->ncoords].parent = m;
added->coords[added->ncoords].index = i;
added->ncoords++;
}
}
for (i = 0; (m = index234(si->tiles, i)) != NULL; i++) {
if (!m->queued)
debug(("OMITTED %c @ %d,%d d=%d,%d\n", "HTPF"[m->type],
m->start.x, m->start.y, m->orientation.x,
m->orientation.y));
}
/*
* Write out the remapped versions of the tile set's bounding
* vertices.
*/
for (i = 0; i < si->nvertices; i++) {
vm = find234(vmap, &si->vertices[i], vertexmapping_find);
so->vertices[i] = vm->out;
}
so->nvertices = si->nvertices;
while ((vm = delpos234(vmap, 0)) != NULL)
sfree(vm);
freetree234(vmap);
return so;
}
typedef struct Hat {
Point start, orientation;
bool reversed;
const Metatile *parent;
int index;
} Hat;
static size_t metatile_hats(const Metatile *m, Hat *out)
{
static const Hat hats_H[] = {
{{6, 0}, {1, 0}, false},
{{6, 6}, {0, -1}, false},
{{0, 12}, {1, 0}, false},
{{0, 6}, {-1, 0}, true},
};
static const Hat hats_T[] = {
{{-2, 10}, {-1, 1}, false},
};
static const Hat hats_P[] = {
{{-2, 10}, {-1, 1}, false},
{{-2, 16}, {0, 1}, false},
};
static const Hat hats_F[] = {
{{0, 6}, {-1, 1}, false},
{{0, 12}, {0, 1}, false},
};
const Hat *hats;
size_t nhats;
size_t i;
switch (m->type) {
case MT_H:
hats = hats_H;
nhats = lenof(hats_H);
break;
case MT_T:
hats = hats_T;
nhats = lenof(hats_T);
break;
case MT_P:
hats = hats_P;
nhats = lenof(hats_P);
break;
default /* case MT_F */:
hats = hats_F;
nhats = lenof(hats_F);
break;
}
assert(nhats <= MT_MAXHAT);
Point orientation_r = left6(m->orientation);
for (i = 0; i < nhats; i++) {
Hat h = hats[i];
out[i].parent = m;
out[i].index = i;
out[i].start.x = (m->start.x + h.start.x * m->orientation.x +
h.start.y * orientation_r.x);
out[i].start.y = (m->start.y + h.start.x * m->orientation.y +
h.start.y * orientation_r.y);
out[i].orientation.x = (h.orientation.x * m->orientation.x +
h.orientation.y * orientation_r.x);
out[i].orientation.y = (h.orientation.x * m->orientation.y +
h.orientation.y * orientation_r.y);
out[i].reversed = h.reversed;
}
return nhats;
}
static size_t hat_vertices(Hat h, Point *out)
{
static const Point reference_hat[] = {
{0, 0}, {3, 0}, {2, 2}, {0, 3}, {-2, 4}, {-3, 3}, {-6, 6}, {-9, 6},
{-8, 4}, {-6, 3}, {-6, 0}, {-3, -3}, {-2, -2}, {0, -3},
};
size_t i;
Point orientation_r;
if (h.reversed)
orientation_r = right6(h.orientation);
else
orientation_r = left6(h.orientation);
assert(lenof(reference_hat) == HAT_NVERT);
for (i = 0; i < lenof(reference_hat); i++) {
Point v = reference_hat[h.reversed ? HAT_NVERT-1-i : i];
out[i].x = h.start.x + v.x * h.orientation.x + v.y * orientation_r.x;
out[i].y = h.start.y + v.x * h.orientation.y + v.y * orientation_r.y;
}
return lenof(reference_hat);
}
typedef struct BoundingBox {
Point bl, tr;
} BoundingBox;
static bool point_in_bbox(Point p, const BoundingBox *bbox)
{
int xl, xr, x;
if (!bbox)
return true;
/*
* Bounding boxes have vertical edges, not aligned with our basis
* vector r. So the 'true' x coordinate of (x,y) is proportional
* to 2x+y.
*/
if (p.y < bbox->bl.y || p.y > bbox->tr.y)
return false;
xl = 2*bbox->bl.x + bbox->bl.y;
xr = 2*bbox->tr.x + bbox->tr.y;
x = 2*p.x + p.y;
if (x < xl || x > xr)
return false;
return true;
}
static bool hat_in_bbox(Hat h, const BoundingBox *bbox)
{
Point p[HAT_NVERT];
size_t i, np;
if (!bbox)
return true;
np = hat_vertices(h, p);
for (i = 0; i < np; i++)
if (!point_in_bbox(p[i], bbox))
return false;
return true;
}
static Hat *metatile_set_to_hats(MetatileSet *s, size_t *nhats,
const BoundingBox *bbox)
{
Metatile *m;
size_t i, j, k, n;
Hat *h;
n = 0;
for (i = 0; (m = index234(s->tiles, i)) != NULL; i++) {
Hat htmp[MT_MAXHAT];
size_t nthis = metatile_hats(m, htmp);
for (k = 0; k < nthis; k++)
if (hat_in_bbox(htmp[k], bbox))
n++;
}
*nhats = n;
h = snewn(n, Hat);
j = 0;
for (i = 0; (m = index234(s->tiles, i)) != NULL; i++) {
Hat htmp[MT_MAXHAT];
size_t nthis = metatile_hats(m, htmp);
for (k = 0; k < nthis; k++) {
if (hat_in_bbox(htmp[k], bbox)) {
assert(j < n);
h[j++] = htmp[k]; /* structure copy */
}
}
}
assert(j == n);
return h;
}
#if 0
void hat_tiling_randomise(struct HatPatchParams *params, random_state *rs)
{
MetatileSet *s, *s2;
int x0, x1, y0, y1;
/*
* Iterate until we have a good-sized patch to select a rectangle
* from.
*/
s = metatile_initial_set(MT_P);
params->iterations = 0;
while (true) {
x0 = 2 * s->vertices[0].x + s->vertices[0].y;
x1 = 2 * s->vertices[1].x + s->vertices[1].y;
if (x1 < x0) {
int t = x1;
x1 = x0;
x0 = t;
}
y0 = s->vertices[0].y;
y1 = s->vertices[1].y;
if (y1 < y0) {
int t = y1;
y1 = y0;
y0 = t;
}
if (50*params->w <= x1-x0 && 50*params->h <= y1-y0)
break;
params->iterations++;
s2 = metatile_set_expand(s);
metatile_free_set(s);
s = s2;
}
/*
* Now select that rectangle.
*/
params->x = x0 + random_upto(rs, x1 - x0 - params->w + 1);
params->y = y0 + random_upto(rs, y1 - y0 - params->h + 1);
}
void hat_tiling_generate(struct HatPatchParams *params,
hat_tile_callback_fn cb, void *cbctx)
{
MetatileSet *s, *s2;
unsigned i;
size_t j, nh;
BoundingBox bbox;
Hat *hats;
s = metatile_initial_set(MT_P);
for (i = 0; i < params->iterations; i++) {
s2 = metatile_set_expand(s);
metatile_free_set(s);
s = s2;
}
bbox.bl.x = (params->x - params->y) / 2;
bbox.tr.x = ((params->x + params->w) - (params->y + params->h)) / 2;
bbox.bl.y = params->y;
bbox.tr.y = params->y + params->h;
hats = metatile_set_to_hats(s, &nh, &bbox);
for (j = 0; j < nh; j++) {
Point vertices[HAT_NVERT];
size_t nv = hat_vertices(hats[j], vertices);
int out[2 * HAT_NVERT];
size_t k;
for (k = 0; k < nv; k++) {
out[2*k] = 2 * vertices[k].x + vertices[k].y;
out[2*k+1] = vertices[k].y;
}
cb(cbctx, nv, out);
}
sfree(hats);
metatile_free_set(s);
}
#endif
#ifdef TEST_HAT
/*
* Assortment of test modes that output Postscript diagrams.
*/
static size_t hat_kite_centres(Hat h, Point *out)
{
static const Point reference_hat[] = {
{-7,5},{-5,4},{-5,1},{-4,-1},{-1,-1},{-2,1},{-1,2},{1,1},
};
size_t i;
Point orientation_r;
if (h.reversed)
orientation_r = right6(h.orientation);
else
orientation_r = left6(h.orientation);
assert(lenof(reference_hat) == HAT_NKITE);
for (i = 0; i < lenof(reference_hat); i++) {
Point v = reference_hat[i];
out[i].x = h.start.x + v.x * h.orientation.x + v.y * orientation_r.x;
out[i].y = h.start.y + v.x * h.orientation.y + v.y * orientation_r.y;
}
return lenof(reference_hat);
}
static inline int round6(int x)
{
int sign = x<0 ? -1 : +1;
x *= sign;
x += 3;
x /= 6;
x *= 6;
x *= sign;
return x;
}
static inline Point kite_left(Point k)
{
Point centre = { round6(k.x), round6(k.y) };
Point offset = { k.x - centre.x, k.y - centre.y };
offset = left6(offset);
Point r = { centre.x + offset.x, centre.y + offset.y };
return r;
}
static inline Point kite_right(Point k)
{
Point centre = { round6(k.x), round6(k.y) };
Point offset = { k.x - centre.x, k.y - centre.y };
offset = right6(offset);
Point r = { centre.x + offset.x, centre.y + offset.y };
return r;
}
static inline Point kite_forward_left(Point k)
{
Point centre = { round6(k.x), round6(k.y) };
Point offset = { k.x - centre.x, k.y - centre.y };
Point rotate = left6(offset);
Point r = { k.x + rotate.x + offset.x, k.y + rotate.y + offset.y };
return r;
}
static inline Point kite_forward_right(Point k)
{
Point centre = { round6(k.x), round6(k.y) };
Point offset = { k.x - centre.x, k.y - centre.y };
Point rotate = right6(offset);
Point r = { k.x + rotate.x + offset.x, k.y + rotate.y + offset.y };
return r;
}
typedef struct pspoint {
float x, y;
} pspoint;
static inline pspoint pscoords(Point p)
{
pspoint q = { p.x + p.y / 2.0F, p.y * sqrt(0.75) };
return q;
}
typedef struct psbbox {
bool started;
pspoint bl, tr;
} psbbox;
static inline void psbbox_add(psbbox *bbox, pspoint p)
{
if (!bbox->started || bbox->bl.x > p.x)
bbox->bl.x = p.x;
if (!bbox->started || bbox->tr.x < p.x)
bbox->tr.x = p.x;
if (!bbox->started || bbox->bl.y > p.y)
bbox->bl.y = p.y;
if (!bbox->started || bbox->tr.y < p.y)
bbox->tr.y = p.y;
bbox->started = true;
}
static void draw_metatiles_svg(const Metatile *tiles, size_t n,
const Point *bounds, bool coords)
{
size_t i, j;
psbbox bbox = { false };
for (i = 0; i < n; i++) {
Point vertices[MT_MAXVERT];
size_t nv = metatile_vertices(tiles[i], vertices, false);
for (j = 0; j < nv; j++)
psbbox_add(&bbox, pscoords(vertices[j]));
}
float ascale = 10, xscale = ascale, yscale = -ascale;
float border = 0.2 * ascale; /* leave room for strokes at the edges */
float ox = -xscale * bbox.bl.x + border;
float oy = -yscale * bbox.tr.y + border;
printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
printf("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" "
"width=\"%g\" height=\"%g\">\n",
ceil(ox + xscale * bbox.tr.x + 2*border),
ceil(oy + yscale * bbox.bl.y + 2*border));
for (i = 0; i < n; i++) {
Point vertices[MT_MAXVERT];
size_t nv = metatile_vertices(tiles[i], vertices, false);
pspoint pp[MT_MAXVERT];
for (j = 0; j < nv; j++) {
pp[j] = pscoords(vertices[j]);
pp[j].x = ox + xscale * pp[j].x;
pp[j].y = oy + yscale * pp[j].y;
}
printf("<path style=\""
"fill: none; "
"stroke: black; "
"stroke-width: %f; "
"stroke-linejoin: round; "
"stroke-linecap: round; "
"\" d=\"", 0.2 * ascale);
for (j = 0; j < nv; j++)
printf("%s %f %f \n", j ? "L" : "M", pp[j].x, pp[j].y);
printf("z\" />\n");
if (tiles[i].type != MT_F) {
/*
* Mark arrows on three of the metatile types (H, T, P),
* following the diagrams in the paper. (The metatile
* shapes other than F each have some symmetry, but their
* roles in the metatile substitution system are not
* similarly symmetric, so for diagnostic diagrams you
* want to mark their orientation.)
*/
pspoint lstart, lend;
pspoint astart, aend, aforward, aleft;
double d;
/*
* Determine endpoints of a line crossing the polygon in
* the appropriate direction, by case analysis on the
* individual tile types.
*/
switch (tiles[i].type) {
case MT_H:
lstart.x = (pp[4].x + pp[5].x) / 2;
lstart.y = (pp[4].y + pp[5].y) / 2;
lend.x = (pp[1].x + pp[2].x) / 2;
lend.y = (pp[1].y + pp[2].y) / 2;
break;
case MT_T:
lstart = pp[0];
lend.x = (pp[1].x + pp[2].x) / 2;
lend.y = (pp[1].y + pp[2].y) / 2;
break;
default /* case MT_P */:
lstart.x = (5*pp[3].x + 3*pp[0].x) / 8;
lstart.y = (5*pp[3].y + 3*pp[0].y) / 8;
lend.x = (5*pp[1].x + 3*pp[2].x) / 8;
lend.y = (5*pp[1].y + 3*pp[2].y) / 8;
break;
}
/*
* Now shorten that line a little and give it an arrowhead.
*/
astart.x = (4 * lstart.x + lend.x) / 5;
astart.y = (4 * lstart.y + lend.y) / 5;
aend.x = (lstart.x + 4 * lend.x) / 5;
aend.y = (lstart.y + 4 * lend.y) / 5;
aforward.x = aend.x - astart.x;
aforward.y = aend.y - astart.y;
d = sqrt(aforward.x*aforward.x + aforward.y*aforward.y);
aforward.x /= d;
aforward.y /= d;
aleft.x = -aforward.y;
aleft.y = +aforward.x;
printf("<path style=\""
"fill: none; "
"stroke: black; "
"stroke-width: %f; "
"stroke-opacity: 0.2; "
"stroke-linejoin: round; "
"stroke-linecap: round; "
"\" d=\"", 0.9 * ascale);
printf("M %f %f L %f %f ", astart.x, astart.y,
aend.x, aend.y);
printf("L %f %f ",
aend.x - 1.2 * ascale * (aforward.x + aleft.x),
aend.y - 1.2 * ascale * (aforward.y + aleft.y));
printf("M %f %f L %f %f ", aend.x, aend.y,
aend.x - 1.2 * ascale * (aforward.x - aleft.x),
aend.y - 1.2 * ascale * (aforward.y - aleft.y));
printf("\" />\n");
}
if (coords) {
/*
* Print each tile's coordinates.
*/
pspoint centre;
size_t j;
switch (tiles[i].type) {
case MT_H:
centre.x = (pp[0].x + pp[2].x + pp[4].x) / 3;
centre.y = (pp[0].y + pp[2].y + pp[4].y) / 3;
break;
case MT_T:
centre.x = (pp[0].x + pp[1].x + pp[2].x) / 3;
centre.y = (pp[0].y + pp[1].y + pp[2].y) / 3;
break;
case MT_P:
centre.x = (pp[0].x + pp[2].x) / 2;
centre.y = (pp[0].y + pp[2].y) / 2;
break;
default /* case MT_F */:
centre.x = (pp[2].x + pp[4].x) / 2;
centre.y = (pp[2].y + pp[4].y) / 2;
break;
}
double lineheight = ascale * 1.5;
double charheight = lineheight * 0.6; /* close enough */
double allheight = lineheight * (tiles[i].ncoords-1) + charheight;
for (j = 0; j < tiles[i].ncoords; j++) {
const Metatile *it;
unsigned cindex;
printf("<text style=\""
"fill: black; "
"font-family: Sans; "
"font-size: %g; "
"text-anchor: middle; "
"text-align: center; "
"\" x=\"%g\" y=\"%g\">", lineheight,
centre.x,
centre.y - allheight/2 + charheight + lineheight*j);
it = &tiles[i];
cindex = j;
while (cindex < it->ncoords) {
if (it != &tiles[i])
printf(".");
printf("%d", (int)it->coords[cindex].index);
it = it->coords[cindex].parent;
cindex = 0; /* BODGING AHOY */
}
printf("</text>\n");
}
}
}
printf("</svg>\n");
}
static void draw_metatile_set_svg(
MetatileSet *tiles, const Point *bounds, bool coords)
{
/*
* Slurp the tree234 of tiles into an array for display.
* Tedious, but this test code doesn't have to be particularly
* efficient.
*/
size_t nt = count234(tiles->tiles);
Metatile *t = snewn(nt, Metatile);
size_t i;
for (i = 0; i < nt; i++) {
Metatile *m = index234(tiles->tiles, i);
t[i] = *m; /* structure copy */
}
draw_metatiles_svg(t, nt, bounds, coords);
sfree(t);
}
static void draw_hats_svg(const Hat *hats, size_t n,
const Point *bounds, bool kites, char coordtype)
{
size_t i, j;
psbbox bbox = { false };
for (i = 0; i < n; i++) {
Point vertices[HAT_NVERT];
size_t nv = hat_vertices(hats[i], vertices);
for (j = 0; j < nv; j++)
psbbox_add(&bbox, pscoords(vertices[j]));
}
float ascale = (coordtype == 'k' || coordtype == 'K' ? 20 : 10);
float xscale = ascale, yscale = -ascale;
float border = 0.2 * ascale; /* leave room for strokes at the edges */
float ox = -xscale * bbox.bl.x + border;
float oy = -yscale * bbox.tr.y + border;
printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
printf("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" "
"width=\"%g\" height=\"%g\">\n",
ceil(ox + xscale * bbox.tr.x + 2*border),
ceil(oy + yscale * bbox.bl.y + 2*border));
for (i = 0; i < n; i++) {
Point vertices[HAT_NVERT];
pspoint psvs[HAT_NVERT];
size_t nv = hat_vertices(hats[i], vertices);
int is = hats[i].reversed ? -1 : +1;
int io = hats[i].reversed ? 13 : 0;
printf("<path style=\""
"fill: %s; "
"stroke: black; "
"stroke-width: %f; "
"stroke-linejoin: round; "
"stroke-linecap: round; "
"\" d=\"",
hats[i].reversed ? "rgba(0,0,0,0.2)" : "none",
0.2 * ascale);
for (j = 0; j < nv; j++) {
psvs[j] = pscoords(vertices[j]);
printf("%s %f %f\n", j ? "L" : "M",
ox + xscale * psvs[j].x, oy + yscale * psvs[j].y);
}
printf("z\" />\n");
if (kites) {
/*
* Draw internal lines within each hat dividing it into
* kites. This is done in a rather bodgy way, sorry.
*/
const char *fmt = "<path style=\""
"fill: none; "
"stroke: rgba(0,0,0,0.2); "
"stroke-width: %f; "
"stroke-linejoin: round; "
"stroke-linecap: round; "
"\" d=\"M %f %f L %f %f\" />\n";
float strokewidth = 0.1 * ascale;
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*0].x,
oy + yscale * psvs[io+is*0].y,
ox + xscale * psvs[io+is*3].x,
oy + yscale * psvs[io+is*3].y);
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*0].x,
oy + yscale * psvs[io+is*0].y,
ox + xscale * psvs[io+is*5].x,
oy + yscale * psvs[io+is*5].y);
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*6].x,
oy + yscale * psvs[io+is*6].y,
ox + xscale * psvs[io+is*9].x,
oy + yscale * psvs[io+is*9].y);
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*0].x,
oy + yscale * psvs[io+is*0].y,
ox + xscale * psvs[io+is*10].x,
oy + yscale * psvs[io+is*10].y);
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*9].x,
oy + yscale * psvs[io+is*9].y,
ox + xscale * (psvs[io+is*6].x + psvs[io+is*12].x) / 2,
oy + yscale * (psvs[io+is*6].y + psvs[io+is*12].y) / 2);
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*5].x,
oy + yscale * psvs[io+is*5].y,
ox + xscale * (psvs[io+is*6].x + psvs[io+is*12].x) / 2,
oy + yscale * (psvs[io+is*6].y + psvs[io+is*12].y) / 2);
printf(fmt, strokewidth,
ox + xscale * psvs[io+is*12].x,
oy + yscale * psvs[io+is*12].y,
ox + xscale * (psvs[io+is*6].x + psvs[io+is*12].x) / 2,
oy + yscale * (psvs[io+is*6].y + psvs[io+is*12].y) / 2);
}
if (coordtype == 'h') {
double lineheight = ascale * 2;
double charheight = lineheight * 0.6; /* close enough */
printf("<text style=\""
"fill: black; "
"font-family: Sans; "
"font-size: %gpx; "
"text-anchor: middle; "
"text-align: center; "
"\" x=\"%g\" y=\"%g\">", lineheight,
ox + xscale * (psvs[io+is*0].x + psvs[io+is*10].x) / 2,
oy + yscale * (psvs[io+is*0].y + psvs[io+is*10].y) / 2
+ charheight/2);
printf("%d", (int)i);
printf("</text>\n");
} else if (coordtype == 'k') {
Point kites[HAT_NKITE];
size_t nk = hat_kite_centres(hats[i], kites);
double lineheight = ascale * 0.5;
double charheight = lineheight * 0.6; /* close enough */
for (j = 0; j < nk; j++) {
pspoint p = pscoords(kites[j]);
printf("<text style=\""
"fill: black; "
"font-family: Sans; "
"font-size: %gpx; "
"text-anchor: middle; "
"text-align: center; "
"\" x=\"%g\" y=\"%g\">", lineheight,
ox + xscale * p.x,
oy + yscale * p.y + charheight/2);
printf("%d.%d.%d", (int)j, hats[i].index,
hats[i].parent->coords[0].index);
printf("</text>\n");
}
} else if (coordtype == 'K') {
Point kites[HAT_NKITE];
size_t nk = hat_kite_centres(hats[i], kites);
double lineheight = ascale * 1.1;
double charheight = lineheight * 0.6; /* close enough */
for (j = 0; j < nk; j++) {
pspoint p = pscoords(kites[j]);
printf("<text style=\""
"fill: black; "
"font-family: Sans; "
"font-size: %gpx; "
"text-anchor: middle; "
"text-align: center; "
"\" x=\"%g\" y=\"%g\">", lineheight,
ox + xscale * p.x,
oy + yscale * p.y + charheight/2);
printf("%d", (int)j);
printf("</text>\n");
}
}
}
printf("</svg>\n");
}
typedef enum KiteStep { KS_LEFT, KS_RIGHT, KS_F_LEFT, KS_F_RIGHT } KiteStep;
static inline Point kite_step(Point k, KiteStep step)
{
switch (step) {
case KS_LEFT: return kite_left(k);
case KS_RIGHT: return kite_right(k);
case KS_F_LEFT: return kite_forward_left(k);
default /* case KS_F_RIGHT */: return kite_forward_right(k);
}
}
int main(int argc, char **argv)
{
if (argc <= 1) {
printf("usage: hat-test <mode>\n");
printf("modes: H,T,P,F display a single unexpanded tile\n");
printf(" xH,xT,xP,xF display the expansion of one tile\n");
printf(" cH,cT,cP,cF display expansion with tile coords\n");
printf(" CH,CT,CP,CF display double expansion with coords\n");
printf(" hH,hT,hP,hF display the hats from one tile\n");
printf(" HH,HT,HP,HF hats from an expansion, with coords\n");
printf(" m1, m2, ... nth expansion of one H metatile\n");
printf(" M1, M2, ... nth expansion turned into real hats\n");
printf(" --hat show the kites in a single hat\n");
printf(" --tables generate hat-tables.h for hat.c\n");
return 0;
}
if (!strcmp(argv[1], "H") || !strcmp(argv[1], "T") ||
!strcmp(argv[1], "P") || !strcmp(argv[1], "F")) {
MetatileType type = (argv[1][0] == 'H' ? MT_H :
argv[1][0] == 'T' ? MT_T :
argv[1][0] == 'P' ? MT_P : MT_F);
Metatile m = {type, {0, 0}, {1, 0}};
draw_metatiles_svg(&m, 1, NULL, false);
return 0;
}
if (!strcmp(argv[1], "xH") || !strcmp(argv[1], "xT") ||
!strcmp(argv[1], "xP") || !strcmp(argv[1], "xF")) {
MetatileType type = (argv[1][1] == 'H' ? MT_H :
argv[1][1] == 'T' ? MT_T :
argv[1][1] == 'P' ? MT_P : MT_F);
Metatile m = {type, {0, 0}, {1, 0}};
Metatile t[MT_MAXEXPAND];
size_t nt = metatile_expand(m, t);
draw_metatiles_svg(t, nt, NULL, false);
return 0;
}
if (argv[1][0] && argv[1][1] &&
strchr("cC", argv[1][0]) && strchr("HTPF", argv[1][1])) {
MetatileType type = (argv[1][1] == 'H' ? MT_H :
argv[1][1] == 'T' ? MT_T :
argv[1][1] == 'P' ? MT_P : MT_F);
MetatileSet *t[3];
int nsets = (argv[1][0] == 'c' ? 2 : 3);
int i;
t[0] = metatile_initial_set(type);
for (i = 1; i < nsets; i++)
t[i] = metatile_set_expand(t[i-1]);
draw_metatile_set_svg(t[nsets-1], NULL, true);
for (i = 0; i < nsets; i++)
metatile_free_set(t[i]);
return 0;
}
if (!strcmp(argv[1], "hH") || !strcmp(argv[1], "hT") ||
!strcmp(argv[1], "hP") || !strcmp(argv[1], "hF")) {
MetatileType type = (
!strcmp(argv[1], "hH") ? MT_H :
!strcmp(argv[1], "hT") ? MT_T :
!strcmp(argv[1], "hP") ? MT_P : MT_F);
Metatile m = {type, {0, 0}, {1, 0}};
Hat h[MT_MAXHAT];
size_t nh = metatile_hats(&m, h);
draw_hats_svg(h, nh, NULL, false, 'h');
return 0;
}
if (!strcmp(argv[1], "--hat")) {
Hat h = { { 0, 0 }, { 1, 0 }, false, NULL, 0 };
draw_hats_svg(&h, 1, NULL, true, 'K');
return 0;
}
if (argv[1][0] == 'H' && argv[1][1] && strchr("HTPF", argv[1][1])) {
MetatileType type = (argv[1][1] == 'H' ? MT_H :
argv[1][1] == 'T' ? MT_T :
argv[1][1] == 'P' ? MT_P : MT_F);
MetatileSet *t[2];
size_t i, nh;
t[0] = metatile_initial_set(type);
t[1] = metatile_set_expand(t[0]);
Hat *h = metatile_set_to_hats(t[1], &nh, NULL);
draw_hats_svg(h, nh, NULL, true, 'k');
sfree(h);
for (i = 0; i < 2; i++)
metatile_free_set(t[i]);
return 0;
}
if (argv[1][0] == 'm' || argv[1][0] == 'M') {
int niter = atoi(argv[1] + 1);
MetatileSet *tiles = metatile_initial_set(MT_P);
while (niter-- > 0) {
MetatileSet *t2 = metatile_set_expand(tiles);
metatile_free_set(tiles);
tiles = t2;
}
if (argv[1][0] == 'M') {
size_t nh;
Hat *h = metatile_set_to_hats(tiles, &nh, NULL);
draw_hats_svg(h, nh, tiles->vertices, false, 0);
sfree(h);
} else {
draw_metatile_set_svg(tiles, tiles->vertices, false);
}
metatile_free_set(tiles);
return 0;
}
if (!strcmp(argv[1], "--tables")) {
size_t i, j, k;
printf("/*\n"
" * Header file autogenerated by aux/hatgen.c\n"
" *\n"
" * To regenerate, run 'hatgen --tables > hat-tables.h'\n"
" */\n\n");
static const char HTPF[] = "HTPF";
printf("static const unsigned hats_in_metatile[] = {");
for (i = 0; i < 4; i++) {
Metatile m = {i, {0, 0}, {1, 0}};
Hat h[MT_MAXHAT];
size_t nh = metatile_hats(&m, h);
printf(" %zu,", nh);
}
printf(" };\n\n");
{
struct Parent {
MetatileType t;
unsigned index;
} parents[4][4*MT_MAXEXPAND];
size_t psizes[4] = {0, 0, 0, 0};
size_t csizes[4] = {0, 0, 0, 0};
for (i = 0; i < 4; i++) {
Metatile m = {i, {0, 0}, {1, 0}};
Metatile t[MT_MAXEXPAND];
size_t nt = metatile_expand(m, t);
printf("static const TileType children_%c[] = {\n"
" ", HTPF[i]);
for (j = 0; j < nt; j++) {
MetatileType c = t[j].type;
parents[c][psizes[c]].t = i;
parents[c][psizes[c]].index = j;
psizes[c]++;
csizes[i]++;
printf(" TT_%c,", HTPF[c]);
}
printf("\n};\n");
}
printf("static const TileType *const children[] = {\n");
for (i = 0; i < 4; i++)
printf(" children_%c,\n", HTPF[i]);
printf("};\n");
printf("static const size_t nchildren[] = {\n");
for (i = 0; i < 4; i++)
printf(" %u,\n", (unsigned)csizes[i]);
printf("};\n\n");
for (i = 0; i < 4; i++) {
printf("static const MetatilePossibleParent "
"permitted_parents_%c[] = {\n", HTPF[i]);
for (j = 0; j < psizes[i]; j++)
printf(" { TT_%c, %u },\n", HTPF[parents[i][j].t],
parents[i][j].index);
printf("};\n");
}
printf("static const MetatilePossibleParent *const "
"permitted_parents[] = {\n");
for (i = 0; i < 4; i++)
printf(" permitted_parents_%c,\n", HTPF[i]);
printf("};\n");
printf("static const size_t n_permitted_parents[] = {\n");
for (i = 0; i < 4; i++)
printf(" %u,\n", (unsigned)psizes[i]);
printf("};\n\n");
}
{
for (i = 0; i < 4; i++) {
MetatileSet *t[2];
size_t j, k, nh, nmeta, ti;
struct list {
Point kite;
unsigned ik, ih, im;
} list[8*MT_MAXHAT*MT_MAXEXPAND];
size_t len = 0;
t[0] = metatile_initial_set(i);
t[1] = metatile_set_expand(t[0]);
Hat *h = metatile_set_to_hats(t[1], &nh, NULL);
printf("static const KitemapEntry kitemap_%c[] = {\n",
HTPF[i]);
Point origin = h[0].start;
for (j = 0; j < nh; j++) {
Point kites[HAT_NKITE];
size_t nk = hat_kite_centres(h[j], kites);
for (k = 0; k < nk; k++) {
struct list *le = &list[len++];
le->kite.x = kites[k].x - origin.x;
le->kite.y = kites[k].y - origin.y;
le->ik = k;
le->ih = h[j].index;
le->im = h[j].parent->coords[0].index;
#if 0
printf("// %d,%d = %u.%u.%u\n", le->kite.x, le->kite.y, le->ik, le->ih, le->im);
#endif
}
}
nmeta = count234(t[1]->tiles);
for (ti = 0; ti < 8 * 4 * nmeta; ti++) {
unsigned ik = ti % 8;
unsigned ih = ti / 8 % 4;
unsigned im = ti / (8*4);
struct list *src = NULL, *dst = NULL;
int istep;
for (j = 0; j < len; j++) {
struct list *tmp = &list[j];
if (tmp->ik == ik && tmp->ih == ih && tmp->im == im) {
src = tmp;
break;
}
}
if (ik == 0) {
printf(" /* hat #%u in metatile #%u (type %c)",
ih, im, HTPF[((Metatile *)index234(
t[1]->tiles, im))->type]);
if (!src)
printf(" does not exist");
printf(" */\n");
}
#if 0
if (src)
printf(" // src=%d,%d\n", src->kite.x, src->kite.y);
#endif
printf(" ");
for (istep = 0; istep < 4; istep++) {
KiteStep step = istep;
dst = NULL;
if (src) {
Point pdst = kite_step(src->kite, step);
#if 0
printf(" /* dst=%d,%d */", pdst.x, pdst.y);
#endif
for (k = 0; k < len; k++) {
struct list *tmp = &list[k];
if (tmp->kite.x == pdst.x &&
tmp->kite.y == pdst.y) {
dst = tmp;
break;
}
}
}
if (!dst) {
printf(" {-1,-1,-1},");
} else {
printf(" {%u,%u,%u},", dst->ik, dst->ih, dst->im);
}
}
printf("\n");
}
printf("};\n");
sfree(h);
for (j = 0; j < 2; j++)
metatile_free_set(t[j]);
}
printf("static const KitemapEntry *const "
"kitemap[] = {\n");
for (i = 0; i < 4; i++)
printf(" kitemap_%c,\n", HTPF[i]);
printf("};\n\n");
}
{
for (i = 0; i < 4; i++) {
MetatileSet *t[3];
Metatile *m;
int map[MT_MAXEXPAND * MT_MAXEXPAND];
size_t maplen;
for (j = 0; j < lenof(map); j++)
map[j] = -1;
t[0] = metatile_initial_set(i);
for (j = 1; j < 3; j++)
t[j] = metatile_set_expand(t[j-1]);
for (j = 0; (m = index234(t[2]->tiles, j)) != NULL; j++) {
unsigned coords[4];
size_t ncoords = 0;
int cindex;
#if 0
printf("// ***\n");
#endif
for (cindex = 0; cindex < m->ncoords; cindex++) {
#if 0
printf("// %d.%d\n", (int)m->coords[cindex].index,
(int)m->coords[cindex].parent->coords[0].index);
#endif
coords[ncoords++] = (
m->coords[cindex].index + MT_MAXEXPAND *
m->coords[cindex].parent->coords[0].index);
}
unsigned prev = ncoords-1;
for (k = 0; k < ncoords; k++) {
map[coords[prev]] = coords[k];
prev = k;
}
}
printf("static const MetamapEntry metamap_%c[] = {\n",
HTPF[i]);
maplen = MT_MAXEXPAND * count234(t[1]->tiles);
for (j = 0; j < maplen; j++) {
printf(" /* %u, %u -> */ ",
(unsigned)(j % MT_MAXEXPAND),
(unsigned)(j / MT_MAXEXPAND));
if (map[j] == -1) {
printf("{-1,-1}, /* does not exist */\n");
} else {
printf("{%u, %u},",
(unsigned)(map[j] % MT_MAXEXPAND),
(unsigned)(map[j] / MT_MAXEXPAND));
if (map[j] == j)
printf(" /* no alternatives */");
printf("\n");
}
}
printf("};\n");
for (j = 0; j < 3; j++)
metatile_free_set(t[j]);
}
printf("static const MetamapEntry *const "
"metamap[] = {\n");
for (i = 0; i < 4; i++)
printf(" metamap_%c,\n", HTPF[i]);
printf("};\n");
}
return 0;
}
fprintf(stderr, "unknown test mode '%s'\n", argv[1]);
return 1;
}
#endif