
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 * Earlier versions Copyright 1992,  Minnesota Supercomputer Center Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/bob/RCS/draw.c,v 2.2 92/10/27 16:50:23 ken Exp Locker: ken $
 *
 * $Log:	draw.c,v $
 * Revision 2.2  92/10/27  16:50:23  ken
 * Added basic point support
 * 
 * Revision 2.1  92/10/19  17:03:36  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  17:03:01  ken
 * Initial revision
 * 
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>

#include <X11/Xlib.h>
#include <gl/gl.h>

#include "util.h"
#include "glutil.h"
#include "draw.h"

FinderEnum	finderMode = FinderSync;
unsigned	finderPlane = 0;


void DrawVoxel(int ix, int direction, unsigned dim[3], float *pos[3],
	       unsigned char *cdata, float cmap[256][4],
	       unsigned char *faces, unsigned soft)
{
    /* Time critical.
     * 
     * This is the core routine for drawing voxels.  The fastest way to
     * draw would be to prepare the colors and locations ahead of
     * time and draw from that array.  This entails a huge overhead in
     * memory (approx 32x), but acheives the fastest quad rate (350 k/sec).
     *
     * Next best is to construct the color and location information
     * on the fly, which is what this routine does.  The maximum quad rate
     * for this technique is about 260 k/sec.
     *
     * The faces array contains one byte per strip, with bits set
     * depending on whether a 1/8 section is opaque.  If all the
     * bits are set, the entire strip is drawn.
     *
     * This routine is passed the correct dimensions, positions,
     * data brick and faces array for the given direction ix.
     *
     * 'soft' indicates that the edges are faded to black.
     * A special color table, btab, is used to color these verticies.
     */
    register int	k, kstop;
    register unsigned	i, j, l, m, ibit, drawing;
    register unsigned	iy, iz, nx, ny, nx1, nx8, ny1;
    register float	**c1, **c2, *v1, *v2;
    register unsigned	c1b, c2b, v1b, v2b;
    register float	*posy, *posz, pp;
    register unsigned char *cd;
    register unsigned char *face;
    float		vnorm[3];
    
    /* Save buffers from pass to pass, 
     * hopefully saving speed on allocing memory.
     */
    static float	*ctab = NULL, *btab = NULL;
    static float	**cbuf = NULL, *vbuf = NULL;
    static unsigned	*qdraw = NULL, *build = NULL;
    
    blendfunction(BF_SA, BF_MSA);

    if (!ctab) {
	ctab = CallocType(float, 1024+4);
	MemCheck(ctab);
	while ((unsigned)ctab & 0xf)	/* Quad word align */
	    ++ctab;
	btab = CallocType(float, 1024+4);
	MemCheck(btab);
	while ((unsigned)btab & 0xf)	/* Quad word align */
	    ++btab;
    }
    
    /* Create color table, ctab, and blank table, btab
     */
    for (i = j = 0; j < 256; i += 4, ++j) {
	ctab[i  ] = cmap[j][0];
	ctab[i+1] = cmap[j][1];
	ctab[i+2] = cmap[j][2];
	ctab[i+3] = cmap[j][3];
    }
    if (soft)
	for (i = j = 0; j < 256; i += 4, ++j) {
	    btab[i  ] = cmap[j][0];	/* Same color as ctab */
	    btab[i+1] = cmap[j][1];
	    btab[i+2] = cmap[j][2];
	    btab[i+3] = 0;		/* Clear opacity */
	}

    /* Indecies of the other axes
     */
    iy = (ix + 1) % 3;
    iz = (ix + 2) % 3;

    /* Loop invariants
     */
    nx = dim[ix];
    ny = dim[iy];
    nx1 = nx - 1;
    nx8 = RoundUp(nx, 8) / 8;
    ny1 = ny - 1;
    
    posy = pos[iy];
    posz = pos[iz];
    
    vnorm[ix] = 0;
    vnorm[iy] = 0;
    vnorm[iz] = direction;
    
    /* Allocate buffers for drawing.
     * vbuf, and cbuf are filled on the fly for each quad strip.
     * They are double length to hold both sides of the quad strip.
     * qdraw and build control drawing.
     */
    vbuf = ReallocType(vbuf, float, 8*nx + 4);	MemCheck(vbuf);
    cbuf = ReallocType(cbuf, float *, 2*nx);	MemCheck(cbuf);
    qdraw = ReallocType(qdraw, unsigned, ny);	MemCheck(qdraw);
    build = ReallocType(build, unsigned, ny);	MemCheck(build);

    /* Set buffer indecies.  Align vbuf to quad boundary.
     */
    c1b = 0;
    c2b = nx;

    v1b = 0;
    while ((unsigned)(vbuf + v1b) & 0xf)
	++v1b;
    v2b = v1b + 4*nx;

    /* The X postions are always the same, so declare them here.
     * v1 points to the lower edge, v2 the upper edge.
     */
    v1 = vbuf + v1b;
    v2 = vbuf + v2b;
    for (i = 0, l = ix; i < nx; ++i, l += 4)
	v1[l] = v2[l] = pos[ix][i];
    
    /* Increment Z in the proper direction.  For soft edges,
     * Don't display the front & back planes (looks consistent
     * as the volume switched view directions.)
     */
    if (soft)
	if (direction > 0) {
	    k = 1;
	    kstop = dim[iz] - 1;
	} else {
	    k = dim[iz] - 2;
	    kstop = 0;
	}
    else
	if (direction > 0) {
	    k = 0;
	    kstop = dim[iz];
	} else {
	    k = dim[iz] - 1;
	    kstop = -1;
	}
    for (; k != kstop; k += direction) {
	
	/* Select the correct plane of voxel data and the
	 * correct strip of face values.
	 */
	cd = cdata + k*nx*ny;
	face = faces + k*ny;

	/* Construct the build and qdraw flags.  A vertex entry needs
	 * to be built if either it or its neighbors are opaque.
	 * A vertex entry is drawn if it or its lower neighbor are opaque.
	 * Drawing begins with the j=1 strip.
	 */
	for (j = 1; j < ny1; ++j) {
	    qdraw[j] = face[j-1] | face[j];
	    build[j] = qdraw[j]  | face[j+1];
	}
	qdraw[0]   = 0;
	build[0]   = face[0] | face[1];
	qdraw[ny1] = face[ny1-1] | face[ny1];
	build[ny1] = qdraw[ny1];

	/* Fill in the Z location for this plane.
	 */
	v1 = vbuf + v1b;
	v2 = vbuf + v2b;
	pp = posz[k];
	for (i = nx, l = iz; i; --i, l += 4)
	    v1[l] = v2[l] = pp;

	for (j = 0; j < ny; ++j, cd += nx) {

	    /* v1, v2 and c1, c2 swap every strip.
	     */
	    i = c1b; c1b = c2b; c2b = i;
	    i = v1b; v1b = v2b; v2b = i;
	    
	    v1 = vbuf + v1b;
	    v2 = vbuf + v2b;
	    c1 = cbuf + c1b;
	    c2 = cbuf + c2b;
	    pp = posy[j];

	    if (soft)
		/* Soft edges need special treatment at the edges.
		 * The edge color array points into btab, the interior
		 * into ctab.
		 */
		switch (build[j]) {

		  case 0:
		    /* Nothing to build
		     */
		    break;

		  case 0xff:
		    /* Build the whole strip in one run.  
		     * The end strips are allocated all out of btab.
		     */
		    if (j > 0 && j < ny1) {
			for (i = 0, l = iy; i < nx; ++i, l += 4) {
			    c2[i] = ctab + 4*cd[i];
			    v2[l] = pp;
			}
			c2[0]   = btab + 4*cd[0];
			c2[nx1] = btab + 4*cd[nx1];

		    } else
			for (i = 0, l = iy; i < nx; ++i, l += 4) {
			    c2[i] = btab + 4*cd[i];
			    v2[l] = pp;
			}
		    break;

		  default:
		    /* Build each eighth as needed.
		     * The end strips are allocated all out of btab.
		     */
		    if (j > 0 && j < ny1) {
			for (i = 0, l = iy, ibit = 1; i < nx; ibit <<= 1) {
			    if (ibit & build[j]) {
				if ((m = i + nx8) > nx)
				    m = nx;
				for (; i < m; ++i, l += 4) {
				    c2[i] = ctab + 4*cd[i];
				    v2[l] = pp;
				}
			    } else {
				i += nx8;
				l += 4*nx8;
			    }
			}
			c2[0]   = btab + 4*cd[0];
			c2[nx1] = btab + 4*cd[nx1];
			
		    } else
			for (i = 0, l = iy, ibit = 1; i < nx; ibit <<= 1) {
			    if (ibit & build[j]) {
				if ((m = i + nx8) > nx)
				    m = nx;
				for (; i < m; ++i, l += 4) {
				    c2[i] = btab + 4*cd[i];
				    v2[l] = pp;
				}
			    } else {
				i += nx8;
				l += 4*nx8;
			    }
			}
		    break;
		}
	    else
		/* Hard edges are allocated out of ctab only.
		 */
		switch (build[j]) {

		  case 0:
		    /* Nothing to build
		     */
		    break;

		  case 0xff:
		    /* Build the whole strip in one run.
		     */
		    for (i = 0, l = iy; i < nx; ++i, l += 4) {
			c2[i] = ctab + 4*cd[i];
			v2[l] = pp;
		    }
		    break;

		  default:
		    /* Build one eighth at a time.
		     */
		    for (i = 0, l = iy, ibit = 1; i < nx; ibit <<= 1) {
			if (ibit & build[j]) {
			    if ((m = i + nx8) > nx)
				m = nx;
			    for (; i < m; ++i, l += 4) {
				c2[i] = ctab + 4*cd[i];
				v2[l] = pp;
			    }
			} else {
			    i += nx8;
			    l += 4*nx8;
			}
		    }
		    break;
		}

	    switch (qdraw[j]) {

	      case 0:
		/* Nothing to draw.
		 */
		break;

	      case 0xff:
		/* Draw one full strip.
		 */
		bgnqstrip();
		n3f(vnorm);
		for (i = nx / 4; i; --i) {
		    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
		    c4f(c1[1]); v3f(v1+ 4); c4f(c2[1]); v3f(v2+ 4);
		    c4f(c1[2]); v3f(v1+ 8); c4f(c2[2]); v3f(v2+ 8);
		    c4f(c1[3]); v3f(v1+12); c4f(c2[3]); v3f(v2+12);
		    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
		}
		if (nx & 0x2) {
		    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
		    c4f(c1[1]); v3f(v1+ 4); c4f(c2[1]); v3f(v2+ 4);
		    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
		}
		if (nx & 0x1) {
		    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
		}
		endqstrip();
		break;

	      default:
		/* Draw a strip one eighth at a time.  Begin and
		 * end the quad strip as needed.
		 */
		drawing = 1;
		bgnqstrip();
		n3f(vnorm);
		for (i = nx, ibit = 1; i; ibit <<= 1) {
		    l = MIN(nx8, i);
		    if (ibit & qdraw[j]) {
			if (!drawing) {
			    bgnqstrip();
			    n3f(vnorm);
			    drawing = 1;
			}
			for (m = l / 4; m; --m) {
			    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
			    c4f(c1[1]); v3f(v1+ 4); c4f(c2[1]); v3f(v2+ 4);
			    c4f(c1[2]); v3f(v1+ 8); c4f(c2[2]); v3f(v2+ 8);
			    c4f(c1[3]); v3f(v1+12); c4f(c2[3]); v3f(v2+12);
			    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
			}
			if (l & 0x2) {
			    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
			    c4f(c1[1]); v3f(v1+ 4); c4f(c2[1]); v3f(v2+ 4);
			    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
			}
			if (l & 0x1) {
			    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
			    c1 += 1;    v1 += 4;    c2 += 1;    v2 += 4;
			}
		    } else {
			if (drawing) {
			    endqstrip();
			    drawing = 0;
			}
			v1 += l*4;
			v2 += l*4;
			c1 += l;
			c2 += l;
		    }
		    i -= l;
		}
		if (drawing)
		    endqstrip();
		break;
	    }
	}
    }
    blendfunction(BF_ONE, BF_ZERO);
}


