#ifndef lint
static const char	RCSid[] = "$Id: rtrace.c,v 2.37 2004/03/30 16:13:01 schorsch Exp $";
#endif
/*
 *  rtrace.c - program and variables for individual ray tracing.
 */

#include "copyright.h"

/*
 *  Input is in the form:
 *
 *	xorg	yorg	zorg	xdir	ydir	zdir
 *
 *  The direction need not be normalized.  Output is flexible.
 *  If the direction vector is (0,0,0), then the output is flushed.
 *  All values default to ascii representation of real
 *  numbers.  Binary representations can be selected
 *  with '-ff' for float or '-fd' for double.  By default,
 *  radiance is computed.  The '-i' or '-I' options indicate that
 *  irradiance values are desired.
 */

#include  <time.h>

#include  "platform.h"
#include  "ray.h"
#include  "ambient.h"
#include  "source.h"
#include  "otypes.h"
#include  "resolu.h"

CUBE  thescene;				/* our scene */
OBJECT	nsceneobjs;			/* number of objects in our scene */

int  dimlist[MAXDIM];			/* sampling dimensions */
int  ndims = 0;				/* number of sampling dimensions */
int  samplendx = 0;			/* index for this sample */

int  imm_irrad = 0;			/* compute immediate irradiance? */
int  lim_dist = 0;			/* limit distance? */

int  inform = 'a';			/* input format */
int  outform = 'a';			/* output format */
char  *outvals = "v";			/* output specification */

int  do_irrad = 0;			/* compute irradiance? */

void  (*trace)() = NULL;		/* trace call */

char  *tralist[128];			/* list of modifers to trace (or no) */
int  traincl = -1;			/* include == 1, exclude == 0 */
#define	 MAXTSET	511		/* maximum number in trace set */
OBJECT	traset[MAXTSET+1]={0};		/* trace include/exclude set */

int  hresolu = 0;			/* horizontal (scan) size */
int  vresolu = 0;			/* vertical resolution */

double  dstrsrc = 0.0;			/* square source distribution */
double  shadthresh = .03;		/* shadow threshold */
double  shadcert = .75;			/* shadow certainty */
int  directrelay = 2;			/* number of source relays */
int  vspretest = 512;			/* virtual source pretest density */
int  directvis = 1;			/* sources visible? */
double  srcsizerat = .2;		/* maximum ratio source size/dist. */

COLOR  cextinction = BLKCOLOR;		/* global extinction coefficient */
COLOR  salbedo = BLKCOLOR;		/* global scattering albedo */
double  seccg = 0.;			/* global scattering eccentricity */
double  ssampdist = 0.;			/* scatter sampling distance */

double  specthresh = .15;		/* specular sampling threshold */
double  specjitter = 1.;		/* specular sampling jitter */

int  backvis = 1;			/* back face visibility */

int  maxdepth = 8;			/* maximum recursion depth */
double  minweight = 2e-3;		/* minimum ray weight */

char  *ambfile = NULL;			/* ambient file name */
COLOR  ambval = BLKCOLOR;		/* ambient value */
int  ambvwt = 0;			/* initial weight for ambient value */
double  ambacc = 0.15;			/* ambient accuracy */
int  ambres = 256;			/* ambient resolution */
int  ambdiv = 1024;			/* ambient divisions */
int  ambssamp = 512;			/* ambient super-samples */
int  ambounce = 0;			/* ambient bounces */
char  *amblist[128];			/* ambient include/exclude list */
int  ambincl = -1;			/* include == 1, exclude == 0 */

static int  castonly = 0;

static RAY  thisray;			/* for our convenience */

typedef void putf_t(double v);
static putf_t puta, putd, putf;

typedef void oputf_t(RAY *r);
static oputf_t  oputo, oputd, oputv, oputl, oputL, oputc,
		oputp, oputn, oputN, oputs, oputw, oputm;

static void setoutput(char *vs);
static void tranotify(OBJECT obj);
static void bogusray(void);
static void rad(FVECT  org, FVECT  dir, double	dmax);
static void irrad(FVECT  org, FVECT  dir);
static void printvals(RAY  *r);
static int getvec(FVECT  vec, int  fmt, FILE  *fp);
static void tabin(RAY  *r);
static void ourtrace(RAY  *r);

