
/* 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/vox.c,v 2.2 92/10/27 16:50:32 ken Exp Locker: ken $
 *
 * $Log:	vox.c,v $
 * Revision 2.2  92/10/27  16:50:32  ken
 * Added basic point support
 * 
 * Revision 2.1  92/10/19  17:03:40  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  17:03:03  ken
 * Initial revision
 * 
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include <math.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <ulocks.h>
#include <task.h>

#include <Xm/Xm.h>
#include <Xm/ToggleBG.h>
#include <gl/gl.h>

#include "util.h"
#include "skip.h"
#include "xtutil.h"
#include "glutil.h"
#include "draw.h"
#include "vox.h"
#include "bob.h"

#define VNEAR	0.5
#define VFAR	10.0


void SetView(int width, int height)
{
    /* Setup gl viewing from scratch.
     * Use bob->fieldOfView deg of perspective.
     */
    loadmatrix(idmatrix);
    perspective(bob->fieldOfView, (float) width / (float) height,
		VNEAR, VFAR);
    viewport(0, width - 1, 0, height - 1);
    translate(0.0, 0.0, -bob->eyeDist);
}


static float sFogSetup[] = { 1.0, 0.0, 0.0, 0.0 };

void SetupGL(void)
{
    glcompat(GLC_OLDPOLYGON, 0);
    glcompat(GLC_ZRANGEMAP, 0);
    subpixel(TRUE);
    bob->blend = getgdesc(GD_BLEND) == 1;
    if (bob->blend) {
 	pntsmooth(SMP_ON | SMP_SMOOTHER);
	linesmooth(SML_ON);
    }
    zbuffer(TRUE);
    
    bob->fog = getgdesc(GD_FOGVERTEX) == 1;
    if (bob->fog) {
	sFogSetup[0] = bob->fogDensity;
        fogvertex(FG_VTX_EXP, sFogSetup);
	fogvertex(FG_OFF, NULL);
    }
    
    mmode(MVIEWING);
}


static void ParallelPrep(unsigned interpolate)
{
    register unsigned	nx, ny, nz, nxy, dz;
    register unsigned	i, j, k, l, ix, iy, iz;
    register unsigned char *src, *fwd, *dst;
    
    nx = bob->dataDim[0];
    ny = bob->dataDim[1];
    nz = bob->dataDim[2];
    
    /* transpose x to z */
    src = bob->cdata[0];
    dst = bob->cdata[2];
    while ((k = m_next()) < nz) {
	l = k*nx*ny;
	for (j = ny; j; --j)
	    for (i = nx; i; --i, l++, k += nz)
		dst[k] = src[l];
    }
    
    m_sync();
    
    /* transpose z to y */
    src = bob->cdata[2];
    dst = bob->cdata[1];
    while ((k = m_next()) < ny) {
	l = k*nx*nz;
	for (j = nx; j; --j)
	    for (i = nz; i; --i, l++, k += ny)
		dst[k] = src[l];
    }
    
    if (interpolate) {
	m_sync();
	
	while ((ix = m_next()) < 3) {
	    iy = (ix + 1) % 3;
	    iz = (ix + 2) % 3;
	    
	    nx = bob->voxDim[ix][ix];
	    ny = bob->voxDim[ix][iy];
	    nz = bob->voxDim[ix][iz];
	    dz = bob->dataDim[iz];
	    nxy = nx*ny;
	    
	    src = bob->cdata[ix] + (dz-1)*nxy;
	    dst = bob->cdata[ix] + (nz-1)*nxy;
	    for (k = 1; k < dz; k++) {
		(void) memcpy(dst, src, nxy);
		src -= nxy;
		dst -= 2*nxy;
	    }
	    
	    src = bob->cdata[ix];
	    dst = src + nxy;
	    fwd = dst + nxy;
	    for (k = 1; k < dz; k++) {
		for (i = 0; i < nxy; ++i)
		    dst[i] = (src[i] + fwd[i]) / 2;
		src = fwd;
		dst = src + nxy;
		fwd = dst + nxy;
	    }
	}
    }
}
    

