<pre>
/*
 * ============================================================================
 * File:	sample.c
 * Purpose:	Explore different regular sampling patterns
 *
 * Author:	David Martindale
 *
 * ============================================================================
 *
 * $Log$
 */


#define	WSIZE		3.6		/* window size */

/*
 * Define non-zero to use double buffering.
 */
#define DOUBLEBUFFER	1

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <GL/glut.h>

char usage[] =
"Usage: sample [-w]\n"
;

/*
 * Boolean type and values.
 */
typedef int	Bool;
#define	FALSE	0
#define	TRUE	1

/*
 * Display control.
 */
int	vp_x, vp_y;			/* viewport width/height */
float	win_x, win_y;			/* window half-width/height */
Bool	white_bkg = FALSE;		/* white background */

/*
 * Forward declarations.
 */

void	make_window(char *, void (*)());
void	reshape(int, int);
void	project();
void	display_square();
void	display_qc();
void	display_rot();
void	display_hex1();
void	display_hex2();
void	keyboard(unsigned char, int, int);
void	draw_rectangle(float, float, float, float);
void	draw_diamond(float, float, float, float);
void	draw_hexagon(float, float, float, float, Bool);
void	draw_grid(float, float, float, Bool);

#define	CIRCLE_SEGS	36

int
main(int argc, char **argv)
{
	glutInit(&argc, argv);		/* may eat some arguments */

	if (argc > 1 && strcmp(argv[1], "-w") == 0) {
		white_bkg = TRUE;
		argv++;
		argc--;
	}
	if (argc != 1) {
		fprintf(stderr, usage);
		return 1;
	}

	/*
	 * Set up windows.
	 * Pass control to glut.
	 */
	make_window("square", display_square);
	make_window("quincunx", display_qc);
	make_window("rotated", display_rot);
	make_window("hexagonal1", display_hex1);
	make_window("hexagonal2", display_hex2);
	glutMainLoop();

	return 0;
}

/*
 * make_window: Create the GL window and menu.
 */

#define	DEFSIZE	500		/* Default window size */

void
make_window(char *name, void (*display)())
{

#if DOUBLEBUFFER
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
#else
	glutInitDisplayMode(GLUT_RGB);
#endif
	glutInitWindowSize(DEFSIZE, DEFSIZE);

	/*
	 * Create the window.
	 * Register callbacks.
	 */
	glutCreateWindow(name);
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboard);

	/*
	 * Set background colour.
	 * Set point size.
	 */
	if (white_bkg)
		glClearColor(1.0, 1.0, 1.0, 0.0);
	glPointSize(2.0);
}

/*
 * keyboard: handle keypress events.
 *
 * "q" or ESC to quit.
 */

#define	ESC	033		/* ASCII Escape key */

void
keyboard(unsigned char key, int x, int y)
{
	switch (key) {

	 case 'q':
	 case 'Q':
	 case ESC:
	 	exit(0);
		return;

	}
}

/*
 * reshape: window resize callback.
 * Just save the size and call project() to set up for the
 * new window size.
 */

void
reshape(int w, int h)
{
	/*
	 * Make the whole window the viewport.
	 */
	glViewport(0, 0, w, h);

	vp_x = w;
	vp_y = h;
	project();
}

/*
 * project: adjust the projection matrix and viewport for a given
 * size of window.  The window is always [-WSIZE..WSIZE] in both axes.
 */

void
project()
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-WSIZE, WSIZE, -WSIZE, WSIZE, -1, 1);
}

/*
 * display: display callback function.
 *
 * One for each window.
 */

GLfloat	black[3] =	{ 0, 0, 0 };
GLfloat	red[3] =	{ 1, 0, 0 };
GLfloat	green[3] =	{ 0, 1, 0 };
GLfloat	blue[3] =	{ 0, 0, 1 };
GLfloat	cyan[3] =	{ 0, 1, 1 };
GLfloat	magenta[3] =	{ 1, 0, 1 };
GLfloat	yellow[3] =	{ 1, 1, 0 };
GLfloat	white[3] =	{ 1, 1, 1 };
GLfloat	grey[3] =	{ 0.5, 0.5, 0.5 };

/*
 * Ordinary square grid sampling pattern.
 *
 * The square box shows the "natural" tiling of the space.
 */

