
/* First cut at lchw weighted. Problems with list size, memory use and */
/* performance. Version uses direct bwd cell nnrev[] creation */ 

/* 
 * Argyll Color Management System
 * Multi-dimensional regularized spline data structure
 *
 * Reverse interpolation support code.
 *
 * Author: Graeme W. Gill
 * Date:   30/1/2000
 *
 * Copyright 1999 - 2008 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 *
 * Latest simplex/linear equation version.
 */

/* TTBD:

	Add option/function to return a gamut surface triangle list
	based on the rev setup thinned vertex list.
	Need to add code to convert over ink edges to triangles
	and then shadow test them though.

	XYZ PCS doesn't work with a LCh weighting, although this is
	no an issue when xicc uses separate Jab rspl for clip case (CAM CLIP).

	Allow function callback to set auxiliary values for 
	flag RSPL_AUXLOCUS. 
	How to pass enough info back to aux_compute() ?

	Should auxil return multiple solutions if it finds them ???

	Sometimes slivers remain in the surface in the exact
	direction of the focal point. See test/HarveyMiller colprof -qu
	with #define REVVRML. Probably not actually a problem, just not 100%
	correct gamut surface.

 */

/* TTBD:

	Get rid of error() calls - return status instead

	Need to add a hefty overview and explanation of
	how all this works, before I forget it !

	ie:

	  Basic function requirements:  exact, auxil, locus, clip

	  Fwd cell - fxcell list lookup

	  Basic layout di -> fdi + auxils + ink limit

	  Basic search strategy

	  Sub Simplex decomposition & properties

	  How each type of function finds solutions
		Sub-simplex dimensionality & dof + target dim & dof
		Linear algebra choices.
		
	  How final solutions are chosen

 */

/* PROBLEMS:

	Sometimes the aux locus doesn't correspond exactly to
	the inversion :- ie. one locus segment is returned,
	yet the inversion can't return a solution with
	a particular aux target that lies within that segment.
	(1150 near black, k ~= 0.4).

 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <memory.h>
#include <time.h>

#ifdef NT 
# ifdef WINVER
#  undef WINVER
# endif
# define WINVER 0x0500		/* We need 2k features */
# include <windows.h>
#else
# include <unistd.h>
# ifdef __APPLE__
#  include <fcntl.h>
#  include <sys/types.h>
#  include <sys/sysctl.h>
# endif
#endif

#define INKSCALE 5000.0	/* For ink limit weighting to fudge SVD least squares solution */

#include "rspl_imp.h"
#include "numlib.h"
#include "sort.h"		/* Heap sort */
#include "counters.h"	/* Counter macros */

//#define DMALLOC_GLOBALS
//#include "dmalloc.h"
//#undef DMALLOC_GLOBALS

#define DOSORT			/* [def] Cell sort for better speed */
#undef EN_UNTWIST		/* [und] Force attempt to try and untwist gamut surface */
						/* - Seems to improve some, make some worse ?? (i.e Bonet) */
						/* By default this is controlled using ARGYLL_UNTWIST_GAMUT_SURFACE */
						/* environment variable. */

#undef REVTABLESTATS	/* [und] Reverse table stats */
#undef REVVRML			/* [und] Reverse table 3D plots */

#undef DEBUG1			/* [und] Higher level code */
#undef DEBUG2			/* [und] Lower level code */

/* Debug memory usage accounting */
#ifdef NEVER
#ifdef NEVER
int thissz, lastsz = -1;
#define INCSZ(s, bbb) {						\
					(s)->rev.sz += (bbb);	\
					(s)->rev.thissz = (s)->rev.sz/1000000;		\
                    if ((s)->rev.thissz != (s)->rev.lastsz) fprintf(stderr,"~1 0x%x: %s, %d: rev size = %d Mbytes, delta %d, limit %d\n",((int)(s) >> 8) & 0xf, __FILE__, __LINE__,(s)->rev.thissz,(bbb),(s)->rev.max_sz/1000000);	\
					(s)->rev.lastsz = (s)->rev.thissz;	\
					}
#define DECSZ(s, bbb) {						\
					(s)->rev.sz -= (bbb);	\
					(s)->rev.thissz = (s)->rev.sz/1000000;		\
                    if ((s)->rev.thissz != (s)->rev.lastsz) fprintf(stderr,"~1 0x%x: %s, %d: rev size = %d Mbytes, delta %d, limit %d\n",((int)(s) >> 8) & 0xf, __FILE__, __LINE__,(s)->rev.thissz,-(bbb),(s)->rev.max_sz/1000000);	\
					(s)->rev.lastsz = (s)->rev.thissz;	\
					}
#else
#define INCSZ(s, bbb) (s)->rev.sz += (bbb);	\
                     fprintf(stderr,"%s, %d: rev.sz += %d\n",__FILE__, __LINE__, bbb)
#define DECSZ(s, bbb) (s)->rev.sz -= (bbb);	\
                     fprintf(stderr,"%s, %d: rev.sz -= %d\n",__FILE__, __LINE__, bbb)
#endif
#else
#define INCSZ(s, bbb) (s)->rev.sz += (bbb)
#define DECSZ(s, bbb) (s)->rev.sz -= (bbb)
#endif

/* Set STATS in rev.h */

/* Print a vectors value */
#define DBGVI(text, dim, out, vec, end)			\
{	int pveci;									\
	printf("%s",text);							\
	for (pveci = 0 ; pveci < (dim); pveci++)		\
		printf(out,(vec)[pveci]);				\
	printf(end);								\
}

/* Print a matrix value */
#define DBGMI(text, rows, cols, out, mat, end)		\
{	int pveci, pvecr;								\
	printf("%s",text);								\
	for (pvecr = 0 ; pvecr < (rows); pvecr++) {		\
		for (pveci = 0 ; pveci < (cols); pveci++)		\
			printf(out,(mat)[pvecr][pveci]);		\
	if ((pvecr+1) < (rows))							\
		printf("\n");								\
	}												\
	printf(end);									\
}

#if defined(DEBUG1) || defined(DEBUG2)
# define REVTABLESTATS	/* [und] Reverse table stats */
#endif

#ifdef REVTABLESTATS
# pragma message("!!!!!!!!! REVTABLESTATS set in rspl/rev.c !!!!!!!!!!!")
#endif

#ifdef REVVRML
# pragma message("!!!!!!!!! REVVRML set in rspl/rev.c !!!!!!!!!!!")
# include "vrml.h"
#endif

#ifdef CHECK_NNLU
# pragma message("!!!!!!!!! CHECK_NNLU set in rspl/rspl.h !!!!!!!!!!!")
#endif

/* Do an arbitrary printf */
#define DBGI(text) printf text ;

#undef DEBUG
#undef DBG
#undef DBGV
#undef DBGM

#undef NEVER
#define ALWAYS

#ifdef DEBUG1
#undef DBGS
#undef DBG
#undef DBGV
#undef DBGM
#define DEBUG
#define DBGS(xxx) xxx
#define DBG(xxx) DBGI(xxx)
#define DBGV(xxx) DBGVI xxx
#define DBGM(xxx) DBGMI xxx
#else
#undef DEBUG
#undef DBGS
#undef DBG
#undef DBGV
#undef DBGM
#define DBGS(xxx) 
#define DBG(xxx) 
#define DBGV(xxx) 
#define DBGM(xxx) 
#endif

/* Debug string routines */
static char *pcellorange(fxcell *c);

/* Convention is to use:
   i to index grid points u.a
   n to index data points d.a
   e to index position dimension di
   f to index output function dimension fdi
   j misc and cube corners
   k misc
 */

#define	EPS (2e-6)			/* 2e-6 Allowance for numeric error */

static void make_rev(rspl *s);
static void init_revaccell(rspl *s);

static fxcell *get_fxcell(schbase *b, int ix, int force);
static void uncache_fxcell(revcache *r, fxcell *cp);
#define unget_fxcell(r, cp) uncache_fxcell(r, cp)		/* These are the same */
static void invalidate_revaccell(rspl *s);
static int decrease_revcache(revcache *rc);

/* ====================================================== */

static schbase *init_search(rspl *s, int flags, double *av, int *auxm,
                        double *v, double *cdir, co *cpp, int mxsoln, enum ops op);
static void adjust_search(rspl *s, int flags, double *av, enum ops op);
static schbase *set_search_limit(rspl *s, double (*limit)(void *vcntx, double *in),
                                 void *lcntx, double limitv);
static void set_lsearch(rspl *s, int e);
static void free_search(schbase *b);

static int *calc_fwd_cell_list(rspl *s, double *v);

static int *calc_fwd_nn_cell_list(rspl *s, double *v);

static void init_line_eq(schbase *b, double st[MXRO], double de[MXRO]);
static int *init_line(rspl *s, line *l, double st[MXRO], double de[MXRO]);
static int *next_line_cell(line *l);

static void search_list(schbase *b, int *rip, unsigned int tcount);

static void clear_limitv(rspl *s);

static double get_limitv(schbase *b, int ix,	float *fcb, double *p);

#ifdef STATS
static char *opnames[6] = { "exact", "clipv", "clipn", "auxil", "locus" };
#endif /* STATS */

#define INF_DIST 1e38		/* Stands for infinite "current best" distance */

/* ====================================================== */
/* Globals that track overall usage of reverse cache to aportion memory */
/* This is incremented for rspl with di > 1 when rev.rev_valid != 0 */
size_t g_avail_ram = 0;			/* Total maximum memory to be used */
size_t g_test_ram = 0;			/* Amount of memory that has been tested to be allocatable */
int g_no_rev_cache_instances = 0;
rev_struct *g_rev_instances = NULL;

/* ------------------------------------------------------ */
/* Retry allocation routines - if the malloc fails,       */
/* try reducing the cache size and trying again */
/* (This won't catch the problem if it occurs in a malloc outside rev) */

/* When a malloc fails, reduce the maximum cache to */
/* it's current allocation minus the given size. */
static void rev_reduce_cache(size_t size) {
	rev_struct *rsi;
	size_t ram;

	/* Compute how much ram is currently allocated */
	for (ram = 0, rsi = g_rev_instances; rsi != NULL; rsi = rsi->next)
		ram += rsi->sz;

	if (size > ram)
		error("rev_reduce_cache: run out of rev virtual memory! (want %d, got %d)",size,ram);

//printf("~1 size = %" PFSTPREC "u, g_test_ram = %" PFSTPREC "u\n",size,g_test_ram);
//printf("~1 rev: Reducing cache because alloc of %" PFSTPREC "u bytes failed. Reduced from %lu to %lu MB\n", size, (unsigned long)(g_avail_ram/1000000), (unsigned long)((ram - size)/1000000));
	ram = g_avail_ram = ram - size;

	/* Aportion the memory, and reduce the cache allocation to match */
	ram /= g_no_rev_cache_instances; 
	for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) {
		revcache *rc = rsi->cache;

		rsi->max_sz = ram;
		while (rc->nunlocked > 0 && rsi->sz > rsi->max_sz) {
			if (decrease_revcache(rc) == 0)
				break;
		}
//printf("~1 rev instance ram = %lu MB\n",(unsigned long)(rsi->sz/1000000));
	}
	if (g_rev_instances != NULL && g_rev_instances->sb->s->verbose)
		printf("%cThere %s %d rev cache instance%s with %lu Mbytes limit\n",
              cr_char,
				g_no_rev_cache_instances > 1 ? "are" : "is",
                   g_no_rev_cache_instances,
				g_no_rev_cache_instances > 1 ? "s" : "",
                   (unsigned long)(ram/1000000));
}

/* Check that the requested allocation plus 20 M Bytes */
/* can be allocated, and if not, reduce the rev-cache limit. */
/* This is so as to detect running out of VM before */
/* we actually run out and (on OS X) avoid emitting a warning. */
static void rev_test_vram(size_t size) {
	char *a1;
#ifdef __APPLE__
	int old_stderr, new_stderr;

	/* OS X malloc() blabs about a malloc failure. This */
	/* will confuse users, so we temporarily redirect stdout */
	fflush(stderr);
	old_stderr = dup(fileno(stderr));
	new_stderr = open("/dev/null", O_WRONLY | O_APPEND);
	dup2(new_stderr, fileno(stderr));
#endif
	size += 20 * 1024 * 1024;	/* This depends on the VM region allocation size */
	if ((a1 = malloc(size)) == NULL) {
		rev_reduce_cache(size);
	} else {
		free(a1);
	}
	g_test_ram = size/2;		/* Allow for twice as much VM to be used for each allocation */
#ifdef __APPLE__
	fflush(stderr);
	dup2(old_stderr, fileno(stderr));	/* Restore stderr */
	close(new_stderr);
	close(old_stderr);
#endif
}

static void *rev_malloc(rspl *s, size_t size) {
	void *rv;

	if ((size + 1 * 1024 * 1024) > g_test_ram)
		rev_test_vram(size);
	if ((rv = malloc(size)) == NULL) {
		rev_reduce_cache(size);
		rv = malloc(size);
	}
	if (rv != NULL)
		g_test_ram -= size;

	return rv;
}

static void *rev_calloc(rspl *s, size_t num, size_t size) {
	void *rv;

	if (((num * size) + 1 * 1024 * 1024) > g_test_ram)
		rev_test_vram(size);
	if ((rv = calloc(num, size)) == NULL) {
		rev_reduce_cache(num * size);
		rv = calloc(num, size);
	}
	if (rv != NULL)
		g_test_ram -= size;

	return rv;
}

static void *rev_realloc(rspl *s, void *ptr, size_t size) {
	void *rv;

	if ((size + 1 * 1024 * 1024) > g_test_ram)
		rev_test_vram(size);
	if ((rv = realloc(ptr, size)) == NULL) {
		rev_reduce_cache(size);		/* approximation */
		rv = realloc(ptr, size);
	}
	if (rv != NULL)
		g_test_ram -= size;

	return rv;
}


/* ====================================================== */
/* Set the ink limit information for any reverse interpolation. */
/* Calling this will clear the reverse interpolaton cache and acceleration structures. */
static void
rev_set_limit_rspl(
	rspl *s,		/* this */
	double (*limit)(void *lcntx, double *in),	/* Optional input space limit function. Function */
					/* should evaluate in[0..di-1], and return number that is not to exceed */
					/* limitv. NULL if not used */
	void *lcntx,	/* Context passed to limit() */
	double limitv	/* Value that limit() is not to exceed */
) {
	schbase *b;

	DBG(("rev: setting ink limit function %p and limit %f\n",limit,limitv));

	/* This is a restricted size function */
	if (s->di > MXRI)
		error("rspl: rev_set_limit can't handle di = %d",s->di);
	if (s->fdi > MXRO)
		error("rspl: rev_set_limit can't handle fdi = %d",s->fdi);

	b = set_search_limit(s, limit, lcntx, limitv);	/* Init and set limit info */

	if (s->rev.inited) {		/* If cache and acceleration has been allocated */
		invalidate_revaccell(s);		/* Invalidate the reverse cache */
	}

	/* Invalidate any ink limit values cached with the fwd grid data */
	clear_limitv(s);
}

/* Get the ink limit information for any reverse interpolation. */
static void
rev_get_limit_rspl(
	rspl *s,		/* this */
	double (**limitf)(void *lcntx, double *in),	/* Return pointer to function of NULL if not set */
	void **lcntx,	/* return context pointer */
	double *limitv	/* Return limit value */
) {
	schbase *b = s->rev.sb;

	/* This is a restricted size function */
	if (s->di > MXRI)
		error("rspl: rev_get_limit can't handle di = %d",s->di);
	if (s->fdi > MXRO)
		error("rspl: rev_get_limit can't handle fdi = %d",s->fdi);

	if (b == NULL) {
		*limitf = NULL;
		*lcntx = NULL;
		*limitv = 0.0;
	} else {
		*limitf = s->limitf;
		*lcntx = s->lcntx;
		*limitv = s->limitv/INKSCALE;
	}
}

/* Set the RSPL_NEARCLIP LCh weightings. */
/* Will only work with L*a*b* like output spaces. */
/* Calling this will clear the reverse interpolaton cache. */
static void rev_set_lchw(
	struct _rspl *s,	/* this */
	double lchw[MXRO]	/* Weighting */
) {
	int f;

	DBG(("rev: setting LCH weightings %f %f %f \n",lchw[0], lchw[1], lchw[2]));

	/* This is a restricted size function */
	if (s->di > MXRI)
		error("rspl: rev_set_lchw can't handle di = %d",s->di);
	if (s->fdi > MXRO || s->fdi != 3)
		error("rspl: rev_set_lchw can't handle fdi = %d",s->fdi);

	s->rev.lchweighted = 1;
	for (f = 0; f < s->fdi; f++) {
		s->rev.lchw[f] = lchw[f];
		s->rev.lchw_sq[f] = s->rev.lchw[f] * s->rev.lchw[f];
	}
	s->rev.lchw_chsq = s->rev.lchw_sq[1] - s->rev.lchw_sq[2];	/* C - H squared weight */

	if (s->rev.inited) {				/* If cache and acceleration has been allocated */
		invalidate_revaccell(s);		/* Invalidate the reverse cache */
	}
}

#define RSPL_CERTAIN 0x80000000 						/* WILLCLIP hint is certain */
#define RSPL_WILLCLIP2 (RSPL_CERTAIN | RSPL_WILLCLIP)	/* Clipping will certainly be needed */

#ifdef CHECK_NNLU
static void check_nn(rspl *s, double *oval, co *cpp);
static void print_nnck(rspl *s);
#endif

/* Do reverse interpolation given target output values and (optional) auxiliary target */
/* input values. Return number of results and clipping flag. If return value == mxsoln, */
/* then there might be more results. The target values returned will correspond to the */
/* actual (possibly clipped) point. The return value is the number of solutions + */
/* a clipped flag. Properly set hint flags improve performance, but a correct result should */
/* be returned if the RSPL_NEARCLIP is set, even if they are not set correctly. */
/* If RSPL_NONNSETUP is set, then rev.fastsetup will be set for this call, avoiding */
/* initialization of the nngrid if RSPL_NEARCLIP hasn't been used before. */ 
static int
rev_interp_rspl(
	rspl *s,		/* this */
	int flags,		/* Hint flag */
	int mxsoln,		/* Maximum number of solutions allowed for */
	int *auxm,		/* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */
	double cdir[MXRO],	/* Clip vector direction and length - NULL if not used */
	co *cpp			/* Given target output space value in cpp[0].v[] +  */
					/* target input space auxiliaries in cpp[0].p[], return */
					/* input space solutions in cpp[0..retval-1].p[], and */
) {
	int e, di = s->di;
	int fdi = s->fdi;
	int i, *rip = NULL;
	schbase *b = NULL;		/* Base search information */
	double auxv[MXRI];		/* Locus proportional auxiliary values */
	int didclip = 0;		/* flag - set if we clipped the target */
	int fastsetup;			/* fastsetup on entry */
	
	DBGV(("\nrev interp called with out targets", fdi, " %f", cpp[0].v, "\n"));

	/* This is a restricted size function */
	if (di > MXRI)
		error("rspl: rev_interp can't handle di = %d",di);
	if (fdi > MXRO)
		error("rspl: rev_interp can't handle fdi = %d",fdi);

	if (auxm != NULL) {
		double ax[MXRI];
		for (i = 0; i < di; i++) {
			if (auxm[i] != 0)
				ax[i] = cpp[0].p[i];
			else
				ax[i] = 0.0;
		}
		DBGV(("                  auxiliaries mask", di, " %d", auxm, "\n"));
		DBGV(("                auxiliaries values", di, " %f", ax, "\n"));
	}
	DBG(("di = %d, fdi = %d\n",di, fdi));
	DBG(("flags = 0x%x\n",flags));

	fastsetup = s->rev.fastsetup;	/* fastsetup on entry */
	if (flags & RSPL_NONNSETUP)		/* Avoid triggering nnsetup on this call */
		s->rev.fastsetup = 1;

	mxsoln &= RSPL_NOSOLNS;			/* Prevent silliness */

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* Auxiliary is proportion of locus, so we need to find locus extent */	
	if (flags & RSPL_AUXLOCUS) {
		DBG(("rev interp has aux targets as proportion of locus\n"));

		flags &= ~RSPL_WILLCLIP;		/* Reset hint flag, as we will figure it out */

		/* For each valid auxiliary */
		for (e = 0; e < di; e++) {
			if (auxm[e] == 0)
				continue;			/* Skip unsused auxiliaries */
	
			/* Do search for min and max */
			DBG(("rev locus searching for aux %d min/max\n", e));
			if (b == NULL) {
				b = init_search(s, flags, cpp[0].p, auxm, cpp[0].v, cdir, cpp, mxsoln, locus);
#ifdef STATS
				s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
			} else
				set_lsearch(s, e);		/* Reset locus search for next auxiliary */

			if (rip == NULL) {		/* Not done this yet */
				rip = calc_fwd_cell_list(s, cpp[0].v); /* Reverse grid index for out target */
				if (rip == NULL) {
					DBG(("Got NULL list (point outside range) for auxiliary locus search\n"));
					flags |= RSPL_WILLCLIP2;
					break;
				}
			}
	
			search_list(b, rip, s->get_next_touch(s)); /* Setup, sort and search the list */
	
			if (b->min > b->max) {			/* Failed to find locus */
				DBG(("rev interp failed to find locus for aux %d, so expect clip\n",e));
				flags |= RSPL_WILLCLIP2;
				break;
			}
			auxv[e] = (cpp[0].p[e] * (b->max - b->min)) + b->min;
		}

		DBG(("rev interp got all locuses, so expect exact result\n",e));
		if (!(flags & RSPL_WILLCLIP)) {
			flags |= RSPL_EXACTAUX;				/* Got locuses, so expect exact result */
		}
	}

	/* Init the search information */
	if (b == NULL)
		b = init_search(s, flags, cpp[0].p, auxm, cpp[0].v, cdir, cpp, mxsoln, exact);
	else
		adjust_search(s, flags, auxv, exact);		/* Using proportion of locus aux */

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* If hinted that we will not need to clip, look for exact solution. */
	if (!(flags & RSPL_WILLCLIP)) {
		DBG(("Hint we won't clip, so trying exact search\n"));

		/* First do an exact search (init will select auxil if requested) */
		adjust_search(s, flags, NULL, exact);
	
		/* Figure out the reverse grid index appropriate for this request */
		if (rip == NULL)	/* Not done this yet */
			rip = calc_fwd_cell_list(s, cpp[0].v);
	
#ifdef STATS
			s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
		if (rip != NULL) {
			/* Setup, sort and search the list */
			search_list(b, rip, s->get_next_touch(s));
		} else {
			DBG(("Got NULL list (point outside range) for first exact fxcell\n"));
		}
	
		/* If we selected exact aux, but failed to find a solution, relax expectation */
		if (b->nsoln == 0 && b->naux > 0 && (flags & RSPL_EXACTAUX)) {
//printf("~1 relaxing notclip expactation when nsoln == %d, naux = %d, falgs & RSPL_EXACTAUX = 0x%x\n", b->nsoln,b->naux,flags & RSPL_EXACTAUX);
			DBG(("Searching for exact match to auxiliary target failed, so try again\n"));
			adjust_search(s, flags & ~RSPL_EXACTAUX, NULL, exact);

#ifdef STATS
			s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
			/* Candidate cell list should be the same */
			if (rip != NULL) {
				/* Setup, sort and search the list */
				search_list(b, rip, s->get_next_touch(s));
			} else {
				DBG(("Got NULL list (point outside range) for nearest search fxcell\n"));
			}
		}
	}

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* If the exact search failed, and we should look for a nearest solution */
	if (b->nsoln == 0 && (flags & RSPL_NEARCLIP)) {
#ifdef CHECK_NNLU
		int f, fdi = s->fdi;
		double oval[MXRO];			/* Save the input target value for check_nn() */

		for (f = 0; f < fdi; f++)
			oval[f] = cpp[0].v[f];
#endif
		DBG(("Trying nearest search\n"));

#ifdef STATS
		s->rev.st[b->op].searchcalls++;
#endif	/* STATS */

		/* We get returned a list of cube base indexes of all cubes that have */
		/* the closest valid vertex value to the target value. */
		adjust_search(s, flags, NULL, clipn);

		/* Get list of cells enclosing nearest vertex */
		if ((rip = calc_fwd_nn_cell_list(s, cpp[0].v)) != NULL) {
			search_list(b, rip, s->get_next_touch(s)); /* Setup, sort and search the list */
		} else {
			DBG(("Got NULL list! (point inside gamut \?\?) for nearest search\n"));
		}

		if (b->nsoln > 0) {
			didclip = RSPL_DIDCLIP;
#ifdef CHECK_NNLU
			check_nn(s, oval, cpp);		/* Run diagnostic to check sanity of result */
#endif
		}
	}

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* If we still don't have a solution, do a vector direction clip */
	if (b->nsoln == 0 && b->canvecclip) {
		/* Find clipping solution in vector direction */
		line ln;				/* Structure to hold line context */
		unsigned int tcount;	/* grid touch count for this operation */

		DBG(("Starting a clipping vector search now!!\n"));

		adjust_search(s, flags, NULL, clipv);

		tcount = s->get_next_touch(s);		/* Get next grid touched generation count */

#ifdef STATS
		s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
		init_line_eq(b, b->v, cdir);				/* Init the implicit line equation */
		rip = init_line(s, &ln, cpp[0].v, cdir);	/* Init the line cell dda */
//~~1 HACK!!! should be <= 1.0 !!!
		for (; ln.t <= 2.0; rip = next_line_cell(&ln)) {
			if (rip == NULL) {
				DBG(("Got NULL list for this fxcell\n"));
				continue;
			}

			/* Setup, sort and search the list */
			search_list(b, rip, tcount);

			/* If we have found a solution, then abort the search - */
			/* this line will be taking us away from the best solution. */
			if (b->nsoln > 0)
				break;
		}
		if (b->nsoln > 0)
			didclip = RSPL_DIDCLIP;
	}

	/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
	/* If the clipped solution seems to have been jumping to conclusions, */
	/* search for an exact solution. */
	if (didclip && (flags & RSPL_WILLCLIP && !(flags & RSPL_CERTAIN))
	 && (b->cdist/s->get_out_scale(s)) < 0.002) {
		co c_cpp       = b->cpp[0];	/* Save clip solution in case we want it */
		double c_idist = b->idist;	
		int c_iabove   = b->iabove;	
		int c_nsoln    = b->nsoln;
		int c_pauxcell = b->pauxcell;
		double c_cdist = b->cdist;
		int c_iclip    = b->iclip;

		DBG(("Trying exact search again\n"));

		/* Do an exact search (init will select auxil if requested) */
		adjust_search(s, flags & ~RSPL_WILLCLIP, NULL, exact);
	
		/* Figure out the reverse grid index appropriate for this request */
		rip = calc_fwd_cell_list(s, cpp[0].v);
	
#ifdef STATS
		s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
		if (rip != NULL) {
			/* Setup, sort and search the list */
			search_list(b, rip, s->get_next_touch(s));
		} else {
			DBG(("Got NULL list (point outside range) for first exact fxcell\n"));
		}
	
		/* If we selected exact aux, but failed to find a solution, relax expectation */
		if (b->nsoln == 0 && b->naux > 0 && (flags & RSPL_EXACTAUX)) {
			DBG(("Searching for exact match to auxiliary target failed, so try again\n"));
//printf("~1 relaxing didclip expactation when nsoln == %d, naux = %d, flags & RSPL_EXACTAUX = 0x%x\n", b->nsoln,b->naux,flags & RSPL_EXACTAUX);
			adjust_search(s, flags & ~RSPL_EXACTAUX, NULL, exact);

#ifdef STATS
			s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
			/* Candidate cell list should be the same */
			if (rip != NULL) {
				/* Setup, sort and search the list */
				search_list(b, rip, s->get_next_touch(s));
			} else {
				DBG(("Got NULL list (point outside range) for nearest search fxcell\n"));
			}
		}

		/* If we did get an exact solution */
		if (b->nsoln > 0) {
			DBG(("Deciding to return exact solution after finding clipped\n"));
			didclip = 0;		/* Reset did-clip and return exact solution */

		} else {
			DBG(("keeping clipped solution\n"));
			/* Restore the clipped solution */
			b->cpp[0] = c_cpp;
			b->idist = c_idist;	
			b->iabove = c_iabove;	
			b->nsoln = c_nsoln;
			b->pauxcell = c_pauxcell;
			b->cdist = c_cdist;
			b->iclip = c_iclip;
		}
	}

	if (b->nsoln > 0) {
		DBGV(("rev interp returning 1st soln: ",di," %f", cpp[0].p, "\n"));
	}
	DBG(("rev interp returning %d solutions%s\n",b->nsoln, didclip ? " [clip]" : ""));

	s->rev.fastsetup = fastsetup;	/* retore fastsetup state */

	return b->nsoln | didclip;
}

/* ------------------------------------------------------------------------------------ */
/* Do reverse search for the auxiliary min/max ranges of the solution locus for the */
/* given target output values. */
/* Return number of locus segments found, up to mxsoln. 0 will be returned if no solutions */
/* are found. */

static int
rev_locus_segs_rspl (
	rspl *s,		/* this */
	int *auxm,		/* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */
	co *cpp,		/* Input value in cpp[0].v[] */
	int mxsoln,		/* Maximum number of solutions allowed for */
	double min[][MXRI],	/* Array of min[MXRI] to hold return segment minimum values. */
	double max[][MXRI]	/* Array of max[MXRI] to hold return segment maximum values. */
) {
	int e, di = s->di;
	int f, fdi = s->fdi;
	int six;		/* solution index */
	int *rip = NULL;
	int rv = 1;				/* Return value */
	schbase *b = NULL;		/* Base search information */
	
	DBGV(("rev locus called with out targets", fdi, " %f", cpp[0].v, "\n"));
	
	/* This is a restricted size function */
	if (di > MXRI)
		error("rspl: rev_locus_segs can't handle di = %d",di);
	if (fdi > MXRO)
		error("rspl: rev_locus_segs can't handle fdi = %d",fdi);

	if (mxsoln < 1) {
		return 0;			/* Guard against silliness */
	}

	if (auxm != NULL) {
		int i;
		double ax[MXRI];
		for (i = 0; i < di; i++) {
			if (auxm[i] != 0)
				ax[i] = cpp[0].p[i];
			else
				ax[i] = 0.0;
		}
		DBGV(("                  auxiliaries mask", di, " %d", auxm, "\n"));
		DBGV(("                auxiliaries values", di, " %f", ax, "\n"));
	}

	/* Init default return values */
	for (six = 0; six < mxsoln; six++) {
		for (e = 0; e < di; e++) {
			if (auxm[e] == 0) {
				min[six][e] = max[six][e] = 0;	/* Return 0 for unused auxiliaries */
			} else {
				min[six][e] = 1.0;			/* max < min indicates invalid range */
				max[six][e] = 0.0;
			}
		}
	}

	/* For each valid auxiliary */
	for (e = 0; e < di; e++) {
		if (auxm[e] == 0)
			continue;			/* Skip unsused auxiliaries */

		/* Do search for min and max */
		DBG(("rev locus searching for aux %d min/max\n", e));
		if (b == NULL)
			b = init_search(s, 0, cpp[0].p, auxm, cpp[0].v, NULL, cpp, mxsoln, locus);
		else
			set_lsearch(s, e);		/* Reset locus search for next auxiliary */

		if (rip == NULL) {		/* Not done this yet */
			rip = calc_fwd_cell_list(s, cpp[0].v); /* Reverse grid index for this request */
			if (rip == NULL) {
				DBG(("Got NULL list (point outside range) for auxiliary locus search\n"));
				rv = 0;
				break;
			}
		}

		search_list(b, rip, s->get_next_touch(s)); /* Setup, sort and search the list */

		if (b->min > b->max) {
			rv = 0;				/* Failed to find a result */
			break;
		}

		if (b->asegs == 0) {		/* Overall min max only */

			min[0][e] = b->min;		/* Save single result */
			max[0][e] = b->max;

		} else {				/* Tracking auxiliary segments */
			int si;					/* Start i */
			int i, j, ff;

			/* Sort the segment list */
#define 	HEAP_COMPARE(A,B) (A.xval < B.xval)
			HEAPSORT(axisec, b->axisl, b->axisln)
#undef 		HEAP_COMPARE

#ifdef NEVER
for (i = 0; i < b->axisln; i++) {
printf("~2 xval = %f, verts = ",b->axisl[i].xval);
for (f = 0; f < b->axisl[i].nv; f++)
printf(" %d", b->axisl[i].vix[f]);
printf("\n");
}
#endif
			/* Find the segments by finding common vertices */
			six = si = i = 0;

			min[six][e] = b->axisl[i].xval;

			for (i++; i < (b->axisln-1); i++) {
				/* Check if any i and i-1 to j are connected */
				for (j = i-1; j >= si; j--) {
					for (f = 0; f < b->axisl[j].nv; f++) {
						for (ff = 0; ff < b->axisl[i].nv; ff++) {
							if (b->axisl[j].vix[f] == b->axisl[i].vix[ff])
								break;		/* Found a link */
						}
						if (ff < b->axisl[i].nv)
							break;
					}
					if (f < b->axisl[j].nv)
						break;
				}
				if (j < si) {	/* Wasn't linked */
					int ii, jj;
					/* Think we found a break. Check that all the rest of */
					/* the entries don't have any links to the previous group */

					/* This could be rather a slow way of checking ! (On^2) */
					for (ii = i+1; ii < (b->axisln); ii++) {
						for (jj = i-1; jj >= si; jj--) {
							for (f = 0; f < b->axisl[jj].nv; f++) {
								for (ff = 0; ff < b->axisl[ii].nv; ff++) {
									if (b->axisl[jj].vix[f] == b->axisl[ii].vix[ff])
										break;		/* Found a link */
								}
								if (ff < b->axisl[ii].nv)
									break;
							}
							if (f < b->axisl[jj].nv)
								break;
						}
						if (jj >= si)
							break;
					}
					if (ii >= b->axisln) {	/* Wasn't forward linked */
						/* Nothing ahead links to last group */
						max[six][e] = b->axisl[i-1].xval;

						/* If we run out of solution space */
						/* merge the last segments */
						if ((six+1) < mxsoln) {
							six++;
							min[six][e] = b->axisl[i].xval;
						}
					}
				}
			}
			max[six++][e] = b->axisl[i].xval;

			if (six > rv)
				rv = six;
		}
	}

#ifdef STATS
	s->rev.st[b->op].searchcalls++;
#endif	/* STATS */
	if (rv) {
		for (six = 0; six < rv; six++) {
			DBG(("rev locus returning:\n"));
			DBGV(("     min", di, " %f", min[six], "\n"));
			DBGV(("     max", di, " %f", max[six], "\n"));
		}
	}

	DBG(("rev locus returning status %d\n",rv));
	return rv;
}

/* ------------------------------------------------------------------------------------ */
typedef double mxdi_ary[MXRI];

/* Do reverse search for the locus of the auxiliary input values given a target output. */
/* Return 1 on finding a valid solution, and 0 if no solutions are found. */
static int
rev_locus_rspl(
	rspl *s,		/* this */
	int *auxm,		/* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */
	co *cpp,		/* Input value in cpp[0].v[] */
	double min[MXRI],/* Return minimum auxiliary values */
	double max[MXRI] /* Return maximum auxiliary values */
) {

	/* Use segment routine to compute oveall locus */
	return rev_locus_segs_rspl (s, auxm, cpp, 1, (mxdi_ary *)min, (mxdi_ary *)max);
}

/* ------------------------------------------------------------------------------------ */

#ifdef DEBUG2
#define DEBUG
#undef DBG
#undef DBGV
#undef DBGM
#define DBG(xxx) DBGI(xxx)
#define DBGV(xxx) DBGVI xxx
#define DBGM(xxx) DBGMI xxx
#else
#undef DEBUG
#undef DBG
#undef DBGV
#undef DBGM
#define DBG(xxx) 
#define DBGV(xxx) 
#define DBGM(xxx) 
#endif

/* ------------------------------------------------ */
/* subroutines of top level reverse lookup routine */

static int exact_setsort(schbase *b, fxcell *c);
static int exact_compute(schbase *b, simplex *x);

static int auxil_setsort(schbase *b, fxcell *c);
static int auxil_check(schbase *b, fxcell *c);
static int auxil_compute(schbase *b, simplex *x);

static int locus_setsort(schbase *b, fxcell *c);
static int locus_check(schbase *b, fxcell *c);
static int locus_compute(schbase *b, simplex *x);

static int clipv_setsort(schbase *b, fxcell *c);
static int clipv_check(schbase *b, fxcell *c);
static int clipv_compute(schbase *b, simplex *x);

static int clipn_setsort(schbase *b, fxcell *c);
static int clipn_check(schbase *b, fxcell *c);
static int clipn_compute(schbase *b, simplex *x);

/* Allocate the search base structure */
static schbase *
alloc_sb(rspl *s) {
	schbase *b;
	if ((b = s->rev.sb = (schbase *)rev_calloc(s, 1, sizeof(schbase))) == NULL)
		error("rspl malloc failed - rev.sb structure");
	INCSZ(s, sizeof(schbase));

	b->s = s;				/* rsp */
	b->pauxcell =			/* Previous solution cell indexes */
	b->plmaxcell = 
	b->plmincell = -1;

	return b;
}

/* Free the search base structure */
static void
free_sb(schbase *b) {
	DECSZ(b->s, sizeof(schbase));
	free(b);
}

/* Do the basic search type independent initialization */
static schbase *	/* Return pointer to base search information */
init_search(
	rspl *s,		/* rsp; */
	int flags,		/* Hint flag */

	double *av,		/* Auxiliary input values - may be NULL */
	int *auxm,		/* Array of di mask flags, !=0 for valid auxliaries (NULL if no auxiliaries) */
					/* Locus search will search for max/min of first valid auxlilary */
	double *v,		/* Output value target, NULL if none */
	double *cdir,	/* Clip vector direction/LCh weighting, NULL if none */
	co *cpp,		/* Array that hold solutions, NULL if none. */
	int mxsoln,		/* Maximum number of solutions allowed for */
	enum ops op		/* Type of reverse search operation requested */
) {
	schbase *b = NULL;		/* Pointer to search base information structure */
	int e, di = s->di;
	int f, fdi = s->fdi;

	DBG(("Initializing search di %d fdi %d\n",s->di,s->fdi));

	if (s->rev.inited == 0) 	/* Compute reverse info if it doesn't exist */
		make_rev(s);

	/* If first time initialisation (Fourth section init) */
	if ((b = s->rev.sb) == NULL)
		b = alloc_sb(s);

	/* Init some basic search info */
	b->op    = op;				/* operation */
	b->flags = flags;			/* hint flags */
	b->canvecclip = 0;			/* Assume invalid clip direction */

	b->ixc = (1<<di)-1;			/* Cube index of corner that holds maximum input values */

	/* Figure out if auxiliaries have been requested */
	b->naux = 0;
	b->auxbm = 0;
	if (auxm != NULL) {
		unsigned bm;

		if (mxsoln > 1)
			b->asegs = 1;		/* Find all segments */
		else
			b->asegs = 0;		/* Find only overall aux locus range */

		for (e = di-1, bm = 1 << e; e >= 0; e--, bm >>= 1) {	/* Record auxiliary mask bits */
			if (av != NULL)
				b->av[e] = av[e];	/* Auxiliary target values */
			b->auxm[e] = auxm[e];	/* Auxiliary mask */
			if (auxm[e] != 0) {
				b->auxbm |= bm;			/* Auxiliary bit mask */
				b->auxi[b->naux++] = e;	/* Index of next auxiliary input to be used */
				/* Auxiliary locus extent */
				b->lxi = e;			/* Assume first one */
				b->max = -INF_DIST;	/* In case searching for max */
				b->min =  INF_DIST;	/* In case searching for minimum */
				b->axisln = 0;		/* No intersects in list */
			}
		}
	}

	/* Figure out if the clip direction is meaningfull */
	/* Check that the clip vector makes sense */
	if (!(flags & RSPL_NEARCLIP)  && cdir != NULL) {	/* Clip vector is specified */
		double ss;
		for (ss = 0.0, f = 0; f < fdi; f++) {
			double tt = cdir[f];
			b->cdir[f] = tt;
			ss += tt * tt;
		}

		if (ss > 1e-6) {
			b->canvecclip = 1;	/* It has a non-zero length */
			ss = sqrt(ss);
			/* Compute normalised clip vector direction */
			for (f = 0; f < fdi; f++) {
				b->ncdir[f] = b->cdir[f]/ss;
			}
		}
	}

	if (di <= fdi)		/* Only allow auxiliaries if di > fdi */
		b->naux = 0;

	/* Switch to appropriate operation */
	if (b->op == exact && (b->naux > 0 || di != fdi)) {
		b->op = auxil;
	} else if (b->op == auxil && b->naux == 0 && di == fdi) {
		b->op = exact;
	}

	/* Set appropriate functions for type of operation */
	switch (b->op) {
		case exact:
			b->setsort = exact_setsort;
			b->check   = NULL;
			b->compute = exact_compute;
			b->snsdi = b->ensdi = di;	/* Search full dimension simplex, expect point soln. */
			break;
		case auxil:
			b->setsort = auxil_setsort;
			b->check   = auxil_check;
			b->compute = auxil_compute;
			b->snsdi = di;				/* Start here DOF = di-fdi locus solutions */
			b->ensdi = fdi;				/* End with DOF = 0 for point solutions */
			break;
		case locus:
			b->setsort = locus_setsort;
			b->check   = locus_check;
			b->compute = locus_compute;
			b->snsdi = b->ensdi = fdi;	/* Search for point solutions */
			break;
		case clipv:
			b->setsort = clipv_setsort;
			b->check   = clipv_check;
			b->compute = clipv_compute;
											/* Clip vector 1 dimension in output space, */
			b->snsdi = b->ensdi = fdi-1;	/* search planes for combined point solution */
			break;
		case clipn:
			b->setsort = clipn_setsort;
			b->check   = clipn_check;
			b->compute = clipn_compute;
			b->snsdi = 0;				/* Start with DOF = 0 for point solutions */
			b->ensdi = fdi-1;			/* End on DOF = di-fdi-1 on surfaces of simplexes */
			break;
		default:
			error("init_search: Unknown operation %d\n",b->op);
	}

	if (v != NULL) {
		for (f = 0; f < fdi; f++)	/* Record target output values */
			b->v[f] = v[f];
		b->v[fdi] = s->limitv;		/* Limitvalue is output target for limit clip subsimplexes */
	}

	b->mxsoln = mxsoln;				/* Allow solutions to be returned */
	b->cpp    = cpp;				/* Put solutions here */
	b->nsoln = 0;					/* No solutions at present */
	b->iclip = 0;					/* Default solution isn't above ink limit */

	if (flags & RSPL_EXACTAUX)		/* Expect to be able to match auxiliary target exactly */
		b->idist = 2.0 * EPS;		/* Best input distance to beat - helps sort/triage */
	else
		b->idist = INF_DIST;		/* Best input distance to beat. */
	b->iabove = 0;					/* Best isn't known to be above (yet) */

	b->cdist = INF_DIST;			/* Best clip distance to beat. */

	DBG(("Search initialized\n"));

	return b;
}

/* Adjust the search */
static void
adjust_search(
	rspl *s,		/* rsp; */
	int flags,		/* Hint flag */
	double *av,		/* Auxiliary input values - may be NULL */
	enum ops op		/* Type of reverse search operation requested */
) {
	schbase *b = s->rev.sb;		/* Pointer to search base information structure */
	int e, di = s->di;
	int fdi = s->fdi;

	DBG(("Adjusting search\n"));

	b->op    = op;				/* operation */
	b->flags = flags;			/* hint flags */

	/* Switch from exact to aux if we need to */
	if (b->op == exact && (b->naux > 0 || di != fdi)) {
		b->op = auxil;
	} else if (b->op == auxil && b->naux == 0 && di == fdi) {
		b->op = exact;
	}

	/* Update auxiliary target values */
	if (av != NULL) {
		for (e = 0; e < b->naux; e++) {
			int ee = b->auxi[e];
			b->av[ee] = av[ee];
		}
	}

	/* Set appropriate functions for type of operation */
	switch (b->op) {
		case exact:
			b->setsort = exact_setsort;
			b->check   = NULL;
			b->compute = exact_compute;
			b->snsdi = b->ensdi = di;		/* Expect point solution */
			break;
		case auxil:
			b->setsort = auxil_setsort;
			b->check   = auxil_check;
			b->compute = auxil_compute;
			b->snsdi = di;				/* Start here DOF = di-fdi locus solutions */
			b->ensdi = fdi;				/* End with DOF = 0 for point solutions, */
			break;						/* will early exit DOF if good soln found. */
		case locus:
			b->setsort = locus_setsort;
			b->check   = locus_check;
			b->compute = locus_compute;
			b->snsdi = b->ensdi = fdi;	/* Search for point solutions */
			break;
		case clipv:
			b->setsort = clipv_setsort;
			b->check   = clipv_check;
			b->compute = clipv_compute;
											/* Clip vector 1 dimension in output space, */
			b->snsdi = b->ensdi = fdi-1;	/* so the intersection with the simplex is a point. */
			break;
		case clipn:
			b->setsort = clipn_setsort;
			b->check   = clipn_check;
			b->compute = clipn_compute;
			b->snsdi = 0;				/* Start with DOF = 0 for point solutions */
			b->ensdi = fdi-1;			/* End on DOF = di-fdi-1 on surfaces of simplexes */
			break;						/* Will go through all DOF */
		default:
			error("init_search: Unknown operation %d\n",b->op);
	}

	b->nsoln = 0;					/* No solutions at present */

	if (flags & RSPL_EXACTAUX)		/* Expect to be able to match auxiliary target exactly */
		b->idist = 2.0 * EPS;		/* Best input distance to beat - helps sort/triage */
	else
		b->idist = INF_DIST;		/* Best input distance to beat. */
	b->iabove = 0;					/* Best isn't known to be above (yet) */

	b->cdist = INF_DIST;			/* Best clip distance to beat. */

	DBG(("Search adjusted\n"));
}

/* Adjust existing locus search for a different auxiliary */
static void
set_lsearch(
rspl *s,
int e			/* Next auxiliary */
) {
	schbase *b = s->rev.sb;		/* Pointer to search base information structure */

	b->lxi = e;			/* Assume first one */
	b->max = -INF_DIST;	/* In case searching for max */
	b->min =  INF_DIST;	/* In case searching for minimum */
	b->axisln = 0;		/* No intersects in list */
}

/* Set the limit search information */
/* Note this doesn't create or init the main rev information. */
static schbase *	/* Return pointer to base search information */
set_search_limit(
	rspl *s,		/* rsp; */
	double (*limitf)(void *vcntx, double *in),	/* Optional input space limit function. Function */
					/* should evaluate in[0..di-1], and return number that is not to exceed */
					/* limitv. NULL if not used */
	void *lcntx,	/* Context passed to limit() */
	double limitv	/* Value that limitf() is not to exceed */
) {
	schbase *b = NULL;		/* Pointer to search base information structure */

	/* If sb info needs initialising (Fourth section init) */
	if ((b = s->rev.sb) == NULL) {
		b = alloc_sb(s);
	}

	s->limitf = limitf;				/* Input limit function */
	s->lcntx  = lcntx; 				/* Context passed to limit() */
	s->limitv = INKSCALE * limitv; 	/* Context passed to values not to be exceeded by limit() */
	if (limitf != NULL) {
		s->limiten = 1;				/* enable limiting by default */
	} else
		s->limiten = 0;				/* No limit function, so limiting not enabled. */

	return b;
}

/* Free any search specific data, plus the search base. */
static void
free_search(
schbase *b	/* Base search information */
) {
	DBG(("Freeing search\n"));

	/* Clip line implicit equation (incuding space for ink target) */
	if (b->cla != NULL) {
		int fdi = b->s->fdi;
		free_dmatrix(b->cla, 0, fdi-1, 0, fdi);
		b->cla = NULL;
	}

	/* Auxiliary segment list */
	if (b->axislz > 0) {
		free(b->axisl);
		DECSZ(b->s, b->axislz * sizeof(axisec));
		b->axisl = NULL;
		b->axislz = 0;
		b->axisln = 0;
	}

	/* Sorted cell list */
	if (b->lclistz > 0) {
		free(b->lclist);
		DECSZ(b->s, b->lclistz * sizeof(fxcell *));
		b->lclist = NULL;
		b->lclistz = 0;
	}

	/* Simplex filter list */
	if (b->lsxfilt > 0) {
		free(b->sxfilt);
		DECSZ(b->s, b->lsxfilt * sizeof(char));
		b->sxfilt = NULL;
		b->lsxfilt = 0;
	}

	free_sb(b);
}

/* Return the pointer to the list of fwd cells given */
/* the target output values. The pointer will be to the first */
/* index in the list (ie. list address + 3) */
/* Return NULL if none in list (out of gamut). */
static int *
calc_fwd_cell_list(
	rspl *s,		/* this */
	double *v		/* Output values */
) {
	int f, fdi = s->fdi;
	int **rpp;
	int rgres_1 = s->rev.res - 1;

	if (s->rev.rev_valid == 0)
		init_revaccell(s);
		
	for (rpp = s->rev.rev, f = 0; f < fdi; f++) {
		int mi;
		double t = (v[f] - s->rev.gl[f])/s->rev.gw[f];
		mi = (int)floor(t);				/* Grid coordinate */
		if (mi < 0 || mi > rgres_1) { 	/* If outside valid reverse range */
			return NULL;
		}
		rpp += mi * s->rev.coi[f];	/* Accumulate reverse grid pointer */
	}
	s->rev.sb->rix = rpp - s->rev.rev;	/* Set diagnostic value */

	if (*rpp == NULL)
		return NULL;
	return (*rpp) + 3;
}

void alloc_simplexes(fxcell *c, int nsdi);

/* Given a pointer to a list of fwd cells, cull cells that */
/* cannot contain or improve the solution, sort the list, */
/* and then compute the final best solution. */
static void
search_list(
schbase *b,				/* Base search information */
int     *rip,			/* Pointer to first index in cell list */
unsigned int tcount		/* grid touch count for this operation */
) {
	rspl *s = b->s;
	int nsdi;
	int i;
	int nilist;				/* Number in cell list */
	unsigned int stouch;	/* Simplex touch count */
	
	DBG(("search_list called\n"));

	/* (rip[-3] contains allocation for fwd cells in the list) */
	/* (rip[-2] contains the index of the next free entry in the list) */
	/* (rip[-1] contains the reference count for the list) */
	if (b->lclistz < rip[-3]) {	/* Allocate more space if needed */

		if (b->lclistz > 0) {	/* Free old space before allocating new */
			free(b->lclist);
			DECSZ(b->s, b->lclistz * sizeof(fxcell *));
		}
		b->lclistz = 0;
		/* Allocate enough space for all the candidate cells */
		if ((b->lclist = (fxcell **)rev_malloc(s, rip[-3] * sizeof(fxcell *))) == NULL)
			error("rev: malloc failed - candidate cell list, count %d",rip[-3]);
		b->lclistz = rip[-3];	/* Current allocated space */
		INCSZ(b->s, b->lclistz * sizeof(fxcell *));
	}
		
	/* Get the next simplex touch count, so that we don't search shared */
	/* face simplexes more than once in this pass through the cells. */
	if ((stouch = ++s->rev.stouch) == 0) {	/* If touch count rolls over */
		fxcell *cp;
		stouch = s->rev.stouch = 1;

		/* For all of the cells */
		DBG(("touch has rolled over, resetting it\n"));
		for (cp = s->rev.cache->mrubot; cp != NULL; cp = cp->mruup) {
			int nsdi;
	
			if (cp->s == NULL)	/* Cell has never been used */
				continue;

			/* For all the simplexes in the fxcell */
			for (nsdi = 0; nsdi <= s->di; nsdi++) {
				if (cp->sx[nsdi] != NULL) {
					int si;

					for (si = 0; si < cp->sxno[nsdi]; si++) {
						cp->sx[nsdi][si]->touch = 0;
					}
				}
			}
		}
	}

	/* For each chunk of the list that we can fit in the rcache: */
	for (; *rip != -1;)  {

		/* Go through all the candidate fwd cells, and build up the list of search cells */
		for (nilist = 0; *rip != -1; rip++)  {
			int ix = *rip;				/* Fwd cell index */
			float *fcb = s->g.a + ix * s->g.pss;	/* Pointer to base float of fwd cell */
			fxcell *c;

			if (TOUCHF(fcb) >= tcount) {	/* If we have visited this cell before */
				DBG((" Already touched cell index %d\n",ix));
				continue;
			}
			/* Get pointers to cells from cache, and lock it in the cache */
			if ((c = get_fxcell(b, ix, nilist == 0 ? 1 : 0)) == NULL) {
				static int warned = 0;
				if (!warned) {
					warning("%cWarning - Reverse Cell Cache exausted, processing in chunks",cr_char);
					warned = 1;
				}
				DBG(("revcache is exausted, do search in chunks\n"));
				if (nilist == 0) {
					/* This should never happen, because nz force should prevent it */
					revcache *rc = s->rev.cache;
					fxcell *cp;
					int nunlk = 0;
					/* Double check that there are no unlocked cells */
					for (cp = rc->mrubot; cp != NULL && cp->refcount > 0; cp = cp->mruup) {
						if (cp->refcount == 0)
							nunlk++;
					}
					fprintf(stdout,"Diagnostic: rev.sz = %lu, rev.max_sz = %lu, numlocked = %d, nunlk = %d\n",
					               (unsigned long)rc->s->rev.sz, (unsigned long)rc->s->rev.max_sz,
					               rc->nunlocked, nunlk);
					error("Not enough memory to process in chunks");
				}
				break;		/* cache has run out of room, so abandon, and do it next time */
			}

			DBG(("checking out cell %d range %s\n",ix,pcellorange(c)));
			TOUCHF(fcb) = tcount;			/* Touch it */

			/* Check mandatory conditions, and compute search key */
			if (!b->setsort(b, c)) {
				DBG(("cell %d rejected from list\n",ix));
				unget_fxcell(s->rev.cache, c);
				continue;
			}
			DBG(("cell %d accepted into list\n",ix));

			b->lclist[nilist++] = c; /* Cell is accepted as recursion candidate */
		}

		if (nilist == 0) {
			DBG(("List was empty\n"));
		}

#ifdef DOSORT
		/* If appropriate, sort child cells into best order */
		/* == sort key smallest to largest */
		switch (b->op) {
			case locus:
				{	/* Special case, adjust sort values */
					double min = INF_DIST, max = -INF_DIST;
					for (i = 0; i < nilist; i++) {
						fxcell *c = b->lclist[i];
						if (c->sort < min)
							min = c->sort;
						if (c->sort > max)
							max = c->sort;
					}
					max = min + max;	/* Total of min/max */
					min = 0.5 * max;	/* Average sort value */
					for (i = 0; i < nilist; i++) {
						fxcell *c = b->lclist[i];
						if (c->ix == b->plmincell || c->ix == b->plmaxcell) {
							c->sort = -1.0;		/* Put previous solution cells at head of list */
						} else if (c->sort > min) {
							c->sort = max - c->sort;	/* Reflect about average */
						}
					}
				}
				/* Fall through to sort */
			case auxil:
			case clipv:
			case clipn:
#define 	HEAP_COMPARE(A,B) (A->sort < B->sort)
				HEAPSORT(fxcell *,b->lclist, nilist)
#undef 		HEAP_COMPARE
				break;
			default:
				break;
		}
#endif /* DOSORT */

		DBG(("List sorted, about to search\n"));
#ifdef NEVER
		printf("\n~1 Op = %s, Cell sort\n",opnames[b->op]);
		for (i = 0; i < nilist; i++) {
			printf("~1 List %d, cell %d, sort = %f\n",i,b->lclist[i]->ix,b->lclist[i]->sort);
		}
#endif /* NEVER */

		/* 
			Tried reversing the "for each cell" and "for each level" loops,
			but it made a negligible difference to the performance.
			We choose to have cell on the outer so that we can unlock
			them as we go, so that they may be freed, even though
			this is a couple of percent slower (?).
		 */

		/* For each cell in the list */
		for (i = 0; i < nilist; i++) {
			fxcell *c = b->lclist[i];

#ifdef STATS
			s->rev.st[b->op].csearched++;
#endif /* STATS */

			/* For each dimensionality of sub-simplexes, in given order */
			DBG(("Searching from level %d to level %d\n",b->snsdi, b->ensdi));
			for (nsdi = b->snsdi;;) {
				int j, nospx;					/* Number of simplexes in cell */

				DBG(("\n******************\n"));
				DBG(("Searching level %d\n",nsdi));

				/* For those searches that have an optimisation goal, */
				/* re-check the cell to see if the goal can still improve on. */
				if (b->check != NULL && !b->check(b, c))
					break;

				if (c->sx[nsdi] == NULL) {
					alloc_simplexes(c, nsdi);	/* Do level 1 initialisation for nsdi */
				}

				/* For each simplex in a cell */
				nospx = c->sxno[nsdi];			/* Number of nsdi simplexes */
				for (j = 0; j < nospx; j++) {
					simplex *x = c->sx[nsdi][j];

					if (x->touch >= stouch) {
						continue;						/* We've already seen this one */
					}

					if (s->limiten == 0) {
						if (x->flags & SPLX_CLIPSX)		/* If limiting is disabled, we're */
							continue;					/* not interested in clip plane simplexes */
					}
#ifdef STATS
					s->rev.st[b->op].ssearched++;
#endif /* STATS */
					if (b->compute(b, x)) {
						DBG(("search aborted by compute\n"));
						break;					/* Found enough solutions */
					}
					x->touch = stouch;			/* Don't look at it again */

				}	/* Next Simplex */

				if (nsdi == b->ensdi)
					break;						/* We're done with levels */

				/* Next Simplex dimensionality */
				if (b->ensdi < b->snsdi) {
					if (nsdi == b->snsdi && b->nsoln > 0
					 && (b->op != auxil || b->idist <= 2.0 * EPS))
						break; 		/* Don't continue though decreasing */
									/* sub-simplex dimensions if we found a solution at */
									/* the highest dimension level. */
					nsdi--;
				} else if (b->ensdi > b->snsdi) {
					nsdi++;				/* Continue through increasing sub-simplex dimenionality */
				}						/* until we get to the top. */
			}
			/* Unlock the fxcell now that we're done with it */
			unget_fxcell(s->rev.cache, b->lclist[i]);
		}	/* Next cell */

	}	/* Next chunk */

	DBG(("search_list complete\n"));
	return;
}

/* ------------------------------------- */
/* Vector search in output space support */

/* Setup the line, and fetch the first cell */
/* Return the pointer to the list of fwd cells, NULL if none in list. */
static int *
init_line(
	rspl *s,			/* this */
	line *l,			/* line structure */
	double st[MXRO],	/* start of line */
	double de[MXRO]		/* line direction and length */
) {
	int f, fdi = s->fdi;
	int **rpp;
	int rgres_1 = s->rev.res - 1;
	int nvalid = 0;		/* Flag set if outside reverse grid range */

	DBGV(("Line from ", fdi, " %f", st, "\n"));
	DBGV(("In dir    ", fdi, " %f", de, "\n"));
	DBGV(("gl        ", fdi, " %f", s->rev.gl, "\n"));
	DBGV(("gh        ", fdi, " %f", s->rev.gh, "\n"));
	DBGV(("gw        ", fdi, " %f", s->rev.gw, "\n"));
	
	/* Init */
	l->s = s;
	for (f = 0; f < fdi; f++) {
		l->st[f] = st[f] - s->rev.gl[f];
		l->de[f] = de[f];
		if (de[f] > 0.0)
			l->di[f] = 1;	/* Axis increments */
		else if (de[f] < 0.0)
			l->di[f] = -1;
		else
			l->di[f] = 0;
	}
	l->t = 0.0;
	DBGV(("increments =", fdi, " %d", l->di, "\n"));

	/* Figure out the starting cell */
	for (rpp = s->rev.rev, f = 0; f < fdi; f++) {
		double t = l->st[f]/s->rev.gw[f];
		l->ci[f] = (int)floor(t);					/* Grid coordinate */
		if (l->ci[f] < 0 || l->ci[f] > rgres_1) 	/* If outside valid reverse range */
			nvalid = 1;
		rpp += l->ci[f] * s->rev.coi[f];	/* Accumulate reverse grid pointer */
	}
	DBGV(("current line cell = ", fdi, " %d", l->ci, "")); DBG((",  t = %f, nvalid = %d\n",l->t,nvalid));
#ifdef DEBUG
	{
		int ii;
		double tt;
		printf("Current cell = ");
		for (ii = 0; ii < fdi; ii++) {
			tt = l->ci[ii] * s->rev.gw[ii] + s->rev.gl[ii];
			printf(" %f - %f",tt,tt+s->rev.gw[ii]);
		}
		printf("\n");
	}
#endif	/* DEBUG */
	if (nvalid)
		return NULL;
	if (*rpp == NULL)
		return NULL;
	return *rpp + 3;
}

/* Get the next cell on the line. */
/* Return the pointer to the list of fwd cells, NULL if none in list. */
static int *
next_line_cell(
	line *l		/* line structure */
) {
	rspl *s = l->s;
	int bf = 0, f, fdi = s->fdi;
	int **rpp;
	int rgres_1 = s->rev.res - 1;
	double bt = 100.0;	/* Best (smalest +ve) parameter value to move */

	/* See which axis cell crossing we will hit next */
	for (f = 0; f < fdi; f++) {
		double t;
		if (l->de[f] != 0) {
			t = ((l->ci[f] + l->di[f]) * s->rev.gw[f] - l->st[f])/l->de[f];
			DBG(("t for dim %d = %f\n",f,t));
			if (t < bt) {
				bt = t;
				bf = f;		/* Best direction to move */
			}
		}
	}

	/* Move to the next reverse grid coordinate */
	l->ci[bf] += l->di[bf];
	l->t = bt;

	DBGV(("current line cell =", fdi, " %d", l->ci, "")); DBG((",  t = %f\n",l->t));

#ifdef DEBUG
	{
		int ii;
		double tt;
		printf("Current cell = ");
		for (ii = 0; ii < fdi; ii++) {
			tt = l->ci[ii] * s->rev.gw[ii] + s->rev.gl[ii];
			printf(" %f - %f",tt,tt+s->rev.gw[ii]);
		}
		printf("\n");
	}
#endif	/* DEBUG */

	/* Compute fxcell index */
	for (rpp = s->rev.rev, f = 0; f < fdi; f++) {
		if (l->ci[f] < 0 || l->ci[f] > rgres_1) { 	/* If outside valid reverse range */
			DBG(("Outside list on dim %d, 0 <= %d <= %d\n", f, l->ci[f],rgres_1));
			return NULL;
		}
		rpp += l->ci[f] * s->rev.coi[f];	/* Accumulate reverse grid pointer */
	}
	if (*rpp == NULL)
		return NULL;
	return *rpp + 3;
}

/* ------------------------------------- */
/* Clip nearest support. */

/* Weighted distance function macro: */

#define LCHW_SQ(fname, arg2type) 		\
									\
static double fname(rspl *s, double in1[MXDO], arg2type in2[MXDO]) {	\
	int f, fdi = s->fdi;	\
	double tt, rr = 0.0;	\
	\
	/* Fall back */	\
	if (!s->rev.lchweighted || fdi < 3) {	\
		for (f = 0; f < fdi; f++) {	\
			tt = in1[f] - (double)in2[f];	\
			rr += tt * tt;	\
		}	\
		return rr;	\
	}	\
	\
	{	\
		double dxsq = 0.0, dchsq;	\
		double dlsq, dcsq, dhsq;	\
		double dc, c1, c2;	\
		\
		/* Compute delta L squared and delta E squared */	\
		{	\
			double dl, da, db;	\
			dl = in1[0] - (double)in2[0];	\
			da = in1[1] - (double)in2[1];	\
			db = in1[2] - (double)in2[2];	\
		\
			dlsq = dl * dl;		/* dl squared */	\
			dchsq = da * da + db * db;	\
		}	\
		\
		/* Add any extra dims */	\
		for (f = 3; f < fdi; f++) {	\
			tt = in1[f] - (double)in2[f];	\
			dxsq += tt * tt;	\
		}	\
		\
		/* compute delta chromanance squared */	\
		{	\
			/* Compute chromanance for the two colors */	\
			c1 = sqrt(in1[1] * in1[1] + in1[2] * in1[2]);	\
			c2 = sqrt((double)in2[1] * (double)in2[1] + (double)in2[2] * (double)in2[2]);	\
		\
			dc = c1 - c2;	\
			dcsq = dc * dc;	\
		}	\
		\
		/* Compute delta hue squared */	\
		/* (Hue is simply the orthogonal delta to chromanance in the a*b* plane) */	\
		if ((dhsq = dchsq - dcsq) < 0.0)	\
			dhsq = 0.0;	\
		\
		/* Compute weighted error squared */	\
		rr = dxsq + s->rev.lchw_sq[0] * dlsq + s->rev.lchw_sq[1] * dcsq + s->rev.lchw_sq[2] * dhsq;	\
		\
		return rr;	\
	}	\
}

/* Compute weighted LCh output distance squared. */
/* Weighting is to L,C,h, delta's squared - double[], double[] version */
LCHW_SQ(lchw_sq, double)

/* Weighting is to L,C,h, delta's squared - double[], float[] version */
LCHW_SQ(lchw_sq_f, float)

/*	Notes:

	Estimation accuracy is hobbled by 100% at HWEIGHT 1.0 
	compare to pure euclidean estimate, due to the conservative
	maxDlc maxDh of points in group, but this reduces at larger
	HWEIGHT's. The handicap also decreases quickly with tighter
	group size, since C variation is diminished.

	The handicap limits filtering efficiency for large group to group,
	so ideally group size shouldn't be larger than about 10 DE in diameter.

	It's not clear if any better approach is possible.
*/

#define       NN_GCMIN (1e-6)

/* Create a nn group. */
/* If G != NULL, use it as group center rather than computing from members. */
static void nn_grpinit(rspl *s, nn_grp *p, double **pnts, int npnts, double *G) {
	int f, ee, ff, fdi = s->fdi;
	int i;
	double *min[MXRO], *max[MXRO];	/* Pointers to points with min/max values */
	double rad, radsq = -1.0;		/* Span/radius squared */
	int spf;
	double dxsq = 0.0, desq, dchsq, dlcsq;	
	double dlsq, dcsq, dhsq;	
	double dc, c1, c2;	
	double c, minc = 1e200, maxc = -1.0;

	if (G != NULL) {
		for (f = 0; f < fdi; f++)
			p->bcent[f] = G[f];

		if (fdi >= 3) {
			/* Track minimum and maximum member C squared */
			for (i = 0; i < npnts; i++) {
				c = pnts[i][1] * pnts[i][1] + pnts[i][2] * pnts[i][2];
				if (c < minc)
					minc = c;
				if (c > maxc)
					maxc = c;
			}
		}

	} else if (npnts <= 2) {

		/* Compute center as simple average */
		for (f = 0; f < fdi; f++)
			p->bcent[f] = 0.0;
	
		for (i = 0; i < npnts; i++) {
			for (f = 0; f < fdi; f++)
				p->bcent[f] += pnts[i][f];
	
			if (fdi >= 3) {
				/* Track minimum and maximum member C squared */
				c = pnts[i][1] * pnts[i][1] + pnts[i][2] * pnts[i][2];
				if (c < minc)
					minc = c;
				if (c > maxc)
					maxc = c;
			}
		}
		for (f = 0; f < fdi; f++)
			p->bcent[f] *= 1.0/(double)npnts;

	} else {
		/* We establish a center point in un-weighted space, because this is */
		/* what's needed for in-gamut work, and is computationally faster */
		/* and easier than attempting it using weighted space. */
	
		/* Find vertices of cell that have min and max values in output space */
		for (f = 0; f < fdi; f++)
			min[f] = max[f] = NULL;
	
		for (ee = 0; ee < npnts; ee++) {
			double *vp = pnts[ee];
			for (f = 0; f < fdi; f++) {
				if (min[f] == NULL || min[f][f] > vp[f])
					min[f] = vp;
				if (max[f] == NULL || max[f][f] < vp[f])
					max[f] = vp;
			}
		}
	
		/* Find the pair of points with the largest span (diameter) in output space */
		for (ff = 0; ff < fdi; ff++) {
			double ss;
			for (ss = 0.0, f = 0; f < fdi; f++) {
				double tt;
				tt = max[ff][f] - min[ff][f];
				ss += tt * tt;
			}
			if (ss > radsq) {
				radsq = ss;
				spf = ff;		/* Output dimension max was in */
			}
		}
	
		/* Set initial bounding sphere */
		for (f = 0; f < fdi; f++)
			p->bcent[f] = (max[spf][f] + min[spf][f])/2.0;
		radsq /= 4.0;			/* diam^2 -> rad^2 */
		rad = sqrt(radsq);
		
		/* Go though all the points again, expanding sphere if necessary */
		for (ee = 0; ee < npnts; ee++) {
			double ss;
			double *vp = pnts[ee];
	
			/* Compute distance squared of point to bounding shere */
			for (ss = 0.0, f = 0; f < fdi; f++) {
				double tt = vp[f] - p->bcent[f];
				ss += tt * tt;
			}
			if (ss > radsq) {
				double tt;
				/* DBG(("Expanding bounding sphere by %f\n",sqrt(ss) - rad)); */
	
				ss = sqrt(ss) + EPS;			/* Radius to point */
				rad = (rad + ss)/2.0;
				radsq = rad * rad;
				tt = ss - rad;
				for (f = 0; f < fdi; f++)
					p->bcent[f] = (rad * p->bcent[f] + tt * vp[f])/ss;
			} else {
				/* DBG(("Bounding sphere encloses by %f\n",rad - sqrt(ss))); */
			}
		}
		if (fdi >= 3) {
			/* Establish the minimum and maximum member C squared */
			for (ee = 0; ee < npnts; ee++) {
				c = pnts[ee][1] * pnts[ee][1] + pnts[ee][2] * pnts[ee][2];
				if (c < minc)
					minc = c;
				if (c > maxc)
					maxc = c;
			}
		}
	}

	p->brad = p->bradsq = -1.0;
	p->maxDlc = -1.0;
	p->maxDh = p->maxDh_ = -1.0;
	p->sratio = 1.0;
	p->Wsratio = s->rev.lchw_sq[2]; 
	p->bratio = 1.0;
	p->Wbratio = s->rev.lchw_sq[2]; 
	p->Gc = p->Gc_ = NN_GCMIN;

	/* No weighting */
	if (!s->rev.lchweighted || fdi < 3) {

		for (i = 0; i < npnts; i++) {
			desq = 0.0;
			for (f = 0; f < fdi; f++) {
				double tt = p->bcent[f] - pnts[i][f];
				desq += tt * tt;
			}
			/* Track maximum euclidean  distance */
			if (desq > p->bradsq)
				p->bradsq = desq;
		}
		p->brad = sqrt(p->bradsq);	/* Distance rather than squared */

	/* Weighted */
	} else {
		double maxde = -1.0;

		/* Locate member maximum deltaLC and deltaH */
		for (i = 0; i < npnts; i++) {
	
			/* Compute delta L squared and delta E squared */
			{	
				double dl, dasq, dbsq;	
				dl = p->bcent[0] - pnts[i][0];	
				dlsq = dl * dl;		/* dl squared */	
				dasq = p->bcent[1] - pnts[i][1];	
				dasq *= dasq;
				dbsq = p->bcent[2] - pnts[i][2];	
				dbsq *= dbsq;
			
				dchsq = dasq + dbsq;	
				desq = dlsq + dchsq;	
			}	

			/* Add any extra dims */
			for (f = 3; f < fdi; f++) {
				double tt = p->bcent[f] - pnts[i][f];
				dxsq += tt * tt;
			}
			desq += dxsq;

			/* Track maximum euclidean distance too */
			if (desq > p->bradsq)
				p->bradsq = desq;
		
			/* compute delta chromanance squared */	
			{	
				/* Compute chromanance of member to group center */
				c1 = sqrt(p->bcent[1] * p->bcent[1] + p->bcent[2] * p->bcent[2]);	
				c2 = sqrt(pnts[i][1] * pnts[i][1] + pnts[i][2] * pnts[i][2]);	
			
				dc = c1 - c2;	
				dcsq = dc * dc;	
			}	
			
			/* Compute delta hue squared */	
			/* (Hue is simply the orthogonal delta to chromanance in the a*b* plane) */	
			if ((dhsq = dchsq - dcsq) < 0.0)	
				dhsq = 0.0;	
		
			/* Weighted delta extra + luminance + chromanance squared */
			dlcsq = dxsq + s->rev.lchw_sq[0] * dlsq + s->rev.lchw_sq[1] * dcsq;
	
			/* Using maxDlc & maxDh is an absolute worst case, but */
			/* using a more exact approximation to the worst point */
			/* for a given hue correction factor, doesn't seem to help */
			/* for HWEIGHT > 1.5 */ 

			/* Track maximum weighted deltaLC squared */
			if (dlcsq > p->maxDlc)
				p->maxDlc = dlcsq;
	
			/* Track maximum deltaH squared */
			if (dhsq > p->maxDh)
				p->maxDh = dhsq;
		}
		p->brad = sqrt(p->bradsq);	/* Euclidean distance rather than squared */
		p->maxDh_ = sqrt(p->maxDh);
	
		/* Pre-calculate center C squared */
		p->Gc = p->bcent[1] * p->bcent[1] + p->bcent[2] * p->bcent[2];
		if (p->Gc < NN_GCMIN)
			p->Gc = NN_GCMIN;
		p->Gc_ = sqrt(p->Gc);
	
		/* Calculate hue scale down factor for Group center to smallest member C */
		/* (This is used to scale point/center to center distance) */
		if (minc < p->Gc) {
			p->sratio = sqrt(minc/p->Gc);
			if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
				p->Wsratio = (s->rev.lchw_sq[2] - 1.0) * p->sratio + 1.0;
			else
				p->Wsratio = s->rev.lchw_sq[2] * p->sratio;
		}
	
		/* Calculate hue scale up factor for Group center to largest member C */
		/* (This is used to scale point/center to center distance) */
		/* (For group target, multiply group ->bratio values ??) */
		if (maxc > p->Gc) {
			p->bratio = sqrt(maxc/p->Gc);
			if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
				p->Wbratio = (s->rev.lchw_sq[2] - 1.0) * p->bratio + 1.0;
			else
				p->Wbratio = s->rev.lchw_sq[2] * p->bratio;
		}
	}
} 

/* Return nz if point is within euclidean bounding sphere. */
/* Also return distance squared in *dist if non-NULL */
static int nn_insphere(rspl *s, double *dist, nn_grp *p, double *src) {
	int f, fdi = s->fdi;
	double desq = 0.0;

	for (f = 0; f < fdi; f++) {
		double tt = p->bcent[f] - src[f];	
		desq += tt * tt;
	}

	if (dist != NULL)
		*dist = desq;

	return desq <= p->bradsq;
}

/* Estimate possible smallest weighted distance of point to group. */
/* If lgst != NULL, also return the estimated largest possible distance. */
static double nn_pntgrp_est(rspl *s, double *lgst, nn_grp *p, double *src) {
	int f, fdi = s->fdi;
	double dxsq = 0.0, desq, dchsq;
	double dlsq, dcsq, dhsq;
	double dc, c1, c2;	
	double Tc;			/* Point chromanance squared */
	double sGrr;		/* Min Point to group center diatance squared */
	double bGrr;		/* Max Point to group center diatance squared */
	double rr;			/* Largest member distance squared */
	double sdist;		/* Min. estimated distance squared */
	double bdist;		/* Max.. estimated distance squared */
	double aratio = 1.0;

	/* If not using LCh weighted distances */
	if (!s->rev.lchweighted || fdi < 3) {

		desq = 0.0;
		for (f = 0; f < fdi; f++) {
			double tt = p->bcent[f] - src[f];	
			desq += tt * tt;
		}

		/* Return largest possible distance */
		if (lgst != NULL) {
			bdist = sqrt(desq) + p->brad + EPS;
			*lgst = bdist;
		}

		/* Return min possible distance */
		sdist = sqrt(desq) - p->brad - EPS;
		if (sdist < 0.0)
			sdist = 0.0;
		return sdist;

	/* We're using LCh weighting, so we need to do some adjustments */
	} else {
		/* Compute components of weighted distance of point */
		/* to group center. */
		{	
			double dl, dasq, dbsq;	
			dl = p->bcent[0] - src[0];	
			dlsq = dl * dl;				/* dl squared */	
			dasq = p->bcent[1] - src[1];	
			dasq *= dasq;
			dbsq = p->bcent[2] - src[2];	
			dbsq *= dbsq;

			dchsq = dasq + dbsq;
		}	

		/* Compute any extra dims */
		for (f = 3; f < fdi; f++) {
			double tt = p->bcent[f] - src[f];
			dxsq += tt * tt;
		}

		/* compute delta chromanance squared of target to group center */	
		{	
			/* Compute delta chromanance between target point and group center */
			c1 = p->Gc_;
			c2 = Tc = src[1] * src[1] + src[2] * src[2];	
			c2 = sqrt(c2);
			dc = c1 - c2;	
			dcsq = dc * dc;	
		}	
		
		/* Compute delta hue squared of target point to group center */	
		/* (Hue is simply the orthogonal delta to chromanance in the a*b* plane) */	
		if ((dhsq = dchsq - dcsq) < 0.0)	
			dhsq = 0.0;	
	
		/* Weighted values of L and C delta's */
		dlsq *= s->rev.lchw_sq[0];
		dcsq *= s->rev.lchw_sq[1];

		/* Most distant member hue delta adjustment factor */
		aratio = s->rev.lchw_sq[2];
		if (Tc > p->Gc) {
			aratio = sqrt(Tc/p->Gc);
			if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
				aratio = (s->rev.lchw_sq[2] - 1.0) * aratio + 1.0;
			else
				aratio = s->rev.lchw_sq[2] * aratio;
		}

		/* Adjusted maximum member distance to group center */
		rr = sqrt(p->maxDlc + aratio * p->maxDh);

		/* Return max. possible distance squared */
		if (lgst != NULL) {

			/* Adjusted weighted max. distance squared of target to group center */
			bGrr = dxsq + dlsq + dcsq + dhsq * p->Wbratio;

			/* max. possible distance of target to most distant member */
			bdist = sqrt(bGrr) + rr + EPS;
			*lgst = bdist;
		}

		/* Adjusted weighted min. distance squared of target to group center */
		sGrr = dxsq + dlsq + dcsq + dhsq * p->Wsratio;

		/* min. possible distance of target to most distant member */
		sdist = sqrt(sGrr) - rr - EPS;
		if (sdist < 0.0)
			sdist = 0.0;

		return sdist;
	}
}

/* Estimate possible smallest weighted distance of group to group. */
/* If lgst != NULL, also return the estimated largest possible distance. */
static double nn_grpgrp_est(rspl *s, double *lgst, nn_grp *p1, nn_grp *p2) {
	int f, fdi = s->fdi;
	double dxsq = 0.0, desq, dchsq;
	double dlsq, dcsq, dhsq;
	double dc, c1, c2;	
	double sGrr;		/* Min Point to group center diatance squared */
	double bGrr;		/* Max Point to group center diatance squared */
	double rr1, rr2;		/* Largest member distance squared */
	double sdist;			/* Min. estimated distance squared */
	double bdist;			/* Max.. estimated distance squared */
	double aratio1 = 1.0, aratio2 = 1.0;

	/* If not using LCh weighted distances */
	if (!s->rev.lchweighted || fdi < 3) {

		desq = 0.0;
		for (f = 0; f < fdi; f++) {
			double tt = p1->bcent[f] - p2->bcent[f];	
			desq += tt * tt;
		}

		/* Return largest possible distance */
		if (lgst != NULL) {
			bdist = sqrt(desq) + p1->brad + p2->brad + EPS;
			*lgst = bdist;
		}

		/* Return min possible distance */
		sdist = sqrt(desq) - p1->brad - p2->brad - EPS;
		if (sdist < 0.0)
			sdist = 0.0;
		return sdist;

	/* We're using LCh weighting, so we need to do some adjustments */
	} else {
		double Wratio;

		/* Compute components of weighted distance of group center */
		/* to group center. */
		{	
			double dl, dasq, dbsq;	
			dl = p1->bcent[0] - p2->bcent[0];	
			dlsq = dl * dl;				/* dl squared */	
			dasq = p1->bcent[1] - p2->bcent[1];	
			dasq *= dasq;
			dbsq = p1->bcent[2] - p2->bcent[2];	
			dbsq *= dbsq;
			
			dchsq = dasq + dbsq;
		}	

		/* Compute any extra dims */
		for (f = 3; f < fdi; f++) {
			double tt = p1->bcent[f] - p2->bcent[f];
			dxsq += tt * tt;
		}

		/* compute delta chromanance squared of point to group center */	
		{	
			/* Compute delta chromanance group centers */
			c1 = p1->Gc_;
			c2 = p2->Gc_;
			dc = c1 - c2;	
			dcsq = dc * dc;	
		}	
		
		/* Compute delta hue squared of group centers */	
		/* (Hue is simply the orthogonal delta to chromanance in the a*b* plane) */	
		if ((dhsq = dchsq -  dcsq) < 0.0)	
			dhsq = 0.0;	
	
		/* Weighted values of L and C delta's */
		dlsq *= s->rev.lchw_sq[0];
		dcsq *= s->rev.lchw_sq[1];

		/* Most distant member hue delta adjustment factor */
		aratio1 = aratio2 = s->rev.lchw_sq[2];

		if ((p1->Gc_ + p1->maxDh) > p2->Gc_) {
			aratio2 = (p1->Gc_ + p1->maxDh)/p2->Gc_;
			if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
				aratio2 = (s->rev.lchw_sq[2] - 1.0) * aratio2 + 1.0;
			else
				aratio2 = s->rev.lchw_sq[2] * aratio2;

		}
		if ((p2->Gc_ + p2->maxDh) > p1->Gc_) {
			aratio1 = (p2->Gc_ + p2->maxDh)/p1->Gc_;
			if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
				aratio1 = (s->rev.lchw_sq[2] - 1.0) * aratio1 + 1.0;
			else
				aratio1 = s->rev.lchw_sq[2] * aratio1;
		}

		/* Adjusted maximum member distance to group center */
		rr1 = sqrt(p1->maxDlc + aratio1 * p1->maxDh);
		rr2 = sqrt(p2->maxDlc + aratio2 * p2->maxDh);

		/* Returne max. possible distance squared */
		if (lgst != NULL) {
			if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
				Wratio = (s->rev.lchw_sq[2] - 1.0) * p1->bratio * p2->bratio + 1.0;
			else
				Wratio = s->rev.lchw_sq[2] * p1->bratio * p2->bratio;

			/* Adjusted weighted max. distance squared of group centers */
			bGrr = dxsq + dlsq + dcsq + dhsq * Wratio;

			/* max. possible distance of target to most distant member */
			bdist = sqrt(bGrr) + rr1 + rr2 + EPS;
			*lgst = bdist;
		}

		if (s->rev.lchw_sq[2] > 1.0)			/* Slightly improves filter ratio */
			Wratio = (s->rev.lchw_sq[2] - 1.0) * p1->sratio * p2->sratio + 1.0;
		else
			Wratio = s->rev.lchw_sq[2] * p1->sratio * p2->sratio;

		/* Adjusted weighted min. distance squared of group centers */
		sGrr = dxsq + dlsq + dcsq + dhsq * Wratio;

		/* min. possible distance of target to most distant member */
		sdist = sqrt(sGrr) - rr1 - rr2 - EPS;
		if (sdist < 0.0)
			sdist = 0.0;

		return sdist;
	}
}

/* ------------------------------------------------------------ */
static void fill_nncell(rspl *s, int *co, int ix);

/* Return the pointer to the list of nearest fwd cells given */
/* the target output values. The pointer will be to the first */
/* index in the list (ie. list address + 3) */
/* Return NULL if none in list (out of gamut). */
static int *
calc_fwd_nn_cell_list(
	rspl *s,		/* this */
	double *v		/* Output values */
) {
	int f, fdi = s->fdi, ix;
	int **rpp;
	int rgres_1 = s->rev.res - 1;
	int mi[MXDO];

	if (s->rev.rev_valid == 0)
		init_revaccell(s);

	for (ix = 0, f = 0; f < fdi; f++) {
		double t = (v[f] - s->rev.gl[f])/s->rev.gw[f];
		mi[f] = (int)floor(t);			/* Grid coordinate */
		if (mi[f] < 0)	 				/* Clip to reverse range, so we always return a result  */
			mi[f] = 0;
		else if (mi[f] > rgres_1)
			mi[f] = rgres_1;
		ix += mi[f] * s->rev.coi[f];	/* Accumulate reverse grid index */
	}
	s->rev.sb->rix = ix;				/* Set diagnostic value */

	rpp = s->rev.nnrev + ix;
	if (*rpp == NULL) {
		if (s->rev.fastsetup)
			fill_nncell(s, mi, ix);		/* Fill on-demand */
		if (*rpp == NULL)
			rpp = s->rev.rev + ix;		/* fall back to in-gamut lookup */ 
	}
	if (*rpp == NULL) {
#ifdef CHECK_NNLU
		printf("Got NULL list for nearest search, targ %s,\n coord %s, rix %d\n", debPdv(fdi,v),debPiv(fdi,mi),ix);
		if (ix < 0 || ix >= s->rev.no)
			printf("Index is outside range 0 .. %d\n",s->rev.no-1);
		else {
			if (s->rev.nnrev[ix] == NULL)
				printf(" nnrev = NULL\n");
			else
				printf(" nnrev length = %d\n",s->rev.nnrev[ix][1]-3);
			if (s->rev.rev[ix] == NULL)
				printf(" rev = NULL\n");
			else
				printf(" rev = length = %d\n",s->rev.rev[ix][1]-3);
		}
#endif
		return NULL;
	}
	return (*rpp) + 3;
}

/* =================================================== */
/* The cell and simplex solver top level routines */

static int add_lu_svd(simplex *x);
static int add_locus(schbase *b, simplex *x);
static int add_auxil_lu_svd(schbase *b, simplex *x);
static int within_simplex(simplex *x, double *p);
static int within_simplex_limit(simplex *x, double *p);
static void simplex_to_abs(simplex *x, double *in, double *out);

static int auxil_solve(schbase *b, simplex *x, double *xp);

/* ---------------------- */
/* Exact search functions */
/* Return non-zero if cell is acceptable */
static int exact_setsort(schbase *b, fxcell *c) {
	rspl *s = b->s;
	int f, fdi = s->fdi;
	double ss;

	DBG(("Reverse exact search, evaluate and set sort key on cell\n"));

	/* Check that the target lies within the cell bounding sphere */
	for (ss = 0.0, f = 0; f < fdi; f++) {
		double tt = c->g.bcent[f] - b->v[f];
		ss += tt * tt;
	}
	if (ss > c->g.bradsq) {
		DBG(("Cell rejected - %s outside sphere c %s rad %f\n",debPdv(fdi,b->v),debPdv(fdi,c->g.bcent),sqrt(c->g.bradsq)));
		return 0;
	}

	if (s->limiten != 0 && c->limmin > s->limitv) {
		DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv));
		return 0;
	}

	/* Sort can't be used, because we return all solutions */
	c->sort = 0.0;

	DBG(("Cell is accepted\n"));

	return 1;
}

/* Compute a solution for a given sub-simplex (if there is one) */
/* Return 1 if search should be aborted */
static int exact_compute(schbase *b, simplex *x) {
	rspl *s     = b->s;
	int e, di = s->di, sdi  = x->sdi;
	int f, fdi  = s->fdi;
	int i;
	datai xp;	/* solution in simplex relative coord order */
	datai p;	/* absolute solution */
	int wsrv;	/* Within simplex return value */

	DBG(("\nExact: computing possible solution\n"));

#ifdef DEBUG
	/* Sanity check */
	if (sdi != fdi || sdi != di || x->efdi != fdi) {
		printf("di = %d, fdi = %d\n",di,fdi);
		printf("sdi = %d, efdi = %d\n",sdi,x->efdi);
		error("rspl exact reverse interp called with sdi != fdi, sdi != di, efdi != fdi");
		/* !!! could switch to SVD solution if di != fdi ?? !!! */
	}
#endif

	/* This may not be worth it here since it may not filter out */
	/* many more simplexes than the cube check did. */
	/* This is due to full dimension simplexes all sharing the main */
	/* diagonal axis. */

	/* Check that the target lies within the simplex bounding cube */
	for (f = 0; f < fdi; f++) {
		if (b->v[f] < x->min[f] || b->v[f] > x->max[f]) {
			DBG(("Simplex is rejected - bounding cube\n"));
			return 0;
		}
	}

	/* Create the LU decomp needed to exactly solve */
	if (add_lu_svd(x)) {
		DBG(("LU decomp was singular, skip simplex\n"));
		return 0;
	}

	/* Init the RHS B[] vector (note di == fdi) */
	for (f = 0; f < fdi; f++) {
		xp[f] = b->v[f] - x->v[di][f];
	}

	/* Compute the solution (in simplex space) */
	lu_backsub(x->d_u, sdi, (int *)x->d_w, xp);

	/* Check that the solution is within the simplex & meets ink limit */
	if ((wsrv = within_simplex(x, xp)) == 0) {
		DBG(("Solution rejected because not in simplex\n"));
		return 0;
	}

	/* Convert solution from simplex relative to absolute space */
	simplex_to_abs(x, p, xp);

	/* Check if a very similiar input solution has been found before */
	for (i = 0; i < b->nsoln; i++) {
		double tt;
		for (e = 0; e < di; e++) {
			tt = b->cpp[i].p[e] - p[e];
			if (fabs(tt) > (2 * EPS))
				break;	/* Mismatch */
		}
		if (e >= di)	/* Found good match */
			break;
	}

	/* Probably alias caused by solution lying close to a simplex boundary */
	if (i < b->nsoln) {
		DBG(("Another solution has been found before - index %d\n",i));
		return 0;		/* Skip this, since betters been found before */
	}

	/* Check we haven't overflowed space */
	if (i >= b->mxsoln) {
		DBG(("Run out of space for new solution\n"));
		return 1;		/* Abort */
	}

	DBG(("######## Accepting new solution\n"));

	/* Put solution in place */
	for (e = 0; e < di; e++)
		b->cpp[i].p[e] = p[e];
	for (f = 0; f < fdi; f++)
		b->cpp[i].v[f] = b->v[f];	/* Assumed to be an exact solution */
	if (i == b->nsoln)
		b->nsoln++;
	if (wsrv == 2)					/* Is above (disabled) ink limit */
		b->iclip = 1;
	return 0;
}

/* -------------------------- */
/* Auxiliary search functions */
static int auxil_setsort(schbase *b, fxcell *c) {
	rspl *s = b->s;
	int f, fdi  = b->s->fdi;
	int ee, ixc = b->ixc;
	double ss, sort, nabove;

	DBG(("Reverse auxiliary search, evaluate and set sort key on cell\n"));

	if (b->s->di <= fdi) {	/* Assert */
		error("rspl auxiliary reverse interp called with di <= fdi (%d %d)", b->s->di, fdi);
	}

	/* Check that the target lies within the cell bounding sphere */
	for (ss = 0.0, f = 0; f < fdi; f++) {
		double tt = c->g.bcent[f] - b->v[f];
		ss += tt * tt;
	}
	if (ss > c->g.bradsq) {
		DBG(("Cell rejected - %s outside sphere c %s rad %f\n",debPdv(fdi,b->v),debPdv(fdi,c->g.bcent),sqrt(c->g.bradsq)));
		return 0;
	}

	if (s->limiten != 0 && c->limmin > s->limitv) {
		DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv));
		return 0;
	}

	/* Check if this cell could possible improve b->idist */
	/* and compute sort key as the distance to auxilliary target */
	/* (We may have a non INF_DIST idist before commencing the */
	/* search if we already know that the auxiliary target is */
	/* within gamut - the usual usage case!) */
	for (sort = 0.0, nabove = ee = 0; ee < b->naux; ee++) {
		int ei = b->auxi[ee];
		double tt = (c->p[0][ei] + c->p[ixc][ei]) - b->av[ei];
		sort += tt * tt;
		if (c->p[ixc][ei] >= (b->av[ei] - EPS))		/* Could be above */
			nabove++;
	}

	if (b->flags & RSPL_MAXAUX && nabove < b->iabove) {
		DBG(("Doesn't contain solution that has as many aux above auxiliary goal\n"));
		return 0;
	}
	if (!(b->flags & RSPL_MAXAUX) || nabove == b->iabove) {
		for (ee = 0; ee < b->naux; ee++) {
			int ei = b->auxi[ee];
			if (c->p[0][ei]   >= (b->av[ei] + b->idist)
			 || c->p[ixc][ei] <= (b->av[ei] - b->idist)) {
				DBG(("Doesn't contain solution that will be closer to auxiliary goal\n"));
				return 0;
			}
		}
	}
	c->sort = sort + 0.01 * ss;

	if (c->ix == b->pauxcell)
		c->sort = -1.0;			/* Put previous calls solution cell at top of sort list */

	DBG(("Cell is accepted\n"));
	return 1;
}

/* Re-check whether it's worth searching cell */
static int auxil_check(schbase *b, fxcell *c) {
	int ee, ixc = b->ixc, nabove;

	DBG(("Reverse auxiliary search, re-check cell\n"));

	/* Check if this cell could possible improve b->idist */
	/* and compute sort key as the distance to auxilliary target */

	for (nabove = ee = 0; ee < b->naux; ee++) {
		int ei = b->auxi[ee];
		if (c->p[ixc][ei] >= (b->av[ei] - EPS))		/* Could be above */
			nabove++;
	}

	if (b->flags & RSPL_MAXAUX && nabove < b->iabove) {
		DBG(("Doesn't contain solution that has as many aux above auxiliary goal\n"));
		return 0;
	}
	if (!(b->flags & RSPL_MAXAUX) || nabove == b->iabove) {
		for (ee = 0; ee < b->naux; ee++) {
			int ei = b->auxi[ee];
			if (c->p[0][ei]   >= (b->av[ei] + b->idist)
			 || c->p[ixc][ei] <= (b->av[ei] - b->idist)) {
				DBG(("Doesn't contain solution that will be closer to auxiliary goal\n"));
				return 0;
			}
		}
	}
	DBG(("Cell is still ok\n"));
	return 1;
}

/* Compute a solution for a given simplex (if there is one) */
/* Return 1 if search should be aborted */
static int auxil_compute(schbase *b, simplex *x) {
	rspl *s     = b->s;
	int e, di   = s->di;
	int f, fdi  = s->fdi;
	datai xp;		/* solution in simplex relative coord order */
	datai p;		/* absolute solution */
	double idist;	/* Auxiliary input distance */
	int wsrv;		/* Within simplex return value */
	int nabove;		/* Number above aux target */

	DBG(("\nAuxil: computing possible solution\n"));

#ifdef DEBUG
	{
	unsigned int sum = 0;
	for (f = 0; f <= x->sdi; f++)
		sum += x->vix[f];
	printf("Simplex of cell ix %d, sum 0x%x, sdi = %d, efdi = %d\n",x->ix, sum, x->sdi, x->efdi);
	printf("Target val %s\n",debPdv(fdi, b->v));
	for (f = 0; f <= x->sdi; f++) {
		int ix = x->vix[f], i;
		float *fcb = s->g.a + ix * s->g.pss;	/* Pointer to base float of fwd cell */
		printf("Simplex vtx %d [cell ix %d] val %s\n",f,ix,debPfv(fdi, fcb));
	}
	}
#endif

	/* Check that the target lies within the simplex bounding cube */
	for (f = 0; f < fdi; f++) {
		if (b->v[f] < x->min[f] || b->v[f] > x->max[f]) {
			DBG(("Simplex is rejected - bounding cube\n"));
			return 0;
		}
	}

	/* Check if this cell could possible improve b->idist */
	for (nabove = e = 0; e < b->naux; e++) {
		int ei = b->auxi[e];					/* pmin/max[] is indexed in input space */
		if (x->pmax[ei] >= (b->av[ei] - EPS))	/* Could be above */
			nabove++;
	}
	if ((b->flags & RSPL_MAXAUX) && nabove < b->iabove) {
		DBG(("Simplex doesn't contain solution that has as many aux above auxiliary goal\n"));
		return 0;
	}

	if (!(b->flags & RSPL_MAXAUX) || nabove == b->iabove) {
		for (nabove = e = 0; e < b->naux; e++) {
			int ei = b->auxi[e];					/* pmin/max[] is indexed in input space */
			if (x->pmin[ei] >= (b->av[ei] + b->idist)
			 || x->pmax[ei] <= (b->av[ei] - b->idist)) {
				DBG(("Simplex doesn't contain solution that will be closer to auxiliary goal\n"));
				return 0;
			}
		}
	}

//printf("~~ About to create svd decomp\n");
	/* Create the SVD or LU decomp needed to compute solution or locus */
	if (add_lu_svd(x)) {
		DBG(("SVD decomp failed, skip simplex\n"));
		return 0;
	}

//printf("~~ About to solve locus for aux target\n");
	/* Now solve for locus parameter that minimises */
	/* distance to auxliary target. */
	if ((wsrv = auxil_solve(b, x, xp)) == 0) {
		DBG(("Target auxiliary along locus is outside simplex,\n"));
		DBG(("or computation failed, skip simplex\n"));
		return 0;
	}

//printf("~~ About to convert solution to absolute space\n");
	/* Convert solution from simplex relative to absolute space */
	simplex_to_abs(x, p, xp);

	DBG(("Got solution at %s\n", debPdv(di,p)));

//printf("~~ soln = %f %f %f %f\n",p[0],p[1],p[2],p[3]);
//printf("~~ About to compute auxil distance\n");
	/* Compute distance to auxiliary target */
	for (idist = 0.0, nabove = e = 0; e < b->naux; e++) {
		int ei = b->auxi[e];
		double tt = b->av[ei] - p[ei];
		idist += tt * tt;
		if (p[ei] >= (b->av[ei] - EPS))
			nabove++;
	}
	idist = sqrt(idist);
//printf("~1 idist %f, nabove %d\n",idist, nabove);
//printf("~1 best idist %f, best iabove %d\n",b->idist, b->iabove);

	/* We want the smallest error from auxiliary target */
	if (b->flags & RSPL_MAXAUX) {
		if (nabove < b->iabove || (nabove == b->iabove && idist >= b->idist)) {
			DBG(("nsoln %d, nabove %d, iabove %d, idist = %f, better solution has been found before\n",b->nsoln, nabove, b->iabove, idist));
			return 0;
		}
	} else {
		if (idist >= b->idist) {	/* Equal or worse auxiliary solution */
			DBG(("nsoln %d, idist = %f, better solution has been found before\n",b->nsoln,idist));
			return 0;
		}
	}

	/* Solution is accepted */
	DBG(("######## Accepting new solution with nabove %d <= iabove %d and idist %f <= %f\n",nabove,b->iabove,idist,b->idist));
	for (e = 0; e < di; e++)
		b->cpp[0].p[e] = p[e];
	for (f = 0; f < fdi; f++)
		b->cpp[0].v[f] = b->v[f];	/* Assumed to be an exact solution */
	b->idist = idist;
	b->iabove = nabove;
	b->nsoln = 1;
	b->pauxcell = x->ix;
	if (wsrv == 2)					/* Is above (disabled) ink limit */
		b->iclip = 1;

	return 0;
}

/* ------------------------------------ */
/* Locus range search functions */

static int locus_setsort(schbase *b, fxcell *c) {
	rspl *s = b->s;
	int f, fdi  = s->fdi;
	int lxi = b->lxi;	/* Auxiliary we are finding min/max of */
	int ixc = b->ixc;
	double sort, ss;

	DBG(("Reverse locus evaluate and set sort key on cell\n"));

#ifdef DEBUG
	if (b->s->di <= fdi) {	/* Assert ~1 */
		error("rspl auxiliary locus interp called with di <= fdi");
	}
#endif /* DEBUG */

	/* Check that the target lies within the cell bounding sphere */
	for (ss = 0.0, f = 0; f < fdi; f++) {
		double tt = c->g.bcent[f] - b->v[f];
		ss += tt * tt;
	}
	if (ss > c->g.bradsq) {
		DBG(("Cell rejected - %s outside sphere c %s rad %f\n",debPdv(fdi,b->v),debPdv(fdi,c->g.bcent),sqrt(c->g.bradsq)));
		return 0;
	}

	if (s->limiten != 0 && c->limmin > s->limitv) {
		DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv));
		return 0;
	}

	/* Check if this cell could possible improve the locus min/max */
	if (b->asegs == 0) {	/* If we aren't find all segments of the locus */
		if (c->p[0][lxi] >= b->min && c->p[ixc][lxi] <= b->max ) {
			DBG(("Doesn't contain solution that will expand the locus\n"));
			return 0;
		}
	}

	/* Compute sort index from average of auxiliary values */
	sort = (c->p[0][b->lxi] + c->p[ixc][b->lxi]);
	
	c->sort = sort + 0.01 * ss;

	DBG(("Cell is accepted\n"));
	return 1;
}

/* Re-check whether it's worth searching simplexes */
static int locus_check(schbase *b, fxcell *c) {
	int lxi = b->lxi;	/* Auxiliary we are finding min/max of */
	int ixc = b->ixc;

	DBG(("Reverse locus re-check\n"));

	/* Check if this cell could possible improve the locus min/max */
	if (b->asegs == 0) {	/* If we aren't find all segments of the locus */
		if (c->p[0][lxi] >= b->min && c->p[ixc][lxi] <= b->max ) {
			DBG(("Doesn't contain solution that will expand the locus\n"));
			return 0;
		}
	}

	DBG(("Cell is still ok\n"));
	return 1;
}

static int auxil_locus(schbase *b, simplex *x);

/* We expect to be given a sub-simplex with no DOF, to give an exact solution */
static int locus_compute(schbase *b, simplex *x) {
	rspl *s  = b->s;
	int f, fdi  = s->fdi;
	int lxi  = b->lxi;	/* Auxiliary we are finding min/max of */

	DBG(("\nLocus: computing possible solution\n"));

#ifdef DEBUG
	{
	unsigned int sum = 0;
	for (f = 0; f <= x->sdi; f++)
		sum += x->vix[f];
	printf("Simplex of cell ix %d, sum 0x%x, sdi = %d, efdi = %d\n",x->ix, sum, x->sdi, x->efdi);
	printf("Target val %s\n",debPdv(fdi, b->v));
	for (f = 0; f <= x->sdi; f++) {
		int ix = x->vix[f], i;
		float *fcb = s->g.a + ix * s->g.pss;	/* Pointer to base float of fwd cell */
		double v[MXDO];
		printf("Simplex vtx %d [cell ix %d] val %s\n",f,ix,debPfv(fdi, fcb));
	}
	}
#endif

	/* Check that the target lies within the simplex bounding cube */
	for (f = 0; f < fdi; f++) {
		if (b->v[f] < x->min[f] || b->v[f] > x->max[f]) {
			DBG(("Simplex is rejected - bounding cube\n"));
			return 0;
		}
	}

	/* Check if simplex could possible improve the locus min/max */
	if (b->asegs == 0) {	/* If we aren't find all segments of the locus */
		if (x->pmin[lxi] >= b->min && x->pmax[lxi] <= b->max ) {
			DBG(("Simplex doesn't contain solution that will expand the locus\n"));
			return 0;
		}
	}

//printf("~~ About to create svd decomp\n");
	/* Create the SVD decomp needed to compute solution extreme points */
	if (add_lu_svd(x)) {
		DBG(("SVD decomp failed, skip simplex\n"));
		return 0;
	}

//printf("~~ About to solve locus for aux extremes\n");
	/* Now solve for locus parameter that are at the extremes */
	/* of the axiliary we are interested in. */
	if (!auxil_locus(b, x)) {
		DBG(("Target auxiliary is outside simplex,\n"));
		DBG(("or computation failed, skip simplex\n"));
		return 0;
	}

	return 0;
}

/* ------------------- */
/* Vector clipping search functions */
static int clipv_setsort(schbase *b, fxcell *c) {
	rspl *s = b->s;
	int f, fdi  = s->fdi;
	double ss, dp;

	DBG(("Reverse clipping search evaluate cell\n"));

//printf("~~sphere center = %f %f %f, radius %f\n",c->bcent[0],c->bcent[1],c->bcent[2],sqrt(c->bradsq));
	/* Check if the clipping line intersects the bounding sphere */
	/* First compute dot product cdir . (bcent - v) */
	/* == distance to center of sphere in direction of clip vector */
	for (dp = 0.0, f = 0; f < fdi; f++) {
		dp += b->ncdir[f] * (c->g.bcent[f] - b->v[f]);
	}

	if (s->limiten != 0 && c->limmin > s->limitv) {
		DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv));
		return 0;
	}

//printf("~~ dot product = %f\n",dp);
	/* Now compute closest distance to sphere center */
	for (ss = 0.0, f = 0; f < fdi; f++) {
		double tt = b->v[f] + dp * b->ncdir[f] - c->g.bcent[f];
		ss += tt * tt;
	}

//printf("~~ distance to sphere center = %f\n",sqrt(ss));
	if (ss > c->g.bradsq) {
		DBG(("Cell is rejected - wrong direction or bounding sphere\n"));
		return 0;
	}
	c->sort = dp;		/* May be -ve if beyond clip target point ? */

	DBG(("Cell is accepted\n"));
	return 1;
}

/* Clipping check functions */
/* Note that we don't bother with this check in setsort(), */
/* because we assume that nothing will set a small cdist */
/* before the search commences (unlike auxil). */
/* Note that line search loop exits on finding any solution. */
static int clipv_check(schbase *b, fxcell *c) {

	DBG(("Reverse clipping re-check\n"));

	if (b->cdist < INF_DIST) {	/* If some clip solution has been found */
		int f, fdi = b->s->fdi;
		double dist;
		/* Compute a conservative "best possible solution clip distance" */
		for (dist = 0.0, f = 0; f < fdi ; f++) {
			double tt = (c->g.bcent[f] - b->v[f]);
			dist += tt * tt;
		}
		dist = sqrt(dist); /* Target distance to bounding */

		if (dist >= (c->g.brad + b->cdist)) {	/* Equal or worse clip solution */
			DBG(("Cell best possible solution worse than current\n"));
			return 0;
		}
	}

	DBG(("Cell is still ok\n"));
	return 1;
}

static int vnearest_clip_solve(schbase *b, simplex *x, double *xp, double *xv, double *err);

/* Compute a clip solution */
static int clipv_compute(schbase *b, simplex *x) {
	rspl   *s  = b->s;
	int f, fdi = s->fdi;
	datai p;				/* Input space solution */
	datao v;				/* Output space solution */
	double err;				/* output error of solution */
	int wsrv;	/* Within simplex return value */

	DBG(("Clips: computing possible solution\n"));

	/* Compute a solution value */
	if ((wsrv = vnearest_clip_solve(b, x, p, v, &err)) == 0) {
		DBG(("Doesn't contain a solution\n"));
		return 0;
	}

	/* We want the smallest clip error */
	/* (Should we reject points in -ve vector direction ??) */
	if (err >= b->cdist) {	/* Equal or worse clip solution */
		DBG(("better solution has been found before\n"));
		return 0;
	}

	simplex_to_abs(x, b->cpp[0].p, p);	/* Convert to abs. space & copy */

	DBG(("######## Accepting new clipv solution with error %f\n",err));
#ifdef DEBUG
	if (s->limiten != 0) {
		DBG(("######## Ink value = %f, limit %f\n",get_limitv(b, x->ix, NULL, b->cpp[0].p), s->limitv));
	}
#endif

	/* Put solution in place */
	for (f = 0; f < fdi; f++)
		b->cpp[0].v[f] = v[f];
	b->cdist = err;
	b->nsoln = 1;
	if (wsrv == 2)					/* Is above (disabled) ink limit */
		b->iclip = 1;

	return 0;
}

/* ------------------- */
/* Nearest clipping search functions. */
/* We use weighted distances if lchweighted. */
static int clipn_setsort(schbase *b, fxcell *c) {
	rspl *s = b->s;
	int f, fdi  = s->fdi;
	double ss;

	DBG(("Reverse nearest clipping search evaluate fwd cell ix %d\n",c->ix));
//if (b->rix == 7135) printf("Reverse nearest clipping search evaluate fwd cell ix %d\n",c->ix);

	/* Compute an estimated weighted clip distance from target point to this fxcell */
	ss = nn_pntgrp_est(s, NULL, &c->g, b->v);

	/* Check that the cell could possibly improve the solution */
	if (b->cdist < INF_DIST) {	/* If some clip solution has been found */
		if (ss >= b->cdist) {	/* Equal or worse clip solution */
			DBG(("Cell best possible solution worse than current\n"));

//if (b->rix == 7135) {
//	printf("Cell best possible solution worse than current\n");
//	printf("current dist %f, best to fwd %f\n",b->cdist,ss); 
//}
			return 0;
		}
	}

	if (s->limiten != 0 && c->limmin > s->limitv) {
		DBG(("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv));
//if (b->rix == 7135) printf("Cell is rejected - ink limit, min = %f, limit = %f\n",c->limmin,s->limitv);
		return 0;
	}

	c->sort = ss;

	DBG(("Cell is accepted (%f < %f)\n",ss,b->cdist));
//if (b->rix == 7135) printf("Cell is accepted (%f < %f)\n",ss,b->cdist);
	return 1;
}

/* Clipping check functions */
static int clipn_check(schbase *b, fxcell *c) {

	DBG(("Reverse nearest clipping re-check fwd cell ix %d\n",c->ix));
//if (b->rix == 7135) printf("Reverse nearest clipping re-check fwd cell ix %d\n",c->ix);

	if (b->cdist < INF_DIST) {	/* If some clip solution has been found */
		/* re-use sort value, best possible distance to solution */
		if (c->sort >= b->cdist) {	/* Equal or worse clip solution */
			DBG(("Cell best possible solution now worse than current\n"));
//if (b->rix == 7135) {
//	printf("Cell best possible solution now worse than current\n");
//	printf("current dist %f, best to fwd %f\n",b->cdist,c->sort); 
//}
			return 0;
		}
	}

	DBG(("Cell is still ok\n"));
//if (b->rix == 7135) printf("Cell is still ok\n");
	return 1;
}

static int lchw_nnearest_clip_solve(schbase *b, simplex *x, double *xp, double *xv, double *err);
static int nnearest_clip_solve(schbase *b, simplex *x, double *xp, double *xv, double *err);

/* Compute a clip solution */
static int clipn_compute(schbase *b, simplex *x) {
	rspl   *s  = b->s;
	int f, fdi = s->fdi;
	datai p;			/* Simplex input space solution */
	datao v;			/* Output space solution */
	double err;			/* output error of solution */
	int wsrv;			/* Within simplex return value */

	DBG(("Clipn: computing possible solution cell %d, simplex %d, sdi = %d, efdi = %d\n",x->ix,x->si,x->sdi,x->efdi));
//if (b->rix == 7135) printf("Clipn: computing possible solution cell %d, simplex %d, sdi = %d, efdi = %d\n",x->ix,x->si,x->sdi,x->efdi);

	/* Compute a solution value */
	if (s->rev.lchweighted) {
		if ((wsrv = lchw_nnearest_clip_solve(b, x, p, v, &err)) == 0) {
			DBG(("Doesn't contain a solution\n"));
//if (b->rix == 7135) printf("Doesn't contain a solution\n");
			return 0;
		}
	} else {
		if ((wsrv = nnearest_clip_solve(b, x, p, v, &err)) == 0) {
			DBG(("Doesn't contain a solution\n"));
//if (b->rix == 7135) printf("Doesn't contain a solution\n");
			return 0;
		}
	}

	/* We want the smallest clip error */
	if (err >= b->cdist) {	/* Equal or worse clip solution */
		DBG(("better solution has been found before (%f < %f)\n",b->cdist,err));
//if (b->rix == 7135) printf("better solution has been found before (%f < %f)\n",b->cdist,err);
		return 0;
	}

	DBG(("######## Accepting new clipn solution with error %f (replaces %f)\n",err,b->cdist));
//if (b->rix == 7135) printf("######## Accepting new clipn solution with error %f (replaces %f)\n",err,b->cdist);

	simplex_to_abs(x, b->cpp[0].p, p);	/* Convert to abs. space & copy */

	/* Put solution in place */
	for (f = 0; f < fdi; f++)
		b->cpp[0].v[f] = v[f];
	b->cdist = err;
	b->nsoln = 1;
	if (wsrv == 2)					/* Is above (disabled) ink limit */
		b->iclip = 1;

	return 0;
}

/* -------------------------------------------------------- */
/* Cell/simplex solver middle level code */

/* Find the point on this sub-simplexes solution locus that is */
/* closest to the target auxiliary values, and return it in xp[] */
/* Return zero if this point canot be calculated, */
/* or it lies outside the simplex. */
/* Return 1 normally, and 2 if the solution would be over the ink limit */
static int
auxil_solve(
schbase *b,
simplex *x,
double *xp		/* Return solution xp[sdi] */
) {
	rspl *s = b->s;
	int ee, e, di = s->di, sdi = x->sdi; 
	int f, efdi = x->efdi; 
	int dof = sdi-efdi;			 /* Degree of freedom of simplex locus */
	int *icomb = x->psxi->icomb; /* abs -> simplex coordinate translation */
	double auxt[MXRI];			 /* Simplex relative auxiliary targets */
	double bb[MXRI];
	int wsrv;	/* Within simplex return value */

	DBG(("axuil_solve called\n"));

	if (dof < 0)
		error("Error - auxil_solve got sdi < efdi (%d < %d) - don't know how to handle this",sdi, efdi);

	/* If there is no locus, compute an exact solution */
	if (dof == 0) {
		DBG(("axuil_solve dof = zero\n"));

		/* Init the RHS B[] vector (note sdi == efdi) */
		for (f = 0; f < efdi; f++) {
			xp[f] = b->v[f] - x->v[sdi][f];
		}

		/* Compute the solution (in simplex space) */
		lu_backsub(x->d_u, sdi, (int *)x->d_w, xp);

		/* Check that the solution is within the simplex & meets ink limit */
		if ((wsrv = within_simplex(x, xp)) != 0) {
			DBG(("Got solution at %s\n", debPdv(sdi,xp)));
			return wsrv;				/* OK, got solution */
		}

		DBG(("No solution (not within simplex)\n"));
		return 0;
	}

	/* There is a locus, so find solution nearest auxiliaries */

	/* Compute locus for target function values (if sdi > efdi) */
	if (add_locus(b, x)) {
		DBG(("Locus computation failed, skip simplex\n"));
		return 0;
	}

	/* Convert aux targets from absolute space to simplex relative */
	for (e = 0; e < di; e++) {	/* For abs coords */
		int ei = icomb[e];		/* Simplex coord */

		if (ei >= 0 &&  b->auxm[e] != 0) {
			auxt[ei] = (b->av[e] - x->p0[e])/s->g.w[e];	/* Only sets those needed */
		}
	}

	if (dof == 1 && b->naux == 1) {		/* Special case, because it's common and easy! */
		int ei = icomb[b->auxi[0]];		/* Simplex relative auxiliary index */
		double tt;

		DBG(("axuil_solve dof = naux = 1\n"));
		if (ei < 0)
			return 0;					/* Not going to find solution */
		if ((tt = x->lo_l[ei][0]) == 0.0)
			return 0;
		tt = (auxt[ei] - x->lo_bd[ei])/tt;	/* Parameter solution for target auxiliary */

		/* Back substitute parameter */
		for (e = 0; e < sdi; e++) {
			xp[e] = x->lo_bd[e] + tt * x->lo_l[e][0];
		}
		/* Check that the solution is within the simplex & meets ink limit */
		if ((wsrv = within_simplex(x, xp)) != 0) {
			DBG(("Got solution %s\n",debPdv(di,xp)));
			return wsrv;				/* OK, got solution */
		}
		DBG(("No solution (not within simplex)\n"));
		return 0;
	}

	/* Compute the locus decompositions needed (info #5) */
	if (add_auxil_lu_svd(b, x)) {	/* Will set x->naux */
		DBG(("LU/SVD decomp failed\n"));
		return 0;
	}

	/* Setup B[], equation RHS  */
	for (e = ee = 0; ee < b->naux; ee++) {
		int ei = icomb[b->auxi[ee]];		/* Simplex relative auxiliary index */
		if (ei >= 0)						/* Usable auxiliary on this sub simplex */ 
			bb[e++] = auxt[ei] - x->lo_bd[ei];
	}
	if (e != x->naux)	/* Assert */
		error("Internal error - auxil_solve got mismatching number of auxiliaries");

	if (x->naux == dof) {			/* Use LU decomp to solve */
		DBG(("axuil_solve using LU\n"));
		lu_backsub(x->ax_u, dof, (int *)x->ax_w, bb);

	} else if (x->naux > 0) {	/* Use SVD to solve least squares */
		DBG(("axuil_solve using SVD\n"));
		svdbacksub(x->ax_u, x->ax_w, x->ax_v, bb, bb, x->naux, dof);

	} else {	/* x->naux == 0 */
		DBG(("axuil_solve  naux = 0\n"));
		for (f = 0; f < dof; f++)
			bb[f] = 0.0;		/* Use base solution ?? */
	}

	/* Now back substitute the locus parameters */
	/* to calculate the solution point (in simplex space) */
	for (e = 0; e < sdi; e++) {
		double tt;
		for (tt = 0.0, f = 0; f < dof; f++) {
			tt += bb[f] * x->lo_l[e][f];
		}
		xp[e] = x->lo_bd[e] + tt;
	}

	/* Check that the solution is within the simplex & meets ink limit */
	if ((wsrv = within_simplex(x, xp)) != 0) {
		DBG(("Got solution %s\n",debPdv(di,xp)));
		return wsrv;				/* OK, got solution */
	}
	DBG(("No solution (not within simplex)\n"));
	return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Compute the min/max values for the current auxiliary of interest. */
/* Return zero if this point canot be calculated, */
/* or it lies outside the simplex. */
/* Return 1 normally, 2 if it would be outside the simplex if limting was enabled */
/* We expect to get a sub-simplex that will give an exact solution. */
static int
auxil_locus(
schbase *b,
simplex *x
) {
	rspl *s = b->s;
	int sdi = x->sdi; 
	int f, efdi = x->efdi; 
	double pp[MXRI];
	int wsrv;	/* Within simplex return value */

	DBG(("axuil_locus called\n"));

	if (sdi != efdi)
		warning("Internal error - auxil_locus got sdi != efdi (%d < %d)",sdi, efdi);

	/* Init the RHS B[] vector (note sdi == efdi) */
	for (f = 0; f < efdi; f++) {
		pp[f] = b->v[f] - x->v[sdi][f];
	}

	/* Compute the solution (in simplex space) */
	lu_backsub(x->d_u, sdi, (int *)x->d_w, pp);

	/* Check that the solution is within the simplex & meets ink limit */
	if ((wsrv = within_simplex(x, pp)) != 0) {
		double xval;
		int lxi = b->lxi;	/* Auxiliary we are finding min/max of (Abs space) */
		int xlxi = x->psxi->icomb[lxi];	/* Auxiliary we are finding min/max of (simplex space) */

		DBG(("Got locus solution within simplex\n"));

		/* Compute auxiliary value for this solution (absolute space) */
		xval = x->p0[lxi];
		if (xlxi >= 0)				/* Simplex param value */
			xval += s->g.w[lxi] * pp[xlxi];
		else if (xlxi == -2)		/* 1 value */
			xval += s->g.w[lxi];
									/* Else 0 value */

		if (b->asegs != 0) {		/* Tracking auxiliary segments */
			if (b->axisln >= b->axislz) {	/* Need some more space in list */
				if (b->axislz == 0) {
					b->axislz = 10;
					if ((b->axisl = (axisec *)rev_malloc(s, b->axislz * sizeof(axisec))) == NULL)
						error("rev: malloc failed - Auxiliary intersect list size %d",b->axislz);
					INCSZ(b->s, b->axislz * sizeof(axisec));
				} else {
					INCSZ(b->s, b->axislz * sizeof(axisec));
					b->axislz *= 2;
					if ((b->axisl = (axisec *)rev_realloc(s, b->axisl, b->axislz * sizeof(axisec)))
					    == NULL)
						error("rev: realloc failed - Auxiliary intersect list size %d",b->axislz);
				}
			}
			b->axisl[b->axisln].xval = xval;
			b->axisl[b->axisln].nv = x->sdi + 1;
			for (f = 0; f <= x->sdi; f++) {
				b->axisl[b->axisln].vix[f] = x->vix[f];
			}
			b->axisln++;
		}

#ifdef DEBUG
		if (xval >= b->min && xval <= b->max)
			DBG(("auxil_locus: solution %f doesn't improve on min %f, max %f\n",xval,b->min,b->max));
#endif
		/* If this solution is expands the min or max, save it */
		if (xval < b->min) {
			DBG(("######## Improving minimum to %f\n",xval));
			b->min = xval;
			b->plmincell = x->ix;
		}
		if (xval > b->max) {
			DBG(("######## Improving maximum to %f\n",xval));
			b->max = xval;
			b->plmaxcell = x->ix;
		}
	} else {
		DBG(("Solution wasn't within the simplex\n"));
		return 0;
	}

	return wsrv;
}

/* - - - - - - - - - - - - - - - - - - - - - - - */
/* Find the point on the clip line locus and simplexes */
/* valid surface, that is closest to the target output value. */
/* We expect to be given a sub simplex with sdi = fdi-1, and efdi = fdi */
/* or a limit sub-simplex with sdi = fdi, and efdi = fdi+1 */
/* Return zero if solution canot be calculated, */
/* return 1 normally, 2 if solution would be above the (disabled) ink limit */
static int
vnearest_clip_solve(
schbase *b,
simplex *x,
double *xp,		/* Return solution (simplex parameter space) */
double *xv,		/* Return solution (output space) */
double *err		/* Output error distance at solution point */
) {
	rspl *s = b->s;
	int e, sdi = x->sdi; 
	int f, fdi = s->fdi, efdi = x->efdi; 
	int g;
	int wsrv;	/* Within simplex return value */

	double *ta[MXRO], TA[MXRO][MXRO];
	double tb[MXRO];

	DBG(("Vector nearest clip solution called, cell %d, splx %d\n", x->ix, x->si));

	/* Setup temporary matricies */
	for (f = 0; f < sdi; f++) {
		ta[f] = TA[f];
	}

	/* Substitute simplex equation for output values V */
	/* in terms of sub-simplex parameters P, */
	/* into  clip line implicit equation in V, to give */
	/* clip line simplex implicit equation in terms of P (simplex input space) */
	/* If this is a limit sub-simlex, the ink limit part of the clip vector */
	/* equations will be used. */

	/* LHS: ta[sdi][sdi] = cla[sdi][efdi] * vv[efdi][sdi] */
	/* RHS: tb[sdi] = clb[sdi] - cla[sdi][efdi] * vv_di[efdi] */
	for (f = 0; f < sdi; f++) {
		double tt;
		for (e = 0; e < sdi; e++) {
			for (tt = 0.0, g = 0; g < efdi; g++)
				tt += b->cla[f][g] * (x->v[e][g] - x->v[e+1][g]);
			ta[f][e] = tt;
		}
		for (tt = 0.0, g = 0; g < efdi; g++)
			tt += b->cla[f][g] * x->v[sdi][g];
		tb[f] = b->clb[f] - tt;
	}

	/* Compute the solution */
	if (gen_solve_se(ta, tb, sdi, sdi)) {
		DBG(("Equation solution failed!\n"));
		return 0;		/* No solution */
	}

	/* Check that the solution is within the simplex & meets ink limit */
	if ((wsrv = within_simplex(x, tb)) != 0) {
		double dist;				/* distance to clip target */

		DBG(("Got solution within simplex %s\n", debPdv(sdi,tb)));

		/* Compute the output space solution point */
		for (f = 0; f < fdi; f++) {
			double tt = 0.0;
			for (e = 0; e < sdi; e++) {
				tt += (x->v[e][f] - x->v[e+1][f]) * tb[e];
			}
			xv[f] = tt + x->v[sdi][f];
		}

		/* Copy to return array */
		for (e = 0; e < sdi; e++)
			xp[e] = tb[e];

		// ~~~ are we properly checking if the intersection is 
		// ~~~ backwards rather than forwards in the line direction ?

		/* Compute distance to clip target */
		for (dist = 0.0, f = 0; f < fdi ; f++) {
			double tt = (b->v[f] - xv[f]);
			dist += tt * tt;
		}
		DBGV(("Vector clip output soln: ",fdi," %f", xv, "\n"));

		/* Return the solution in xp[]m xv[] and *err */
		*err = sqrt(dist);

		DBG(("Vector clip returning a solution with error %f\n",*err));
		return wsrv;
	}

	DBG(("Vector clip solution not in simplex\n"));
	return 0;		/* No solution */
}

/* - - - - - - - - - - - - - - - - - - - - - - - */
/* Find the point on the simplexes valid surface, that is closest */
/* to the target output value, for the linear (unweighted) case. */
/* We expect to be given a sub simplex with sdi = fdi-1, and efdi = fdi */
/* or a limit sub-simplex with sdi = fdi, and efdi = fdi+1 */
/* Return zero if solution canot be calculated, */
/* return 1 normally, 2 if solution would be above the (disabled) ink limit */
static int
nnearest_clip_solve(
schbase *b,
simplex *x,
double *xp,		/* Return solution (simplex parameter space) */
double *xv,		/* Return solution (output space) */
double *err		/* Output error (weighted) distance at solution point */
) {
	rspl *s = b->s;
	int e, sdi = x->sdi; 
	int f, fdi = s->fdi, efdi = x->efdi; 
	double tb[MXRO];		/* RHS & Parameter solution */
	double dist;			/* distance to clip target */
	int wsrv = 0;			/* Within simplex return value */

	DBG(("Nearest clip solution called, cell %d, splx %d\n", x->ix, x->si));

	if (sdi == 0) {		/* Solution is vertex */
		wsrv = 1;
		for (f = 0; f < efdi; f++)
			xv[f] = x->v[0][f]; 		/* Copy vertex value */
		if (x->v[0][fdi] > s->limitv) {
			if (s->limiten) 			/* Needed when limiten == 0 */
				return 0;				/* Over ink limit - no good */
			wsrv = 2;					/* Would be over */
		}
		DBG(("Got assumed vertex solution (vtx ix %d)\n",x->vix[0]));

	/* General linear nearest solver */
	} else {

#ifdef NEVER	/* Don't specialise ink limit version - use INKSCALE fudge instead */
		if (!(x->flags & SPLX_CLIPSX)) {	/* Not an ink limited plane simplex */
		
#endif
			/* Create the SVD decomp needed for least squares solution */
			if (add_lu_svd(x)) {
				DBG(("SVD decomp failed, skip simplex\n"));
				return 0;
			}
		
			/* Setup RHS to solve */
			for (f = 0; f < efdi; f++)
				tb[f] = b->v[f] - x->v[sdi][f]; 

			/* Find least squares solution */
			svdbacksub(x->d_u, x->d_w, x->d_v, tb, tb, efdi, sdi);
	
			/* Check that the solution is within the simplex & meets ink limit */
			if ((wsrv = within_simplex(x, tb)) == 0) {
				DBG(("Nearest clip solution not in simplex\n"));
				return 0;		/* No solution */
			}
	
			DBG(("Got solution within simplex %s\n",debPdv(sdi,tb)));
//if (b->rix == 7135) printf("Got solution within simplex params %s\n",debPdv(sdi,tb));
//if (b->rix == 7135) printf(" vertices ix %s\n",debPiv(sdi+1,x->vix));
	
			/* Compute the output space solution point */
			for (f = 0; f < fdi; f++) {
				double tt = 0.0;
				for (e = 0; e < sdi; e++)
					tt += (x->v[e][f] - x->v[e+1][f]) * tb[e];
				xv[f] = tt + x->v[sdi][f];
			}
//if (b->rix == 7135) printf("Computed Got simplex solution %s\n",debPdv(fdi,xv));
#ifdef NEVER /* ~~1 Haven't figured out equations to make this a special case. */
			 /* Content to use INKSCALE fudge and rely on SVD least squares. */
		} else {
			/* We can't use the given equations, because we want the solution */
			/* to lie exactly on the ink limit plane, and be least squares to the */
			/* other target parameters. */
			/* Extract the ink limit parameters, and transform them into */
			/* a parameterised surface for this simplex. */
			/* Substitute the ink plane equation into the remaining target */
			/* parameter equations, and solve for least squares. */

		}
#endif

		/* Copy to return array */
		for (e = 0; e < sdi; e++)
			xp[e] = tb[e];
	}

	/* Compute weighted distance to clip target */
	dist = sqrt(lchw_sq(s, b->v, xv));

//if (b->rix == 7135 && dist < b->cdist) {
//	printf("Got dist %f from %s -> %s with weight %d, %s\n", dist,debPdv(fdi,b->v),debPdv(fdi,xv),s->rev.lchweighted,debPdv(fdi,s->rev.lchw)); }

	DBGV(("Nearest clip output soln: ",fdi," %f", xv, "\n"));

	/* Return the solution in xp[], xv[] and *err */
	*err = dist;

	DBG(("Nearest clip returning a solution with error %f\n",*err));
	return wsrv;
}


#ifdef NEVER
/* Utility to convert an implicit ink limit plane equation */
/* held at the end of the simplex output value equations), */
/* into a parameterized surface equation. */
static void
compute_param_limit_surface(
schbase *b,
simplex *x
) {
	rspl *s = b->s;
	int ff, f, fdi = s->fdi;
	int i, p;
	double lgst;

double st[MXRO],	/* Start point */
double de[MXRO]		/* Delta */
//	DBG(("Computing clipping line implicit equation, dim = %d\n", fdi));
	
	/* Pick a pivot element - the smallest */
	for (lgst = -1.0, p = -1, f = 0; f < fdi; f++) {
		double tt = de[f];
		b->cdir[f] = tt;		/* Stash this away */
		tt = fabs(tt);
		if (tt > lgst) {
			lgst = tt;
			p = f;
		}
	}
	if (p < 0)	/* Shouldn't happen */
		error("rspl rev, internal, trying to cope with zero length clip line\n");
	
	if (b->cla == NULL)
		b->cla = dmatrix(0, fdi-1, 0, fdi);	/* Allow for ink limit supliment */

	for (i = ff = 0;  ff < fdi; ff++) {	/* For the input rows */
		if (ff == p) {
			continue;					/* Skip pivot row */
		}
		for (f = 0; f < fdi; f++) {		/* For input & output columns */
			if (f == p) {
				b->cla[i][f] = -de[ff];	/* Last column is -ve delta value */
			} else if (f == ff) {
				b->cla[i][f] = de[p];	/* Diagonal is pivot value */
			} else {
				b->cla[i][f] = 0.0;		/* Else zero */
			}
		}
		b->clb[i] = de[p] * st[ff] - de[ff] * st[p];
		i++;
	}

	/* Add ink limit target equation - */
	/* interpolated ink value == target */
	if (s->limitf != NULL) {
		for (i = 0;  i < (fdi-1); i++)
			b->cla[i][fdi] = 0.0;

		for (f = 0; f < fdi; f++) 
			b->cla[fdi-1][f] = 0.0;
		
		b->cla[fdi-1][fdi] = 1.0;
		b->clb[fdi-1] = s->limitv;
	}

#ifdef NEVER
/* Verify that the implicit equation is correct */
{
	double pnt[MXRO], v[MXRO];
	double pa;	/* Parameter */
	for (pa = 0.0; pa <= 1.0; pa += 0.125) {
		for (f = 0; f < fdi; f++) {
			pnt[f] = st[f] + pa * de[f];
		}

		/* Verify the implicit equation */
		for (ff = 0; ff < (fdi-1); ff++) {
			v[ff] = 0.0;
			for (f = 0; f < fdi; f++) {
				v[ff] += b->cla[ff][f] * pnt[f];
			}
			v[ff] -= b->clb[ff];
			if (v[ff] < 0.0)
				v[ff] = -v[ff];
			if (v[ff] > 0.000001) {
				printf("Point on clip line = %f %f %f\n",pnt[0],pnt[1],pnt[2]);
				printf("Implicit %d error of = %f\n",ff, v[ff]);
			}
		}
	}
}
#endif /* NEVER */

}

#endif


/* -------------------------------------------------------- */
static int lchw_edge_solve(rspl *s, double *vv, double *p, double *vt, double v[MXRI+1][MXRO+1]);
static int lchw_tri_solve(rspl *s, double *vv, double *p, double *vt, double v[MXRI+1][MXRO+1]); 

/* Find the point on the simplexes valid surface, that is closest */
/* to the target output value, for the LCh weighted case. */
/* We use Newton itteration to solve this for the 1D (line) and 2D (triangle) */
/* cases, and explicitly decode the ink limit surfaces back to point, line */
/* and triangled cases. */ 
/* We expect to be given a sub simplex with sdi = 0..2, and efdi = fdi */
/* or a limit sub-simplex with sdi = 1..3, and efdi = fdi+1 */
/* We bail with an assert if we get more than 2D to solve. */
/* Return zero if solution canot be calculated, */
/* return 1 normally, 2 if solution would be above the (disabled) ink limit */
static int
lchw_nnearest_clip_solve(
schbase *b,
simplex *x,
double *xp,		/* Return solution (simplex parameter space) */
double *xv,		/* Return solution (output space) */
double *err		/* Output error (weighted) distance at solution point */
) {
	rspl *s = b->s;
	int e, ee, sdi = x->sdi; 
	int f, fdi = s->fdi, efdi = x->efdi; 
	double tb[MXRO];		/* RHS & Parameter solution */
	double dist;			/* distance to clip target */
	int wsrv = 0;			/* Within simplex return value */

	DBG(("LChw nearest clip solution called, cell %d, splx %d\n", x->ix, x->si));

	/* - - - - - - - */
	if (sdi == 0) {		/* Solution is vertex */
		wsrv = 1;
		for (f = 0; f < efdi; f++)
			xv[f] = x->v[0][f]; 		/* Copy vertex value */
		if (x->v[0][fdi] > s->limitv) {
			if (s->limiten) 			/* Needed when limiten == 0 */
				return 0;				/* Over ink limit - no good */
			wsrv = 2;					/* Would be over */
		}
		DBG(("Got assumed vertex solution (vtx ix %d)\n",x->vix[0]));

	/* - - - - - - - */
	/* Ink limit simplex case */
	} else if (efdi == (fdi+1)) {

		/* Convert line into vertex and return it */
		if (sdi == 1) {
			wsrv = 1;

			/* Ink limit plane point along line */
			xp[0] = (s->limitv - x->v[1][fdi])/(x->v[0][fdi] - x->v[1][fdi]);

			/* Output value at that point */
			for (f = 0; f < fdi; f++)
				xv[f] = (x->v[0][f] - x->v[1][f]) * xp[0] + x->v[1][f];

			DBG(("Got ink limit point on edge\n"));

		/* Turn triangle into line and solve line. */
		} else if (sdi == 2) {
			int pos = 0, neg = 0;
			int ix[MXRI+1];		/* Odd index and the two other indexes */
			double p[MXRI+1], pp[MXRI+1];
			double v[MXRI+1][MXRO+1];

			/* Count ink limit signs of vertexes */
			for (e = 0; e <= sdi; e++) {
				ix[e] = e;
				if (x->v[e][fdi] > s->limitv)
					pos++;
				else
					neg++;
			}

			/* We expect one vertex to be on the other side of the */
			/* ink limit to the two others. */
			if (pos == 0 || neg == 0)
				error("Ink limit tri doesn't have one opposite sign");

			/* Make the first ix be the odd one */
			if (pos == 1) {
				if (x->v[0][fdi] <= s->limitv) {
					if (x->v[1][fdi] > s->limitv) {
						ix[0] = 1;
						ix[1] = 0;
					} else {
						ix[0] = 2;
						ix[2] = 0;
					}
				}
			} else {
				if (x->v[0][fdi] > s->limitv) {
					if (x->v[1][fdi] <= s->limitv) {
						ix[0] = 1;
						ix[1] = 0;
					} else {
						ix[0] = 2;
						ix[2] = 0;
					}
				}
			}
			
			/* Compute the points on the two edges that cross the ink limit. */
			/* i.e. for edges ix 0..1 & 0..2 */
			pp[0] = (s->limitv - x->v[ix[1]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[1]][fdi]);
			pp[1] = (s->limitv - x->v[ix[2]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[2]][fdi]);
			for (f = 0; f < fdi; f++) {
				v[0][f] = (x->v[ix[0]][f] - x->v[ix[1]][f]) * pp[0] + x->v[ix[1]][f];
				v[1][f] = (x->v[ix[0]][f] - x->v[ix[2]][f]) * pp[1] + x->v[ix[2]][f];
			}
			
			/* Solve it */
			if ((wsrv = lchw_edge_solve(s, xv, p, b->v, v)) != 0) {

				/* Figure out the solution simplex coords */
				/* (p is weighting of lower indexes vertex) */

				/* Convert solution simplex coords into baricentric weighting */ 
				p[1] = 1.0 - p[0];

				/* Sum baricentric weightings for each vertex */
				for (e = 0; e <= sdi; e++)
					xp[e] = 0.0;

				xp[ix[0]] += pp[0] * p[0];
				xp[ix[1]] += (1.0 - pp[0]) * p[0];
				xp[ix[0]] += pp[1] * p[1];
				xp[ix[2]] += (1.0 - pp[1]) * p[1];

				/* Convert back to simplex coords */
				xp[1] = 1.0 - xp[2];
				xp[0] = xp[0];

				DBG(("Got ink limit edge in triangle\n"));
			}

		/* Turn tetrahedron into one or two triangles */
		/* and solve triangles. */
		} else if (sdi == 3) {
			int pos = 0, neg = 0;
			int ix[MXRI+1];		/* Odd index and the three other indexes or 2 + 2 */
			double p[MXRI+1], pp[MXRI+1];
			double v[MXRI+1][MXRO+1];

			/* Count ink limit signs of vertexes */
			for (e = 0; e <= sdi; e++) {
				ix[e] = e;
				if (x->v[e][fdi] > s->limitv)
					pos++;
				else
					neg++;
			}

			/* We expect one or two vertexes t be on the other side of the */
			/* ink limit to the two others. */
			if (pos == 0 || neg == 0)
				error("Ink limit tetrahedron doesn't have one opposite sign");

			/* If we can decompose this into a single triangle */
			if (pos == 1 || neg == 1) {

				/* Make the first ix be the odd one */
				for (e = 0; e <= sdi; e++) {
					if ((pos == 1 && x->v[e][fdi] > s->limitv)
					 || (neg == 1 && x->v[e][fdi] <= s->limitv)) {
						int tt = ix[0];
						ix[0] = e;
						ix[e] = tt;
						break;
					}
				}

				/* Compute the points on the three edges that cross the ink limit. */
				/* i.e. for edges ix 0..1, 0..2 & 0..3 */
				pp[0] = (s->limitv - x->v[ix[1]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[1]][fdi]);
				pp[1] = (s->limitv - x->v[ix[2]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[2]][fdi]);
				pp[2] = (s->limitv - x->v[ix[3]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[3]][fdi]);
				for (f = 0; f < fdi; f++) {
					v[0][f] = (x->v[ix[0]][f] - x->v[ix[1]][f]) * pp[0] + x->v[ix[1]][f];
					v[1][f] = (x->v[ix[0]][f] - x->v[ix[2]][f]) * pp[1] + x->v[ix[2]][f];
					v[2][f] = (x->v[ix[0]][f] - x->v[ix[3]][f]) * pp[2] + x->v[ix[3]][f];
				}
				
				/* Solve it */
				if ((wsrv = lchw_tri_solve(s, xv, p, b->v, v)) != 0) {

					/* Figure out the solution simplex coords */
					/* (p is weighting of lower indexes vertex) */

					/* Convert solution simplex coords into baricentric weighting */ 
					p[2] = 1.0 - p[1];
					p[1] = p[1] - p[0];
					p[0] = p[0];

					/* Sum baricentric weightings for each vertex */
					for (e = 0; e <= sdi; e++)
						xp[e] = 0.0;

					xp[ix[0]] += pp[0] * p[0];
					xp[ix[1]] += (1.0 - pp[0]) * p[0];
					xp[ix[0]] += pp[1] * p[1];
					xp[ix[2]] += (1.0 - pp[1]) * p[1];
					xp[ix[0]] += pp[2] * p[2];
					xp[ix[3]] += (1.0 - pp[2]) * p[2];

					/* Convert back to simplex coords */
					xp[2] = 1.0 - xp[3];
					xp[1] = xp[1] + xp[0];
					xp[0] = xp[0];

					DBG(("Got single ink limit triangle in tetrahedron\n"));
				}

			/* We need to decompose this into two triangles */
			} else {
				int wsrv2 = 0;
				double dist2;
				double xv2[MXRO];		/* 2nd triangle solution */

				/* Make the first two ix's be the same, leaving second two the same. */
				for (e = 1; e <= sdi; e++) {
					if (x->v[0][fdi] > s->limitv && x->v[e][fdi] > s->limitv) {
						int tt = ix[1];
						ix[1] = e;
						ix[e] = tt;
						break;
					}
				}

				/* We choose disjoint vertex pairs as the common edge of the two */
				/* triangles, and then use each of the remaining pairs to form */
				/* the other edges. */
				/* i.e. common edge 0..2 + 1..3, then add 0..3 then 1..2 */
				pp[0] = (s->limitv - x->v[ix[2]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[2]][fdi]);
				pp[1] = (s->limitv - x->v[ix[3]][fdi])/(x->v[ix[1]][fdi] - x->v[ix[3]][fdi]);
				pp[2] = (s->limitv - x->v[ix[3]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[3]][fdi]);
				for (f = 0; f < fdi; f++) {
					v[0][f] = (x->v[ix[0]][f] - x->v[ix[2]][f]) * pp[0] + x->v[ix[2]][f];
					v[1][f] = (x->v[ix[1]][f] - x->v[ix[3]][f]) * pp[1] + x->v[ix[3]][f];
					v[2][f] = (x->v[ix[0]][f] - x->v[ix[3]][f]) * pp[2] + x->v[ix[3]][f];
				}
				
				/* Solve first one */
				if ((wsrv = lchw_tri_solve(s, xv, p, b->v, v)) != 0) {

					dist = sqrt(lchw_sq(s, b->v, xv));

					/* Figure out the solution simplex coords */
					/* (p is weighting of lower indexes vertex) */

					/* Convert solution simplex coords into baricentric weighting */ 
					p[2] = 1.0 - p[1];
					p[1] = p[1] - p[0];
					p[0] = p[0];

					/* Sum baricentric weightings for each vertex */
					for (e = 0; e <= sdi; e++)
						xp[e] = 0.0;

					xp[ix[0]] += pp[0] * p[0];
					xp[ix[2]] += (1.0 - pp[0]) * p[0];
					xp[ix[1]] += pp[1] * p[1];
					xp[ix[3]] += (1.0 - pp[1]) * p[1];
					xp[ix[0]] += pp[2] * p[2];
					xp[ix[3]] += (1.0 - pp[2]) * p[2];

					/* Convert back to simplex coords */
					xp[2] = 1.0 - xp[3];
					xp[1] = xp[1] + xp[0];
					xp[0] = xp[0];
				}

				/* Setup other triangle, 0..2 + 1..3, with 1..2 */
				pp[0] = (s->limitv - x->v[ix[2]][fdi])/(x->v[ix[0]][fdi] - x->v[ix[2]][fdi]);
				pp[1] = (s->limitv - x->v[ix[3]][fdi])/(x->v[ix[1]][fdi] - x->v[ix[3]][fdi]);
				pp[2] = (s->limitv - x->v[ix[2]][fdi])/(x->v[ix[1]][fdi] - x->v[ix[2]][fdi]);
				for (f = 0; f < fdi; f++) {
					v[0][f] = (x->v[ix[0]][f] - x->v[ix[2]][f]) * pp[0] + x->v[ix[2]][f];
					v[1][f] = (x->v[ix[1]][f] - x->v[ix[3]][f]) * pp[1] + x->v[ix[3]][f];
					v[2][f] = (x->v[ix[1]][f] - x->v[ix[2]][f]) * pp[2] + x->v[ix[2]][f];
				}
				
				/* Solve second triangle */
				if ((wsrv2 = lchw_tri_solve(s, xv2, p, b->v, v)) != 0) {

					dist2 = sqrt(lchw_sq(s, b->v, xv2));

					/* Use this second solution */
					if (wsrv == 0 || dist2 < dist) {

						dist = dist2;

						/* Figure out the solution simplex coords */
						/* (p is weighting of lower indexes vertex) */
	
						/* Convert solution simplex coords into baricentric weighting */ 
						p[2] = 1.0 - p[1];
						p[1] = p[1] - p[0];
						p[0] = p[0];
	
						/* Sum baricentric weightings for each vertex */
						for (e = 0; e <= sdi; e++)
							xp[e] = 0.0;
	
						xp[ix[0]] += pp[0] * p[0];
						xp[ix[2]] += (1.0 - pp[0]) * p[0];
						xp[ix[1]] += pp[1] * p[1];
						xp[ix[3]] += (1.0 - pp[1]) * p[1];
						xp[ix[1]] += pp[2] * p[2];
						xp[ix[2]] += (1.0 - pp[2]) * p[2];
	
						/* Convert back to simplex coords */
						xp[2] = 1.0 - xp[3];
						xp[1] = xp[1] + xp[0];
						xp[0] = xp[0];

						for (f = 0; f < fdi; f++)
							xv[f] = xv2[f];

					} else {
						wsrv2 = 0;
					}
				}

#ifdef DEBUG
				if (wsrv2) {
					DBG(("Got second ink limit triangle in tetrahedron\n"));
				} else if (wsrv) {
					DBG(("Got first ink limit triangle in tetrahedron\n"));
				}
#endif
				*err = dist;
				return wsrv;
			}
		} else {
			error("rev: lchw_nnearest_clip_solve sdi = %d\n",sdi);
		}

		/* All solutions computed on the ink limit surface */
		/* are assumed to be valid */

	/* - - - - - - - */
	/* Non-ink limit simplex case */
	} else {

		/* Line */
		if (sdi == 1) {
			wsrv = lchw_edge_solve(s, xv, xp, b->v, x->v);

			DBG(("Got line solution\n"));

		/* Triangle */
		} else if (sdi == 2) {
			wsrv = lchw_tri_solve(s, xv, xp, b->v, x->v);

			DBG(("Got triangle solution\n"));

		/* Oops */
		} else {
			error("rev: lchw_nnearest_clip_solve sdi = %d\n",sdi);
		}

		/* Check that the result is within the ink limit */
		if (wsrv != 0)
			wsrv = within_simplex_limit(x, xp);
	}

	if (wsrv == 0)
		return wsrv;

	/* Compute weighted distance to clip target */
	dist = sqrt(lchw_sq(s, b->v, xv));

	DBGV(("LChw nearest clip output soln: ",fdi," %f", xv, "\n"));

	/* Return the solution in xp[], xv[] and *err */
	*err = dist;

	DBG(("LChw nearest clip returning a solution with error %f\n",*err));

#ifdef NEVER
	{
		double chxv[MXRO]; 
	
		printf("LChw nearest clip returning a solution with error %f\n",dist);
	
		printf("Solution (sx in) %s -> out %s\n", debPdv(sdi, xp), debPdv(fdi, xv));
	
		if (dist < b->cdist) {	/* Equal or worse clip solution */
			printf("Will be new best solution\n");
		}
	
		/* Check the output space solution point */
		for (f = 0; f < fdi; f++) {
			double tt = 0.0;
			for (e = 0; e < sdi; e++)
				tt += (x->v[e][f] - x->v[e+1][f]) * xp[e];
			chxv[f] = tt + x->v[sdi][f];
		}
		for (f = 0; f < fdi; f++) {
			if (fabs(chxv[f] - xv[f]) > 1e-3) {
				break;
			}
		}
		if (f < fdi)
			printf(" ###### Check of out failed: %s\n", debPdv(fdi, chxv));
	}
#endif

	return wsrv;
}

/* - - - - - - - - - - - - - - - - - - - - - - - */
/* Edge lchw Newton itteration code */ 

#ifdef NEVER		/* Not actually used here */
/* return weighted delta squared for target to edge at param value p */
static double lchw_edge_sq(rspl *s, double *vt, double v[MXRI+1][MXRO+1], double p) {
	int f, fdi = s->fdi;
	double vv[MXRO];			/* Point at parameter location */
	double dlsq;				/* Delta L squared */
	double da, db, dchsq;		/* Delta CH squared */
	double ct, cv, dc, dcsq;	/* Delta C squared */
	double lcomp, chcomp, ccomp;
	double de;

	/* Compute point at parameter location */
	for (f = 0; f < fdi; f++)
		vv[f] = (v[0][f] - v[1][f]) * p + v[1][f];

	/* Delta L component */
	dlsq = vv[0] - vt[0];
	dlsq = dlsq * dlsq;
	lcomp = s->rev.lchw_sq[0] * dlsq;

	/* Delta CH component */
	da = vv[1] - vt[1];
	db = vv[2] - vt[2];
	dchsq = da * da + db * db;
	chcomp = s->rev.lchw_sq[2] * dchsq;

	/* Compute chromanance for the two colors */	
	ct = sqrt(vt[1] * vt[1] + vt[2] * vt[2]);	
	cv = sqrt(vv[1] * vv[1] + vv[2] * vv[2]);	
	dc = ct - cv;	
	dcsq = dc * dc;	
	
	ccomp = s->rev.lchw_chsq * dcsq;		/* w = cw - hw because dh = dch - dc */

	de = lcomp + chcomp + ccomp;

	return de;
}
#endif /* NEVER */

/* return weighted 1st derivativ of delta squared for target to edge at param value p */
static double lchw_edge_Dp_sq(rspl *s, double *vt, double v[MXRI+1][MXRO+1], double p) {
	int f, fdi = s->fdi;
	double vv[MXRO];			/* Point at parameter location */
	double Dvv[MXRO];			/* Derivative wrt p of vv */
	double dl, Ddlsq;			/* Delta L squared */
	double da, Ddasq, db, Ddbsq, Ddchsq;		/* Delta CH squared */
	double ct, cv, Dcv, dc, Ddc, Dvv1sq, Dvv2sq, Ddcsq;	/* Delta C squared */
	double Dlcomp, Dchcomp, Dccomp;
	double Dde;

	/* Compute point at parameter location */
	for (f = 0; f < fdi; f++) {
		vv[f] = (v[0][f] - v[1][f]) * p + v[1][f];
		Dvv[f] = v[0][f] - v[1][f];
	}

	/* Delta L component */
	dl = vv[0] - vt[0];
	Ddlsq = 2.0 * dl * Dvv[0];
	Dlcomp = s->rev.lchw_sq[0] * Ddlsq;

	/* Delta CH component */
	da = vv[1] - vt[1];
	db = vv[2] - vt[2];
	Ddasq = 2.0 * da * Dvv[1];
	Ddbsq = 2.0 * db * Dvv[2];
	Ddchsq = Ddasq + Ddbsq;
	Dchcomp = s->rev.lchw_sq[2] * Ddchsq;

	/* Compute chromanance for the two colors */	
	ct = sqrt(vt[1] * vt[1] + vt[2] * vt[2]);	
	cv = sqrt(vv[1] * vv[1] + vv[2] * vv[2]);	
	dc = cv - ct;	
	Dvv1sq = 2.0 * vv[1] * Dvv[1];
	Dvv2sq = 2.0 * vv[2] * Dvv[2];
	Dcv = 0.5/cv * (Dvv1sq + Dvv2sq);
	Ddcsq = 2.0 * dc * Dcv;	
	Dccomp = s->rev.lchw_chsq * Ddcsq;

	Dde = Dlcomp + Dchcomp + Dccomp;

	return Dde;
}

/* return weighted 2nd derivative of delta squared for target to edge at param value p */
static double lchw_edge_DDp_sq(rspl *s, double *vt, double v[MXRI+1][MXRO+1], double p) {
	int f, fdi = s->fdi;
	double vv[MXRO];			/* Point at parameter location */
	double Dvv[MXRO];			/* Derivative wrt p of vv */
	double DDvvsq[MXRO];		/* 2nd Derivative wrt p of vv */
	double DDdchsq;
	double ct, cv, Dcv, DDcv, dc, Dvv1sq, Dvv2sq, DDdcsq;
	double DDlcomp, DDchcomp, DDccomp;
	double DDde;

	/* Compute point at parameter location */
	for (f = 0; f < fdi; f++) {
		vv[f] = (v[0][f] - v[1][f]) * p + v[1][f];
		Dvv[f] = v[0][f] - v[1][f];
		DDvvsq[f] = 2.0 * Dvv[f] * Dvv[f];
	}

	/* Delta L component */
	DDlcomp = s->rev.lchw_sq[0] * DDvvsq[0];

	/* Delta CH component */
	DDdchsq = DDvvsq[1] + DDvvsq[2];
	DDchcomp = s->rev.lchw_sq[2] * DDdchsq;

	/* Compute chromanance for the two colors */	
	ct = sqrt(vt[1] * vt[1] + vt[2] * vt[2]);	
	cv = sqrt(vv[1] * vv[1] + vv[2] * vv[2]);	
	dc = cv - ct;	
	Dvv1sq = 2.0 * vv[1] * Dvv[1];
	Dvv2sq = 2.0 * vv[2] * Dvv[2];

	Dcv = 0.5/cv * (Dvv1sq + Dvv2sq);
	DDcv = -0.5/(cv * cv) * Dcv * (Dvv1sq + Dvv2sq) + 0.5/cv * (DDvvsq[1] + DDvvsq[2]);

	DDdcsq = 2.0 * (Dcv * Dcv + dc * DDcv);	
	DDccomp = s->rev.lchw_chsq * DDdcsq;

	DDde = DDlcomp + DDchcomp + DDccomp;

	return DDde;
}

/* Solve for an edge. Return nz of solution. */
static int lchw_edge_solve(rspl *s, double *vv, double *p, double *vt, double v[MXRI+1][MXRO+1]) {
	int i, f, fdi = s->fdi;
	double pp, ee, dedp;
	double e0, e1; 

	/* Decide whether there is a solution on this edge. */
	/* This is reliable, and saves any itters in the loop. */
	e0 = lchw_edge_Dp_sq(s, vt, v, 0.0);
	e1 = lchw_edge_Dp_sq(s, vt, v, 1.0);

	if ((e0 < 0.0 && e1 < 0.0)
	 || (e0 > 0.0 && e1 > 0.0)) {
		return 0;
	}

	pp = 0.5;
	for (i = 0; i < 30; i++) {
		ee = lchw_edge_Dp_sq(s, vt, v, pp);
		dedp = lchw_edge_DDp_sq(s, vt, v, pp);
		pp -= ee/dedp;  

		if (fabs(ee) < 1e-6)
			break;
	}
	ee = lchw_edge_Dp_sq(s, vt, v, pp);

	if (fabs(ee) > 1e-6 || pp < -EPS || pp > (1.0 + EPS)) {
		return 0;
	}

	/* Return solution (output space) */
	for (f = 0; f < fdi; f++)
		vv[f] = (v[0][f] - v[1][f]) * pp + v[1][f];

	/* Return solution (simplex parameter space) */
	*p = pp;

	return 1;
}

/* - - - - - - - - - - - - - - - - - - - - - - - */
/* Triangle lchw Newton itteration code */ 

/* return weighted delta squared for target to triangle at param values p */
/* [ 0 <= p0 <= p1 <= 1 ] */
static double lchw_tri_sq(rspl *s, double *vt, double v[MXRI+1][MXRO+1], double *p) {
	int f, fdi = s->fdi;
	double vv[MXRO];			/* Point at parameter location */
	double dlsq;				/* Delta L squared */
	double da, db, dchsq;		/* Delta CH squared */
	double ct, cv, dc, dcsq;	/* Delta C squared */
	double lcomp, chcomp, ccomp;
	double de;

	/* Compute point at parameter location */
	for (f = 0; f < fdi; f++)
		vv[f] = (v[0][f] - v[1][f]) * p[0]
		      + (v[1][f] - v[2][f]) * p[1]
		      +  v[2][f];

	/* Delta L component */
	dlsq = vv[0] - vt[0];
	dlsq = dlsq * dlsq;
	lcomp = s->rev.lchw_sq[0] * dlsq;

	/* Delta CH component */
	da = vv[1] - vt[1];
	db = vv[2] - vt[2];
	dchsq = da * da + db * db;
	chcomp = s->rev.lchw_sq[2] * dchsq;

	/* Compute chromanance for the two colors */	
	ct = sqrt(vt[1] * vt[1] + vt[2] * vt[2]);	
	cv = sqrt(vv[1] * vv[1] + vv[2] * vv[2]);	
	dc = ct - cv;	
	dcsq = dc * dc;	
	
	ccomp = s->rev.lchw_chsq * dcsq;		/* w = cw - hw because dh = dch - dc */

	de = lcomp + chcomp + ccomp;

	return de;
}

/* return weighted two 1st derivativ of delta squared for target to edge at param value p */
static void lchw_tri_Dp_sq(rspl *s, double Dde[2], double *vt, double v[MXRI+1][MXRO+1], double *p) {
	int f, fdi = s->fdi;
	double vv[MXRO];			/* Point at parameter location */
	double Dvv[2][MXRO];			/* Derivative wrt p of vv */
	double dl, Ddl[2], Ddlsq[2];			/* Delta L squared */
	double da, Ddasq[2], db, Ddbsq[2], Ddchsq[2];		/* Delta CH squared */
	double ct, cv, Dcv[2], dc, Dvv1sq[2], Dvv2sq[2], Ddcsq[2];	/* Delta C squared */
	double Dlcomp[2], Dchcomp[2], Dccomp[2];

	/* Compute point at parameter location */
	for (f = 0; f < fdi; f++) {
		vv[f] = (v[0][f] - v[1][f]) * p[0]
		      + (v[1][f] - v[2][f]) * p[1]
		      +  v[2][f];
		Dvv[0][f] = v[0][f] - v[1][f];
		Dvv[1][f] = v[1][f] - v[2][f];
	}

	/* Delta L component */
	dl = vv[0] - vt[0];
	Ddlsq[0] = 2.0 * dl * Dvv[0][0];
	Ddlsq[1] = 2.0 * dl * Dvv[1][0];
	Dlcomp[0] = s->rev.lchw_sq[0] * Ddlsq[0];
	Dlcomp[1] = s->rev.lchw_sq[0] * Ddlsq[1];

	/* Delta CH component */
	da = vv[1] - vt[1];
	db = vv[2] - vt[2];
	Ddasq[0] = 2.0 * da * Dvv[0][1];
	Ddasq[1] = 2.0 * da * Dvv[1][1];
	Ddbsq[0] = 2.0 * db * Dvv[0][2];
	Ddbsq[1] = 2.0 * db * Dvv[1][2];
	Ddchsq[0] = Ddasq[0] + Ddbsq[0];
	Ddchsq[1] = Ddasq[1] + Ddbsq[1];
	Dchcomp[0] = s->rev.lchw_sq[2] * Ddchsq[0];
	Dchcomp[1] = s->rev.lchw_sq[2] * Ddchsq[1];

	/* Compute chromanance for the two colors */	
	ct = sqrt(vt[1] * vt[1] + vt[2] * vt[2]);	
	cv = sqrt(vv[1] * vv[1] + vv[2] * vv[2]);	
	dc = cv - ct;	
	Dvv1sq[0] = 2.0 * vv[1] * Dvv[0][1];
	Dvv1sq[1] = 2.0 * vv[1] * Dvv[1][1];
	Dvv2sq[0] = 2.0 * vv[2] * Dvv[0][2];
	Dvv2sq[1] = 2.0 * vv[2] * Dvv[1][2];
	Dcv[0] = 0.5/cv * (Dvv1sq[0] + Dvv2sq[0]);
	Dcv[1] = 0.5/cv * (Dvv1sq[1] + Dvv2sq[1]);
	Ddcsq[0] = 2.0 * dc * Dcv[0];	
	Ddcsq[1] = 2.0 * dc * Dcv[1];	
	Dccomp[0] = s->rev.lchw_chsq * Ddcsq[0];
	Dccomp[1] = s->rev.lchw_chsq * Ddcsq[1];

	Dde[0] = Dlcomp[0] + Dchcomp[0] + Dccomp[0];
	Dde[1] = Dlcomp[1] + Dchcomp[1] + Dccomp[1];
}

/* return weighted four 2nd derivatives of delta squared for target to edge at param value p */
/* ([first][second]) */
static void lchw_tri_DDp_sq(rspl *s, double DDde[2][2], double *vt, double v[MXRI+1][MXRO+1], double *p) {
	int f, fdi = s->fdi;
	double vv[MXRO];					/* Point at parameter location */
	double Dvv[2][MXRO];				/* Derivative wrt p of vv */
	double DDvvsq[2][2][MXRO];			/* 2nd Derivative wrt p of vv */
	double DDdchsq[2][2];				/* Delta CH squared */
	double ct, cv, Dcv[2], DDcv[2][2], dc, Dvv1sq[2], Dvv2sq[2], DDdcsq[2][2];
	double DDlcomp[2][2], DDchcomp[2][2], DDccomp[2][2];

	/* Due to comutivity, [0][1] == [1][0], so we omit */
	/* those redundant calculations. */

	/* Compute point at parameter location */
	for (f = 0; f < fdi; f++) {
		vv[f] = (v[0][f] - v[1][f]) * p[0]
		      + (v[1][f] - v[2][f]) * p[1]
		      +  v[2][f];
		Dvv[0][f] = v[0][f] - v[1][f];
		Dvv[1][f] = v[1][f] - v[2][f];

		DDvvsq[0][0][f] = 2.0 * Dvv[0][f] * Dvv[0][f];
		DDvvsq[1][0][f] = 2.0 * Dvv[1][f] * Dvv[0][f];
//		DDvvsq[0][1][f] = 2.0 * Dvv[0][f] * Dvv[1][f];
		DDvvsq[1][1][f] = 2.0 * Dvv[1][f] * Dvv[1][f];
	}

	/* Delta L component */
	DDlcomp[0][0] = s->rev.lchw_sq[0] * DDvvsq[0][0][0];
	DDlcomp[1][0] = s->rev.lchw_sq[0] * DDvvsq[1][0][0];
//	DDlcomp[0][1] = s->rev.lchw_sq[0] * DDvvsq[0][1][0];
	DDlcomp[1][1] = s->rev.lchw_sq[0] * DDvvsq[1][1][0];

	/* Delta CH component */
	DDdchsq[0][0] = DDvvsq[0][0][1] + DDvvsq[0][0][2];
	DDdchsq[1][0] = DDvvsq[1][0][1] + DDvvsq[1][0][2];
//	DDdchsq[0][1] = DDvvsq[0][1][1] + DDvvsq[0][1][2];
	DDdchsq[1][1] = DDvvsq[1][1][1] + DDvvsq[1][1][2];

	DDchcomp[0][0] = s->rev.lchw_sq[2] * DDdchsq[0][0];
	DDchcomp[1][0] = s->rev.lchw_sq[2] * DDdchsq[1][0];
//	DDchcomp[0][1] = s->rev.lchw_sq[2] * DDdchsq[0][1];
	DDchcomp[1][1] = s->rev.lchw_sq[2] * DDdchsq[1][1];

	/* Compute chromanance for the two colors */	
	ct = sqrt(vt[1] * vt[1] + vt[2] * vt[2]);	
	cv = sqrt(vv[1] * vv[1] + vv[2] * vv[2]);	
	dc = cv - ct;	

	Dvv1sq[0] = 2.0 * vv[1] * Dvv[0][1];
	Dvv1sq[1] = 2.0 * vv[1] * Dvv[1][1];

	Dvv2sq[0] = 2.0 * vv[2] * Dvv[0][2];
	Dvv2sq[1] = 2.0 * vv[2] * Dvv[1][2];

	Dcv[0] = 0.5/cv * (Dvv1sq[0] + Dvv2sq[0]);
	Dcv[1] = 0.5/cv * (Dvv1sq[1] + Dvv2sq[1]);


	DDcv[0][0] = -0.5/(cv * cv) * Dcv[0] * (Dvv1sq[0] + Dvv2sq[0])
	            + 0.5/cv * (DDvvsq[0][0][1] + DDvvsq[0][0][2]);

	DDcv[1][0] = -0.5/(cv * cv) * Dcv[0] * (Dvv1sq[1] + Dvv2sq[1])
		        + 0.5/cv * (DDvvsq[1][0][1] + DDvvsq[1][0][2]);

//	DDcv[0][1] = -0.5/(cv * cv) * Dcv[1] * (Dvv1sq[0] + Dvv2sq[0])
//	            + 0.5/cv * (DDvvsq[0][1][1] + DDvvsq[0][1][2]);

	DDcv[1][1] = -0.5/(cv * cv) * Dcv[1] * (Dvv1sq[1] + Dvv2sq[1])
		        + 0.5/cv * (DDvvsq[1][1][1] + DDvvsq[1][1][2]);

	DDdcsq[0][0] = 2.0 * (Dcv[0] * Dcv[0] + dc * DDcv[0][0]);	
	DDdcsq[1][0] = 2.0 * (Dcv[1] * Dcv[0] + dc * DDcv[1][0]);	
//	DDdcsq[0][1] = 2.0 * (Dcv[0] * Dcv[1] + dc * DDcv[0][1]);	
	DDdcsq[1][1] = 2.0 * (Dcv[1] * Dcv[1] + dc * DDcv[1][1]);	

	DDccomp[0][0] = s->rev.lchw_chsq * DDdcsq[0][0];
	DDccomp[1][0] = s->rev.lchw_chsq * DDdcsq[1][0];
//	DDccomp[0][1] = s->rev.lchw_chsq * DDdcsq[0][1];
	DDccomp[1][1] = s->rev.lchw_chsq * DDdcsq[1][1];

	DDde[0][0] = DDlcomp[0][0] + DDchcomp[0][0] + DDccomp[0][0];
	DDde[1][0] = DDlcomp[1][0] + DDchcomp[1][0] + DDccomp[1][0];
//	DDde[0][1] = DDlcomp[0][1] + DDchcomp[0][1] + DDccomp[0][1];
	DDde[0][1] = DDde[1][0];
	DDde[1][1] = DDlcomp[1][1] + DDchcomp[1][1] + DDccomp[1][1];
}


/* Solve for a triangle face. Return nz of solution. */
static int lchw_tri_solve(rspl *s, double *vv, double *p, double *vt, double v[MXRI+1][MXRO+1]) {
	int f, fdi = s->fdi;
	int i, j, k;
	double pp[2], ee[2], dedp[2][2];
	int ff1 = 0, ff2 = 0, fit = -1;

	/* Decide whether there is a solution in this triangle */
	j = k = 0;
	pp[0] = 0.0; pp[1] = 0.0;
	lchw_tri_Dp_sq(s, ee, vt, v, pp);
	if (ee[0] < 0.0) j++;
	if (ee[1] < 0.0) k++;

	pp[0] = 0.0; pp[1] = 1.0;
	lchw_tri_Dp_sq(s, ee, vt, v, pp);
	if (ee[0] < 0.0) j++;
	if (ee[1] < 0.0) k++;

	if (j != 1 || k != 1) {
		pp[0] = 1.0; pp[1] = 1.0;
		lchw_tri_Dp_sq(s, ee, vt, v, pp);
		if (ee[0] < 0.0) j++;
		if (ee[1] < 0.0) k++;
	
		/* Making this || filters out lots more for an avg itter of 0.74, */
		/* but has a failure rate of 1 in 50000. */
		/* This less stringent filter has an avg itter of 2.0 and 0 failure rate. */
		if ((j == 0 || j == 3) && (k == 0 || k == 3)) {
			return 0;
		}
	}

	pp[0] = 0.3333; pp[1] = 0.6667;

	for (i = 0; i < 30; i++) {
		double det;

		lchw_tri_Dp_sq(s, ee, vt, v, pp);
		lchw_tri_DDp_sq(s, dedp, vt, v, pp);

		/* Correct the point using inverse of dedp */
		det = (dedp[0][0] * dedp[1][1] - dedp[0][1] * dedp[1][0]);
		if (fabs(det) < 1e-20)
			break;			/* Hmm. */

		det = 1.0/det;
		pp[0] -= det * ( dedp[1][1] * ee[0] - dedp[0][1] * ee[1]);
		pp[1] -= det * (-dedp[1][0] * ee[0] + dedp[0][0] * ee[1]);

		/* If we're sufficiently close to zero point */
		if (fabs(ee[0]) < 1e-6 && fabs(ee[1]) < 1e-6)
			break;

#ifdef NEVER
#define THR 0.25
		/* If we're too far out of bounds, give up */
		/* (Speeds things up by about 40% at the cost of failing */
		/*  some that would succeed.) */
		if (i >= 2 && (pp[0] < -THR || pp[0] > (1.0 + THR) || pp[1] < -THR || pp[1] > (1.0 + THR) 
		  || pp[1] < (pp[0]-THR))) {
			return 0;
		}
#undef THR
#endif
	}

	lchw_tri_Dp_sq(s, ee, vt, v, pp);

	if (fabs(ee[0]) > 1e-6 || fabs(ee[1]) > 1e-6
	 || pp[0] < -EPS || pp[1] < (pp[0]-EPS) || pp[1] > (1.0 + EPS)) {
		return 0;
	}

	/* Return solution (output space) */
	for (f = 0; f < fdi; f++) {
		vv[f] = (v[0][f] - v[1][f]) * pp[0]
		      + (v[1][f] - v[2][f]) * pp[1]
		      +  v[2][f];
	}

	/* Return solution (simplex parameter space) */
	p[0] = pp[0];
	p[1] = pp[1];

	return 1;
}

/* -------------------------------------------------------- */
/* Cell/simplex object lower level code */

/* Utility to get or calculate a vertexes ink limit value */
static double get_limitv(
schbase *b,			/* Base search information */
int ix,				/* fwd index of cell */
float *fcb,			/* Pointer to base of vertex value array (ix is used if NULL) */
double *p			/* Array of input values (can be NULL to compute) */
) {
	rspl *s = b->s;
	float *base = fcb;
	double lv;
	if (base == NULL)
		base = s->g.a + ix * s->g.pss;
	lv = base[-1];					/* Fetch existing ink limit function value */
	if ((float)lv == L_UNINIT) {			/* Not been computed yet */
		if (p != NULL) {
			lv = INKSCALE * s->limitf(s->lcntx, p);	/* Do it */
			base[-1] = (float)lv;
		} else {
			int e, di = s->di;
			double pp[MXRI];			/* Copy from float to double */
			int tix;					/* Temp fwd cell index */

			for (tix = ix, e = 0; e < di; e++) {
				int dix;
				dix = tix % s->g.res[e];
				tix /= s->g.res[e];
				pp[e] = s->g.l[e] + (double)dix * s->g.w[e];	/* Base point */
			}
			lv = INKSCALE * s->limitf(s->lcntx, pp);	/* Do it */
			base[-1] = (float)lv;
		}
		s->g.limitv_cached = 1;			/* At least one limit value is cached */
	}
	return lv;
}

/* Utility to invalidate all the ink limit values */
/* cached in the main rspl array */
static void clear_limitv(
rspl *s
) {
	int i;
	float *gp;		/* Grid point pointer */

	if (s->g.limitv_cached != 0) {	/* If any have been set */
		/* Unset them all */
		for (i = 0, gp = s->g.a; i < s->g.no; i++, gp += s->g.pss) {
			gp[-1] = L_UNINIT;
		}
		s->g.limitv_cached = 0;
	}
}

/* Cell code */

static void free_cell_contents(fxcell *c);
static fxcell *cache_fxcell(revcache *r, int ix, int force);
static void uncache_fxcell(revcache *r, fxcell *cp);

/* Return a pointer to an appropriate fxcell cache structure. */
/* None of the sub simplex lists will be initialised. */
/* NOTE: must unget_cell() (== uncache_fxcell()) when fxcell */
/* is no longer needed. */
/* Return NULL if we ran out of room in the cache. */
static fxcell *get_fxcell(
schbase *b,			/* Base search information */
int ix,				/* fwd index of cell */
int force			/* if nz, force memory allocation, so that we have at least one cell */
) {
	rspl *s = b->s;
	int ee, e, di = s->di;
	int p2di = (1<<di);
	int ff, f, fdi = s->fdi;
	fxcell *c;

	c = cache_fxcell(s->rev.cache, ix, force);	/* Fetch it from the cache and lock it */
	if (c == NULL)
		return NULL;

	if (!(c->flags & CELL_FLAG_1)) {			/* Have to (re)initialize cell & simplexes */
		int tix;								/* Temp fwd cell index */
		float *fcb = s->g.a + ix * s->g.pss;	/* Pointer to base float of fwd cell */

		/* Compute basic Cell info and vertex output values */
		for (ee = 0; ee < p2di; ee++) {
			float *vp = fcb + s->g.fhi[ee];
			for (f = 0; f < fdi; f++)		/* Transfer cell verticy values from grid */
				c->v[ee][f] = vp[f];

			/* ~~ reset any other cell info that will be stale */
		}

		/* Convert from cell index, to absolute fwd coord base values */
		c->limmin = INF_DIST;				/* and min/max values */
		c->limmax = -INF_DIST;
		for (tix = ix, e = 0; e < di; e++) {
			int dix;
			dix = tix % s->g.res[e];
			tix /= s->g.res[e];
			c->p[0][e] = s->g.l[e] + (double)dix * s->g.w[e];	/* Base point */
		}
		if (s->limitf != NULL) {			/* Compute ink limit values at base verticy */
			double lv = get_limitv(b, ix, fcb, c->p[0]); /* Fetch or generate limit value */
			c->v[0][fdi] = lv;
			if (lv < c->limmin)	/* And min/max for this cell */
				c->limmin = lv;
			if (lv > c->limmax)
				c->limmax = lv;
		}
			
		/* Setup cube verticy input position values, and ink limit values */
		for (ee = 1; ee < p2di; ee++) {
			for (e = 0; e < di; e++) {
				c->p[ee][e] = c->p[0][e];
				if (ee & (1 << e))
					c->p[ee][e] += s->g.w[e];		/* In input space offset */
			}
			if (s->limitf != NULL) {			/* Compute ink limit values at cell vertices */
				double lv = get_limitv(b, ix, fcb + s->g.fhi[ee], c->p[ee]);
				c->v[ee][fdi] = lv;
				if (lv < c->limmin)	/* And min/max for this cell */
					c->limmin = lv;
				if (lv > c->limmax)
					c->limmax = lv;
			}
		}
		
		/* Compute the output bounding group for fast rejection testing */
		{
			double *vp[POW2MXRI];

			/* Make array of pointers to double vectors */
			for (ee = 0; ee < p2di; ee++)
				vp[ee] = c->v[ee];

			nn_grpinit(s, &c->g, vp, p2di, NULL);
		}
		c->flags = CELL_FLAG_1;
	}

	return c;
}

void free_simplex_info(fxcell *c, int dof);

/* Free up any allocated simplexes in a cell, */
/* and set the pointers to NULL. */
/* Nothing else is changed (ie. it's NOT removed from */
/* the cache index or unthrheaded from the mru list). */
static void
free_cell_contents(
fxcell *c
) {
	int nsdi;
	
	/* Free up all the simplexes */
	if (c->s != NULL) {
		for (nsdi = 0; nsdi <= c->s->di; nsdi++) {
			if (c->sx[nsdi] != NULL) {
				free_simplex_info(c, nsdi);
				c->sx[nsdi] = NULL;
			}
		}
	}
	/* ~~ free any other cell information */
}

/* - - - - - -  */
/* Simplex code */

/* Simplex and Cell hash index size increments */
int primes[] = {
	367,
	853,
	1489,
	3373,
	6863,
	12919,
	23333,
	43721,
	97849,
	146221,
	254941,
	407843,
	756869,
	999983,
	-1
};

/* Compute a simplex hash index */
unsigned int simplex_hash(revcache *rc, int sdi, int efdi, int *vix) {
	unsigned int hash = 0;
	int i;

	for (i = 0; i <= sdi; i++)
		hash = hash * 17 + vix[i];
	hash = hash * 17 + sdi;
	hash = hash * 17 + efdi;

	hash %= rc->spx_hash_size;
	return hash;
}

/* Allocate and do the basic initialisation for a DOF list of simplexes */
void alloc_simplexes(
fxcell *c,
int nsdi			/* Non limited sub simplex dimensionality */
) {
	rspl *s = c->s;
	schbase *b = s->rev.sb;
	revcache *rc = s->rev.cache;
	int ee, e, di = s->di;
	int f, fdi = s->fdi;
	int lsdi;			/* Ink limited Sub-simplex sdi */
	int tsxno;			/* Total number of DOF simplexes */
	int nsxno;			/* Number of non-ink limited DOF simplexes */
	int si, so;			/* simplex index in and out */

	DBG(("Allocating level %d sub simplexes in cell %d\n",nsdi,c->ix));
	if (c->sx[nsdi] != NULL)
		error("rspl rev, internal, trying allocate already allocated simplexes\n");

	/* Figure out how many simplexes will be at this nsdi */
	lsdi = nsdi + 1;	/* Ink limit simplexes sdi */

	tsxno = nsxno = s->rev.sspxi[nsdi].nospx;

	if (s->limitf != NULL && lsdi <= di)
		tsxno += s->rev.sspxi[lsdi].nospx;		/* Second set with extra input dimension */

	/* Make sure there is enough space in temp simplex filter list */
	if (b->lsxfilt < tsxno) {	/* Allocate more space if needed */

		if (b->lsxfilt > 0) {	/* Free old space before allocating new */
			free(b->sxfilt);
			DECSZ(b->s, b->lsxfilt * sizeof(char));
		}
		b->lsxfilt = 0;
		/* Allocate enough space for all the candidate cells */
		if ((b->sxfilt = (char *)rev_malloc(s, tsxno * sizeof(char))) == NULL)
			error("rev: malloc failed - temp simplex filter list, count %d",tsxno);
		b->lsxfilt = tsxno;	/* Current allocated space */
		INCSZ(b->s, b->lsxfilt * sizeof(char));
	}
		
	/* Figure out the number of simplexes that will actually be needed */
	for (si = so = 0; si < tsxno; si++) {
		psxinfo *psxi = NULL;
		int *icomb, *offs;
		int sdi = nsdi;
		int efdi = fdi;
		int ssi = si;
		int isclip = 0;
		if (si >= nsxno) {				/* If limit boundary simplex */
			sdi++;						/* One more dimension */
			efdi++;						/* One more constraint */
			ssi -= nsxno;				/* In second half of list */
			isclip++;					/* Limit clipped simplex */
		}
		psxi = &s->rev.sspxi[sdi].spxi[ssi];
		icomb = psxi->icomb;
		offs  = psxi->offs;

		b->sxfilt[si] = 0;				/* Assume simplex won't be used */

		/* Check if simplex should be discared due to the ink limit */
		if (s->limitf != NULL) {
			double max = -INF_DIST;
			double min =  INF_DIST;

			/* Find the range of ink limit values covered by simplex */
			for (e = 0; e <= sdi; e++) {		/* For all the simplex vertices */
				int i = offs[e];
				double vv = c->v[i][fdi];		/* Ink limit value */
				if (vv < min)
					min = vv;
				if (vv > max)
					max = vv;
			}
			
//if ((max - min) > EPS) printf("~1 Found simplex sdi %d, efdi %d, min = %f, max = %f, limitv = %f\n", sdi, efdi, min,max,s->limitv);
			if (isclip) {	/* Limit clipped simplex */
				/* (Make sure it straddles the limit boundary) */
				if (max <= s->limitv || min > s->limitv)
					continue;		/* Discard this simplex - it can't straddle the ink limit */
//printf("~1 using sub simplex sdi %d, efdi %d, min = %f, max = %f, limitv = %f\n", sdi, efdi, min,max,s->limitv);
			} else {
				if (min > s->limitv)
					continue;		/* Discard this simplex - it is above the ink limit */
			}
		}

		b->sxfilt[si] |= 1;		/* This cell will be OK */
		so++;
	}

	DBG(("There are %d level %d sub simplexes\n",so, nsdi));
	/* Allocate space for all the DOF simplexes that will be used */
	if (so > 0) {
		if ((c->sx[nsdi] = (simplex **) rev_calloc(s, so, sizeof(simplex *))) == NULL)
			error("rspl malloc failed - fxcell simplexes - list of pointers");
		INCSZ(s, so * sizeof(simplex *));
	}

	/* Setup SPLX_FLAG_1 level information in the simplex */
	for (si = so = 0; si < tsxno; si++) {
		simplex *x;
		psxinfo *psxi = NULL;
		int *icomb;
		int sdi, efdi;
		int ssi;
		int vix[MXRI+1];            /* fwd cell vertex indexes of this simplex [sdi+1] */

		if (b->sxfilt[si] == 0)		/* Decided not to use this one */
			continue;

#ifdef STATS
		s->rev.st[b->op].sinited++;
#endif /* STATS */

		sdi = nsdi;
		efdi = fdi;
		ssi = si;
		if (si >= nsxno) {				/* If limit boundary simplex */
			sdi++;						/* One more dimension */
			efdi++;						/* One more constraint */
			ssi -= nsxno;				/* In second half of list */
		}

		psxi = &s->rev.sspxi[sdi].spxi[ssi];
		icomb = psxi->icomb;

		/* Compute simplex vertexes so we can match it in the cache */
		for (e = 0; e <= sdi; e++) 
			vix[e] = c->ix + s->g.hi[psxi->offs[e]];

		x = c->sx[nsdi][so];

		/* If this is a shared face simplex, see if we already have it in another fxcell */
		if (x == NULL && psxi->face) {
			unsigned int hash;
//printf("~1 looking for existing simplex nsdi = %d\n",nsdi);
			hash = simplex_hash(rc, sdi, efdi, vix);
			for (x = rc->spxhashtop[hash]; x != NULL; x = x->hlink) {
				if (x->sdi != sdi
				 || x->efdi != efdi)
					continue;			/* miss */
				for (e = 0; e <= sdi; e++) {
					if (x->vix[e] != vix[e])
						break;			/* miss */
				}
				if (e > sdi)
					break;				/* hit */
			}
			if (x != NULL) {
				x->refcount++;
//printf("~1 found hit in simplex face list hash %d, refcount = %d\n",hash,x->refcount);
			}
		}
		/* Doesn't already exist */
		if (x == NULL) {
			if ((x = (simplex *) rev_calloc(s, 1, sizeof(simplex))) == NULL)
				error("rspl malloc failed - fxcell simplexes - base simplex %d bytes",sizeof(simplex));
			INCSZ(s, sizeof(simplex));
			x->refcount = 1;
			x->touch = s->rev.stouch-1;
			x->flags = 0;

			if (si >= nsxno) {				/* If limit boundary simplex */
				x->flags |= SPLX_CLIPSX;	/* Limit clipped simplex */
			}

			/* Fill in the other simplex details */
			x->s    = s;					/* Parent rspl */
			x->ix   = c->ix;				/* Construction cube base index */
			for (e = 0; e <= sdi; e++)		/* Indexs of fwd vertices that make up this simplex */
				x->vix[e] = vix[e];
			x->psxi = psxi;					/* Pointer to constant per simplex info */
//printf("~1 set simplex 0x%x psxi = 0x%x\n",x,x->psxi);
			x->si   = so;					/* Diagnostic, simplex offset in list */
			x->sdi  = sdi;					/* Copy of simplex dimensionaity */
			x->efdi = efdi;					/* Copy of effective output dimensionality */

			/* Copy cell simplex vertex output and limit values */
			for (e = 0; e <= sdi; e++) {		/* For all the simplex vertices */
				int i = x->psxi->offs[e];

				for (f = 0; f <= fdi; f++)		/* Copy vertex value + ink sum */
					x->v[e][f] = c->v[i][f];

				/* Setup output bounding box values (the hard way) */
				if (e == 0) {						/* Init to first vertex of simplex */
					for (f = 0; f <= fdi; f++)		/* Output space */
						x->min[f] = x->max[f] = c->v[i][f];
				} else {
					for (f = 0; f <= fdi; f++) {	/* Output space + ink sum */
						double vv;
//						if (f == fdi && s->limit == NULL)
//							continue;			/* Skip ink */
						vv = c->v[i][f];
						if (vv < x->min[f])
							x->min[f] = vv;
						else if (vv > x->max[f])
							x->max[f] = vv;
					}
				}
			}
			/* Add a margin */
			for (f = 0; f <= fdi; f++) {	/* Output space + ink sum */
				x->min[f] -= EPS;
				x->max[f] += EPS;
			}

			/* Setup input bounding box value pointers (the easy way) */
			for (ee = 0; ee < di; ee++) {
				x->p0[ee]   = c->p[0][ee];		/* Construction base cube origin */
				x->pmin[ee] = c->p[x->psxi->pmino[ee]][ee] - EPS;
				x->pmax[ee] = c->p[x->psxi->pmaxo[ee]][ee] + EPS;
			}

			x->flags |= SPLX_FLAG_1;		/* vv & iv done, nothing else */

			x->aloc2 = x->aloc5 = NULL;		/* Matrix allocations not done yet */

			/* Add it to the shared face simplex hash index */
			if (x->psxi->face) {
				unsigned int hash;
				int i;
				/* See if we should re-size the simplex hash index */
				if (++rc->nspx > (HASH_FILL_RATIO * rc->spx_hash_size)) {
					for (i = 0; primes[i] > 0 && primes[i] <= rc->spx_hash_size; i++)
						;
					if (primes[i] > 0) {
						int spx_hash_size = rc->spx_hash_size;	/* Old */
						simplex **spxhashtop = rc->spxhashtop;

						rc->spx_hash_size = primes[i];

						DBG(("Increasing face simplex hash index to %d\n",spx_hash_size));
//printf("~1 increasing simplex hash index size to %d\n",spx_hash_size);
						/* Allocate a new index */
						if ((rc->spxhashtop = (simplex **) rev_calloc(s, rc->spx_hash_size,
						                                   sizeof(simplex *))) == NULL)
							error("rspl malloc failed - reverse simplex cache index");
						INCSZ(s, rc->spx_hash_size * sizeof(simplex *));

						/* Transfer all the simplexes to the new index */
						for (i = 0; i < spx_hash_size; i++) {
							simplex *x, *nx;
							for (x = spxhashtop[i]; x != NULL; x = nx) {
								nx = x->hlink;
								hash = simplex_hash(rc, x->sdi, x->efdi, x->vix);	/* New hash */
								x->hlink = rc->spxhashtop[hash];	/* Add to new hash index */
								rc->spxhashtop[hash] = x;
							}
						}
						free(spxhashtop); /* Done with old index */
						DECSZ(s, spx_hash_size * sizeof(simplex *));
					}
				}
				hash = simplex_hash(rc, sdi, efdi, vix);

				/* Add this to hash index */
				x->hlink = rc->spxhashtop[hash];
				rc->spxhashtop[hash] = x;
//printf("~1 Added simplex to hash %d, rc->nspx = %d\n",hash,rc->nspx);
			}

//if (rc->nunlocked == 0 && rc->s->rev.sz > rc->s->rev.max_sz)
//printf("~1 unable to decrease_revcache 1\n");

			/* keep memory in check */
			while (rc->nunlocked > 0 && rc->s->rev.sz > rc->s->rev.max_sz) {
				if (decrease_revcache(rc) == 0)
					break;
			}
		}
		c->sx[nsdi][so] = x;
		so++;
	}
	c->sxno[nsdi] = so;				/* Record actual number in list */
	c->flags |= CELL_FLAG_2;		/* Note that cell now has simplexes */
}

/* Free up any allocated for a list of sub-simplexes */
void
free_simplex_info(
fxcell *c,
int nsdi			/* non limit sub simplex dimensionaity */
) {
	int si, sxno = c->sxno[nsdi];	/* Number of simplexes */

	for (si = 0; si < sxno; si++) { /* For all the simplexes */
		simplex *x = c->sx[nsdi][si];
		int dof = x->sdi - x->efdi;

//printf("~1 freeing simplex, refcount = %d\n",x->refcount);
		if (--x->refcount <= 0) {		/* Last reference to this simplex */

//printf("~1 freeing simplex 0x%x psxi = 0x%x\n",x,x->psxi);
			if (x->psxi->face) {
				unsigned int hash;
				revcache *rc = c->s->rev.cache;
				
				hash = simplex_hash(rc, x->sdi, x->efdi, x->vix);

				/* Free it from the hash list */
				if (rc->spxhashtop[hash] == x) {
					rc->spxhashtop[hash] = x->hlink;
					rc->nspx--;
//printf("~1 removed simplex from hash %d, nspx now = %d\n",hash,rc->nspx);
				} else {
					simplex *xx;
					for (xx = rc->spxhashtop[hash]; xx != NULL && xx->hlink != x; xx = xx->hlink)
						;
					if (xx != NULL) {		/* Found it */
						xx->hlink = x->hlink;
						rc->nspx--;
//printf("~1 removed simplex from hash %d, nspx now = %d\n",hash,rc->nspx);
					}
//else
//printf("~1 warning, failed to find face simplex hash %d, sdi = %d in cache index (nspx = %d)!!\n",hash,x->sdi,rc->nspx);
				}
			}
			if (x->aloc2 != NULL) {
				int adof = dof >= 0 ? dof : 0;		/* Allocation dof */
				int asize;
				if (dof == 0)
					asize = sizeof(double) * (x->efdi * x->sdi)
				          + sizeof(double *) * x->efdi 
				          + sizeof(int) * x->sdi;
				else
					asize = sizeof(double) * (x->sdi * (x->efdi + x->sdi + adof + 2) + x->efdi)
				          + sizeof(double *) * (x->efdi + 2 * x->sdi);
				free(x->aloc2);
				DECSZ(x->s, asize);
			}

			if (x->aloc5 != NULL) {
				int asize;
				if (x->naux == dof)
					asize = sizeof(double *) * x->naux
				          + sizeof(double) * (x->naux * dof)
				          + sizeof(int) * dof;
				else
					asize = sizeof(double *) * (x->naux + dof) 
					      + sizeof(double) * (dof * (x->naux + dof + 1));
				free(x->aloc5);
				DECSZ(x->s, asize);
			}

			/* ~~ free any other simplex information */

			free(x);
			DECSZ(c->s, sizeof(simplex));
			c->sx[nsdi][si] = NULL;
		}
	}
	free(c->sx[nsdi]);
	DECSZ(c->s, c->sxno[nsdi] * sizeof(simplex *));
	c->sx[nsdi] = NULL;
	c->sxno[nsdi] = 0;

	/* ~~ free any other cell information */
}

/* - - - - - - - - - - - - */
/* Check that an input space vector is within a given simplex, */
/* and that it meets any ink limit. */
/* Return zero if outside the simplex, */
/* 1 normally if within the simplex, */
/* and 2 if it would be over the ink limit if limit was enabled. */
static int
within_simplex(
simplex *x,				/* Simplex */
double *p				/* Input coords in simplex space */
) {
	rspl *s = x->s;
	int    fdi = s->fdi;
	int e, sdi = x->sdi;		/* simplex dimensionality */
	double cp, lp;
	int rv = 1;
	/* EPS is allowance for numeric error */
	/* (Don't want solutions falling down */
	/* the numerical cracks between the simplexes) */

	/* Check we are within baricentric limits */
	for (lp = 0.0, e = 0; e < sdi; e++) {
		cp = p[e];
		if ((cp+EPS) < lp) 		/* Outside baricentric or not in correct */
			return 0;			/* order for this simplex  */
		lp = cp;
	}
	if ((1.0+EPS) < lp)  		/* outside baricentric range */
		return 0;

	/* Compute limit using interp. - assume simplex would have been trivially rejected */
	if (s->limitf != NULL) {
		double sum = 0.0;			/* Might be over the limit */
		for (e = 0; e < sdi; e++)
			sum += p[e] * (x->v[e][fdi] - x->v[e+1][fdi]);
		sum += x->v[sdi][fdi];
		if (sum > s->limitv) {
			if (s->limiten != 0)
	 			return 0;			/* Exceeds ink limit */
			else
				rv = 2;				/* would have exceeded limit */
		}
	}

#ifdef NEVER
	/* Constrain to legal values */
	/* (Is this needed ?????) */
	for (e = 0; e < sdi; e++) {
		cp = p[e];
		if (cp < 0.0)
			p[e] = 0.0;
		else if (cp > 1.0)
			p[e] = 1.0;
	}
#endif
	return rv;
}

/* Check that an input space vector of a simplex meets the ink limit. */
/* Return zero if outside the simplex, */
/* 1 normally if within the simplex, */
/* and 2 if it would be over the ink limit if limit was enabled. */
/* This is the same as within_simplex() but only checks the ink limit. */
static int
within_simplex_limit(
simplex *x,				/* Simplex */
double *p				/* Input coords in simplex space */
) {
	rspl *s = x->s;
	int    fdi = s->fdi;
	int e, sdi = x->sdi;		/* simplex dimensionality */
	int rv = 1;

	/* Compute limit using interp. - assume simplex would have been trivially rejected */
	if (s->limitf != NULL) {
		double sum = 0.0;			/* Might be over the limit */
		for (e = 0; e < sdi; e++)
			sum += p[e] * (x->v[e][fdi] - x->v[e+1][fdi]);
		sum += x->v[sdi][fdi];
		if (sum > s->limitv) {
			if (s->limiten != 0)
	 			return 0;			/* Exceeds ink limit */
			else
				rv = 2;				/* would have exceeded limit */
		}
	}
	return rv;
}

/* Similar check to within_simplex(), but with explicit simplex definition */
/* and no ink limit check. Returns 0 if outside, 1 if within */
static int
simple_within_simplex(
double v[MXRI+1][MXRO],	/* Vertex values */
double *p,				/* Input coords in simplex space */
int sdi					/* input dimensionality of simplex */
) {
	int e;
	double cp, lp;

	/* Check we are within baricentric limits */
	for (lp = 0.0, e = 0; e < sdi; e++) {
		cp = p[e];
		if ((cp+EPS) < lp) 		/* Outside baricentric or not in correct */
			return 0;			/* order for this simplex  */
		lp = cp;
	}
	if ((1.0+EPS) < lp)  		/* outside baricentric range */
		return 0;

	return 1;
}

/* Convert vector from simplex space to absolute cartesian space */
static void simplex_to_abs(
simplex *x,
double *out,	/* output in absolute space */
double *in		/* Input in simplex space */
) {
	rspl *s     = x->s;
	int e, di   = s->di;
	int *icomb  = x->psxi->icomb;	/* Coord combination order */

	for (e = 0; e < di; e++) {		/* For each absolute coord */
		double ov = x->p0[e];		/* Base value */
		int ee = icomb[e];			/* Simplex param index */
		if (ee >= 0)				/* Simplex param value */
			ov += s->g.w[e] * in[ee];
		else if (ee == -2)			/* 1 value */
			ov += s->g.w[e];
									/* Else 0 value */
		out[e] = ov;
	}
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Given the parametric clip line equation, compute the */
/* implicit equation in terms of the absolute output space. */
/* Pad equation with target ink limit in case it is use */
/* with CLIPSX sub-simplexes. */
/* Note that no line equation values are returned if fdi = 1, */
/* since there is no such thing as an implicit line equation. */
/* (Re-usable version for lines in general) */
static void
init_line_eq_imp(
rspl *s,
schbase *b,			/* to set cdir, may be NULL if not needed. */
double ***pcla,		/* pointer to clip vector LHS implicit equation matrix */
double clb[MXRO+1],	/* Clip vector RHS implicit equation vector */
double st[MXRO],	/* Start point */
double de[MXRO],	/* Delta */
int inkeq			/* nz to add ink limit target equation if s->limitf != NULL */
) {
	int ff, f, fdi = s->fdi;
	int i, p;
	double lgst;
	double **cla = *pcla;

//	DBG(("Computing clipping line implicit equation, dim = %d\n", fdi));
	
	/* Pick a pivot element */
	for (lgst = -1.0, p = -1, f = 0; f < fdi; f++) {
		double tt = de[f];
		if (b != NULL)
			b->cdir[f] = tt;		/* Stash this away */
		tt = fabs(tt);
		if (tt > lgst) {
			lgst = tt;
			p = f;
		}
	}
	if (p < 0)	/* Shouldn't happen */
		error("rspl rev, internal, trying to cope with zero length clip line\n");
	
	if (cla == NULL) {
		cla = dmatrix(0, fdi-1, 0, fdi);	/* Allow for ink limit supliment */
		*pcla = cla;
	}

	for (i = ff = 0;  ff < fdi; ff++) {	/* For the input rows */
		if (ff == p) {
			continue;					/* Skip pivot row */
		}
		for (f = 0; f < fdi; f++) {		/* For input & output columns */
			if (f == p) {
				cla[i][f] = -de[ff];	/* Last column is -ve delta value */
			} else if (f == ff) {
				cla[i][f] = de[p];	/* Diagonal is pivot value */
			} else {
				cla[i][f] = 0.0;		/* Else zero */
			}
		}
		clb[i] = de[p] * st[ff] - de[ff] * st[p];
		i++;
	}

	/* Add ink limit target equation - */
	/* interpolated ink value == target */
	if (inkeq && s->limitf != NULL) {
		for (i = 0;  i < (fdi-1); i++)
			cla[i][fdi] = 0.0;

		for (f = 0; f < fdi; f++) 
			cla[fdi-1][f] = 0.0;
		
		cla[fdi-1][fdi] = 1.0;
		clb[fdi-1] = s->limitv;
	}

#ifdef NEVER
/* Verify that the implicit equation is correct */
{
	double pnt[MXRO], v[MXRO];
	double pa;	/* Parameter */
	for (pa = 0.0; pa <= 1.0; pa += 0.125) {
		for (f = 0; f < fdi; f++) {
			pnt[f] = st[f] + pa * de[f];
		}

		/* Verify the implicit equation */
		for (ff = 0; ff < (fdi-1); ff++) {
			v[ff] = 0.0;
			for (f = 0; f < fdi; f++) {
				v[ff] += cla[ff][f] * pnt[f];
			}
			v[ff] -= clb[ff];
			if (v[ff] < 0.0)
				v[ff] = -v[ff];
			if (v[ff] > 0.000001) {
				printf("Point on clip line = %f %f %f\n",pnt[0],pnt[1],pnt[2]);
				printf("Implicit %d error of = %f\n",ff, v[ff]);
			}
		}
	}
}
#endif /* NEVER */

}

/* Version of above used to set vector clipping line up */
static void
init_line_eq(
schbase *b,
double st[MXRO],	/* Start point */
double de[MXRO]		/* Delta */
) {
//	DBG(("Computing clipping line implicit equation, dim = %d\n", b->s->fdi));
	
	init_line_eq_imp(b->s, b, &b->cla, b->clb, st, de, 1); 
}

/* - - - - - -  */
/* Simpex solution info #2 */

/* Create the LU or SVD decomp needed to compute solution or locus. */
/* Return non-zero if it cannot be created */
static int
add_lu_svd(simplex *x) {

	if (x->flags & SPLX_FLAG_2F) {		/* Previously failed */
		return 1;
	}
	if (!(x->flags & SPLX_FLAG_2)) {
		int ee, e, sdi = x->sdi; 
		int f, efdi = x->efdi; 
		int dof = sdi-efdi;		/* Degree of freedom of locus, or -ve over specification */
		int adof = dof >= 0 ? dof : 0;		/* Allocation dof */
		int i;

		if (x->aloc2 == NULL) {	/* Allocate space for matricies and arrays */
			/* Do this in one hit to minimise malloc overhead */
			if (dof == 0) {
				int i;
				char *mem;
				int asize = sizeof(double) * (efdi * sdi)
				          + sizeof(double *) * efdi 
				          + sizeof(int) * sdi;

				if ((x->aloc2 = mem = (char *) rev_malloc(x->s, asize)) == NULL)
					error("rspl malloc failed - fxcell sub-simplex matricies");
				INCSZ(x->s, asize);

				/* Allocate biggest to smallest (double, pointers, ints) */
				/* to make sure that items lie on the natural boundaries. */

				/* Reserve matrix doubles */
				mem += efdi * sdi * sizeof(double);

				/* Allocate pointers */
				x->d_u = (double **)mem, mem += efdi * sizeof(double *);

				/* Allocate ints */
				x->d_w = (double *)mem, mem += sdi * sizeof(int);

#ifdef DEBUG
				if (mem != (x->aloc2 + asize))
					error("~1 aloc2a assert failed! Is %d, should be %d\n",mem - x->aloc2,asize);
#endif /* DEBUG */

				/* Reset and allocate matrix doubles */
				mem = x->aloc2; 
				for (i = 0; i < efdi; i++)
					x->d_u[i] = (double *)mem,	mem += sdi * sizeof(double);

			} else {
				int i;
				char *mem;
				int asize = sizeof(double) * (sdi * (efdi + sdi + adof + 2) + efdi)
				          + sizeof(double *) * (efdi + 2 * sdi);

				if ((x->aloc2 = mem = (char *) rev_malloc(x->s, asize)) == NULL)
					error("rspl malloc failed - fxcell sub-simplex matricies");
				INCSZ(x->s, asize);

				/* Allocate biggest to smallest (double, pointers, ints) */
				/* to make sure that items lie on the natural boundaries. */

				/* Reserve matrix doubles */
				mem += sdi * (efdi + sdi + adof) * sizeof(double);

				/* Allocate doubles */
				x->lo_xb = (double *)mem, mem += efdi * sizeof(double);
				x->lo_bd = (double *)mem; mem += sdi * sizeof(double);
				x->d_w = (double *)mem, mem += sdi * sizeof(double);

				/* Allocate pointers */
				x->d_u = (double **)mem, mem += efdi * sizeof(double *);
				x->d_v = (double **)mem, mem += sdi * sizeof(double *);
				x->lo_l = (double **)mem, mem += sdi * sizeof(double *);

#ifdef DEBUG
				if (mem != (x->aloc2 + asize))
					error("~1 aloc2b assert failed! Is %d, should be %d\n",mem - x->aloc2,asize);
#endif /* DEBUG */

				/* Reset and allocate matrix doubles */
				mem = x->aloc2;
				for (i = 0; i < efdi; i++)
					x->d_u[i] = (double *)mem,	mem += sdi * sizeof(double);
				for (i = 0; i < sdi; i++)
					x->d_v[i] = (double *)mem,	mem += sdi * sizeof(double);
				for (i = 0; i < sdi; i++)
					x->lo_l[i] = (double *)mem,	mem += adof * sizeof(double);

				/* Init any values that will be read before being written to. */
				for (f = 0; f < efdi; f++)
					x->lo_xb[f] = 1e100;		/* Silly value */
			}
		}

		/* Setup matrix from vertex values */
		for (f = 0; f < efdi; f++)
			for (e = 0; e < sdi; e++)
				x->d_u[f][e] = x->v[e][f] - x->v[e+1][f];

		if (dof == 0) {	/* compute LU */
			double rip;
#ifdef STATS
			x->s->rev.st[x->s->rev.sb->op].sinited2a++;
#endif /* STATS */
			if (lu_decomp(x->d_u, sdi, (int *)x->d_w, &rip)) {
				x->flags |= SPLX_FLAG_2F;	/* Failed */
				return 1;
			}
		} else {
//printf("~~ Creating SVD decomp, sdi = %d, efdi = %d\n", sdi, efdi);

#ifdef STATS
			x->s->rev.st[x->s->rev.sb->op].sinited2b++;
#endif /* STATS */
			if (svdecomp(x->d_u, x->d_w, x->d_v, efdi, sdi)) {
				x->flags |= SPLX_FLAG_2F;	/* Failed */
				return 1;
			}
	
			/* Threshold the singular values W[] */ 
			svdthresh(x->d_w, sdi);
	
			if (dof >= 0) {		/* If we expect a locus */
//printf("~~ got dif %d locus from SVD\n",dof);
				/* copy the locus direction coefficients out */
				for (i = e = 0; e < sdi; e++) {
					if (x->d_w[e] == 0.0) {		/* Found a zero W[] */
						if (i < dof) {
							for (ee = 0; ee < sdi; ee++) {	/* Copy column of V[][] */
								x->lo_l[ee][i] = x->d_v[ee][e];
							}
						}
						i++;
					}
				}
				if (i != dof) {
//printf("~~ got unexpected dof in svd\n");
					x->flags |= SPLX_FLAG_2F;	/* Failed */
					return 1;					/* Didn't get expected d.o.f. */
				}
			}
		}
		x->flags |= SPLX_FLAG_2;	/* Set flag so that it isn't attempted again */

//if (x->s->rev.cache->nunlocked == 0 && x->s->rev.sz > x->s->rev.max_sz)
//printf("~1 unable to decrease_revcache 2\n");

		/* keep memory in check */
		while (x->s->rev.cache->nunlocked > 0 && x->s->rev.sz > x->s->rev.max_sz) {
			if (decrease_revcache(x->s->rev.cache) == 0)
				break;
		}
	}
	return 0;
}

/* - - - - - -  */
/* Simplex solution info #4 */

/* Calculate the solution locus equation for this simplex and target */
/* (The direction was calculated by add_svd(), but now calculate */
/* the base solution point for this particular reverse lookup) */
/* Return non-zero if this point canot be calculated */
/* We are assuming that sdi > efdi */
static int
add_locus(
schbase *b,
simplex *x
) {
	int sdi = x->sdi; 
	int f, efdi = x->efdi; 
	int doback = 0;

#ifdef STATS
	x->s->rev.st[x->s->rev.sb->op].sinited4++;
#endif /* STATS */
	/* Use output of svdcmp() to solve overspecified and/or */
	/* singular equation A.x = b */

	/* Init the RHS B[] vector, and check if it doesn't match */
	/* that used to compute base value last time. */
	for (f = 0; f < efdi; f++) {
		double xb = b->v[f] - x->v[sdi][f];
		if (x->lo_xb[f] != xb) {
			x->lo_xb[f] = xb;
			doback = 1;			/* RHS differs, so re-compute */
		}
	}
	
#ifdef STATS
	if (doback && (x->flags & SPLX_FLAG_4))
		x->s->rev.st[x->s->rev.sb->op].sinited4i++;
#endif /* STATS */

	/* Compute locus */
	if (doback || !(x->flags & SPLX_FLAG_4))
		svdbacksub(x->d_u, x->d_w, x->d_v, x->lo_xb, x->lo_bd, efdi, sdi);
	
	x->flags |= SPLX_FLAG_4;

//if (x->s->rev.cache->nunlocked == 0 && x->s->rev.sz > x->s->rev.max_sz)
//printf("~1 unable to decrease_revcache 3\n");

	/* keep memory in check */
	while (x->s->rev.cache->nunlocked > 0 && x->s->rev.sz > x->s->rev.max_sz) {
		if (decrease_revcache(x->s->rev.cache) == 0)
			break;
	}

	return 0;
}

/* - - - - - -  */
/* Simplex solution info #5 */

/* Compute LU or SVD decomp of lo_l */
/* Allocates the memory for the various matricies */
/* Return non-zero if this canot be calculated. */
static int
add_auxil_lu_svd(
schbase *b,
simplex *x
) {
	int ee, sdi = x->sdi; 
	int f, efdi = x->efdi; 
	int dof = sdi-efdi;		/* Degree of freedom of locus */
	int naux = b->naux;		/* Number of auxiliaries actually available */

#ifdef STATS
	if (x->aaux != b->naux || x->auxbm != b->auxbm)
		x->s->rev.st[x->s->rev.sb->op].sinited5i++;
#endif /* STATS */

	if (x->aaux != b->naux) {	/* Number of auxiliaries has changed */
		if (x->aloc5 != NULL) {
			int asize;
			if (x->naux == dof)
				asize = sizeof(double *) * x->naux
			          + sizeof(double) * (x->naux * dof)
			          + sizeof(int) * dof;
			else
				asize = sizeof(double *) * (x->naux + dof) 
				      + sizeof(double) * (dof * (x->naux + dof + 1));
			free(x->aloc5);
			x->aloc5 = NULL;
			DECSZ(x->s, asize);
		}
		x->flags &= ~(SPLX_FLAG_5 | SPLX_FLAG_5F);	/* Force recompute */
	}
	
	if (x->auxbm != b->auxbm) {	/* Different selection of auxiliaries */
		x->flags &= ~(SPLX_FLAG_5 | SPLX_FLAG_5F);	/* Force recompute */
	}

	if (x->flags & SPLX_FLAG_5F) {		/* Previously failed */
		return 1;
	}
	if (!(x->flags & SPLX_FLAG_5)) {
		int *icomb = x->psxi->icomb; /* abs -> simplex coordinate translation */

		if (x->aloc5 == NULL) {	/* Allocate space for matricies and arrays */
			/* Do this in one hit to minimise malloc overhead */
			if (naux == dof) {
				int i;
				char *mem;
				int asize = sizeof(double *) * naux
				          + sizeof(double) * (naux * dof)
				          + sizeof(int) * dof;

				if ((x->aloc5 = mem = (char *) rev_malloc(x->s, asize)) == NULL)
					error("rspl malloc failed - fxcell sub-simplex matricies");
				INCSZ(x->s, asize);

				/* Allocate biggest to smallest (double, pointers, ints) */
				/* to make sure that items lie on the natural boundaries. */

				/* Reserve matrix doubles */
				mem += naux * dof * sizeof(double);

				/* Allocate pointers and ints */
				x->d_u = (double **)mem, mem += naux * sizeof(double *);
				x->d_w = (double *)mem, mem += dof * sizeof(int);

#ifdef DEBUG
				if (mem != (x->aloc5 + asize))
					error("aloc5a assert failed! Is %d, should be %d\n",mem - x->aloc5,asize);
#endif /* DEBUG */

				/* Reset and allocate matrix doubles */
				mem = x->aloc5;
				for (i = 0; i < naux; i++)
					x->d_u[i] = (double *)mem,	mem += dof * sizeof(double);
			} else {
				int i;
				char *mem;
				int asize = sizeof(double *) * (naux + dof) 
				          + sizeof(double) * (dof * (naux + dof + 1));

				if ((x->aloc5 = mem = (char *) rev_malloc(x->s, asize)) == NULL)
					error("rspl malloc failed - fxcell sub-simplex matricies");
				INCSZ(x->s, asize);

				/* Allocate biggest to smallest (double, pointers, ints) */
				/* to make sure that items lie on the natural boundaries. */

				/* Reserve matrix doubles */
				mem += dof * (naux + dof) * sizeof(double);

				/* Allocate doubles */
				x->ax_w = (double *)mem, mem += dof * sizeof(double);

				/* Allocate pointers, ints */
				x->ax_u = (double **)mem, mem += naux * sizeof(double *);
				x->ax_v = (double **)mem, mem += dof * sizeof(double *);

#ifdef DEBUG
				if (mem != (x->aloc5 + asize))
					error("aloc5b assert failed! Is %d, should be %d\n",mem - x->aloc5,asize);
#endif /* DEBUG */

				/* Reset and allocate matrix doubles */
				mem = x->aloc5;
				for (i = 0; i < naux; i++)
					x->ax_u[i] = (double *)mem,	mem += dof * sizeof(double);
				for (i = 0; i < dof; i++)
					x->ax_v[i] = (double *)mem, mem += dof * sizeof(double);
			}
			x->aaux = naux;				/* Number of auxiliaries allocated for */
		}
	
		/* Setup A[][] matrix to decompose, and figure number of auxiliaries actually needed */
		for (ee = naux = 0; ee < b->naux; ee++) {
			int ei = icomb[b->auxi[ee]];		/* Simplex relative auxiliary index */
			if (ei < 0)
				continue;		/* aux corresponds with fixed input value for this simplex */
			for (f = 0; f < dof; f++)
				x->ax_u[naux][f] = x->lo_l[ei][f];
			naux++;
		}
		x->naux = naux;					/* Number of auxiliaries actually available */
		x->auxbm = b->auxbm;			/* Mask of auxiliaries used */

		if (naux == dof) {				/* Use LU decomp to solve exactly */
			double rip;

#ifdef STATS
			x->s->rev.st[x->s->rev.sb->op].sinited5a++;
#endif /* STATS */
			if (lu_decomp(x->ax_u, dof, (int *)x->ax_w, &rip)) {
				x->flags |= SPLX_FLAG_5F;
				return 1;
			}

		} else if (naux > 0) {			/* Use SVD to solve least squares */

#ifdef STATS
			x->s->rev.st[x->s->rev.sb->op].sinited5b++;
#endif /* STATS */
			if (svdecomp(x->ax_u, x->ax_w, x->ax_v, naux, dof)) {
				x->flags |= SPLX_FLAG_5F;
				return 1;
			}
	
			/* Threshold the singular values W[] */ 
			svdthresh(x->ax_w, dof);
		} /* else naux == 0, don't setup anything */

		x->flags |= SPLX_FLAG_5;

//if (x->s->rev.cache->nunlocked == 0 && x->s->rev.sz > x->s->rev.max_sz)
//printf("~1 unable to decrease_revcache 4\n");

		/* keep memory in check */
		while (x->s->rev.cache->nunlocked > 0 && x->s->rev.sz > x->s->rev.max_sz) {
			if (decrease_revcache(x->s->rev.cache) == 0)
				break;
		}
	}
	return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* Initialise a static sub-simplex verticy information table */
void
rspl_init_ssimplex_info(
rspl *s,
ssxinfo *xip,				/* Pointer to sub-simplex info structure to init. */
int sdi						/* Sub-simplex dimensionality (range 0 - di) */
) {
	int e, di = s->di;		/* Dimensionality */
	int vi, nospx;			/* Number of sub-simplexes */
	XCOMBO(vcmb, MXDI, sdi+1, 1 << di);/* Simplex dimension sdi out of cube dimension di counter */

	DBG(("init_ssimplex_info called with sdi = %d\n",sdi));
	/* First count the number of sub-simplexes */
	nospx = 0;
	XCB_INIT(vcmb);
	while (!XCB_DONE(vcmb)) {
		nospx++;
		XCB_INC(vcmb);
	}

	xip->sdi = sdi;
	xip->nospx = nospx;
	if ((xip->spxi = (psxinfo *) rev_calloc(s, nospx, sizeof(psxinfo))) == NULL)
		error("rspl malloc failed - fxcell sub-simplex info array");
	INCSZ(s, nospx * sizeof(psxinfo));
	
	DBG(("Number of subsimplex = %d\n",nospx));
	/* For all sub-simplexes */
	XCB_INIT(vcmb);
	for (vi = 0; vi < nospx; vi++) {
		psxinfo *x = &xip->spxi[vi];
		int i;
		int andm, orm;
		int pmin[MXRI], pmax[MXRI];

		/* XCOMB generates vertices in order from max to min offset */

		/* Compute Absolute -> Parameter mapping */
		for (e = 0; e < di; e++) {				/* For each absolute axis */

			if ((vcmb[sdi] & (1<<e)) != 0) {
				x->icomb[e] = -2;	/* This abs is always '1' */

			} else if ((vcmb[0] & (1<<e)) == 0) {
				x->icomb[e] = -1;	/* This abs is always '0' */

			} else {
				for (i = 0; i < sdi; i++) {	/* For each verticy in large to small order (!first) */
					if ((vcmb[i]   & (1<<e)) != 0 && 
					    (vcmb[i+1] & (1<<e)) == 0) {/* Transition from offset 1 to 0 */
						x->icomb[e] = i;	/* This is parameter */
						break;
					}
				}
			}
		}
		
		/* Compute fwd grid offsets for each simplex vertex in baricentric order */
		for (i = 0; i <= sdi; i++) {	/* For each verticy */
			x->offs[i]  = vcmb[i];
			x->goffs[i] = s->g.hi[vcmb[i]];
			x->foffs[i] = s->g.fhi[vcmb[i]];

			/* Setup input coordinate bounding box value offsets */
			if (i == 0) {								/* Init to first vertex of simplex */
				for (e = 0; e < di; e++) {				/* Input space */
					x->pmino[e] = x->pmaxo[e] = vcmb[i];
					pmin[e] = pmax[e] = vcmb[i] & (1<<e);
				}
			} else {
				for (e = 0; e < di; e++) {			/* Input space */
					int vv = vcmb[i] & (1<<e);
					if (vv < pmin[e]) {				/* Adjust min/max offsets */
						x->pmino[e] = vcmb[i];
						pmin[e] = vv;
					} else if (vv > pmax[e]) {
						x->pmaxo[e] = vcmb[i];
						pmax[e] = vv;
					}
				}
			}
		}

		/* See if the sub-simplex lies on a cube face */
		andm = ~0;
		orm = 0;
		for (i = 0; i <= sdi; i++) {	/* For each verticy */
			andm &= vcmb[i];
			orm  |= vcmb[i];
		}
		/* If one coordinate is common (all 0 or all 1) to the vertices, */
		/* they must all be on the same cube face. */
		if (andm != 0 || orm != ((1 << di)-1))
			x->face = 1;
		else
			x->face = 0;

#ifdef DEBUG
		printf("Vertices   = ");
		for (i = 0; i <= sdi; i++)
			printf("%d ",vcmb[i]);
		printf("\n");
		
		printf("Face        = %s\n",x->face ? "True" : "False");
		
		printf("Abs -> Parm = ");
		for (e = 0; e < di; e++)
			printf("%d ",x->icomb[e]);
		printf("\n");
		
		printf("Grid Offset      = ");
		for (e = 0; e <= sdi; e++)
			printf("%d ",x->goffs[e]);
		printf("Float Offset      = ");
		for (e = 0; e <= sdi; e++)
			printf("%d ",x->foffs[e]);
		printf("\n");
		printf("\n");
#endif /* DEBUG */

		/* Increment the counter value */
		XCB_INC(vcmb);
	}
}

/* Free the given sub-simplex verticy information */
void
rspl_free_ssimplex_info(
rspl *s,
ssxinfo *xip		/* Pointer to sub-simplex info structure */
) {
	if (xip == NULL)	/* Assert */
		return;

	free(xip->spxi);
	DECSZ(s, xip->nospx * sizeof(psxinfo));
	xip->spxi = NULL;
}

/* ====================================================== */
/* Reverse cell cache code                                */

/* Allocate and initialise the fxcell cache */
static revcache *
alloc_revcache(
rspl *s
) {
	revcache *rc;

	DBG(("alloc_revcache called\n"));
	if ((rc = (revcache *) rev_calloc(s, 1, sizeof(revcache))) == NULL)
		error("rspl malloc failed - fxcell cache");
	INCSZ(s, sizeof(revcache));
	
	rc->s = s;		/* For stats */

	/* Allocate an initial cell hash index */
	rc->cell_hash_size = primes[0];

	if ((rc->hashtop = (fxcell **) rev_calloc(s, rc->cell_hash_size, sizeof(fxcell *))) == NULL)
		error("rspl malloc failed - fxcell cache index");
	INCSZ(s, rc->cell_hash_size * sizeof(fxcell *));

	/* Allocate an initial simplex face match hash index */
	rc->spx_hash_size = primes[0];

	if ((rc->spxhashtop = (simplex **) rev_calloc(s, rc->spx_hash_size, sizeof(simplex *))) == NULL)
		error("rspl malloc failed - reverse simplex cache index");
	INCSZ(s, rc->spx_hash_size * sizeof(simplex *));

	return rc;
}

/* Free the fxcell cache */
static void
free_revcache(revcache *rc) {
	int i;
	fxcell *cp, *ncp;

	/* Free any stuff allocated in the cell contents, and the cell itself. */
	for (cp = rc->mrubot; cp != NULL; cp = ncp) {
		ncp = cp->mruup;
		free_cell_contents(cp);
		free(cp);
		DECSZ(rc->s, sizeof(fxcell));
	}

	/* Free the hash indexes */
	free(rc->hashtop);
	DECSZ(rc->s, rc->cell_hash_size * sizeof(fxcell *));
	free(rc->spxhashtop);
	DECSZ(rc->s, rc->spx_hash_size * sizeof(simplex *));

	DECSZ(rc->s, sizeof(revcache));
	free(rc);
}

/* Invalidate the whole cache */
static void
invalidate_revcache(
revcache *rc)
{
	int i;
	fxcell *cp;

	rc->nunlocked = 0;

	/* Free any stuff allocated in the cell contents */
	for (cp = rc->mrubot; cp != NULL; cp = cp->mruup) {
		free_cell_contents(cp);
		cp->refcount = 0;		/* Make sure they can now be reused */
		cp->ix = 0;
		cp->flags = 0;			/* Contents needs re-initializing */
		rc->nunlocked++;
	}

	/* Clear the hash table so they can't be hit */
	for (i = 0; i < rc->cell_hash_size; i++) {
		rc->hashtop[i] = NULL;
	}

}

#define HASH(xx, yy) ((yy) % xx->cell_hash_size)

/* Allocate another cell, and add it to the cache. */
/* This may re-size the hash index too. */
/* Return the pointer to the new cell. */
/* (Note it's not our job here to honour the memory limit) */
static fxcell *
increase_revcache(
revcache *rc
) {
	fxcell *nxcell;		/* Newly allocated fxcell */
	int i;

//	DBG(("Adding another cell to cache\n"));

#ifdef NEVER	/* We may be called with force != 0 */
	if (rc->s->rev.sz >= rc->s->rev.max_sz)
		return NULL;
#endif

	if ((nxcell = (fxcell *) rev_calloc(rc->s, 1, sizeof(fxcell))) == NULL)
		error("rspl malloc failed - reverse fxcells");
	INCSZ(rc->s, sizeof(fxcell));

	nxcell->s = rc->s;

	/* Add cell to the bottom of the cache mru linked list */
	if (rc->mrutop == NULL)					/* List was empty */
		rc->mrutop = nxcell;
	else {
		rc->mrubot->mrudown = nxcell;	/* Splice into bottom */
		nxcell->mruup = rc->mrubot;
	}
	rc->mrubot = nxcell;
	rc->nacells++;
	rc->nunlocked++;

//	DBG(("cache is now %d cells\n",rc->nacells));

	/* See if the hash index should be re-sized */
	if (rc->nacells > (HASH_FILL_RATIO * rc->cell_hash_size)) {
		for (i = 0; primes[i] > 0 && primes[i] <= rc->cell_hash_size; i++)
			;
		if (primes[i] > 0) {
			int cell_hash_size = rc->cell_hash_size;	/* Old */
			fxcell **hashtop = rc->hashtop;

			rc->cell_hash_size = primes[i];

			DBG(("Increasing cell cache hash index to %d\n",cell_hash_size));
			/* Allocate a new index */
			if ((rc->hashtop = (fxcell **) rev_calloc(rc->s, rc->cell_hash_size, sizeof(fxcell *))) == NULL)
				error("rspl malloc failed - fxcell cache index");
			INCSZ(rc->s, rc->cell_hash_size * sizeof(fxcell *));

			/* Transfer all the cells to the new index */
			for (i = 0; i < cell_hash_size; i++) {
				fxcell *c, *nc;
				for (c = hashtop[i]; c != NULL; c = nc) {
					int hash;
					nc = c->hlink;
					hash = HASH(rc, c->ix); 		/* New hash */
					c->hlink = rc->hashtop[hash];	/* Add to new hash index */
					rc->hashtop[hash] = c;
				}
			}

			/* Done with old index */
			free(hashtop);
			DECSZ(rc->s, cell_hash_size * sizeof(fxcell *));
		}
	}
	
	return nxcell;
}

/* Reduce the cache memory usage by freeing the least recently used unlocked cell. */
/* Return nz if we suceeeded in freeing some memory. */
static int decrease_revcache(
revcache *rc		/* Reverse cache structure */
) {
	int hit = 0;
	int hash;
	fxcell *cp;
	
	DBG(("Decreasing cell cache memory allocation by freeing a cell\n"));

	/* Use the least recently used unlocked fxcell */
	for (cp = rc->mrubot; cp != NULL && cp->refcount > 0; cp = cp->mruup)
		;

	/* Run out of unlocked cells */
	if (cp == NULL) {
		DBG(("Failed to find unlocked cell to free\n"));
//printf("~1 failed to decrease memory\n");
		return 0;
	}
	
	/* If it has been used before, free up the simplexes */
	free_cell_contents(cp);

	/* Remove from current hash index (if it is in it) */
	hash = HASH(rc,cp->ix);			/* Old hash */
	if (rc->hashtop[hash] == cp) {
		rc->hashtop[hash] = cp->hlink;
	} else {
		fxcell *c;
		for (c = rc->hashtop[hash]; c != NULL && c->hlink != cp; c = c->hlink)
			;
		if (c != NULL)
			c->hlink = cp->hlink;
	}

	/* Free up this cell - Remove it from LRU list */
	if (rc->mrutop == cp)
		rc->mrutop = cp->mrudown;
	if (rc->mrubot == cp)
		rc->mrubot = cp->mruup;
	if (cp->mruup != NULL)
		cp->mruup->mrudown = cp->mrudown;
	if (cp->mrudown != NULL)
		cp->mrudown->mruup = cp->mruup;
	cp->mruup = cp->mrudown = NULL;
	free(cp);
	DECSZ(rc->s, sizeof(fxcell));
	rc->nacells--;
	rc->nunlocked--;

	DBG(("Freed a rev fxcell\n"));
	return 1;
}

/* Return a pointer to an appropriate fxcell */
/* cache structure. cell->flags will be 0 if the fxcell */
/* has been reallocated. cell contents will be 0 if */
/* never used before. */
/* The cell reference count is incremented, so that it */
/* can't be thrown out of the cache. The cell must be */
/* released with uncache_fxcell() when it's no longer needed. */
/* return NULL if we ran out of room in the cache */
static fxcell *cache_fxcell(
revcache *rc,		/* Reverse cache structure */
int ix,				/* fwd index of cell */
int force			/* if nz, force memory allocation, so that we have at least one fxcell */
) {
	int hit = 0;
	int hash;
	fxcell *cp;
	
	/* keep memory in check - fail if we're out of memory and can't free any */
	/* (Doesn't matter if it might be a hit, it will get picked up the next time) */
	if (!force && rc->s->rev.sz > rc->s->rev.max_sz && rc->nunlocked <= 0) {
		return NULL;
	}

//if (rc->nunlocked == 0 && rc->s->rev.sz > rc->s->rev.max_sz)
//printf("~1 unable to decrease_revcache 5\n");

	/* Free up memory to get below threshold */
	while (rc->nunlocked > 0 && rc->s->rev.sz > rc->s->rev.max_sz) {
		if (decrease_revcache(rc) == 0)
			break;
	}

	hash = HASH(rc,ix);		/* Compute hash of fwd cell index */

	/* See if we get a cache hit */
	for (cp = rc->hashtop[hash]; cp != NULL; cp = cp->hlink) {
		if (ix == cp->ix) {	/* Hit */
			hit = 1;
#ifdef STATS
			rc->s->rev.st[rc->s->rev.sb->op].chits++;
#endif /* STATS */
			break;
		}
	}
	if (!hit) {			/* No hit, use new cell or the least recently used fxcell */
		int ohash;

		/* If we haven't used all our memory, or if we are forced and have */
		/* no cell we can re-use, then allocate another fxcell */
		if (rc->s->rev.sz < rc->s->rev.max_sz
		 || (force && rc->nunlocked == 0)) {
			cp = increase_revcache(rc);
			hash = HASH(rc,ix);			/* Re-compute hash in case hash size changed */
//printf("~1 using new cell\n");
		} else {
//printf("~1 memory limit has been reached, using old cell\n");

			for (;;) {
				/* Use the least recently used unlocked fxcell */
				for (cp = rc->mrubot; cp != NULL && cp->refcount > 0; cp = cp->mruup)
					;
	
				/* Run out of unlocked cells */
				if (cp == NULL) {
//printf("~1 none available\n");
					return NULL;
				}
	
				/* If it has been used before, free up the simplexes */
				free_cell_contents(cp);

				/* Remove from current hash index (if it is in it) */
				ohash = HASH(rc,cp->ix);			/* Old hash */
				if (rc->hashtop[ohash] == cp) {
					rc->hashtop[ohash] = cp->hlink;
				} else {
					fxcell *c;
					for (c = rc->hashtop[ohash]; c != NULL && c->hlink != cp; c = c->hlink)
						;
					if (c != NULL)
						c->hlink = cp->hlink;
				}

				/* If we're now under the memory limit, use this fxcell */
				if (rc->s->rev.sz < rc->s->rev.max_sz) {
					break;
				}

//printf("~1 freeing a cell\n");
				/* Free up this cell and look for another one */
				/* Remove it from LRU list */
				if (rc->mrutop == cp)
					rc->mrutop = cp->mrudown;
				if (rc->mrubot == cp)
					rc->mrubot = cp->mruup;
				if (cp->mruup != NULL)
					cp->mruup->mrudown = cp->mrudown;
				if (cp->mrudown != NULL)
					cp->mrudown->mruup = cp->mruup;
				cp->mruup = cp->mrudown = NULL;
				free(cp);
				DECSZ(rc->s, sizeof(fxcell));
				rc->nacells--;
				rc->nunlocked--;
			}
		}

#ifdef STATS
		rc->s->rev.st[rc->s->rev.sb->op].cmiss++;
#endif /* STATS */

		/* Add this cell to hash index */
		cp->hlink = rc->hashtop[hash];
		rc->hashtop[hash] = cp;	/* Add to hash table and list */

		cp->ix = ix;
		cp->flags = 0;			/* Contents needs re-initializing */
//printf("~1 returning fresh cell\n");
	}
	
	/* Move slected cell to the top of the mru list */
	if (cp->mruup != NULL) {		/* This one wasn't already at top */
		cp->mruup->mrudown = cp->mrudown;
		if (cp->mrudown == NULL)	/* This was bottom */
			rc->mrubot = cp->mruup;	/* New bottom */
		else
			cp->mrudown->mruup = cp->mruup;
		/* Put this one at the top */
		rc->mrutop->mruup = cp;
		cp->mrudown = rc->mrutop;
		rc->mrutop = cp;
		cp->mruup = NULL;
	}
	if (cp->refcount == 0) {
		rc->nunlocked--;
	}

	cp->refcount++;

	return cp;
}

/* Tell the cache that we aren't using this cell anymore, */
/* but to keep it in case it is needed again. */
static void uncache_fxcell(
revcache *rc,		/* Reverse cache structure */
fxcell *cp
) {
	if (cp->refcount > 0) {
		cp->refcount--;
		if (cp->refcount == 0) {
			rc->nunlocked++;
		}
	} else
		warning("rspl cell cache assert: refcount overdecremented!");
}

/* ====================================================== */
/* Reverse rspl setup functions                           */

static void del_bxcell(rspl *s, bxcell *bx);
static void free_sharelist(rspl *s);
static void free_indexlist(rspl *s, int **rp);
static void free_surfhash(rspl *s, int del);
static void free_surflist(rspl *s);

/* Called by rspl initialisation */
/* Note that fxcell lookup tables are not */
/* allocated & created until the first call */
/* to a reverse interpolation function. */
void
init_rev(rspl *s) {

	/* First section */
	s->rev.inited = 0;
	s->rev.res = 0;
	s->rev.no = 0;
	s->rev.rev = NULL;

	/* Second section */
	s->rev.rev_valid = 0;
	s->rev.nnrev = NULL;

	/* Third section */
	s->rev.cache = NULL;

	/* Fourth section */
	s->rev.sb = NULL;

	/* Methods */
	s->rev_set_limit   = rev_set_limit_rspl;
	s->rev_get_limit   = rev_get_limit_rspl;
	s->rev_set_lchw    = rev_set_lchw;
	s->rev_interp      = rev_interp_rspl;
	s->rev_locus       = rev_locus_rspl;
	s->rev_locus_segs  = rev_locus_segs_rspl;
}

/* Free up all the reverse interpolation info */
void free_rev(
rspl *s		/* Pointer to rspl grid */
) {
	int e, di = s->di;
	int **rpp, *rp;
		
#ifdef STATS
	{
		int i, totcalls = 0;
		for (i = 0; i < 5; i++) {
			totcalls += s->rev.st[i].searchcalls;
		}

		printf("\n===============================\n");
		printf("di = %d, do = %d\n",s->di, s->fdi);
		for (i = 0; i < 5; i++) {
			int calls = s->rev.st[i].searchcalls;
			if (calls == 0) 
				continue;
			printf("\n- - - - - - - - - - - - - - - -\n");
			printf("Operation %s\n",opnames[i]);
			printf("Search calls = %d = %f%%\n",s->rev.st[i].searchcalls,
			100.0 * s->rev.st[i].searchcalls/totcalls);
			printf("Cells searched/call = %f\n",s->rev.st[i].csearched/(double)calls);
			printf("Simplexes searched/call = %f\n",s->rev.st[i].ssearched/(double)calls);
			printf("Simplexes inited level 1/call = %f\n",s->rev.st[i].sinited/(double)calls);
			printf("Simplexes inited level 2 (LU)/call = %f\n",s->rev.st[i].sinited2a/(double)calls);
			printf("Simplexes inited level 2 (SVD)/call = %f\n",s->rev.st[i].sinited2b/(double)calls);
			printf("Simplexes invalidated level 4/call = %f\n",s->rev.st[i].sinited4i/(double)calls);
			printf("Simplexes inited level 4/call = %f\n",s->rev.st[i].sinited4/(double)calls);
			printf("Simplexes invalidated level 5/call = %f\n",s->rev.st[i].sinited5i/(double)calls);
			printf("Simplexes inited level 5 (LU)/call = %f\n",s->rev.st[i].sinited5a/(double)calls);
			printf("Simplexes inited level 5 (SVD)/call = %f\n",s->rev.st[i].sinited5b/(double)calls);
			if ((s->rev.st[i].chits + s->rev.st[i].cmiss) == 0)
				printf("No cache calls\n");
			else
				printf("Cell hit rate = %f%%\n",
					100.0 * s->rev.st[i].chits/(double)(s->rev.st[i].chits + s->rev.st[i].cmiss));
		}
		printf("\n===============================\n");
	}
#endif /* STATS */

	/* Free up Fourth section */
	if (s->rev.sb != NULL) {
		free_search(s->rev.sb);
		s->rev.sb = NULL;
	}
	/* Free up the Third section */
	if (s->rev.cache != NULL) {
		free_revcache(s->rev.cache);	/* Reverse cell cache */
		s->rev.cache = NULL;
	}

	/* Free up the Second section */
	if (s->rev.nnrev != NULL) {

		/* Free up nn list sharelist records - this will free and set */
		/* any shared lists to NULL */
		free_sharelist(s);

		/* Free any remaining arrays at grid points */
		for (rpp = s->rev.nnrev; rpp < (s->rev.nnrev + s->rev.no); rpp++) {
			if (*rpp != NULL)
				free_indexlist(s, rpp);
		}
		free(s->rev.nnrev);
		DECSZ(s, s->rev.no * sizeof(int *));
		s->rev.nnrev = NULL;
	}

	if (di > 1 && s->rev.rev_valid) {
		rev_struct *rsi, **rsp;
		size_t ram_portion = g_avail_ram;

		/* Remove it from the linked list */
		for (rsp = &g_rev_instances; *rsp != NULL; rsp = &((*rsp)->next)) {
			if (*rsp == &s->rev) {
				*rsp = (*rsp)->next;
				break;
			}
		}

		/* Aportion the memory */
		g_no_rev_cache_instances--;

		if (g_no_rev_cache_instances > 0) {
			ram_portion /= g_no_rev_cache_instances; 
			for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next)
				rsi->max_sz = ram_portion;
			if (s->verbose)
				fprintf(stdout, "%cThere %s %d rev cache instance%s with %lu Mbytes limit\n",
				                cr_char,
								g_no_rev_cache_instances > 1 ? "are" : "is",
			                    g_no_rev_cache_instances,
								g_no_rev_cache_instances > 1 ? "s" : "",
			                    (unsigned long)(ram_portion/1000000));
		}
	}

	s->rev.rev_valid = 0;

	if (s->rev.rev != NULL) {
		/* Free arrays at grid points */
		for (rpp = s->rev.rev; rpp < (s->rev.rev + s->rev.no); rpp++) {
			if (*rpp != NULL)
				free_indexlist(s, rpp);
		}
		free(s->rev.rev);
		DECSZ(s, s->rev.no * sizeof(int *));
		s->rev.rev = NULL;
	}

	/* If first section has been initialised */
	if (s->rev.inited != 0)	 {

		/* Sub-simplex information */
		for (e = 0; e <= di; e++) {
			rspl_free_ssimplex_info(s, &s->rev.sspxi[e]);
		}
		s->rev.res = 0;
		s->rev.no = 0;
		s->rev.inited = 0;
	}

	/* Free up surface linked list and the bxcells in it. */
	free_surflist(s);

	/* Free up surface bxcell hash index */
	free_surfhash(s, 0);

	DBG(("rev allocation left after free = %d bytes\n",s->rev.sz));

#ifdef CHECK_NNLU
	print_nnck(s);
#endif /* CHECK_NNLU */
}


/* ========================================================== */
/* reverse lookup acceleration structure initialisation code. */

/* The reverse lookup relies on a search of the fwd interpolation tables.
   To eliminate out of gamut points quickly, to provide a starting point for
   the search, and to guarantee that all possible reverse solutions are discovered,
   a spatial indexing structure is used to provide a list of starting candidate
   forward cell indexes for a given output value. (rev.rev[])
   The reverse structure contains two fdi dimensional bwd cell grids, each element of the
   cell grid holding the indexes of the forward interpolation grid.
   The rev[] grid holds fwd cell indexes which intersect that bwd cell's range of
   output values. A rev[] cell will be empty if there is no potential exact solution.
   The nnrev[] grid holds fwd cell indexes of those cells that may be the lch weighted
   closest to that bwd cell.
   The rev.nnrev[] array is almost a complement of the rev.rev[] array,
   with the exception of any overlap near the gamut surface.
   Since many of the nnrev[] bwd cells map to nearly the same surface region, many
   of the fwd cell lists are shared.

   When s->rev.fastsetup is set, then the rev.nnrev[] grid is left empty, and
   any call for nn lookup is satisfied by filling the requisite rev.nnrev[] on-demand,
   by an exaustive search of the surface bwd cells (rev.surflist)

   Note that unlike the forward grid which is composed of vertices,
   these rev lists are composed of fwd cells.

   The nnrev[] setup code identifies possible surface bwd revp[] cells
   by them being face neighbors of empty (out of gamut) bwd cells.
   It then converts the vertexes of the fwd cell list into a vertex list,
   and "thins" the list by deleting any vertex that is shaded by a triangle
   that other vertexes are part of. This is done on a backward cell basis,
   but includes vertexes of other possibly shadowed backward cells. 

   If ink limiting is being used, then over ink limit partners to
   the vertexes are added in, and then the list of vertexes is
   converted back into fwd cells in a way that ensures 2 dimensional
   connectivity of the cells, while minimizing the number of
   extra (non surface) vertexes implied by the fwd cells.

 */

/*
	The gamut hull fwcell finding code is not robust - it assumes visiblity
	of the surface from some center point(s).

	Perfect gamut hull finding approach would be something like this:
	(using vertex and triangle caching structures.)

	Add all triangles on device gamut surface with at least
	one vertex within ink limit.

	Add all triangles that are part of a full di simplex
	with at least one vertex within ink limit (and not on device gamut),
	where all the other vertices of the simplex are on one
	side of the triangle (non-monotonic surfaces).

	Add all triangles on the ink limit plane.
	(Will be 1 or more triangles per simplex that has
	1..di vertices that are over the ink limit.)

	Check all triangles for instersection with each other.
	Convert any such intersections into smaller, non-intersecting
	triangles that share vertices along intersection line.

	Delete triangles that have dangling edges (i.e. triangles that
	have edges with odd number of associated triangles).
	This is to eliminate "dangling" triangles. Should only be left
	with "bubles" in surface after this ?

	Bubbles join at edges where more than 2 triangles co-incide.
	Can internal bubles be "un-stitched" if we can decide which
	triangles are part of a bubble ????
	i.e. use even/odd inside rule for points between
	the triangles at the edge.
	
	Delete all vertexes and associated triangles that are
	inside the surface.
	Will odd/even test work ? - i.e. from vertex of triangle,
	is on surface if intersections in one direction are even, and
	other direction are odd. 

	Or "point within odd number of tetrahedrons formed with point on surface" ?	
	- seems to be the same as the odd/even rule. Can't detect connectivity.
		
	Or do this using a winding number algorithm
	with signed crossings optimization ?
	<Point in Polyhedron Testing Using Spherical Polygons, Graphics Gems V pp42>
    But do we have to order triangles in a consistent direction ?
  	How to do this when more than 2 triangles meet at an adge ???
	i.e. catch-22 - need to know which are inside triangles to
	set edge direction, but need edge direction to detect inside-outside.

*/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
#if defined(REVTABLESTATS) || defined(DEBUG)
static int bxcount = 0;
static int maxbxcount = 0;
#endif

static void add2indexlist(rspl *s, int **rpp, int ix, int shrec);
static void comp_shadow_group(rspl *s, double *gcent, double *rgc, double *pcc,
	double *pdw, double *gc, double (*v)[MXRO], int nverta);

/* Allocate a new bx cell. */
/* (Doesn't add to hash or list) */
static bxcell *new_bxcell(
	rspl *s,
	int ix,				/* rev[] index of cell being created */
	int *gc,			/* Coord of rev[] cell being created */
	bxcell *ss,			/* search starting bxcell to commence with, this cell if NULL */
	double sdist,		/* Est. distance from this cell to six */
	char *vflag			/* If non-NULL, create a super-cell if far from seed */
) {
	int f, fdi = s->fdi;
	int i;
	bxcell *bx = NULL;
	DCOUNT(cc, MXRO, fdi, 0, 0, 2);		/* Vertex counter */

//printf("~1 creating new bxcell with index %d\n",ix);
	if ((bx = (bxcell *) rev_calloc(s, 1, sizeof(bxcell))) == NULL)
		error("rspl malloc failed - rev bxcell structs");
	INCSZ(s, sizeof(bxcell));

	bx->ix = ix;
	bx->tix = -1;
	for (f = 0; f < fdi; f++)
		bx->gc[f] = gc[f];
	bx->ss = (ss == NULL) ? bx : ss;
	bx->sdist = sdist;

//printf("~1 new_bxcell ix %d, co %s, base %s\n",ix,debPiv(s->fdi, bx->gc),debPdv(s->fdi, vp[0]));

	/* super-cell code (to speed filling) */
	if (vflag != NULL && (vflag[ix] & 2) == 0 && ss != NULL) {
		double codist = 0.0;

		/* Compute distance of seed from this cell */
		for (codist = 0.0, f = 0; f < fdi; f++) {
			int tt = bx->gc[f] - ss->gc[f];
			codist += tt * tt;
		}
		codist = sqrt(codist);

//printf("~1 codist %f, codist/s->rev.res = %f\n",codist,codist/s->rev.res);
		/* Create a super-cell if we are far enough from the seed. */
		/* (this determines what portion of filling uses super-cells) */
//		if (codist >= 1.0 && (codist/s->rev.res) > 0.05)
		if (codist >= 2.0)
		{
			int co[MXRO]; 
			DCOUNT(ss, MXRO, s->fdi, -1, -1, 2);
			double (*vp)[MXRO];
			double **vpp;
			int nverts;
//printf("~1 creating super-cell for bx %d\n",ix);

			/* Maximum number of vertices for all surrounders */
			for (nverts = (1 << fdi), f = 0; f < fdi; f++)
				nverts *= 3;

			if ((vp = (double(*)[MXRO]) rev_calloc(s, nverts, sizeof(double) * MXRO)) == NULL)
				error("rspl malloc failed - rev bxcell vertex list");
			INCSZ(s, nverts * sizeof(double) * MXRO);
	
			if ((vpp = (double **) rev_calloc(s, nverts, sizeof(double *))) == NULL)
				error("rspl malloc failed - rev bxcell vertex list");
			INCSZ(s, nverts * sizeof(double *));
	
			/* Search around this cell for other cells to be filled */
			i = 0;
			DC_INIT(ss);
			while (!DC_DONE(ss)) {
				int nix = ix;
				for (f = 0; f < fdi; f++) {
					nix += ss[f] * s->rev.coi[f];
					co[f] = bx->gc[f] + ss[f];
					if (co[f] < 0 || co[f] >= s->rev.res)
						break;
				}

				/* If within boundary and un-filled non-surface bxcell */
				if (f >= fdi && (vflag[nix] & 0xf) == 0) {
					add2indexlist(s, &bx->scell, nix, 0);
					vflag[nix] = (vflag[nix] & ~0xf) | 1;	/* Assume it's now on the seed list */

					/* Create vertex locations for this bxcell */
					DC_INIT(cc);
					while (!DC_DONE(cc)) {
						for (f = 0; f < fdi; f++)
							vp[i][f] = (co[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
						vpp[i] = vp[i];
						DC_INC(cc);
						i++;
					}
				}
				DC_INC(ss);
			}

			/* Init the group boundary data */
			nn_grpinit(s, &bx->g, vpp, i, NULL);
	
			/* Compute the default shadowing test width and distance */
			/* (Not actually used for super-cell ?) */
			comp_shadow_group(s, s->rev.ocent, NULL, &bx->cc, &bx->dw, bx->g.bcent, vp, i);   

			free(vpp);
			DECSZ(s, nverts * sizeof(double *));

			free(vp);
			DECSZ(s, nverts * sizeof(double) * MXRO);
//printf(" - %d sub-cells\n",bx->scell[1]-3);
		}
	}

	if (bx->scell == NULL) {
		double vp[POW2MXRO][MXRO];
		double *vpp[POW2MXRO];

		/* Create vertex locations for this bxcell */
		i = 0;
		DC_INIT(cc);
		while (!DC_DONE(cc)) {
			for (f = 0; f < fdi; f++)
				vp[i][f] = (gc[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
			vpp[i] = vp[i];
			DC_INC(cc);
			i++;
		}

		/* Init the group boundary data */
		nn_grpinit(s, &bx->g, vpp, i, NULL);

		/* Compute the default shadowing test width and distance */
		comp_shadow_group(s, s->rev.ocent, NULL, &bx->cc, &bx->dw, bx->g.bcent, vp, 1 << fdi);   
	}

//printf("~1 grp bcent %s, brad %f\n",debPdv(s->fdi, bx->g.bcent), bx->g.brad);
#if defined(REVTABLESTATS) || defined(DEBUG)
	bxcount++;
	if (bxcount > maxbxcount)
		maxbxcount = bxcount;
//printf("~1 now %d bxcells\n",bxcount);
#endif
	return bx;
}

/* Free a bxcell (up to caller to free bx->sl, remove from cache etc.) */
/* We free the super-cell info. */
static void del_bxcell(rspl *s, bxcell *bx) {
	if (bx->scell != NULL)	/* If this is a supercell */
		free_indexlist(s, &bx->scell);
	if (bx->dl != NULL)		/* We have a deleted fwd vertex list */
		free_indexlist(s, &bx->dl);
	free(bx);
	DECSZ(s, sizeof(bxcell));
#if defined(REVTABLESTATS) || defined(DEBUG)
	bxcount--;
//printf("~1 now %d bxcells\n",--bxcount);
#endif
}

/* Allocate the surflist hash index */
static void create_surfhash(rspl *s) { 

	s->rev.surf_hash_size = primes[2];		/* 1489 */
	if ((s->rev.surfhash = (bxcell **) rev_calloc(s, s->rev.surf_hash_size, sizeof(bxcell *))) == NULL)
		error("rspl malloc failed - reverse bxcell surface cache index");
	INCSZ(s, s->rev.surf_hash_size * sizeof(bxcell *));
}

/* Add a bxcell to the surface hash list */
static void add_bxcell_hash(rspl *s, bxcell *bx) {
	unsigned int hash = 0;

	hash = bx->ix % s->rev.surf_hash_size;
	bx->hlink = s->rev.surfhash[hash];
	s->rev.surfhash[hash] = bx;
}

/* Remove a bxcell from the surface hash list. */
/* Doesn't delete the bxcell though. */
static void rem_bxcell_hash(rspl *s, int ix) {
	unsigned int hash = 0;
	bxcell *bx = NULL, **pbx;

	hash = ix % s->rev.surf_hash_size;

	for (pbx = &s->rev.surfhash[hash], bx = *pbx; bx != NULL; pbx = &bx->hlink, bx = *pbx) {
		if (bx->ix == ix) {
			*pbx = bx->hlink;
			return;
		}
	}
}

/* Fetch a surface bxcell from the surface hash list, given its index, or */ 
/* Return NULL if none */
static bxcell *get_surface_bxcell(rspl *s, int ix) {
	unsigned int hash = 0;
	bxcell *bx = NULL;

	hash = ix % s->rev.surf_hash_size;

	for (bx = s->rev.surfhash[hash]; bx != NULL; bx = bx->hlink) {
		if (bx->ix == ix)
			return bx;
	}
	return NULL;
}

/* Free up surface linked list and delete the bxcells. */
/* (If we use this, don't use free_surfhash with del set.) */
static void free_surflist(rspl *s) {

	while (s->rev.surflist != NULL) {
		bxcell *this = s->rev.surflist;
		s->rev.surflist = s->rev.surflist->slist;
		if (this->sl != NULL)
			free_indexlist(s, &this->sl);
		del_bxcell(s, this);
	}
}


/* If del is set, free up all the bxcell cells in the hash index, */
/* then free the surfhash itself. */
/* (Use instead of surflist to manage allocation, */
/*  or to clean up hashlist after surflist has been freed.) */
static void free_surfhash(rspl *s, int del) {

	if (s->rev.surfhash != NULL) {
		if (del) {
			int i;
			for (i = 0; i < s->rev.surf_hash_size; i++) {
				bxcell *bx, *nbx;
				for (bx = s->rev.surfhash[i]; bx != NULL; bx = nbx) {
					nbx = bx->hlink;
					if (bx->sl != NULL)
						free_indexlist(s, &bx->sl);
					del_bxcell(s, bx);
				}
			}
		}
		free(s->rev.surfhash);
		DECSZ(s, s->rev.surf_hash_size * sizeof(bxcell *));
		s->rev.surfhash = NULL;
		s->rev.surf_hash_size = 0;
	}
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Structure to cache prime vertex information when filtering surface */
/* cell vertex lists. */

/* vertex status */
typedef enum {
	vtx_norm = 0,		/* Normal vertex in primary bxcell - initial value */
	vtx_sha  = 1,		/* Vertex has been shadowed */
	vtx_del  = 2,		/* Vertex has been deleted because it's shadowed */
	vtx_oil  = 3		/* Vertex is over ink limit */
} vstat;

struct _vtxrec {
	int ix;						/* fwd index of vertex */
	int cix;					/* Fwd cell vertex is in index */
	double vv[MXRO];			/* Output value of vertex */
	double vl[MXRO];			/* Log compressed output value of vertex */
	double dist;				/* Distance from center point squared */
	int tcount;					/* Touch count for converting to fwd cells */
	int acount;					/* Actual count for converting to fwd cells */

	vstat status;					
	int tix;					/* Target vertex when being created */

	struct _vtxrec *hlink;		/* Linked list of vtxrecs with same ix hash */
	int rix;					/* nnrev[] index vertex falls into */
	int ival[MXRO];				/* nnrev[] coordinate rix */ 

	char prim;					/* nz when primary vertex of bx (not shadow bx) */
	char cross;					/* nz when part of suspected crossed triangle */
	char pres;					/* nz when preserved shadowed vertex from crossed triangle */

	char tflag;					/* nz when on tlist */
	struct _vtxrec *tlist;		/* Linked list of vertexes for nnrev[] cell/freelist */

#if defined(REVVRML) || defined(REVTABLESTATS)
	int addvtx;					/* Vertex that caused a bxcell to be added */
	int vrmlix;					/* Index for plotting */
#endif

}; typedef struct _vtxrec vtxrec;

struct _vtxcache {
	vtxrec *vtxlist;		/* vertex list for soring/itterating selected nnrev cell. */
	int nilist;				/* Number of vertexes in the list */

	int hash_size;			/* Current size of vtxrec hash list */
	vtxrec **hash;			/* hash index list */

	vtxrec *freelist;		/* Unused vertex structures (to avoid memory allocs) */
}; typedef struct _vtxcache vtxcache;


/* Create the (empty) vertex list & hash */
static void create_vtxrec_list(rspl *s, vtxcache *vc) {
	vc->hash_size = primes[3];		/* 3373 */
	if ((vc->hash = (vtxrec **) rev_calloc(s, vc->hash_size, sizeof(vtxrec *))) == NULL)
		error("rspl malloc failed - vtxrec cache index");
	INCSZ(s, vc->hash_size * sizeof(vtxrec *));
	vc->vtxlist = NULL;
	vc->nilist = 0;
	vc->freelist = NULL;
}

/* Clear the vertex hash and list */
static void clear_vtxrec_lists(rspl *s, vtxcache *vc) {
	vtxrec *vp, *nvp;
	int i;

	/* Transfer all records in hash to freelist, */
	/* and clear hash. */
	for (i = 0; i < vc->hash_size; i++) {
		for (vp = vc->hash[i]; vp != NULL; vp = nvp) {
			nvp = vp->hlink;
			vp->tlist = vc->freelist;
			vc->freelist = vp;
		}
		vc->hash[i] = NULL;
	}

	vc->vtxlist = NULL;
	vc->nilist = 0;
}

/* Free the vertex list & hash */
static void free_vtxrec_list(rspl *s, vtxcache *vc) {
	clear_vtxrec_lists(s, vc);

	while (vc->freelist != NULL) {
		vtxrec *this = vc->freelist;
		vc->freelist = vc->freelist->tlist;
		free(this);
		DECSZ(s, sizeof(vtxrec));
	}
	free(vc->hash);
	DECSZ(s, vc->hash_size * sizeof(vtxrec *));
	vc->hash = NULL;
	vc->hash_size = 0;
}

/* Add a vtxrec to the vertex hash list */
static void add_vtxrec_hash(vtxcache *vc, vtxrec *vx) {
	unsigned int hash = 0;

	hash = vx->ix % vc->hash_size;
	vx->hlink = vc->hash[hash];
	vc->hash[hash] = vx;
}

/* Delete a vtxrec from the vertex hash list */
/* (Assume it's not part of vtxlist!) */
static void del_vtxrec_hash(vtxcache *vc, int ix) {
	unsigned int hash = 0;
	vtxrec *vx = NULL, **pvx;

	hash = ix % vc->hash_size;

	for (pvx = &vc->hash[hash], vx = *pvx; vx != NULL; pvx = &vx->hlink, vx = *pvx) {
		if (vx->ix == ix) {
			*pvx = vx->hlink;
			vx->tlist = vc->freelist;
			vc->freelist = vx;
			vx->hlink = NULL;
			return;
		}
	}
}

/* Fetch a surface vtxrec from the hash list, given its index */ 
/* Return NULL if none */
static vtxrec *get_vtxrec(vtxcache *vc, int ix) {
	unsigned int hash = 0;
	vtxrec *vx = NULL;

	hash = ix % vc->hash_size;

	for (vx = vc->hash[hash]; vx != NULL; vx = vx->hlink) { 
		if (vx->ix == ix)
			return vx;
	}
	return NULL;
}

/* Log compress an output value wrt to center point */
static void logcomp(
	rspl *s,
	double *out,
	double *in,
	double *cent
) {
	int f, fdi = s->fdi;
	double len;

	if (s->rev.surflin_en) {
#ifdef NEVER
		/* (This doesn't seem to improve things) */
		/* Calculate vector length */
		for (len = 0.0, f = 0; f < fdi; f++) {
			double tt= in[f] - cent[f]; 
			len += tt * tt;
		}
		len = sqrt(len);

		/* change length to log length */
		if (len > DBL_EPSILON) {
 
			len = 20.0 * pow(len, 0.25)/len;	/* Ratio */ 

			for (f = 0; f < fdi; f++) {
				double tt = in[f] - cent[f]; 
				out[f] = len * tt + cent[f];
			}
		}
#else
		if (s->rev.surflin != NULL) {
			co p;

			for (f = 0; f < fdi; f++)
				p.p[f] = in[f];
			s->rev.surflin->interp(s->rev.surflin, &p);
			for (f = 0; f < fdi; f++)
				out[f] = p.v[f] - s->rev.linoff[f];
		} else {
			for (f = 0; f < fdi; f++)
				out[f] = in[f];
		}
#endif
	} else {
		for (f = 0; f < fdi; f++)
			out[f] = in[f];
	}
}

/* Create a new vtxrec or return the current one. */
/* Allocates it, adds it to cache. */
/* DOESN"T add it to vtxlist. */
/* If new, sets status = vtx_norm */
static vtxrec *new_vtxrec(
	rspl *s,
	vtxcache *vc,
	int ix				/* fwd index of vertex */
) {
	int e, di = s->di;
	int f, fdi = s->fdi;
	vtxrec *vx = NULL;
	float *gp;
	int rix;
	int rgres_1 = s->rev.res -1;	/* rgres -1 == maximum base coord value */

	/* See if we've already got this vertex */
	if ((vx = get_vtxrec(vc, ix)) != NULL)
		return vx;

	/* Fetch or allocate a new structure */
	if (vc->freelist != NULL) {		/* Grab one from free list */
		vx = vc->freelist;
		vc->freelist = vx->tlist;
		memset((void *)vx, 0, sizeof(vtxrec));

	} else {
		if ((vx = (vtxrec *) rev_calloc(s, 1, sizeof(vtxrec))) == NULL)
			error("rspl malloc failed - rev vtxrec structs");
		INCSZ(s, sizeof(vtxrec));
	}

	/* Our fwd index */
	vx->ix = ix;

	/* Add it to the hash */
	add_vtxrec_hash(vc, vx);

	/* Fwd vertex array address */
	gp = s->g.a + ix * s->g.pss;

	/* Set cell index so that cell vertices don't exceed grid boundary */
	vx->cix = ix;
	for (e = 0; e < di; e++) {
		if (G_FL(gp, e) == 0)		/* At the top edge */
			vx->cix -= s->g.ci[e];	/* Move cell base down a row */
	}

	/* Get the output value */
	for (f = 0; f < fdi; f++)
		vx->vv[f] = gp[f];

	/* Set vl[] */
	logcomp(s, vx->vl, vx->vv, s->rev.ocent);

	/* Compute distance to overall center point squared */
	vx->dist = 0.0;
	for (f = 0; f < fdi; f++) {
		double tt = vx->vl[f] - s->rev.ocent[f]; 
		vx->dist += tt * tt;
	}

	/* Figure the actual nncell it lands in */
	for (rix = f = 0; f < fdi; f++) {
		double t;
		int mi;
		double gw = s->rev.gw[f];
		double gl = s->rev.gl[f];
		t = (vx->vv[f] - gl)/gw;
		mi = (int)floor(t);			/* Grid coordinate */
		if (mi < 0)					/* Limit to valid cube base index range */
			mi = 0;
		else if (mi > rgres_1)
			mi = rgres_1;
		vx->ival[f] = mi;
		rix += mi * s->rev.coi[f];
	}
	vx->rix = rix;

	return vx;
}

/* Add a vertex to the list. */
/* Don't add if already on list (if tflag set), or if shadowed) */
/* set prim flag to value */
static void add_vtxrec_list(vtxcache *vc, vtxrec *vx, int prim) {

	vx->prim = (char)prim;		/* Always set prim flag */

	if (vx->tflag || vx->status != vtx_norm)
		return;

	vx->tlist = vc->vtxlist;
	vc->vtxlist = vx;
	vx->tflag = 1;
	vc->nilist++;

}

int dumpvtxsort = 0;

/* Sort the vertex linked list by dist. */
/* Also reset the tflag */ 
static void sort_vtxrec_list(rspl *s, vtxcache *vc) {
	int i;
	vtxrec **sort, *vx;

	/* Create temporary array of pointers to vtxrec's in list */
	if ((sort = (vtxrec **) rev_calloc(s, vc->nilist, sizeof(vtxrec *))) == NULL)
		error("rspl malloc failed - rev vtxrec sort array");
	INCSZ(s, vc->nilist * sizeof(vtxrec *));

	for (i = 0, vx = vc->vtxlist; vx != NULL; vx = vx->tlist, i++)
		sort[i] = vx;

	/* Sort the list into ascending distance from center */
#define 	HEAP_COMPARE(A,B) (A->dist < B->dist)
	HEAPSORT(vtxrec *, sort, vc->nilist)
#undef 		HEAP_COMPARE

	/* Re-create the linked list in descending order */
	vc->vtxlist = NULL;
	for (i = 0; i < vc->nilist; i++) {
		vx = sort[i];
		vx->tlist = vc->vtxlist;
		vc->vtxlist = vx;
		vx->tflag = 0;
	}

	free(sort);
	DECSZ(s, vc->nilist * sizeof(vtxrec *));

#ifndef NEVER
	if (dumpvtxsort) {
		printf("sorted vertex list:\n");
		for (i = 0, vx = vc->vtxlist; vx != NULL; vx = vx->tlist, i++)
			printf("%d: ix %d, dist %f\n",i,vx->ix, sqrt(vx->dist));
	}
#endif
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Structure to cache surface triangle vertexes, to avoid repeated */
/* shadowing test */

struct _trirec{
	int ix[3];					/* vertex indexes of triangle in simplex order */
	struct _trirec *hlink;		/* Linked list of triangles in hash/freelist */
}; typedef struct _trirec trirec;

typedef struct {
	int hash_size;			/* Current size of trirec hash list */
	trirec **hash;			/* hash index list */
	trirec *freelist;		/* Unused trirec structures (to avoid memory allocs) */
} tricache;

/* Create the tricache list & hash. */
/* Set sm flag if we only want a small cache size */
static void create_trirec(rspl *s, tricache *tc, int sm) {
	if (sm)
		tc->hash_size = primes[1];		/* 853 */
	else
		tc->hash_size = primes[5];		/* 12919 */
	if ((tc->hash = (trirec **) rev_calloc(s, tc->hash_size, sizeof(trirec *))) == NULL)
		error("rspl malloc failed - trirec cache index");
	INCSZ(s, tc->hash_size * sizeof(trirec *));
	tc->freelist = NULL;
}

/* Clear the trirec list & hash */
static void clear_trirec(rspl *s, tricache *tc) {
	int i;
	trirec *tp, *ntp;

	/* Transfer all records in hash to freelist, */
	/* and clear hash. */
	for (i = 0; i < tc->hash_size; i++) {
		for (tp = tc->hash[i]; tp != NULL; tp = ntp) {
			ntp = tp->hlink;
			tp->hlink = tc->freelist;
			tc->freelist = tp;
		}
		tc->hash[i] = NULL;
	}
}

/* Free the triangle list & hash */
static void free_trirec(rspl *s, tricache *tc) {
	clear_trirec(s, tc);

	while (tc->freelist != NULL) {
		trirec *this = tc->freelist;
		tc->freelist = tc->freelist->hlink;
		free(this);
		DECSZ(s, sizeof(trirec));
	}
	free(tc->hash);
	DECSZ(s, tc->hash_size * sizeof(trirec *));
	tc->hash = NULL;
	tc->hash_size = 0;
}

/* Check if a triangle is in the cache. */
/* return nz if it is, and z if it isn't, and add it. */
static int check_trirec(rspl *s, tricache *tc, int *ix) {
	int i;
	unsigned int hash = 0;
	trirec *tp = NULL;

	hash = ix[0];
	hash = hash * 17 + ix[1];
	hash = hash * 17 + ix[2];
	hash %= tc->hash_size;

	for (tp = tc->hash[hash]; tp != NULL; tp = tp->hlink) {
		if (tp->ix[0] == ix[0]
		 && tp->ix[1] == ix[1]
		 && tp->ix[2] == ix[2]) {
//printf("check_trirec %d %d %d is in cache\n",ix[0], ix[1], ix[2]);
			return 1;
		}
	}
//printf("check_trirec %d %d %d NOT in cache\n",ix[0], ix[1], ix[2]);

	/* Allocate a new structure */
	if (tc->freelist != NULL) {		/* Grab one from free list */
		tp = tc->freelist;
		tc->freelist = tp->hlink;
		memset((void *)tp, 0, sizeof(trirec));

	} else {
		if ((tp = (trirec *) rev_calloc(s, 1, sizeof(trirec))) == NULL)
			error("rspl malloc failed - rev trirec structs");
		INCSZ(s, sizeof(trirec));
	}
	
	tp->ix[0] = ix[0];
	tp->ix[1] = ix[1];
	tp->ix[2] = ix[2];

	/* add it into the hash */
	tp->hlink = tc->hash[hash];
	tc->hash[hash] = tp;

	return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Add another entry to an index/share list, taking care of any re-sizing */
/* Set shlist if this is a sharer record */
static void add2indexlist(rspl *s, int **rpp, int ix, int shrec) {
	int *rp = *rpp;

	if (rp == NULL) {
		if ((rp = (int *) rev_malloc(s, 6 * sizeof(int))) == NULL)
			error("rspl malloc failed - rev.grid list");
		INCSZ(s, 6 * sizeof(int));
		rp[0] = 6;		/* Allocation */
		rp[1] = 4;		/* Next free Cell */
		rp[2] = -1;		/* share list index - default none */
		rp[3] = ix;		/* Index added to list */
		rp[4] = -1;		/* End of list marker */
		*rpp = rp;		/* Update pointer */
	} else {
		int z = rp[1], ll = rp[0];
		if (z >= (ll-1)) {			/* Not enough space */
			if (!shrec && rp[2] != -1)
				error("Re-allocating shared fwd index list");
			INCSZ(s, ll * sizeof(int));
			ll *= 2;
			if ((rp = (int *) rev_realloc(s, rp, sizeof(int) * ll)) == NULL)
				error("rspl realloc failed - rev.grid list size %d",ll);
			rp[0] = ll;	/* Allocation */
			*rpp = rp;	/* Update pointer */
		}
		rp[z++] = ix;	/* Index added to list */
		rp[z] = -1;		/* End of list marker */
		rp[1] = z;		/* Next free Cell */
	}
}

/* Copy an index list (i.e. from nnrev[] to bxcell->sl) */
static void copy_indexlist(rspl *s, int **dp, int *sp) {
	if (sp == NULL)
		*dp = NULL;
	else {
		int i;
		if ((*dp = (int *) rev_malloc(s, sp[0] * sizeof(int))) == NULL)
			error("rspl malloc failed - rev.grid list");
		INCSZ(s, sp[0] * sizeof(int));
		for (i = 0; i <= sp[1]; i++)
			(*dp)[i] = sp[i];
		(*dp)[2] = -1;
	}
}

/* Free an index list, at set it to NULL */
static void free_indexlist(rspl *s, int **rp) {
	if (*rp != NULL) {
		DECSZ(s, (*rp)[0] * sizeof(int));
		free(*rp);
		*rp = NULL;
	}
}

/* Add a (fwd index list) sharer to share list. */
/* Record will be created if list[2] == -1, */
/* or incremented otherwise. */
/* sharerix is the index of the cell sharing the *list */
static void add2sharelist(rspl *s, int sharerix, int *list) {
	int *sharerec = NULL;

	/* Create a new record and add our (one) sharer to it */
	if (list[2] == -1) {
		if (s->rev.sharellen >= s->rev.sharelaloc) {
			/* Allocate another sharelist entry */
			INCSZ(s, (10 + s->rev.sharelaloc) * sizeof(int *));
			s->rev.sharelaloc = 10 + 2 * s->rev.sharelaloc;
			if ((s->rev.sharelist = (int **)rev_realloc(s, s->rev.sharelist,
				                           s->rev.sharelaloc * sizeof(int *))) == NULL)
				error("add2sharelist: realloc failed");
		}
		add2indexlist(s, &sharerec, sharerix, 1);
		s->rev.sharelist[s->rev.sharellen] = sharerec;
		list[2] = s->rev.sharellen;
		s->rev.sharellen++;

	/* Add the sharer to the existing sharer list */
	} else {
		if (list[2] >= s->rev.sharellen)
			error("add2sharelist got list with sharelist index out of range");
		sharerec = s->rev.sharelist[list[2]];
		add2indexlist(s, &sharerec, sharerix, 1);
		s->rev.sharelist[list[2]] = sharerec;
	}
}

/* Return the sharer list for the given (fwd cell) list */
/* Return NULL if not shared */
static int *getsharelist(rspl *s, int *list) {
	if (list[2] == -1)
		return NULL;
	if (list[2] >= s->rev.sharellen) {
		error("getsharelist got list with sharelist index out of range (%d > %d)",list[2],s->rev.sharellen);
	}
	return s->rev.sharelist[list[2]];
}

/* Free all the sharelist and the shared nnrev[] fwd cell lists as well */
static void free_sharelist(rspl *s) {
	if (s->rev.sharelist != NULL) {
		int i, j;
		for (i = 0; i < s->rev.sharellen; i++) {
			int *shrec = s->rev.sharelist[i];

			/* Free the shared fwd cell list */
			if (shrec[1] > 3) {
				int *clist = s->rev.nnrev[shrec[3]];
				DECSZ(s, clist[0] * sizeof (int));
				free(clist);
			}

			/* Make sure freeing of s->rev.nnrev[] doesn't free them twice */
			for (j = 3; shrec[j] != -1; j++) 
				s->rev.nnrev[shrec[j]] = NULL;

			DECSZ(s, s->rev.sharelist[i][0] * sizeof (int));
			free(s->rev.sharelist[i]);
		}
		DECSZ(s, s->rev.sharelaloc * sizeof(int *));
		free(s->rev.sharelist);
	}
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* For shadow bxcell testing, compute the delta distance */
/* rev.ocent, and delta "width" between two vertex values. */ 

/* Compute shadow testing group values */ 
static void comp_shadow_group(
	rspl *s,
	double *gcent,			/* In gamut center point to compute from */			
	double *rgc,			/* Return group center if non-NULL */
	double *pcc,			/* Return distance of group center to gamut center */  
	double *pdw,			/* Return width of furthest point from group center */
	double *gc,				/* if not-NULL, the group center */
	double (*v)[MXRO],		/* Input vertices */
	int nvert				/* Number of vertices */
) {
	double _gc[MXRO];
	int i;
	int f, fdi = s->fdi;
	double cc;				/* gamut center to group center */
	double dw = -1.0;		/* Largest goup vertex width */

	/* if no group center given, compute one simply as an average */ 
	/* (Used for triangle) */
	if (gc == NULL) {
		gc = _gc;
		for (f = 0; f < fdi; f++)
			gc[f] = 0.0;
		for (i = 0; i < nvert; i++) {
			for (f = 0; f < fdi; f++) {
				gc[f] += v[i][f];
			}
		}
		for (f = 0; f < fdi; f++)
			gc[f] /= (double)nvert;
	}

	/* Return it if requested */
	if (rgc != NULL) {
		for (f = 0; f < fdi; f++)
			rgc[f] = gc[f];
	}

	/* Compute distance from gamut center to group center */ 
	for (cc = 0.0, f = 0; f < fdi; f++) {
		double tt = gcent[f] - gc[f];
		cc += tt * tt;
	}
	cc = sqrt(cc);

	if (pcc != NULL)
		*pcc = cc;

	/* Compute width for each vertex, and track maximum */
	for (i = 0; i < nvert; i++) {
		double vlen, scale;
		double sv[MXRO];		/* Vertex scaled to same distance as group center */
		double w;

		/* vertex length from gamut center */
		for (vlen= 0.0, f = 0; f < fdi; f++) {
			double tt = v[i][f] - gcent[f];
			vlen += tt * tt;
		}
		vlen = sqrt(vlen);
	
		if (vlen > 1e-6)
			scale = cc/vlen;
		else
			scale = 1.0;

		for (f = 0; f < fdi; f++)
			sv[f] = (scale * (v[i][f] - gcent[f])) + gcent[f];

		/* Distance from scaled vertex to group center */
		for (w = 0.0, f = 0; f < fdi; f++) {
			double tt = sv[f] - gc[f];
			w += tt * tt;
		}
		if (w > dw)
			dw = w;
		
	}
	dw = sqrt(dw);

	if (pdw != NULL)
		*pdw = dw;
}

/* Expand a bxcell's shadow testing group values based on it's vertex list */
static void extend_bxcell_shadow_group(
	rspl *s,
	vtxcache *vc,
	bxcell *bx
) {
	int *ip;
	int f, fdi = s->fdi;
	double dw;

	if (bx->sl == NULL)
		return;

	/* Current dw squared */
	dw = bx->dw * bx->dw;

	/* Compute width for each vertex, and track maximum */
	for (ip = bx->sl+3; *ip != -1; ip++) {
		vtxrec *vx;
		double vlen, scale;
		double sv[MXRO];		/* Vertex scaled to same distance as group center */
		double w;

		if ((vx = get_vtxrec(vc, *ip)) == NULL)
			continue;

		/* vertex length from gamut center */
		for (vlen= 0.0, f = 0; f < fdi; f++) {
			double tt = vx->vl[f] - s->rev.ocent[f];
			vlen += tt * tt;
		}
		vlen = sqrt(vlen);
	
		if (vlen > 1e-6)
			scale = bx->cc/vlen;
		else
			scale = 1.0;

		for (f = 0; f < fdi; f++)
			sv[f] = (scale * (vx->vl[f] - s->rev.ocent[f])) + s->rev.ocent[f];

		/* Distance from scaled vertex to group center */
		for (w = 0.0, f = 0; f < fdi; f++) {
			double tt = sv[f] - bx->g.bcent[f];
			w += tt * tt;
		}
		if (w > dw)
			dw = w;
		
	}
	if (dw > bx->dw)
		bx->dw = sqrt(dw);
}

/* Shadow group to group compare. Return nz if within range */
static int shadow_group_group(
	rspl *s,
	double *gcent,		/* Input gamut center point to compute from */			
	double *gc1,		/* Reference group center point */
	double cc1,			/* Reference point cc value */
	double dw1,			/* Reference point dw value */
	double *gc2,		/* Comparison group center point */
	double cc2,			/* Comparison point cc value */
	double dw2			/* Comparison point dw value */
) {
	int i;
	int f, fdi = s->fdi;
	double dot, scale;
	double sv[MXRO];		/* Comparison group center scaled to same distance as ref center */
	double w;

	/* Compute dot product of cc1 and cc2 */
	for (dot = 0.0, f = 0; f < fdi ; f++)
		dot += (gc1[f] - gcent[f]) * (gc2[f] - gcent[f]);

	/* If the groupls are not in the same direction, return false */
	if (dot < 0.0)
		return 0;
  
	if (cc2 > 1e-6)
		scale = cc1/cc2;
	else
		scale = 1.0;

	for (f = 0; f < fdi; f++)
		sv[f] = (scale * (gc2[f] - gcent[f])) + gcent[f];

	/* Distance from scaled group center to ref. group center */
	for (w = 0.0, f = 0; f < fdi; f++) {
		double tt = sv[f] - gc1[f];
		w += tt * tt;
	}
	w = sqrt(w);

	if (w <= (dw1 + (scale * dw2) + EPS))
		return 1;

	return 0;
}

/* Shadow group to vertex compare. Return nz if within range */
static int shadow_group_vertex(
	rspl *s,
	double *gcent,		/* Input gamut center point to compute from */			
	double *gc1,		/* Reference group center point */
	double cc1,			/* Reference point cc value */
	double dw1,			/* Reference point dw value */
	double *v			/* Comparison vertex location */
) {
	int i;
	int f, fdi = s->fdi;
	double vlen, dot, scale;
	double sv[MXRO];		/* Vertex scaled to same distance as group center */
	double w;

	/* Compute dot product of cc1 and cc2 */
	for (dot = 0.0, f = 0; f < fdi ; f++)

	/* vertex length from center */
	/* and dot product with group center vector */
	for (vlen= 0.0, f = 0; f < fdi; f++) {
		double tt = v[f] - gcent[f];
		vlen += tt * tt;
		dot += (gc1[f] - gcent[f]) * tt;
	}

	/* If the groupls are not in the same direction, return false */
	if (dot < 0.0)
		return 0;
  
	vlen = sqrt(vlen);
	
	if (vlen > 1e-6)
		scale = cc1/vlen;
	else
		scale = 1.0;

	for (f = 0; f < fdi; f++)
		sv[f] = (scale * (v[f] - gcent[f])) + gcent[f];

	/* Distance from scaled vertex to group center */
	for (w = 0.0, f = 0; f < fdi; f++) {
		double tt = sv[f] - gc1[f];
		w += tt * tt;
	}
	w = sqrt(w);

	if (w <= (dw1 + EPS))
		return 1;

	return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/* Given a pointer to a bxcell, use the ->tlist to fill in the corresponding nnrev[]. */
/* Expand and share lists with nearby nnrev[] cells if they are similar. */
static void create_nnrev_list(
rspl *s,
bxcell *tx,			/* Target nnrev[] cell */
bxcell *ss,			/* Head of solution list of surface nnrev[] cells */
double emax			/* smallest emax in solution list */
) {
	int i, j;
	bxcell *bx;
	int *dp = NULL, *sp;
	double *eminlist;
	unsigned int hashk;

	DBG(("create_nnrev_list: di %d target cell ix %d co[] %s emax = %f\n",s->di, tx->ix,debPiv(s->fdi, tx->gc),emax));

	/* Update tx->ss and tx->sdist with best in tlist for future */
	/* searches from this surface cell. */
	tx->sdist = 1e200;
	for (bx = ss; bx != NULL; bx = bx->tlist) {
//printf("~1 checking ix %d\n",bx->ix);
		if (bx->sdist < tx->emin) {
			tx->ss = bx;
			tx->sdist = bx->emin;
			DBG((" Set target ss to ix %d, emin %f\n",tx->ss->ix, tx->sdist));
//printf(" Set target ss to ix %d, emin %f\n",tx->ss->ix, tx->sdist);
		}
	}
//printf("~1 set sdist\n");

#ifdef DEBUG2
	{
		int tot = 0;
		printf(" Initial fwd list from following surface cells:\n");
		for (bx = ss; bx != NULL; bx = bx->tlist) {
			if (bx->emin <= emax) {
				if (bx->sl == NULL)
					error("rev create_nnrev_list: found empty surface bxcell");
				printf("   ix %d co %s fwd count %d\n",bx->ix,debPiv(s->fdi, bx->gc),bx->sl[1]-3);
				tot += bx->sl[1]-3;
			}
		}
		printf(" Total fwd cells = %d\n",tot);
	}
#endif

	/* Create an initial list of fwd cells from all bxcells */
	/* on the solution list that have ->emin <= emax */
	for (bx = ss; bx != NULL; bx = bx->tlist) {
//printf("~1 checking solution cell ix %d co %s\n",bx->ix,debPiv(s->fdi, bx->gc));
		if (bx->emin <= emax) {
//printf("~1 solution cell has emin %f < emax %f\n",bx->emin, emax);
			sp = bx->sl;
			if (sp == NULL)
				error("rev create_nnrev_list: found empty surface bxcell %d",ss->ix);
			for (sp += 3; *sp != -1; sp++)
				add2indexlist(s, &dp, *sp, 0);
		}
	}

	if (dp == NULL)
		error("create_nnrev_list got NULL new list\n");

#ifdef DEBUG2
	printf(" Initial fwd list (length %d, alloc %d):\n",dp[1]-3,dp[0]);
	for (i = 3; dp[i] != -1; i++)
		printf("  %d: ix %d\n",i-3,dp[i]);
#endif

	/* Sort the list into ascending order */
#define 	HEAP_COMPARE(A,B) (A < B)
	HEAPSORT(int, dp + 3, dp[1]-3)
#undef 		HEAP_COMPARE

#ifdef DEBUG2
	printf(" After sorting:\n");
	for (i = 3; dp[i] != -1; i++)
		printf("  %d: ix %d\n",i-3,dp[i]);
#endif

	/* Delete any duplicates */
	for (i = 3, j = i+1; ; j++) {
		if (dp[i] != dp[j])
			dp[++i] = dp[j];
		if (dp[j] == -1)
			break;
	}
	dp[1] = i;

#ifdef DEBUG2
	printf(" After de-duplication (length %d, alloc %d):\n",dp[1],dp[0]);
	for (i = 3; dp[i] != -1; i++)
		printf("  %d: ix %d\n",i-3,dp[i]);
#endif

	/* Filter fwd cells against emin/emax. */
	/* (Don't bother for 1D, as there's no point in filling up the cache */
	/* at this point, since 1D ins't participating in RAM management ?) */
	if (s->fdi > 1) {
		/* Allocate a temporary array to hold fwd cell emin */
		if ((eminlist = (double *) rev_malloc(s, (dp[1]-3) * sizeof(double))) == NULL)
			error("rspl malloc failed - rev create_nnrev_list emin array");
		INCSZ(s, (dp[1]-3) * sizeof(double));
	
		for (i = 0; i < (dp[1]-3); i++)
			eminlist[i] = 1e200;
		
		/* Get an fxcell for each fwd index, and compute emin & emax for this target. */
		/* Tracl smallest maximum and record each fxcell emin */
		emax = 1e200;
		for (i = 3; dp[i] != -1; i++) {
			fxcell *fc;
			double em, ex;
	
			fc = get_fxcell(s->rev.sb, dp[i], 1);
	
			eminlist[i-3] = nn_grpgrp_est(s, &ex, &fc->g, &tx->g);
			if (ex < emax)
				emax = ex;
			
			unget_fxcell(s->rev.cache, fc);
		}
	
#ifdef DEBUG2
		printf(" Smallest emax = %f\n",emax);
		for (i = 3; dp[i] != -1; i++)
			printf("  %d: ix %d, emin %f\n",i-3,dp[i],eminlist[i-3]);
#endif
	
		/* Delete any fwd cells/indexes that have an emin > smallest emax */
		for (i = j = 3; dp[j] != -1; j++) {
			if (eminlist[j-3] <= emax)
				dp[i++] = dp[j];
		}
		dp[i] = -1;
		dp[1] = i;
	
		free(eminlist);
		DECSZ(s, sizeof(schbase));

#ifdef DEBUG2
		printf(" After removing too far cells (length %d, alloc %d):\n",i-3,dp[0]);
		for (i = 3; dp[i] != -1; i++)
			printf("  %d: ix %d\n",i-3,dp[i]);
#endif
	}

	/* If the size of the list has reduced substatially, reclaim some memory */
	if ((dp[1]+1) <= (dp[0]/2)) {
		int ll = dp[0];
		while (ll > (dp[1]+1))
			ll /= 2;
		ll *= 2;
		DBG((" Reducing list allocation from %d to %d entries\n",dp[0],ll));
		DECSZ(s, (dp[0] - ll) * sizeof(int));
		if ((dp = (int *) rev_realloc(s, dp, sizeof(int) * ll)) == NULL)
			error("rspl realloc failed - create_nnrev_list");
		dp[0] = ll;	/* New allocation */
	}

	/* Check if any neighbor lists are similar to the list we just created, */
	/* so that we can merge similar lists, greatly reducing memory usage */
	/* at the cost of slightly longer lists. */
	/* Don't do this if this is a super-cell. */
	/* [ This seems to increase nnrev fill time by about 5% ] */
	if (tx->scell == NULL) {
		DCOUNT(cc, MXRO, s->fdi, -1, -1, 2);	/* bwd neighborhood offset counter */
		int nn[MXRO];
		int shlim, lnlim;
		int sh, ln;
		int bnix = -1, *blist = NULL, bwhgt = 0x7ffffff, bsh, bln;
		int f, nix;

		/* Set limits of an acceptable match at 2% short, 15% long */
		/* This trades off list size against number of lists/memory */
		/* i.e. a 10% rise in average list length for a 100 x reduction in */
		/* number of lists. (vary lnlim for most effect) */
		shlim = (2 * (dp[1]-3) + 50)/100;
		lnlim = (15 * (dp[1]-3) + 50)/100;

		DC_INIT(cc);
		while (!DC_DONE(cc)) {

			nix = tx->ix;
			for (f = 0; f < s->fdi; f++) {
				nn[f] = tx->gc[f] + cc[f];
				if (nn[f] < 0 || nn[f] >= s->rev.res)
					break;					/* Out of bounds */
				nix += cc[f] * s->rev.coi[f];
			}
			if (nix == tx->ix)			/* Skip this cell */
				goto next_neighbor;

			/* If neighbor is in bounds and has a fwd cell list, */
			/* check what sort of match it is to this list */
			if (f >= s->fdi && s->rev.nnrev[nix] != NULL) {
				int *np = s->rev.nnrev[nix];
				int *shrecs = getsharelist(s, np);

				if (shrecs != NULL) {
					if (shrecs[2] == tx->ix)		/* Already looked at this list */
						goto next_neighbor;
					shrecs[2] = tx->ix;				/* Remember we've done this one */
				}

				/* See how much it is a super or sub-set */
//printf("~1 checking ix %d against nix %d\n",tx->ix, nix);
				if ((dp[1] - np[1]) > shlim
				 || (np[1] - dp[1]) > lnlim) {
					goto next_neighbor;				/* No possibility of being acceptable */
				}

				sh = ln = 0;
				for (j = i = 3; dp[i] != -1 || np[j] != -1;) {

//printf("1: dp[%d] %d - np[%d] %d\n",i,dp[i],j,np[j]);
					while (np[j] != -1 && (dp[i] == -1 || dp[i] > np[j])) {
						j++;
						ln++;
//printf("2: dp[%d] %d - np[%d] %d, ln %d\n",i,dp[i],j,np[j],ln);
						if (ln > lnlim)
							goto next_neighbor;		/* No possibility of being acceptable */
					}

					while (dp[i] != -1 && (np[j] == -1 || dp[i] < np[j])) {
						i++;
						sh++;
//printf("3: dp[%d] %d - np[%d] %d, sh %d\n",i,dp[i],j,np[j],sh);
						if (sh > shlim)
							goto next_neighbor;		/* No possibility of being acceptable */
					}

					while (dp[i] != -1 && np[j] != -1 && dp[i] == np[j]) {
						i++;
						j++;
//printf("4: dp[%d] %d - np[%d] %d\n",i,dp[i],j,np[j]);
					}
				}
//printf("~1 len %d, short %d, long %d\n",dp[1]-3,sh,ln);

				/* remember best similar list within our criteria */
				if (sh <= shlim && ln <= lnlim) {
					int whgt = 2 * sh + ln;
					if (whgt < bwhgt) {
						bnix = nix;
						blist = np;
						bwhgt = bwhgt;
						bsh = sh;
						bln = ln;
					}
				}
			}
		next_neighbor:;
			DC_INC(cc);
		}

		/* Got a list we want to share with */
		if (blist != NULL) {
			int *shrecs = NULL;
			int *exlist = NULL;

			DBG((" Found similar existing list (short %d, long %d)\n",bsh,bln));

#ifdef DEBUG2
			printf(" Similar list (length %d, alloc %d):\n",blist[1]-3,blist[0]);
//			for (i = 3; blist[i] != -1; i++)
//				printf("  %d: ix %d\n",i-3,blist[i]);
#endif
			/* If the neighbor list is not a super-set */
			if (bsh > 0) {

				/* But new list is superset of neighbor list */
				if (bln == 0) {
					DBG((" Using new list to share\n"));

					exlist = dp;			/* Use our new list */
					exlist[2] = blist[2];	/* Same sharers */
					dp = NULL;

					/* Free neighbor list */
					free_indexlist(s, &blist);

				/* Create superset list from new list and neighbor list */
				} else {
	
					DBG((" Creating superset list\n"));

					for (j = i = 3; dp[i] != -1 || blist[j] != -1;) {
		
						while (blist[j] != -1 && (dp[i] == -1 || dp[i] > blist[j])) {
							add2indexlist(s, &exlist, blist[j], 0);
							j++;
						}
		
						while (dp[i] != -1 && (blist[j] == -1 || dp[i] < blist[j])) {
							add2indexlist(s, &exlist, dp[i], 0);
							i++;
						}
		
						while (dp[i] != -1 && blist[j] != -1 && dp[i] == blist[j]) {
							add2indexlist(s, &exlist, dp[i], 0);
							i++;
							j++;
						}
					}
	
					exlist[2] = blist[2];		/* Same sharers */
	
					/* Free neighbor list */
					free_indexlist(s, &blist);

					/* Done with list we created for this nnrev[] */
					free_indexlist(s, &dp);
				}

			} else {
				DBG((" Using existing list to share\n"));

				exlist = blist;		/* blist is already a super-set */
				blist = NULL;		/* Done with neighbor list */

				/* Done with list we created for this nnrev[] */
				free_indexlist(s, &dp);
			}
	
#ifdef DEBUG2
			printf(" Superset list nnrev[%d] (length %d, alloc %d):\n",tx->ix,exlist[1]-3,exlist[0]);
//			for (i = 3; exlist[i] != -1; i++)
//				printf("  %d: ix %d\n",i-3,exlist[i]);
#endif
//if (s->fdi > 1 && (tx->ix == 19054 || tx->ix == 19055)) { 
//printf(" Superset list nnrev[%d] (length %d, alloc %d):\n",tx->ix,exlist[1]-3,exlist[0]);
//for (i = 3; exlist[i] != -1; i++)
//	printf("  %d: ix %d\n",i-3,exlist[i]);
//}

			/* If this list has not been shared before, create share record for it */
			if (getsharelist(s, exlist) == NULL) 
				add2sharelist(s, bnix, exlist);

			/* Add this cell as a sharer */
			add2sharelist(s, tx->ix, exlist);

			/* Update pointers for all sharers of this (possibly new) list */
			shrecs = getsharelist(s, exlist);
//printf("Number shared now %d\n", shrecs[1]-3);
			for (i = 3; shrecs[i] != -1; i++) {
				s->rev.nnrev[shrecs[i]] = exlist;
			}
	
		} else {
			DBG((" no matching existing list\n"));
//printf(" no matching existing list\n");
	
			/* Put list in place for target nnrev[]*/
			s->rev.nnrev[tx->ix] = dp;
		}
	} else {

		if (tx->scell != NULL) {

			/* Put list in place for all nnrev[]'s covered by super-cell */
			for (sp = tx->scell + 3; *sp != -1; sp++) {  

				/* Add this cell as a sharer */
				add2sharelist(s, *sp, dp);

				s->rev.nnrev[*sp] = dp;
			}

		} else {
			/* Put list in place for target nnrev[]*/
			s->rev.nnrev[tx->ix] = dp;
		}
	}

	DBG(("create_nnrev_list done, total fwd cells = %d\n",s->rev.nnrev[tx->ix][1]-3));
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* This is the routine used to fill nnrev[] cells on demand, */
/* because s->rev.fastsetup is set. */
/* This is similar to the code used in the normal case, except */
/* we search the rev[] bxcell list rather than use the bxcell surface list */
static void fill_nncell(
	rspl *s,
	int *co,	/* Integer coords of cell to be filled */
	int ix		/* Index of cell to be filled */
) {
	int f, fdi = s->fdi;
	DCOUNT(gg, MXRO, fdi, 0, 0, s->rev.res);	/* search seed coordinate */
	int i, six = -1, nn[MXRO];
	double bdist = 1e200;
	bxcell *tx, *ss;
	DCOUNT(cc, MXRO, fdi, -1, -1, 2);	/* bwd neighborhood offset counter */
	int nix;						/* Neighbor offset index */
	bxcell *xlist = NULL;			/* Linked list of cells being searched */
	bxcell *xlistend = NULL;		/* Last item on xlist */
	bxcell *tlist;					/* Linked list of cells being considered as soln. */
	double emax;					/* Current smallest estimated max weigted distance */

	DBG(("fill_nncell: (triggered on-demand)\n"));
	
	/* Allocate the bxcell hash index */
	create_surfhash(s);

	/* Locate a starting search cell. */
	/* We use a simple full search of rev[] for the cell */
	/* closest to our target. */
	DC_INIT(gg);
	for (i = 0; i < s->rev.no; i++) {
		if (s->rev.rev[i] != NULL) {
			double dist;
			for (dist = 0.0, f = 0; f < fdi; f++) {
				double tt = co[f] - gg[f];
				dist += tt * tt;
			}
			if (dist < bdist) {
				bdist = dist; 
				six = i;
				for (f = 0; f < fdi; f++)
					nn[f] = gg[f];
			}
		}
		DC_INC(gg);
	}
	if (six < 0)
		error("fill_nncell: rev[] is empty");

	/* Create search seed cell */
	ss = new_bxcell(s, six, nn, NULL, 0.0, NULL);
	add_bxcell_hash(s, ss);

	/* Create a target cell */
	tx = new_bxcell(s, ix, co, ss, 0.0, NULL);
	add_bxcell_hash(s, tx);

	DBG((" Target ix = %d, co[] %s\n",ix,debPiv(fdi, tx->gc)));
	DBG((" Search start ix = %d, co[] %s\n",six,debPiv(fdi, ss->gc)));
//printf(" Target ix = %d, co[] %s\n",ix,debPiv(fdi, tx->gc));
//printf(" Search start ix = %d, co[] %s\n",six,debPiv(fdi, ss->gc));

	emax = 1e200;				/* Smallest emax */
	ss->tix = tx->ix;			/* Mark this cell as being in search list */
	
	/* Make start cell the only entry in the search list */
	ss->xlist = NULL;
	xlist = ss;
	xlistend = ss;

	/* Clear the solution list */
	tlist = NULL;

	/* While there are cells to search for solutions */
	while (xlist != NULL) {
		double em, ex;

		ss = xlist;					/* Remove next search cell from linked list */
		xlist = xlist->xlist;

		/* Check if this cell could be in solution */
		em = nn_grpgrp_est(s, &ex, &tx->g, &ss->g);
		ss->emin = em;

		DBG(("Searching rev[%d] co %s, em %f, ex %f\n",ss->ix, debPiv(s->fdi, ss->gc), em, ex));
//printf("Searching rev[%d] co %s, em %f, ex %f\n",ss->ix, debPiv(s->fdi, ss->gc), em, ex);
	
		if (em < emax) {		/* Yes */

			/* Add it to the solution list */
			ss->tlist = tlist;
			tlist = ss;

			// copy rev[] list to ss->sl
			copy_indexlist(s, &ss->sl, s->rev.rev[ss->ix]);

			DBG(("Adding %d to solution list\n",ss->ix));
//printf("Adding %d to to solution list\n",ss->ix);

			/* Update smallest maximum */
			/* (Will cull existing bxcell solutions with emin > emax later) */
			if (ex < emax)
				emax = ex;

			/* Explore all neighbours, and add any surface cells that haven't been */
			/* searched for this target yet. */ 
			DC_INIT(cc);
			while (!DC_DONE(cc)) {
				bxcell *nbx;

				nix = ss->ix;
				for (f = 0; f < fdi; f++) {
					nn[f] = ss->gc[f] + cc[f];
					if (nn[f] < 0 || nn[f] >= s->rev.res)
						break;					/* Out of bounds */
					nix += cc[f] * s->rev.coi[f];
				}
				if (f < fdi || nix == ss->ix) {
//printf("Rejecting search neigbor co %s because out of bounds or current cell\n",debPiv(s->fdi,nn));
					goto next_neighbor;
				}

				/* Can only search within filled rev[] cells */
				if (s->rev.rev[nix] == NULL) {
					goto next_neighbor;
				}

				/* If neighbor is in bounds, and a surface bxcell*/
				{
					/* Make sure we have a bxcell for the neighbor */
					if ((nbx = get_surface_bxcell(s, nix)) == NULL) {
						nbx = new_bxcell(s, nix, nn, NULL, 0.0, NULL);
						add_bxcell_hash(s, nbx);
					}

					/* If not already in search list */
					if (nbx->tix != tx->ix) {
//						DBG(("Adding search neigbor nnrev[%d] co %s to search list\n",nbx->ix, debPiv(s->fdi, nbx->gc)));
//printf("Adding search neigbor nnrev[%d] co %s to search list\n",nbx->ix, debPiv(s->fdi, nbx->gc));
						/* Add neigbor to end of search list */
						nbx->tix = tx->ix;		/* Is now in search list */
						nbx->xlist = NULL;
						if (xlist == NULL)
							xlist = nbx;
						else
							xlistend->xlist = nbx;
						xlistend = nbx;
					}
//else 
//printf("Rejecting search neigbor nnrev[%d] co %s because already in list\n",nbx->ix, debPiv(s->fdi, nbx->gc));
				}
			next_neighbor:;
				DC_INC(cc);
			}
		}
//else
//printf("Rejected rev[%d] co %s, because em %f >= emax %f\n",ss->ix, debPiv(s->fdi, ss->gc), em, emax);
	}

	if (tlist == NULL)
		error("fill_nncell: search for rev[] cells failed");

//printf("Got solution list, filling in nnrev[] cell\n");
	/* Create the nnrev[] list from the candidate bxcell solutions */
	create_nnrev_list(s, tx, tlist, emax);  

//printf("nnrev[%d] list length = %d\n",tx->ix,s->rev.nnrev[tx->ix][1]-3);

	/* Free up bxcell hash index and all bxcell's we've created */
	free_surfhash(s, 1);

	DBG(("fill_nncell done\n"));
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Associated sub-simplex tables. For a given base vertex with a given fwd */
/* access flags FLV(), create is a pointer to a list of sub-simplex vertices */
/* offset from the base vertex that the base vertex is part of, in all possible */
/* directions. We do all possible directions to make the bxcell triangle */
/* search symetrical, and searching triangles using points outside */
/* the bxcell list seems to actually speed it up (by culling more effectively) */

typedef struct {
	int pos;			/* nz if this is a ssimplex that is only in the +ve direction */ 
	int ee;
	int goffs[MXDI+1];	/* Offsets to sub-simplex vertices within grid in simplex order. */
} assinfo;

typedef struct {
	int sdi;			/* Sub dimensionality */
	int no;				/* Number of sub-simplexes in list */
	assinfo *ti;		/* Per sub-simplex info array */
} assdire;

#if (FL_BITS != 3)
#error FL_BITS is not 3!
#endif

/* init the triangle/edge directory list and related assinfo tables */
/* sdi = 2 for triangles, 1 for edges */
static void init_assdir(rspl *s, assdire **passdir, int sdi) {
	assdire *assdir;
	int i, j, k;
	int e, ee, di = s->di;
	int dirsize;
	DCOUNT(cc, MXRI, di, -1, -1, 2);	/* Clipping values for each dim */
	ssxinfo *xip;		/* Pointer to sub-simplex info structure */

	DBG(("init_assdir called, di = %d\n",di));

	dirsize = (1 << (FL_BITS * di));

	if ((assdir = (assdire *) rev_calloc(s, dirsize, sizeof(assdire))) == NULL)
		error("rspl malloc failed - assdir");
	INCSZ(s, dirsize * sizeof(assdire));

	assdir->sdi = sdi;
	xip = &s->rev.sspxi[sdi];

#ifdef NEVER
	printf("simplex dim %d:\n",xip->sdi);
	for (i = 0; i < xip->nospx; i++) {
		printf("offs = %s\n", debPiv(sdi+1, xip->spxi[i].offs));
		printf("goffs = %s\n", debPiv(sdi+1, xip->spxi[i].goffs));
	}
#endif

	/* For each possible clip combination */
	/* (where < 0 == clipping lower edge, > 0 == clipping upper edge */
	DC_INIT(cc);
	while (!DC_DONE(cc)) {
		int trilaloc, trillen;
		assinfo *trilist;

		/* Start a new table, allocate the maximum possible number of entries. */
		trilaloc = (1 << di) * xip->nospx;
		if ((trilist = (assinfo *) rev_calloc(s, trilaloc, sizeof(assinfo))) == NULL)
			error("rspl malloc failed - trilist");
		INCSZ(s, trilaloc * sizeof(assinfo));
		trillen = 0;

		/* For all cube directions from base, 0 = +ve, 1 = -ve */
		for (ee = 0; ee < (1<<di); ee++) {

			/* For all the sub-simplexes in a cube */
			for (i = 0; i < xip->nospx; i++) {
				int gotbase = 0;

				/* Offset the sub-simplex by the direction, and check that the */
				/* base vertex is part of it. */
				trilist[trillen].ee = ee;
				trilist[trillen].pos = (ee == 0);
				for (j = 0; j < (sdi+1); j++) {
					trilist[trillen].goffs[j] = xip->spxi[i].goffs[j] - s->g.hi[ee];
					if (trilist[trillen].goffs[j] == 0)	/* Base vertex is present */
						gotbase = 1;
				}
				if (!gotbase) {
					continue;
				}

				/* See if the direction of each vertex of the sub-simplex is */
				/* compatible with the clipping. */
				for (j = 0; j < (sdi+1); j++) {
					for (e = 0; e < di; e++) {
						if (xip->spxi[i].offs[j] & (1<<e)) {

							if ((cc[e] < 0 && (ee & (1<<e)) != 0)	
							 || (cc[e] > 0 && (ee & (1<<e)) == 0)) {
								break;		/* not compatible */
							}
						}
					}
					if (e < di) { 		/* Not compatible */
						break;
					}
				}
				if (j < (sdi+1)) {
					continue;			/* Not compatible */
				}

				/* We end up with aliases due to the sspxi having all */
				/* sub-simplexes within a cube, so see if we already */
				/* created this one. */ 
				for (k = 0; k < trillen; k++) {
					for (j = 0; j < (sdi+1); j++) {
						if (trilist[k].goffs[j] != trilist[trillen].goffs[j])
							break;
					}
					if (j >= (sdi+1))
						break;		/* Redundant - don't add this point */
				}
				if (k < trillen) {
					continue;		/* Skip redundant combination */
				}

//printf(" Clip %s off %d tri %d goffs = %s\n", debPiv(di, cc), ee, trillen, debPiv(sdi+1, trilist[trillen].goffs));
				trillen++;
			}
		}

//printf("Got %d triangles for cc %s\n", trillen, debPiv(di, cc));

		/* Add table to all matching combination of FLV() */
		for (i = 0; i < dirsize; i++) {
			for (e = 0; e < di; e++) {
				int fl = (i >> (3 * e)) & 7;
				if (!								/* NOT: */
				    ((cc[e] > 0 && fl == 0) 			/* Top edge clip and on top edge */
				  || (cc[e] < 0 && fl == 4) 			/* Bottom edge clip and on bottom edge */
				  || (cc[e] == 0 && fl != 0 && fl != 4)))	/* No clipping and in middle */ 
					break;	/* Not a match */
			}
			if (e >= di) {	/* Table matches this FLV() */
				assdir[i].no = trillen;
				assdir[i].ti = trilist;
			}
		}
		DC_INC(cc);		/* Next clip combination */
	}

#ifdef NEVER
	/* Check that there is a list for every flag value */
	for (i = 0; i < dirsize; i++) {
		if (assdir[i].no == 0)
			error("init_assdir has fl %d entry with no sub-simplexes",i);
		else
			printf("fl %d has %d triangles\n",i,assdir[i].no);
	}
#endif

	*passdir = assdir;
}

static void free_assdir(rspl *s, assdire *assdir) {
	int i, j;
	int e, ee, di = s->di;
	int sdi = assdir->sdi;
	int dirsize = (1 << (FL_BITS * di));
	int trilaloc = (1 << di) * s->rev.sspxi[sdi].nospx;

	for (i = 0; i < dirsize; i++) {
		assinfo *trilist;

		if ((trilist = assdir[i].ti) == NULL)
			continue;

		/* Free all aliases of list */
		for (j = i; j < dirsize; j++) {
			if (trilist == assdir[j].ti) {
				assdir[j].ti = NULL;
			}
		}

		free(trilist);
		DECSZ(s, trilaloc * sizeof(assinfo));
	}
	free(assdir);
	DECSZ(s, dirsize * sizeof(assdire));
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Solve the 2x2 simultaneous linear equations A.X = B */
static int solve_se_2x2(double **ta, double *tb) {
	double b[2] = { tb[0], tb[1] };
	double det;
	int rv;

	det = (ta[0][0] * ta[1][1] - ta[0][1] * ta[1][0]);

	if (fabs(det) < 1e-20)
		return 1;

	det = 1.0/det;
	tb[0] = det * ( ta[1][1] * b[0] - ta[0][1] * b[1]);
	tb[1] = det * (-ta[1][0] * b[0] + ta[0][0] * b[1]);

	return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#ifdef CHECK_NNLU

/* Debug code */

static int debug(int ix) {
	if (
		(ix == 619 || ix == 618 || ix == 329)
	 || (ix == 330 || ix == 329 || ix == 312)
	 || (ix == 619 || ix == 329 || ix == 312)
	 || (ix == 329 || ix == 312 || ix == 23)
	 || (ix == 329 || ix == 23 || ix == 22)
	 || (ix == 40 || ix == 23 || ix == 22)
	)
		return 1;
	return 0;
}

static int debug2(int *ix) {
	if (
		(ix[0] == 619 && ix[1] == 618 && ix[2] == 329)
	 || (ix[0] == 330 && ix[1] == 329 && ix[2] == 312)
	 || (ix[0] == 619 && ix[1] == 329 && ix[2] == 312)
	 || (ix[0] == 329 && ix[1] == 312 && ix[2] == 23)
	 || (ix[0] == 329 && ix[1] == 23 && ix[2] == 22)
	 || (ix[0] == 40 && ix[1] == 23 && ix[2] == 22)
	)
		return 1;
	return 0;
}

#endif /* CHECK_NNLU */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef REVVRML		/* Plotting routine declarations */

static void plot_bxfwcells(rspl *s, int dobxcells, int dofwcells, int dofwlabels);

static void plot_tri_check(rspl *s, int dobxcells, int dowait, bxcell *bx, int vtxix,
	int trii, int triix[3], int nvtxix, int sorv, int wsrv, int shdwd,
	double v[MXRI+1][MXRO], double de[MXRO], double pv[MXRO], double xv[MXRO]);

static void plot_vtx_surface(rspl *s, int dovtxlabels, int dodeleted, int doadded,
	int dopres, int dooil, int dobxcells, int dowait, vtxcache *vc, assdire *edgdir); 

static void plot_touched_bxcells(rspl *s, int bxix);

static void plot_fxcell_surface(rspl *s, int dofclabels, int dobxcells, int dowait);

#endif /* REVVRML */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/*
	The basic strategy to thin the gamut surface as much as possible
	to reduce the nnrev[] list size for best memory consuption and
	rev lookup speed relies on being able to decide if a vertx
	is inside or on the surface of the gamut. A simple and definitive
	topological rule hasn't been forthcoming, so a simpler heursitic
	of visiblilty from a singe internal "focal" point is currently used.
	calc_ocent() attempts to choose a point with best visibility of
	all the gamut surfaces, since any self-shadowing results in
	gamut surface holes.

	Improvements would be to create per-axis mappings (and separate
	the shadow vertex locations from the real ones) to re-shape
	the gamut into a square as much as possible.
	Multiple external focal points could be used,
    a vertex being shadowed only when it can't be "seen" by any
	external focal point. It's hard to figure how to make the latter
    fast enough to be useful though, unless some breakthrough
    in the algorithm or spatial data structure can be developed.

	The code is still slower than desired. A possible avenue for
	improving the thinning would be to add an explicit triangle
	structure (similar to gamut ?), add a suitable spatial
	accelleration structure for shadow testing (BSP tree ??),
	and build the gamut surface incrementally from existing
	furthest points.

	(Have loop re-orderings been exausted ? i.e. can overlap
	 triangle processing "Do a first pass for each test vertex,
	 testing against just the triangles that are associated with
	 it's triangle" be used for main shadowing testing ?)
*/

/* Struct to slice locus points */
struct _slpoint {
	double v[MXRO];			/* Point location */
	double wrad;			/* Weighted radius */
	double rad;				/* Distance from ccent along slice */
	double minrad;			/* Minimum distance from ccent */

	double cvec[MXRO];		/* Vector from this point to ccent */
	double len;				/* Length of segment, -1 if no good */
	double trad;			/* Trial center point to this v[] radius */
}; typedef struct _slpoint slpoint;

/* Center finding context */
struct _ocenctx {
	rspl *s;
	int ares;					/* angle resolution */ 
	slpoint *p[MXRO];			/* Slice locus points */
	double ccent[MXRO];			/* Construction center point */
	double ret;					/* return value */
	int oog;					/* flag set if center is out of gamut */
	int debug;
}; typedef struct _ocenctx ocenctx;

/* Given a set of slice locus points and a proposed center point, */
/* compute the weighted average of the orthogonality of the point */
/* to each locus line segment. (smaller is better) */
/* (Used for optimizing the focal/center point.) */
static double aorthog(void *_ctx, double *cent) {
	ocenctx *ctx = (ocenctx *) _ctx;
	rspl *s = ctx->s;
	int f, ff, fdi = s->fdi;
	int aa, ares = ctx->ares;
	double tcent[MXRO];
	double ang, aang = 0.0;
	int naang = 0;
	
	ctx->oog = 0;

	if (ctx->debug) printf("aorthog called with cent %s\n",debPdv(fdi,cent));

	for (ff = 0; ff < fdi; ff++) {
		if (ctx->debug) printf(" Axis %d\n",ff);

		for (f = 0; f < fdi; f++)
			tcent[f] = cent[f];
		/* Flatten the points to lie on the notional center */
		tcent[ff] = ctx->ccent[ff]; 

		for (aa = 0; aa < ares; aa++) {
			double trad, nrad;
			double cvec[MXRO], dot;

			if (ctx->p[ff][aa].len < 0.0)
				continue;

			if (aa == 0) {
				/* Compute normalize vector from cent to locus to this point */ 
				trad = 0.0;
				for (f = 0; f < fdi; f++) {
					double tt = tcent[f] - ctx->p[ff][aa].v[f];
					trad += tt * tt;
				}
				trad = sqrt(trad);
			} else {		/* Was computed by previous */
				trad = ctx->p[ff][aa].trad;
			}

			/* Compute normalize vector from tcent to locus to next point */ 
			nrad = 0.0;
			for (f = 0; f < fdi; f++) {
				cvec[f] = tcent[f] - ctx->p[ff][aa+1].v[f];
				nrad += cvec[f] * cvec[f];
			}
			nrad = ctx->p[ff][aa+1].trad = sqrt(nrad);
			
			/* Normalized difference in distance over length */
			/* Compute dot product of cv and segment vector */
			/* (ang range 0.0 .. 1.0 */
			ang = fabs(trad - nrad)/ctx->p[ff][aa].len;
			if (ang > 1.0)
				ang = 1.0;

			if (ctx->debug) printf("  aa %d: trad %f nrad %f, diff %f, len %f, ang %f\n",aa,trad,nrad,fabs(trad - nrad),ctx->p[ff][aa].len,ang);

			/* Compute dot of next point vector from trial center */
			/* with vector from construction center, to detect */
			/* if the trial has wandered outside of gamut. */
			dot = 0.0;
			for (f = 0; f < fdi; f++)
				dot += cvec[f] * ctx->p[ff][aa+1].cvec[f];

			if (dot < 0.0) {
				if (ctx->debug) printf("  dot is %f\n",dot);
				ang = 50.0;				/* Big value */
				ctx->oog = 1;
			} else {
				ang = pow(ang, 50.0);	/* Weight high angles */
			}
			aang += ang;
			naang++;
		}
	}
	aang /= (double)naang;
	
	if (ctx->debug) printf(" returning %f\n",aang);

	ctx->ret = aang;

	return aang;
}

/* Determine a gamut center point, for surface triangle shadow testing. */
/* We assume that rev[] has been setup. */
/* The idea is to locate a point that best "sees" all internal */
/* surface of the gamut. */
static void calc_ocent(rspl *s) {
	int i, j, aa, mm;
	int e, ee, di = s->di;
	int f, ff, fdi = s->fdi;
	int rgres = s->rev.res;			/* number of bwd cells */
	double minmax[2][MXRO][MXRO];	/* Range min/max points for each axis */
	float *gp, *ep;
	int midix[MXDO];				/* Middle rev[] index */ 
	double mid[MXRO];				/* Middle of midix[] */
	double ss[MXRO];
	ocenctx ctx;					/* Context */
	double atanscale;
	int ici, nici;

	/* Scan the forward array for the min and max points of each axis */
	for (f = 0; f < fdi; f++) {
		minmax[0][f][f] = 1e200;
		minmax[1][f][f] = -1e200;
	}

	/* Scan the fwd Grid for min/max values */
	for (gp = s->g.a, ep = s->g.a + s->g.no * s->g.pss; gp < ep; gp += s->g.pss) {
		for (ff = 0; ff < fdi; ff++) {
			if (minmax[0][ff][ff] > gp[ff]) {
				for (f = 0; f < fdi; f++)
					 minmax[0][ff][f]= gp[f];
			}
			if (minmax[1][ff][ff] < gp[ff]) {
				for (f = 0; f < fdi; f++)
					 minmax[1][ff][f] = gp[f];
			}
		}
	}

	if (fdi == 1) {
		for (f = 0; f < fdi; f++)
			s->rev.ocent[f] = 0.5 * (minmax[0][f][f] + minmax[1][f][f]);
		DBG(("calc_ocent: got 1d ocent = %s\n",debPdv(fdi,s->rev.ocent)));
		return;
	}

	/* Aprox. mid point of gamut from average of min/max points */
	for (f = 0; f < fdi; f++)
		ctx.ccent[f] = 0.0;
	for (ff = 0; ff < fdi; ff++) {
		for (f = 0; f < fdi; f++) {
			if (f == ff)
				continue;
			for (mm = 0; mm < 2; mm++)
				ctx.ccent[f] += minmax[mm][ff][f];
		}
	}
	for (f = 0; f < fdi; f++)
		s->rev.ocent[f] = ctx.ccent[f] /= ((fdi-1) * 2.0);

	DBG(("calc_ocent: initial ccent = %s\n",debPdv(fdi,ctx.ccent)));
//printf("calc_ocent: initial ccent = %s\n",debPdv(fdi,ctx.ccent));

	/* If it's all to hard ... */
	if (fdi != 3) {
		return;
	}

	/* Index of data mid point in rev[] grid */
	for (f = 0; f < fdi; f++) {
		midix[f] = (int)((ctx.ccent[f] - s->rev.gl[f])/s->rev.gw[f] + 0.5);
		mid[f] = (midix[f]+0.5) * s->rev.gw[f] + s->rev.gl[f];
	}
//printf("calc_ocent: mid point = %s\n",debPdv(fdi,mid));

	/* Array for each slice values at angle (+ repeat at end) */
	ctx.debug = 0;
	ctx.s = s;
	ctx.ares = (rgres + 1) & ~1;	/* Make even so that there is an opposite angle */
	if (ctx.ares < 6)
		ctx.ares = 6;
	else if (ctx.ares > 20)
		ctx.ares = 20;
//printf("  ocent ares %d\n",ctx.ares);
	atanscale = ctx.ares/(2.0 * DBL_PI);
	for (ff = 0; ff < fdi; ff++) {
		if ((ctx.p[ff] = (slpoint *)rev_calloc(s, ctx.ares+1,sizeof(slpoint))) == NULL)
			error("rspl malloc failed - calc_ocent arrays");
		INCSZ(s, (ctx.ares+1) * sizeof(slpoint));
	}

	/* Use 5 passes to locate a more reliable initial center point */
	for (nici = 10, ici = 0; ici < nici; ici++) {
//printf("  locating center point iter %d\n",ici);

		/* Set initial radius values */
		for (ff = 0; ff < fdi; ff++) {
			for (aa = 0; aa < ctx.ares; aa++) {
				ctx.p[ff][aa].wrad = -1.0;
				ctx.p[ff][aa].rad = -1.0;
				ctx.p[ff][aa].minrad = 1e38;
			}
		}
				
		/* Take three slices through the rev[] array, plotting */
		/* the maximum circumference for the slice */

		/* For the axis we're slicing */
		for (ff = 0; ff < fdi; ff++) {
			FCOUNT(cc, MXRO, 3);	/* Counter through bwd cells */
			int start[3], endp1[3];
			double vv[MXRO];
			int aa;
			
//printf(" slice axis %d\n",ff);

			/* Setup "fat" slice range */
			for (f = 0; f < fdi; f++) {
				if (f == ff) {
					start[f] = midix[f]-1;
					if (start[f] < 0)
						start[f] = 0;
					endp1[f] = midix[f]+2;
					if (endp1[f] > rgres)
						endp1[f] = rgres;
				} else {
					start[f] = 0;
					endp1[f] = rgres;
				}
			}
			FRECONFA(cc, start, endp1);

//printf("  slice range %d - %d, %d - %d, %d - %d\n", start[0], endp1[0]-1, start[1], endp1[1]-1, start[2], endp1[2]-1);

			/* Scan this 3 thick, 2D slice of rev[] */
			FC_INIT(cc);
			while (!FC_DONE(cc)) {
				int ix;
				int slix[MXRO];		/* Indexes in slice direction + orthogonal */
				int *rp;

				/* Compute bx index */
				ix = 0;
				for (j = f = 0; f < fdi; f++) {
					ix += cc[f] * s->rev.coi[f];
					if (f != ff)
						slix[j++] = f;
				}
				slix[j++] = ff;
//printf("  bx %d, %d ix %d\n",cc[0],cc[1],cc[2],ix);

				if (s->rev.rev[ix] == NULL) {
//printf("  rev is empty\n");
					goto next_bx;
				}

				/* For all the cubes bx rev[] */
				for (rp = s->rev.rev[ix]+3; *rp != -1; rp++) {

					/* For each vertx of this cube */
					for (ee = 0; ee < (1<<di); ee++) {
						int vix = *rp + s->g.hi[ee];
						float *gp = s->g.a + vix * s->g.pss;	/* Pointer to float of fwd vertex */
						double fcb[MXRO];
						double x, y, z, wrad, rad, ang;

						/* Don't add over ink limit vertexes */
						if (s->limiten && gp[-1] > s->limitv) {
							continue;
						}

						for (f = 0; f < fdi; f++)
							fcb[f] = gp[f]; 

						/* (Don't) Convert output values to log values */
						/* logcomp(s, fcb, fcb, ctx.ccent); */

						/* Compute 2D radius and normalize */
						x = fcb[slix[0]] - ctx.ccent[slix[0]];
						y = fcb[slix[1]] - ctx.ccent[slix[1]];
						z = fcb[slix[2]] - ctx.ccent[slix[2]];
						/* wrad is "elipsoid" weighted radius in slice */
						wrad = x * x + y * y - 1.5 * z * z;
						wrad = sqrt(wrad < 0.0 ? 0.0 : wrad);
						rad = sqrt(x * x + y * y);
						if (rad < EPS || wrad < EPS)
							continue;

						/* Quantized angle this point is at */
						ang = atanscale * atan2(y, x);
						aa = (int)floor(ang);
						if (aa < 0)
							aa += ctx.ares;
						if (aa >= ctx.ares) 
							aa -= ctx.ares;

//printf("   slice %d vtx %f %f %f rad %f, ang %f aa %d\n", ff, fcb[0], fcb[1], fcb[2], rad, ang, aa);

						if (wrad > ctx.p[ff][aa].wrad) {
							ctx.p[ff][aa].wrad = wrad;
							ctx.p[ff][aa].rad = rad;

							/* Copy far point */
							for (f = 0; f < fdi; f++)
								ctx.p[ff][aa].v[f] = fcb[f];

							/* (don't) Flatten the points to lie on the notional center */
							/* ctx.p[ff][aa].v[ff] = ctx.ccent[ff]; */
						}

						/* Track min in case ccent is not within slice */
						if (rad < ctx.p[ff][aa].minrad) {
							ctx.p[ff][aa].minrad = rad;
						}
					}
				}
			  next_bx:;
				FC_INC(cc);
			}

			/* Repeat first in extra at end */
			ctx.p[ff][ctx.ares] = ctx.p[ff][0];		/* Structure copy */
		}

		/* Check if center point is within slice by looking for empty entries. */
		{
			double ccvec[MXRO];		/* Center correction vector */
			double ccount = 0.0;
			
			for (f = 0; f < fdi; f++)
				ccvec[f] = 0.0;

			for (ff = 0; ff < fdi; ff++) {
				for (aa = 0; aa < ctx.ares; aa++) {

//printf("   slice %d aa %d, vtx %s rad %f, irad %f\n", ff, aa, debPdv(fdi,ctx.p[ff][aa].v), ctx.p[ff][aa].rad, ctx.p[ff][aa].minrad);

					/* Either the grid is very sparse, or our center */
					/* is outside */
					if (ctx.p[ff][aa].rad < 0.0) {
						int oaa = aa + (ctx.ares/2);
						if (oaa >= ctx.ares) 
							oaa -= ctx.ares;

//printf("   oaa %d, vtx %s rad %f, irad %f\n", oaa, debPdv(fdi,ctx.p[ff][oaa].v), ctx.p[ff][oaa].rad, ctx.p[ff][oaa].minrad);

						/* If oposite side has an entry */
						if (ctx.p[ff][oaa].rad > 0.0) {
							double cor[MXRO];
							double prop = (3.0 * ctx.p[ff][oaa].minrad
							                   + ctx.p[ff][oaa].rad)/4.0;

							prop /= ctx.p[ff][oaa].rad;		/* Proportion of distance to v[] */

							for (f = 0; f < fdi; f++)
								cor[f] = prop * (ctx.p[ff][oaa].v[f] - ctx.ccent[f]);
//printf("   prop %f, corr %s\n",prop,debPdv(fdi,cor));
							for (f = 0; f < fdi; f++)
								ccvec[f] += cor[f];
							ccount++;
						}
					}
				}
			}

//printf("ccount %f\n",ccount);
			if (ccount > 0.0) {	/* Make adjustment */
				if (ici < (nici-1)) {
					for (f = 0; f < fdi; f++)
						ccvec[f] /= ccount;
//printf("Corecting center by %s\n",debPdv(fdi,ccvec));
					for (f = 0; f < fdi; f++)
						ctx.ccent[f] += ccvec[f];
//printf("cceny now %s\n",debPdv(fdi,ctx.ccent));
				} else {		/* Last round and correction needed */
					if (0.0 && s->verbose)
						fprintf(stdout, "%cFailed to locate aprox. gamut center\n",cr_char);
				}
			} else {
				break;		/* We're done */
			}
		}
	}

//printf("calc_ocent: refined ccent = %s\n",debPdv(fdi,ctx.ccent));

	/* Pre-compute point to point info to speed optimization */
	for (ff = 0; ff < fdi; ff++) {
		for (aa = 0; aa < ctx.ares; aa++) {
			double len = 0.0;

			for (f = 0; f < fdi; f++)
				ctx.p[ff][aa+1].cvec[f] = ctx.ccent[f] - ctx.p[ff][aa].v[f];

			for (f = 0; f < fdi; f++) {
				double tt = ctx.p[ff][aa+1].v[f] - ctx.p[ff][aa].v[f];
				len += tt * tt;
			}
			if (len < EPS)
				ctx.p[ff][aa].len = -1.0;
			else
				ctx.p[ff][aa].len = sqrt(len);
		}
	}

	/* Locate center point that maximised the orthogonallity to each */
	/* slice segment. This should maximize visibility of the inner of the */
	/* gamut surface, for shadow testing. */
	for (f = 0; f < fdi; f++)
		ss[f] = fabs(0.1 * (minmax[1][f][f] - minmax[0][f][f]));

//ctx.debug = 1;

	/* return 0 on sucess, 1 on failure due to excessive itterations */
	if (powell(NULL, fdi, s->rev.ocent, ss, 1e-3, 500, aorthog, (void *)&ctx, NULL, NULL)) {
		printf("calc_ocent powell failed\n");
		for (f = 0; f < fdi; f++)
			s->rev.ocent[f] = ctx.ccent[f];
	}

//ctx.debug = 1;

	/* Check result */
	aorthog(&ctx, ctx.ccent);

	/* Hmm. This isn't very reliable in detecting failure. */
	if (ctx.oog)
		printf("calc_ocent failed to return in-gamut focal point!\n");
			
//printf("Final angle = %f\n", ctx.ret);

#ifdef REVVRML		/* Plotting routine declarations */
	/* Diagnostic - dump the gamut slice locii */
	{
		vrml *wrl;
		double grey[3] = { 0.5, 0.5, 0.5 };
		double white[3] = { 1.0, 1.0, 1.0 };
		double red[3] = { 0.8, 0.1, 0.1 };
		double green[3] = { 0.1, 1.0, 0.1 };
		double blue[3] = { 0.1, 0.1, 0.8 };
		double *rgb[3] = { red, green, blue };

		wrl = new_vrml("section", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);
		wrl->add_marker(wrl, s->rev.ocent, NULL, 1.0);

		/* Show vertex labels */
		for (ff = 0; ff < fdi; ff++) {
			char index[100];

			for (aa = 0; aa < ctx.ares; aa++) {
				if (ctx.p[ff][aa].rad > 0) {
					sprintf(index, "%d:%d",ff,aa);
					wrl->add_text(wrl, index, ctx.p[ff][aa].v, white, 1.0);
				}
			}
		}

		/* Axis we're slicing */
		for (ff = 0; ff < fdi; ff++) {
			int vix[100];

			for (aa = 0; aa < ctx.ares; aa++) {
				if (ctx.p[ff][aa].rad > 0)
					vix[aa] = wrl->add_vertex(wrl, 0, ctx.p[ff][aa].v);
			}
			vix[aa] = vix[0];

			for (aa = 0; aa < ctx.ares; aa++) {
				if (ctx.p[ff][aa].rad > 0
				 && ctx.p[ff][aa+1].rad > 0)
					wrl->add_col_line(wrl, 0, vix + aa, rgb[ff]);
			}
		}
		wrl->make_lines_vc(wrl, 0, 0.0);
		printf("Created %s\n",wrl->name);
		wrl->del(wrl);
	}
#endif /* REVVRML */

	/* Free up the context data */
	for (ff = 0; ff < fdi; ff++) {
		free(ctx.p[ff]);
		DECSZ(s, (ctx.ares+1) * sizeof(slpoint));
	}

	DBG(("calc_ocent: final ocent = %s\n",debPdv(fdi,s->rev.ocent)));
//printf("calc_ocent: final ocent = %s\n",debPdv(fdi,s->rev.ocent));
}

/* Create gamut surface linearization (surflin) transform. */
/* This is used by logcomp() to try and straighten out the */
/* device response so that the ocent is "visible" from */
/* any point on the surface. */ 
/* (We assume we are called at the correct point when bx->status == bx_uninit) */
static int calc_surflin(
rspl *s,
vtxcache *vc,		/* Vertexes */
assdire *edgdir		/* Edge lookup for vertex */
) {
	int i, j, g;
	int e, ee, di = s->di;
	int f, ff, fdi = s->fdi;
	vtxrec *vx, *nvx;
	int nitter, itter;

	double vxv[POW2MXRI][MXRO];		/* Overal fwd interp vertex values */
	int nvtx;
	cow *mpoints;
	int gres[MXDO];
	double min[MXDO], max[MXDO];
	double vmin[MXDO], vmax[MXDO];

	for (f = 0; f < fdi; f++)
		gres[f] = s->g.bres;		// ??

//printf("calc_surflin: rspl res %d\n",gres[0]);
	DBG(("calc_surflin: rspl res %d\n",gres[0]));

//printf("~1 gres = %d %d %d\n", s->g.res[0], s->g.res[1], s->g.res[2]);
//printf("~1 ci = %d %d %d\n", s->g.ci[0], s->g.ci[1], s->g.ci[2]);

	/* Lookup interpolation cube corners */
	for (ee = 0; ee < (1<<di); ee++) {
		int ix;
		float *fcb;
		for (ix = e = 0; e < di; e++) {
			if (ee & (1<<e))
				ix += s->g.ci[e] * (s->g.res[e] - 1);
		}
		fcb = s->g.a + ix * s->g.pss;
		for (f = 0; f < fdi; f++)
			vxv[ee][f] = fcb[f];
//printf("~1 cube corners %d = %f %f %f\n",ee, vxv[ee][0], vxv[ee][1], vxv[ee][2]);
	}

//printf("calc_surflin: counting number of vertexes:\n");
	/* Count the number of vertexes we may need */
	nvtx = 0;
	for (i = 0; i < vc->hash_size; i++) {
		for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
			nvtx++;
		}
	}
	DBG(("calc_surflin: %d mapping points\n",nvtx));
//printf("calc_surflin: %d mapping points\n",nvtx);

//printf("calc_surflin: computing goal values:\n");
	/* Currently the vertex vl = vv = output value of vertex. */
	/* Temporarily replace vv with the idealize (linear interp) output "goal" values. */
	for (i = 0; i < vc->hash_size; i++) {
		for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
			int tix;				/* Temp fwd cell index */
			double we[MXRI];		/* Vertex input position == 1.0 - Weight */
			double gw[POW2MXRI];	/* weight for each grid cube corner */
			double w;

			/* Compute this vertexes relative input position */
			for (tix = vx->ix, e = 0; e < di; e++) {
				int dix;
				dix = tix % s->g.res[e];
				tix /= s->g.res[e];
				we[e] = (double)dix/(s->g.res[e]-1.0);
			}
			
			/* Compute corner weights needed for interpolation */
			gw[0] = 1.0;
			for (e = 0, g = 1; e < di; g *= 2, e++) {
				for (j = 0; j < g; j++) {
					gw[g+j] = gw[j] * we[e];
					gw[j] *= (1.0 - we[e]);
				}
			}
		
			/* Linear interpolated output values */
			w = gw[0];
			for (f = 0; f < fdi; f++)			/* Base of cube */
				vx->vv[f] = w * vxv[0][f];
		
			for (g = 1; g < (1<<di); g++) {	/* For all other corners of cube */
				w = gw[g];
				for (f = 0; f < fdi; f++)
					vx->vv[f] += w * vxv[g][f];
			}
		}
	}

	/* Now itteratively adjust the vl values to better match the scaled */
	/* relative positions of the goal values. */

	/* Go through them again to get every line they are part of */
	/* (We're assuming we need exponentially more itters with finer point */
	/* spacing ?) */
	nitter = (int)(0.06 * s->g.bres * s->g.bres + 0.5);
	if (nitter < 1)
		nitter = 1;
	for (itter = 0; itter < nitter; itter++) {

		DBG(("calc_surflin: maping itter %d\n",itter));
//printf("calc_surflin: maping itter %d\n",itter);

		for (i = 0; i < vc->hash_size; i++) {
			for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
				assdire *edg;			/* Edge table */
				float *fp;
				int fl;
				double agrad, aorad;
				int nn;
				double scale;

				fp = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
				fl = FLV(fp);		/* Edge flags for this vertex */
				edg = edgdir + fl;

				/* For vertexes at the end of all possible edges common with this vertex, */
				/* compute average radius */
				agrad = aorad = 0.0;
				for (j = 0; j < edgdir[fl].no; j++) {
					int eix;

					/* Index number of vertex other than the one we got it from */
					if (edg->ti[j].goffs[0] != 0)
						eix = vx->ix + edg->ti[j].goffs[0];
					else
						eix = vx->ix + edg->ti[j].goffs[1];

					if ((nvx = get_vtxrec(vc, eix)) != NULL) {
						double glen, olen;
						glen = olen = 0.0;
						for (f = 0; f < fdi; f++) {
							double tt;

							tt = vx->vv[f] - nvx->vv[f];
							glen += tt * tt;

							tt = vx->vl[f] - nvx->vl[f];
							olen += tt * tt;
						}
						glen = sqrt(glen);
						olen = sqrt(olen);
						agrad += glen;
						aorad += olen;
						nn++;
					}
				}

				if (nn == 0) {					/* Hmm. No neighors ? */
					vx->status = vtx_del;		/* Mark it as isolated */
					continue;
				}

				scale = aorad/agrad;	/* Local scale factor from goal to output */

				/* Reset the current vertex output value based on the relative */
				/* position of it in goal space */
				for (f = 0; f < fdi; f++)
					vx->vl[f] = 0.0;

				nn = 0;
				for (j = 0; j < edgdir[fl].no; j++) {
					int eix;

					/* Index number of vertex other than the one we got it from */
					if (edg->ti[j].goffs[0] != 0)
						eix = vx->ix + edg->ti[j].goffs[0];
					else
						eix = vx->ix + edg->ti[j].goffs[1];

					if ((nvx = get_vtxrec(vc, eix)) != NULL) {
						for (f = 0; f < fdi; f++)
							vx->vl[f] += nvx->vl[f] + scale * (vx->vv[f] - nvx->vv[f]);
						nn++;
					}
				}
				for (f = 0; f < fdi; f++)
					vx->vl[f] /= (double)nn;
			}
		}
	}

	DBG(("calc_surflin: creating rspl\n"));
//printf("calc_surflin: creating rspl\n");

	/* Now construct rspl setup mapping points from vertex normal output values */
	/* to adjusted vl values */

	if ((s->rev.surflin = new_rspl(RSPL_NOFLAGS, fdi, fdi)) == NULL)
		error("calc_surflin: new_rspl failed");

	nvtx++;		/* One for center point */

	/* Allocate rspl setup points */
	if ((mpoints = malloc(sizeof(cow) * 2 * nvtx)) == NULL)
//	if ((mpoints = malloc(sizeof(cow) * nvtx)) == NULL)
		error("calc_surflin: malloc of %d rspl setup points failed",nvtx);
	
	nvtx = 0;

#ifndef NEVER
	/* Center point */
	for (f = 0; f < fdi; f++) {
		mpoints[nvtx].p[f] = s->rev.ocent[f]; 
		mpoints[nvtx].v[f] = s->rev.ocent[f]; 
	}
	mpoints[nvtx].w = 10.0;
	nvtx++;
#endif

	/* Set the surface mapping points and restore vertexes values */
	for (i = 0; i < vc->hash_size; i++) {
		for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
			float *fcb;

			fcb = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */

			/* Actual output value as source of mapping */
			for (f = 0; f < fdi; f++)
				mpoints[nvtx].p[f] = vx->vv[f] = fcb[f];

			if (vx->status != vtx_norm) {	/* Skip isolated values */
				vx->status = vtx_norm;		/* Restore vtx contents */
				for (f = 0; f < fdi; f++)
					vx->vl[f] = vx->vv[f];
				continue;
			}

			for (f = 0; f < fdi; f++)
				mpoints[nvtx].v[f] = vx->vl[f];
			mpoints[nvtx].w = 1.0;
			nvtx++;

			vx->status = vtx_norm;		/* Restore vtx contents */
			for (f = 0; f < fdi; f++)
				vx->vl[f] = vx->vv[f];
			
//printf("Map[%d] %f %f %f -> %f %f %f\n", nvtx-1, mpoints[nvtx-1].p[0], mpoints[nvtx-1].p[1], mpoints[nvtx-1].p[2], mpoints[nvtx-1].v[0], mpoints[nvtx-1].v[1], mpoints[nvtx-1].v[2]);

#ifndef NEVER
			/* Add intermediate "fixed" point */
			for (f = 0; f < fdi; f++) {
				mpoints[nvtx].p[f] = mpoints[nvtx].v[f]
				                  = 0.5 * mpoints[nvtx-1].p[f] + 0.5 * s->rev.ocent[f];
			}
			mpoints[nvtx].w = 0.5;
			nvtx++;
#endif
		}
	}

	for (f = 0; f < fdi; f++) {
		min[f] = 1e38;
		max[f] = -1e38;
		vmin[f] = 1e38;
		vmax[f] = -1e38;
	}
	for (i = 0; i < nvtx; i++) {

#ifdef NEVER
		/* Blend with original values */
		for (f = 0; f < fdi; f++)
			mpoints[i].v[f] = 0.5 * mpoints[i].p[f] + 0.5 * mpoints[i].v[f];
#endif

		for (f = 0; f < fdi; f++) {
			if (mpoints[i].p[f] < min[f])
				min[f] = mpoints[i].p[f];
			if (mpoints[i].p[f] > max[f])
				max[f] = mpoints[i].p[f];

			if (mpoints[i].v[f] < vmin[f])
				vmin[f] = mpoints[i].v[f];
			if (mpoints[i].v[f] > vmax[f])
				vmax[f] = mpoints[i].v[f];
		}
	}

#ifdef REVVRML	/* Plot mapping vectors red->green */
	{
		vrml *wrl;
		double red[3] = { 1.0, 0.0, 0.0 };
		double green[3] = { 0.0, 1.0, 0.0 };

		wrl = new_vrml("suflinvecss", 0, vrml_lab);
		wrl->start_line_set(wrl, 0);

		for (i = 0; i < nvtx; i++) {
			wrl->add_col_vertex(wrl, 0, mpoints[i].p, red);
			wrl->add_col_vertex(wrl, 0, mpoints[i].v, green);
			
		}

		wrl->make_lines(wrl, 0, 2);
		wrl->del(wrl);
	}
#endif

	DBG(("calc_surflin: mapping points set, about to creat rspl:\n"));
//printf("calc_surflin: mapping points set, about to creat rspl:\n");

	/* Fit the rspl */
	s->rev.surflin->fit_rspl_w(s->rev.surflin, RSPL_NOFLAGS, mpoints, nvtx,  
	                           min, max, gres, vmin, vmax, 4.0, NULL, NULL);   

	DBG(("calc_surflin: mapping created\n"));
//printf("calc_surflin: mapping created\n");

#ifdef NEVER
	{
		double de;
		co p;
		extern double icmNorm33(double *, double *);

		/* Check fit */
		de = 0.0;

		for (i = 0; i < nvtx; i++) {
			for (f = 0; f < fdi; f++)
				p.p[f] = mpoints[i].p[f];

			s->rev.surflin->interp(s->rev.surflin, &p);
	
			de += icmNorm33(mpoints[i].v, p.v);
		}
		de = de/(double)nvtx;

		printf("Avg fit error = %f\n",de);
	}
#endif
	
	free(mpoints);

	/* Lookup ocent mapping offset */
	{
		co p;

		for (f = 0; f < fdi; f++)
			p.p[f] = s->rev.ocent[f];
		s->rev.surflin->interp(s->rev.surflin, &p);
//printf("opoint mapping %f %f %f -> %f %f %f\n", p.p[0], p.p[1], p.p[2], p.v[0], p.v[1], p.v[2]);
		for (f = 0; f < fdi; f++)
			s->rev.linoff[f] = p.v[f] - s->rev.ocent[f];
	}
	

#ifndef NEVER
	/* Put the transform into use */
	s->rev.surflin_en = 1;

	/* Transform all the vertexes */
	for (i = 0; i < vc->hash_size; i++) {
		for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
			logcomp(s, vx->vl, vx->vv, s->rev.ocent);

			/* Compute distance to overall center point squared */
			vx->dist = 0.0;
			for (f = 0; f < fdi; f++) {
				double tt = vx->vl[f] - s->rev.ocent[f]; 
				vx->dist += tt * tt;
			}
		}
	}
#endif

	return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Initialise the rev Second section acceleration information. */
/* This is called when it is discovered on a call that s->rev.rev_valid == 0 */
static void init_revaccell(
rspl *s
) {
	int i, j;		/* Index of fwd grid point */
	int e, f, ee, ff;
	int di = s->di;
	int fdi = s->fdi;
	int gno = s->g.no;
	int rgno = s->rev.no;
	int rgres = s->rev.res;			/* number of bwd cells */
	int rgres_1 = rgres-1;			/* rgres -1 == maximum base coord value */

	schbase *b = s->rev.sb;		/* Base search information */
	char *vflag = NULL;			/* Per bwd vertex flag used during construction of nnrev */
								/* 0  nnrev[] cell empty,       not surface */
								/* 1  nnrev[] done/don't fill,  not surface */
								/* 2  nnrev[] cell empty,        on surface */
								/* 3  nnrev[] done,              on surface */
								/* 1X nnrev[] contains ink limited fwcells */
								/* Note that bit 1 can be set for cells that are not */
								/* to be explored because they are in the gamut interior, */
								/* and because they have already been added to the seedlist. */
	int pass;					/* Construction pass */
	float *gp;					/* Pointer to fwd grid points */

	DCOUNT(gg, MXRO, fdi, 0, 0, rgres);	/* Track the prime seed coordinate */
	int nn[MXRO];						/* bwd neighbor coordinate */

	vtxcache vc;				/* List + cache of vertexes being processed */
	tricache tc;				/* cache of surface triangles that have been processed */
	tricache stc;				/* small cache of surface triangles that have been processed */
	bxcell *bx, *nbx, **pbx;
	bxcell *xlist = NULL;		/* Linked list of added surface bxcells */
	assdire *tridir = NULL;		/* Triangle tables */
	assdire *edgdir = NULL;		/* Edge tables */
	double **cla = NULL;		/* Line LHS implicit equation matrix [fdi][fdi+1] */
	double *ta[MXRO], TA[MXRO][MXRO];	/* temp for intersection solving */

#if defined(REVTABLESTATS) || defined(DEBUG)
	/* Some statistics */
	unsigned long smsec;
	int nskcells = 0;					/* Number of skipped cells because over ink limit (debug) */
	int nascells = 0;					/* Number of added surface cells */
	int nrscells = 0;					/* Number of removed surface cells */
	int naoulvtxs = 0;					/* Number of added over ink limit vertexes */
	int revcells = 0;					/* Non-empty rev[] cells */
	int revcelldepth = 0;				/* Sum of rev[] list lengths */
	int ingamutcells = 0;				/* No of rev[] cells not on surface */
	int surfcells = 0;					/* No. surface cells */
	int emptycells = 0;					/* No. empty cells */
	int nnrevcells = 0;					/* Non-empty nnrev[] cells */
	int nnrevcellsearch = 0;			/* Sum of number of surface cells searched */
	int nnsinglefill = 0;				/* Number of nnrev[] cells seeded singly */
	int nnsuperfill = 0;				/* Number of nnrev[] cells seeded using supercell */
	int nnrevcelldepth = 0;				/* Sum of nnrev[] list lengths */
	int nnmxrevcelldepth = 0;			/* Maximum nnrev[] list lengths */
	int nnrevshare = 0;					/* Sum of nnrev[] list reference counts */
#endif
	datao rgmin, rgmax;

	DBG(("init_revaccell called, di = %d, fdi = %d, mgres = %d\n",di,fdi,(int)s->g.mres));

	/* To help VRML diagnostics, make a guess as to whether the output */
	/* space is XYZ like, or L*a*b* like */

	s->get_out_range(s, rgmin, rgmax);	/* overall output min/max */

	if (fdi >= 3
	 && rgmin[0] >= -1.0 && rgmax[0] < 3.0
	 && rgmin[1] >= -1.0 && rgmax[1] < 3.0
	 && rgmin[2] >= -1.0 && rgmax[2] < 3.0) {
		s->rev.probxyz = 1;
		if (s->verbose)
			fprintf(stdout, "%cLooks like an XYZ space\n",cr_char);
	}

	if (fdi > 1 && s->verbose)
		fprintf(stdout, "%cInitializing nnrev arrays...\n",cr_char);

	/* Add this instance into memory management */
	if (s->rev.rev_valid == 0 && di > 1) {
		rev_struct *rsi;
		size_t ram_portion = g_avail_ram;

		/* Add into linked list */
		s->rev.next = g_rev_instances;
		g_rev_instances = &s->rev;

		/* Aportion the memory, and reduce cache if it is over new limit. */
		g_no_rev_cache_instances++;
		ram_portion /= g_no_rev_cache_instances; 
		for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next) {
			revcache *rc = rsi->cache;

			rsi->max_sz = ram_portion;
			while (rc->nunlocked > 0 && rsi->sz > rsi->max_sz) {
				if (decrease_revcache(rc) == 0)
					break;
			}
//printf("~1 rev instance ram = %lu MB\n",(unsigned long)(rsi->sz/1000000));
		}
		
		if (s->verbose)
			fprintf(stdout, "%cThere %s %d rev cache instance%s with %lu Mbytes limit\n",
			                    cr_char,
								g_no_rev_cache_instances > 1 ? "are" : "is",
			                    g_no_rev_cache_instances,
								g_no_rev_cache_instances > 1 ? "s" : "",
			                    (unsigned long)(ram_portion/1000000));
	}

#if defined(REVTABLESTATS) || defined(DEBUG)
	smsec = msec_time();
#endif

	/* Temporary per bwd vertex/cell flag for nn setup */
	if ((vflag = (char *) rev_calloc(s, rgno, sizeof(char))) == NULL)
		error("rspl malloc failed - rev.vflag points");
	INCSZ(s, rgno * sizeof(char));

	/*
	 * The rev[] and nnrev[] grids contain pointers to lists of grid cube base indexes.
	 * If the pointer is NULL, then there are no base indexes in that list.
	 * A non NULL list uses element [0] to indicate the allocation size of the list,
	 * [1] contains the index of the next free location, [2] contains the reference
     * count (lists may be shared), the list starts at [3]. The last entry is marked with -1.
	 */

	/* We won't include any fwd cells that are over the ink limit, */
	/* so makes sure that the fwd cell nodes all have an ink limit value. */ 
	if (b != NULL && s->limiten) {
		ECOUNT(gc, MXDIDO, s->di, 0, s->g.res, 0);    /* coordinates */
		double iv[MXDI];				/* Input value corresponding to grid */

		DBG(("Looking up fwd vertex ink limit values\n"));
//printf("Looking up fwd vertex ink limit values\n");
//printf("s->limitv = %f\n",s->limitv);
		/* Calling the limit function for each fwd vertex could be bad */
		/* if the limit function is slow. Maybe an octree type algorithm */
		/* could be used if this is a problem ? */
		EC_INIT(gc);
		for (i = 0, gp = s->g.a; i < s->g.no; i++, gp += s->g.pss) {
			if (gp[-1] == L_UNINIT) {
				for (e = 0; e < di; e++)
					iv[e] = s->g.l[e] + gc[e] * s->g.w[e];  /* Input sample values */
				gp[-1] = (float)(INKSCALE * s->limitf(s->lcntx, iv));
//printf("~1 set ix %d limitv to %f\n",i,gp[-1]);
			}
//else printf("~1 ix %d limitv is %f\n",i,gp[-1]);
			EC_INC(gc);
		}
		s->g.limitv_cached = 1;
	}

	/* We then fill in the in-gamut reverse grid lookups, */
	/* and identify nnrev prime seed vertices to put in the surface bxcells. */

	DBG(("filling in rev.rev[] grid\n"));
	
	/* To create rev.rev[], for all fwd grid points, form the cube with that */
	/* point at its base, and determine the bounding box of the output values */
	/* that could intersect that fwd cube. Add that fwd index to the lists of */
	/* of all bwd cells that the bounding box intersects. */
	/* As a start for creating surface bxcell list, flag which bwd vertices */
	/* are covered by the fwd grid output range. */

	/* Pre-marking device edge rev cells creates many more initial cells, */
	/* but avoids having to discover them with multiple passes ? */
	for (gp = s->g.a, i = 0; i < gno; gp += s->g.pss, i++) {
		datao min, max;
		int imin[MXRO], imax[MXRO], gc[MXRO];
		int edge = 0;		/* This fwd cell contains a device edge */
		int uil;			/* One is under the ink limit */
		int oil;			/* One is over the ink limit */

//printf("~1 i = %d/%d\n",i,gno);
		/* Skip grid points on the upper edge of the grid, since there */
		/* is no further grid point to form a cube range with. */
		for (e = 0; e < di; e++) {
			int flags = G_FL(gp, e);

			if (flags == 0)		/* At the top edge */
				break;

			/* If we at the bottom edge, or one away from top edge */
			if (flags == 4 || flags == 1)
				edge = 1;				/* This fwd cell is on device gamut edge */
		}
		if (e < di) {	/* Top edge - skip this cube */
//printf("~1 skipping base vertex %d on top edge\n",i);
			continue;
		}

//printf("~1 adding to rev[]\n");

		/* Find the output value bounding box values for this grid cell */
		/* Start with base vertex */
		uil = oil = 0;
		for (f = 0; f < fdi; f++)	/* Init output min/max */
			min[f] = max[f] = gp[f];

		if (!s->limiten || gp[-1] <= s->limitv)
			uil = 1;
		else
			edge = oil = 1;		/* May be stradling ink limit edge */
	
		/* Then add all other fwd cube vertices */
		for (ee = 1; ee < (1 << di); ee++) {
			float *gt = gp + s->g.fhi[ee];	/* Pointer to cube vertex */
			
			if (!s->limiten || gt[-1] <= s->limitv)
				uil = 1;
			else
				edge = oil = 1;

			/* Update bounding box for this grid point */
			for (f = 0; f < fdi; f++) {
				if (min[f] > gt[f])	
					 min[f] = gt[f];
				if (max[f] < gt[f])
					 max[f] = gt[f];
			}
		}

		/* Skip any fwd cells that have every vertex over the ink limit */
		if (!uil) {
#if defined(REVTABLESTATS) || defined(DEBUG)
			nskcells++;
#endif
			continue;
		}

		/* Figure out intersection range in bwd cell grid */
		for (f = 0; f < fdi; f++) {
			double t;
			int mi;
			double gw = s->rev.gw[f];
			double gl = s->rev.gl[f];
			t = (min[f] - gl - EPS)/gw;
			mi = (int)floor(t);			/* Grid coordinate */
			if (mi < 0)					/* Limit to valid cube base index range */
				mi = 0;
			else if (mi > rgres_1)
				mi = rgres_1;
			imin[f] = mi;	
			t = (max[f] - gl + EPS)/gw;
			mi = (int)floor(t);			/* Grid coordinate */
			if (mi < 0)					/* Limit to valid cube base index range */
				mi = 0;
			else if (mi > rgres_1)
				mi = rgres_1;
			imax[f] = mi;	
		}

//printf(" Scanning over bwd cell range grid:\n");
//for (f = 0; f < fdi; f++)
//printf(" Min[%d] = %d -> Max[%d] = %d\n",f,imin[f],f,imax[f]);

		/* Now create forward index and vector with all the reverse grid cells */
		for (f = 0; f < fdi; f++)
			gc[f] = imin[f];	/* init coords */

		/* Until increment at bottom carries */
		for (f = 0; f < fdi;) {	/* For all of intersect bwd cube */
			int **rpp;
			char *vflagp;
			
			/* Compute pointer to bwd grid cell and vflag[] */
			for (rpp = s->rev.rev, vflagp = vflag, f = 0; f < fdi; f++) {
				int inc = gc[f] * s->rev.coi[f];
				rpp    += inc;
				vflagp += inc;
			}

#undef PRE_LOAD_SURFACE		/* [und] Makes it slower ? */
#ifdef PRE_LOAD_SURFACE				/* Pre-load device edge cells */
			if (edge) {
				*vflagp = 2;		/* This is definitely a gamut surface bwd cell */
									/* and so nnrev[] needs to be filled */
			} else
#endif
			if (*vflagp == 0) {
				*vflagp = 1;		/* This is possibly not a surface bwd cell, */
									/* and otherwise is an inside gamut bwd cell */
			}

			if (oil)
				*vflagp |= 0x10;	/* Contains over ink limit vertexes */

//printf("seting vflag[%d] to surface done (%x)\n",vflagp-vflag,*vflagp);

//printf("  Currently at grid ix %d, (vflag = %x) adding fwd %d:\n",vflagp-vflag,*vflagp,i);
//for (f = 0; f < fdi; f++)
//printf("  gc[%d] = %d\n",f,gc[f]);

			/* Add fwd cells to rev[] list */
			add2indexlist(s, rpp, i, 0);

			/* Increment index up to and including imax[] */
			for (f = 0; f < fdi; f++) {
				gc[f]++;
				if (gc[f] <= imax[f])
					break;	/* No carry */
				gc[f] = imin[f];
			}
		}	/* Next reverse grid point in intersecting cube */
	}	/* Next base grid point */

	DBG(("We skipped %d/%d cells that were over the limit\n",nskcells,gno));

#ifdef CHECK_NNLU
	if (fdi > 1) {
		/* Check that every flagged rev[] cell is filled */
		printf("Checking all %d flagged rev[] cells are filled\n",rgno);
		for (i = 0; i < rgno; i++) {
			if (   (vflag[i] & 1) != 0 
			     && (s->rev.rev[i] == NULL || s->rev.rev[i][1] == 3)) {
				printf("Found empty rev[%d] ?:\n",i);
				printf(" vflag %x\n",vflag[i]);
				if (s->rev.rev[i] == NULL)
					printf(" rev = NULL\n");
				else
					printf(" rev = length = %d\n",s->rev.rev[i][1]-3);
			}
		} 
	}
#endif	/* CHECK_NNLU */

	/* If doing fast setup, then this is all we need. */
	if (s->rev.fastsetup) {

		/* Free up flag array used for construction */
		if (vflag != NULL) {
			DECSZ(s, rgno * sizeof(char));
			free(vflag);
		}

		s->rev.rev_valid = 1;

		if (fdi > 1 && s->verbose)
			fprintf(stdout, "%cFast nnrev initialization done\n",cr_char);

		DBG(("init_revaccell fastsetup finished\n"));

#if defined(REVTABLESTATS) || defined(DEBUG)
		printf("Fastsetup took %f seconds\n",0.001 * (msec_time()-smsec));
#endif

		return;
	}

	/* Rough outline of overall nn setup process:

		Fill rev[] array by scanning fwd cells.

		Locating initial surface bwd cells.

		loop:
			Fill empty surface cells from rev[] list and convert to vertexes.

			(In two phases, first just against primary bx, second with all 
			 shadowed bx's:)

			Test all triangles against all vertexes and mark those that are shadowed.

			Remove vertexes from bx if they have been deleted, but leave
			them in the vertex cache for testing against.

			If any vertexes of a bx land outside it in a bx that is not
			part of the surface list, add that bx to the surface list and
			mark it for processing.

			Track which bx cells shadow newly added bx cells, 
			so that new bx cells get tested against all their shadowers,
			as well as being used to test against their shadowees.

		Locate and preserve all overlapping surface triangles.

		Delete any shadowed vertexes, and remove any empty bxcells.

		Add extra over ink limit vertexes.

		Convert vertexes back to minimum number of fwd cubes.

	*/

	calc_ocent(s);

	/* Locate and process the surface bxcells and fill the nnrev array if we */
	/* are not doing a fast setup. (fastsetup will instead fill the nnrev[] array */
	/* on demand, by searching the rev[] array.) */
	DBG(("Identifying surface rev cells\n"));

	/* Allocate the surflist hash index. */
	/* (Note that we track bxcells in the surface list rather */
	/* than the hash list, in this context.) */
	create_surfhash(s);

	/* Locate surface reverse cells */
	DC_INIT(gg);
	for (i = 0; i < rgno; i++) {

		if ((vflag[i] & 0xf) == 1) {	/* if filled rev[] cell but not surface */ 
			char *vflagp;

			/* Check face neighbors */
			int cc[MXDO];				/* Neigbor offset counter */

			/* Check if any of the face neigbors of this bwd cell are empty. */
			/* If so, mark it as a surface cell. */
			/* [This won't detect all surface nncells, but will hit most of them */
			/* without including too many false ones. The vertex filter code */
			/* should discover any surface nncells that are missed.] */
			for (f = 0; f < fdi; f++)
				cc[f] = gg[f];
			vflagp = vflag + i;

			for (ff = 0; ff < (fdi << 1); ff++) {
				f = ff >> 1;

				cc[f]  += (ff & 1) ? 1 : -1;
				vflagp += (ff & 1) ? s->rev.coi[f] : -s->rev.coi[f];

				/* Out of bounds or empty */
				if (cc[f] < 0 || cc[f] >= rgres || ((*vflagp & 0xf) == 0)) {
					vflag[i] = (vflag[i] & ~0xf) | 2;	/* Convert this one to empty surface cell */
//printf("seting vflag[%d] to surface cell (%x)\n",i,vflag[i]);

					/* Add a bxcell to surf hash. Initial status = bx_uninit */
					if ((bx = get_surface_bxcell(s, i)) == NULL) {
						/* Since it's a surface point, the seeding point is itself (NULL). */
						bx = new_bxcell(s, i, gg, NULL, 0.0, NULL);
						add_bxcell_hash(s, bx);
		
						/* Add to surface linked list */
						bx->slist = s->rev.surflist; 
						s->rev.surflist = bx;
//printf("~1 adding nnrev[%d] to surface list\n",bx->ix);
					}
					break;		
				}

				cc[f]  -= (ff & 1) ? 1 : -1;
				vflagp -= (ff & 1) ? s->rev.coi[f] : -s->rev.coi[f];
			}
		} 
#ifdef PRE_LOAD_SURFACE
		  else if ((vflag[i] & 0xf) == 2) {		/* Pre-marked surface rev cell */

			/* Add a bxcell to surf hash. Initial status = bx_uninit */
			if ((bx = get_surface_bxcell(s, i)) == NULL) {
				/* Since it's a surface point, the seeding point is itself (NULL). */
				bx = new_bxcell(s, i, gg, NULL, 0.0, NULL);
				add_bxcell_hash(s, bx);

				/* Add to surface linked list */
				bx->slist = s->rev.surflist; 
				s->rev.surflist = bx;
//printf("~1 adding pre-marked nnrev[%d] to surface list\n",bx->ix);
			}
		}
#endif /* PRE_LOAD_SURFACE */

#if defined(REVTABLESTATS) || defined(DEBUG)
		if (vflag[i] & 2) 
			surfcells++;
		else if ((vflag[i] & 0xf) != 0)
			ingamutcells++;
		else 
			emptycells++;

		if (s->rev.rev[i] != NULL) {
			revcells++;
			revcelldepth += s->rev.rev[i][1]-3;
		}
#endif
		DC_INC(gg);
	}

	if (di < 2)
	{
		/* Create surface fwd cell list */
		DBG(("create surface fwd cell lists\n"));

		/* For each rev[] containing fwd cells, */
		/* copy the cells to the corresponding surface bxcel cell */
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int *crp, *rp;
	
			if ((crp = s->rev.rev[bx->ix]) == NULL)
				error("Surface list bxcell ix %d has no vertexes",bx->ix);

			/* For each fwd cell in surface rev[] */ 
			for (rp = crp+3; *rp != -1; rp++) {
				add2indexlist(s, &bx->sl, *rp, 0);
			}
		}

	} else {		/* di >= 3 gamut surface finding */

#ifdef REVVRML
		/* Plot the initial surface bxcells & their fwd cells. */
		/* Rev cells? Fwd cells? Fwd cell base indexs? */
		if (0) plot_bxfwcells(s, 0, 1, 0);
#endif /* REVVRML */

		/* per reverse cell vertex cache */
		create_vtxrec_list(s, &vc);

		/* per reverse cell surface triangle cache */
		create_trirec(s, &tc, 0);

		/* small per reverse cell surface triangle cache */
		create_trirec(s, &stc, 1);
	
		/* create associated sub-simplex (triangle) lookup table */
		init_assdir(s, &tridir, 2);

		/* create associated sub-simplex (edge) lookup table */
		init_assdir(s, &edgdir, 1);
	
		/* Process surface bxcells */
		/* (Maintain current list of vtxrec's for all vertexes) */
			
		/* Setup temporary matrix */
		for (f = 0; f < 2; f++)
			ta[f] = TA[f];

		/* - - - - - - - - - - - - - - */
		/* fill, thin and add, until */
		/* there is no more work to do. */
		for (pass = 0;; pass++) {
			int phase;
			int morevtxadded = 0;
#if defined(REVTABLESTATS) || defined(DEBUG)
			unsigned long lmsec = msec_time();
			int thcount = 0, rethcount = 0;

//			printf("At top of gamut surface loop\n");
#endif

			/* For each surface bxcell, convert the corresponding */
			/* rev[] fwd cubes into vertices. */
			/* (Must keep bxcells even if none of their vertices */
			/*  are physically in them, so that those vertices get thinned. */
			/*  could only remove them if vertex was not in any surface cell ?) */
			for (pbx = &s->rev.surflist, bx = *pbx; bx != NULL; bx = nbx) {
				int *crp, *rp, *nrp;
				vtxrec *vx;

				nbx = bx->slist;
		
				if (bx->status != bx_uninit) {
					pbx = &bx->slist;
					continue;
				}

				if ((crp = s->rev.rev[bx->ix]) == NULL)
					error("Surface list bxcell ix %d has no vertexes",bx->ix);
		
//printf("Initializing bxcell %d with vertexes\n",bx->ix);
				/* For each fwd cell in surface rev[] */ 
				for (rp = crp+3; *rp != -1; rp++) {
		
//adding cube %d to bx %d\n",*rp, bx->ix);
					/* For each vertex of cube */
					for (ee = 0; ee < (1<<di); ee++) {
						int vix = *rp + s->g.hi[ee];
						float *fcb = s->g.a + vix * s->g.pss;	/* Pointer to base float of fwd cell */
						vtxrec *vx;
		
//printf("~1 adding cube %d vtx %d to bx %d\n",*rp, vix, bx->ix);

						/* Don't add over ink limit vertexes */
						/* (we'll re-add them in later) */
						if (s->limiten && fcb[-1] > s->limitv) {
//printf("Skipping vtx %d because over ink limit\n",vix);
							continue;
						}
		
						if ((vx = get_vtxrec(&vc, vix)) != NULL) {
							if (vx->rix == bx->ix) {
//printf("Already have vertex %d in bx %d\n",vx->ix,vx->rix);
							}

							/* Skip vertexes that we've already added to this bxcell */
							if (vx->tix == bx->ix) {
//printf("Skipping vtx %d because alread in bx %d\n",vix,bx->ix);
								continue;
							}
						} else {
							/* Create new vertex */
							vx = new_vtxrec(s, &vc, vix); 
							vx->tix = bx->ix;		/* Added to this bx */
//printf("Create vtx %d for bx %d (actually in bx %d)\n",vix,bx->ix,vx->rix);
						}
		
						/* Add vertex to bxcell sl list */
						add2indexlist(s, &bx->sl, vix, 0);

						if (vx->rix == bx->ix) {
//printf("Added vertex %d is in this bx %d\n",vx->ix,vx->rix);
						} else {
//printf("Added vertex %d is in different bx %d to this one %d\n",vx->ix,vx->rix,bx->ix);
						}
					}
				}
				/* Expand a bxcell's shadow testing group values based on it's vertex list */
				/* so that shadow testing works correctly for vertexes that don't */
				/* actually lie within the bxcell. (Note that in fact the triangle */
				/* testing creates triangles that are made of vertexes that may not */
				/* be in this bx's list, so the shadow size doesn't accuratly  represent */
				/* the possible shadow area. It's not clear what consequences this has, */
				/* if any. If we expanded the group to cover this, we would need to have */
				/* two groups, a shadower group including those vertexes, and a shadowee */
				/* goup for just those vertexes that are part of the bx. */
				extend_bxcell_shadow_group(s, &vc, bx);
				bx->status = bx_filled;
				pbx = &bx->slist;
				morevtxadded = 1;
			}
	
			/* Compute transform rspl that helps "unfold" any regions of the surface */
			/* that overlap from the perspective of ocent, to try and avoid gaps in */
			/* the final gamut surface. Existing vtxrec are converted to have vl */
			/* in the unfolded space. */
			if (pass == 0 /* && function flag set */) {
#ifndef EN_UNTWIST		/* Control using an environment variable */
				if (getenv("ARGYLL_UNTWIST_GAMUT_SURFACE") != NULL)
#endif
				{
					calc_surflin(s, &vc, edgdir);
				}
			}

			DBG(("thinning surface vertex lists and converting to cells\n"));

			/* (Sorting bxcells doesn't seem to make any performace difference.) */

			for (phase = 0; phase < 2; phase++) {

//printf("Phase %d\n",phase);

				/* For each surface bxcell, form triangles from vertexes */
				/* and mark as shadowed and other vertexes that are in the */
				/* triangles shadow. */
				/* rev[] fwd cubes into vertices. */
				for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
					int sdi = 2;						/* sub-simplexes are triangles */
					double clb[MXRO+1];	/* Line RHS implicit equation vector [fdi+1] */
					int *crp, *rp, *nrp;
					vtxrec *vx, *nvx;
					int aftercount;			/* vertex count after thinning */

					if (bx->status != bx_filled && bx->status != bx_rethinnd) {
//printf("~1 skipping bx %d because status = %d\n",bx->ix,bx->status);
						continue;
					}
//printf("~1 checking bx %d\n",bx->ix);

					/* Only do first pass through primary alone if never thinned before */
					if (phase == 0 && bx->status == bx_rethinnd) {
						continue;
					}

					/* If this bxcell is empty (because all it's vertexes are shadowed ?) */
					if (bx->sl == NULL || bx->sl[1] == 3) {
//printf("~1 skipping nnrev[%d] because it's empty\n",bx->ix);
						continue;
					}
//printf("Thinning bxcell %d\n",bx->ix);
					/* Create nnrev[] shadowing linked list. nnrev[] cells who's shadow in */
					/* the direction of rev.ocent[] touches another nnrev[], add that nnrev[] */
					/* to their shadow list. This allows us to filter vertexes in other */
					/* nnrev[] cells from triangles above them */
					bx->wlist = NULL;

					/* Only go through all shadowed bxcells once primary has been */
					/* thinned alone */
					if (phase == 1) {

						/* Use just extra list for re-thinning, for 10% speed advantage. */
						if (bx->status == bx_rethinnd && xlist != NULL) {
//printf("Adding shadows to bxcell %d from xlist\n",bx->ix);
							for (nbx = xlist; nbx != NULL; nbx = nbx->xlist) {

								if (nbx->status == bx_uninit)	/* Newly added cells (shouldn't happen) */
									break;

								if (nbx == bx)
									continue;
							
								/* If any of bx is further from nbx and their bounding */
								/* cylinders overlap in perspective from rev.ocenter, */
								/* assume nbx is a shadow */
								if (shadow_group_group(s, s->rev.ocent, bx->g.bcent, bx->cc,
									                 bx->dw, nbx->g.bcent, nbx->cc, nbx->dw)) {
									nbx->wlist = bx->wlist;
									bx->wlist = nbx;
//printf("~1 adding shadow nnrev[%d] from xlist\n",nbx->ix);
								}
							}
						} else {

//printf("Adding shadows to bxcell %d from surflist\n",bx->ix);
							for (nbx = s->rev.surflist; nbx != NULL; nbx = nbx->slist) {

//printf("Considering bx %d for shadow list\n",nbx->ix);
								if (nbx->status == bx_uninit) /* Newly added cells (shouldn't happen) */
									break;

								if (nbx == bx) 
									continue;
							
								/* If any of bx is further from nbx and their bounding */
								/* cylinders overlap in perspective from rev.ocenter, */
								/* assume nbx is a shadow */
								if (shadow_group_group(s, s->rev.ocent, bx->g.bcent, bx->cc, bx->dw,    
									                                    nbx->g.bcent, nbx->cc, nbx->dw))
								{
//printf("Added bx %d for shadow list, prim bx %d\n",nbx->ix,bx->ix);
									nbx->wlist = bx->wlist;
									bx->wlist = nbx;
								}
							}
						}
					}	/* if phase == 1 */

#if defined(REVTABLESTATS) || defined(DEBUG)
					if (bx->status == bx_rethinnd)
						rethcount++;
					else
						thcount++;
#endif

					/* Abort doing this cell until all its shadowees are filled */
					/* (Shouldn't happen ?) */
					if (nbx != NULL) {
//printf("Skipping thinning of bx %d because newly added bx %d is in surfce list\n",bx->ix,nbx->ix);
						continue;
					}
			
					/* Be able to detect triangles already tested */
					/* from this shadowing bxcell. */
					clear_trirec(s, &tc);

					/* Put just primary and shadows on vx->tlist */
					vc.vtxlist = NULL;
					vc.nilist = 0;

					/* Add all the secondary bxcell vertexes to the vtxlist */
					for (nbx = bx->wlist; nbx != NULL; nbx = nbx->wlist) {
//printf("Adding bx %d vertices\n",nbx->ix);
						for (rp = nbx->sl+3; *rp != -1; rp++) {

							if ((vx = get_vtxrec(&vc, *rp)) == NULL)
								error("Failed to find vertex %s in cache",*rp);

//printf("Checking ix %d from bx %d\n",vx->ix,nbx->ix);
							/* Check vertex falls within shadow of main bx */
							/* (just checking non-deleted vertexes (triangles) */
							/*  improves speed by 20%, but we end up with stray fwd cells */
							/*  and some holes, because crossed triangles vertexes get */
							/* marked deleted ??) */
							if (
//						    vx->status == vtx_norm &&
							    shadow_group_vertex(s, s->rev.ocent, bx->g.bcent, bx->cc,
								                                          bx->dw, vx->vl)) { 
									add_vtxrec_list(&vc, vx, 0);	/* Add if not deleted */
//printf(" Added ix %d from bx %d\n",vx->ix,nbx->ix);
							}
//else 
//printf(" Not added ix %d from bx %d because no within prim bx %d\n",vx->ix,nbx->ix,bx->ix);
						}
					}

					/* Add all the primary bxcell vertices to the list, and */
					/* mark them (override shadow mark) */
//printf("Adding bx %d vertices\n",bx->ix);
					for (rp = bx->sl+3; *rp != -1; rp++) {
						if ((vx = get_vtxrec(&vc, *rp)) == NULL)
							error("Failed to find vertex %s in cache",*rp);

						if (vx->status == vtx_norm &&
						    shadow_group_vertex(s, s->rev.ocent, bx->g.bcent, bx->cc,
							                                          bx->dw, vx->vl)) { 
								add_vtxrec_list(&vc, vx, 1);	/* Add if not hidden/deleted */
						}
					}

					aftercount = vc.nilist;

					/* sort vertexes by decending distance to center point */
					/* (and also reset list tflag) */
					sort_vtxrec_list(s, &vc);
		
					/* For vertexes of this bxcell and shadowers, */
					/* in order from largst to smallest distance from center. */
					for (vx = vc.vtxlist; vx != NULL; vx = vx->tlist) {
						float *fcb;				/* Vertex being tested */
						int fl;
						assdire *tri;			/* Triangle table */

//printf("~1 checking against vtx %d\n",vx->ix);

						/* Only check triangles using vertices of the primary bxcell, */
						/* not shadow bx's. */
						if (!vx->prim)
							continue;

//printf("~1 doing vertex %d at %s dist %f\n",vx->ix, debPdv(fdi,vx->v), sqrt(vx->dist));

						fcb = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
						fl = FLV(fcb);		/* Edge flags for this vertex */

						tri = tridir + fl;
//printf("~1 fl %d = 0o%o, no triangles %d\n",fl, fl, tri->no);

						/* For all possible triangles that use this vertex */
						for (i = 0; i < tridir[fl].no; i++) {
							int triix[3];
							vtxrec *trivx[3];
							double v[MXRI+1][MXRO]; 	/* Triangle vertex values */
							double gc[MXRO], cc, dw;	/* Triangle shadow group info. */
							int ntvsh = 0;				/* Number of triangle vertices shadowed */
							double bdist = -1.0;

							/* Get triangle verticy values */
							for (e = 0; e <= sdi; e++) {
								triix[e] = vx->ix + tri->ti[i].goffs[e];
								
								if ((trivx[e] = get_vtxrec(&vc, triix[e])) == NULL)
									break;		/* Vertex doesn't exist in our set */

							    if (trivx[e]->status != vtx_norm)
									ntvsh++;

								if (trivx[e]->dist > bdist)
									bdist = trivx[e]->dist;
							}
//printf("~1 tri %d: vtxs %s goffs %s\n",i, debPiv(di,triix), debPiv(sdi+1, tri->ti[i].goffs));

							/* Don't test against triangle unless all vertexes */
							/* are in current surface, and whole triangle is visible. */
							if (e <= sdi || ntvsh >= 3)
								continue;

							/* If triangle has been done before for this bxcell, skip it. */
							if (check_trirec(s, &tc, triix))
								continue;

							for (e = 0; e <= sdi; e++) {
								for (f = 0; f < fdi; f++)
									v[e][f] = trivx[e]->vl[f];
							}

							/* Compute shadow group params of triangle for quick vertex test */
							comp_shadow_group(s, s->rev.ocent, gc, &cc, &dw, NULL, v, sdi+1);    

							/* For all vertexes */
							for (nvx = vc.vtxlist; nvx != NULL; nvx = nvx->tlist) {
								double pv[MXRO];	/* Vertex being tested */
								double de[MXRO];	/* Line delta */
								double tb[MXRI];	/* Solution point in input space */
								double xv[MXRO];	/* Solution point in output space */
								int g, sorv, wsrv;	/* Solved & within simplex return value */
								double dist;		/* distance to line origin */
								double dot;			/* dot product of solution to line */
								int shdwd;			/* whether vertex is shadowed */

								/* If vertex is above triangle, it can't be shadowed */
								if (nvx->dist > bdist)
									continue;

								/* If this other vertex has already been deleted, skip it */
								if (nvx->status != vtx_norm)
									continue;

								/* If this other vertex is part of the triangle, skip it */
								if (nvx->ix == triix[0]
								 || nvx->ix == triix[1]
								 || nvx->ix == triix[2]) {
									continue;
								}

//printf("~1 checking vertex %d against tri %s\n",nvx->ix,debPiv(3,triix));

								/* Do quick check against triangle */
								if (!shadow_group_vertex(s,
									     s->rev.ocent, gc, cc, dw, nvx->vl)) { 
//printf("~1 shadow group check shows no intersection\n");
									continue;
								}

//printf("~1 checking vertex %d at %s dist %f\n",nvx->ix, debPdv(fdi,nvx->v), sqrt(nvx->dist));

								/* Compute intersection: */
								shdwd = wsrv = 0;

								/* Compute line delta */
								fcb = s->g.a + nvx->ix * s->g.pss;
								for (f = 0; f < fdi; f++)
									pv[f] = fcb[f];

								logcomp(s, pv, pv, s->rev.ocent);

								for (f = 0; f < fdi; f++)
									de[f] = pv[f] - s->rev.ocent[f];

								/* Setup line cla and clb */
								init_line_eq_imp(s, NULL, &cla, clb, s->rev.ocent, de, 0);

								/* Solve line/triangle intersection using same */
								/* method as vnearest_clip_solve(). */
							
								/* LHS: ta[sdi][sdi] = cla[sdi][fdi] * vv[fdi][sdi] */
								/* RHS: tb[sdi] = clb[sdi] - cla[sdi][fdi] * vv_di[fdi] */
								for (f = 0; f < sdi; f++) {
									double tt;
									for (e = 0; e < sdi; e++) {
										for (tt = 0.0, g = 0; g < fdi; g++)
											tt += cla[f][g] * (v[e][g] - v[e+1][g]);
										ta[f][e] = tt;
									}
									for (tt = 0.0, g = 0; g < fdi; g++)
										tt += cla[f][g] * v[sdi][g];
									tb[f] = clb[f] - tt;
								}
							
								/* Compute the solution */
								/* (Solve the simultaneous linear equations A.X = B) */
//								sorv = !solve_se(ta, tb, sdi);
								sorv = !solve_se_2x2(ta, tb);		/* Saves a few % only */

								/* If it was solved */
								if (sorv) {
								
									/* Check that the solution is within the simplex & ink limit */
									if ((wsrv = simple_within_simplex(v, tb, sdi)) != 0) {

										/* Compute the output space solution point */
										for (f = 0; f < fdi; f++) {
											double tt = 0.0;
											for (e = 0; e < sdi; e++)
												tt += (v[e][f] - v[e+1][f]) * tb[e];
											xv[f] = tt + v[sdi][f];
										}
								
										/* Compute distance to gamut center squared, */
										/* as well as the dot product */
										for (dot = dist = 0.0, f = 0; f < fdi ; f++) {
											double tt = (xv[f] - s->rev.ocent[f]);
											dist += tt * tt;
											dot += de[f] * tt;
										}
//printf("~1 intersection at %s dist %f\n", debPdv(fdi,xv), sqrt(dist));
   
										/* If intersection distance is greater than vertex distance, */
										/* delete the vertex */
										if (dot > 0.0 && dist > (nvx->dist + EPS)) {
											shdwd = 1;
											nvx->status = vtx_sha;		/* Shadowed */
											aftercount--;
//printf("~1 deleting vx %d\n",nvx->ix);
										}
									}
								}
//if (!sorv) printf("~1 solve failed\n");
//if (sorv && !wsrv) printf("~1 %d not within simplex, tb = %s\n",nvx->ix, debPdv(sdi,tb));
//if (sorv && wsrv && shdwd) printf("~1 tri %s deleting vertex %d\n",debPiv(3,triix), nvx->ix);

#ifdef REVVRML
							/* Plot vertex & triangle check setup & solution */
							/* + the primary and shadow bxcells. */ 
							/* Plot prim & shadow bxcell cells ? Wait for user press ? */
							if (0 && phase && shdwd) plot_tri_check(s, 1, 1,
								bx, vx->ix, i, triix, nvx->ix, sorv, wsrv, shdwd, v, de, pv, xv); 
#endif /* REVVRML */

							}	/* Next other vertex */
						}		/* Next triangle */
					}			/* Next main vertex */

					if (phase == 1)
						bx->status = bx_thinned;
//printf("Thinned vertexes in bx %d from %d to %d (%d)\n",bx->ix, vc.nilist,aftercount, vc.nilist-aftercount);
				}			/* Next surface bx cell */
				/* Done with lists */
				vc.vtxlist = NULL;
				vc.nilist = 0;
				xlist = NULL;

				DBG(("deleting vertices in all bxcells\n"));

				/* The thinning may have deleted vertices from bxcell's that */
				/* were not involved in the thinning, so go though all bxcells */
				/* to do deletions. Look also for any needed additional surface bxcells. */
				for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
					int beforecount, aftercount;
					vtxrec *nvx;
					int *crp, *rp, *nrp;
			
					if (bx->status == bx_uninit)
						continue;

#ifdef REVVRML
					bx->debug = 0;		/* Not an addition */
#endif

					beforecount = bx->sl[1]-3;

#undef DELETE_SHAD	/* [und] try deleting shadowed vertexes with no un-shadowed neighbors. */
					/* Seems to actually slow things down though ? */

					/* Delete all the marked vertexes from bxcell list */
					for (nrp = rp = bx->sl+3; *rp != -1; rp++) {
						vtxrec *vx;
#ifdef DELETE_SHAD
						int nshad = 0, nnshad = 0;
#endif

						if ((vx = get_vtxrec(&vc, *rp)) == NULL)
							continue;			/* Already deleted */

#ifdef REVVRML
						vx->addvtx = 0;
#endif

#ifdef DELETE_SHAD
						/* Check all of its neighbor vertexes, to see if */
						/* it's safe to actually delete them. */
						if (vx->status >= vtx_sha) {	/* vertex to delete ? */
							float *fcb;
							int fl;
							assdire *edg;			/* Edge table */

//printf("Checking vx %d neighbors\n",vx->ix);
							fcb = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
							fl = FLV(fcb);		/* Edge flags for this vertex */
							edg = edgdir + fl;

							/* For all possible edges that use this vertex */
							for (i = 0; i < edgdir[fl].no; i++) {
								int eix;

								/* Edge vertex index number of other vertex */
								if (edg->ti[i].goffs[0] != 0)
									eix = vx->ix + edg->ti[i].goffs[0];
								else
									eix = vx->ix + edg->ti[i].goffs[1];
				
								if ((nvx = get_vtxrec(&vc, eix)) != NULL) {
//printf("vx %d neighbor vx %d status %d\n",vx->ix,nvx->ix,nvx->status);
								 	if (nvx->status >= vtx_sha) {
										nshad++;
									} else {
										nnshad++;
									}
								}
							}
						}
//printf("vx %d nshad %d nnshad %d\n",vx->ix);
#endif	/* DELETE_SHAD */

						/* Keep un-shadowed vertexes and */
						/* shadowes ones that have non-shadows neigbors */
						if (vx->status == vtx_norm
#ifdef DELETE_SHAD
						 || vx->status >= vtx_sha && nnshad != 0
#endif
						) {
							*nrp++ = *rp;
//printf("~1 leaving vtx %d status %d in bxcell %d list\n",vx->ix,vx->status,bx->ix);

							if (phase == 1) {
#ifndef NEVER 					/* Do additions */
								/* If vertex doesn't land in a surface bxcell, */
								/* create a new surface bxcell for it. */
								if (vx->status == vtx_norm			// ????
								 && (vflag[vx->rix] & 2) == 0) {
									bxcell *nx;
				
//if (get_surface_bxcell(s, vx->rix) != NULL)
//error("new addition bx %d is already surface cell!\n",vx->rix);

#if defined(REVTABLESTATS) || defined(DEBUG)
									nascells++;
#endif
									/* Since it's a surface point, the seeding point is itself (NULL). */
									nx = new_bxcell(s, vx->rix, vx->ival, NULL, 0.0, NULL);
									add_bxcell_hash(s, nx);
									/* Convert to empty surface cell */
									vflag[nx->ix] = (vflag[nx->ix] & ~0xf) | 2;
			
									/* Add to surface linked list */
									nx->slist = s->rev.surflist;
									s->rev.surflist = nx;

									/* Add to additions list */
									nx->xlist = xlist;
									xlist = nx;
//printf("Added bxcell %d, status %d due to vx %d status %d\n",nx->ix, nx->status,vx->ix,vx->status);

#ifdef REVVRML
									vx->addvtx = 1;		/* Cause of added bxcell */
									nx->debug = 1;		/* Mark added bxcells */
#endif
								}
#if defined(REVTABLESTATS) || defined(DEBUG)
								/* Keep addvtx flag straight */
								else if (vx->status == vtx_norm) {
									bxcell *nx;
									if ((nx = get_surface_bxcell(s, vx->rix)) != NULL) {
										if (nx->status == bx_uninit)	/* Must be just added */
											vx->addvtx = 1;				/* Cause of added bxcell */
									}
								}
#endif
							}
#endif	/* Do additions */
						/* Omit vertex from bx list, and mark it as deleted, */
						/* and remove it if it has no un-shadowed neighbors */
						} else {
							vx->status = vtx_del;
//printf("~1 marking vtx %d status %d nnshad %d deleted bxcell %d list\n",vx->ix,vx->status,nnshad,bx->ix);
#ifdef DELETE_SHAD
							/* Remove it from cache if all its neighbors are */
							/* shadowed too. */
							if (nnshad == 0) {
//printf("~1 deleting vtx %d\n",vx->ix);
								del_vtxrec_hash(&vc, vx->ix);
								if (get_vtxrec(&vc, vx->ix) != NULL)
									error("get_vtxrec succeeded after del_vtxrec_hash!");
							}
#else /* !DELETE_SHAD */
							/* Keep track of deleted vertices that are in this bx, */
							/* so we can add back in crossing triangle vertexes */
							add2indexlist(s, &bx->dl, vx->ix, 0);
#endif /* !DELETE_SHAD */
						}
					}			/* Next vertex in bx's list */
					*nrp = -1;
					bx->sl[1] = nrp - bx->sl;

//aftercount = bx->sl[1]-3;
//if (beforecount != 0 && aftercount < beforecount) printf("Reduced bx from %d to %d vertices\n",beforecount,aftercount);
				}				/* Next bx */
			}				/* Next phase */

#ifdef REVVRML
			/* Main summary plot at each thinning round */
			/* Vtx ix tag ? Deleted vtxs ? Added vtxs ? Preserved vtxs ? oil ? bxcells ? Wait ? */
			if (0) plot_vtx_surface(s, 0, 0, 1, 0, 0, 0, 1, &vc, edgdir); 
#endif /* REVVRML */

			if (xlist == NULL) {
				break;			/* No added surface cells */
			}

			DBG(("reseting shadowers of new bxcells\n"));

			/* Locate all the bxcells that shadow the added bxcells, */
			/* and revert status to rethinned. */
			for (bx = xlist; bx != NULL; bx = bx->xlist) {
		
#ifdef REVVRML
				for (nbx = s->rev.surflist; nbx != NULL; nbx = nbx->slist)
					nbx->debug = 0;
#endif

				/* Locate the nnrev[] bxcells that shadow this added bxcell */
				bx->wlist = NULL;		/* For debug */
				for (nbx = s->rev.surflist; nbx != NULL; nbx = nbx->slist) {

					if (
#ifdef REVVRML
					    (nbx->status != bx_thinned && nbx->status != bx_filled)	 // Show all
#else
					    (nbx->status != bx_thinned)
#endif
					 || nbx == bx
					 || nbx->sl == NULL
					 || nbx->sl[1] == 3)
						continue;
				
					/* If any of nbx is further from bx and their bounding cylinders */
					/* overlap in perspective from rev.ocenter, assume nbx is a shadower. */
					if (shadow_group_group(s, s->rev.ocent, nbx->g.bcent, nbx->cc, nbx->dw,   
						                                    bx->g.bcent, bx->cc, bx->dw)) {
						nbx->status = bx_rethinnd;

#ifdef REVVRML
						bx->debug = 1;				/* rethinned bx */
						nbx->debug = 2;				/* added bx */
						nbx->wlist = bx->wlist;		/* For debug */
						bx->wlist = nbx;
#endif
//printf("~1 marking bxcell %d as un-thinned due to added bxcell %d\n",nbx->ix, bx->ix);
					}
				}

#ifdef REVVRML
				/* Plot bxcells touched by added cell */
				if (0) plot_touched_bxcells(s, bx->ix);
#endif	/* VRML */
			}

#if defined(REVTABLESTATS) || defined(DEBUG)
			printf(" %d bxcells thinned, %d re-thinned\n",thcount,rethcount);
			printf("Loop took %f seconds\n",0.001 * (msec_time()-lmsec));
#endif
		}		/* Loop until done */

#if defined(REVTABLESTATS) || defined(DEBUG)
		printf("Thinning took %f seconds\n",0.001 * (msec_time()-smsec));
#endif

		/* = = = = = = = = = = = = = = = = = = */
		DBG(("Preserving overlapping triangles\n"));
		{
#ifdef REVTABLESTATS
			int notverts = 0;			/* Number of possible crossed triangles/test vertices */
			int nopreserved = 0;		/* Number of vertices preseved for crossied triangles */
			unsigned long lmsec = msec_time();
#endif
			int sdi = 2;				/* sub-simplexes are triangles */
			int k, jj;
			vtxrec *vx;

			/* Struct to hold test vertex locations */
			struct _tvxrec {
				double v[MXRO];			/* Log output vertex value */
				double dist;			/* Distance from center point squared */
				int ix[MXRO+1];			/* Indexes of the triangle vertices */
				int shad;				/* Test result */
				struct _tvxrec *tlist;
			}; typedef struct _tvxrec tvxrec;
			tvxrec *tlist = NULL, *ftlist = NULL, *tvx, *ntvx;
			int nitlist = 0;

			/* For each surface bxcell, form triangles from vertexes */
			/* and detect possible crossed triangles */
			for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
				int sdi = 2;						/* sub-simplexes are triangles */
				double clb[MXRO+1];	/* Line RHS implicit equation vector [fdi+1] */
				int *crp, *rp, *nrp;
				vtxrec *vx, *nvx;
				int aftercount;			/* vertex count after thinning */

				/* Skip cell if empty */
				if (bx->sl == NULL || bx->sl[1] == 3)
					continue;

				/* Put the testing triangle vertices on the vtxlist */
				vc.vtxlist = NULL;
				vc.nilist = 0;

				/* Be able to detect triangles already tested */
				/* from this shadowing bxcell. */
				clear_trirec(s, &tc);

				/* See whether to add cell vertices to the list. */
				for (rp = bx->sl+3; *rp != -1; rp++) {
					assdire *tri;			/* Triangle table */
					float *fp;
					int fl;
					int added = 0;

					if ((vx = get_vtxrec(&vc, *rp)) == NULL)
						error("Failed to find vertex %s in cache",*rp);

					if (vx->status != vtx_norm)		// ???
						continue;

					fp = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
					fl = FLV(fp);		/* Edge flags for this vertex */
					tri = tridir + fl;

					/* For all +ve triangles that use this vertex */
					for (k = 0; k < tridir[fl].no; k++) {
						int triix[MXRI+1];
						vtxrec *trivx[3];
						int ntvsh = 0;			/* Number of vertices shadowed */
						int nntvsh = 0;			/* Number of vertices not shadowed */

//printf("~1 tri %d: goffs = %s\n", k, debPiv(sdi+1, tri->ti[k].goffs));

						/* Triangle vertex index numbers */
						for (j = 0; j <= sdi; j++) {
							triix[j] = vx->ix + tri->ti[k].goffs[j];

							if ((trivx[j] = get_vtxrec(&vc, triix[j])) == NULL) {
								break;		/* Vertex doesn't exist */
							}
						    if (trivx[j]->status != vtx_norm)
								ntvsh++;
							else
								nntvsh++;
						}

						/* If a vertex isn't valid, or all vertexes are shadowed or not shadowed */
						if (j <= sdi
						 || ntvsh == (sdi+1)
						 || nntvsh == (sdi+1)) {
//printf("~1 vtx missing %d, ntvsh %d, nntvxsh %d\n",j <= sdi, ntvsh, nntvsh);
								continue;			/* Skip this triangle */
						}

						/* If triangle has been done before for this bxcell, skip it. */
						if (check_trirec(s, &tc, triix)) {
							continue;
						}

						/* We've decided to add triangle and test vertex */
						if (!added) {
							add_vtxrec_list(&vc, vx, 1);	/* Add vertex to list to test against */
							added = 1;
						}

						/* Create or re-use test vertex */
						if (ftlist != NULL) {		/* Grab one from free list */
							tvx = ftlist;
							ftlist = tvx->tlist;
							memset((void *)tvx, 0, sizeof(tvxrec));
					
						} else {
							if ((tvx = (tvxrec *) rev_calloc(s, 1, sizeof(tvxrec))) == NULL)
								error("rspl malloc failed - rev tvxrec structs");
							INCSZ(s, sizeof(tvxrec));
						}

						tvx->tlist = tlist;
						tlist = tvx;
						nitlist++;

						for (f = 0; f < fdi; f++)
							tvx->v[f] = 0.0;

						for (j = 0; j <= sdi;  j++) {
							if (trivx[j]->status == vtx_norm) {
								for (f = 0; f < fdi; f++)
									tvx->v[f] += 0.95/nntvsh * trivx[j]->vl[f];
							} else {
								for (f = 0; f < fdi; f++)
									tvx->v[f] += 0.05/ntvsh * trivx[j]->vl[f];
								trivx[j]->cross = 1;		/* For diagnostics */
							}
						}

						/* Compute distance of test vertex to overall center point squared */
						tvx->dist = 0.0;
						for (f = 0; f < fdi; f++) {
							double tt = tvx->v[f] - s->rev.ocent[f]; 
							tvx->dist += tt * tt;
						}

						/* Note the triangles vertexes indexes */
						for (j = 0; j <= sdi;  j++)
							tvx->ix[j] = trivx[j]->ix;
#ifdef REVTABLESTATS
						notverts++;
#endif
					}
				}

				/* Do a first pass for each test vertex, testing against */
				/* just the triangles that are associated with it's triangle. */
				/* (This quickly culls the test vertex list size, greatly */
				/* reducing the time taken in the second pass */

				/* For each test vertex */
				for (tvx = tlist; tvx != NULL; tvx = tvx->tlist) {
					double pv[MXRO];	/* Vertex being tested */
					double de[MXRO];	/* Line delta */

					clear_trirec(s, &stc);

					/* Compute line delta */
					for (f = 0; f < fdi; f++) {
						pv[f] = tvx->v[f];
						de[f] = pv[f] - s->rev.ocent[f];
					}

					/* Setup line cla and clb */
					init_line_eq_imp(s, NULL, &cla, clb, s->rev.ocent, de, 0);
	
					/* For each vertex of the test vertex triangle */
					for (jj = 0; jj <= sdi; jj++) {
						assdire *tri;			/* Triangle table */
						float *fp;
						int fl;

						if ((vx = get_vtxrec(&vc, tvx->ix[jj])) == NULL)
							error("rev crossing test - failed to get vertex");

						if (vx->status != vtx_norm)
							continue;

						fp = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
						fl = FLV(fp);		/* Edge flags for this vertex */
						tri = tridir + fl;

						/* For all +ve triangles that use this vertex */
						for (k = 0; k < tridir[fl].no; k++) {
							int triix[MXRI+1];
							vtxrec *trivx[MXRI+1];
							double v[MXRI+1][MXRO]; 	/* Triangle vertex values */
							double gc[MXRO], cc, dw;	/* Triangle shadow group info. */
							int ntvsh = 0;			/* Number of vertices shadowed */
							double bdist = -1.0;
							double tb[MXRI];	/* Solution point in input space */
							double xv[MXRO];	/* Solution point in output space */
							int g, sorv, wsrv;	/* Solved & within simplex return value */
							double dist;		/* distance to line origin */
							double dot;			/* dot product of solution to line */

//printf("~1 tri %d: goffs = %s\n", i, debPiv(sdi+1, tri->ti[k].goffs));

							/* Triangle vertex index numbers */
							triix[0] = vx->ix + tri->ti[k].goffs[0];
							triix[1] = vx->ix + tri->ti[k].goffs[1];
							triix[2] = vx->ix + tri->ti[k].goffs[2];

							/* If triangle has been done before for this tvx, skip it. */
							if (check_trirec(s, &stc, triix)) {
								continue;
							}

							/* Triangle vertex index numbers */
							for (j = 0; j <= sdi; j++) {
//								triix[j] = vx->ix + tri->ti[k].goffs[j];

								if ((trivx[j] = get_vtxrec(&vc, triix[j])) == NULL) {
									break;		/* Vertex doesn't exist */
								}
							    if (trivx[j]->status != vtx_norm)
									ntvsh++;

								if (trivx[j]->dist > bdist)
									bdist = trivx[j]->dist;
							}

							/* If vertex is above triangle, it can't be shadowed */
							if (tvx->dist > bdist)
								continue;

							/* If a vertex isn't valid, or all vertexes are shadowed */
							if (j <= sdi
							 || ntvsh >= (sdi+1)) {
									continue;			/* Skip this triangle */
							}

							/* If this triangle is the test vertex triangle, skip it */
							if (tvx->ix[0] == triix[0]
							 && tvx->ix[1] == triix[1]
							 && tvx->ix[2] == triix[2]) {
								continue;
							}

							for (j = 0; j <= sdi; j++) {
								for (f = 0; f < fdi; f++)
									v[j][f] = trivx[j]->vl[f];
							}

							/* Compute shadow group params of triangle for quick vertex test */
							comp_shadow_group(s, s->rev.ocent, gc, &cc, &dw, NULL, v, sdi+1);    

							/* Do quick check against triangle */
							if (!shadow_group_vertex(s,
								     s->rev.ocent, gc, cc, dw, tvx->v)) { 
								continue;
							}

//printf("~1 checking vertex %d at %s dist %f\n",tvx->ix, debPdv(fdi,tvx->v), sqrt(tvx->dist));
							/* Compute intersection: */
							wsrv = 0;

							/* Solve line/triangle intersection using same */
							/* method as vnearest_clip_solve(). */
						
							/* LHS: ta[sdi][sdi] = cla[sdi][fdi] * vv[fdi][sdi] */
							/* RHS: tb[sdi] = clb[sdi] - cla[sdi][fdi] * vv_di[fdi] */
							for (f = 0; f < sdi; f++) {
								double tt;
								for (e = 0; e < sdi; e++) {
									for (tt = 0.0, g = 0; g < fdi; g++)
										tt += cla[f][g] * (v[e][g] - v[e+1][g]);
									ta[f][e] = tt;
								}
								for (tt = 0.0, g = 0; g < fdi; g++)
									tt += cla[f][g] * v[sdi][g];
								tb[f] = clb[f] - tt;
							}
						
							/* Compute the solution */
							/* (Solve the simultaneous linear equations A.X = B) */
//							sorv = !solve_se(ta, tb, sdi);
							sorv = !solve_se_2x2(ta, tb);		/* Saves a few % only */

							if (!sorv)
								continue;
							
							/* Check that the solution is within the simplex & meets ink limit */
							if ((wsrv = simple_within_simplex(v, tb, sdi)) != 0) {

								/* Compute the output space solution point */
								for (f = 0; f < fdi; f++) {
									double tt = 0.0;
									for (e = 0; e < sdi; e++)
										tt += (v[e][f] - v[e+1][f]) * tb[e];
									xv[f] = tt + v[sdi][f];
								}
						
								/* Compute distance to gamut center squared, */
								/* as well as the dot product */
								for (dot = dist = 0.0, f = 0; f < fdi ; f++) {
									double tt = (xv[f] - s->rev.ocent[f]);
									dist += tt * tt;
									dot += de[f] * tt;
								}
   
								/* If intersection distance is greater than vertex distance, */
								/* mark the test vertex as shadowed (== crossed triangle */
								/* is shadowed) */
								if (dot > 0.0 && dist > (tvx->dist + EPS)) {
									tvx->shad = 1;
									goto next_tvx;
								}
							}
						}	/* Next associated triangle */
					}		/* Next vertex of test triangle */
				  next_tvx:;
				}			/* Next test vertex */

				/* Delete shadowed tvx, and sort remaining tlist by distance so */
				/* that we have a better chance of shadowing it early ? */
				{
					int i;
					tvxrec **sort, *vx, *nvx;
				
					/* Create temporary array of pointers to tvxrec's in list */
					if ((sort = (tvxrec **) rev_calloc(s, nitlist, sizeof(tvxrec *))) == NULL)
						error("rspl malloc failed - rev tvxrec sort array");
					INCSZ(s, nitlist * sizeof(tvxrec *));
				
					for (i = 0, vx = tlist; vx != NULL; vx = nvx) {
						nvx = vx->tlist;
						if (!vx->shad) {
							sort[i++] = vx;
						} else {
							/* Put deleted tvxrec on the free list to re-use */
							vx->tlist = ftlist;
							ftlist = vx;
						}
					}
					nitlist = i;
				
					/* Sort the list into ascending distance from center */
#define 	HEAP_COMPARE(A,B) (A->dist < B->dist)
					HEAPSORT(tvxrec *, sort, nitlist)
#undef 		HEAP_COMPARE
				
					/* Re-create the linked list in descending order */
					tlist = NULL;
					for (i = 0; i < nitlist; i++) {
						vx = sort[i];
						vx->tlist = tlist;
						tlist = vx;
					}
				
					free(sort);
					DECSZ(s, nitlist * sizeof(tvxrec *));

#ifdef NEVER
					printf("sorted test vertex list:\n");
					for (i = 0, vx = tlist; vx != NULL; vx = vx->tlist, i++)
						printf("%d: ix %s dist %f\n",i,debPiv(3,vx->ix), sqrt(vx->dist));
#endif
				}

				/* Be able to detect triangles already tested */
				/* from this shadowing bxcell. */
				clear_trirec(s, &tc);

				/* sort vertexes by descending distance to center point */
				/* (and also reset list tflag), to detect shadowing early */
				sort_vtxrec_list(s, &vc);
	
				/* Check if the test points are shadowed by any triangle */
				for (vx = vc.vtxlist; vx != NULL; vx = vx->tlist) {
					assdire *tri;			/* Triangle table */
					float *fp;
					int fl;

					if (vx->status != vtx_norm)		// ???
						continue;

					fp = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
					fl = FLV(fp);		/* Edge flags for this vertex */
					tri = tridir + fl;

					/* For all +ve triangles that use this vertex */
					for (k = 0; k < tridir[fl].no; k++) {
						int triix[MXRI+1];
						vtxrec *trivx[MXRI+1];
						double v[MXRI+1][MXRO]; 	/* Triangle vertex values */
						double gc[MXRO], cc, dw;	/* Triangle shadow group info. */
						int ntvsh = 0;			/* Number of vertices shadowed */
						double bdist = -1.0;

//printf("~1 tri %d: goffs = %s\n", i, debPiv(sdi+1, tri->ti[k].goffs));

						/* Triangle details */
						for (j = 0; j <= sdi; j++) {
							triix[j] = vx->ix + tri->ti[k].goffs[j];

							if ((trivx[j] = get_vtxrec(&vc, triix[j])) == NULL) {
								break;		/* Vertex doesn't exist */
							}
						    if (trivx[j]->status != vtx_norm)
								ntvsh++;

							if (trivx[j]->dist > bdist)
								bdist = trivx[j]->dist;
						}

						/* If a vertex isn't valid, or all vertexes are shadowed */
						if (j <= sdi
						 || ntvsh >= (sdi+1)) {
								continue;			/* Skip this triangle */
						}

						/* If triangle has been done before for this bxcell, skip it. */
						if (check_trirec(s, &tc, triix)) {
							continue;
						}

						for (j = 0; j <= sdi; j++) {
							for (f = 0; f < fdi; f++)
								v[j][f] = trivx[j]->vl[f];
						}

						/* Compute shadow group params of triangle for quick vertex test */
						comp_shadow_group(s, s->rev.ocent, gc, &cc, &dw, NULL, v, sdi+1);    

						/* For all test vertexes */
						for (tvx = tlist; tvx != NULL; tvx = tvx->tlist) {
							double pv[MXRO];	/* Vertex being tested */
							double de[MXRO];	/* Line delta */
							double tb[MXRI];	/* Solution point in input space */
							double xv[MXRO];	/* Solution point in output space */
							int g, sorv, wsrv;	/* Solved & within simplex return value */
							double dist;		/* distance to line origin */
							double dot;			/* dot product of solution to line */

							/* If vertex is above triangle, it can't be shadowed */
							if (tvx->dist > bdist)
								continue;

							/* If we have already determined this one is shadowed */
							if (tvx->shad)
								continue;

							/* If this vertex for this triangle, skip it */
							if (tvx->ix[0] == triix[0]
							 && tvx->ix[1] == triix[1]
							 && tvx->ix[2] == triix[2]) {
								continue;
							}

							/* Do quick check against triangle */
							if (!shadow_group_vertex(s, s->rev.ocent, gc, cc, dw, tvx->v))
								continue;
//printf("~1 checking vertex %d at %s dist %f\n",tvx->ix, debPdv(fdi,tvx->v), sqrt(tvx->dist));
							/* Compute intersection: */
							wsrv = 0;

							/* Compute line delta */
							for (f = 0; f < fdi; f++) {
								pv[f] = tvx->v[f];
								de[f] = pv[f] - s->rev.ocent[f];
							}

							/* Setup line cla and clb */
							init_line_eq_imp(s, NULL, &cla, clb, s->rev.ocent, de, 0);

							/* Solve line/triangle intersection using same */
							/* method as vnearest_clip_solve(). */
						
							/* LHS: ta[sdi][sdi] = cla[sdi][fdi] * vv[fdi][sdi] */
							/* RHS: tb[sdi] = clb[sdi] - cla[sdi][fdi] * vv_di[fdi] */
							for (f = 0; f < sdi; f++) {
								double tt;
								for (e = 0; e < sdi; e++) {
									for (tt = 0.0, g = 0; g < fdi; g++)
										tt += cla[f][g] * (v[e][g] - v[e+1][g]);
									ta[f][e] = tt;
								}
								for (tt = 0.0, g = 0; g < fdi; g++)
									tt += cla[f][g] * v[sdi][g];
								tb[f] = clb[f] - tt;
							}
						
							/* Compute the solution */
							/* (Solve the simultaneous linear equations A.X = B) */
//							sorv = !solve_se(ta, tb, sdi);
							sorv = !solve_se_2x2(ta, tb);		/* Saves a few % only */

							/* If it was solved */
							if (sorv) {
							
								/* Check that the solution is within the simplex & ink limit */
								if ((wsrv = simple_within_simplex(v, tb, sdi)) != 0) {

									/* Compute the output space solution point */
									for (f = 0; f < fdi; f++) {
										double tt = 0.0;
										for (e = 0; e < sdi; e++)
											tt += (v[e][f] - v[e+1][f]) * tb[e];
										xv[f] = tt + v[sdi][f];
									}
							
									/* Compute distance to gamut center squared, */
									/* as well as the dot product */
									for (dot = dist = 0.0, f = 0; f < fdi ; f++) {
										double tt = (xv[f] - s->rev.ocent[f]);
										dist += tt * tt;
										dot += de[f] * tt;
									}
//printf("~1 intersection at %s dist %f\n", debPdv(fdi,xv), sqrt(dist));
   
									/* If intersection distance is greater than vertex distance, */
									/* mark the test vertex as shadowed (== crossed triangle */
									/* is shadowed) */
									if (dot > 0.0 && dist > (tvx->dist + EPS)) {
										tvx->shad = 1;
									}
								}
							}
						}	/* Next test vertex */
					}		/* Next triangle from vertex */
				}			/* Next vertex */

				/* Go through test vertex results, and if it is un-shadowed, */
				/* mark all the corresponding triangle vertexes as un-shadowed. */
				/* For all test vertexes */
				for (tvx = tlist; tvx != NULL; tvx = ntvx) {
					ntvx = tvx->tlist;

					/* If the test point wasn't shadowed, assume it */
					/* is part of the gamut surface, and mark all its */
					/* vertexes as valid. */
					if (!tvx->shad) {
						for (j = 0; j <= sdi; j++) {
							if ((vx = get_vtxrec(&vc, tvx->ix[j])) == NULL)
								error("rev - failed to locate vertex %d\n",tvx->ix[j]);

							if (vx->status != vtx_norm) {
								vx->pres = 1;		/* Don't treat it as deleted */
							}
						}
					}
					/* Put all the tvxrec's on the free list to re-use */
					tvx->tlist = ftlist;
					ftlist = tvx;
				}
				tlist = NULL;
				nitlist = 0;

				/* If the preseved vertexes have been deleted from the bx list, */
				/* add them back in again */
				if (bx->dl != NULL) {
					for (nrp = rp = bx->dl+3; *rp != -1; rp++) {
						vtxrec *vx;
		
						if ((vx = get_vtxrec(&vc, *rp)) == NULL)
							continue;		/* Hmm. */
		
						/* If preserved, transfer it to the active bx list */
						if (vx->pres) {
							add2indexlist(s, &bx->sl, *rp, 0);
	
						/* Leave it in deleted list */
						} else {
							*nrp++ = *rp;
						}
					}
					*nrp = -1;
					bx->dl[1] = nrp - bx->dl;
	
					/* We don't need the deleted list now */
					free_indexlist(s, &bx->dl);
				}

			}		/* Next bxcell */

			/* Free up tvxrec's */
			while (ftlist != NULL) {
				tvxrec *this = ftlist;
				ftlist = ftlist->tlist;
				free(this);
				DECSZ(s, sizeof(tvxrec));
			}

#ifdef REVTABLESTATS
			/* Count the number of preserved vertexes */
			for (i = 0; i < vc.hash_size; i++) {
				for (vx = vc.hash[i]; vx != NULL; vx = vx->hlink) {
					if (vx->pres)
						nopreserved++;
				}
			}
			printf("%d crossed triangles tested\n",notverts);
			printf("%d hidden vertices retained for crossed triangles\n",nopreserved);
			printf("Took %f secs to preserving crossing triangless\n",0.001 * (msec_time()-lmsec));
#endif
		}	/* End of preserve shadowed triangles */

		/* = = = = = = = = = = = = = = = = = = */
		/* Delete any shadowed vertexes, and remove any empty bxcells. */
		for (pbx = &s->rev.surflist, bx = *pbx; bx != NULL; bx = nbx) {
			int *rp, *nrp;

			/* Delete all the shadowed or delted vertexes from bxcell list, */
			/* unless they are preserved because they are part of a crossed triangle. */
			for (nrp = rp = bx->sl+3; *rp != -1; rp++) {
				vtxrec *vx;

				if ((vx = get_vtxrec(&vc, *rp)) == NULL)
					continue;		/* Hmm. Delete it.*/

				/* Keep all the un-shadowed or preserved vertexes */
				if (vx->status == vtx_norm	
				  || vx->pres) {
					*nrp++ = *rp;
				} else {
					del_vtxrec_hash(&vc, vx->ix);
				}
			}
			*nrp = -1;
			bx->sl[1] = nrp - bx->sl;

			if (bx->sl == NULL	/* Missing or empty fwd index list */
			 || bx->sl[1] == 3
			) {
				/* Remove it from vflag array */
				if (s->rev.rev[bx->ix] != NULL) {
					vflag[bx->ix] = (vflag[bx->ix] & ~0xf) | 1;		/* Not surface and done */
				} else {
					vflag[bx->ix] = (vflag[bx->ix] & ~0xf) | 0;		/* Not surface and empty */
				}

				/* Remove it from hash */
				rem_bxcell_hash(s, bx->ix);

				/* Free fwd index list (none are shared at this point) */
				if (bx->sl != NULL)
					free_indexlist(s, &bx->sl);

				/* Remove it from surface list */
				*pbx = nbx = bx->slist;

				/* Free it */
				del_bxcell(s, bx);
#if defined(REVTABLESTATS) || defined(DEBUG)
				nrscells++;
#endif

			} else {						/* Move on to next */
				pbx = &bx->slist;
				nbx = bx->slist;
			}
		}

		/* Add extra over ink limit vertexes. */
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int sdi = 1;					/* sub-simplexes are edges */
			int *rp;
			vtxrec *vx, *nvx;
			int *exlist = NULL;
	
			/* Add over ink limit vertexes, so that fwd cells will straddle */
			/* the ink limit boundary. */
			/* Do this by checking all vertexes edge neighbors, */
			/* and adding any that are over the ink limit. */
			/* (Only do this for bx cells that are known to contain */
			/* over ink limit vertices.) */ 
			if (s->limiten && vflag[bx->ix] & 0x10) {
				int *rp;

//printf("~1 ink limitin is enabled bx %d\n", bx->ix);
				for (rp = bx->sl+3; *rp != -1; rp++) {
					float *vp, *evp;
					int fl;
					assdire *edg;			/* Edge table */

					if ((vx = get_vtxrec(&vc, *rp)) == NULL)
						continue;		/* Hmm. */

					/* Don't do this for preserved or oil vertexes */
					if (vx->status != vtx_norm)
						continue;
 
					vp = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
					fl = FLV(vp);		/* Edge flags for this vertex */
					edg = edgdir + fl;

#ifdef CHECK_NNLU
					if (vp[-1] > s->limitv)
						error("Thinned vertex %d is over ink limit!",vx->ix);
#endif

//printf("~1 fl %d = 0o%o, no edges %d\n",fl, fl, edg->no);

					/* For all possible edges that use this vertex */
					for (i = 0; i < edgdir[fl].no; i++) {
						int eix;

//printf("~1 edg %d: goffs = %s\n", i, debPiv(sdi+1, edg->ti[i].goffs));

						/* Edge vertex index number of other vertex */
						if (edg->ti[i].goffs[0] != 0)
							eix = vx->ix + edg->ti[i].goffs[0];
						else
							eix = vx->ix + edg->ti[i].goffs[1];
		
						evp = s->g.a + eix * s->g.pss;	/* Other vertex in fwd grid */

//printf(" Checking edge %d (%f) -> %d (%f)\n", vx->ix, vp[-1], eix, evp[-1]);

						/* If over limit, add it to the expansion list */
						if (evp[-1] > s->limitv) {
//printf("~1 added over ink limit vertex %d\n",eix);

							if (get_vtxrec(&vc, eix) != NULL)
								continue;		/* Added by another bx */
							nvx = new_vtxrec(s, &vc, eix);
							nvx->status = vtx_oil; 
							add2indexlist(s, &exlist, eix, 0);
						}
					}
				}
		
				/* If we found over ink limit vertices, add them to our list */
				if (exlist != NULL) {
					for (rp = exlist+3; *rp != -1; rp++) {
						add2indexlist(s, &bx->sl, *rp, 0);
					}
					free_indexlist(s, &exlist);
				}
			}
		}

#ifdef REVTABLESTATS
		/* Count the number of over ink limit vertexes */
		for (i = 0; i < vc.hash_size; i++) {
			vtxrec *vx;
			for (vx = vc.hash[i]; vx != NULL; vx = vx->hlink) {
				if (vx->status == vtx_oil)
					naoulvtxs++;
			}
		}
#endif

#ifdef REVVRML
		/* Plot final vertex surface before converting to fwcells */
		/* Vtx ix tag ? Deleted vtxs ? Added vtxs ? Preserved vtxs ? oil vtxs ? bxcells ? Wait ? */
		if (1) plot_vtx_surface(s, 0, 0, 0, 1, 1, 0, 0, &vc, edgdir); 
#endif /* REVVRML */

		/* Convert vertexes to cube lists */
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int sdi = 1;					/* sub-simplexes are edges */
			int *crp, *rp, *nrp;
			int ttouch;
			vtxrec *vx, *nvx;
	
			/* If there are no vertexes left (i.e. they have all been deleted) */
			/* Don't try and convert to fwd cells. */
			if (bx->sl == NULL || bx->sl[1] == 3) {
				bx->status = bx_conv;
				continue;
			}
	
			/* Create cach list of vxrec's for just this nnrev[] */
			clear_vtxrec_lists(s, &vc);

			/* Add all this bxcell vertices to cache and list */
			for (rp = bx->sl+3; *rp != -1; rp++) {
				vx = new_vtxrec(s, &vc, *rp); 
				add_vtxrec_list(&vc, vx, 0);
			}

			/* Convert fwd index list into fwd cells list. Do this in */
			/* a way that minimizes the number of cells needed while still */
			/* ensuring that there is 2 dimensional connectivity for all the vertexes. */
	
			/* Count number of touches if we add a cube for each prime vertex */ 
//printf("~1 counting number of touches\n");
			crp = bx->sl;
			i = 0;
			for (rp = crp+3; *rp != -1; rp++) {
				vtxrec *vx;
	
				if ((vx = get_vtxrec(&vc, *rp)) == NULL)
					error("get_vtxrec() failed on surface vtx");
	
				i++;

				/* For each vertex of cube placed at vx->cix */
				for (ee = 0; ee < (1<<di); ee++) {
					int vix = vx->cix + s->g.hi[ee];
					vtxrec *nx;
	
					if ((nx = get_vtxrec(&vc, vix)) != NULL)
						vx->tcount++;
				}
			}
//printf("there were %d vertexes",i);
	
//printf("~1 adding cells in order of touch count\n");
			/* Add cells in order of touch count, i.e. from most necessary */
			/* to least necessary. Allow a maximum touch of 4, to ensure */
			/* 2 dimensional connectivity of the fwd cells */
			nrp = NULL;
			i = 0;
			for (ttouch = 1; ; ttouch++) {
				int more = 0;
//printf("~1 ttouch = %d\n",ttouch);
				for (rp = crp+3; *rp != -1; rp++) {
					vtxrec *vx = get_vtxrec(&vc, *rp); 
	
					if (vx->tcount == 0)
						continue;
	
					more = 1;
					if (vx->tcount > ttouch)
						continue;
	
					/* For each cube vertex placed at vx->cix */
					for (ee = 0; ee < (1<<di); ee++) {
						int vix = vx->cix + s->g.hi[ee];
						vtxrec *nx;
	
						/* Track touch count on creating cells, and */
						/* clear vertexes that have reached 4, */
						/* so that they don't get any more */
						if ((nx = get_vtxrec(&vc, vix)) != NULL) {
//printf("bx %d, adding fwcell vertex %d for vertex %d\n",bx->ix,vix,*rp);  
							vx->acount++;
							if (vx->acount >= 4)
								vx->tcount = 0;
						}
					}
					i++;
					add2indexlist(s, &nrp, vx->cix, 0);
				}
				if (!more)
					break;
			}
//printf(", now %d fwdcells\n",i);
//printf("~1 replacing vertex list with cell list\n");
	
			if (nrp == NULL)
				error("Surface list bxcell ix %d has no fwd cells",bx->ix);
	
			/* Replace vertex list with cell list */
			free_indexlist(s, &bx->sl);
			bx->sl = nrp;
	
			if (bx->sl == NULL)
				error("Surcface cell nnrev[%d] is empty!\n",bx->ix);
			bx->status = bx_conv;
		}

		if (s->rev.surflin != NULL) {	/* Don't need surflin anymore */
			s->rev.surflin->del(s->rev.surflin);
			s->rev.surflin = NULL;
			s->rev.surflin_en = 0;
		}
		if (cla != NULL)
			free_dmatrix(cla, 0, fdi-1, 0, fdi);
		free_trirec(s, &stc);
		free_trirec(s, &tc);
		free_vtxrec_list(s, &vc);
		free_assdir(s, edgdir);
		free_assdir(s, tridir);
	}

#if defined(REVTABLESTATS) || defined(DEBUG)
	if (fdi > 1) {
		bxcell *bx;
		int surfcelldepth = 0, surfcells = 0;
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			if (bx->sl == NULL
			 || bx->sl[1] == 3)
				continue;
			surfcells++;
			surfcelldepth += bx->sl[1]-3;
		}

		printf("%d/%d surface cells\n",surfcells,rgno);
		printf("%d/%d non-surface cells\n",ingamutcells,rgno);
		printf("%d/%d empty cells\n",emptycells,rgno);
		printf("%d/%d used cells in rev[]\n",revcells,rgno);
		printf("%f average rev[] list length\n",(double)revcelldepth/(double)revcells);
		printf("%f average nnrev[] surface list length\n",(double)surfcelldepth/(double)surfcells);
		printf("%d added surface cells\n",nascells);
		printf("%d removed surface cells\n",nrscells);
		printf("%d added over ink limit vertexes\n",naoulvtxs);
	}
#endif

#ifdef REVVRML
	/* Plot the thinned surface fwd cells */
	/* fwd cell base ix's ? bxcells ? Wait ? */
	if (1 && fdi > 1) plot_fxcell_surface(s, 0, 0, 0);
#endif /* REVVRML */

	/* Fill the non-surface nnrev array from the surface list. */
	{
		bxcell *seedlist = NULL;		/* Linked list of active seeds */
		bxcell *seedlistend = NULL;		/* Last item on seedlist */
		bxcell *xlist = NULL;			/* Linked list of cells being searched */
		bxcell *xlistend = NULL;		/* Last item on xlist */
		bxcell *tlist;					/* Linked list of cells being considered as soln. */
		double emax;					/* Current smallest estimated max weigted distance */
#if defined(REVTABLESTATS) || defined(DEBUG)
		unsigned long smsec = msec_time();
#endif

		DBG(("Filling in rev.nnrev[] grid\n"));

		/* Start the seeding of the nnrev[] array with all the surface cells */
		{
			bxcell *ss;

			for (ss = s->rev.surflist; ss != NULL; ss = ss->slist) {
				/* Add to end of seedlist */
				ss->flist = NULL;
				if (seedlist == NULL)
					seedlist = ss;
				else
					seedlistend->flist = ss;
				seedlistend = ss;

				vflag[ss->ix] |= 1;		/* They are on seed list, so will be filled */
			}
		}

		/* While there are nnrev[] cells to fill */
		while (seedlist != NULL) {
			DCOUNT(cc, MXRO, fdi, -1, -1, 2);	/* bwd neighborhood offset counter */
			int nix;					/* Neighbor offset index */
			bxcell *ss, *tx;

			tx = seedlist;				/* Remove target cell from front of seed list */
			seedlist = tx->flist;

			if (s->rev.nnrev[tx->ix] != NULL)
				error("nncel[%d] in seed list is not empty\n",tx->ix);

#ifdef CHECK_NNLU
			if (tx->ss == NULL || (vflag[tx->ss->ix] & 2) == 0 ) {
				if (tx->ss == NULL)
					printf("nnrev[%d] has NULL seed\n",tx->ix);
				else
					printf("nnrev[%d] has seed %d with flag %x != 3\n",tx->ix,tx->ss->ix, vflag[tx->ss->ix]);
			}
#endif
				
			DBG(("Doing nnrev[%d] vflag %x co %s\n",tx->ix, vflag[tx->ix], debPiv(s->fdi, tx->gc)));
//printf("Doing nnrev[%d] vflag %x co %s\n",tx->ix, vflag[tx->ix], debPiv(s->fdi, tx->gc));

			emax = 1e200;				/* Smallest emax */
			ss = tx->ss;				/* Search start cell */ 
			ss->tix = tx->ix;			/* Mark this cell as being in search list */
			
			/* Make start cell the only entry in the search list */
			ss->xlist = NULL;
			xlist = ss;
			xlistend = ss;

			/* Clear the solution list */
			tlist = NULL;

			/* Note that filling an nnrev[] cell using a seeded search may miss fw cells */
			/* that should be in it, if they are in physically dis-continuous locations */
			/* due to gamut hull convexity. LCh weighting will reduce this somewhat, and */
			/* discontinuity is rarely a desired characteristic of a color conversion, so */
			/* we are ignoring this issue for now. */

			/* While there are cells to search for solutions */
			while (xlist != NULL) {
				double em, ex;

				ss = xlist;					/* Remove next search cell from linked list */
				xlist = xlist->xlist;

				/* Check if this cell could be in solution */
				em = nn_grpgrp_est(s, &ex, &tx->g, &ss->g);
				ss->emin = em;
#if defined(REVTABLESTATS) || defined(DEBUG)
				nnrevcellsearch++;
#endif

				DBG(("Searching rev[%d] co %s, em %f, ex %f\n",ss->ix, debPiv(s->fdi, ss->gc), em, ex));
//printf("Searching rev[%d] co %s, em %f, ex %f\n",ss->ix, debPiv(s->fdi, ss->gc), em, ex);
			
				if (em < emax) {		/* Yes */

					/* Add it to the solution list */
					ss->tlist = tlist;
					tlist = ss;

					DBG(("Adding it to solution list\n"));

					/* Update smallest maximum */
					/* (Will cull existing bxcell solutions with emin > emax later) */
					if (ex < emax)
						emax = ex;

					/* Explore all neighbours, and add any surface cells that haven't been */
					/* searched for this target yet. */ 
					DC_INIT(cc);
					while (!DC_DONE(cc)) {
						bxcell *nbx;

						nix = ss->ix;
						for (f = 0; f < fdi; f++) {
							nn[f] = ss->gc[f] + cc[f];
							if (nn[f] < 0 || nn[f] >= rgres)
								break;					/* Out of bounds */
							nix += cc[f] * s->rev.coi[f];
						}
						if (f < fdi || nix == ss->ix) {
//printf("Rejecting search neigbor co %s because out of bounds or current cell\n",debPiv(s->fdi,cc));
							goto next_neighbor;
						}

						/* We only search surface bxcells */
						if ((vflag[nix] & 2) == 0) {
//printf("Rejecting search neigbor nnrev[%d] co %s because flags = %x\n",nix, debPiv(s->fdi, cc),vflag[nix]);
							goto next_neighbor;
						}

						/* If neighbor is in bounds, and a surface bxcell*/
						{

							/* Expect all all surface bxcells to be in cache */
							if ((nbx = get_surface_bxcell(s, nix)) == NULL)
								error("rspl rev get_surface_bxcell %d failed",nix);

							/* If not already in search list */
							if (nbx->tix != tx->ix) {
//									DBG(("Adding search neigbor nnrev[%d] co %s to search list\n",nbx->ix, debPiv(s->fdi, nbx->gc)));
//printf("Adding search neigbor nnrev[%d] co %s to search list\n",nbx->ix, debPiv(s->fdi, nbx->gc));
								/* Add neigbor to end of search list */
								nbx->tix = tx->ix;		/* Is now in search list */
								nbx->xlist = NULL;
								if (xlist == NULL)
									xlist = nbx;
								else
									xlistend->xlist = nbx;
								xlistend = nbx;
							}
//else 
//printf("Rejecting search neigbor nnrev[%d] co %s because already in list\n",nbx->ix, debPiv(s->fdi, nbx->gc));
						}
					next_neighbor:;
						DC_INC(cc);
					}
				} 
//else
//printf("Rejected rev[%d] co %s, because em %f >= emax %f\n",ss->ix, debPiv(s->fdi, ss->gc), em, emax);
			}

			/* Create the nnrev[] list from the candidate bxcell solutions */
			if (tlist != NULL) {
				create_nnrev_list(s, tx, tlist, emax);  
			}
#if defined(REVTABLESTATS) || defined(DEBUG)
			nnrevcells++;
			nnrevcelldepth += s->rev.nnrev[tx->ix][1]-3;
			if (s->rev.nnrev[tx->ix][1]-3 > nnmxrevcelldepth)
				nnmxrevcelldepth = s->rev.nnrev[tx->ix][1]-3;
#endif

			/* If this was a super-cell, explore the 2nd row around this cell, */
			/* and locate any cells not on the seeding list */
			if (tx->scell != NULL) {
				DCOUNT(sc, MXRO, fdi, -3, -3, 4);
				DC_INIT(sc);
				while (!DC_DONE(sc)) {
					int co[MXRO]; 
					int ok = 0;
					int nix = tx->ix;

					for (f = 0; f < fdi; f++) {
						co[f] = tx->gc[f] + sc[f];
						if (co[f] < 0 || co[f] >= s->rev.res)
							break;
						nix += sc[f] * s->rev.coi[f];
						if (sc[f] == -3 || sc[f] == 3)
							ok = 1;				/* Just surface of +/- 2 */
					}
					if (!ok && sc[0] == -2)
						sc[0] = 2;			/* Skip center */

					/* Put this cell on list and stop searching. */
					if (f >= fdi && (vflag[nix] & 1) == 0) {

						if ((vflag[nix] & 2) != 0) {	/* If un-filled surface bxcell */
							/* Get surface bxcell from cache index for seed */
							if ((ss = get_surface_bxcell(s, nix)) == NULL)
								error("rspl rev get_surface_bxcell %d failed #2, vflag = %x",nix,vflag[nix]);
//printf("Fetched surface bxcell seed %d vflag %x\n",ss->ix, vflag[ss->ix]);
						} else {	/* If un-filled nnrev */
							if (get_surface_bxcell(s, nix) != NULL)
								error("vflag[%d] = %x, but cell is in surface list hash\n");

							/* Create new temporary (non-surface) bxcell seed. */
							/* If we are sufficiently far from the seed point, */
							/* a super-cell to improve seeding performance will be created. */
							ss = new_bxcell(s, nix, co, tx->ss, tx->sdist, vflag);
#if defined(REVTABLESTATS) || defined(DEBUG)
							if (tx->scell != NULL)
//								nnsuperfill += tx->scell[3]-3;
								nnsuperfill++;
							else
								nnsinglefill++;
#endif
//printf("Created temporary seed bxcell %d vflag %x\n",ss->ix, vflag[ss->ix]);
						}
						DBG(("Adding seed neighbor nnrev[%d] vflag %x co %s to seed list\n",ss->ix, vflag[ss->ix], debPiv(s->fdi, ss->gc)));
//printf("Adding seed neighbor nnrev[%d] vflag %x co %s to seed list\n",ss->ix, vflag[ss->ix], debPiv(s->fdi, ss->gc));

						/* Add to end of seedlist */
						ss->flist = NULL;
						if (seedlist == NULL)
							seedlist = ss;
						else
							seedlistend->flist = ss;
						seedlistend = ss;
						vflag[ss->ix] |= 1;		/* This is on seed list, so will be filled */
					}
					DC_INC(sc);
				}
			} else {
				/* Explore neighbours, and add any nnrev[] cells that haven't been */
				/* put on the seed list yet. */
				for (f = 0; f < fdi; f++)
					cc[f] = tx->gc[f];
				nix = tx->ix;

				for (ff = 0; ff < (fdi << 1); ff++) {
					f = ff >> 1;			/* Dimension being explored */

					cc[f] += (ff & 1) ? 1 : -1;
					nix   += (ff & 1) ? s->rev.coi[f] : -s->rev.coi[f];

					/* If found unfilled nnrev[] cell */
					if (cc[f] >= 0 && cc[f] < rgres && (vflag[nix] & 1) == 0) {

						if ((vflag[nix] & 2) != 0) {	/* If un-filled surface bxcell */
							/* Get surface bxcell from cache index for seed */
							if ((ss = get_surface_bxcell(s, nix)) == NULL)
								error("rspl rev get_surface_bxcell %d failed #2, vflag = %x",nix,vflag[nix]);
//printf("Fetched surface bxcell seed %d vflag %x\n",ss->ix, vflag[ss->ix]);
						} else {	/* If un-filled nnrev */
							if (get_surface_bxcell(s, nix) != NULL)
								error("vflag[%d] = %x, but cell is in surface list hash\n");

							/* Create new temporary (non-surface) bxcell seed. */
							/* If we are sufficiently far from the seed point, */
							/* a super-cell to improve seeding performance will be created. */
							ss = new_bxcell(s, nix, cc, tx->ss, tx->sdist, vflag);
#if defined(REVTABLESTATS) || defined(DEBUG)
							if (tx->scell != NULL)
//								nnsuperfill += tx->scell[3]-3;
								nnsuperfill++;
							else {
								nnsinglefill++;
							}
#endif
//printf("Created temporary seed bxcell %d vflag %x\n",ss->ix, vflag[ss->ix]);
						}
						DBG(("Adding seed neighbor nnrev[%d] vflag %x co %s to seed list\n",ss->ix, vflag[ss->ix], debPiv(s->fdi, ss->gc)));
//printf("Adding seed neighbor nnrev[%d] vflag %x co %s to seed list\n",ss->ix, vflag[ss->ix], debPiv(s->fdi, ss->gc));

						/* Add to end of seedlist */
						ss->flist = NULL;
						if (seedlist == NULL)
							seedlist = ss;
						else
							seedlistend->flist = ss;
						seedlistend = ss;
						vflag[ss->ix] |= 1;		/* This is on seed list, so will be filled */
					}

					cc[f] -= (ff & 1) ? 1 : -1;
					nix   -= (ff & 1) ? s->rev.coi[f] : -s->rev.coi[f];
				} 
			}

			/* if this is a temporary bxcell (i.e. not a surface bxcell), */
			/* we can now free it */
			if ((vflag[tx->ix] & 2) == 0) {
//printf("Done with non-surface bxcell %d vflag %x\n",tx->ix,vflag[tx->ix]);
				del_bxcell(s, tx);
			}
		}
		/* We've done the nnrev[] setup */
		DBG(("rev.nnrev[] grid done - cleaning up\n"));

#ifdef CHECK_NNLU
		if (fdi > 1) {
			/* Check that every nnrev[] cell is filled */
			printf("Checking all %d nnrev[] cells are filled\n",rgno);
			for (i = 0; i < rgno; i++) {
				if (   ((vflag[i] & 2) != 0 || s->rev.rev[i] == NULL || s->rev.rev[i][1] == 3)
				     && (s->rev.nnrev[i] == NULL || s->rev.nnrev[i][1] == 3)) {
					printf("Found empty nnrev[%d] ?:\n",i);
					printf(" vflag %x\n",vflag[i]);
					if (s->rev.nnrev[i] == NULL)
						printf(" nnrev = NULL\n");
					else
						printf(" nnrev length = %d\n",s->rev.nnrev[i][1]-3);
					if (s->rev.rev[i] == NULL)
						printf(" rev = NULL\n");
					else
						printf(" rev = length = %d\n",s->rev.rev[i][1]-3);
				}
			} 
		}
#endif	/* CHECK_NNLU */

		/* Free up flag array used for construction */
		if (vflag != NULL) {
			DECSZ(s, rgno * sizeof(char));
			free(vflag);
		}

#ifndef CHECK_NNLU
		/* Free up surface linked list and delete the bxcells. */
		free_surflist(s);
#endif

		/* Free up surface bxcell hash index */
		free_surfhash(s, 0);
	
#if defined(REVTABLESTATS) || defined(DEBUG)
		if (fdi > 1) {
			nnrevshare = nnrevcells;
			for (i = 0; i < s->rev.sharellen; i++)
				nnrevshare += (s->rev.sharelist[i][1]-4) * (s->rev.sharelist[i][1]-3);

			printf("%d/%d used cells in nnrev list\n",nnrevcells,rgno);
			printf("%f average cells searched\n",(double)nnrevcellsearch/(double)nnrevcells);
			printf("%d max bxcells used\n",maxbxcount);
			printf("%.1f%% super-cell filled\n",100.0 * nnsuperfill/(nnsuperfill+nnsinglefill));
			printf("%f average list length\n",(double)nnrevcelldepth/(double)nnrevcells);
			printf("%d max list length\n",nnmxrevcelldepth);
			printf("%f average shared lists\n",(double)nnrevshare/(double)nnrevcells);
			printf("Took %f seconds\n",0.001 * (msec_time()-smsec));
			printf("Overall took %f seconds\n",0.001 * (msec_time()-smsec));
		}
#endif
	}

	s->rev.rev_valid = 1;

	if (fdi > 1 && s->verbose)
		fprintf(stdout, "%cnnrev initialization done\n",cr_char);

	DBG(("init_revaccell finished\n"));
}

/* Invalidate the reverse acceleration structures (section Two) */
static void invalidate_revaccell(
rspl *s		/* Pointer to rspl grid */
) {
	int e, di = s->di;
	int **rpp, *rp;

	/* Invalidate the whole rev cache (Third section) */
	invalidate_revcache(s->rev.cache);

	/* Free up the contents of rev.rev[] and rev.nnrev[] */
	if (s->rev.rev != NULL) {
		for (rpp = s->rev.rev; rpp < (s->rev.rev + s->rev.no); rpp++) {
			if (*rpp != NULL)
				free_indexlist(s, rpp);
		}
	}
	if (s->rev.nnrev != NULL) {

		/* Free up nn list sharelist records - this will free and set */
		/* any shared lists to NULL */
		free_sharelist(s);

		for (rpp = s->rev.nnrev; rpp < (s->rev.nnrev + s->rev.no); rpp++) {
			if (*rpp != NULL)
				free_indexlist(s, rpp);
		}
	}

	if (di > 1 && s->rev.rev_valid) {
		rev_struct *rsi, **rsp;
		size_t ram_portion = g_avail_ram;

		/* Remove it from the linked list */
		for (rsp = &g_rev_instances; *rsp != NULL; rsp = &((*rsp)->next)) {
			if (*rsp == &s->rev) {
				*rsp = (*rsp)->next;
				break;
			}
		}

		/* Aportion the memory */
		g_no_rev_cache_instances--;

		if (g_no_rev_cache_instances > 0) {
			ram_portion /= g_no_rev_cache_instances; 
			for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next)
				rsi->max_sz = ram_portion;
			if (s->verbose)
				fprintf(stdout, "%cThere %s %d rev cache instance%s with %lu Mbytes limit\n",
				                cr_char,
								g_no_rev_cache_instances > 1 ? "are" : "is",
			                    g_no_rev_cache_instances,
								g_no_rev_cache_instances > 1 ? "s" : "",
			                    (unsigned long)(ram_portion/1000000));
		}
	}
	s->rev.rev_valid = 0;
}

#ifdef CHECK_NNLU
/* ====================================================== */

/* Used exautive searches to check that nn lookup found a good solution */
static void check_nn(
rspl *s,
double *oval,	/* Un-clipped output target value */
co *cpp			/* Clipped output space value in cpp[0].v[] */
				/* nn solution in cpp[0].p[] */
) {
	int i, j;		/* Index of fwd grid point */
	int e, f, ee, ff;
	int di = s->di;
	int fdi = s->fdi;
	int gno = s->g.no;
	int good = 1;
	int found = 0;
	int printed = 0;

	ECOUNT(gc, MXRI, di, 0, s->g.res, 0);/* coordinates */
	float *gp;			/* Pointer to grid data */
	double iv[MXDI];
	double ov[MXDO];
	double chov[MXDO], de;
	
	int bix = -1;
	double bdist = 1e200;
	double biv[MXDI];
	double bov[MXDO];
	int six = -1;
	double sdist = 1e200;
	double siv[MXDI];
	double sov[MXDO];

	double odelta;
	double idelta;
	double fsdelta;
	double sodelta;
	double sidelta;

	s->rev.cknn_no++;

	/* Compute the given solutions de */
	de = sqrt(lchw_sq(s, oval, cpp[0].v));

	/* Go through every fwd vertex looking for closest and 2nd closest */
	EC_INIT(gc);
	for (gp = s->g.a, i = 0; i < gno; gp += s->g.pss, i++) {
		double dist;

		if (s->limiten && gp[-1] > s->limitv) {
			EC_INC(gc);
			continue;			/* Over the ink limit */
		}

		for (f = 0; f < fdi; f++)
			ov[f] = gp[f];

		dist = lchw_sq(s, oval, ov);

		if (dist < bdist) {
			six = bix;
			bix = i;
			for (e = 0; e < s->di; e++) {
				siv[e] = biv[e];
				biv[e] = s->g.l[e] + gc[e] * s->g.w[e];
			}
			for (f = 0; f < fdi; f++) {
				sov[f] = bov[f];
				bov[f] = ov[f];
			}
			sdist = bdist;
			bdist = dist;

		} else if (dist < sdist) {
			six = i;
			for (e = 0; e < s->di; e++)
				siv[e] = s->g.l[e] + gc[e] * s->g.w[e];
			for (f = 0; f < fdi; f++)
				sov[f] = ov[f];
			sdist = dist;

		}
		EC_INC(gc);
	}

	/* What is magnitude of target match ? */
	odelta = sqrt(lchw_sq(s, bov, oval));
	
	/* What is magnitude of solution match */
	idelta = 0.0;
	for (e = 0; e < s->di; e++) {
		double tt = biv[e] - cpp[0].p[e];
		idelta += tt * tt;
	}
	idelta = sqrt(idelta);
	
	/* What is scale of solution from closest to 2nd closest ? */
	fsdelta = 0.0;
	for (e = 0; e < s->di; e++) {
		double tt = biv[e] - siv[e];
		fsdelta += tt * tt;
	}
	fsdelta = sqrt(fsdelta);

	/* What is magnitude of target match to secondary ? */
	sodelta = sqrt(lchw_sq(s, sov, oval));
	
	/* What is magnitude of solution match to secondary ?*/
	sidelta = 0.0;
	for (e = 0; e < s->di; e++) {
		double tt = siv[e] - cpp[0].p[e];
		sidelta += tt * tt;
	}
	sidelta = sqrt(sidelta);
	
	/* If our exaustive search is better than the nn solution: */
	if (odelta < (de - 1e-6)) {
		double dde = de - odelta;
		if (dde > s->rev.cknn_we)
			s->rev.cknn_we = dde;
		s->rev.cknn_noerrs++;
		good = 0;
		printf("check_nn: target %s\n",debPdv(s->fdi,oval));
		printf("check_nn: cliped to %s, de %f\n",debPdv(s->di,cpp[0].v),de);
		printf("check_nn: solution %s\n",debPdv(s->di,cpp[0].p));
		printf("check_nn: check target %s, de %f\n",debPdv(s->fdi, bov),odelta);
		printf("check_nn: check solution %s, de %f @ix %d\n",debPdv(s->di, biv),idelta,bix);
		printf("check_nn: check 2nd target %s, de %f\n",debPdv(s->fdi, sov),sodelta);
		printf("check_nn: check 2nd solution %s, de %f @ ix %d\n",debPdv(s->di, siv),sidelta,six);
		printf("check_nn: excess delta %f\n",dde);
		printf("check_nn: first-second delta %f\n",fsdelta);
		if (six >= 0 && (de - sodelta) > 1e-6) {
			printf("check_nn: beyond 2nd best by %f!\n",de-sodelta);
			s->rev.cknn_nobsb++;
		}
		printed = 1;
	}

	/* Search surface nnrev cells, to make sure our best is in it somewhere */
	if (s->rev.surflist != NULL) {
		bxcell *ss;

		for (ss = s->rev.surflist; ss != NULL; ss = ss->slist) {
			int *flist = ss->sl;		/* List of fwd cells */

			if (flist == NULL)
				error("surflist nnrev[%d] is empty!",ss->ix);

			/* For each forward cell */
			for (flist += 3; *flist != -1; flist++) {
				/* For each cube vertex */
				for (ee = 0; ee < (1<<di); ee++) {
					int vix = *flist + s->g.hi[ee];
					if (vix == bix) {
						found = 1;
						if (!good)
							printf("check_nn: found best vertex in surf nnrev[%d] fwd %d \n",ss->ix,*flist);
						break;
					}
				}
			}
		}

		if (!found) {
			int rgno = s->rev.no;
			int **rpp;
			int revfound = 0;

			s->rev.cknn_nonis++;
			if (good) {
				printf("check_nn: target %s\n",debPdv(s->fdi,oval));
				printf("check_nn: cliped to %s, de %f\n",debPdv(s->di,cpp[0].v),de);
				printf("check_nn: solution %s\n",debPdv(s->di,cpp[0].p));
				printf("check_nn: check target %s, de %f\n",debPdv(s->fdi, bov),odelta);
				printf("check_nn: check solution %s, de %f\n",debPdv(s->di, biv),idelta);
				printf("check_nn: result is OK\n");
			}
			if (s->rev.surflist == NULL) {
				printf("check_nn: No surface list to check against\n");
			} else {
				printf("check_nn: DIDN'T find best vertex %d in nnrev[] surface list\n",bix);
			}
			printed = 1;

			/* See where it is in the rev[] list, and what the corresponding nnrev[] */
			/* looks like */
			for (rpp = s->rev.rev, i = 0; i < rgno; rpp++, i++) {
				int *flist = *rpp;

				if (flist == NULL)
					continue;

				/* For each forward cell */
				for (flist += 3; *flist != -1; flist++) {
					/* For each cube vertex */
					for (ee = 0; ee < (1<<di); ee++) {
						int vix = *flist + s->g.hi[ee];
						if (vix == bix) {
							revfound = 1;
							printf("check_nn: found best vertex in rev[%d] fwd %d",i,*flist);
							if (s->rev.nnrev[i] != NULL)
								printf(" - cspndg. nnrev has list\n");
							else
								printf(" - cspndg. nnrev is empty\n");
							break;
						}
					}
				}
			}
			if (!revfound) {
				printf("check_nn: DIDN'T find best vertex %d in rev list\n",bix);
			}
		}
	}

	/* Check if the nnrev[] cell for this target has the fwd cell */
	if (s->rev.surflist != NULL && (!good || !found)) {
		int mi[MXDO];
		int rgres_1 = s->rev.res - 1;
		int ix, *flist;
		int found2 = 0;

		for (ix = 0, f = 0; f < fdi; f++) {
			double t = (oval[f] - s->rev.gl[f])/s->rev.gw[f];
			mi[f] = (int)floor(t);			/* Grid coordinate */
			if (mi[f] < 0)			/* Clip to reverse range, so we always return a result  */
				mi[f] = 0;
			else if (mi[f] > rgres_1)
				mi[f] = rgres_1;
			ix += mi[f] * s->rev.coi[f];	/* Accumulate reverse grid index */
		}
		flist = s->rev.nnrev[ix];

		if (flist != NULL) {
			/* For each forward cell */
			for (flist += 3; *flist != -1; flist++) {
				/* For each cube vertex */
				for (ee = 0; ee < (1<<di); ee++) {
					int vix = *flist + s->g.hi[ee];
					if (vix == bix) {
						found2 = 1;
						printf("check_nn: found best vertex %d in expected nnrev[%d], fwd %d\n",bix,ix,*flist);
						printed = 1;
						break;
					}
				}
			}
		}
		if (!found2) {
			printf("check_nn: DIDN'T find best vertex %d in expected nnrev[%d] list\n",bix,ix);
			printed = 1;
		}
	}
	if (printed)
		printf("\n");
}

static void print_nnck(rspl *s) {
	printf("check_nn di %d fdi %d checked %d lookups:\n",s->di,s->fdi,s->rev.cknn_no);
	printf("check_nn got %d not as good as best vertex\n",s->rev.cknn_noerrs);
	printf("check_nn got %d not as good as 2nd best vertex\n",s->rev.cknn_nobsb);
	printf("check_nn got %d not in surface list\n",s->rev.cknn_nonis);
	printf("check_nn got %f worst excess de\n",s->rev.cknn_we);
	printf("\n");
}

#endif /* CHECK_NNLU */
/* ====================================================== */

/* Initialise the rev First section, basic information that doesn't change */
/* This is called on initial setup when s->rev.inited == 0 */
static void make_rev_one(
rspl *s
) {
	int i, j;		/* Index of fwd grid point */
	int e, f, ee, ff;
	int di = s->di;
	int fdi = s->fdi;
	int rgno, gno = s->g.no;
	int rgres;		/* bwd cell grid (rev[], nnrev[]) resolution */
	int rgres_1;	/* rgres -1 == maximum base coord value */
	datao rgmin, rgmax;

	DBG(("make_rev_one called, di = %d, fdi = %d, mgres = %d\n",di,fdi,(int)s->g.mres));

//printf("~1 nnb = %d\n",nnb);

	s->get_out_range(s, rgmin, rgmax);	/* overall output min/max */

	/* Expand out range to encompass declared range */
	/* The declared range is assumed to be the range over which */
	/* we may want an reasonably accurate nearest reverse lookup. */
	for (f = 0; f < fdi; f++) {
		if ((s->d.vl[f] + s->d.vw[f]) > rgmax[f])
				rgmax[f] = s->d.vl[f] + s->d.vw[f];
		if (s->d.vl[f] < rgmin[f])
				rgmin[f] = s->d.vl[f];
	}

	/* Expand out range slightly to allow for out of gamut points */
	for (f = 0; f < fdi; f++) {
		double del = (rgmax[f] - rgmin[f]) * 0.10;	/* Expand by +/- 10% */
		rgmax[f] += del;
		rgmin[f] -= del;
	}
//printf("~~got output range\n");

	/* Heuristic - reverse grid acceleration resolution ? */
	/* Should this really be adapted to be constant in output space ? */
	/* (ie. make the gw aprox equal ?) Would complicate code rev accell */
	/* indexing though. */
	{
		char *ev;
		double gresmul = REV_ACC_GRES_MUL;		/* Typically 2.0 */

		if ((gresmul * s->g.mres) > (double)REV_ACC_GRES_LIMIT) {
			gresmul = (double)REV_ACC_GRES_LIMIT/s->g.mres;		/* Limit target res to typ. 43. */
		}

		/* Allow the user to override if it causes memory consumption problems */
		/* or to speed things up if more memory is available */
		if ((ev = getenv("ARGYLL_REV_ACC_GRID_RES_MULT")) != NULL) {
			double mm;
			mm = atof(ev);
			if (mm > 0.1 && mm < 20.0)
				gresmul *= mm;
		}
		/* Less than 4 is not functional */
		if ((rgres = (int) gresmul * s->g.mres) < 4)
			rgres = 4;
	}
	s->rev.res = rgres;			/* == number of cells per side */
	rgres_1 = rgres-1;

	/* Number of elements in the rev.grid */
	for (rgno = 1, f = 0; f < fdi; f++, rgno *= rgres);
	s->rev.no = rgno;

//printf("~1 rgres = %d\n",rgres);
	/* Compute coordinate increments */
	s->rev.coi[0] = 1;
//printf("~1 coi[0] = %d\n",s->rev.coi[0]);
	for (f = 1; f < fdi; f++) {
		s->rev.coi[f] = s->rev.coi[f-1] * rgres;
//printf("~1 coi[%d] = %d\n",f,s->rev.coi[f]);
	}

	/* Compute index offsets from base of cube to other corners. */
	for (s->rev.hoi[0] = f = 0, j = 1; f < fdi; j *= 2, f++) {
		for (i = 0; i < j; i++)
			s->rev.hoi[j+i] = s->rev.hoi[i] + s->rev.coi[f];	/* In grid points */
	}
//for (ff = 0; ff < (1 << fdi); ff++)
//printf("~1 hoi[%d] = %d\n",ff,s->rev.hoi[ff]);

	/* Conversion from output value to cell indexes */
	for (f = 0; f < fdi; f++) {
		s->rev.gl[f] = rgmin[f];
		s->rev.gh[f] = rgmax[f];
		s->rev.gw[f] = (rgmax[f] - rgmin[f])/(double)rgres;
	}

	if ((s->rev.rev = (int **) rev_calloc(s, rgno, sizeof(int *))) == NULL)
		error("rspl malloc failed - rev.grid points");
	INCSZ(s, rgno * sizeof(int *));

	if ((s->rev.nnrev = (int **) rev_calloc(s, rgno, sizeof(int *))) == NULL)
		error("rspl malloc failed - rev.nngrid points");
	INCSZ(s, rgno * sizeof(int *));

	s->rev.inited = 1;
	s->rev.stouch = 1;

	DBG(("make_rev_one finished\n"));
}

/* ====================================================== */

/* First section of rev_struct init. */
/* Initialise the fxcell cache, sub simplex information */
/* and reverse lookup acceleration structures. */
/* This is called by a reverse interpolation call */
/* that discovers that the reverse index list haven't */
/* been initialised. */
static void make_rev(
rspl *s
) {
	int e, di = s->di;
	char *ev;
	size_t avail_ram = 256 * 1024 * 1024;	/* Default assumed RAM in the system */
	size_t ram1, ram2;						/* First Gig and rest */
	static int repsr = 0;					/* Have we reported system RAM size ? */
	size_t max_vmem = 0;

	DBG(("make_rev called, di = %d, fdi = %d, mgres = %d\n",di,s->fdi,(int)s->g.mres));

	/* Figure out how much RAM we can use for the rev cache. */
	/* (We compute this for each rev instance, to account for any VM */
	/* limit changes due to intervening allocations) */
	if (di > 1 || g_avail_ram == 0) {
	#ifdef NT 
		{
			BOOL (WINAPI* pGlobalMemoryStatusEx)(MEMORYSTATUSEX *) = NULL;
			MEMORYSTATUSEX mstat;
	
			pGlobalMemoryStatusEx = (BOOL (WINAPI*)(MEMORYSTATUSEX *))
			                        GetProcAddress(LoadLibrary("KERNEL32"), "GlobalMemoryStatusEx");
	
			if (pGlobalMemoryStatusEx == NULL)
				error("Unable to link to GlobalMemoryStatusEx()");
			mstat.dwLength = sizeof(MEMORYSTATUSEX);
			if ((*pGlobalMemoryStatusEx)(&mstat) != 0) {
//printf("~1 ullTotalPhys = 0x%" PF64PREC "x\n",mstat.ullTotalPhys);
//printf("~1 ullAvailPhys = 0x%" PF64PREC "x\n",mstat.ullAvailPhys);
				if (sizeof(avail_ram) < 8 && mstat.ullTotalPhys > 0xffffffffL)
					mstat.ullTotalPhys = 0xffffffffL;
				avail_ram = mstat.ullTotalPhys;
//printf("~1 avail_ram = %" PFSTPREC "u\n",avail_ram);
			} else {
				warning("%cWarning - Unable to get system memory size",cr_char);
			}
		}
	#else
	#ifdef __APPLE__
		{
			long long memsize;
			size_t memsize_sz = sizeof(long long);
			if (sysctlbyname("hw.memsize", &memsize, &memsize_sz, NULL, 0) == 0) {
				if (sizeof(avail_ram) < 8 && memsize > 0xffffffffL)
					memsize = 0xffffffff;
				avail_ram = memsize;
			} else {
				warning("%cWarning - Unable to get system memory size",cr_char);
			}
			
		}
	#else	/* Linux */
		{
			long long total;
			total = (long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES);
			if (sizeof(avail_ram) < 8 && total > 0xffffffffL)
				total = 0xffffffffL;
			avail_ram = total;
		}
	#endif
	#endif
		DBG(("System RAM = %lu Mbytes\n",(unsigned long)(avail_ram/1000000)));
	
		/* Make it sane */
		if (avail_ram < (256 * 1024 * 1024)) {
			warning("%cWarning - System RAM size seems very small (%lu MBytes),"
			        " assuming 256Mb instead",cr_char,(unsigned long)(avail_ram/1000000));
			avail_ram = 256 * 1024 * 1024;
		}
		// avail_ram = -1;		/* Fake 4GB of RAM. This will swap! */
	
		ram1 = avail_ram;
		ram2 = 0;

		/* Don't be greedy, and limit to 1024 Mbytes */
		if (ram1 > (1024 * 1024 * 1024)) {
			ram1 = 1024 * 1024 * 1024;
			ram2 = avail_ram - ram1;
		}
	
		/* Default maximum reverse memory (typically 50% of the first Gig, 75% of the rest) */
		g_avail_ram = (size_t)(REV_MAX_MEM_RATIO * ram1
		            +          REV_MAX_MEM_RATIO2 * ram2);
	
//printf("~1 ram1 = %" PFSTPREC "u, ram2 = %" PFSTPREC "u\n",ram1,ram2);
//printf("~1 g_avail_ram = %" PFSTPREC "u\n",g_avail_ram);

		/* Many 32 bit systems have a virtual memory limit, so we'd better stay under it. */
		/* This is slightly dodgy though, since we don't know how much memory other */
		/* software will need to malloc. A more sophisticated approach would be to */
		/* replace all malloc/calloc/realloc calls in the exe with a version that on failure, */
		/* sets the current memory usage as the new limit, and then */
		/* frees up some rev cache space before re-trying. This is a non-trivial change */
		/* to the source code though, and really has to include all user mode */
		/* libraries we're linked to, making implementation problematic. */ 
		/* Instead we do a simple test to see what the maximum allocation is, and */
		/* then use 75% of that for cache, and free cache and retry if */
		/* malloc failes in rev.c. Too bad if 25% isn't enough, and a malloc fails */
		/* outside rev.c... */
		if (sizeof(avail_ram) < 8) {
			char *alocs[4 * 1024];
			size_t safe_max_vmem = 0;
			int i; 
	
#ifdef __APPLE__
			int old_stderr, new_stderr;

			/* OS X malloc() blabs about a malloc failure. This */
			/* will confuse users, so we temporarily redirect stdout */
			fflush(stderr);
			old_stderr = dup(fileno(stderr));
			new_stderr = open("/dev/null", O_WRONLY | O_APPEND);
			dup2(new_stderr, fileno(stderr));
#endif
			for (i = 0; (i < 4 * 1024);i++) {
				if ((alocs[i] = malloc(1024 * 1024)) == NULL) {
					break;
				}
				max_vmem = (i+1) * 1024 * 1024;
			}
			for (--i; i >= 0; i--) {
				free(alocs[i]);
			}
#ifdef __APPLE__
			fflush(stderr);
			dup2(old_stderr, fileno(stderr));	/* Restore stderr */
			close(new_stderr);
			close(old_stderr);
#endif
			/* To compute a true value, we need to allow for any VM already */
			/* used by any rev instances. */
			{
				rev_struct *rsi;

				for (rsi = g_rev_instances; rsi != NULL; rsi = rsi->next)
					max_vmem += rsi->sz;
			}
			
//fprintf(stdout,"~1 Abs max VM = %lu Mbytes\n",(unsigned long)(max_vmem/1000000));
			safe_max_vmem = (size_t)(0.85 * max_vmem);
			if (g_avail_ram > safe_max_vmem) {
				g_avail_ram = safe_max_vmem;
				if (s->verbose && repsr == 0)
					fprintf(stdout,"%cTrimmed maximum cache RAM to %lu Mbytes to allow for VM limit\n",cr_char,(unsigned long)(g_avail_ram/1000000));
			}
		}
	
		/* Check for environment variable tweak  */
		if ((ev = getenv("ARGYLL_REV_CACHE_MULT")) != NULL) {
			double mm, gg;
			mm = atof(ev);
			if (mm < 0.01)			/* Make it sane */
				mm = 0.01;
			else if (mm > 100.0)
				mm = 100.0;
			gg = g_avail_ram * mm + 0.5;
			if (gg > (double)(((size_t)0)-1))
				gg  = (double)(((size_t)0)-1);
			g_avail_ram = (size_t)(gg);
		}
		if (max_vmem != 0 && g_avail_ram > max_vmem && repsr == 0) {
			g_avail_ram = (size_t)(0.95 * max_vmem);
			fprintf(stdout,"%cARGYLL_REV_CACHE_MULT * RAM trimmed to %lu Mbytes to allow for VM limit\n",cr_char,(unsigned long)(g_avail_ram/1000000));
		}
	}

//printf("~1 g_avail_ram = %" PFSTPREC "u\n",g_avail_ram);
	/* Default - this will get aportioned as more instances appear */
	s->rev.max_sz = g_avail_ram;

//printf("~1 g_avail_ram = %" PFSTPREC "u\n",g_avail_ram);
	DBG(("reverse cache max memory = %lu Mbytes\n",(unsigned long)(s->rev.max_sz/1000000)));
	if (s->verbose && repsr == 0) {
		fprintf(stdout, "%cRev cache RAM = %lu Mbytes\n",cr_char,(unsigned long)(g_avail_ram/1000000));
		repsr = 1;
	}

	/* Sub-simplex information for each sub dimension */
	for (e = 0; e <= di; e++) {
		if (s->rev.sspxi[e].spxi != NULL)	/* Assert */
			error("rspl rev, internal, init_ssimplex_info called on already init'd\n");

		rspl_init_ssimplex_info(s, &s->rev.sspxi[e], e);
	}

	make_rev_one(s);

	/* Reverse cell cache allocation */
	s->rev.cache = alloc_revcache(s);

	DBG(("make_rev finished\n"));
}

/* ====================================================== */

#if defined(DEBUG1) || defined(DEBUG2)

/* Utility - return a string containing a fwd cells output value range */
static char *pcellorange(fxcell *c) {
	static char buf[5][300];
	static int ix = 0;
	char *bp;
	rspl *s = c->s;
	int di = s->di, fdi = s->fdi;
	int ee, e, f;
	
	datao min, max;

//	double p[POW2MXRI][MXRI]; /* Vertex input positions for this cube. */
//	double v[POW2MXRI][MXRO+1]; /* Vertex data for this cube. Copied to x->v[] */
//							/* v[][fdi] is the ink limit values, if relevant */

	for (f = 0; f < fdi; f++) {
		min[f] = 1e60;
		max[f] = -1e60; 
	}

	/* For all other grid points in the cube */
	for (ee = 0; ee < (1 << di); ee++) {
		
		/* Update bounding box for this grid point */
		for (f = 0; f < fdi; f++) {
			if (min[f] > c->v[ee][f])	
				 min[f] = c->v[ee][f];
			if (max[f] < c->v[ee][f])
				 max[f] = c->v[ee][f];
		}
	}
	if (++ix >= 5)
		ix = 0;
	bp = buf[ix];

	for (e = 0; e < fdi; e++) {
		if (e > 0)
			*bp++ = ' ';
		sprintf(bp, "%f:%f", min[e],max[e]); bp += strlen(bp);
	}
	return buf[ix];
}

#endif
/* ====================================================== */

#undef DEBUG
#undef DBGV
#undef DBG
#define DBGV(xxx)
#define DBG(xxx) 

#ifdef REVVRML
/* ====================================================== */
/* VRML diagnostic output functions */

/* Plot the initial surface rev cells */
static void plot_bxfwcells(
rspl *s,
int dobxcells,			/* Plot rev cells */
int dofwcells,			/* Plot fwd cells */
int dofwlabels			/* Plot fwd cell base indexs */ 
) {
	int i, j;		/* Index of fwd grid point */
	int e, f, ee, ff;
	int di = s->di;
	int fdi = s->fdi;
	bxcell *bx;
	vrml *wrl;
	double grey[3] = { 0.5, 0.5, 0.5 };
	double white[3] = { 1.0, 1.0, 1.0 };

	wrl = new_vrml("raw_bxfwcells", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);
	wrl->add_marker(wrl, s->rev.ocent, NULL, 1.0);

	if (dofwlabels) {
		/* Put text for every base cube index */
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int vix[POW2MXRI];
			int *crp, *rp;
		
			crp = s->rev.rev[bx->ix];
		
			for (rp = crp+3; *rp != -1; rp++) {
				int ix = *rp;
				char index[100];
				double vv[MXRI];
				int off = 0;		// 0 .. 7, choose cube vertex
				float *fcb = s->g.a + (ix + s->g.hi[off]) * s->g.pss;
		
				for (e = 0; e < di; e++)
					vv[e] = fcb[e];
				sprintf(index, "%d",ix);
				wrl->add_text(wrl, index, vv, white, 0.3);
			}
		}
	}

	if (dobxcells) {
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int vix[POW2MXRO];
			DCOUNT(cc, MXRO, fdi, 0, 0, 2);		/* Vertex counter */
			int *crp, *rp;
		
			/* Plot bxcell's */
			i = 0;
			DC_INIT(cc);
			while (!DC_DONE(cc)) {
				double vv[MXRO];
				for (f = 0; f < fdi; f++)
					vv[f] = (bx->gc[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
				vix[i] = wrl->add_vertex(wrl, 0, vv);
				DC_INC(cc);
				i++;
			}

			/* For each vertex */
			for (i = 0; i < (1 << fdi); i++) {
				int lix[2];

				lix[0] = vix[i];

				/* for each dimension */ 
				for (j = 0; j < fdi; j++) {
					if (i & (1<<j))
						continue;		/* Would go outside cube */

					lix[1] = vix[i | (1 << j)];
					if (dofwcells)
						wrl->add_col_line(wrl, 0, lix, grey);
					else
						wrl->add_line(wrl, 0, lix);
				}
			}
		}
	}

	if (dofwcells) {
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int vix[POW2MXRI];
			int *crp, *rp;
		
			/* Add fwd cells */
			crp = s->rev.rev[bx->ix];
			for (rp = crp+3; *rp != -1; rp++) {
				float *fcb = s->g.a + *rp * s->g.pss;

				/* Skip grid base points on the upper edge of the grid */
				for (e = 0; e < di; e++) {
					if (G_FL(fcb, e) == 0)		/* At the top edge */
						break;
				}
				if (e < di) {
					printf("Fwd cell base index %d is on upper edge!\n",*rp);
					continue;
				}

				/* For each vertex of cube */
				for (i = 0; i < (1<<di); i++) {
					double vv[MXRI];
					int ix = *rp + s->g.hi[i];
					fcb = s->g.a + ix * s->g.pss;

					if (!s->limiten || fcb[-1] <= s->limitv)
						break;
				}
				/* Skip any cubes that a completely over the ink limit */
				if (i >= (1<<di))
					continue;

				/* For each vertex of cube */
				for (i = 0; i < (1<<di); i++) {
					double vv[MXRI];
					int ix = *rp + s->g.hi[i];
					fcb = s->g.a + ix * s->g.pss;

					for (e = 0; e < di; e++)
						vv[e] = fcb[e];
					vix[i] = wrl->add_vertex(wrl, 1, vv);
				}
					
				/* For each vertex of cube */
				for (i = 0; i < (1<<di); i++) {
					int lix[2];
		
					lix[0] = vix[i];
		
					/* for each dimension */ 
					for (j = 0; j < di; j++) {
						if (i & (1<<j))
							continue;		/* Would go outside cube */
		
						lix[1] = vix[i | (1 << j)];
						wrl->add_line(wrl, 1, lix);
					}
				}
			}
		}
	}
	wrl->make_lines_vc(wrl, 0, 0.0);
	wrl->make_lines_vc(wrl, 1, 0.0);

	printf("Created %s\n",wrl->name);
	wrl->del(wrl);
}

/* Plot vertex & triangle check setup & solution */
/* + the primary and shadow bxcells. */ 
static void plot_tri_check(
rspl *s,
int dobxcells,			/* Plot prim & shadow bxcell cells */
int dowait,				/* Wait for the user to hit return */
bxcell *bx,				/* First bx cell (if dobxcells set) */
int vtxix,				/* triangle base vertex index (-1 if not applicable) */
int trii,				/* Triangle eneration */
int triix[3],			/* Triangle indexes */
int nvtxix,				/* test point vertex index number (may be -1 if not vtxrec) */
int sorv,				/* Intersection was solved ? */			
int wsrv,				/* Within simplex ? */
int shdwd,				/* Vertex is shadowed ? */
double v[MXRI+1][MXRO],	/* Triangle vertex values */
double de[MXRO],		/* Line delta */
double pv[MXRO],		/* Vertex being tested */
double xv[MXRO]			/* Intersection point */
) {
	int j;
	int e, f, ee, ff;
	int di = s->di;
	int fdi = s->fdi;
	vrml *wrl;
	bxcell *vbx;
	int first = 1;
	int ii, vix[POW2MXRO], lix[3];
	double vv[MXRO];
	double white[3] = { 1.0, 1.0, 1.0 };
	double grey[3] = { 0.5, 0.5, 0.5 };
	double green[3] = { 0.1, 1.0, 0.1 };
	double red[3] = { 0.8, 0.1, 0.1 };
	double blue[3] = { 0.1, 0.1, 0.8 };
	double yellow[3] = { 0.8, 0.8, 0.1 };

	wrl = new_vrml("tri_check", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);

	/* Gamut center point marker */
	wrl->add_marker(wrl, s->rev.ocent, NULL, 1.0);

	/* point being tested marker */
	wrl->add_marker(wrl, pv, shdwd ? red : blue, 0.5);

	/* Intersection point */
	if (wsrv)
		wrl->add_marker(wrl, xv, blue, 0.2);

	/* Line from center through point being tested */
	lix[0] = wrl->add_vertex(wrl, 0, s->rev.ocent);
	for (ii = 0; ii < fdi; ii++)
		vv[ii] = s->rev.ocent[ii] + 10.0 * de[ii];
	lix[1] = wrl->add_vertex(wrl, 0, vv);
	wrl->add_col_line(wrl, 0, lix, grey);

	/* Triangle */
	lix[0] = wrl->add_vertex(wrl, 1, v[0]);
	lix[1] = wrl->add_vertex(wrl, 1, v[1]);
	lix[2] = wrl->add_vertex(wrl, 1, v[2]);
	wrl->add_col_triangle(wrl, 1, lix, green);
	/* And again to get both faces */
	lix[0] = wrl->add_vertex(wrl, 1, v[0]);
	lix[1] = wrl->add_vertex(wrl, 1, v[2]);
	lix[2] = wrl->add_vertex(wrl, 1, v[1]);
	wrl->add_col_triangle(wrl, 1, lix, green);

	if (dobxcells) {
//printf(" bx = %p\n",bx);
		for (vbx = bx; vbx != NULL; vbx = vbx->wlist) {
			DCOUNT(cc, MXRO, fdi, 0, 0, 2);		/* Vertex counter */
			int *crp, *rp;
		
//printf(" vrml adding bxcell %d\n",vbx->ix);
			/* Plot bxcell's */
			ii = 0;
			DC_INIT(cc);
			while (!DC_DONE(cc)) {
				for (f = 0; f < fdi; f++)
					vv[f] = (vbx->gc[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
//printf("  vrml vtx %d from %s\n",vix[i], debPdv(3,vv));
				vix[ii] = wrl->add_vertex(wrl, 0, vv);
				DC_INC(cc);
				ii++;
			}

			/* For each vertex */
			for (ii = 0; ii < (1 << fdi); ii++) {

				lix[0] = vix[ii];

				/* for each dimension */ 
				for (j = 0; j < fdi; j++) {
					if (ii & (1<<j))
						continue;		/* Would go outside cube */

					lix[1] = vix[ii | (1 << j)];
//printf("  vrml line from vtx %d - %d\n",lix[0],lix[1]);
					wrl->add_col_line(wrl, 0, lix, first ? white : red);
				}
			}
			first = 0;
		}
	}

	wrl->make_lines_vc(wrl, 0, 0.0);
	wrl->make_triangles(wrl, 1, 0.0, NULL);
	printf("Created %s\n",wrl->name);
	wrl->del(wrl);

	printf(" Solved %s, Within triang %s, shadowed %s\n", sorv ? "true" : "false", wsrv ? "true" : "false", shdwd ? "true" : "false");
	printf("Testing against tri %d %d %d\n", triix[0], triix[1], triix[2]);

	printf(" bx %d vtx %d tri %d checking nvx %d, hit return key:\n",bx->ix, vtxix, trii, nvtxix);
	if (dowait) {
		printf(" hit return key to continue:\n");
		getchar();
	}
}

/* Main summary plot at each thinning round and at end. */
/* Show vertex surface & optional added or deleted vertexes, */
/* + optional bxcells. */
#define VV vv		/* Actual surface values */
//#define VV vl		/* Logf mapped surface values */
static void plot_vtx_surface(
rspl *s,
int dovtxlabels,		/* Show vertex index numbers */
int dodeleted,			/* Show deleted vertexes */
int doadded,			/* Show added vertexes */
int dopres,				/* Show preserved vertexes */
int dooil,				/* Show over ink limit vertexes */
int dobxcells,			/* Show bxcells */
int dowait,				/* Wait for a return key */
vtxcache *vc,			/* Vertexes */
assdire *edgdir		/* Edge lookup for vertex */
) {
	vtxrec *vx, *nvx;
	int i, j;
	int f, fdi = s->fdi;
	vrml *wrl;
	double grey[3] = { 0.5, 0.5, 0.5 };
	double red[3] = { 0.8, 0.1, 0.1 };
	double green[3] = { 0.2, 0.8, 0.2 };
	double blue[3] = { 0.2, 0.2, 0.8 };
	double white[3] = { 0.8, 0.8, 0.8 };
	double magenta[3] = { 0.8, 0.2, 0.8 };
	double cyan[3] = { 0.0, 1.0, 1.0 };
	double yellow[3] = { 1.0, 1.0, 0.0 };
	bxcell *vbx;

	if (dopres)
		wrl = new_vrml("last_surface", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);
	else
		wrl = new_vrml("thinned_surface", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);
	wrl->add_marker(wrl, s->rev.ocent, NULL, 1.0);

	if (dovtxlabels) {
		for (i = 0; i < vc->hash_size; i++) {
			for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
				char index[100];

				if (vx->status == vtx_norm
				 || (dodeleted && (vx->status == vtx_sha || vx->status == vtx_del))
				 || (doadded && vx->addvtx)
				 || (dopres && vx->pres)
				 || (dooil && vx->status == vtx_oil)) {
					sprintf(index, "%d",vx->ix);
					wrl->add_text(wrl, index, vx->VV, cyan, 0.3);
				}
			}
		}
	}

	/* Go through the vertex hash to set every vertex value */
	for (i = 0; i < vc->hash_size; i++) {
		for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
			double *col = NULL;

			if (vx->status != vtx_norm && vx->addvtx)
				error ("Found vertex that is both deleted and cause of added bxcell");

			if (doadded && vx->addvtx)				/* Cause of added bxcell */
				col = green;
			else if (dopres && vx->pres)			/* Preserved vertex */
				col = yellow;
			else if (dodeleted && (vx->status == vtx_sha || vx->status == vtx_del))
				col = red;
			else if (dooil && vx->status == vtx_oil)
				col = blue;
			else if (vx->status == vtx_norm)
				col = white;

			if (col != NULL) {
				vx->vrmlix = wrl->add_col_vertex(wrl, 0, vx->VV, col);
			}
		}
	}

	/* Go through them again to get every line they are part of */
	for (i = 0; i < vc->hash_size; i++) {
		for (vx = vc->hash[i]; vx != NULL; vx = vx->hlink) {
			assdire *edg;			/* Edge table */
			float *fp;
			int fl;
			int pline = 0;			/* Plotted at least 1 line */
			int lix[2];

			fp = s->g.a + vx->ix * s->g.pss;	/* This vertex in fwd grid */
			fl = FLV(fp);		/* Edge flags for this vertex */
			edg = edgdir + fl;

			/* For all possible edges that use this vertex */
			for (j = 0; j < edgdir[fl].no; j++) {
				int fix;
				int eix;

				/* Index of first vertex of the line */
				fix = vx->ix + edg->ti[j].goffs[0];

				/* Index number of vertex other than the one we got it from */
				if (edg->ti[j].goffs[0] != 0)
					eix = vx->ix + edg->ti[j].goffs[0];
				else
					eix = vx->ix + edg->ti[j].goffs[1];

				if ((nvx = get_vtxrec(vc, eix)) != NULL) {
					if ( (vx->status == vtx_norm
					  || (dodeleted && (vx->status == vtx_sha || vx->status == vtx_del))
					  || (doadded && vx->addvtx)
					  || (dopres && vx->pres)
					  || (dooil && vx->status == vtx_oil))
					 &&  (nvx->status == vtx_norm
					  || (dodeleted && (nvx->status == vtx_sha || nvx->status == vtx_del))
					  || (doadded && nvx->addvtx)
					  || (dopres && nvx->pres)
					  || (dooil && nvx->status == vtx_oil))) {

						pline = 1; /* Will/would plot this */

						/* Only plot the line once though */
						if (fix == vx->ix) {
							lix[0] = vx->vrmlix; 
							lix[1] = nvx->vrmlix;
							wrl->add_line(wrl, 0, lix);
						}
					}
				}
			}

			/* we have an orphan vertex */
			if (pline == 0
			 && (dodeleted || vx->status == vtx_norm)
			 && (doadded || !vx->addvtx)) {
				double vv[MXRO], off = 0.15, *col;

				if (doadded && vx->addvtx)				/* Cause of added bxcell */
					col = green;
				else if (dopres && vx->pres)			/* Preserved vertex */
					col = yellow;
				else if (dodeleted && vx->status != vtx_norm)
					col = red;
				else if (dooil && vx->status == vtx_oil)
					col = blue;
				else if (vx->status == vtx_norm)
					col = white;

				for (f = 0; f < fdi; f++)
					vv[f] = vx->VV[f] + off;
				lix[0] = wrl->add_vertex(wrl, 2, vv);
				for (f = 0; f < fdi; f++)
					vv[f] = vx->VV[f] - off;
				lix[1] = wrl->add_vertex(wrl, 2, vv);
				wrl->add_col_line(wrl, 2, lix, col);

				for (f = 0; f < fdi; f++)
					vv[f] = vx->VV[f] + ((f & 1) ? off : -off);
				lix[0] = wrl->add_vertex(wrl, 2, vv);
				for (f = 0; f < fdi; f++)
					vv[f] = vx->VV[f] - ((f & 1) ? off : -off);
				lix[1] = wrl->add_vertex(wrl, 2, vv);
				wrl->add_col_line(wrl, 2, lix, col);

				for (f = 0; f < fdi; f++)
					vv[f] = vx->VV[f] + ((f & 2) ? off : -off);
				lix[0] = wrl->add_vertex(wrl, 2, vv);
				for (f = 0; f < fdi; f++)
					vv[f] = vx->VV[f] - ((f & 2) ? off : -off);
				lix[1] = wrl->add_vertex(wrl, 2, vv);
				wrl->add_col_line(wrl, 2, lix, col);
			}
		}
	}
	wrl->make_lines_vc(wrl, 0, 0.0);
	wrl->make_lines_vc(wrl, 2, 0.0);

	/* Plot surface cells */
	if (dobxcells) {
		for (vbx = s->rev.surflist; vbx != NULL; vbx = vbx->slist) {
			DCOUNT(cc, MXRO, fdi, 0, 0, 2);		/* Vertex counter */
			int *crp, *rp, ii;
			double vv[MXRO];
			int vix[POW2MXRO], lix[2];
		
			ii = 0;
			DC_INIT(cc);
			while (!DC_DONE(cc)) {
				for (f = 0; f < fdi; f++) {
					vv[f] = (vbx->gc[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
					vv[f] += d_rand(-0.05, 0.05);
				}
				vix[ii] = wrl->add_vertex(wrl, 1, vv);
				DC_INC(cc);
				ii++;
			}

			for (ii = 0; ii < (1 << fdi); ii++) {

				lix[0] = vix[ii];

				/* for each dimension */ 
				for (j = 0; j < fdi; j++) {
					if (ii & (1<<j))
						continue;		/* Would go outside cube */

					lix[1] = vix[ii | (1 << j)];
					if (vbx->debug) {		/* Added bxcell */
						wrl->add_col_line(wrl, 1, lix, magenta);
					} else {				/* Existing bxcell */
						wrl->add_col_line(wrl, 1, lix, grey);
					}
				}
			}
		}
		wrl->make_lines_vc(wrl, 1, 0.0);
	}

	printf("Created %s\n",wrl->name);
	wrl->del(wrl);
	if (dowait) {
		printf(" Thinned vertexes surface: Hit return to continue\n");
		getchar();
	}
}

/* Plot bxcells touched by added cell */
static void plot_touched_bxcells(
rspl *s,
int bxix			/* Index of bx cell causing touches */
) {
	int j, f, fdi = s->fdi;
	vrml *wrl;
	bxcell *vbx;
	int first = 1;
	int ii, vix[POW2MXRO], lix[3];
	double vv[MXRO];
	double green[3] = { 0.1, 0.6, 0.1 };
	double white[3] = { 1.0, 1.0, 1.0 };
	double red[3] = { 0.8, 0.1, 0.1 };

	wrl = new_vrml("add_touch_bxcells", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);

	/* Gamut center point marker */
	wrl->add_marker(wrl, s->rev.ocent, NULL, 1.0);

	for (vbx = s->rev.surflist; vbx != NULL; vbx = vbx->slist) {
		DCOUNT(cc, MXRO, fdi, 0, 0, 2);		/* Vertex counter */
		int *crp, *rp;
	
		/* Plot bxcell's */
		ii = 0;
		DC_INIT(cc);
		while (!DC_DONE(cc)) {
			for (f = 0; f < fdi; f++) {
				vv[f] = (vbx->gc[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
				if (vbx->debug == 2)
					vv[f] += 0.05;
				else if (vbx->debug == 1)
				vv[f] -= 0.05;
			}
			vix[ii] = wrl->add_vertex(wrl, 0, vv);
			DC_INC(cc);
			ii++;
		}

		/* For each vertex */
		for (ii = 0; ii < (1 << fdi); ii++) {

			lix[0] = vix[ii];

			/* for each dimension */ 
			for (j = 0; j < fdi; j++) {
				if (ii & (1<<j))
					continue;		/* Would go outside cube */

				lix[1] = vix[ii | (1 << j)];
				wrl->add_col_line(wrl, 0, lix,
				           vbx->debug == 2 ? white :  vbx->debug == 1 ? red : green);
			}
		}
	}

	wrl->make_lines_vc(wrl, 0, 0.0);
	printf("Created %s\n",wrl->name);
	wrl->del(wrl);

	printf(" Touched bx cells for bx %d: Hit return to continue\n",bxix);
	getchar();
}

/* Plot the thinned surface fwd cells */
static void plot_fxcell_surface(
rspl *s,
int dofclabels,			/* Show fwd cell base indexes */
int dobxcells,			/* Show bxcells */
int dowait				/* Wait for a return key */
) {
	bxcell *bx;
	int i, j;
	int e, di = s->di;
	int f, fdi = s->fdi;
	vrml *wrl;
	double grey[3] = { 0.5, 0.5, 0.5 };
	double white[3] = { 1.0, 1.0, 1.0 };

	wrl = new_vrml("thinned_fwcells", 0, s->rev.probxyz ? vrml_xyz : vrml_lab);
	wrl->add_marker(wrl, s->rev.ocent, NULL, 1.0);

	if (dofclabels) {
		/* Put text for every base cube index */
		for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
			int vix[POW2MXRI];
			int *crp, *rp;

			crp = bx->sl;

			for (rp = crp+3; *rp != -1; rp++) {
				int ix = *rp;
				char index[100];
				double vv[MXRI];
				int off = 0;		// 0 .. 7, choose cube vertex
				float *fcb = s->g.a + (ix + s->g.hi[off]) * s->g.pss;

				for (e = 0; e < di; e++)
					vv[e] = fcb[e];
				sprintf(index, "%d",ix + s->g.hi[off]);
				wrl->add_text(wrl, index, vv, white, 0.3);
			}
		}
	}

	for (bx = s->rev.surflist; bx != NULL; bx = bx->slist) {
		DCOUNT(cc, MXRO, fdi, 0, 0, 2);		/* Vertex counter */
		int vix[POW2MXRI];
		int *crp, *rp;

		if (dobxcells) {
			/* Plot bxcell's */
			i = 0;
			DC_INIT(cc);
			while (!DC_DONE(cc)) {
				double vv[MXRO];
				for (f = 0; f < fdi; f++)
					vv[f] = (bx->gc[f] + cc[f]) * s->rev.gw[f] + s->rev.gl[f];  
				vix[i] = wrl->add_vertex(wrl, 1, vv);
				DC_INC(cc);
				i++;
			}

			/* For each vertex */
			for (i = 0; i < (1 << fdi); i++) {
				int lix[2];

				lix[0] = vix[i];

				/* for each dimension */ 
				for (j = 0; j < fdi; j++) {
					if (i & (1<<j))
						continue;		/* Would go outside cube */

					lix[1] = vix[i | (1 << j)];
					wrl->add_col_line(wrl, 1, lix, white);
				}
			}
		}

		crp = bx->sl;

		for (rp = crp+3; *rp != -1; rp++) {
			float *fcb = s->g.a + *rp * s->g.pss;

			/* Skip grid base points on the upper edge of the grid */
			for (e = 0; e < di; e++) {
				if (G_FL(fcb, e) == 0)		/* At the top edge */
					break;
			}
			if (e < di) {
				printf("Fwd cell base index %d is on upper edge!\n",*rp);
				continue;
			}

			/* For each vertex of cube */
			for (i = 0; i < (1<<di); i++) {
				double vv[MXRI];
				int ix = *rp + s->g.hi[i];
				float *fcb = s->g.a + ix * s->g.pss;

				if (!s->limiten || fcb[-1] <= s->limitv)
					break;
			}
			/* Skip any cubes that a completely over the ink limit */
			if (i >= (1<<di))
				continue;

			/* For each vertex of cube */
			for (i = 0; i < (1<<di); i++) {
				double vv[MXRI];
				int ix = *rp + s->g.hi[i];
				float *fcb = s->g.a + ix * s->g.pss;

				for (e = 0; e < di; e++)
					vv[e] = fcb[e];
				vix[i] = wrl->add_vertex(wrl, 0, vv);
			}
				
			/* For each vertex of cube */
			for (i = 0; i < (1<<di); i++) {
				int lix[2];

				lix[0] = vix[i];

				/* for each dimension */ 
				for (j = 0; j < di; j++) {
					if (i & (1<<j))
						continue;		/* Would go outside cube */

					lix[1] = vix[i | (1<<j)];
					wrl->add_line(wrl, 0, lix);
				}
			}
		}
	}
	if (dobxcells)
		wrl->make_lines_vc(wrl, 1, 0.0);
	wrl->make_lines_vc(wrl, 0, 0.0);
	printf("Created %s\n",wrl->name);
	wrl->del(wrl);

	if (dowait) {
		printf(" Thinned fwd cell surface: Hit return to continue\n");
		getchar();
	}
}

/* ====================================================== */
#endif /* REVVRML */