static void PrepareVoxel(unsigned interpolate)
{
    DrawFinderSpecial(FinderInterp, 0);
    
    if (bob->parked)
	(void) m_rele_procs();

    Verify(m_fork(ParallelPrep, interpolate) == 0, 
	   "m_fork ParallelPrep failure");

    if (m_park_procs() == 0)
	bob->parked = True;
}


static void ParallelMark(unsigned *opaque, unsigned *bittab)
{
    register unsigned	nx, ny, nz, ix, iy, iz, nx8;
    register unsigned	i, j, k, ibit, m, last, optot;
    register unsigned char *face;
    register unsigned char *src;

    while ((ix = m_next()) < 3) {
	if (m_get_numprocs() == 1)
	    DrawFinderSpecial(FinderScan, 2 + 2*ix);

	iy = (ix + 1) % 3;
	iz = (iy + 1) % 3;

	nx = bob->voxDim[ix][ix];
	ny = bob->voxDim[ix][iy];
	nz = bob->voxDim[ix][iz];
	nx8 = RoundUp(nx, 8) / 8;

	bob->faces[ix] = ReallocType(bob->faces[ix], unsigned char, ny*nz);
	MemCheck(bob->faces[ix]);

	face = bob->faces[ix];
	src = bob->cdata[ix];
	
	optot = 0;

	for (k = 0; k < nz; ++k) {
	    for (j = 0; j < ny; ++j) {
		last = 0;
		face[j] = 0;
		for (i = 0, ibit = 1; i < nx; ibit <<= 1) {
		    if (last)
			face[j] |= ibit;
		    if (opaque[src[i]])
			face[j] |= ibit >> 1;
		    if ((m = i + nx8) > nx)
			m = nx;
		    for (; i < m; ++i)
			if (opaque[src[i]]) {
			    face[j] |= ibit;
			    i = m;
			    break;
			}
		    last = opaque[src[i-1]];
		}
		if (face[j])
		    for (; ibit < 256; ibit <<= 1)
			face[j] |= ibit;
		optot += bittab[face[j]];
		src += nx;
	    }
	    face += ny;
	}

	if (bob->fastRate) {
	    optot *= bob->fastRate * nx8;
	    bob->fastStride[ix] = cbrt((float) optot / bob->polyRate) + 1;
	    if (bob->fastStride[ix] < 2)
		bob->fastStride[ix] = 0;
	} else
	    bob->fastStride[ix] = 0;
    }
}


static void MarkOpaque(void)
{
    unsigned		i, j, opaque[256];
    static unsigned	*bittab = NULL;

    if (!bittab) {
	bittab = CallocType(unsigned, 256);
	MemCheck(bittab);
	for (i = 0; i < 256; ++i) {
	    bittab[i] = 0;
	    for (j = 1; j < 256; j <<= 1)
		if (j & i)
		    ++bittab[i];
	}
    }
    
    /* fill in face bytes */
    for (i = 0; i < 256; ++i)
	opaque[i] = bob->atab[i] > 0;

    if (bob->parked)
	(void) m_rele_procs();

    if (m_get_numprocs() > 1)
	DrawFinderSpecial(FinderScan, 2);
    Verify(m_fork(ParallelMark, opaque, bittab) == 0, 
	   "m_fork ParallelMark failure");
    if (m_get_numprocs() > 1)
	DrawFinderSpecial(FinderScan, 6);

    if (m_park_procs() == 0)
	bob->parked = True;
}


