/* Copyright (c) 1992 Regents of the University of California */

#ifndef lint
static char SCCSid[] = "@(#)func.c 2.7 9/26/92 LBL";
#endif

/*
 *  func.c - interface to calcomp functions.
 *
 *     4/7/86
 */

#include  "ray.h"

#include  "otypes.h"

#include  "func.h"


#define  INITFILE	"rayinit.cal"
#define  REFVNAME	"`FILE_REFCNT"
#define  CALSUF		".cal"
#define  LCALSUF	4

XF  unitxf = {			/* identity transform */
	1.0, 0.0, 0.0, 0.0,
	0.0, 1.0, 0.0, 0.0,
	0.0, 0.0, 1.0, 0.0,
	0.0, 0.0, 0.0, 1.0,
	1.0
};

XF  funcxf;			/* current transformation */
static OBJREC  *fobj = NULL;	/* current function object */
static RAY  *fray = NULL;	/* current function ray */

static double  l_erf(), l_erfc(), l_arg();


MFUNC *
getfunc(m, ff, ef, dofwd)	/* get function for this modifier */
OBJREC  *m;
int  ff;
unsigned  ef;
int  dofwd;
{
	extern EPNODE  *curfunc;
	static char  initfile[] = INITFILE;
	char  sbuf[MAXSTR];
	register char  **arg;
	register MFUNC  *f;
	int  ne, na;
	register int  i;
					/* check to see if done already */
	if ((f = (MFUNC *)m->os) != NULL)
		return(f);
	fobj = NULL; fray = NULL;
	if (initfile[0]) {		/* initialize on first call */
		setcontext("");
		scompile("Dx=$1;Dy=$2;Dz=$3;", NULL, 0);
		scompile("Nx=$4;Ny=$5;Nz=$6;", NULL, 0);
		scompile("Px=$7;Py=$8;Pz=$9;", NULL, 0);
		scompile("T=$10;Ts=$25;Rdot=$11;", NULL, 0);
		scompile("S=$12;Tx=$13;Ty=$14;Tz=$15;", NULL, 0);
		scompile("Ix=$16;Iy=$17;Iz=$18;", NULL, 0);
		scompile("Jx=$19;Jy=$20;Jz=$21;", NULL, 0);
		scompile("Kx=$22;Ky=$23;Kz=$24;", NULL, 0);
		funset("arg", 1, '=', l_arg);
		funset("erf", 1, ':', l_erf);
		funset("erfc", 1, ':', l_erfc);
		setnoisefuncs();
		loadfunc(initfile);
		initfile[0] = '\0';
	}
	if ((na = m->oargs.nsargs) <= ff)
		goto toofew;
	arg = m->oargs.sarg;
	if ((f = (MFUNC *)calloc(1, sizeof(MFUNC))) == NULL)
		goto memerr;
	i = strlen(arg[ff]);			/* set up context */
	if (i == 1 && arg[ff][0] == '.')
		setcontext(f->ctx = "");	/* "." means no file */
	else {
		strcpy(sbuf,arg[ff]);	/* file name is context */
		if (i > LCALSUF && !strcmp(sbuf+i-LCALSUF, CALSUF))
			sbuf[i-LCALSUF] = '\0';	/* remove suffix */
		setcontext(f->ctx = savestr(sbuf));
		if (!vardefined(REFVNAME)) {	/* file loaded? */
			loadfunc(arg[ff]);
			varset(REFVNAME, '=', 1.0);
		} else				/* reference_count++ */
			varset(REFVNAME, '=', varvalue(REFVNAME)+1.0);
	}
	curfunc = NULL;			/* parse expressions */
	sprintf(sbuf, "%s \"%s\"", ofun[m->otype].funame, m->oname);
	for (i=0, ne=0; ef && i < na; i++, ef>>=1)
		if (ef & 1) {			/* flagged as an expression? */
			if (ne >= MAXEXPR)
				objerror(m, INTERNAL, "too many expressions");
			initstr(arg[i], sbuf, 0);
			f->ep[ne++] = getE1();
			if (nextc != EOF)
				syntax("unexpected character");
		}
	if (ef)
		goto toofew;
	if (i <= ff)			/* find transform args */
		i = ff+1;
	while (i < na && arg[i][0] != '-')
		i++;
	if (i == na)			/* no transform */
		f->f = f->b = &unitxf;
	else {				/* get transform */
		if ((f->b = (XF *)malloc(sizeof(XF))) == NULL)
			goto memerr;
		if (invxf(f->b, na-i, arg+i) != na-i)
			objerror(m, USER, "bad transform");
		if (f->b->sca < 0.0)
			f->b->sca = -f->b->sca;
		if (dofwd) {			/* do both transforms */
			if ((f->f = (XF *)malloc(sizeof(XF))) == NULL)
				goto memerr;
			xf(f->f, na-i, arg+i);
			if (f->f->sca < 0.0)
				f->f->sca = -f->f->sca;
		}
	}
	m->os = (char *)f;
	return(f);
toofew:
	objerror(m, USER, "too few string arguments");
memerr:
	error(SYSTEM, "out of memory in getfunc");
}


