
/* 
 * Argyll Color Management System
 * Multi-dimensional regularized spline data structure
 *
 * Spline forward interpolation support.
 *
 * Author: Graeme W. Gill
 * Date:   12/10/98
 *
 * Copyright 1998, 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.
 */

/* TTBD:
	Get rid of error() calls - return status instead
 */

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

#include "rspl_imp.h"
#include "numlib.h"

int spline_interp_rspl(rspl *ss, co *cp);

#undef DEBUG

#undef NEVER
#define ALWAYS

/* 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
 */

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

/* Init spline elements in rspl */
void
init_spline(rspl *s) {
	s->spline.nm = 0;
	s->spline.spline = 0;
	s->spline.magic = NULL;

	s->spline_interp = spline_interp_rspl;
}

/* Free up the spline interpolation info */
void free_spline(
rspl *s		/* Pointer to rspl grid */
) {
	if (s->spline.magic != NULL) {
		free(s->spline.magic);
	}
	s->spline.nm = 0;
	s->spline.spline = 0;
}

/* ====================================================== */
/* Setup functions first: */

/* Hermite spline, magic matrix */
/* Indexes are: param powers 0, 1, 2, 3; Offset from base vertex 0,1; Dimension mask 0,1 */
static double hmagic[4][2][2] = {
	{ { 1.0,  0.0}, { 0.0,  0.0} },
	{ { 0.0,  1.0}, { 0.0,  0.0} },
	{ {-3.0, -2.0}, { 3.0, -1.0} },
	{ { 2.0,  1.0}, {-2.0,  1.0} }
};

