#ifndef lint
static const char	RCSid[] = "$Id: rhd_odraw.c,v 3.19 2018/10/05 19:19:16 greg Exp $";
#endif
/*
 * Routines for drawing samples using depth buffer checks.
 */

#include "standard.h"

#include <GL/glx.h>
#include <GL/glu.h>

#include "rhd_odraw.h"

#ifndef DEPTHEPS
#define DEPTHEPS	0.02		/* depth epsilon */
#endif
#ifndef SAMPSPERBLOCK
#define SAMPSPERBLOCK	1024		/* target samples per image block */
#endif
#ifndef SFREEFRAC
#define SFREEFRAC	0.2		/* fraction to free at a time */
#endif
#ifndef REDRAWTHRESH
#define REDRAWTHRESH	10240		/* number of samples for dissolve */
#endif
#ifndef MAXFAN
#define MAXFAN		32		/* maximum arms in a triangle fan */
#endif
#ifndef MINFAN
#define MINFAN		4		/* minimum arms in a triangle fan */
#endif
#ifndef FANSIZE
#define FANSIZE		3.5		/* fan sizing factor */
#endif

#define NEWMAP		01		/* need to recompute mapping */
#define NEWRGB		02		/* need to remap RGB values */
#define NEWHIST		04		/* clear histogram as well */

struct ODview	*odView;	/* our view list */
int	odNViews;		/* number of views in our list */

struct ODsamp	odS;		/* sample values */

static int	needmapping;	/* what needs doing with tone map */


#define SAMP32	(32*(2*sizeof(short)+sizeof(union ODfunion)+sizeof(TMbright)+\
			6*sizeof(uby8))+sizeof(int32))

static int sampcmp(const void *s0, const void *s1);
static int odAllocBlockSamp(int vn, int hh, int vh, double prox);
static int make_arms(GLshort ar[MAXFAN][3], short cp[2], struct ODview *vp,
		double sz);
static int depthchange(struct ODview *vp, int x0, int y0, int x1, int y1);
static void clip_edge(GLshort p[3], short o[2], struct ODview *vp);
static int getblock(struct ODview *vp, int h, int v);
static int blockedge(struct ODview *vp, int bi0, int bi1);
static void odDrawSamp(int vn, int id);


