[comp.graphics] Hilbert curve generation

spencer@eecs.umich.edu (Spencer W. Thomas) (03/13/91)

I've had several requests for this.  It should be self explanatory
(man page included).  Sorry, no makefile, but it should be obvious (no
special libraries required, except for the demo program, which
requires X11.)

: Run this shell script with "sh" not "csh"
PATH=:/bin:/usr/bin:/usr/ucb
export PATH
all=FALSE
if [ $1x = -ax ]; then
	all=TRUE
fi
/bin/echo 'Extracting README'
sed 's/^X//' <<'//go.sysin dd *' >README
Stuff here:

hilbert.c: basic routines for computing Hilbert curves in N-d space.
hilbert*.3: Man page.
test-hilbert.c: A simple program that draws some Hilbert curves using
	the X window system.

Spencer W. Thomas
EECS Dept.
University of Michigan
spencer@eecs.umich.edu

//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 README
	/bin/echo -n '	'; /bin/ls -ld README
fi
/bin/echo 'Extracting hilbert.3'
sed 's/^X//' <<'//go.sysin dd *' >hilbert.3
X.\" Copyright 1991, The Regents of the University of Michigan
X.TH HILBERT 3 3/12/91 3
X.UC 4
X.SH NAME
hilbert_i2c, hilbert_c2i \- Compute points on a Hilbert curve.
X.SH SYNOPSIS
X.B
void hilbert_i2c( dim, bits, idx, coords )
X.br
X.B
int dim, bits;
X.br
X.B
long int idx;
X.br
X.B
int coords[];
X.sp
X.B
void hilbert_c2i( dim, bits, coords, idx )
X.br
X.B
int dim, bits;
X.br
X.B
int coords[];
X.br
X.B
long int *idx;
X.SH DESCRIPTION
These procedures map the real line onto a Hilbert curve and vice
versa.  (A Hilbert curve is a space filling curve similar to the Peano
curve, except it is not closed.)  The procedure \fIhilbert_i2c\fP
returns the coordinates of a point on the Hilbert curve, given an
index value representing its sequential position on the curve.  The
procedure \fIhilbert_c2i\fP reverses the process.  The arguments are:
X.TP
X.I dim
The dimensionality of the Hilbert curve.  For the usual planar curve
case, this would be 2.
X.TP
X.I bits
The resolution to which the Hilbert curve will be computed.  The space
is quantized to 2^\fIbits\fP values on each axis, so there are
2^(3*\fIbits\fP) points on the curve.  The product of
\fIdim\fP*\fIbits\fP should be less than or equal to the number of
bits in a long integer (typically 32), and \fIbits\fP should be less
than or equal to the number of bits in an integer.
X.TP
X.I idx
The sequential position of the point along the curve (starting from
0).  This is a 3*\fIbits\fP bit integer.
X.TP
X.I coords
The spatial coordinates of the point on the curve.  The array should
hold \fIdim\fP values.  Each is a \fIbits\fP bit integer.
X.SH REFERENCE
A. R. Butz, "Alternative algorithm for Hilbert's space-filling
curve," \fIIEEE Trans. Comput.\fP, vol C-20, pp. 424-426, Apr. 1971.
X.SH AUTHOR
Spencer W. Thomas
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 hilbert.3
	/bin/echo -n '	'; /bin/ls -ld hilbert.3
fi
/bin/echo 'Extracting hilbert.c'
sed 's/^X//' <<'//go.sysin dd *' >hilbert.c
X/*
 * This software is copyrighted as noted below.  It may be freely copied,
 * modified, and redistributed, provided that the copyright notice is 
 * preserved on all copies.
 * 
 * There is no warranty or other guarantee of fitness for this software,
 * it is provided solely "as is".  Bug reports or fixes may be sent
 * to the author, who may or may not act on them as he desires.
 *
 * You may not include this software in a program or other software product
 * without supplying the source, or without informing the end-user that the 
 * source is available for no extra charge.
 *
 * If you modify this software, you should include a notice giving the
 * name of the person performing the modification, the date of modification,
 * and the reason for such modification.
 */