void
display_square()
{
	/*
	 * Clear background.
	 * Set drawing colour.
	 */
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3fv(white);
	draw_grid(WSIZE, 1.0, 1.0, FALSE);

	/*
	 * Draw frequency response boxes.
	 */
	glColor3fv(red);
	draw_rectangle(-1.0,  1.0, 0.5, 0.5);
	draw_rectangle( 0.0,  1.0, 0.5, 0.5);
	draw_rectangle( 1.0,  1.0, 0.5, 0.5);
	draw_rectangle(-1.0,  0.0, 0.5, 0.5);
	draw_rectangle( 0.0,  0.0, 0.5, 0.5);
	draw_rectangle( 1.0,  0.0, 0.5, 0.5);
	draw_rectangle(-1.0, -1.0, 0.5, 0.5);
	draw_rectangle( 0.0, -1.0, 0.5, 0.5);
	draw_rectangle( 1.0, -1.0, 0.5, 0.5);

#if DOUBLEBUFFER
	glutSwapBuffers();
#else
	glFlush();
#endif
}

/*
 * Quincunx sampling pattern (version 1)
 *
 * This is just the square grid pattern with alternate lines
 * shifted by half the horizontal pixel pitch.
 * This corresponds to one definition of "quincunx".
 * Note that the horizontal distance between adjacent samples
 * remains 1, but the diagonal distance to the nearest sample
 * above and below is sqrt(5)/2.
 */

void
display_qc()
{
	/*
	 * Calculate grid spacing parameters
	 */
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3fv(white);
	draw_grid(WSIZE, 1.0, 1.0, TRUE);

	glColor3fv(red);
	draw_diamond( 0.0,  2.0, 0.5, 1.0);
	draw_diamond(-0.5,  1.0, 0.5, 1.0);
	draw_diamond( 0.5,  1.0, 0.5, 1.0);
	draw_diamond(-1.0,  0.0, 0.5, 1.0);
	draw_diamond( 0.0,  0.0, 0.5, 1.0);
	draw_diamond( 1.0,  0.0, 0.5, 1.0);
	draw_diamond(-0.5, -1.0, 0.5, 1.0);
	draw_diamond( 0.5, -1.0, 0.5, 1.0);
	draw_diamond( 0.0, -2.0, 0.5, 1.0);

#if DOUBLEBUFFER
	glutSwapBuffers();
#else
	glFlush();
#endif
}

/*
 * Rotated/quincunx sampling pattern.
 *
 * This is just a rotation of the square grid by 45 degrees, keeping the
 * minimum spacing at 1 unit as before.
 * This is a different definition of "quincunx".
 */

void
display_rot()
{
	float	xs, ys;

	/*
	 * Calculate grid spacing parameters
	 */
	xs = sqrt(2.0);
	ys = sqrt(2.0) / 2.0;

	glClear(GL_COLOR_BUFFER_BIT);
	glColor3fv(white);
	draw_grid(WSIZE, xs, ys, TRUE);
	glColor3fv(red);
	draw_diamond(0.0,     ys*2.0,  xs/2.0, ys);
	draw_diamond(-xs/2.0, ys,      xs/2.0, ys);
	draw_diamond( xs/2.0, ys,      xs/2.0, ys);
	draw_diamond(-xs,     0.0,     xs/2.0, ys);
	draw_diamond(0.0,     0.0,     xs/2.0, ys);
	draw_diamond(xs,      0.0,     xs/2.0, ys);
	draw_diamond(-xs/2.0, -ys,     xs/2.0, ys);
	draw_diamond( xs/2.0, -ys,     xs/2.0, ys);
	draw_diamond(0.0,     -ys*2.0, xs/2.0, ys);

#if DOUBLEBUFFER
	glutSwapBuffers();
#else
	glFlush();
#endif
}

/*
 * Hexagonal sampling pattern.
 *
 * This is the rotated grid rescaled to give a hexagonal grid.
 * The spacing between samples in a row returns to 1 unit, the same as
 * the square grid, while the spacing between rows becomes sqrt(3)/2.
 * The minimum spacing between neighbours is 1.
 *
 * The red square box from the rotated grid is scaled by the same
 * amount to show how it would fit if it was used to tile the plane.
 * A green hexagon is also drawn to show a better tiling for the hexagonal
 * grid.
 */