void ReadAllPoint(void)
{
    int		i, j, tp, pframe;
    int		memfile, shmid;
    char	*pname, *buf;
    
    Free(bob->allpoint);
    bob->allpoint = NULL;
    
    pframe = bob->iframe;
    if (pframe > StackSize(bob->pointList))
	pframe = StackSize(bob->pointList);
    pname = StackSearch(bob->pointList, pframe);
    if (!pname)
	return;
    
    memfile = strncmp(pname, MEMFILE, strlen(MEMFILE)) == 0;
    
    if (memfile) {
	shmid = atoi(pname + strlen(MEMFILE));
	buf = (char *) shmat(shmid, 0, 0);
	if (!bob->nallpoint) {
	    Error("Need to specify number of points\n");
	    return;
	}

    } else {
	FILE	*pfile = fopen(pname, "r");
	int	fsize;

	if (!pfile) {
	    Error("Cannot open point file %s\n", pname);
	    return;
	}

	fsize = FindFileSize(pname);
	bob->nallpoint = fsize / bob->pstride;
	if (!bob->nallpoint) {
	    Error("no points in file %s\n", pname);
	    return;
	}

	buf = CallocType(char, fsize);
	MemCheck(buf);
	if (fread(buf, fsize, 1, pfile) < 1)
	    Error("short read on point file %s\n", pname);
	(void) fclose(pfile);
    }

    tp = bob->nallpoint * 3;
    bob->allpoint = CallocType(float, tp);
    MemCheck(bob->allpoint);

    for (i = 0, j = bob->poffset; i < tp; i += 3, j += bob->pstride)
	memcpy(&bob->allpoint[i], buf + j, 3*sizeof(float));
    
    if (memfile)
	(void) shmdt(buf);
    else
	Free(buf);
}


void ReadPoint(void)
{
    int		i, j, tp, np;
    float	hi[3], lo[3], stride;
    float	*ap, *pt;
    
    Free(bob->point);
    bob->point = NULL;
    bob->npoint = 0;

    if (StackSize(bob->pointList) == 0)
	return;
    ReadAllPoint();
    if (!bob->allpoint)
	return;

    for (i = 0; i < 3; i++) {
	lo[i] = bob->dataOffset[i];
	hi[i] = lo[i] + bob->dataDim[i] * bob->stride;
    }
    
    tp = bob->nallpoint * 3;
    ap = bob->allpoint;

    pt = bob->point = CallocType(float, tp);
    np = bob->npoint = 0;

    j = 0;
    stride = bob->stride;
    for (i = 0; i < tp; i += 3)
	if (ap[i+0] >= lo[0]  &&  ap[i+0] <= hi[0]  &&
	    ap[i+1] >= lo[1]  &&  ap[i+1] <= hi[1]  &&
	    ap[i+2] >= lo[2]  &&  ap[i+2] <= hi[2]) {
	    pt[j+0] = (ap[i+0] - lo[0]) / stride;
	    pt[j+1] = (ap[i+1] - lo[1]) / stride;
	    pt[j+2] = (ap[i+2] - lo[2]) / stride;
	    j += 3;
	    np++;
	}
    
    bob->npoint = np;
}