X/* 
 * hilbert.c - Computes Hilbert curve coordinates from position and v.v.
 * 
 * Author:	Spencer W. Thomas
 * 		EECS Dept.
 * 		University of Michigan
 * Date:	Thu Feb  7 1991
 * Copyright (c) 1991, University of Michigan
 */

X/*
 * Lots of tables to simplify calculations.  Notation: p#i means bit i
 * in byte p (high order bit first).
 * p_to_s:	Output s is a byte from input p such that
 * 		s#i = p#i xor p#(i-1)
 * s_to_p:	The inverse of the above.
 * p_to_J:	Output J is "principle position" of input p.  The
 * 		principle position is the last bit s.t.
 * 		p#J != p#(n-1) (or n-1 if all bits are equal).
 * bit:		bit[i] == (1 << (n - i))
 * circshift:	circshift[b][i] is a right circular shift of b by i
 * 		bits in n bits.
 * parity:	Parity[i] is 1 or 0 depending on the parity of i (1 is odd).
 * bitof:	bitof[b][i] is b#i.
 * nbits:	The value of n for which the above tables have been
 * 		calculated.
 */

typedef unsigned char byte;

static int nbits = 0;

static byte
    p_to_s[256],
    s_to_p[256],
    p_to_J[256],
    bit[8],
    circshift[256][8],
    parity[256],
    bitof[256][8];

X/* Calculate the above tables when nbits changes. */
static void
calctables(n)
int n;
{
    register int i, b;
    int two_n = 1 << n;

    if ( nbits == n )
	return;

    nbits = n;
    /* bit array is easy. */
    for ( b = 0; b < n; b++ )
	bit[b] = 1 << (n - b - 1);

    /* Next, do bitof. */
    for ( i = 0; i < two_n; i++ )
	for ( b = 0; b < n; b++ )
	    bitof[i][b] = (i & bit[b]) ? 1 : 0;

    /* circshift is independent of the others. */
    for ( i = 0; i < two_n; i++ )
	for ( b = 0; b < n; b++ )
	    circshift[i][b] = (i >> (b)) |
		((i << (n - b)) & (two_n - 1));

    /* So is parity. */
    parity[0] = 0;
    for ( i = 1, b = 1; i < two_n; i++ )
    {
	/* Parity of i is opposite of the number you get when you
	 * knock the high order bit off of i.
	 */
	if ( i == b * 2 )
	    b *= 2;
	parity[i] = !parity[i - b];
    }

    /* Now do p_to_s, s_to_p, and p_to_J. */
    for ( i = 0; i < two_n; i++ )
    {
	int s;

	s = i & bit[0];
	for ( b = 1; b < n; b++ )
	    if ( bitof[i][b] ^ bitof[i][b-1] )
		s |= bit[b];
	p_to_s[i] = s;
	s_to_p[s] = i;

	p_to_J[i] = n - 1;
	for ( b = 0; b < n; b++ )
	    if ( bitof[i][b] != bitof[i][n-1] )
		p_to_J[i] = b;
    }
}

X/*****************************************************************
 * TAG( hilbert_i2c )
 * 
 * Convert an index into a Hilbert curve to a set of coordinates.
 * Inputs:
 * 	n:	Number of coordinate axes.
 * 	m:	Number of bits per axis.
 * 	r:	The index, contains n*m bits (so n*m must be <= 32).
 * Outputs:
 * 	a:	The list of n coordinates, each with m bits.
 * Assumptions:
 * 	n*m < (sizeof r) * (bits_per_byte), n <= 8, m <= 8.
 * Algorithm:
 * 	From A. R. Butz, "Alternative Algorithm for Hilbert's
 * 		Space-Filling Curve", IEEE Trans. Comp., April, 1971,
 * 		pp 424-426.
 */