void
display_hex1()
{
	float	xs, ys, hsx, hsy;

	xs = 1.0;
	ys = sqrt(3.0) / 2.0;
	hsx = xs / sqrt(3.0);
	hsy = ys * 2.0 / 3.0;

	glClear(GL_COLOR_BUFFER_BIT);
	glColor3fv(white);
	draw_grid(WSIZE, xs, ys, TRUE);

	glColor3fv(red);
	draw_diamond(0.0,     ys*2.0,  xs/2.0, ys);
	draw_diamond(-xs/2.0, ys,      xs/2.0, ys);
	draw_diamond( xs/2.0, ys,      xs/2.0, ys);
	draw_diamond(-xs,     0.0,     xs/2.0, ys);
	draw_diamond(0.0,     0.0,     xs/2.0, ys);
	draw_diamond(xs,      0.0,     xs/2.0, ys);
	draw_diamond(-xs/2.0, -ys,     xs/2.0, ys);
	draw_diamond( xs/2.0, -ys,     xs/2.0, ys);
	draw_diamond(0.0,     -ys*2.0, xs/2.0, ys);

	glColor3fv(green);
	draw_hexagon(0.0,     ys*2.0,  hsx, hsy, TRUE);
	draw_hexagon(-xs/2.0, ys,      hsx, hsy, TRUE);
	draw_hexagon( xs/2.0, ys,      hsx, hsy, TRUE);
	draw_hexagon(-xs,     0.0,     hsx, hsy, TRUE);
	draw_hexagon(0.0,     0.0,     hsx, hsy, TRUE);
	draw_hexagon(xs,      0.0,     hsx, hsy, TRUE);
	draw_hexagon(-xs/2.0, -ys,     hsx, hsy, TRUE);
	draw_hexagon( xs/2.0, -ys,     hsx, hsy, TRUE);
	draw_hexagon(0.0,     -ys*2.0, hsx, hsy, TRUE);

#if DOUBLEBUFFER
	glutSwapBuffers();
#else
	glFlush();
#endif
}

/*
 * Alternate hexagonal grid pattern.
 *
 * The horizontal spacing is now sqrt(3) and the vertical is 0.5.
 * The distance between a point and its closest neighbours is still 1.
 *
 * Perhaps surprisingly, the result is still a uniform hexagonal grid,
 * but the set of 6 points which are the neighbours of any original
 * point have changed.  Also, the orientation of the hexagons has
 * changed; two of the vertices are on the Y axis now instead of the
 * X axis.
 *
 * The red square box from the rotated pattern is drawn to show
 * how it is now shaped.  A green hexagonal grid shows the conventional
 * tiling with these points.
 */

void
display_hex2()
{
	float	xs, ys, hsx, hsy;

	xs = sqrt(3.0);
	ys = 0.5;
	hsx = xs / sqrt(3.0);
	hsy = ys * 2.0 / 3.0;

	glClear(GL_COLOR_BUFFER_BIT);
	glColor3fv(white);
	draw_grid(WSIZE, xs, ys, TRUE);

	glColor3fv(red);
	draw_diamond(0.0,     ys*2.0,  xs/2.0, ys);
	draw_diamond(-xs/2.0, ys,      xs/2.0, ys);
	draw_diamond( xs/2.0, ys,      xs/2.0, ys);
	draw_diamond(-xs,     0.0,     xs/2.0, ys);
	draw_diamond(0.0,     0.0,     xs/2.0, ys);
	draw_diamond(xs,      0.0,     xs/2.0, ys);
	draw_diamond(-xs/2.0, -ys,     xs/2.0, ys);
	draw_diamond( xs/2.0, -ys,     xs/2.0, ys);
	draw_diamond(0.0,     -ys*2.0, xs/2.0, ys);

	hsx = xs / 3.0;
	hsy = ys * 2.0 / sqrt(3.0);
	glColor3fv(green);
	draw_hexagon(0.0,     ys*2.0,  hsx, hsy, FALSE);
	draw_hexagon(-xs/2.0, ys,      hsx, hsy, FALSE);
	draw_hexagon( xs/2.0, ys,      hsx, hsy, FALSE);
	draw_hexagon(-xs,     0.0,     hsx, hsy, FALSE);
	draw_hexagon(0.0,     0.0,     hsx, hsy, FALSE);
	draw_hexagon(xs,      0.0,     hsx, hsy, FALSE);
	draw_hexagon(-xs/2.0, -ys,     hsx, hsy, FALSE);
	draw_hexagon( xs/2.0, -ys,     hsx, hsy, FALSE);
	draw_hexagon(0.0,     -ys*2.0, hsx, hsy, FALSE);

#if DOUBLEBUFFER
	glutSwapBuffers();
#else
	glFlush();
#endif
}

