Edit

IABSD.fr/xenocara/app/xlockmore/modes/penrose.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2006-11-26 11:07:42
    Hash : 110b2a92
    Message : Importing xlockmore 5.22

  • app/xlockmore/modes/penrose.c
  • /* -*- Mode: C; tab-width: 4 -*- */
    /* penrose --- quasiperiodic tilings */
    
    #if !defined( lint ) && !defined( SABER )
    static const char sccsid[] = "@(#)penrose.c	5.09 2003/07/08 xlockmore";
    
    #endif
    
    /*-
     * Copyright (c) 1996 by Timo Korvola <tkorvola@dopey.hut.fi>
     *
     * Permission to use, copy, modify, and distribute this software and its
     * documentation for any purpose and without fee is hereby granted,
     * provided that the above copyright notice appear in all copies and that
     * both that copyright notice and this permission notice appear in
     * supporting documentation.
     *
     * This file is provided AS IS with no warranties of any kind.  The author
     * shall have no liability with respect to the infringement of copyrights,
     * trade secrets or any patents by this file or any part thereof.  In no
     * event will the author be liable for any lost revenue or profits or
     * other special, indirect and consequential damages.
     *
     * Revision History:
     * 08-Jul-2003: Mono made a little more interesting.
     * 01-Nov-2000: Allocation checks
     * 10-May-1997: Jamie Zawinski <jwz@jwz.org> compatible with xscreensaver
     * 09-Sep-1996: Written.
     */
    
    /*-
    Be careful, this probably still has a few bugs (many of which may only
    appear with a very low probability).  These are seen with -verbose .
    If one of these are hit penrose will reinitialize.
    */
    
    /*-
     * See Onoda, Steinhardt, DiVincenzo and Socolar in
     * Phys. Rev. Lett. 60, #25, 1988 or
     * Strandburg in Computers in Physics, Sep/Oct 1991.
     *
     * This implementation uses the simpler version of the growth
     * algorithm, i.e., if there are no forced vertices, a randomly chosen
     * tile is added to a randomly chosen vertex (no preference for those
     * 108 degree angles).
     *
     * There are two essential differences to the algorithm presented in
     * the literature: First, we do not allow the tiling to enclose an
     * untiled area.  Whenever this is in danger of happening, we just
     * do not add the tile, hoping for a better random choice the next
     * time.  Second, when choosing a vertex randomly, we will take
     * one that lies within the viewport if available.  If this seems to
     * cause enclosures in the forced rule case, we will allow invisible
     * vertices to be chosen.
     *
     * Tiling is restarted whenever one of the following happens: there
     * are no incomplete vertices within the viewport or the tiling has
     * extended a window's length beyond the edge of the window
     * horizontally or vertically or forced rule choice has failed 100
     * times due to areas about to become enclosed.
     *
     * Introductory info:
     * Science News March 23 1985 Vol 127, No. 12
     * Science News July 16 1988 Vol 134, No. 3
     * The Economist Sept 17 1988 pg. 100
     *
     */
    
    #ifdef STANDALONE
    #define MODE_penrose
    #define PROGCLASS "Penrose"
    #define HACK_INIT init_penrose
    #define HACK_DRAW draw_penrose
    #define penrose_opts xlockmore_opts
    #define DEFAULTS "*delay: 10000 \n" \
     "*size: 40 \n" \
     "*ncolors: 64 \n" \
     "*fullrandom: True \n" \
     "*verbose: False \n"
    #include "xlockmore.h"		/* from the xscreensaver distribution */
    #else /* !STANDALONE */
    #include "xlock.h"		/* from the xlockmore distribution */
    #endif /* !STANDALONE */
    
    #ifdef MODE_penrose
    
    #define DEF_AMMANN  "False"
    
    static Bool ammann;
    
    static XrmOptionDescRec opts[] =
    {
    	{(char *) "-ammann", (char *) ".penrose.ammann", XrmoptionNoArg, (caddr_t) "on"},
    	{(char *) "+ammann", (char *) ".penrose.ammann", XrmoptionNoArg, (caddr_t) "off"}
    };
    static argtype vars[] =
    {
    	{(void *) & ammann, (char *) "ammann", (char *) "Ammann", (char *) DEF_AMMANN, t_Bool}
    };
    static OptionStruct desc[] =
    {
    	{(char *) "-/+ammann", (char *) "turn on/off Ammann lines"}
    };
    
    ModeSpecOpt penrose_opts =
    {sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};
    
    #ifdef USE_MODULES
    ModStruct   penrose_description =
    {"penrose", "init_penrose", "draw_penrose", "release_penrose",
     "init_penrose", "init_penrose", (char *) NULL, &penrose_opts,
     10000, 1, 1, -40, 64, 1.0, "",
     "Shows Penrose's quasiperiodic tilings", 0, NULL};
    
    #endif
    
    /*-
     * Annoyingly the ANSI C library people have reserved all identifiers
     * ending with _t for future use.  Hence we use _c as a suffix for
     * typedefs (c for class, although this is not C++).
     */
    
    #define MINSIZE 5
    
    /*-
     * In theory one could fit 10 tiles to a single vertex.  However, the
     * vertex rules only allow at most seven tiles to meet at a vertex.
     */
    
    #define CELEBRATE 31415		/* This causes a pause, an error occurred. */
    #define COMPLETION 3141		/* This causes a pause, tiles filled up screen. */
    
    #define MAX_TILES_PER_VERTEX 7
    #define N_VERTEX_RULES 8
    #define ALLOC_NODE(type) (type *)malloc(sizeof (type))
    
    /*-
     * These are used to specify directions.  They can also be used in bit
     * masks to specify a combination of directions.
     */
    #define S_LEFT 1
    #define S_RIGHT 2
    
    
    /*-
     * We do not actually maintain objects corresponding to the tiles since
     * we do not really need them and they would only consume memory and
     * cause additional bookkeeping.  Instead we only have vertices, and
     * each vertex lists the type of each adjacent tile as well as the
     * position of the vertex on the tile (hereafter refered to as
     * "corner").  These positions are numbered in counterclockwise order
     * so that 0 is where two double arrows meet (see one of the
     * articles).  The tile type and vertex number are stored in a single
     * integer (we use char, and even most of it remains unused).
     *
     * The primary use of tile objects would be draw traversal, but we do
     * not currently do redraws at all (we just start over).
     */
    #define VT_CORNER_MASK 0x3
    #define VT_TYPE_MASK 0x4
    #define VT_THIN 0
    #define VT_THICK 0x4
    #define VT_BITS 3
    #define VT_TOTAL_MASK 0x7
    
    typedef unsigned char vertex_type_c;
    
    /*-
     * These allow one to compute the types of the other corners of the tile.  If
     * you are standing at a vertex of type vt looking towards the middle of the
     * tile, VT_LEFT( vt) is the vertex on your left etc.
     */
    #define VT_LEFT( vt) ((((vt) - 1) & VT_CORNER_MASK) | (((vt) & VT_TYPE_MASK)))
    #define VT_RIGHT( vt) ((((vt) + 1) & VT_CORNER_MASK) | (((vt) & VT_TYPE_MASK)))
    #define VT_FAR( vt) ((vt) ^ 2)
    
    
    /*-
     * Since we do not do redraws, we only store the vertices we need.  These are
     * the ones with still some empty space around them for the growth algorithm
     * to fill.
     *
     * Here we use a doubly chained ring-like structure as vertices often need
     * to be removed or inserted (they are kept in geometrical order
     * circling the tiled area counterclockwise).  The ring is refered to by
     * a pointer to one more or less random node.  When deleting nodes one
     * must make sure that this pointer continues to refer to a valid
     * node.  A vertex count is maintained to make it easier to pick
     * vertices randomly.
     */
    typedef struct forced_node forced_node_c;
    
    typedef struct fringe_node {
    	struct fringe_node *prev;
    	struct fringe_node *next;
    	/* These are numbered counterclockwise.  The gap, if any, lies
    	   between the last and first tiles.  */
    	vertex_type_c tiles[MAX_TILES_PER_VERTEX];
    	int         n_tiles;
    	/* A bit mask used to indicate vertex rules that are still applicable for
    	   completing this vertex.  Initialize this to (1 << N_VERTEX_RULES) - 1,
    	   i.e., all ones, and the rule matching functions will automatically mask
    	   out rules that no longer match. */
    	unsigned char rule_mask;
    	/* If the vertex is on the forced vertex list, this points to the
    	   pointer to the appropriate node in the list.  To remove the
    	   vertex from the list just set *list_ptr to the next node,
    	   deallocate and decrement node count. */
    	struct forced_node **list_ptr;
    	/* Screen coordinates. */
    	XPoint      loc;
    	/* We also keep track of 5D coordinates to avoid rounding errors.
    	   These are in units of edge length. */
    	int         fived[5];
    	/* This is used to quickly check if a vertex is visible. */
    	unsigned char off_screen;
    } fringe_node_c;
    
    typedef struct {
    	fringe_node_c *nodes;
    	/* This does not count off-screen nodes. */
    	int         n_nodes;
    } fringe_c;
    
    
    /*-
     * The forced vertex pool contains vertices where at least one
     * side of the tiled region can only be extended in one way.  Note
     * that this does not necessarily mean that there would only be one
     * applicable rule.  forced_sides are specified using S_LEFT and
     * S_RIGHT as if looking at the untiled region from the vertex.
     */
    struct forced_node {
    	fringe_node_c *vertex;
    	unsigned    forced_sides;
    	struct forced_node *next;
    };
    
    typedef struct {
    	forced_node_c *first;
    	int         n_nodes, n_visible;
    } forced_pool_c;
    
    
    /* This is the data related to the tiling of one screen. */
    typedef struct {
    	int         width, height;
    	XPoint      origin;
    	int         edge_length;
    	fringe_c    fringe;
    	forced_pool_c forced;
    	int         done, failures;
    	unsigned long thick_color, thin_color;
    	int         busyLoop;
    	Bool        ammann;
    } tiling_c;
    
    static tiling_c *tilings = (tiling_c *) NULL;
    
    /* The tiles are listed in counterclockwise order. */
    typedef struct {
    	vertex_type_c tiles[MAX_TILES_PER_VERTEX];
    	int         n_tiles;
    } vertex_rule_c;
    
    static vertex_rule_c vertex_rules[N_VERTEX_RULES] =
    {
    	{
      {VT_THICK | 2, VT_THICK | 2, VT_THICK | 2, VT_THICK | 2, VT_THICK | 2}, 5},
    	{
      {VT_THICK | 0, VT_THICK | 0, VT_THICK | 0, VT_THICK | 0, VT_THICK | 0}, 5},
    	{
    		{VT_THICK | 0, VT_THICK | 0, VT_THICK | 0, VT_THIN | 0}, 4},
    	{
    	 {VT_THICK | 2, VT_THICK | 2, VT_THIN | 1, VT_THIN | 3, VT_THICK | 2,
    	  VT_THIN | 1, VT_THIN | 3}, 7},
    	{
    		{VT_THICK | 2, VT_THICK | 2, VT_THICK | 2, VT_THICK | 2,
    		 VT_THIN | 1, VT_THIN | 3}, 6},
    	{
    		{VT_THICK | 1, VT_THICK | 3, VT_THIN | 2}, 3},
    	{
    		{VT_THICK | 0, VT_THIN | 0, VT_THIN | 0}, 3},
    	{
         {VT_THICK | 2, VT_THIN | 1, VT_THICK | 3, VT_THICK | 1, VT_THIN | 3}, 5}
    };
    
    
    /* Match information returned by match_rules. */
    typedef struct {
    	int         rule;
    	int         pos;
    } rule_match_c;
    
    
    /* Occasionally floating point coordinates are needed. */
    typedef struct {
    	float       x, y;
    } fcoord_c;
    
    
    /* All angles are measured in multiples of 36 degrees. */
    typedef int angle_c;
    
    static angle_c vtype_angles[] =
    {4, 1, 4, 1, 2, 3, 2, 3};
    
    #define vtype_angle( v) (vtype_angles[ v])
    
    
    /* Direction angle of an edge. */
    static      angle_c
    vertex_dir(ModeInfo * mi, fringe_node_c * vertex, unsigned side)
    {
    	tiling_c   *tp = &tilings[MI_SCREEN(mi)];
    	fringe_node_c *v2 =
    	(side == S_LEFT ? vertex->next : vertex->prev);
    	register int i;
    
    	for (i = 0; i < 5; i++)
    		switch (v2->fived[i] - vertex->fived[i]) {
    			case 1:
    				return 2 * i;
    			case -1:
    				return (2 * i + 5) % 10;
    		}
    	tp->done = True;
    	if (MI_IS_VERBOSE(mi)) {
    		(void) fprintf(stderr,
    		       "Weirdness in vertex_dir (this has been reported)\n");
    		for (i = 0; i < 5; i++)
    			(void) fprintf(stderr, "v2->fived[%d]=%d, vertex->fived[%d]=%d\n",
    				       i, v2->fived[i], i, vertex->fived[i]);
    	}
    	tp->busyLoop = CELEBRATE;
    	return 0;
    }
    
    
    /* Move one step to a given direction. */
    static void
    add_unit_vec(angle_c dir, int *fived)
    {
    	static int  dir2i[] =
    	{0, 3, 1, 4, 2};
    
    	while (dir < 0)
    		dir += 10;
    	fived[dir2i[dir % 5]] += (dir % 2 ? -1 : 1);
    }
    
    
    /* For comparing coordinates. */
    #define fived_equal( f1, f2) (!memcmp( (f1), (f2), 5 * sizeof( int)))
    
    
    /*-
     * This computes screen coordinates from 5D representation.  Note that X
     * uses left-handed coordinates (y increases downwards).
     */
    static void
    fived_to_loc(int fived[], tiling_c * tp, XPoint *pt)
    {
    	static fcoord_c fived_table[5] =
    	{
    		{.0, .0}};
    	float       fifth = 8 * atan(1.) / 5;
    	register int i;
    	register float r;
    	register fcoord_c offset;
    
    	*pt = tp->origin;
    	offset.x = 0.0;
    	offset.y = 0.0;
    	if (fived_table[0].x == .0)
    		for (i = 0; i < 5; i++) {
    			fived_table[i].x = cos(fifth * i);
    			fived_table[i].y = sin(fifth * i);
    		}
    	for (i = 0; i < 5; i++) {
    		r = fived[i] * tp->edge_length;
    		offset.x += r * fived_table[i].x;
    		offset.y -= r * fived_table[i].y;
    	}
    	(*pt).x += (int) (offset.x + .5);
    	(*pt).y += (int) (offset.y + .5);
    }
    
    
    /* Mop up dynamic data for one screen. */
    static void
    free_penrose(tiling_c * tp)
    {
    	register fringe_node_c *fp1, *fp2;
    	register forced_node_c *lp1, *lp2;
    
    	if (tp->fringe.nodes == NULL)
    		return;
    	fp1 = tp->fringe.nodes;
    	do {
    		fp2 = fp1;
    		fp1 = fp1->next;
    		free(fp2);
    	} while (fp1 != tp->fringe.nodes);
    	tp->fringe.nodes = (fringe_node_c *) NULL;
    	for (lp1 = tp->forced.first; lp1 != 0;) {
    		lp2 = lp1;
    		lp1 = lp1->next;
    		free(lp2);
    	}
    	tp->forced.first = 0;
    }
    
    
    /* Called to init the mode. */
    void
    init_penrose(ModeInfo * mi)
    {
    	tiling_c   *tp;
    	fringe_node_c *fp;
    	int         i, size;
    
    	if (tilings == NULL) {
    		if ((tilings = (tiling_c *) calloc(MI_NUM_SCREENS(mi),
    						 sizeof (tiling_c))) == NULL)
    			return;
    	}
    	tp = &tilings[MI_SCREEN(mi)];
    
    	if (MI_IS_FULLRANDOM(mi))
    		tp->ammann = (Bool) (LRAND() & 1);
    	else
    		tp->ammann = ammann;
    	tp->done = False;
    	tp->busyLoop = 0;
    	tp->failures = 0;
    	tp->width = MI_WIDTH(mi);
    	tp->height = MI_HEIGHT(mi);
    	if (MI_NPIXELS(mi) > 2) {
    		tp->thick_color = NRAND(MI_NPIXELS(mi));
    		/* Insure good contrast */
    		tp->thin_color = (NRAND(2 * MI_NPIXELS(mi) / 3) + tp->thick_color +
    				  MI_NPIXELS(mi) / 6) % MI_NPIXELS(mi);
    	} else {
    		if (LRAND() & 1) {
    			tp->thick_color = MI_WHITE_PIXEL(mi);
    			tp->thin_color = MI_BLACK_PIXEL(mi);
    		} else {
    			tp->thick_color = MI_BLACK_PIXEL(mi);
    			tp->thin_color = MI_WHITE_PIXEL(mi);
    		}
    	}
    	size = MI_SIZE(mi);
    	if (size < -MINSIZE)
    		tp->edge_length = NRAND(MIN(-size, MAX(MINSIZE,
    		   MIN(tp->width, tp->height) / 2)) - MINSIZE + 1) + MINSIZE;
    	else if (size < MINSIZE) {
    		if (!size)
    			tp->edge_length = MAX(MINSIZE, MIN(tp->width, tp->height) / 2);
    		else
    			tp->edge_length = MINSIZE;
    	} else
    		tp->edge_length = MIN(size, MAX(MINSIZE,
    					    MIN(tp->width, tp->height) / 2));
    	tp->origin.x = (tp->width / 2 + NRAND(tp->width)) / 2;
    	tp->origin.y = (tp->height / 2 + NRAND(tp->height)) / 2;
    	tp->fringe.n_nodes = 2;
    	if (tp->fringe.nodes != NULL)
    		free_penrose(tp);
    	if (tp->fringe.nodes != NULL || tp->forced.first != 0) {
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in init_penrose()\n");
    			(void) fprintf(stderr, "tp->fringe.nodes = NULL && tp->forced.first = 0\n");
    		}
    		free_penrose(tp);	/* Try again */
    		tp->done = True;
    	}
    	tp->forced.n_nodes = tp->forced.n_visible = 0;
    	if ((fp = tp->fringe.nodes = ALLOC_NODE(fringe_node_c)) == NULL) {
    		free_penrose(tp);
    		return;
    	}
    	if (fp == 0) {
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in init_penrose()\n");
    			(void) fprintf(stderr, "fp = 0\n");
    		}
    		if ((fp = tp->fringe.nodes = ALLOC_NODE(fringe_node_c)) == NULL) {
    			free_penrose(tp);
    			return;
    		}
    		tp->done = True;
    	}
    	/* First vertex. */
    	fp->rule_mask = (1 << N_VERTEX_RULES) - 1;
    	fp->list_ptr = 0;
    	if  ((fp->prev = fp->next = ALLOC_NODE(fringe_node_c)) == NULL) {
    		free_penrose(tp);
    		return;
    	}
    	if (fp->next == 0) {
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in init_penrose()\n");
    			(void) fprintf(stderr, "fp->next = 0\n");
    		}
    		if ((fp->prev = fp->next = ALLOC_NODE(fringe_node_c)) == NULL) {
    			free_penrose(tp);
    			return;
    		}
    		tp->done = True;
    	}
    	fp->n_tiles = 0;
    	fp->loc = tp->origin;
    	fp->off_screen = False;
    	for (i = 0; i < 5; i++)
    		fp->fived[i] = 0;
    
    	/* Second vertex. */
    	*(fp->next) = *fp;
    	fp->next->prev = fp->next->next = fp;
    	fp = fp->next;
    	i = NRAND(5);
    	fp->fived[i] = 2 * NRAND(2) - 1;
    	fived_to_loc(fp->fived, tp, &(fp->loc));
    	/* That's it!  We have created our first edge. */
    }
    
    /*-
     * This attempts to match the configuration of vertex with the vertex
     * rules.   The return value is a total match count.  If matches is
     * non-null, it will be used to store information about the matches
     * and must be large enough to contain it.  To play it absolutely
     * safe, allocate room for MAX_TILES_PER_VERTEX * N_VERTEX_RULES
     * entries when searching all matches.   The rule mask of vertex will
     * be applied and rules masked out will not be searched.  Only strict
     * subsequences match.  If first_only is true, the search stops when
     * the first match is found.  Otherwise all matches will be found and
     * the rule_mask of vertex will be updated, which also happens in
     * single-match mode if no match is found.
     */
    static int
    match_rules(fringe_node_c * vertex, rule_match_c * matches, int first_only)
    {
    	/* I will assume that I can fit all the relevant bits in vertex->tiles
    	   into one unsigned long.  With 3 bits per element and at most 7
    	   elements this means 21 bits, which should leave plenty of room.
    	   After packing the bits the rest is just integer comparisons and
    	   some bit shuffling.  This is essentially Rabin-Karp without
    	   congruence arithmetic. */
    	register int i, j;
    	int         hits = 0, good_rules[N_VERTEX_RULES], n_good = 0;
    	unsigned long
    	            vertex_hash = 0, lower_bits_mask = ~(VT_TOTAL_MASK << VT_BITS * (vertex->n_tiles - 1));
    	unsigned    new_rule_mask = 0;
    
    	for (i = 0; i < N_VERTEX_RULES; i++)
    		if (vertex->n_tiles >= vertex_rules[i].n_tiles)
    			vertex->rule_mask &= ~(1 << i);
    		else if (vertex->rule_mask & 1 << i)
    			good_rules[n_good++] = i;
    	for (i = 0; i < vertex->n_tiles; i++)
    		vertex_hash |= (unsigned long) vertex->tiles[i] << (VT_BITS * i);
    
    	for (j = 0; j < n_good; j++) {
    		unsigned long rule_hash = 0;
    		vertex_rule_c *vr = vertex_rules + good_rules[j];
    
    		for (i = 0; i < vertex->n_tiles; i++)
    			rule_hash |= (unsigned long) vr->tiles[i] << (VT_BITS * i);
    		if (rule_hash == vertex_hash) {
    			if (matches != 0) {
    				matches[hits].rule = good_rules[j];
    				matches[hits].pos = 0;
    			}
    			hits++;
    			if (first_only)
    				return hits;
    			else
    				new_rule_mask |= 1 << good_rules[j];
    		}
    		for (i = vr->n_tiles - 1; i > 0; i--) {
    			rule_hash = vr->tiles[i] | (rule_hash & lower_bits_mask) << VT_BITS;
    			if (vertex_hash == rule_hash) {
    				if (matches != 0) {
    					matches[hits].rule = good_rules[j];
    					matches[hits].pos = i;
    				}
    				hits++;
    				if (first_only)
    					return hits;
    				else
    					new_rule_mask |= 1 << good_rules[j];
    			}
    		}
    	}
    	vertex->rule_mask = new_rule_mask;
    	return hits;
    }
    
    
    /*-
     * find_completions finds the possible ways to add a tile to a vertex.
     * The return values is the number of such possibilities.  You must
     * first call match_rules to produce matches and n_matches.  sides
     * specifies which side of the vertex to extend and can be S_LEFT or
     * S_RIGHT.  If results is non-null, it should point to an array large
     * enough to contain the results, which will be stored there.
     * MAX_COMPL elements will always suffice.  If first_only is true we
     * stop as soon as we find one possibility (NOT USED).
     */
    #define MAX_COMPL 2
    
    static int
    find_completions(fringe_node_c * vertex, rule_match_c * matches, int n_matches,
    	       unsigned side, vertex_type_c * results /*, int first_only */ )
    {
    	int         n_res = 0, cont;
    	register int i, j;
    	vertex_type_c buf[MAX_COMPL];
    
    	if (results == 0)
    		results = buf;
    	if (n_matches <= 0)
    		return 0;
    	for (i = 0; i < n_matches; i++) {
    		vertex_rule_c *rule = vertex_rules + matches[i].rule;
    		int         pos = (matches[i].pos
    		   + (side == S_RIGHT ? vertex->n_tiles : rule->n_tiles - 1))
    		% rule->n_tiles;
    		vertex_type_c vtype = rule->tiles[pos];
    
    		cont = 1;
    		for (j = 0; j < n_res; j++)
    			if (vtype == results[j]) {
    				cont = 0;
    				break;
    			}
    		if (cont)
    			results[n_res++] = vtype;
    	}
    	return n_res;
    }
    
    
    /*-
     * Draw a tile on the display.  Vertices must be given in a
     * counterclockwise order.  vtype is the vertex type of v1 (and thus
     * also gives the tile type).
     */
    static void
    draw_tile(fringe_node_c * v1, fringe_node_c * v2,
    	  fringe_node_c * v3, fringe_node_c * v4,
    	  vertex_type_c vtype, ModeInfo * mi)
    {
    	Display    *display = MI_DISPLAY(mi);
    	Window      window = MI_WINDOW(mi);
    	GC          gc = MI_GC(mi);
    	tiling_c   *tp = &tilings[MI_SCREEN(mi)];
    	XPoint      pts[5];
    	vertex_type_c corner = vtype & VT_CORNER_MASK;
    
    	if (v1->off_screen && v2->off_screen && v3->off_screen && v4->off_screen)
    		return;
    	pts[corner] = v1->loc;
    	pts[VT_RIGHT(corner)] = v2->loc;
    	pts[VT_FAR(corner)] = v3->loc;
    	pts[VT_LEFT(corner)] = v4->loc;
    	pts[4] = pts[0];
    	if (MI_NPIXELS(mi) > 2) {
    		if ((vtype & VT_TYPE_MASK) == VT_THICK)
    			XSetForeground(display, gc, MI_PIXEL(mi, tp->thick_color));
    		else
    			XSetForeground(display, gc, MI_PIXEL(mi, tp->thin_color));
    	} else {
    		if ((vtype & VT_TYPE_MASK) == VT_THICK)
    			XSetForeground(display, gc, tp->thick_color);
    		else
    			XSetForeground(display, gc, tp->thin_color);
    	}
    	XFillPolygon(display, window, gc, pts, 4, Convex, CoordModeOrigin);
    	if (MI_NPIXELS(mi) <= 2) {
    		if ((vtype & VT_TYPE_MASK) == VT_THICK)
    			XSetForeground(display, gc, tp->thin_color);
    		else
    			XSetForeground(display, gc, tp->thick_color);
    	} else
    		XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
    	XDrawLines(display, window, gc, pts, 5, CoordModeOrigin);
    
    	if (tp->ammann) {
    		/* Draw some Ammann lines for debugging purposes.  This will probably
    		   fail miserably on a b&w display. */
    
    		if ((vtype & VT_TYPE_MASK) == VT_THICK) {
    			static float r = .0;
    
    			if (r == .0) {
    				float       pi10 = 2 * atan(1.) / 5;
    
    				r = 1 - sin(pi10) / (2 * sin(3 * pi10));
    			}
    			if (MI_NPIXELS(mi) > 2)
    				XSetForeground(display, gc, MI_PIXEL(mi, tp->thin_color));
    			else {
    				XSetForeground(display, gc, tp->thin_color);
    				XSetLineAttributes(display, gc, 1, LineOnOffDash, CapNotLast, JoinMiter);
    			}
    			XDrawLine(display, window, gc,
    			      (int) (r * pts[3].x + (1 - r) * pts[0].x + .5),
    			      (int) (r * pts[3].y + (1 - r) * pts[0].y + .5),
    			      (int) (r * pts[1].x + (1 - r) * pts[0].x + .5),
    			     (int) (r * pts[1].y + (1 - r) * pts[0].y + .5));
    			if (MI_NPIXELS(mi) <= 2)
    				XSetLineAttributes(display, gc, 1, LineSolid, CapNotLast, JoinMiter);
    		} else {
    			if (MI_NPIXELS(mi) > 2)
    				XSetForeground(display, gc, MI_PIXEL(mi, tp->thick_color));
    			else {
    				XSetForeground(display, gc, tp->thick_color);
    				XSetLineAttributes(display, gc, 1, LineOnOffDash, CapNotLast, JoinMiter);
    			}
    			XDrawLine(display, window, gc,
    				  (int) ((pts[3].x + pts[2].x) / 2 + .5),
    				  (int) ((pts[3].y + pts[2].y) / 2 + .5),
    				  (int) ((pts[1].x + pts[2].x) / 2 + .5),
    				  (int) ((pts[1].y + pts[2].y) / 2 + .5));
    			if (MI_NPIXELS(mi) <= 2)
    				XSetLineAttributes(display, gc, 1, LineSolid, CapNotLast, JoinMiter);
    		}
    	}
    }
    
    /*-
     * Update the status of this vertex on the forced vertex queue.  If
     * the vertex has become untileable set tp->done.  This is supposed
     * to detect dislocations -- never call this routine with a completely
     * tiled vertex.
     *
     * Check for untileable vertices in check_vertex and stop tiling as
     * soon as one finds one.  I don't know if it is possible to run out
     * of forced vertices while untileable vertices exist (or will
     * cavities inevitably appear).  If this can happen, add_random_tile
     * might get called with an untileable vertex, causing ( n <= 1).
     * (This is what the tp->done checks for).
     *
     * A delayLoop celebrates the dislocation.
     */
    static void
    check_vertex(ModeInfo * mi, fringe_node_c * vertex, tiling_c * tp)
    {
    	rule_match_c hits[MAX_TILES_PER_VERTEX * N_VERTEX_RULES];
    	int         n_hits = match_rules(vertex, hits, False);
    	unsigned    forced_sides = 0;
    
    	if (vertex->rule_mask == 0) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Dislocation occurred!\n");
    		}
    		tp->busyLoop = CELEBRATE;	/* Should be able to recover */
    	}
    	if (1 == find_completions(vertex, hits, n_hits, S_LEFT, 0 /*, False */ ))
    		forced_sides |= S_LEFT;
    	if (1 == find_completions(vertex, hits, n_hits, S_RIGHT, 0 /*, False */ ))
    		forced_sides |= S_RIGHT;
    	if (forced_sides == 0) {
    		if (vertex->list_ptr != 0) {
    			forced_node_c *node = *vertex->list_ptr;
    
    			*vertex->list_ptr = node->next;
    			if (node->next != 0)
    				node->next->vertex->list_ptr = vertex->list_ptr;
    			free(node);
    			tp->forced.n_nodes--;
    			if (!vertex->off_screen)
    				tp->forced.n_visible--;
    			vertex->list_ptr = 0;
    		}
    	} else {
    		forced_node_c *node;
    
    		if (vertex->list_ptr == 0) {
    			if ((node = ALLOC_NODE(forced_node_c)) == NULL)
    				return;
    			node->vertex = vertex;
    			node->next = tp->forced.first;
    			if (tp->forced.first != 0)
    				tp->forced.first->vertex->list_ptr = &(node->next);
    			tp->forced.first = node;
    			vertex->list_ptr = &(tp->forced.first);
    			tp->forced.n_nodes++;
    			if (!vertex->off_screen)
    				tp->forced.n_visible++;
    		} else
    			node = *vertex->list_ptr;
    		node->forced_sides = forced_sides;
    	}
    }
    
    
    /*-
     * Delete this vertex.  If the vertex is a member of the forced vertex queue,
     * also remove that entry.  We assume that the vertex is no longer
     * connected to the fringe.  Note that tp->fringe.nodes must not point to
     * the vertex being deleted.
     */
    static void
    delete_vertex(ModeInfo * mi, fringe_node_c * vertex, tiling_c * tp)
    {
    	if (tp->fringe.nodes == vertex) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in delete_penrose()\n");
    			(void) fprintf(stderr, "tp->fringe.nodes == vertex\n");
    		}
    		tp->busyLoop = CELEBRATE;
    	}
    	if (vertex->list_ptr != 0) {
    		forced_node_c *node = *vertex->list_ptr;
    
    		*vertex->list_ptr = node->next;
    		if (node->next != 0)
    			node->next->vertex->list_ptr = vertex->list_ptr;
    		free(node);
    		tp->forced.n_nodes--;
    		if (!vertex->off_screen)
    			tp->forced.n_visible--;
    	}
    	if (!vertex->off_screen)
    		tp->fringe.n_nodes--;
    	free(vertex);
    }
    
    
    /*-
     * Check whether the addition of a tile of type vtype would completely fill
     * the space available at vertex.
     */
    static int
    fills_vertex(ModeInfo * mi, vertex_type_c vtype, fringe_node_c * vertex)
    {
    	return
    		(vertex_dir(mi, vertex, S_LEFT) - vertex_dir(mi, vertex, S_RIGHT)
    		 - vtype_angle(vtype)) % 10 == 0;
    }
    
    
    /*-
     * If you were to add a tile of type vtype to a specified side of
     * vertex, fringe_changes tells you which other vertices it would
     * attach to.  The addresses of these vertices will be stored in the
     * last three arguments.  Null is stored if the corresponding vertex
     * would need to be allocated.
     *
     * The function also analyzes which vertices would be swallowed by the tiling
     * and thus cut off from the fringe.  The result is returned as a bit pattern.
     */
    #define FC_BAG 1		/* Total enclosure.  Should never occur. */
    #define FC_NEW_RIGHT 2
    #define FC_NEW_FAR 4
    #define FC_NEW_LEFT 8
    #define FC_NEW_MASK 0xe
    #define FC_CUT_THIS 0x10
    #define FC_CUT_RIGHT 0x20
    #define FC_CUT_FAR 0x40
    #define FC_CUT_LEFT 0x80
    #define FC_CUT_MASK 0xf0
    #define FC_TOTAL_MASK 0xff
    
    static unsigned
    fringe_changes(ModeInfo * mi, fringe_node_c * vertex,
    	       unsigned side, vertex_type_c vtype,
    	       fringe_node_c ** right, fringe_node_c ** far,
    	       fringe_node_c ** left)
    {
    	fringe_node_c *v, *f = (fringe_node_c *) NULL;
    	unsigned    result = FC_NEW_FAR;	/* We clear this later if necessary. */
    
    	if (far)
    		*far = 0;
    	if (fills_vertex(mi, vtype, vertex)) {
    		result |= FC_CUT_THIS;
    	} else if (side == S_LEFT) {
    		result |= FC_NEW_RIGHT;
    		if (right)
    			*right = 0;
    	} else {
    		result |= FC_NEW_LEFT;
    		if (left)
    			*left = 0;
    	}
    
    	if (!(result & FC_NEW_LEFT)) {
    		v = vertex->next;
    		if (left)
    			*left = v;
    		if (fills_vertex(mi, VT_LEFT(vtype), v)) {
    			result = (result & ~FC_NEW_FAR) | FC_CUT_LEFT;
    			f = v->next;
    			if (far)
    				*far = f;
    		}
    	}
    	if (!(result & FC_NEW_RIGHT)) {
    		v = vertex->prev;
    		if (right)
    			*right = v;
    		if (fills_vertex(mi, VT_RIGHT(vtype), v)) {
    			result = (result & ~FC_NEW_FAR) | FC_CUT_RIGHT;
    			f = v->prev;
    			if (far)
    				*far = f;
    		}
    	}
    	if (!(result & FC_NEW_FAR)
    	    && fills_vertex(mi, VT_FAR(vtype), f)) {
    		result |= FC_CUT_FAR;
    		result &= (~FC_NEW_LEFT & ~FC_NEW_RIGHT);
    		if (right && (result & FC_CUT_LEFT))
    			*right = f->next;
    		if (left && (result & FC_CUT_RIGHT))
    			*left = f->prev;
    	}
    	if (((result & FC_CUT_LEFT) && (result & FC_CUT_RIGHT))
    	    || ((result & FC_CUT_THIS) && (result & FC_CUT_FAR)))
    		result |= FC_BAG;
    	return result;
    }
    
    
    /* A couple of lesser helper functions for add_tile. */
    static void
    add_vtype(fringe_node_c * vertex, unsigned side, vertex_type_c vtype)
    {
    	if (side == S_RIGHT)
    		vertex->tiles[vertex->n_tiles++] = vtype;
    	else {
    		register int i;
    
    		for (i = vertex->n_tiles; i > 0; i--)
    			vertex->tiles[i] = vertex->tiles[i - 1];
    		vertex->tiles[0] = vtype;
    		vertex->n_tiles++;
    	}
    }
    
    static fringe_node_c *
    alloc_vertex(ModeInfo * mi, angle_c dir, fringe_node_c * from, tiling_c * tp)
    {
    	fringe_node_c *v;
    
    	if ((v = ALLOC_NODE(fringe_node_c)) == NULL) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "No memory in alloc_vertex()\n");
    		}
    		tp->busyLoop = CELEBRATE;
    		return v;
    	}
    	*v = *from;
    	add_unit_vec(dir, v->fived);
    	fived_to_loc(v->fived, tp, &(v->loc));
    	if (v->loc.x < 0 || v->loc.y < 0
    	    || v->loc.x >= tp->width || v->loc.y >= tp->height) {
    		v->off_screen = True;
    		if (v->loc.x < -tp->width || v->loc.y < -tp->height
    		  || v->loc.x >= 2 * tp->width || v->loc.y >= 2 * tp->height)
    			tp->done = True;
    	} else {
    		v->off_screen = False;
    		tp->fringe.n_nodes++;
    	}
    	v->n_tiles = 0;
    	v->rule_mask = (1 << N_VERTEX_RULES) - 1;
    	v->list_ptr = 0;
    	return v;
    }
    
    /*-
     * Add a tile described by vtype to the side of vertex.  This must be
     * allowed by the rules -- we do not check it here.  New vertices are
     * allocated as necessary.  The fringe and the forced vertex pool are updated.
     * The new tile is drawn on the display.
     *
     * One thing we do check here is whether the new tile causes an untiled
     * area to become enclosed by the tiling.  If this would happen, the tile
     * is not added.  The return value is true iff a tile was added.
     */
    static int
    add_tile(ModeInfo * mi,
    	 fringe_node_c * vertex, unsigned side, vertex_type_c vtype)
    {
    	tiling_c   *tp = &tilings[MI_SCREEN(mi)];
    
    	fringe_node_c
    		*left = (fringe_node_c *) NULL,
    		*right = (fringe_node_c *) NULL,
    		*far = (fringe_node_c *) NULL,
    		*node;
    	unsigned    fc = fringe_changes(mi, vertex, side, vtype, &right, &far, &left);
    
    	vertex_type_c
    		ltype = VT_LEFT(vtype),
    		rtype = VT_RIGHT(vtype),
    		ftype = VT_FAR(vtype);
    
    	/* By our conventions vertex->next lies to the left of vertex and
    	   vertex->prev to the right. */
    
    	/* This should never occur. */
    	if (fc & FC_BAG) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in add_tile()\n");
    			(void) fprintf(stderr, "fc = %d, FC_BAG = %d\n", fc, FC_BAG);
    		}
    	}
    	if (side == S_LEFT) {
    		if (right == NULL)
    			if ((right = alloc_vertex(mi, vertex_dir(mi, vertex, S_LEFT) -
    					vtype_angle(vtype), vertex, tp)) == NULL)
    				return False;
    		if (far == NULL)
    			if ((far = alloc_vertex(mi, vertex_dir(mi, left, S_RIGHT) +
    					vtype_angle(ltype), left, tp)) == NULL)
    				return False;
    	} else {
    		if (left == NULL)
    			if ((left = alloc_vertex(mi, vertex_dir(mi, vertex, S_RIGHT) +
    					vtype_angle(vtype), vertex, tp)) == NULL)
    				return False;
    		if (far == NULL)
    			if ((far = alloc_vertex(mi, vertex_dir(mi, right, S_LEFT) -
    					vtype_angle(rtype), right, tp)) == NULL)
    				return False;
    	}
    
    	/* Having allocated the new vertices, but before joining them with
    	   the rest of the fringe, check if vertices with same coordinates
    	   already exist.  If any such are found, give up. */
    	node = tp->fringe.nodes;
    	do {
    		if (((fc & FC_NEW_LEFT) && fived_equal(node->fived, left->fived))
    		    || ((fc & FC_NEW_RIGHT) && fived_equal(node->fived, right->fived))
    		    || ((fc & FC_NEW_FAR) && fived_equal(node->fived, far->fived))) {
    			/* Better luck next time. */
    			if (fc & FC_NEW_LEFT)
    				delete_vertex(mi, left, tp);
    			if (fc & FC_NEW_RIGHT)
    				delete_vertex(mi, right, tp);
    			if (fc & FC_NEW_FAR)
    				delete_vertex(mi, far, tp);
    			return False;
    		}
    		node = node->next;
    	} while (node != tp->fringe.nodes);
    
    	/* Rechain. */
    	if (!(fc & FC_CUT_THIS)) {
    		if (side == S_LEFT) {
    			vertex->next = right;
    			right->prev = vertex;
    		} else {
    			vertex->prev = left;
    			left->next = vertex;
    		}
    	}
    	if (!(fc & FC_CUT_FAR)) {
    		if (!(fc & FC_CUT_LEFT)) {
    			far->next = left;
    			left->prev = far;
    		}
    		if (!(fc & FC_CUT_RIGHT)) {
    			far->prev = right;
    			right->next = far;
    		}
    	}
    	draw_tile(vertex, right, far, left, vtype, mi);
    
    	/* Delete vertices that are no longer on the fringe.  Check the others. */
    	if (fc & FC_CUT_THIS) {
    		tp->fringe.nodes = far;
    		delete_vertex(mi, vertex, tp);
    	} else {
    		add_vtype(vertex, side, vtype);
    		check_vertex(mi, vertex, tp);
    		tp->fringe.nodes = vertex;
    	}
    	if (fc & FC_CUT_FAR)
    		delete_vertex(mi, far, tp);
    	else {
    		add_vtype(far, fc & FC_CUT_RIGHT ? S_LEFT : S_RIGHT, ftype);
    		check_vertex(mi, far, tp);
    	}
    	if (fc & FC_CUT_LEFT)
    		delete_vertex(mi, left, tp);
    	else {
    		add_vtype(left, fc & FC_CUT_FAR ? S_LEFT : S_RIGHT, ltype);
    		check_vertex(mi, left, tp);
    	}
    	if (fc & FC_CUT_RIGHT)
    		delete_vertex(mi, right, tp);
    	else {
    		add_vtype(right, fc & FC_CUT_FAR ? S_RIGHT : S_LEFT, rtype);
    		check_vertex(mi, right, tp);
    	}
    	return True;
    }
    
    
    /*-
     * Add a forced tile to a given forced vertex.  Basically an easy job,
     * since we know what to add.  But it might fail if adding the tile
     * would cause some untiled area to become enclosed.  There is also another
     * more exotic culprit: we might have a dislocation.  Fortunately, they
     * are very rare (the PRL article reported that perfect tilings of over
     * 2^50 tiles had been generated).  There is a version of the algorithm
     * that doesn't produce dislocations, but it's a lot hairier than the
     * simpler version I used.
     */
    static int
    add_forced_tile(ModeInfo * mi, forced_node_c * node)
    {
    	tiling_c   *tp = &tilings[MI_SCREEN(mi)];
    	unsigned    side;
    	vertex_type_c vtype;
    	rule_match_c hits[MAX_TILES_PER_VERTEX * N_VERTEX_RULES];
    	int         n;
    
    	if (node->forced_sides == (S_LEFT | S_RIGHT))
    		side = NRAND(2) ? S_LEFT : S_RIGHT;
    	else
    		side = node->forced_sides;
    	n = match_rules(node->vertex, hits, True);
    	n = find_completions(node->vertex, hits, n, side, &vtype /*, True */ );
    	if (n <= 0) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in add_forced_tile()\n");
    			(void) fprintf(stderr, "n = %d\n", n);
    		}
    	}
    	return add_tile(mi, node->vertex, side, vtype);
    }
    
    
    /*-
     * Whether the addition of a tile of vtype on the given side of vertex
     * would conform to the rules.  The efficient way to do this would be
     * to add the new tile and then use the same type of search as in
     * match_rules.  However, this function is not a performance
     * bottleneck (only needed for random tile additions, which are
     * relatively infrequent), so I will settle for a simpler implementation.
     */
    static int
    legal_move(fringe_node_c * vertex, unsigned side, vertex_type_c vtype)
    {
    	rule_match_c hits[MAX_TILES_PER_VERTEX * N_VERTEX_RULES];
    	vertex_type_c legal_vt[MAX_COMPL];
    	int         n_hits, n_legal, i;
    
    	n_hits = match_rules(vertex, hits, False);
    	n_legal = find_completions(vertex, hits, n_hits, side, legal_vt /*, False */ );
    	for (i = 0; i < n_legal; i++)
    		if (legal_vt[i] == vtype)
    			return True;
    	return False;
    }
    
    
    /*-
     * Add a randomly chosen tile to a given vertex.  This requires more checking
     * as we must make sure the new tile conforms to the vertex rules at every
     * vertex it touches. */
    static void
    add_random_tile(fringe_node_c * vertex, ModeInfo * mi)
    {
    	fringe_node_c *right, *left, *far;
    	int         i, j, n, n_hits, n_good;
    	unsigned    side, fc, no_good, s;
    	vertex_type_c vtypes[MAX_COMPL];
    	rule_match_c hits[MAX_TILES_PER_VERTEX * N_VERTEX_RULES];
    	tiling_c   *tp = &tilings[MI_SCREEN(mi)];
    
    	if (MI_NPIXELS(mi) > 2) {
    		tp->thick_color = NRAND(MI_NPIXELS(mi));
    		/* Insure good contrast */
    		tp->thin_color = (NRAND(2 * MI_NPIXELS(mi) / 3) + tp->thick_color +
    				  MI_NPIXELS(mi) / 6) % MI_NPIXELS(mi);
    	} else {
    		unsigned long temp = tp->thick_color;
    
    		tp->thick_color = tp->thin_color;
    		tp->thin_color = temp;
    	}
    	n_hits = match_rules(vertex, hits, False);
    	side = NRAND(2) ? S_LEFT : S_RIGHT;
    	n = find_completions(vertex, hits, n_hits, side, vtypes /*, False */ );
    	/* One answer would mean a forced tile. */
    	if (n <= 0) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in add_random_tile()\n");
    			(void) fprintf(stderr, "n = %d\n", n);
    		}
    	}
    	no_good = 0;
    	n_good = n;
    	for (i = 0; i < n; i++) {
    		fc = fringe_changes(mi, vertex, side, vtypes[i], &right, &far, &left);
    		if (fc & FC_BAG) {
    			tp->done = True;
    			if (MI_IS_VERBOSE(mi)) {
    				(void) fprintf(stderr, "Weirdness in add_random_tile()\n");
    				(void) fprintf(stderr, "fc = %d, FC_BAG = %d\n", fc, FC_BAG);
    			}
    		}
    		if (right) {
    			s = (((fc & FC_CUT_FAR) && (fc & FC_CUT_LEFT)) ? S_RIGHT : S_LEFT);
    			if (!legal_move(right, s, VT_RIGHT(vtypes[i]))) {
    				no_good |= (1 << i);
    				n_good--;
    				continue;
    			}
    		}
    		if (left) {
    			s = (((fc & FC_CUT_FAR) && (fc & FC_CUT_RIGHT)) ? S_LEFT : S_RIGHT);
    			if (!legal_move(left, s, VT_LEFT(vtypes[i]))) {
    				no_good |= (1 << i);
    				n_good--;
    				continue;
    			}
    		}
    		if (far) {
    			s = ((fc & FC_CUT_LEFT) ? S_RIGHT : S_LEFT);
    			if (!legal_move(far, s, VT_FAR(vtypes[i]))) {
    				no_good |= (1 << i);
    				n_good--;
    			}
    		}
    	}
    	if (n_good <= 0) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in add_random_tile()\n");
    			(void) fprintf(stderr, "n_good = %d\n", n_good);
    		}
    	}
    	n = NRAND(n_good);
    	for (i = j = 0; i <= n; i++, j++)
    		while (no_good & (1 << j))
    			j++;
    
    	if (!add_tile(mi, vertex, side, vtypes[j - 1])) {
    		tp->done = True;
    		if (MI_IS_VERBOSE(mi)) {
    			(void) fprintf(stderr, "Weirdness in add_random_tile()\n");
    		}
    		free_penrose(tp);
    	}
    }
    
    /* One step of the growth algorithm. */
    void
    draw_penrose(ModeInfo * mi)
    {
    	int         i = 0, n;
    	forced_node_c *p;
    	tiling_c   *tp;
    
    	if (tilings == NULL)
    		return;
    	tp = &tilings[MI_SCREEN(mi)];
    	if (tp->fringe.nodes == NULL)
    		return;
    
    	MI_IS_DRAWN(mi) = True;
    	p = tp->forced.first;
    	if (tp->busyLoop > 0) {
    		tp->busyLoop--;
    		return;
    	}
    	if (tp->done || tp->failures >= 100) {
    		init_penrose(mi);
    		return;
    	}
    	/* Check for the initial "2-gon". */
    	if (tp->fringe.nodes->prev == tp->fringe.nodes->next) {
    		vertex_type_c vtype = (unsigned char) (VT_TOTAL_MASK & LRAND());
    
    		MI_CLEARWINDOW(mi);
    
    		if (!add_tile(mi, tp->fringe.nodes, S_LEFT, vtype))
    			free_penrose(tp);
    		return;
    	}
    	/* No visible nodes left. */
    	if (tp->fringe.n_nodes == 0) {
    		tp->done = True;
    		tp->busyLoop = COMPLETION;	/* Just finished drawing */
    		return;
    	}
    	if (tp->forced.n_visible > 0 && tp->failures < 10) {
    		n = NRAND(tp->forced.n_visible);
    		for (;;) {
    			while (p->vertex->off_screen)
    				p = p->next;
    			if (i++ < n)
    				p = p->next;
    			else
    				break;
    		}
    	} else if (tp->forced.n_nodes > 0) {
    		n = NRAND(tp->forced.n_nodes);
    		while (i++ < n)
    			p = p->next;
    	} else {
    		fringe_node_c *fringe_p = tp->fringe.nodes;
    
    		n = NRAND(tp->fringe.n_nodes);
    		i = 0;
    		for (; i <= n; i++)
    			do {
    				fringe_p = fringe_p->next;
    			} while (fringe_p->off_screen);
    		add_random_tile(fringe_p, mi);
    		tp->failures = 0;
    		return;
    	}
    	if (add_forced_tile(mi, p))
    		tp->failures = 0;
    	else
    		tp->failures++;
    }
    
    
    /* Total clean-up. */
    void
    release_penrose(ModeInfo * mi)
    {
    	if (tilings != NULL) {
    		int         screen;
    
    		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
    			free_penrose(&tilings[screen]);
    		free(tilings);
    		tilings = (tiling_c *) NULL;
    	}
    }
    
    #endif /* MODE_penrose */