void
hilbert_i2c( n, m, r, a)
int n;
int m;
long int r;
int a[];
{
    byte rho[8], rh, J, sigma, tau,
    	 sigmaT, tauT, tauT1, omega, omega1, alpha[8];
    register int i, b;
    int Jsum;

    /* Initialize bit twiddle tables. */
    calctables(n);

    /* Distribute bits from r into rho. */
    for ( i = m - 1; i >= 0; i-- )
    {
	rho[i] = r & ((1 << n) - 1);
	r >>= n;
    }

    /* Loop over bytes. */
    Jsum = 0;
    for ( i = 0; i < m; i++ )
    {
	rh = rho[i];
	/* J[i] is principle position of rho[i]. */
	J = p_to_J[rh];

	/* sigma[i] is derived from rho[i] by exclusive-oring adjacent bits. */
	sigma = p_to_s[rh];

	/* tau[i] complements low bit of sigma[i], and bit at J[i] if
	 * necessary to make even parity.
	 */
	tau = sigma ^ 1;
	if ( parity[tau] )
	    tau ^= bit[J];
	
	/* sigmaT[i] is circular shift of sigma[i] by sum of J[0..i-1] */
	/* tauT[i] is same circular shift of tau[i]. */
	if ( Jsum > 0 )
	{
	    sigmaT = circshift[sigma][Jsum];
	    tauT = circshift[tau][Jsum];
	}
	else
	{
	    sigmaT = sigma;
	    tauT = tau;
	}

	Jsum += J;
	if ( Jsum >= n )
	    Jsum -= n;

	/* omega[i] is xor of omega[i-1] and tauT[i-1]. */
	if ( i == 0 )
	    omega = 0;
	else
	    omega = omega1 ^ tauT1;
	omega1 = omega;
	tauT1 = tauT;

	/* alpha[i] is xor of omega[i] and sigmaT[i] */
	alpha[i] = omega ^ sigmaT;
    }

    /* Build coordinates by taking bits from alphas. */
    for ( b = 0; b < n; b++ )
    {
	register int ab, bt;
	ab = 0;
	bt = bit[b];
	/* Unroll the loop that stuffs bits into ab.
	 * The result is shifted left by 8-m bits.
	 */
	switch( m )
	{
	case 8:	if ( alpha[7] & bt) ab |= 0x01;
	case 7:	if ( alpha[6] & bt) ab |= 0x02;
	case 6:	if ( alpha[5] & bt) ab |= 0x04;
	case 5:	if ( alpha[4] & bt) ab |= 0x08;
	case 4:	if ( alpha[3] & bt) ab |= 0x10;
	case 3:	if ( alpha[2] & bt) ab |= 0x20;
	case 2:	if ( alpha[1] & bt) ab |= 0x40;
	case 1:	if ( alpha[0] & bt) ab |= 0x80;
	}
	a[b] = ab >> (8 - m);
    }
}

X/*****************************************************************
 * TAG( hilbert_c2i )
 * 
 * Convert coordinates of a point on a Hilbert curve to its index.
 * Inputs:
 * 	n:	Number of coordinates.
 * 	m:	Number of bits/coordinate.
 * 	a:	Array of n m-bit coordinates.
 * Outputs:
 * 	r:	Output index value.  n*m bits.
 * Assumptions:
 * 	n*m <= 32, n <= 8, m <= 8.
 * Algorithm:
 * 	Invert the above.
 */
