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

C89 provided only double-precision mathematical functions (sin() etc), and so despite using single-precision elsewhere, those are what Puzzles has traditionally used. C99 introduced single-precision equivalents (sinf() etc), and I hope it's been long enough that we can safely use them. Maybe they'll even be faster. Rather than directly use the single-precision functions, though, we use the magic macros from <tgmath.h> that automatically choose the precision of mathematical functions based on their arguments. This has the advantage that we only need to change which header we include, and thus that we can switch back again if some platform has trouble with the new header.
1688 lines
53 KiB
C
1688 lines
53 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 <tgmath.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 auxiliary/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");
|
|
|
|
{
|
|
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;
|
|
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++) {
|
|
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
|