int
odInit(				/* initialize drawing routines */
	int	n
)
{
	int	nbytes, i, j, k, nextsamp, count, blockdiv;
	int	res[2];

	if (odNViews > 0) {		/* deallocate view structures */
		for (i = odNViews; i--; ) {
			free((void *)odView[i].bmap);
			free((void *)odView[i].pmap);
			if (odView[i].emap != NULL)
				free((void *)odView[i].emap);
		}
		free((void *)odView);
		odView = NULL;
		odNViews = 0;
	}
	if (n && n != odS.nsamp) {
				/* round space up to nearest power of 2 */
		nbytes = (n+31)/32 * SAMP32;
		for (i = 1024; nbytes > i-8; i <<= 1)
			;
		n = (i-8)/SAMP32 * 32;
		needmapping = NEWHIST;
	}
	if (n != odS.nsamp) {	/* (re)allocate sample array */
		if (odS.nsamp)
			free(odS.base);
		odS.nsamp = 0;
		if (!n)
			return(0);
		nbytes = (n+31)/32 * SAMP32;
		odS.base = (char *)malloc(nbytes);
		if (odS.base == NULL)
			return(0);
				/* assign larger alignment types earlier */
		odS.f = (union ODfunion *)odS.base;
		odS.redraw = (int32 *)(odS.f + n);
		odS.ip = (short (*)[2])(odS.redraw + n/32);
		odS.brt = (TMbright *)(odS.ip + n);
		odS.chr = (uby8 (*)[3])(odS.brt + n);
		odS.rgb = (uby8 (*)[3])(odS.chr + n);
		odS.nsamp = n;
	}
	if (!n)
		return(0);
				/* allocate view information */
	count = 0;			/* count pixels */
	for (i = 0; dev_auxview(i, res) != NULL; i++)
		count += res[0]*res[1];
	odView = (struct ODview *)malloc(i*sizeof(struct ODview));
	if (odView == NULL)
		return(0);
	odNViews = i;
	blockdiv = sqrt(count/(n/SAMPSPERBLOCK)) + 0.5;
	if (blockdiv < 8) blockdiv = 8;
	nextsamp = 0; count /= blockdiv*blockdiv;	/* # blocks */
	while (i--) {			/* initialize each view */
		dev_auxview(i, res);
		odView[i].hhi = res[0];
		odView[i].hlow = (res[0] + blockdiv/2) / blockdiv;
		if (odView[i].hlow < 1) odView[i].hlow = 1;
		odView[i].vhi = res[1];
		odView[i].vlow = (res[1] + blockdiv/2) / blockdiv;
		if (odView[i].vlow < 1) odView[i].vlow = 1;
		odView[i].emap = NULL;
		odView[i].dmap = NULL;
		odView[i].pmap = (int32 *)calloc(FL4NELS(res[0]*res[1]),
				sizeof(int32));
		if (odView[i].pmap == NULL)
			return(0);
		j = odView[i].hlow*odView[i].vlow;
		odView[i].bmap = (struct ODblock *)malloc(
				j * sizeof(struct ODblock));
		if (odView[i].bmap == NULL)
			return(0);
		DCHECK(count<=0 | nextsamp>=n,
				CONSISTENCY, "counter botch in odInit");
		if (!i) count = j;
		odView[i].sfirst = nextsamp;
		while (j--) {		/* initialize blocks & free lists */
			odView[i].bmap[j].pthresh = FHUGE;
			odView[i].bmap[j].first = k = nextsamp;
			nextsamp += odView[i].bmap[j].nsamp = 
				(n - nextsamp)/count--;
			odView[i].bmap[j].free = k;
			while (++k < nextsamp)
				odS.nextfree(k-1) = k;
			odS.nextfree(k-1) = ENDFREE;
			odView[i].bmap[j].nused = 0;
		}
		odView[i].snext = nextsamp;
		odView[i].n2redraw = 0;
	}
	CLR4ALL(odS.redraw, odS.nsamp);		/* clear redraw flags */
	for (i = odS.nsamp; i--; ) {		/* clear values */
		odS.ip[i][0] = odS.ip[i][1] = -1;
		odS.brt[i] = TM_NOBRT;
	}
	needmapping |= NEWMAP;			/* compute new map on update */
	return(odS.nsamp);			/* return number of samples */
}

#undef SAMP32


int
sampcmp(			/* sample order, descending proximity */
	const void	*s0,
	const void	*s1
)
{
	double	diff = odS.closeness(*(int*)s1) - odS.closeness(*(int*)s0);

	return (diff > FTINY ? 1 : diff < -FTINY ? -1 : 0);
}