void ReadData(void)
{
    BobFrame		*bframe = StackSearch(bob->frameList, bob->iframe);
    int			dim[3];
    int			center[3];
    int			maxdim, stride;
    unsigned		rdim[3];
    unsigned		*fdim;
    unsigned char	*sd, *cd;
    unsigned		sliceOff, sliceSize, fpos, foff;
    unsigned		block, interpolate;
    register unsigned	i, j, k, l, ix, iy, iz;

    static unsigned char *slice = NULL;
    
    if (!bframe)
	return;
    if (bframe->bobFile != bob->file)
	SetCurrentFile(bframe->bobFile);
    if (!bob->file)
	return;
    
    interpolate = XmToggleButtonGadgetGetState(GetWidget("interpolate"));
    
    bob->useRatio = False;
    GetDimWidgets(dim, &maxdim, center, &stride);
    CheckDimension(dim, &maxdim, center, &stride);
    SetDimWidgets(dim, &maxdim, center, &stride);

    bob->stride = stride;
    for (i = 0; i < 3; ++i) {
	bob->dataDim[i] = dim[i];
	bob->dataOffset[i] = center[i] - (dim[i] * stride) / 2;
	rdim[i] = dim[i] * stride;
    }
    
    for (ix = 0; ix < 3; ++ix) {
	iy = (ix + 1) % 3;
	iz = (iy + 1) % 3;

	bob->voxDim[ix][ix] = dim[ix];
	bob->voxDim[ix][iy] = dim[iy];
	if (interpolate)
	    bob->voxDim[ix][iz] = 2*dim[iz] - 1;
	else
	    bob->voxDim[ix][iz] = dim[iz];

	l = bob->voxDim[ix][0] * bob->voxDim[ix][1] * bob->voxDim[ix][2];
	bob->cdata[ix] = ReallocType(bob->cdata[ix], unsigned char, l);
	MemCheck(bob->cdata[ix]);

	for (i = 0; i < 3; ++i) {
	    bob->voxPos[ix][i] = ReallocType(bob->voxPos[ix][i], float, 
					     bob->voxDim[ix][i]);
	    MemCheck(bob->voxPos[ix][i]);

	    if (interpolate  &&  i == iz)
		for (j = 0; j < bob->voxDim[ix][i]; ++j)
		    bob->voxPos[ix][i][j] = j*bob->dimScale[i]*0.5;
	    else
		for (j = 0; j < bob->voxDim[ix][i]; ++j)
		    bob->voxPos[ix][i][j] = j*bob->dimScale[i];
	}
    }

    fdim = bob->file->dim;
    block = bob->file->blockSize ? bob->file->blockSize : 512;
    
    sliceOff = bob->dataOffset[2]*fdim[0]*fdim[1] +
	fdim[0]*bob->dataOffset[1] + bob->dataOffset[0];

    sliceSize = RoundUp(fdim[0]*(rdim[1] - 1) + rdim[0], block) + block;
    slice = ReallocType(slice, unsigned char, sliceSize);
    MemCheck(slice);

    for (i = 0; i < dim[2]; ++i) {

	DrawFinderSpecial(FinderRead, i);

	if (bob->file->data) {
	    sd = bob->file->data + 
		bframe->seek + sliceOff + i*stride*fdim[0]*fdim[1];

	} else {
	    fpos = bframe->seek + sliceOff + i*stride*fdim[0]*fdim[1];
	    foff = fpos % block;
	    fpos -= foff;
	    
	    if (lseek(bob->file->fd, fpos, SEEK_SET) < 0) {
		Error("Unable to seek on file %s\n", bob->file->fname);
		break;
	    }
	    if (read(bob->file->fd, slice, sliceSize) < 0) {
		Error("Unable to read from file %s\n", bob->file->fname);
		break;
	    }
	    sd = slice + foff;
	}

	cd = bob->cdata[0] + i*dim[0]*dim[1];
	for (j = 0; j < dim[1]; ++j) {
	    if (stride == 1)
		(void) memcpy(cd, sd, dim[0]);
	    else
		for (k = l = 0; l < rdim[0]; ++k, l += stride)
		    cd[k] = sd[l];
	    cd += dim[0];
	    sd += fdim[0] * stride;
	}
    }

    PrepareVoxel(interpolate);
    MarkOpaque();

    ReadPoint();

    finderMode = FinderSync;
}


void PackColor(unsigned char *cmap)
{
    register unsigned	i, j, doAlpha;
    
    doAlpha = XmToggleButtonGadgetGetState(GetWidget("brightness")); 
    
    if (cmap)
	for (i = 0; i < 256; ++i) {
	    j = 3*i;
	    bob->ctab[i][0] = cmap[j]   / 255.0;
	    bob->ctab[i][1] = cmap[j+1] / 255.0;
	    bob->ctab[i][2] = cmap[j+2] / 255.0;
	}

    if (doAlpha  &&  cmap)
	for (i = 0; i < 256; ++i)
	    bob->atab[i] = (0.299 * bob->ctab[i][0] + 
			    0.587 * bob->ctab[i][1] + 
			    0.114 * bob->ctab[i][2]);
    for (i = 0; i < 256; ++i) {
	bob->ctab[i][3] = bob->atab[i] * bob->afact;
	if (bob->ctab[i][3] > 1)
	    bob->ctab[i][3] = 1;
    }

    if (doAlpha  &&  cmap  &&  bob->cdata[0])
	MarkOpaque();
}


void PackAlpha(unsigned char *amap)
{
    register unsigned	i;

    XmToggleButtonGadgetSetState(GetWidget("brightness"), False, False); 
    
    for (i = 0; i < 256; ++i) {
	bob->atab[i] = amap[i] / 255.0;
	bob->ctab[i][3] = bob->atab[i] * bob->afact;
	if (bob->ctab[i][3] > 1)
	    bob->ctab[i][3] = 1;
    }
    if (bob->cdata[0])
	MarkOpaque();
}