/* Allocate and initialize tangency information for each grid point */
static void make_tang(
rspl *s		/* Pointer to rspl grid */
) {
	int i,p,j;
	int di  = s->di;
	int fdi = s->fdi;
	int nig = s->g.no;
	float *tp;		/* Pointer to tangent values */
	int nim, mix;	/* Number in magic, magic index */
	float *tang_alloc, *tang;	/* Tangency info */
	float *gt;		/* Working grid point */
	
//printf("~~make_tang called\n");
	/* Organized as: tang[[grid]][di combs.][fdi] */
	/* Allocate space for tangency info */
	if ((tang_alloc = (float *) malloc(sizeof(float) * nig * (((1 << di) * fdi)+G_XTRA))) == NULL)
		error("rspl malloc failed - tangecy points");
	tang = tang_alloc + G_XTRA;	/* Offset for flags and non-mono error */

	/* For all grid points */
	for (tp = tang, gt = s->g.a, i = 0; i < nig; i++, gt += s->g.pss, tp += G_XTRA) {
		int ee;
/* printf("\n~~ grid point %d\n",i); */

		/* Look at surrounding grid points in combinations of +- 1 all dimensions */
		for (ee = 0; ee < (1 << di); ee++) {
			double av[MXRO];	/* average */
			int nia = 0;		/* Number in average */
			int f, ec;

/* printf("Dim combo %d\n",ee); */
			/* special case - base value */
			if (ee == 0) { 
				*((int *)(tp-2)) = *((int *)(gt-2));	/* Copy flags */
				tp[-1] = gt[-1];						/* Copy ink limit function value */
				for (f = 0; f < fdi; f++) {
					*tp++ = gt[f];
/* printf("Tang value out %d = %f\n",f,tp[-1]); */
				}
				continue;
			}
			for (f = 0; f < fdi; f++)
				av[f] = 0.0;	/* Init average */
			/* For all surroundin grid points in this combination */
			for (ec = 0; ec < (1 << di); ec++) {
				int xo, io, sgn, e, ex;
/* printf("~~checking out surrounding combo %d\n",ec); */
				if (ec & ~ee) {
/* printf("~~being skipped\n"); */
					continue;	/* Skip invalid combo */
				}
				xo = io = 0;		/* Grid float offset */
				sgn = 1;			/* Sign */
				ex = 0;				/* Flag - No extrapolation */
				for (e = 0; e < di; e++) {	/* For each dimension */
/* printf("~~checking  dimension %d\n",e); */
					if (!(ee & (1 << e))) {
/* printf("~~dimension not active\n"); */
						continue;	/* Dimension is not active */
					}
					if (ec & (1 << e)) {
						/* If + dimension is valid */
						if (((G_FL(gt,e) & 3) > 0) || (G_FL(gt,e) & 0x4)) {
							int to = s->g.fci[e];	/* +1 in dimension */
							io += to;				/* real/pivot point */
							xo += to;				/* reflected point */
						} else {
							ex = 1;					/* Use extrapolation */
							xo -= s->g.fci[e];		/* -1 in dimension */
						}
					} else {
						sgn = -sgn;			/* Reverse sign */
						/* If - dimension is valid */
						if (((G_FL(gt,e) & 3) > 0) || !(G_FL(gt,e) & 0x4)) {
							int to = -s->g.fci[e];	/* -1 in dimension */
							io += to;				/* real/pivot point */
							xo += to;				/* reflected point */
						} else {
							ex = 1;					/* Use extrapolation */
							xo += s->g.fci[e];		/* +1 in dimension */
						}
					}
				}
				/* Add surrounding grid points value into the average */
				if (!ex) {
					for (f = 0; f < fdi; f++) 
						av[f] += (double)sgn * gt[io + f];
				} else {	/* Extrapolate point beyond edge */
							/* Use an extrapolation that tries to maintain curvature */
					for (f = 0; f < fdi; f++)  {
						double v0,v1,v2;
						v0 = gt[io + f];			/* Pivot point */
						v1 = gt[xo + f];			/* Reflection of target in pivot */
						v2 = gt[2 * xo - io + f];	/* Reflection +2 */
						av[f] += (double)sgn * (3.0 * (v0 - v1) + v2);
					}
				}
				nia++;
			}
			for (f = 0; f < fdi; f++) {
				*tp++ = (float)(av[f]/(double)nia);
/* printf("Tang value out %d = %f, average of %d\n",f,tp[-1],nia); */
			}
		}	/* Next dimension combination */
	}	/* Next grid point */

	/* Create a full sized hermite magic matrix */
	/* Organized as: magic[4^di][2^di][2^di] */
	/* = [param power combos][cube vertex index][di combos], */
	/* but then only store non-zero weight values. */
	for (i = 0, nim = 1; i < di; nim *= 10, i++);	/* Number of entries needed */
	if (s->spline.magic == NULL) { /* Allocate space for magic matrix info */
		if ((s->spline.magic = (magic_data *) malloc(sizeof(magic_data) * nim)) == NULL)
			error("rspl malloc failed - hermite magic matrix data");
	}

	mix = 0;
	for (p = 0; p < (1 << (2 * di)); p++) {		/* For all combinations of parameter powers */
		for (i = 0; i < (1 << di); i++)	{		/* For all corners of cube */
			for (j = 0; j < (1 << di); j++)	{	/* For all dimension combinations */
				int ii;
				double wgt = 1.0;
				for (ii = 0; ii < di; ii++) {
					wgt *= hmagic[3&(p>>(2*ii))][1&(i>>ii)][1&(j>>ii)];
				}
				if (wgt != 0.0) {	/* record non-zero weight value */
					s->spline.magic[mix].p = p;
					s->spline.magic[mix].i = i;
					s->spline.magic[mix].j = fdi * j;	/* Pre-scale */
					s->spline.magic[mix].wgt = (float)wgt;
					mix++;
				}
			}
		}
	}
	/* mix should == nim! */
	s->spline.nm = nim;

	/* Free basic grid info, and substitute tangency enhanced version */
	/* ~~~~!! need to free any other structures in rspl that depend on */
	/* ~~~~!! g.pss size, ie. rev stuff ??? */
	if (s->g.alloc != NULL)
		free((void *)s->g.alloc);

	s->g.alloc  = tang_alloc;
	s->g.a      = tang;

	/* Adjust index tables */
	s->g.pss = (1 << di) * fdi + G_XTRA;
	for (i = 0; i < di; i++)
		s->g.fci[i] = s->g.ci[i] * s->g.pss;	/* In floats */
	for (i = 0; i < (1 << di); i++)
		s->g.fhi[i] = s->g.hi[i] * s->g.pss;	/* In floats */

	s->spline.spline = 1;

//printf("~~make_tang finished\n");
}