void DrawStrideVoxel(int ix, int direction, unsigned dim[3], float *pos[3],
		     unsigned char *cdata, float cmap[256][4],
		     unsigned char *faces, unsigned stride)
{
    /* Time critical.
     * 
     * This is the core routine for drawing voxels.  The fastest way to
     * draw would be to prepare the colors and locations ahead of
     * time and draw from that array.  This entails a huge overhead in
     * memory (approx 32x), but acheives the fastest quad rate (350 k/sec).
     *
     * Next best is to construct the color and location information
     * on the fly, which is what this routine does.  The maximum quad rate
     * for this technique is about 260 k/sec.
     *
     * The faces array contains one byte per strip, with bits set
     * depending on whether a 1/8 section is opaque.  If all the
     * bits are set, the entire strip is drawn.
     *
     * This routine is passed the correct dimensions, positions,
     * data brick and faces array for the given direction ix.
     */
    register int	k, kstop;
    register unsigned	i, j, l, m, n, ibit, drawing;
    register unsigned	iy, iz, nx, ny, nx8, ny1;
    register float	**c1, **c2, *v1, *v2;
    register unsigned	c1b, c2b, v1b, v2b;
    register float	*posy, *posz, pp;
    register unsigned char *cd;
    register unsigned char *face;
    float		vnorm[3];
    
    /* Save buffers from pass to pass, 
     * hopefully saving speed on allocing memory.
     */
    static float	*ctab = NULL;
    static float	**cbuf = NULL, *vbuf = NULL;
    static unsigned	*qdraw = NULL, *build = NULL;
    
    blendfunction(BF_SA, BF_MSA);

    if (!ctab) {
	ctab = CallocType(float, 1024+4);
	MemCheck(ctab);
	while ((unsigned)ctab & 0xf)	/* Quad word align */
	    ++ctab;
    }
    
    /* Create color table, ctab
     */
    for (i = j = 0; j < 256; i += 4, ++j) {
	ctab[i  ] = cmap[j][0];
	ctab[i+1] = cmap[j][1];
	ctab[i+2] = cmap[j][2];
	ctab[i+3] = cmap[j][3] * (float) stride * 0.8;
	if (ctab[i+3] > 1.0)
	    ctab[i+3] = 1.0;
    }

    /* Indecies of the other axes
     */
    iy = (ix + 1) % 3;
    iz = (ix + 2) % 3;

    /* Loop invariants
     */
    nx = dim[ix];
    ny = dim[iy];
    nx8 = RoundUp(nx, 8) / 8;
    ny1 = RoundUp(ny, stride) - stride;
    
    posy = pos[iy];
    posz = pos[iz];
    
    vnorm[ix] = 0;
    vnorm[iy] = 0;
    vnorm[iz] = direction;
    
    /* Allocate buffers for drawing.
     * vbuf, and cbuf are filled on the fly for each quad strip.
     * They are double length to hold both sides of the quad strip.
     * qdraw and build control drawing.
     */
    vbuf = ReallocType(vbuf, float, 8*nx + 4);	MemCheck(vbuf);
    cbuf = ReallocType(cbuf, float *, 2*nx);	MemCheck(cbuf);
    qdraw = ReallocType(qdraw, unsigned, ny);	MemCheck(qdraw);
    build = ReallocType(build, unsigned, ny);	MemCheck(build);

    /* Set buffer indecies.  Align vbuf to quad boundary.
     */
    c1b = 0;
    c2b = nx;

    v1b = 0;
    while ((unsigned)(vbuf + v1b) & 0xf)
	++v1b;
    v2b = v1b + 4*nx;

    /* The X postions are always the same, so declare them here.
     * v1 points to the lower edge, v2 the upper edge.
     */
    v1 = vbuf + v1b;
    v2 = vbuf + v2b;
    for (i = 0, l = ix; i < nx; i += stride, l += 4)
	v1[l] = v2[l] = pos[ix][i];
    
    /* Increment Z in the proper direction.  
     */
    if (direction > 0) {
	k = 0;
	kstop = RoundUp(dim[iz], stride);
    } else {
	k = RoundUp(dim[iz], stride) - stride;
	kstop = -stride;
    }
    direction *= stride;
    for (; k != kstop; k += direction) {
	
	/* Select the correct plane of voxel data and the
	 * correct strip of face values.
	 */
	cd = cdata + k*nx*ny;
	face = faces + k*ny;

	/* Construct the build and qdraw flags.  A vertex entry needs
	 * to be built if either it or its neighbors are opaque.
	 * A vertex entry is drawn if it or its lower neighbor are opaque.
	 * Drawing begins with the j=1 strip.
	 */
	for (j = stride; j < ny1; j += stride) {
	    qdraw[j] = face[j-stride] | face[j];
	    build[j] = qdraw[j]  | face[j+stride];
	}
	qdraw[0]   = 0;
	build[0]   = face[0] | face[stride];
	qdraw[ny1] = face[ny1-stride] | face[ny1];
	build[ny1] = qdraw[ny1];

	/* Fill in the Z location for this plane.
	 */
	v1 = vbuf + v1b;
	v2 = vbuf + v2b;
	pp = posz[k];
	for (i = 0, l = iz; i < nx; i += stride, l += 4)
	    v1[l] = v2[l] = pp;

	for (j = 0; j < ny; j += stride, cd += nx*stride) {

	    /* v1, v2 and c1, c2 swap every strip.
	     */
	    i = c1b; c1b = c2b; c2b = i;
	    i = v1b; v1b = v2b; v2b = i;
	    
	    v1 = vbuf + v1b;
	    v2 = vbuf + v2b;
	    c1 = cbuf + c1b;
	    c2 = cbuf + c2b;
	    pp = posy[j];
	    
	    switch (build[j]) {
		
	      case 0:
		/* Nothing to build
		 */
		break;
		
	      case 0xff:
		/* Build the whole strip in one run.
		 */
		for (i = 0, l = iy, n = 0; i < nx; i += stride, l += 4, ++n) {
		    c2[n] = ctab + 4*cd[i];
		    v2[l] = pp;
		}
		break;
		
	      default:
		/* Build one eighth at a time.
		 */
		n = 0;
		l = iy;
		for (i = 0, m = nx8, ibit = 1; i < nx; m += nx8, ibit <<= 1) {
		    if (m > nx)
			m = nx;
		    if (ibit & build[j])
			for (; i < m; i += stride, ++n, l += 4) {
			    c2[n] = ctab + 4*cd[i];
			    v2[l] = pp;
			}
		    else
			for (; i < m; i += stride, ++n)
			    l += 4;
		}
		break;
	    }
	    
	    switch (qdraw[j]) {

	      case 0:
		/* Nothing to draw.
		 */
		break;

	      case 0xff:
		/* Draw one full strip.
		 */
		n = nx / stride;
		bgnqstrip();
		n3f(vnorm);
		for (i = n / 4; i; --i) {
		    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
		    c4f(c1[1]); v3f(v1+ 4); c4f(c2[1]); v3f(v2+ 4);
		    c4f(c1[2]); v3f(v1+ 8); c4f(c2[2]); v3f(v2+ 8);
		    c4f(c1[3]); v3f(v1+12); c4f(c2[3]); v3f(v2+12);
		    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
		}
		if (n & 0x2) {
		    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
		    c4f(c1[1]); v3f(v1+ 4); c4f(c2[1]); v3f(v2+ 4);
		    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
		}
		if (n & 0x1) {
		    c4f(c1[0]); v3f(v1   ); c4f(c2[0]); v3f(v2   );
		}
		endqstrip();
		break;

	      default:
		/* Draw a strip one eighth at a time.  Begin and
		 * end the quad strip as needed.
		 */
		drawing = 1;
		bgnqstrip();
		n3f(vnorm);
		n = 0;
		l = 0;
		for (i = 0, m = nx8, ibit = 1; i < nx; m += nx8, ibit <<= 1) {
		    if (m > nx)
			m = nx;
		    if (ibit & qdraw[j]) {
			if (!drawing) {
			    drawing = 1;
			    bgnqstrip();
			    n3f(vnorm);
			}
			for (; i < m; i += stride, ++n, l += 4) {
			    c4f(c1[n]); v3f(v1+l); c4f(c2[n]); v3f(v2+l);
			}
		    } else {
			if (drawing) {
			    drawing = 0;
			    endqstrip();
			}
			for (; i < m; i += stride, ++n)
			    l += 4;
		    }
		}
		if (drawing)
		    endqstrip();
		break;
	    }
	}
    }
    blendfunction(BF_ONE, BF_ZERO);
}