void
hilbert_c2i( n, m, a, r)
int n;
int m;
int a[];
long int *r;
{
    byte rho[8], J, sigma, tau,
    	 sigmaT, tauT, tauT1, omega, omega1, alpha[8];
    register int i, b;
    int Jsum;
    long int rl;

    calctables(n);

    /* Unpack the coordinates into alpha[i]. */
    /* First, zero out the alphas. */
    switch ( m ) {
    case 8: alpha[7] = 0;
    case 7: alpha[6] = 0;
    case 6: alpha[5] = 0;
    case 5: alpha[4] = 0;
    case 4: alpha[3] = 0;
    case 3: alpha[2] = 0;
    case 2: alpha[1] = 0;
    case 1: alpha[0] = 0;
    }

    /* The loop that unpacks bits of a[b] into alpha[i] has been unrolled.
     * The high-order bit of a[b] has to go into alpha[0], so we pre-shift
     * a[b] so that its high-order bit is always in the 0x80 position.
     */
    for ( b = 0; b < n; b++ )
    {
	register int bt = bit[b], t = a[b] << (8 - m);

	switch (m)
	{
	case 8: if ( t & 0x01 ) alpha[7] |= bt;
	case 7: if ( t & 0x02 ) alpha[6] |= bt;
	case 6: if ( t & 0x04 ) alpha[5] |= bt;
	case 5: if ( t & 0x08 ) alpha[4] |= bt;
	case 4: if ( t & 0x10 ) alpha[3] |= bt;
	case 3: if ( t & 0x20 ) alpha[2] |= bt;
	case 2: if ( t & 0x40 ) alpha[1] |= bt;
	case 1: if ( t & 0x80 ) alpha[0] |= bt;
	}
    }

    Jsum = 0;
    for ( i = 0; i < m; i++ )
    {
	/* Compute omega[i] = omega[i-1] xor tauT[i-1]. */
	if ( i == 0 )
	    omega = 0;
	else
	    omega = omega1 ^ tauT1;

	sigmaT = alpha[i] ^ omega;
	/* sigma[i] is the left circular shift of sigmaT[i]. */
	if ( Jsum != 0 )
	    sigma = circshift[sigmaT][n - Jsum];
	else
	    sigma = sigmaT;

	rho[i] = s_to_p[sigma];

	/* Now we can get the principle position. */
	J = p_to_J[rho[i]];

	/* And compute tau[i] and tauT[i]. */
	/* tau[i] complements low bit of sigma[i], and bit at J[i] if
	 * necessary to make even parity.
	 */
	tau = sigma ^ 1;
	if ( parity[tau] )
	    tau ^= bit[J];

	/* tauT[i] is right circular shift of tau[i]. */
	if ( Jsum != 0 )
	    tauT = circshift[tau][Jsum];
	else
	    tauT = tau;
	Jsum += J;
	if ( Jsum >= n )
	    Jsum -= n;

	/* Carry forth the "i-1" values. */
	tauT1 = tauT;
	omega1 = omega;
    }

    /* Pack rho values into r. */
    rl = 0;
    for ( i = 0; i < m; i++ )
	rl = (rl << n) | rho[i];
    *r = rl;
}


#ifdef test
#include <stdio.h>

main()
{
    int a[8], n, m, i;
    long int r, r1;

    for (;;)
    {
	printf( "Enter n, m: " );
	scanf( "%d", &n );
	if ( n == 0 )
	    break;
	scanf( "%d", &m );
	while ( (i = getchar()) != '\n' && i != EOF )
	    ;
	if ( i == EOF )
	    break;
	for ( r = 0; r < 1 << (n*m); r++ )
	{
	    hilbert_i2c( n, m, r, a );
	    if ( n*m <= 6 )
	    {
		printf( "a = " );
		for ( i = 0; i < n; i++ )
		    printf( "0x%0*x ", (m+3)/4, a[i] );
	    }
	    hilbert_c2i( n, m, a, &r1 );
	    if ( n*m <= 6 )
		printf( "r = 0x%0*x\n", (n*m+3)/4, r1 );
	    else if ( r != r1 )
		printf( "r = 0x%0*x; r1 = 0x%0*x\n", (n*m+3)/4, r,
			(n*m+3)/4, r1 );
	}
    }
}

p1t( tbl, n )
byte tbl[];
int n;
{
    int i;

    for ( i = 0; i < n; i++ )
	printf( "%02x ", tbl[i] );
    putchar( '\n' );
}

p2t( tbl, n, m )
byte tbl[][8];
int n;
{
    int i;

    for ( i = 0; i < n; i++ )
    {
	printf( "%3d: ", i );
	p1t( tbl[i], m );
    }
}


#endif

//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 hilbert.c
	/bin/echo -n '	'; /bin/ls -ld hilbert.c