static oputf_t *ray_out[16], *every_out[16];
static putf_t *putreal;

void  (*addobjnotify[])() = {ambnotify, tranotify, NULL};


void
quit(			/* quit program */
	int  code
)
{
#ifndef  NON_POSIX /* XXX we don't clean up elsewhere? */
	headclean();		/* delete header file */
	pfclean();		/* clean up persist files */
#endif
	exit(code);
}


extern char *
formstr(				/* return format identifier */
	int  f
)
{
	switch (f) {
	case 'a': return("ascii");
	case 'f': return("float");
	case 'd': return("double");
	case 'c': return(COLRFMT);
	}
	return("unknown");
}


extern void
rtrace(				/* trace rays from file */
	char  *fname
)
{
	long  vcount = hresolu>1 ? hresolu*vresolu : vresolu;
	long  nextflush = hresolu;
	FILE  *fp;
	double	d;
	FVECT  orig, direc;
					/* set up input */
	if (fname == NULL)
		fp = stdin;
	else if ((fp = fopen(fname, "r")) == NULL) {
		sprintf(errmsg, "cannot open input file \"%s\"", fname);
		error(SYSTEM, errmsg);
	}
#ifdef _WIN32
	if (inform != 'a')
		SET_FILE_BINARY(fp);
#endif
					/* set up output */
	setoutput(outvals);
	switch (outform) {
	case 'a': putreal = puta; break;
	case 'f': putreal = putf; break;
	case 'd': putreal = putd; break;
	case 'c': 
		if (strcmp(outvals, "v"))
			error(USER, "color format with value output only");
		break;
	default:
		error(CONSISTENCY, "botched output format");
	}
	if (hresolu > 0) {
		if (vresolu > 0)
			fprtresolu(hresolu, vresolu, stdout);
		fflush(stdout);
	}
					/* process file */
	while (getvec(orig, inform, fp) == 0 &&
			getvec(direc, inform, fp) == 0) {

		d = normalize(direc);
		if (d == 0.0) {				/* zero ==> flush */
			bogusray();
			if (--nextflush <= 0 || vcount <= 0) {
				fflush(stdout);
				nextflush = hresolu;
			}
		} else {
			samplendx++;
							/* compute and print */
			if (imm_irrad)
				irrad(orig, direc);
			else
				rad(orig, direc, lim_dist ? d : 0.0);
							/* flush if time */
			if (--nextflush == 0) {
				fflush(stdout);
				nextflush = hresolu;
			}
		}
		if (ferror(stdout))
			error(SYSTEM, "write error");
		if (--vcount == 0)			/* check for end */
			break;
	}
	fflush(stdout);
	if (vcount > 0)
		error(USER, "read error");
	if (fname != NULL)
		fclose(fp);
}


static void
setoutput(				/* set up output tables */
	register char  *vs
)
{
	register oputf_t **table = ray_out;

	castonly = 1;
	while (*vs)
		switch (*vs++) {
		case 't':				/* trace */
			*table = NULL;
			table = every_out;
			trace = ourtrace;
			castonly = 0;
			break;
		case 'o':				/* origin */
			*table++ = oputo;
			break;
		case 'd':				/* direction */
			*table++ = oputd;
			break;
		case 'v':				/* value */
			*table++ = oputv;
			castonly = 0;
			break;
		case 'l':				/* effective distance */
			*table++ = oputl;
			castonly = 0;
			break;
		case 'c':				/* local coordinates */
			*table++ = oputc;
			break;
		case 'L':				/* single ray length */
			*table++ = oputL;
			break;
		case 'p':				/* point */
			*table++ = oputp;
			break;
		case 'n':				/* perturbed normal */
			*table++ = oputn;
			castonly = 0;
			break;
		case 'N':				/* unperturbed normal */
			*table++ = oputN;
			break;
		case 's':				/* surface */
			*table++ = oputs;
			break;
		case 'w':				/* weight */
			*table++ = oputw;
			break;
		case 'm':				/* modifier */
			*table++ = oputm;
			break;
		}
	*table = NULL;
}