void DrawMaxVal(int ix, int direction, unsigned dim[3], float *pos[3],
		unsigned char *cdata, float cmap[256][4],
		unsigned char *faces)
{
    /* Time critical.
     * 
     * This is the core routine for drawing voxels.  The fastest way to
     * draw would be to prepare the colors and locations ahead of
     * time and draw from that array.  This entails a huge overhead in
     * memory (approx 32x), but acheives the fastest quad rate (350 k/sec).
     *
     * Next best is to construct the color and location information
     * on the fly, which is what this routine does.  The maximum quad rate
     * for this technique is about 260 k/sec.
     *
     * The faces array contains one byte per strip, with bits set
     * depending on whether a 1/8 section is opaque.  If all the
     * bits are set, the entire strip is drawn.
     *
     * This routine is passed the correct dimensions, positions,
     * data brick and faces array for the given direction ix.
     */
    register int	k, kstop;
    register unsigned	i, j, l, m, n, ibit, drawing;
    register unsigned	iy, iz, nx, ny, nx8, ny1;
    register float	**c1, **c2, *v1, *v2;
    register unsigned	c1b, c2b, v1b, v2b;
    register float	*posy, *posz, pp;
    register unsigned char *cd;
    register unsigned char *face;
    float		vnorm[3];
    float		trans[3], transy, transz;
    float		zrot[3];
    Matrix		projMat, viewMat, saveMat, viewInv;
    
    /* Save buffers from pass to pass, 
     * hopefully saving speed on allocing memory.
     */
    static float	*ctab = NULL;
    static float	**cbuf = NULL, *vbuf = NULL;
    static unsigned	*qdraw = NULL, *build = NULL;
    
    if (!ctab) {
	ctab = CallocType(float, 1024+4);
	MemCheck(ctab);
	while ((unsigned)ctab & 0xf)	/* Quad word align */
	    ++ctab;
    }
    
    /* Create color table, ctab
     */
    for (i = j = 0; j < 256; i += 4, ++j) {
	ctab[i  ] = cmap[j][0];
	ctab[i+1] = cmap[j][1];
	ctab[i+2] = cmap[j][2];
	ctab[i+3] = cmap[j][3];
    }

    /* Indecies of the other axes
     */
    iy = (ix + 1) % 3;
    iz = (ix + 2) % 3;

    /* Loop invariants
     */
    nx = dim[ix];
    ny = dim[iy];
    nx8 = RoundUp(nx, 8) / 8;
    ny1 = ny - 1;
    
    posy = pos[iy];
    posz = pos[iz];
    
    vnorm[ix] = 0;
    vnorm[iy] = 0;
    vnorm[iz] = direction;
    
    /* Fool with view matrix for max value rendering
     */
    pushmatrix();
    
    mmode(MPROJECTION);
    getmatrix(projMat);
    mcopy(projMat, saveMat);
    projMat[2][2] = -0.5;
    projMat[3][2] = 0.2;
    loadmatrix(projMat);

    mmode(MVIEWING);
    getmatrix(viewMat);
    minvert(viewMat, viewInv);
    trans[0] = -viewInv[3][0];
    trans[1] = -viewInv[3][1];
    trans[2] = -viewInv[3][2];
    transy = trans[iy];
    transz = trans[iz];
    zrot[0] = viewMat[0][2];
    zrot[1] = viewMat[1][2];
    zrot[2] = viewMat[2][2];
    viewMat[3][0] = 0;
    viewMat[3][1] = 0;
    viewMat[3][2] = 0;
    viewMat[3][3] = 1;
    viewMat[0][3] = 0;
    viewMat[1][3] = 0;
    viewMat[2][3] = 0;
    loadmatrix(viewMat);

    /* Allocate buffers for drawing.
     * vbuf, and cbuf are filled on the fly for each quad strip.
     * They are double length to hold both sides of the quad strip.
     * qdraw and build control drawing.
     */
    vbuf = ReallocType(vbuf, float, 8*nx + 4);	MemCheck(vbuf);
    cbuf = ReallocType(cbuf, float *, 2*nx);	MemCheck(cbuf);
    qdraw = ReallocType(qdraw, unsigned, ny);	MemCheck(qdraw);
    build = ReallocType(build, unsigned, ny);	MemCheck(build);

    /* Set buffer indecies.  Align vbuf to quad boundary.
     */
    c1b = 0;
    c2b = nx;

    v1b = 0;
    while ((unsigned)(vbuf + v1b) & 0xf)
	++v1b;
    v2b = v1b + 4*nx;

    /* The X postions are always the same, so declare them here.
     * v1 points to the lower edge, v2 the upper edge.
     */
    v1 = vbuf + v1b;
    v2 = vbuf + v2b;
    for (i = 0, l = ix; i < nx; ++i, l += 4)
	v1[l] = v2[l] = pos[ix][i] + trans[ix];
    
    /* Increment Z in the proper direction.
     */
    if (direction > 0) {
	k = 0;
	kstop = dim[iz];
    } else {
	k = dim[iz] - 1;
	kstop = -1;
    }
    for (; k != kstop; k += direction) {
	
	/* Select the correct plane of voxel data and the
	 * correct strip of face values.
	 */
	cd = cdata + k*nx*ny;
	face = faces + k*ny;

	/* Construct the build and qdraw flags.  A vertex entry needs
	 * to be built if either it or its neighbors are opaque.
	 * A vertex entry is drawn if it or its lower neighbor are opaque.
	 * Drawing begins with the j=1 strip.
	 */
	for (j = 1; j < ny1; ++j) {
	    qdraw[j] = face[j-1] | face[j];
	    build[j] = qdraw[j]  | face[j+1];
	}
	qdraw[0]   = 0;
	build[0]   = face[0] | face[1];
	qdraw[ny1] = face[ny1-1] | face[ny1];
	build[ny1] = qdraw[ny1];

	/* Fill in the Z location for this plane.
	 */
	v1 = vbuf + v1b;
	v2 = vbuf + v2b;
	pp = posz[k] + transz;
	for (i = nx, l = iz; i; --i, l += 4)
	    v1[l] = v2[l] = pp;

	for (j = 0; j < ny; ++j, cd += nx) {

	    /* v1, v2 and c1, c2 swap every strip.
	     */
	    i = c1b; c1b = c2b; c2b = i;
	    i = v1b; v1b = v2b; v2b = i;
	    
	    v1 = vbuf + v1b;
	    v2 = vbuf + v2b;
	    c1 = cbuf + c1b;
	    c2 = cbuf + c2b;
	    pp = posy[j] + transy;
	    
#define ALPHAZ	c2[i][3]*(zrot[0]*v2[n-3]+zrot[1]*v2[n-2]+zrot[2]*v2[n-1])
	    
	    switch (build[j]) {
		
	      case 0:
		/* Nothing to build
		 */
		break;
		
	      case 0xff:
		/* Build the whole strip in one run.
		 */
		for (i = 0, l = iy, n = 3; i < nx;
		     ++i, l += 4, n += 4) {
		    c2[i] = ctab + 4*cd[i];
		    v2[l] = pp;
		    v2[n] = ALPHAZ;
		}
		break;
		
	      default:
		/* Build one eighth at a time.
		 */
		for (i = 0, l = iy, n = 3, ibit = 1;
		     i < nx; ibit <<= 1) {
		    if (ibit & build[j]) {
			if ((m = i + nx8) > nx)
			    m = nx;
			for (; i < m; ++i, l += 4, n += 4) {
			    c2[i] = ctab + 4*cd[i];
			    v2[l] = pp;
			    v2[n] = ALPHAZ;
			}
		    } else {
			i += nx8;
			l += 4*nx8;
			n += 4*nx8;
		    }
		}
		break;
	    }

#undef ALPHAZ

	    switch (qdraw[j]) {

	      case 0:
		/* Nothing to draw.
		 */
		break;

	      case 0xff:
		/* Draw one full strip.
		 */
		bgnqstrip();
		n3f(vnorm);
		for (i = nx / 4; i; --i) {
		    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
		    c3f(c1[1]); v4f(v1+ 4); c3f(c2[1]); v4f(v2+ 4);
		    c3f(c1[2]); v4f(v1+ 8); c3f(c2[2]); v4f(v2+ 8);
		    c3f(c1[3]); v4f(v1+12); c3f(c2[3]); v4f(v2+12);
		    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
		}
		if (nx & 0x2) {
		    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
		    c3f(c1[1]); v4f(v1+ 4); c3f(c2[1]); v4f(v2+ 4);
		    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
		}
		if (nx & 0x1) {
		    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
		}
		endqstrip();
		break;

	      default:
		/* Draw a strip one eighth at a time.  Begin and
		 * end the quad strip as needed.
		 */
		drawing = 1;
		bgnqstrip();
		n3f(vnorm);
		for (i = nx, ibit = 1; i; ibit <<= 1) {
		    l = MIN(nx8, i);
		    if (ibit & qdraw[j]) {
			if (!drawing) {
			    bgnqstrip();
			    n3f(vnorm);
			    drawing = 1;
			}
			for (m = l / 4; m; --m) {
			    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
			    c3f(c1[1]); v4f(v1+ 4); c3f(c2[1]); v4f(v2+ 4);
			    c3f(c1[2]); v4f(v1+ 8); c3f(c2[2]); v4f(v2+ 8);
			    c3f(c1[3]); v4f(v1+12); c3f(c2[3]); v4f(v2+12);
			    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
			}
			if (l & 0x2) {
			    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
			    c3f(c1[1]); v4f(v1+ 4); c3f(c2[1]); v4f(v2+ 4);
			    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
			}
			if (l & 0x1) {
			    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
			    c1 += 1;    v1 += 4;    c2 += 1;    v2 += 4;
			}
		    } else {
			if (drawing) {
			    endqstrip();
			    drawing = 0;
			}
			v1 += l*4;
			v2 += l*4;
			c1 += l;
			c2 += l;
		    }
		    i -= l;
		}
		if (drawing)
		    endqstrip();
		break;
	    }
	}
    }
    popmatrix();
    mmode(MPROJECTION);
    loadmatrix(saveMat);
    mmode(MVIEWING);
}