int
odAllocBlockSamp(	/* allocate sample from block */
	int	vn,
	int	hh,
	int	vh,
	double	prox
)
{
	int	si[SAMPSPERBLOCK+SAMPSPERBLOCK/4];
	int	hl, vl;
	VIEW	*vw;
	int	res[2];
	struct ODblock	*bp;
	int	i, j;
					/* get block */
	hl = hh*odView[vn].hlow/odView[vn].hhi;
	vl = vh*odView[vn].vlow/odView[vn].vhi;
	bp = odView[vn].bmap + vl*odView[vn].hlow + hl;
	if (prox > bp->pthresh)
		return(-1);		/* worse than free list occupants */
					/* check for duplicate pixel */
	if (CHK4(odView[vn].pmap, vh*odView[vn].hhi + hh))
		i = bp->first + bp->nsamp;
	else
		i = 0;
	while (i-- > bp->first)
		if (hh == odS.ip[i][0] && vh == odS.ip[i][1]) {	/* found it! */
						/* search free list for it */
			if (i == bp->free)
				break;		/* special case */
			if (bp->free != ENDFREE)
				for (j = bp->free; odS.nextfree(j) != ENDFREE;
						j = odS.nextfree(j))
					if (odS.nextfree(j) == i) {
						odS.nextfree(j) =
							odS.nextfree(i);
						bp->nused++;
						goto gotit;
					}
			if (prox >= 0.99*odS.closeness(i))
				return(-1);	/* previous sample is fine */
			goto gotit;
		}
	if (bp->free != ENDFREE) {	/* allocate from free list */
		i = bp->free;
		if ((odS.ip[i][0] >= 0) & (odS.ip[i][1] >= 0))
			CLR4(odView[vn].pmap, odS.ip[i][1]*odView[vn].hhi +
							odS.ip[i][0]);
		bp->free = odS.nextfree(i);
		bp->nused++;
		goto gotit;
	}
	DCHECK(bp->nsamp<=0, CONSISTENCY,
			"no available samples in odAllocBlockSamp");
	DCHECK(bp->nsamp > sizeof(si)/sizeof(si[0]), CONSISTENCY,
			"too many samples in odAllocBlockSamp");
					/* free some samples */
	if ((vw = dev_auxview(vn, res)) == NULL)
		error(CONSISTENCY, "bad view number in odAllocBlockSamp");
	for (i = bp->nsamp; i--; )	/* figure out which are worse */
		si[i] = bp->first + i;
	qsort((char *)si, bp->nsamp, sizeof(int), sampcmp);
	i = bp->nsamp*SFREEFRAC + .5;	/* put them into free list */
	if (i >= bp->nsamp) i = bp->nsamp-1;	/* paranoia */
	bp->pthresh = odS.closeness(si[i]);	/* new proximity threshold */
	while (--i > 0) {
		odS.nextfree(si[i]) = bp->free;
		bp->free = si[i];
		bp->nused--;
	}
	i = si[0];			/* use worst sample */
	CLR4(odView[vn].pmap, odS.ip[i][1]*odView[vn].hhi + odS.ip[i][0]);
gotit:
	odS.ip[i][0] = hh;
	odS.ip[i][1] = vh;
	odS.closeness(i) = prox;
	SET4(odView[vn].pmap, vh*odView[vn].hhi + hh);
	return(i);
}


void
odSample(			/* add a sample value */
	COLR	c,
	FVECT	d,
	FVECT	p
)
{
	FVECT	disp;
	double	d0, d1, h, v, prox;
	VIEW	*vw;
	int	hh, vh;
	int	res[2];
	int	i, id;

	DCHECK(odS.nsamp<=0, CONSISTENCY, "no samples allocated in odSample");
						/* add value to each view */
	for (i = 0; (vw = dev_auxview(i, res)) != NULL; i++) {
		DCHECK(i>=odNViews, CONSISTENCY, "too many views in odSample");
		CHECK(vw->type!=VT_PER, INTERNAL,
				"cannot handle non-perspective views");
		if (p != NULL) {		/* compute view position */
			VSUB(disp, p, vw->vp);
			d0 = DOT(disp, vw->vdir);
			if (d0 <= vw->vfore+FTINY)
				continue;		/* too close */
		} else {
			VCOPY(disp, d);
			d0 = DOT(disp, vw->vdir);
			if (d0 <= FTINY)		/* behind view */
				continue;
		}
		h = DOT(disp,vw->hvec)/(d0*vw->hn2) + 0.5 - vw->hoff;
		if (h < 0. || h >= 1.)
			continue;			/* left or right */
		v = DOT(disp,vw->vvec)/(d0*vw->vn2) + 0.5 - vw->voff;
		if (v < 0. || v >= 1.)
			continue;			/* above or below */
		hh = h * res[0];
		vh = v * res[1];
		if (odView[i].dmap != NULL) {		/* check depth */
			d1 = odView[i].dmap[vh*res[0] + hh];
			if (d1 < 0.99*FHUGE && (d0 > (1.+DEPTHEPS)*d1 ||
						(1.+DEPTHEPS)*d0 < d1))
			continue;			/* occlusion error */
		}
		if (p != NULL) {		/* compute closeness (sin^2) */
			d1 = DOT(disp, d);
			prox = 1. - d1*d1/DOT(disp,disp);
		} else
			prox = 0.;
						/* allocate sample */
		id = odAllocBlockSamp(i, hh, vh, prox);
		if (id < 0)
			continue;		/* not good enough */
							/* convert color */
		tmCvColrs(tmGlobal, &odS.brt[id], odS.chr[id], (COLR *)c, 1);
		if (imm_mode | needmapping)		/* if immediate mode */
			needmapping |= NEWRGB;		/* map it later */
		else					/* else map it now */
			tmMapPixels(tmGlobal, odS.rgb[id], &odS.brt[id],
					odS.chr[id], 1);
		SET4(odS.redraw, id);			/* mark for redraw */
		odView[i].n2redraw++;
	}
}


