#ifndef lint
static const char RCSid[] = "$Id: m_mist.c,v 2.18 2014/07/08 18:25:00 greg Exp $";
#endif
/*
 * Mist volumetric material.
 */

#include "copyright.h"

#include <string.h>

#include  "ray.h"
#include  "source.h"
#include  "rtotypes.h"

/*
 *  A mist volume is used to specify a region in the scene where a certain
 *  light source (or sources) is going to contribute to scattering.  The
 *  material can add to the existing global medium, and override any ray
 *  settings for scattering albedo and eccentricity.  Overlapping mist
 *  regions should agree w.r.t. albedo and eccentricity, and
 *  should have disjoint source lists.
 *
 *  A pattern, if used, should compute the line integral of extinction,
 *  and will modify the first three arguments directly.  This will tend
 *  to invalidate results when there are other objects intersected within
 *  the mist region.
 *
 *  The string arguments for MAT_MIST are the identifiers for the important
 *  light sources, which will be looked up in the source array.  The last
 *  source found matching a name is the one used.  A relayed light source
 *  may be indicated by the relay surface name, followed by a '>' character,
 *  followed by the relayed source name (which may be another relay).
 *
 *  Up to five real arguments may be given for MAT_MIST:
 *
 *	[ext_r  ext_g  ext_b  [albedo_r albedo_g albedo_b [gecc]]]
 *
 *  The primaries indicate medium extinction per unit length (absorption
 *  plus scattering), which is added to the global extinction coefficient, set
 *  by the -me option.  The albedo is the ratio of scattering to extinction,
 *  and is set globally by the -ma option (salbedo) and overridden here.
 *  The Heyney-Greenstein eccentricity parameter (-mg seccg) indicates how much
 *  scattering favors the forward direction.  A value of 0 means isotropic
 *  scattering.  A value approaching 1 indicates strong forward scattering.
 */

#ifndef  MAXSLIST
#define  MAXSLIST	32	/* maximum sources to check */
#endif

#define RELAYDELIM	'>'		/* relay delimiter character */

static int inslist(int  *sl, int  n);
static int srcmatch(SRCREC  *sp, char  *id);
static void add2slist(RAY  *r, int  *sl);


static int
inslist(		/* return index of source n if it's in list sl */
	int  *sl,
	int  n
)
{
	int  i;

	for (i = sl[0]; i > 0; i--)
		if (sl[i] == n)
			return(i);
	return(0);
}


static int
srcmatch(	/* check for an id match on a light source */
	SRCREC  *sp,
	char  *id
)
{
	char  *cp;
						/* check for relay sources */
	while ((cp = strchr(id, RELAYDELIM)) != NULL) {
		if (!(sp->sflags & SVIRTUAL) || sp->so == NULL)
			return(0);
		if (strncmp(id, sp->so->oname, cp-id) || sp->so->oname[cp-id])
			return(0);
		id = cp + 1;				/* relay to next */
		sp = source + sp->sa.sv.sn;
	}
	if (sp->sflags & SVIRTUAL || sp->so == NULL)
		return(0);
	return(!strcmp(id, sp->so->oname));
}


static void
add2slist(	/* add source list to ray's */
	RAY  *r,
	int  *sl
)
{
	static int  slspare[MAXSLIST+1];	/* in case of emergence */
	int  i;

	if (sl == NULL || sl[0] == 0)		/* nothing to add */
		return;
	if (r->slights == NULL)
		(r->slights = slspare)[0] = 0;	/* just once per ray path */
	for (i = sl[0]; i > 0; i--)
		if (!inslist(r->slights, sl[i])) {
			if (r->slights[0] >= MAXSLIST)
				error(INTERNAL,
					"scattering source list overflow");
			r->slights[++r->slights[0]] = sl[i];
		}
}