freefunc(m)			/* free memory associated with modifier */
OBJREC  *m;
{
	register MFUNC  *f;
	register int  i;

	if ((f = (MFUNC *)m->os) == NULL)
		return;
	for (i = 0; f->ep[i] != NULL; i++)
		epfree(f->ep[i]);
	if (f->ctx[0]) {			/* done with definitions */
		setcontext(f->ctx);
		i = varvalue(REFVNAME)-.5;	/* reference_count-- */
		if (i > 0)
			varset(REFVNAME, '=', (double)i);
		else
			dcleanup(2);		/* remove definitions */
		freestr(f->ctx);
	}
	if (f->b != &unitxf)
		free((char *)f->b);
	if (f->f != NULL && f->f != &unitxf)
		free((char *)f->f);
	free((char *)f);
	m->os = NULL;
}


setfunc(m, r)			/* set channels for function call */
OBJREC  *m;
register RAY  *r;
{
	static long  lastrno = -1;
	register MFUNC  *f;
					/* get function */
	if ((f = (MFUNC *)m->os) == NULL)
		error(m, CONSISTENCY, "setfunc called before getfunc");
					/* set evaluator context */
	setcontext(f->ctx);
					/* check to see if matrix set */
	if (m == fobj && r->rno == lastrno)
		return(0);
	fobj = m;
	fray = r;
	if (r->rox != NULL)
		if (f->b != &unitxf) {
			funcxf.sca = r->rox->b.sca * f->b->sca;
			multmat4(funcxf.xfm, r->rox->b.xfm, f->b->xfm);
		} else
			copystruct(&funcxf, &r->rox->b);
	else
		copystruct(&funcxf, f->b);
	lastrno = r->rno;
	eclock++;		/* notify expression evaluator */
	return(1);
}


loadfunc(fname)			/* load definition file */
char  *fname;
{
	extern char  *libpath;		/* library search path */
	char  *ffname;

	if ((ffname = getpath(fname, libpath, R_OK)) == NULL) {
		sprintf(errmsg, "cannot find function file \"%s\"", fname);
		error(USER, errmsg);
	}
	fcompile(ffname);
}


static double
l_arg()				/* return nth real argument */
{
	extern double  argument();
	register int  n;

	if (fobj == NULL)
		syntax("arg(n) used in constant expression");

	n = argument(1) + .5;		/* round to integer */

	if (n < 1)
		return(fobj->oargs.nfargs);

	if (n > fobj->oargs.nfargs) {
		sprintf(errmsg, "missing real argument %d", n);
		objerror(fobj, USER, errmsg);
	}
	return(fobj->oargs.farg[n-1]);
}


static double
l_erf()				/* error function */
{
	extern double  erf();

	return(erf(argument(1)));
}


static double
l_erfc()			/* cumulative error function */
{
	extern double  erfc();

	return(erfc(argument(1)));
}


double
chanvalue(n)			/* return channel n to calcomp */
register int  n;
{
	double  sum;
	register RAY  *r;

	if (fray == NULL)
		syntax("ray parameter used in constant expression");

	if (--n < 0)
		goto badchan;

	if (n < 3)			/* ray direction */

		return( (	fray->rdir[0]*funcxf.xfm[0][n] +
				fray->rdir[1]*funcxf.xfm[1][n] +
				fray->rdir[2]*funcxf.xfm[2][n]	)
			 / funcxf.sca );

	if (n < 6)			/* surface normal */

		return( (	fray->ron[0]*funcxf.xfm[0][n-3] +
				fray->ron[1]*funcxf.xfm[1][n-3] +
				fray->ron[2]*funcxf.xfm[2][n-3]	)
			 / funcxf.sca );

	if (n < 9)			/* intersection */

		return( fray->rop[0]*funcxf.xfm[0][n-6] +
				fray->rop[1]*funcxf.xfm[1][n-6] +
				fray->rop[2]*funcxf.xfm[2][n-6] +
					     funcxf.xfm[3][n-6] );

	if (n == 9) {			/* total distance */
		sum = fray->rot;
		for (r = fray->parent; r != NULL; r = r->parent)
			sum += r->rot;
		return(sum * funcxf.sca);

	}

	if (n == 10)			/* dot product (range [-1,1]) */
		return(	fray->rod <= -1.0 ? -1.0 :
			fray->rod >= 1.0 ? 1.0 :
			fray->rod );

	if (n == 11)			/* scale */
		return(funcxf.sca);

	if (n < 15)			/* origin */
		return(funcxf.xfm[3][n-12]);

	if (n < 18)			/* i unit vector */
		return(funcxf.xfm[0][n-15] / funcxf.sca);

	if (n < 21)			/* j unit vector */
		return(funcxf.xfm[1][n-15] / funcxf.sca);

	if (n < 24)			/* k unit vector */
		return(funcxf.xfm[2][n-21] / funcxf.sca);

	if (n == 24) {			/* single ray (shadow) distance */
		sum = fray->rot;
		for (r = fray->parent; r != NULL && r->crtype&SHADOW;
				r = r->parent)
			sum += r->rot;
		return(sum * funcxf.sca);
	}
badchan:
	error(USER, "illegal channel number");
}