void
odRemap(			/* recompute tone mapping */
	int	newhist
)
{
	needmapping |= NEWMAP|NEWRGB;
	if (newhist)
		needmapping |= NEWHIST;
}


void
odRedrawAll(void)				/* mark all samples for redraw */
{
	int	i;

	if ((needmapping&(NEWMAP|NEWRGB)) == (NEWMAP|NEWRGB))
		return;			/* will be called later, anyway */
	for (i = odS.nsamp; i--; )
		if (odS.ip[i][0] >= 0)
			SET4(odS.redraw, i);
					/* not right, but not important */
	for (i = 0; i < odNViews; i++)
		odView[i].n2redraw = odView[i].snext - odView[i].sfirst;
}


void
odRedraw(	/* redraw view region */
	int	vn,
	int	hmin,
	int	vmin,
	int	hmax,
	int	vmax
)
{
	int	i, j;
	struct ODblock	*bp;
	int	k;

	if ((vn<0) | (vn>=odNViews))
		return;
				/* check view limits */
	if (hmin < 0) hmin = 0;
	if (hmax >= odView[vn].hhi) hmax = odView[vn].hhi-1;
	if (vmin < 0) vmin = 0;
	if (vmax >= odView[vn].vhi) vmax = odView[vn].vhi-1;
	if ((hmax <= hmin) | (vmax <= vmin))
		return;
				/* convert to low resolution */
	hmin = hmin * odView[vn].hlow / odView[vn].hhi;
	hmax = hmax * odView[vn].hlow / odView[vn].hhi;
	vmin = vmin * odView[vn].vlow / odView[vn].vhi;
	vmax = vmax * odView[vn].vlow / odView[vn].vhi;
				/* mark block samples for redraw, inclusive */
	for (i = hmin; i <= hmax; i++)
		for (j = vmin; j <= vmax; j++) {
			bp = odView[vn].bmap + j*odView[vn].hlow + i;
			for (k = bp->nsamp; k--; )
				if (odS.ip[bp->first+k][0] >= 0) {
					SET4(odS.redraw, bp->first+k);
					odView[vn].n2redraw++;
				}
		}
}