/*
 * draw_rectangle: Draw a rectangle centred on the specified point
 *	given its extent in X and Y.
 */

void
draw_rectangle(float x, float y, float xsize, float ysize)
{
	glBegin(GL_LINE_LOOP);
	glVertex2f(x-xsize, y-ysize);
	glVertex2f(x+xsize, y-ysize);
	glVertex2f(x+xsize, y+ysize);
	glVertex2f(x-xsize, y+ysize);
	glEnd();
}

/*
 * draw_ellipse: Draw an ellipse (or circle) centred on the specified point
 *	given its semi-axis lengths and the number of points to draw.
 */

void
draw_ellipse(float x, float y, float xsize, float ysize, int npoints)
{
	int	point;
	float	theta;

	glBegin(GL_LINE_LOOP);
	for (point = 0; point < npoints; point++) {
		theta = (float) point / npoints * 2.0 * M_PI;
		glVertex2f(x + cosf(theta) * xsize, y + sinf(theta) * ysize);
	}
	glEnd();
}

/*
 * draw_diamond: Draw a 4-sided "diamond" centred on the specified point
 *	given its extent in X and Y.
 */

void
draw_diamond(float x, float y, float xsize, float ysize)
{
	glBegin(GL_LINE_LOOP);
	glVertex2f(x-xsize, y);
	glVertex2f(x,       y-ysize);
	glVertex2f(x+xsize, y);
	glVertex2f(x,       y+ysize);
	glEnd();
}

/*
 * draw_hexagon: Draw a hexagon centred on the specified point
 *	given its extent in X and Y.
 *
 * By default, it is oriented so that two of its vertices are on
 * the X axis.  The "orient" flag, if true, changes that to put
 * two of the vertices on the X axis instead.
 */

#define	ROOT3_2		0.8660254	/* sqrt(3)/2 */

struct {
	float	x, y;
} hex_vert[6] = {
	{  1.0,  0.0     },
	{  0.5, -ROOT3_2 },
	{ -0.5, -ROOT3_2 },
	{ -1.0,  0.0     },
	{ -0.5,  ROOT3_2 },
	{  0.5,  ROOT3_2 }
};

void
draw_hexagon(float xcent, float ycent, float xsize, float ysize, Bool orient)
{
	int	i;
	float	x, y;

	glBegin(GL_LINE_LOOP);
	for (i = 0; i < 6; i++) {
		if (orient) {		/* transpose X and Y from table */
			x = hex_vert[i].y;
			y = hex_vert[i].x;
		} else {
			x = hex_vert[i].x;
			y = hex_vert[i].y;
		}
		x = x * xsize + xcent;
		y = y * ysize + ycent;
		glVertex2f(x, y);
		glVertex2f(x, y);
	}
	glEnd();
}

/*
 * draw_grid: Draw a sampling grid with specified size and horizontal
 *	and vertical spacing.  Optionally, offset odd-numbered rows
 *	by half the horizontal space (to give a quincunx pattern).
 */

void
draw_grid(float size, float xspacing, float yspacing, int offset)
{
	int	row, col, min, max, ncols;
	float	*xpos;
	float	x, y, xoffset;

	/*
	 * First, calculate the set of standard X positions for the points.
	 * Add one extra on the right, since "offset" shifts all points left
	 * and may make another one visible.
	 */
	min = (int) ceil(-size / xspacing);
	max = (int) floor(size / xspacing) + 1;
	ncols = max - min + 1;
	xpos = new float[ncols];

	for (col = min; col <= max; col++)
		xpos[col - min] = col * xspacing;

	/*
	 * Calculate the limits of Y position.
	 * Draw the grid.
	 */
	min = (int) ceil(-size / yspacing);
	max = (int) floor(size / yspacing);
	glBegin(GL_POINTS);
	for (row = min; row <= max; row++) {
		y = row * yspacing;
		xoffset = 0.0;
		if (offset && (row&01) != 0)	/* odd-numbered row? */
			xoffset = xspacing * 0.5;
		for (col = 0; col < ncols; col++) {
			x = xpos[col] - xoffset;
			glVertex2f(x, y);
		}
	}
	glEnd();
}
</pre>