/* Do a Hermite spline smooth interpolation based on the finest grid */
/* (To do this more accurately, the data point interpolation within */
/*  the grid itteration should be of the same order. This increases */
/*  itteration complexity quite a bit, so we won't bother for the moment.) */
/* This code is not optimised for speed. */
/* Return 0 if OK, 1 if input was clipped to grid */
int spline_interp_rspl(
rspl *s,
co *cp			/* Input value and returned function value */
) {
	int e,f,p,i;
	int di  = s->di;
	int fdi = s->fdi;
	double ppw[MXRI][4];	/* Parameter powers of 0, 1, 2, 3 */
	float  *ga[POW2MXRI];	/* Pointers to grid cubes data in tang[] */
	magic_data *tp;			/* Pointer to items in magic matrix */
	int rv = 0;
	
/* printf("~~smooth interp called\n"); */

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

	if (s->spline.spline == 0) 	/* Compute tangent info if it doesn't exist */
		make_tang(s);

	/* Locate grid base point, and position with base cube */
	ga[0] = s->g.a;	/* Base pointer of cube */
	for (e = 0; e < di; e++) {
		double t, pe;
		int mi, gres_1 = s->g.res[e]-1;

		pe = cp->p[e];
		if (pe < s->g.l[e]) {		/* Clip to grid */
			pe = s->g.l[e];
			rv = 1;
		}
		if (pe > s->g.h[e]) {
			pe = s->g.h[e];
			rv = 1;
		}
		t = (pe - s->g.l[e])/s->g.w[e];
		mi = (int)floor(t);			/* Grid coordinate */
		if (mi < 0)					/* Limit to valid cube base index range */
			mi = 0;
		else if (mi >= gres_1)
			mi = gres_1-1;
		ga[0] += s->g.fci[e] * mi;	/* Add offset in dimen */
		t = t - (double)mi;;		/* sub-cube offset = parameter in dimension e */
		ppw[e][0] = 1.0;			/* Powers of parameter */
		ppw[e][1] = t;
		ppw[e][2] = t * t;
		ppw[e][3] = t * t * t; 
	}

	/* Compute indexes into cube corners in tangent array */
	for (i = 1; i < (1 << di); i++)
		ga[i] = ga[0] + s->g.fhi[i];

	/* Now compute the output values */
	for (f = 0; f < fdi; f++)		/* Zero output value sums */
		cp->v[f] = 0.0;
	
	/* For all non-zero combinations of parameter powers */
	{
		double ppc = -1000.0;			/* Parameter power combination */
		for (tp = s->spline.magic, p = -1; tp < &s->spline.magic[s->spline.nm]; tp++) {
			double wgt;			/* Magic matrix weight */
			float *gp;			/* Pointer to vertex data */

			if (p != tp->p) {		/* Param power needs re-calculating */
				int pp;
				p = tp->p;
				for (ppc = 1.0, pp = 0; pp < di; pp++)
					ppc *= ppw[pp][3&(p>>(2*pp))];		/* comb. of param powers value */
			}

			wgt = tp->wgt * ppc;		/* matrix times parameter */
			gp = ga[tp->i] + tp->j;		/* Point to base of vertex data */
			for (f = 0; f < fdi; f++) 	/* For all output values */
				cp->v[f] += wgt * gp[f];
		}
	}
/* printf("~~smooth interp finished\n"); */
	return rv;
}



