void DrawStrideMaxVal(int ix, int direction, unsigned dim[3], float *pos[3],
		      unsigned char *cdata, float cmap[256][4],
		      unsigned char *faces, unsigned stride)
{
    /* Time critical.
     * 
     * This is the core routine for drawing voxels.  The fastest way to
     * draw would be to prepare the colors and locations ahead of
     * time and draw from that array.  This entails a huge overhead in
     * memory (approx 32x), but acheives the fastest quad rate (350 k/sec).
     *
     * Next best is to construct the color and location information
     * on the fly, which is what this routine does.  The maximum quad rate
     * for this technique is about 260 k/sec.
     *
     * The faces array contains one byte per strip, with bits set
     * depending on whether a 1/8 section is opaque.  If all the
     * bits are set, the entire strip is drawn.
     *
     * This routine is passed the correct dimensions, positions,
     * data brick and faces array for the given direction ix.
     */
    register int	k, kstop;
    register unsigned	i, j, l, m, n, q, ibit, drawing;
    register unsigned	iy, iz, nx, ny, nx8, ny1;
    register float	**c1, **c2, *v1, *v2;
    register unsigned	c1b, c2b, v1b, v2b;
    register float	*posy, *posz, pp;
    register unsigned char *cd;
    register unsigned char *face;
    float		vnorm[3];
    float		trans[3], transy, transz;
    float		zrot[3];
    Matrix		projMat, viewMat, saveMat, viewInv;
    
    /* Save buffers from pass to pass, 
     * hopefully saving speed on allocing memory.
     */
    static float	*ctab = NULL;
    static float	**cbuf = NULL, *vbuf = NULL;
    static unsigned	*qdraw = NULL, *build = NULL;
    
    if (!ctab) {
	ctab = CallocType(float, 1024+4);
	MemCheck(ctab);
	while ((unsigned)ctab & 0xf)	/* Quad word align */
	    ++ctab;
    }
    
    /* Create color table, ctab
     */
    for (i = j = 0; j < 256; i += 4, ++j) {
	ctab[i  ] = cmap[j][0];
	ctab[i+1] = cmap[j][1];
	ctab[i+2] = cmap[j][2];
	ctab[i+3] = cmap[j][3];
    }

    /* Indecies of the other axes
     */
    iy = (ix + 1) % 3;
    iz = (ix + 2) % 3;

    /* Loop invariants
     */
    nx = dim[ix];
    ny = dim[iy];
    nx8 = RoundUp(nx, 8) / 8;
    ny1 = RoundUp(ny, stride) - stride;
    
    posy = pos[iy];
    posz = pos[iz];
    
    vnorm[ix] = 0;
    vnorm[iy] = 0;
    vnorm[iz] = direction;
    
    /* Fool with view matrix for max value rendering
     */
    pushmatrix();
    
    mmode(MPROJECTION);
    getmatrix(projMat);
    mcopy(projMat, saveMat);
    projMat[2][2] = -0.5;
    projMat[3][2] = 0.2;
    loadmatrix(projMat);

    mmode(MVIEWING);
    getmatrix(viewMat);
    minvert(viewMat, viewInv);
    trans[0] = -viewInv[3][0];
    trans[1] = -viewInv[3][1];
    trans[2] = -viewInv[3][2];
    transy = trans[iy];
    transz = trans[iz];
    zrot[0] = viewMat[0][2];
    zrot[1] = viewMat[1][2];
    zrot[2] = viewMat[2][2];
    viewMat[3][0] = 0;
    viewMat[3][1] = 0;
    viewMat[3][2] = 0;
    viewMat[3][3] = 1;
    viewMat[0][3] = 0;
    viewMat[1][3] = 0;
    viewMat[2][3] = 0;
    loadmatrix(viewMat);

    /* Allocate buffers for drawing.
     * vbuf, and cbuf are filled on the fly for each quad strip.
     * They are double length to hold both sides of the quad strip.
     * qdraw and build control drawing.
     */
    vbuf = ReallocType(vbuf, float, 8*nx + 4);	MemCheck(vbuf);
    cbuf = ReallocType(cbuf, float *, 2*nx);	MemCheck(cbuf);
    qdraw = ReallocType(qdraw, unsigned, ny);	MemCheck(qdraw);
    build = ReallocType(build, unsigned, ny);	MemCheck(build);

    /* Set buffer indecies.  Align vbuf to quad boundary.
     */
    c1b = 0;
    c2b = nx;

    v1b = 0;
    while ((unsigned)(vbuf + v1b) & 0xf)
	++v1b;
    v2b = v1b + 4*nx;

    /* The X postions are always the same, so declare them here.
     * v1 points to the lower edge, v2 the upper edge.
     */
    v1 = vbuf + v1b;
    v2 = vbuf + v2b;
    for (i = 0, l = ix; i < nx; i += stride, l += 4)
	v1[l] = v2[l] = pos[ix][i] + trans[ix];
    
    /* Increment Z in the proper direction.  
     */
    if (direction > 0) {
	k = 0;
	kstop = RoundUp(dim[iz], stride);
    } else {
	k = RoundUp(dim[iz], stride) - stride;
	kstop = -stride;
    }
    direction *= stride;
    for (; k != kstop; k += direction) {
	
	/* Select the correct plane of voxel data and the
	 * correct strip of face values.
	 */
	cd = cdata + k*nx*ny;
	face = faces + k*ny;

	/* Construct the build and qdraw flags.  A vertex entry needs
	 * to be built if either it or its neighbors are opaque.
	 * A vertex entry is drawn if it or its lower neighbor are opaque.
	 * Drawing begins with the j=1 strip.
	 */
	for (j = stride; j < ny1; j += stride) {
	    qdraw[j] = face[j-stride] | face[j];
	    build[j] = qdraw[j]  | face[j+stride];
	}
	qdraw[0]   = 0;
	build[0]   = face[0] | face[stride];
	qdraw[ny1] = face[ny1-stride] | face[ny1];
	build[ny1] = qdraw[ny1];

	/* Fill in the Z location for this plane.
	 */
	v1 = vbuf + v1b;
	v2 = vbuf + v2b;
	pp = posz[k];
	for (i = 0, l = iz; i < nx; i += stride, l += 4)
	    v1[l] = v2[l] = pp + transz;

	for (j = 0; j < ny; j += stride, cd += nx*stride) {

	    /* v1, v2 and c1, c2 swap every strip.
	     */
	    i = c1b; c1b = c2b; c2b = i;
	    i = v1b; v1b = v2b; v2b = i;
	    
	    v1 = vbuf + v1b;
	    v2 = vbuf + v2b;
	    c1 = cbuf + c1b;
	    c2 = cbuf + c2b;
	    pp = posy[j] + transy;
	    
#define ALPHAZ	c2[n][3]*(zrot[0]*v2[q-3]+zrot[1]*v2[q-2]+zrot[2]*v2[q-1])

	    switch (build[j]) {
		
	      case 0:
		/* Nothing to build
		 */
		break;
		
	      case 0xff:
		/* Build the whole strip in one run.
		 */
		for (i = 0, l = iy, n = 0, q = 3; i < nx; 
		     i += stride, l += 4, ++n, q += 4) {
		    c2[n] = ctab + 4*cd[i];
		    v2[l] = pp;
		    v2[q] = ALPHAZ;
		}
		break;
		
	      default:
		/* Build one eighth at a time.
		 */
		n = 0;
		l = iy;
		for (i = 0, m = nx8, q = 3, ibit = 1; 
		     i < nx; m += nx8, ibit <<= 1) {
		    if (m > nx)
			m = nx;
		    if (ibit & build[j])
			for (; i < m; i += stride, ++n, l += 4, q += 4) {
			    c2[n] = ctab + 4*cd[i];
			    v2[l] = pp;
			    v2[q] = ALPHAZ;
			}
		    else
			for (; i < m; i += stride, ++n) {
			    l += 4;
			    q += 4;
			}
		}
		break;
	    }

#undef ALPHAZ
	    
	    switch (qdraw[j]) {

	      case 0:
		/* Nothing to draw.
		 */
		break;

	      case 0xff:
		/* Draw one full strip.
		 */
		n = nx / stride;
		bgnqstrip();
		n3f(vnorm);
		for (i = n / 4; i; --i) {
		    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
		    c3f(c1[1]); v4f(v1+ 4); c3f(c2[1]); v4f(v2+ 4);
		    c3f(c1[2]); v4f(v1+ 8); c3f(c2[2]); v4f(v2+ 8);
		    c3f(c1[3]); v4f(v1+12); c3f(c2[3]); v4f(v2+12);
		    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
		}
		if (n & 0x2) {
		    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
		    c3f(c1[1]); v4f(v1+ 4); c3f(c2[1]); v4f(v2+ 4);
		    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
		}
		if (n & 0x1) {
		    c3f(c1[0]); v4f(v1   ); c3f(c2[0]); v4f(v2   );
		}
		endqstrip();
		break;

	      default:
		/* Draw a strip one eighth at a time.  Begin and
		 * end the quad strip as needed.
		 */
		drawing = 1;
		bgnqstrip();
		n3f(vnorm);
		n = 0;
		l = 0;
		for (i = 0, m = nx8, ibit = 1; i < nx; m += nx8, ibit <<= 1) {
		    if (m > nx)
			m = nx;
		    if (ibit & qdraw[j]) {
			if (!drawing) {
			    drawing = 1;
			    bgnqstrip();
			    n3f(vnorm);
			}
			for (; i < m; i += stride, ++n, l += 4) {
			    c3f(c1[n]); v4f(v1+l); c3f(c2[n]); v4f(v2+l);
			}
		    } else {
			if (drawing) {
			    drawing = 0;
			    endqstrip();
			}
			for (; i < m; i += stride, ++n)
			    l += 4;
		    }
		}
		if (drawing)
		    endqstrip();
		break;
	    }
	}
    }
    popmatrix();
    mmode(MPROJECTION);
    loadmatrix(saveMat);
    mmode(MVIEWING);
}