static void
bogusray(void)			/* print out empty record */
{
	thisray.rorg[0] = thisray.rorg[1] = thisray.rorg[2] =
	thisray.rdir[0] = thisray.rdir[1] = thisray.rdir[2] = 0.0;
	rayorigin(&thisray, NULL, PRIMARY, 1.0);
	printvals(&thisray);
}


static void
rad(		/* compute and print ray value(s) */
	FVECT  org,
	FVECT  dir,
	double	dmax
)
{
	VCOPY(thisray.rorg, org);
	VCOPY(thisray.rdir, dir);
	thisray.rmax = dmax;
	rayorigin(&thisray, NULL, PRIMARY, 1.0);
	if (castonly) {
		if (!localhit(&thisray, &thescene)) {
			if (thisray.ro == &Aftplane) {	/* clipped */
				thisray.ro = NULL;
				thisray.rot = FHUGE;
			} else
				sourcehit(&thisray);
		}
	} else
		rayvalue(&thisray);
	printvals(&thisray);
}


static void
irrad(			/* compute immediate irradiance value */
	FVECT  org,
	FVECT  dir
)
{
	register int  i;

	for (i = 0; i < 3; i++) {
		thisray.rorg[i] = org[i] + dir[i];
		thisray.rdir[i] = -dir[i];
	}
	rayorigin(&thisray, NULL, PRIMARY, 1.0);
					/* pretend we hit surface */
	thisray.rot = 1.0-1e-4;
	thisray.rod = 1.0;
	VCOPY(thisray.ron, dir);
	for (i = 0; i < 3; i++)		/* fudge factor */
		thisray.rop[i] = org[i] + 1e-4*dir[i];
					/* compute and print */
	(*ofun[Lamb.otype].funp)(&Lamb, &thisray);
	printvals(&thisray);
}


static void
printvals(			/* print requested ray values */
	RAY  *r
)
{
	register oputf_t **tp;

	if (ray_out[0] == NULL)
		return;
	for (tp = ray_out; *tp != NULL; tp++)
		(**tp)(r);
	if (outform == 'a')
		putchar('\n');
}


static int
getvec(		/* get a vector from fp */
	register FVECT  vec,
	int  fmt,
	FILE  *fp
)
{
	static float  vf[3];
	static double  vd[3];
	char  buf[32];
	register int  i;

	switch (fmt) {
	case 'a':					/* ascii */
		for (i = 0; i < 3; i++) {
			if (fgetword(buf, sizeof(buf), fp) == NULL ||
					!isflt(buf))
				return(-1);
			vec[i] = atof(buf);
		}
		break;
	case 'f':					/* binary float */
		if (fread((char *)vf, sizeof(float), 3, fp) != 3)
			return(-1);
		vec[0] = vf[0]; vec[1] = vf[1]; vec[2] = vf[2];
		break;
	case 'd':					/* binary double */
		if (fread((char *)vd, sizeof(double), 3, fp) != 3)
			return(-1);
		vec[0] = vd[0]; vec[1] = vd[1]; vec[2] = vd[2];
		break;
	default:
		error(CONSISTENCY, "botched input format");
	}
	return(0);
}


static void
tranotify(			/* record new modifier */
	OBJECT	obj
)
{
	static int  hitlimit = 0;
	register OBJREC	 *o = objptr(obj);
	register char  **tralp;

	if (obj == OVOID) {		/* starting over */
		traset[0] = 0;
		hitlimit = 0;
		return;
	}
	if (hitlimit || !ismodifier(o->otype))
		return;
	for (tralp = tralist; *tralp != NULL; tralp++)
		if (!strcmp(o->oname, *tralp)) {
			if (traset[0] >= MAXTSET) {
				error(WARNING, "too many modifiers in trace list");
				hitlimit++;
				return;		/* should this be fatal? */
			}
			insertelem(traset, obj);
			return;
		}
}