int
m_mist(		/* process a ray entering or leaving some mist */
	OBJREC  *m,
	RAY  *r
)
{
	RAY  p;
	int  *myslist = NULL;
	int  newslist[MAXSLIST+1];
	COLOR  mext;
	double  re, ge, be;
	int  i, j;
					/* check arguments */
	if (m->oargs.nfargs > 7)
		objerror(m, USER, "bad arguments");
					/* get source indices */
	if (m->oargs.nsargs > 0 && (myslist = (int *)m->os) == NULL) {
		if (m->oargs.nsargs > MAXSLIST)
			objerror(m, INTERNAL, "too many sources in list");
		myslist = (int *)malloc((m->oargs.nsargs+1)*sizeof(int));
		if (myslist == NULL)
			goto memerr;
		myslist[0] = 0;			/* size is first in list */
		for (j = 0; j < m->oargs.nsargs; j++) {
			i = nsources;		/* look up each source id */
			while (i--)
				if (srcmatch(source+i, m->oargs.sarg[j]))
					break;
			if (i < 0) {
				sprintf(errmsg, "unknown source \"%s\"",
						m->oargs.sarg[j]);
				objerror(m, WARNING, errmsg);
			} else if (inslist(myslist, i)) {
				sprintf(errmsg, "duplicate source \"%s\"",
						m->oargs.sarg[j]);
				objerror(m, WARNING, errmsg);
			} else
				myslist[++myslist[0]] = i;
		}
		m->os = (char *)myslist;
	}
	if (m->oargs.nfargs > 2) {		/* compute extinction */
		setcolor(mext, m->oargs.farg[0], m->oargs.farg[1],
				m->oargs.farg[2]);
		raytexture(r, m->omod);			/* get modifiers */
		multcolor(mext, r->pcol);
	} else
		setcolor(mext, 0., 0., 0.);
						/* start transmitted ray */
	if (rayorigin(&p, TRANS, r, NULL) < 0)
		return(1);
	VCOPY(p.rdir, r->rdir);
	p.slights = newslist;
	if (r->slights != NULL)			/* copy old list if one */
		for (j = r->slights[0]; j >= 0; j--)
			p.slights[j] = r->slights[j];
	else
		p.slights[0] = 0;
	if (r->rod > 0.) {			/* entering ray */
		addcolor(p.cext, mext);
		if (m->oargs.nfargs > 5)
			setcolor(p.albedo, m->oargs.farg[3],
					m->oargs.farg[4], m->oargs.farg[5]);
		if (m->oargs.nfargs > 6)
			p.gecc = m->oargs.farg[6];
		add2slist(&p, myslist);			/* add to list */
	} else {				/* leaving ray */
		if (myslist != NULL) {			/* delete from list */
			for (j = myslist[0]; j > 0; j--)
				if ( (i = inslist(p.slights, myslist[j])) )
					p.slights[i] = -1;
			for (i = 0, j = 1; j <= p.slights[0]; j++)
				if (p.slights[j] != -1)
					p.slights[++i] = p.slights[j];
			if (p.slights[0] - i < myslist[0]) {	/* fix old */
				addcolor(r->cext, mext);
				if (m->oargs.nfargs > 5)
					setcolor(r->albedo, m->oargs.farg[3],
					m->oargs.farg[4], m->oargs.farg[5]);
				if (m->oargs.nfargs > 6)
					r->gecc = m->oargs.farg[6];
				add2slist(r, myslist);
			}
			p.slights[0] = i;
		}
		if ((re = colval(r->cext,RED) - colval(mext,RED)) <
				colval(cextinction,RED))
			re = colval(cextinction,RED);
		if ((ge = colval(r->cext,GRN) - colval(mext,GRN)) <
				colval(cextinction,GRN))
			ge = colval(cextinction,GRN);
		if ((be = colval(r->cext,BLU) - colval(mext,BLU)) <
				colval(cextinction,BLU))
			be = colval(cextinction,BLU);
		setcolor(p.cext, re, ge, be);
		if (m->oargs.nfargs > 5)
			copycolor(p.albedo, salbedo);
		if (m->oargs.nfargs > 6)
			p.gecc = seccg;
	}
	rayvalue(&p);				/* calls rayparticipate() */
	copycolor(r->rcol, p.rcol);		/* return value */
	r->rt = r->rot + p.rt;
	return(1);
memerr:
	error(SYSTEM, "out of memory in m_mist");
	return 0; /* pro forma return */
}