static void DrawVoxelScene(unsigned fast)
{
    float	sf;
    float	axis[3];
    int		ix, direction;
    unsigned	i, hi[3], npoly, ktime;
    unsigned	bounds = XmToggleButtonGadgetGetState(GetWidget("bounds"));
    unsigned	annotate = XmToggleButtonGadgetGetState(GetWidget("annotate"));
    unsigned	soft = XmToggleButtonGadgetGetState(GetWidget("softEdge"));
    unsigned	cbar = XmToggleButtonGadgetGetState(GetWidget("colorBar"));
    unsigned	maxval = XmToggleButtonGadgetGetState(GetWidget("maxval"));
    unsigned	dopoint = XmToggleButtonGadgetGetState(GetWidget("point"));
    unsigned	fog = XmToggleButtonGadgetGetState(GetWidget("fog"));
    
    vset(axis,
	 bob->dataDim[0]*bob->dimScale[0],
	 bob->dataDim[1]*bob->dimScale[1],
	 bob->dataDim[2]*bob->dimScale[2]);
    
    /* Turn off lighting */
    lmbind(MATERIAL, 0);
    lmbind(LMODEL, 0);
    
    /* Move to trackball view of scene */
    pushmatrix();
    TrackballSetMatrix(bob->vball);
    sf = 1.0 / (float) MAX(MAX(axis[0], axis[1]), axis[2]);
    scale(sf, sf, sf);
    translate(-0.5*axis[0], -0.5*axis[1], -0.5*axis[2]);
    
    if (bob->fog  &&  fog)
	fogvertex(FG_ON, NULL);
	
    /* Draw points */
    if (bob->point  &&  dopoint) {
	if (bob->blend)
	    blendfunction(BF_SA, BF_MSA);
	DrawPoint(bob->point, bob->npoint, bob->psize, bob->pcolor);
	if (bob->blend)
	    blendfunction(BF_ONE, BF_ZERO);
    }

    /* Draw decorations */
    if (bounds || annotate) {
	pushmatrix();
	translate(-0.5*bob->dimScale[0],
		  -0.5*bob->dimScale[1],
		  -0.5*bob->dimScale[2]);
	RGBcolor(255, 128, 0);
	DrawBounds(axis, bounds);
	if (annotate) {
	    for (i = 0; i < 3; ++i)
		hi[i] = bob->dataOffset[i] + bob->dataDim[i]*bob->stride;
	    RGBcolor(128, 128, 255);
	    DrawAnnotatedBox(axis, bob->dataOffset, hi);
	}
	popmatrix();
    }
    
    if (bob->debug & 0x1) {
	finish();
	(void) MarkTime();
    }

    /* Draw the voxels last, blending on top of axis */
    if (maxval) {
	ix = (ViewAxis(&direction) + 1) % 3;
	if (fast && bob->fastStride[ix])
	    DrawStrideMaxVal(ix, direction, bob->voxDim[ix], bob->voxPos[ix], 
			     bob->cdata[ix], bob->ctab, bob->faces[ix],
			     bob->fastStride[ix]);
	else
	    DrawMaxVal(ix, direction, bob->voxDim[ix], bob->voxPos[ix], 
		       bob->cdata[ix], bob->ctab, bob->faces[ix]);
    } else if (bob->blend) {
	ix = (ViewAxis(&direction) + 1) % 3;
	if (fast && bob->fastStride[ix])
	    DrawStrideVoxel(ix, direction, bob->voxDim[ix], bob->voxPos[ix], 
			    bob->cdata[ix], bob->ctab, bob->faces[ix],
			    bob->fastStride[ix]);
	else
	    DrawVoxel(ix, direction, bob->voxDim[ix], bob->voxPos[ix], 
		      bob->cdata[ix], bob->ctab, bob->faces[ix], soft);
    } else
	DrawVoxFaces(bob->voxDim, bob->voxPos, bob->cdata, bob->ctab);

    if (bob->debug & 0x1) {
	finish();
	ktime = MarkTime();
	npoly = bob->voxDim[ix][(ix+2)%3] * 
	    (bob->voxDim[ix][(ix+1)%3]-1) * (bob->voxDim[ix][ix]-1);
	(void) fprintf(stderr, "kPoly/sec = %f\n", 
		       (float) npoly / (float) ktime);
    }
    
    if (bob->fog  &&  fog)
	fogvertex(FG_OFF, NULL);
	
    popmatrix();

    if (cbar)
	DrawColorBar(bob->ctab, bob->atab);
}