fi
/bin/echo 'Extracting hilbert_c2i.3'
sed 's/^X//' <<'//go.sysin dd *' >hilbert_c2i.3
X.so man3/hilbert.3
X.\" Copyright (c) 1991, University of Michigan
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 hilbert_c2i.3
	/bin/echo -n '	'; /bin/ls -ld hilbert_c2i.3
fi
/bin/echo 'Extracting hilbert_i2c.3'
sed 's/^X//' <<'//go.sysin dd *' >hilbert_i2c.3
X.so man3/hilbert.3
X.\" Copyright (c) 1991, University of Michigan
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 hilbert_i2c.3
	/bin/echo -n '	'; /bin/ls -ld hilbert_i2c.3
fi
/bin/echo 'Extracting test-hilbert.c'
sed 's/^X//' <<'//go.sysin dd *' >test-hilbert.c
#include <stdio.h>
#include "X11/Xlib.h"

int black, white;
#define	MAXBUF	(32768 / sizeof(XPoint))

main()
{
    Display *dpy;
    Window win;
    XEvent ev;
    int xdim = 512, ydim = 512;
    int done = 0, n = 1;

    if ( (dpy = XOpenDisplay("")) == NULL )
    {
	fprintf( stderr, "Can't open display.\n" );
	exit( 1 );
    }

    black = BlackPixel( dpy, 0 );
    white = WhitePixel( dpy, 0 );

    win = XCreateSimpleWindow( dpy, RootWindow( dpy, 0 ), 0, 0, xdim, ydim,
			       3, white, black );
    XSelectInput( dpy, win,
		  ButtonPressMask|ButtonReleaseMask|
		  ExposureMask|StructureNotifyMask );

    XMapWindow( dpy, win );

    do {
	XNextEvent( dpy, &ev );
	switch( ev.type )
	{
	case ConfigureNotify:
	    xdim = ev.xconfigure.width;
	    ydim = ev.xconfigure.height;
	    break;

	case Expose:
	    drawit( dpy, win, xdim, ydim, n );
	    break;

	case ButtonPress:
	    if ( ++n > 8 )
		n = 1;
	    drawit( dpy, win, xdim, ydim, n );
	    break;

	case ButtonRelease:
	    if ( ev.xbutton.state & ShiftMask )
		done = 1;
	    break;
	}
    } while ( !done );
}


drawit( dpy, win, xdim, ydim, n )
Display *dpy;
Window win;
int xdim, ydim, n;
{
    long int i, dim, side;
    int nbuf;
    int a[2];
    XPoint *pts;
    register XPoint *pt;
    GC gc = DefaultGC( dpy, 0 );

    srandom(getpid());

    XSetForeground( dpy, gc, white );
    XClearWindow( dpy, win );

    dim = 1 << (2*n);
    side = 1 << n;

    if ( dim > MAXBUF )
	nbuf = MAXBUF;
    else
	nbuf = dim;

    pts = (XPoint *)malloc(sizeof(XPoint) * nbuf);

    if ( pts == 0 )
    {
	fprintf( "Malloc failed for size %d\n", n );
	exit( 1 );
    }

    for ( i = 0, pt = pts; i < dim; i+= abs((random() / 10000) % 100), pt++ )
    {
	hilbert_i2c( 2, n, i, a );
	if ( pt - pts >= nbuf )
	{
	    XDrawLines( dpy, win, gc, pts, (int)(pt - pts), CoordModeOrigin );
	    pt = pts;
	}
	pt->x = (a[0] * xdim) / side;
	pt->y = (a[1] * ydim) / side;
    }

    XDrawLines( dpy, win, gc, pts, (int)(pt - pts), CoordModeOrigin );

    free( pts );
}
//go.sysin dd *
made=TRUE
if [ $made = TRUE ]; then
	/bin/chmod 644 test-hilbert.c
	/bin/echo -n '	'; /bin/ls -ld test-hilbert.c
fi

exit 0
--
=Spencer W. Thomas 		EECS Dept, U of Michigan, Ann Arbor, MI 48109
spencer@eecs.umich.edu		313-936-2616 (8-6 E[SD]T M-F)