void
odDepthMap(			/* assign depth map for view */
	int	vn,
	GLfloat	*dm
)
{
	double	d0, d1;
	int	i, j, hmin, hmax, vmin, vmax;
	int	k, l;

	if (dm == NULL) {			/* free edge map */
		if ((vn<0) | (vn>=odNViews))
			return;			/* too late -- they're gone! */
		if (odView[vn].emap != NULL)
			free((void *)odView[vn].emap);
		odView[vn].emap = NULL;
		odView[vn].dmap = NULL;
		return;
	}
	DCHECK(vn<0 | vn>=odNViews, CONSISTENCY,
			"bad view number in odDepthMap");
	odView[vn].dmap = dm;			/* initialize edge map */
	if (odView[vn].emap == NULL) {
		odView[vn].emap = (int32 *)malloc(
			FL4NELS(odView[vn].hlow*odView[vn].vlow)*sizeof(int32));
		if (odView[vn].emap == NULL)
			error(SYSTEM, "out of memory in odDepthMap");
	}
	CLR4ALL(odView[vn].emap, odView[vn].hlow*odView[vn].vlow);
						/* compute edge map */
	vmin = odView[vn].vhi;			/* enter loopsville */
	for (j = odView[vn].vlow; j--; ) {
		vmax = vmin;
		vmin = j*odView[vn].vhi/odView[vn].vlow;
		hmin = odView[vn].hhi;
		for (i = odView[vn].hlow; i--; ) {
			hmax = hmin;
			hmin = i*odView[vn].hhi/odView[vn].hlow;
			for (l = vmin; l < vmax; l++) {	/* vertical edges */
				d1 = dm[l*odView[vn].hhi+hmin];
				for (k = hmin+1; k < hmax; k++) {
					d0 = d1;
					d1 = dm[l*odView[vn].hhi+k];
					if (d0 > (1.+DEPTHEPS)*d1 ||
						(1.+DEPTHEPS)*d0 < d1) {
						SET4(odView[vn].emap,
							j*odView[vn].hlow + i);
						break;
					}
				}
				if (k < hmax)
					break;
			}
			if (l < vmax)
				continue;
			for (k = hmin; k < hmax; k++) {	/* horizontal edges */
				d1 = dm[vmin*odView[vn].hhi+k];
				for (l = vmin+1; l < vmax; l++) {
					d0 = d1;
					d1 = dm[l*odView[vn].hhi+k];
					if (d0 > (1.+DEPTHEPS)*d1 ||
						(1.+DEPTHEPS)*d0 < d1) {
						SET4(odView[vn].emap,
							j*odView[vn].hlow + i);
						break;
					}
				}
				if (l < vmax)
					break;
			}
		}
	}
}


void
odUpdate(				/* update this view */
	int	vn
)
{
	static short	primes[] = {9431,6803,4177,2659,1609,887,587,251,47,1};
	int	myprime;
	int	i, n;

	DCHECK(vn<0 | vn>=odNViews, CONSISTENCY,
			"bad view number in odUpdate");
					/* need to do some tone mapping? */
	if (needmapping & NEWRGB) {
		if (needmapping & NEWMAP) {
			if (needmapping & NEWHIST)
				tmClearHisto(tmGlobal);
			needmapping &= ~NEWHIST;
			if (tmAddHisto(tmGlobal, odS.brt,odS.nsamp,1) != TM_E_OK)
				return;
			if (tmComputeMapping(tmGlobal, 0.,0.,0.) != TM_E_OK)
				return;
			needmapping &= ~NEWMAP;
			odRedrawAll();			/* redraw everything */
		}
		if (tmMapPixels(tmGlobal, (uby8 *)(odS.rgb), odS.brt,
				(uby8 *)(odS.chr), odS.nsamp) != TM_E_OK)
			return;
		needmapping &= ~NEWRGB;
	}
	if (odView[vn].n2redraw <= 0)
		return;
#if REDRAWTHRESH
	if (odView[vn].n2redraw < REDRAWTHRESH)
		goto quickdraw;
					/* pick a good prime step size */
	n = odView[vn].snext - odView[vn].sfirst;
	for (i = 0; primes[i]<<5 >= n; i++)
		;
	while ((myprime = primes[i++]) > 1)
		if (n % myprime)
			break;
					/* dissolve in new samples */
	for (i = odView[vn].sfirst; n-- > 0; i += myprime) {
		if (i >= odView[vn].snext)
			i -= odView[vn].snext - odView[vn].sfirst;
		if (CHK4(odS.redraw, i)) {
			odDrawSamp(vn, i);
			CLR4(odS.redraw, i);
		}
	}
	odView[vn].n2redraw = 0;
	return;
quickdraw:				/* quicker sparse flag checking */
#endif
					/* redraw samples at end */
	for (i = odView[vn].snext-31; i < odView[vn].snext; i++)
		if (CHK4(odS.redraw, i)) {
			odDrawSamp(vn, i);
			CLR4(odS.redraw, i);
		}
					/* faster flag checks in middle */
	for (n = odView[vn].snext>>5; n-- > (odView[vn].sfirst+0x1f)>>5; )
		for (i = 0; odS.redraw[n]; i++)		/* skips faster */
			if (odS.redraw[n] & 1L<<i) {
				odDrawSamp(vn, (n<<5)+i);
				odS.redraw[n] &= ~(1L<<i);
			}
					/* redraw samples at beginning */
	for (i = odView[vn].sfirst; i < odView[vn].sfirst+31; i++)
		if (CHK4(odS.redraw, i)) {
			odDrawSamp(vn, i);
			CLR4(odS.redraw, i);
		}
	odView[vn].n2redraw = 0;
}


					/* this turned out to be unnecessary */