void DrawVoxelWindow(unsigned fast, unsigned save)
{
    unsigned	useFrontBuffer;
    
    if (!bob->vwin)
	return;

    GLXwinset(bob->display, bob->vwin);

    if (!fast && !bob->singleBuffer)
	useFrontBuffer = !XmToggleButtonGadgetGetState(GetWidget("dbuffer"));
    else
	useFrontBuffer = 0;

    if (useFrontBuffer) {
	frontbuffer(TRUE);
	backbuffer(FALSE);
	readsource(SRC_FRONT);
    }

    czclear(bob->bgColor, getgdesc(GD_ZMAX));
    
    if (bob->cdata[0])
	DrawVoxelScene(fast);
    if (save)
	SaveAnimationFrame();

    if (useFrontBuffer) {
	frontbuffer(FALSE);
	backbuffer(TRUE);
	readsource(SRC_AUTO);
    } else if (!bob->singleBuffer)
	swapbuffers();
}


void DrawStereoWindow(unsigned fast)
{
    unsigned	useFrontBuffer;
    
    if (!bob->swin)
	return;

    GLXwinset(bob->display, bob->swin);
    
    if (!fast && !bob->singleBuffer)
	useFrontBuffer = !XmToggleButtonGadgetGetState(GetWidget("dbuffer"));
    else
	useFrontBuffer = 0;
    
    if (useFrontBuffer) {
	frontbuffer(TRUE);
	backbuffer(FALSE);
    }
    
    viewport(0, bob->swidth-1, 0, bob->sheight-1);
    czclear(bob->bgColor, getgdesc(GD_ZMAX));
    
    if (bob->cdata[0]) {
	/* Right eye
	 */
	viewport(0, bob->swidth-1, 0, YMAXSTEREO);
	loadmatrix(idmatrix);
	StereoPerspective(bob->fieldOfView, 
			  (float) bob->swidth / (float) bob->sheight,
			  VNEAR, VFAR, bob->eyeDist, bob->eyeSep);
	translate(0.0, 0.0, -(bob->eyeDist+0.5));
	DrawVoxelScene(fast);
	
	/* Left eye
	 */
	viewport(0, bob->swidth-1, YOFFSET, YOFFSET+YMAXSTEREO);
	loadmatrix(idmatrix);
	StereoPerspective(bob->fieldOfView,
			  (float) bob->swidth / (float) bob->sheight,
			  VNEAR, VFAR, bob->eyeDist, -bob->eyeSep);
	translate(0.0, 0.0, -(bob->eyeDist+0.5));
	DrawVoxelScene(fast);
    }
    
    if (useFrontBuffer) {
	frontbuffer(FALSE);
	backbuffer(TRUE);
    } else if (!bob->singleBuffer)
	swapbuffers();
}



void DrawFinderWindow(void)
{
    unsigned	i;
    int		dim[3];
    int		center[3];
    int		maxdim, stride;
    
    if (!bob->fwin)
	return;

    GLXwinset(bob->display, bob->fwin);

    czclear(bob->bgColor, getgdesc(GD_ZMAX));
    if (bob->file) {
	GetDimWidgets(dim, &maxdim, center, &stride);
	for (i = 0; i < 3; ++i)
	    center[i] -= (dim[i] * stride) / 2;
	DrawFinderScene(dim, stride, center, 
			bob->file->dim, bob->dimScale, bob->fball);
    }

    if (!bob->singleBuffer)
	swapbuffers();
}


void DrawFinderSpecial(FinderEnum mode, unsigned plane)
{
    finderMode = mode;
    finderPlane = plane;
    DrawFinderWindow();
}