void DrawVoxFace(int ix, int direction, unsigned dim[3], float *pos[3],
		 unsigned char *cdata, float cmap[256][4])
{
    /* This routine is overkill for just drawing the face of the brick,
     * but it was easy to copy and modify the voxel routine.
     */
    register unsigned	i, j, k, l;
    register unsigned	iy, iz, nx, ny;
    register float	**c1, **c2, *v1, *v2;
    register unsigned	c1b, c2b, v1b, v2b;
    register float	*posy, *posz, pp;
    register unsigned char *cd;
    float		vnorm[3];
    
    /* Save buffers from pass to pass, 
     * hopefully saving speed on allocing memory.
     */
    static float	*ctab = NULL;
    static float	**cbuf = NULL, *vbuf = NULL;
    
    if (!ctab) {
	ctab = CallocType(float, 1024+4);
	MemCheck(ctab);
	while ((unsigned)ctab & 0xf)	/* Quad word align */
	    ++ctab;
    }
    
    /* Create color table, ctab
     */
    for (i = j = 0; j < 256; i += 4, ++j) {
	ctab[i  ] = cmap[j][0];
	ctab[i+1] = cmap[j][1];
	ctab[i+2] = cmap[j][2];
	ctab[i+3] = cmap[j][3];
    }

    /* Indecies of the other axes
     */
    iy = (ix + 1) % 3;
    iz = (ix + 2) % 3;

    /* Loop invariants
     */
    nx = dim[ix];
    ny = dim[iy];
    
    posy = pos[iy];
    posz = pos[iz];
    
    vnorm[ix] = 0;
    vnorm[iy] = 0;
    vnorm[iz] = direction;
    
    /* Allocate buffers for drawing.
     * vbuf, and cbuf are filled on the fly for each quad strip.
     * They are double length to hold both sides of the quad strip.
     */
    vbuf = ReallocType(vbuf, float, 8*nx + 4);	MemCheck(vbuf);
    cbuf = ReallocType(cbuf, float *, 2*nx);	MemCheck(cbuf);

    /* Set buffer indecies.  Align vbuf to quad boundary.
     */
    c1b = 0;
    c2b = nx;

    v1b = 0;
    while ((unsigned)(vbuf + v1b) & 0xf)
	++v1b;
    v2b = v1b + 4*nx;

    /* The X postions are always the same, so declare them here.
     * v1 points to the lower edge, v2 the upper edge.
     */
    v1 = vbuf + v1b;
    v2 = vbuf + v2b;
    for (i = 0, l = ix; i < nx; ++i, l += 4)
	v1[l] = v2[l] = pos[ix][i];
    
    /* Set Z to proper plane. */
    if (direction > 0)
	k = dim[iz] - 1;
    else
	k = 0;
    
    /* Select the correct plane of voxel data and the
     * correct strip of face values.
     */
    cd = cdata + k*nx*ny;
    
    /* Fill in the Z location for this plane.
     */
    v1 = vbuf + v1b;
    v2 = vbuf + v2b;
    pp = posz[k];
    for (i = nx, l = iz; i; --i, l += 4)
	v1[l] = v2[l] = pp;
    
    for (j = 0; j < ny; ++j, cd += nx) {
	
	/* v1, v2 and c1, c2 swap every strip.
	 */
	i = c1b; c1b = c2b; c2b = i;
	i = v1b; v1b = v2b; v2b = i;
	
	v1 = vbuf + v1b;
	v2 = vbuf + v2b;
	c1 = cbuf + c1b;
	c2 = cbuf + c2b;
	pp = posy[j];
	
	/* Build the strip.
	 */
	for (i = 0, l = iy; i < nx; ++i, l += 4) {
	    c2[i] = ctab + 4*cd[i];
	    v2[l] = pp;
	}
	if (j == 0)
	    continue;

	/* Draw one full strip.
	 */
	bgnqstrip();
	n3f(vnorm);
	for (i = nx / 4; i; --i) {
	    c3f(c1[0]); v3f(v1   ); c3f(c2[0]); v3f(v2   );
	    c3f(c1[1]); v3f(v1+ 4); c3f(c2[1]); v3f(v2+ 4);
	    c3f(c1[2]); v3f(v1+ 8); c3f(c2[2]); v3f(v2+ 8);
	    c3f(c1[3]); v3f(v1+12); c3f(c2[3]); v3f(v2+12);
	    c1 += 4;    v1+= 16;    c2 += 4;    v2 += 16;
	}
	if (nx & 0x2) {
	    c3f(c1[0]); v3f(v1   ); c3f(c2[0]); v3f(v2   );
	    c3f(c1[1]); v3f(v1+ 4); c3f(c2[1]); v3f(v2+ 4);
	    c1 += 2;    v1+= 8;     c2 += 2;    v2 += 8;
	}
	if (nx & 0x1) {
	    c3f(c1[0]); v3f(v1   ); c3f(c2[0]); v3f(v2   );
	}
	endqstrip();
    }
}