#if 0
static
clip_end(p, o, vp)			/* clip line segment to view */
GLshort	p[3];
short	o[2];
struct ODview	*vp;
{
	if (p[0] < 0) {
		p[1] = -o[0]*(p[1]-o[1])/(p[0]-o[0]) + o[1];
		p[2] = -o[0]*p[2]/(p[0]-o[0]);
		p[0] = 0;
	} else if (p[0] >= vp->hhi) {
		p[1] = (vp->hhi-1-o[0])*(p[1]-o[1])/(p[0]-o[0]) + o[1];
		p[2] = (vp->hhi-1-o[0])*p[2]/(p[0]-o[0]);
		p[0] = vp->hhi-1;
	}
	if (p[1] < 0) {
		p[0] = -o[1]*(p[0]-o[0])/(p[1]-o[1]) + o[0];
		p[2] = -o[1]*p[2]/(p[1]-o[1]);
		p[1] = 0;
	} else if (p[1] >= vp->vhi) {
		p[0] = (vp->vhi-1-o[1])*(p[0]-o[0])/(p[1]-o[1]) + o[0];
		p[2] = (vp->vhi-1-o[1])*p[2]/(p[1]-o[1]);
		p[1] = vp->vhi-1;
	}
}
#endif


static int
make_arms(		/* make arms for triangle fan */
	GLshort	ar[MAXFAN][3],
	short	cp[2],
	struct ODview	*vp,
	double	sz
)
{
	int	na, dv;
	double	hrad, vrad, phi;
	int	i;

	DCHECK(sz > 1, CONSISTENCY, "super-unary size in make_arms");
	na = MAXFAN*sz + 0.5;			/* keep arc length constant */
	if (na < MINFAN) na = MINFAN;
	hrad = FANSIZE*sz*vp->hhi/vp->hlow;
	vrad = FANSIZE*sz*vp->vhi/vp->vlow;
	if (hrad*vrad < 2.25)
		hrad = vrad = 1.5;
	dv = OMAXDEPTH*sz + 0.5;
	for (i = 0; i < na; i++) {
		phi = (2.*PI)*i/na;
		ar[i][0] = cp[0] + tcos(phi)*hrad + 0.5;
		ar[i][1] = cp[1] + tsin(phi)*vrad + 0.5;
		ar[i][2] = dv;
		/* clip_end(ar[i], cp, vp); */
	}
	return(na);
}


static int
depthchange(		/* check depth discontinuity */
	struct ODview	*vp,
	int	x0,
	int	y0,
	int	x1,
	int	y1
)
{
	double	d0, d1;

	DCHECK(x0<0 | x0>=vp->hhi | y0<0 | y0>=vp->vhi,
			CONSISTENCY, "coordinates off view in depthchange");

	if ((x1<0) | (x1>=vp->hhi) | (y1<0) | (y1>=vp->vhi))
		return(1);

	d0 = vp->dmap[y0*vp->hhi + x0];
	d1 = vp->dmap[y1*vp->hhi + x1];

	return((1.+DEPTHEPS)*d0 < d1 || d0 > (1.+DEPTHEPS)*d1);
}