static void
ourtrace(				/* print ray values */
	RAY  *r
)
{
	register oputf_t **tp;

	if (every_out[0] == NULL)
		return;
	if (r->ro == NULL) {
		if (traincl == 1)
			return;
	} else if (traincl != -1 && traincl != inset(traset, r->ro->omod))
		return;
	tabin(r);
	for (tp = every_out; *tp != NULL; tp++)
		(**tp)(r);
	putchar('\n');
}


static void
tabin(				/* tab in appropriate amount */
	RAY  *r
)
{
	register RAY  *rp;

	for (rp = r->parent; rp != NULL; rp = rp->parent)
		putchar('\t');
}


static void
oputo(				/* print origin */
	RAY  *r
)
{
	(*putreal)(r->rorg[0]);
	(*putreal)(r->rorg[1]);
	(*putreal)(r->rorg[2]);
}


static void
oputd(				/* print direction */
	RAY  *r
)
{
	(*putreal)(r->rdir[0]);
	(*putreal)(r->rdir[1]);
	(*putreal)(r->rdir[2]);
}


static void
oputv(				/* print value */
	RAY  *r
)
{
	COLR  cout;
	
	if (outform == 'c') {
		setcolr(cout,	colval(r->rcol,RED),
				colval(r->rcol,GRN),
				colval(r->rcol,BLU));
		fwrite((char *)cout, sizeof(cout), 1, stdout);
		return;
	}
	(*putreal)(colval(r->rcol,RED));
	(*putreal)(colval(r->rcol,GRN));
	(*putreal)(colval(r->rcol,BLU));
}


static void
oputl(				/* print effective distance */
	RAY  *r
)
{
	(*putreal)(r->rt);
}


static void
oputL(				/* print single ray length */
	RAY  *r
)
{
	(*putreal)(r->rot);
}


static void
oputc(				/* print local coordinates */
	RAY  *r
)
{
	(*putreal)(r->uv[0]);
	(*putreal)(r->uv[1]);
}


static void
oputp(				/* print point */
	RAY  *r
)
{
	if (r->rot < FHUGE) {
		(*putreal)(r->rop[0]);
		(*putreal)(r->rop[1]);
		(*putreal)(r->rop[2]);
	} else {
		(*putreal)(0.0);
		(*putreal)(0.0);
		(*putreal)(0.0);
	}
}


static void
oputN(				/* print unperturbed normal */
	RAY  *r
)
{
	if (r->rot < FHUGE) {
		(*putreal)(r->ron[0]);
		(*putreal)(r->ron[1]);
		(*putreal)(r->ron[2]);
	} else {
		(*putreal)(0.0);
		(*putreal)(0.0);
		(*putreal)(0.0);
	}
}


static void
oputn(				/* print perturbed normal */
	RAY  *r
)
{
	FVECT  pnorm;

	if (r->rot >= FHUGE) {
		(*putreal)(0.0);
		(*putreal)(0.0);
		(*putreal)(0.0);
		return;
	}
	raynormal(pnorm, r);
	(*putreal)(pnorm[0]);
	(*putreal)(pnorm[1]);
	(*putreal)(pnorm[2]);
}


static void
oputs(				/* print name */
	RAY  *r
)
{
	if (r->ro != NULL)
		fputs(r->ro->oname, stdout);
	else
		putchar('*');
	putchar('\t');
}


static void
oputw(				/* print weight */
	RAY  *r
)
{
	(*putreal)(r->rweight);
}


static void
oputm(				/* print modifier */
	RAY  *r
)
{
	if (r->ro != NULL)
		if (r->ro->omod != OVOID)
			fputs(objptr(r->ro->omod)->oname, stdout);
		else
			fputs(VOIDID, stdout);
	else
		putchar('*');
	putchar('\t');
}


static void
puta(				/* print ascii value */
	double  v
)
{
	printf("%e\t", v);
}


static void
putd(v)				/* print binary double */
double  v;
{
	fwrite((char *)&v, sizeof(v), 1, stdout);
}


static void
putf(v)				/* print binary float */
double  v;
{
	float f = v;

	fwrite((char *)&f, sizeof(f), 1, stdout);
}