void DrawVoxFaces(unsigned dim[3][3], float *pos[3][3],
		  unsigned char *cdata[3], float cmap[256][4])
{
    register unsigned	i, ix;
    Matrix		view;

    /* The trick is to look down the z column for direction.
     */
    ViewMatrix(view);

    for (i = 0; i < 3; ++i) {
	ix = (i + 1) % 3;
	if (view[i][2] > 0)
	    DrawVoxFace(ix, -1, dim[ix], pos[ix], cdata[ix], cmap);
	else
	    DrawVoxFace(ix,  1, dim[ix], pos[ix], cdata[ix], cmap);
    }
}


void DrawBounds(float dim[3], unsigned asBox)
{
    float	vert[3];

#define V(x,y,z)		vset(vert,x,y,z); v3f(vert)
#define DoLine(a,b,c,d,e,f)	bgnline(); V(a,b,c); V(d,e,f); endline();

    DoLine(0,0,0, dim[0],0,0);
    DoLine(0,0,0, 0,dim[1],0);
    DoLine(0,0,0, 0,0,dim[2]);

    if (!asBox)
	return;

    bgnclosedline();
    V(dim[0],     0,     0);
    V(dim[0],dim[1],     0);
    V(dim[0],dim[1],dim[2]);
    V(dim[0],     0,dim[2]);
    endclosedline();
    
    bgnline();
    V(dim[0],dim[1],     0);
    V(     0,dim[1],     0);
    V(     0,dim[1],dim[2]);
    V(dim[0],dim[1],dim[2]);
    endline();
    
    bgnline();
    V(dim[0],     0,dim[2]);
    V(     0,     0,dim[2]);
    V(     0,dim[1],dim[2]);
    endline();

#undef DoLine
#undef V
}