static void
clip_edge(			/* clip line segment to depth edge */
	GLshort	p[3],
	short	o[2],
	struct ODview	*vp
)
{
	int	x, y, xstep, ystep, rise, rise2, run, run2, n;

	DCHECK(vp->dmap==NULL, CONSISTENCY,
			"clip_edge called with no depth map");
	x = o[0]; y = o[1];
	run = p[0] - x;
	xstep = run > 0 ? 1 : -1;
	run *= xstep;
	rise = p[1] - y;
	ystep = rise > 0 ? 1 : -1;
	rise *= ystep;
	rise2 = run2 = 0;
	if (rise > run) rise2 = 1;
	else run2 = 1;
	n = rise + run;
	while (n--)			/* run out arm, checking depth */
		if (run2 > rise2) {
			x += xstep;
			rise2 += rise;
			if (depthchange(vp, x-xstep, y, x, y))
				break;
		} else {
			y += ystep;
			run2 += run;
			if (depthchange(vp, x, y-ystep, x, y))
				break;
		}
	if (n < 0)			/* found something? */
		return;
	if (run > rise)
		p[2] = (x - o[0])*p[2]/(p[0] - o[0]);
	else
		p[2] = (y - o[1])*p[2]/(p[1] - o[1]);
	p[0] = x;
	p[1] = y;
}


static int
getblock(			/* get block index */
	struct ODview	*vp,
	int	h,
	int	v
)
{
	if ((h<0) | (h>=vp->hhi) | (v<0) | (v>=vp->vhi))
		return(-1);
	return(h*vp->hlow/vp->hhi + v*vp->vlow/vp->vhi*vp->hlow);
}


static int
blockedge(			/* check for edge between blocks? */
	struct ODview	*vp,
	int	bi0,
	int	bi1
)
{
	if (bi1 == bi0)
		return(0);		/* same block */
	if (bi1 < 0)
		return(1);		/* end off view */
	if (CHK4(vp->emap, bi1))
		return(1);		/* end block has edges */
	if (bi1 == bi0+1 || bi1 == bi0-1 ||
			bi1 == bi0+vp->hlow || bi1 == bi0-vp->hlow)
		return(0);		/* end in adjacent block -- no edges */
	return(1);			/* conservative for rarer case */
}


static void
odDrawSamp(			/* draw view sample */
	int	vn,
	int	id
)
{
	GLshort	arm[MAXFAN][3];
	int	narms, blockindex;
	struct ODview	*vp;
	double	size;
	int	home_edges;
	int	i;

	vp = odView + vn;
	blockindex = getblock(vp, odS.ip[id][0], odS.ip[id][1]);
	DCHECK(blockindex<0, CONSISTENCY, "bad sample handed to odDrawSamp");
	DCHECK(vp->bmap[blockindex].nused <= 0,
			CONSISTENCY, "bad in-use count in odDrawSamp");
					/* create triangle fan */
	size = 1./sqrt((double)vp->bmap[blockindex].nused);
	narms = make_arms(arm, odS.ip[id], vp, size);
	if (vp->emap != NULL) {		/* check for edge collisions */
		home_edges = CHK4(vp->emap, blockindex);
		for (i = 0; i < narms; i++)
			if (home_edges || blockedge(vp, blockindex,
					getblock(vp, arm[i][0], arm[i][1])))
				clip_edge(arm[i], odS.ip[id], vp);
	}
					/* draw triangle fan */
	glColor3ub(odS.rgb[id][0], odS.rgb[id][1], odS.rgb[id][2]);
	glBegin(GL_TRIANGLE_FAN);
	glVertex3s((GLshort)odS.ip[id][0], (GLshort)odS.ip[id][1], (GLshort)0);
	for (i = 0; i < narms; i++)
		glVertex3sv(arm[i]);
	glVertex3sv(arm[0]);		/* connect last to first */
	glEnd();
}