void DrawAnnotatedBox(float dim[3], unsigned lo[3], unsigned hi[3])
{
    float	cv[3];
    float	co[3];
    char	cb[128];
    
#define Extend(x,y,z)	vset(cv,x,y,z); vscale(cv, 1.10); vsub(cv,co,cv)
#define Lower(x,y,z)	vset(cv,x,y,z); vscale(cv, 0.15); vsub(cv,co,cv)
#define Upper(x,y,z)	vset(cv,x,y,z); vscale(cv, 0.95); vsub(cv,co,cv)
#define DoString(s)	cmov(cv[0],cv[1],cv[2]); charstr(s)
#define BText(x)	(void) sprintf(cb, "%u", x); DoString(cb)

    vset(co, dim[0], dim[1], dim[2]);
    vscale(co, 0.07);

    Extend(dim[0], 0, 0);
    DoString("x");
    Extend(0,dim[1],0);
    DoString("y");
    Extend(0,0,dim[2]);
    DoString("z");

    Lower(dim[0], 0, 0);
    BText(lo[0]);
    Lower(0,dim[1],0);
    BText(lo[1]);
    Lower(0,0,dim[2]);
    BText(lo[2]);
    
    Upper(dim[0], 0, 0);
    BText(hi[0]);
    Upper(0,dim[1],0);
    BText(hi[1]);
    Upper(0,0,dim[2]);
    BText(hi[2]);

#undef Extend
#undef Lower
#undef Upper
#undef DoString
}


void DrawColorBar(float ctab[256][4], float atab[256])
{
    float	vnorm[3], v1[2], v2[2];
    int		i;

    pushmatrix();
    zbuffer(FALSE);
    translate(0.0, -0.72, 0.5);
    scale(0.0025, 0.06, 1.0);

    bgnline();
    RGBcolor(255, 255, 255);
    for (i = 0; i < 256; ++i) {
	v1[0] = i - 255;
	v1[1] = MAX(0.02, atab[i]);
	v2f(v1);
    }
    endline();

    bgnline();
    RGBcolor(0, 0, 0);
    
    vset(vnorm, 0, 0, 1);
    v1[1] = 0;
    v2[1] = 1;

    bgnqstrip();
    n3f(vnorm);
    for (i = 0; i < 256; ++i) {
	v1[0] = v2[0] = i;
	c3f(ctab[i]);
	v2f(v1);
	v2f(v2);
    }
    endqstrip();

    zbuffer(TRUE);
    popmatrix();
}


void DrawAxis(float *axlen)
{
    /* Draw three lines with 'x', 'y' and 'z' at their tips.
     */
    float	zero[3];
    float	tip[3];

    vzero(zero);

    bgnline();
    vset(tip, axlen[0], 0, 0);
    v3f(zero);
    v3f(tip);
    endline();
    cmov(tip[0], tip[1], tip[2]);
    charstr(" x");

    bgnline();
    vset(tip, 0, axlen[1], 0);
    v3f(zero);
    v3f(tip);
    endline();
    cmov(tip[0], tip[1], tip[2]);
    charstr(" y");

    bgnline();
    vset(tip, 0, 0, axlen[2]);
    v3f(zero);
    v3f(tip);
    endline();
    cmov(tip[0], tip[1], tip[2]);
    charstr(" z");
}


void DrawSetupFinder(void)
{
    static float mat1[] = {
	DIFFUSE,	0.3, 0.3, 0.5,
	EMISSION,	0.0, 0.0, 0.3,
	LMNULL,
    };
    static float lt1[] = {
	LCOLOR,		0.8, 0.8, 0.8,
	POSITION,	0.0, 1.0, 1.0, 0.0,
	LMNULL,
    };
    static float lt2[] = {
	LCOLOR,		0.8, 0.8, 0.8,
	POSITION,	-0.5, -1.0, 1.0, 0.0,
	LMNULL,
    };
    static float lm[] = {
	TWOSIDE,	0.0,
	LMNULL,
    };
    
    lmdef(DEFMATERIAL,	100, 0, mat1);
    lmdef(DEFLIGHT,	100, 0, lt1);
    lmdef(DEFLIGHT,	101, 0, lt2);
    lmdef(DEFLMODEL,	100, 0, lm);
}


void DrawFinderScene(int voxdim[3], int stride, int voxoff[3],
		     unsigned filedim[3], float dimScale[3], Trackball *tball)
{
    float		axis[3];
    float		sf;
    long		v[3];
    Matrix		viewmat;
    register unsigned	i, j, k, i1, i2;
    
    static float	boxNormal[] = {
	-1, 0, 0,  1, 0, 0,
	0, -1, 0,  0, 1, 0,
	0, 0, -1,  0, 0, 1,
    };
    static long		boxVert[] = {
	0, 0, 0,  0, 0, 1,  0, 1, 1,  0, 1, 0,
	1, 0, 0,  1, 1, 0,  1, 1, 1,  1, 0, 1,
	0, 0, 0,  1, 0, 0,  1, 0, 1,  0, 0, 1,
	0, 1, 0,  0, 1, 1,  1, 1, 1,  1, 1, 0,
	0, 0, 0,  0, 1, 0,  1, 1, 0,  1, 0, 0,
	0, 0, 1,  1, 0, 1,  1, 1, 1,  0, 1, 1,
    };
    
    
    pushmatrix();
    
    /* Draw cube with lighting */
    lmbind(MATERIAL, 100);
    lmbind(LIGHT0, 100);
    lmbind(LIGHT1, 101);
    lmbind(LMODEL, 100);
    
    lmcolor(LMC_AD);

    switch (finderMode) {
      case FinderSync:
	RGBcolor(32, 32, 128);
	break;
      case FinderScan:
	RGBcolor(100, 100, 100);
	break;
      case FinderDiff:
	RGBcolor(150, 150, 0);
	break;
      case FinderRead:
      case FinderInterp:
	RGBcolor(200, 0, 0);
	break;
    }
    
    /* Move to trackball view of scene */
    TrackballSetMatrix(tball);
    sf = 1.0 / MAX(MAX(filedim[0], filedim[1]), filedim[2]);
    scale(sf, sf, sf);
    scale(dimScale[0], dimScale[1], dimScale[2]);
    translate(-0.5*filedim[0], -0.5*filedim[1], -0.5*filedim[2]);
    
    /* Augment matrix to expand a unit cube in the right spot */
    pushmatrix();
    translate((float) voxoff[0], (float) voxoff[1], (float) voxoff[2]);
    scale((float) voxdim[0], (float) voxdim[1], (float) voxdim[2]);
    scale((float) stride, (float) stride, (float) stride);
    
    switch (finderMode) {
      case FinderSync:
      case FinderDiff:
      case FinderInterp:
	finderPlane = 6;
	/* fall through */
      case FinderScan:
	for (i = 0; i < finderPlane; ++i) {
	    bgnpolygon();
	    n3f(boxNormal + 3*i);
	    for (j = 0; j < 4; ++j)
		v3i(boxVert + i*12 + j*3);
	    endpolygon();
	}
	break;
      case FinderRead:
	pushmatrix();
	ViewMatrix(viewmat);
	i = viewmat[2][2] > 0 ? 4 : 5;
	translate(0, 0, (float) finderPlane / (float) voxdim[2]);
	bgnpolygon();
	n3f(boxNormal + 3*i);
	for (j = 0; j < 4; ++j)
	    v3i(boxVert + 48 + j*3);
	endpolygon();
	popmatrix();
	lmbind(MATERIAL, 0);
	RGBcolor(255, 0, 0);
	vset(axis, 1, 1, 1);
	DrawBounds(axis, True);
	break;
    }

    popmatrix();	/* shaded cube */
    
    /* Draw lines around file dimensions, no lighting */
    lmbind(MATERIAL, 0);
    lmcolor(LMC_COLOR);
    RGBcolor(0, 64, 0);
    linewidth(1);
    scale(0.25*filedim[0], 0.25*filedim[1], 0.25*filedim[2]);

    for (i = 0; i < 3; ++i) {
	i1 = (i+1)%3;
	i2 = (i+2)%3;
	for (j = 0; j <=4; ++j) {
	    v[i1] = j;
	    for (k = 0; k <= 4; ++k) {
		v[i2] = k;
		bgnline();
		v[i] = 0; v3i(v);
		v[i] = 4; v3i(v);
		endline();
	    }
	}
    }
	
    vset(axis, 4, 4, 4);
    RGBcolor(255, 128, 0);
    DrawAxis(axis);
	
    popmatrix();
}


void DrawPoint(float *point, unsigned npoint, int psize, float *pcolor)
{
    unsigned	i, tp;

    c3f(pcolor);
    pntsize(psize);
    zfunction(ZF_ALWAYS);

    bgnpoint();
    tp = npoint * 3;
    for (i = 0; i < tp; i += 3)
	v3f(point + i);
    endpoint();

    zfunction(ZF_LEQUAL);
}
