/****************************************************************************
 * uberlight.sl - a light with many fun controls.
 *
 * Description:
 *   Based on Ronen Barzel's paper "Lighting Controls for Computer
 *   Cinematography" (in Journal of Graphics Tools, vol. 2, no. 1: 1-20).
 *
 * Rather than explicitly pass "from" and "to" points to indicate the
 * position and direction of the light (as spotlight does), this light
 * emits from the origin of the local light shader space, and points
 * toward the +z axis (also in shader space).  Thus, to position and
 * orient the light source, you must translate and rotate the
 * coordinate system in effect when the light source is declared.
 * Perhaps this is a new idea for some users, but it isn't really
 * hard, and it vastly simplifies the math in the shader.
 *
 * Basic color/brightness controls:
 *   intensity - overall intensity scaling of the light
 *   lightcolor - overall color filtering for the light
 *
 * Light type:
 *   lighttype - one of "spot", "omni", or "arealight".  Spot lights are
 *       those that point in a particular direction (+z in local light
 *       space, for this light.  Omni lights throw light in all directions.
 *       Area lights are emitted from actual geometry (this only works on
 *       BMRT area lights for the time being).
 *
 * Distance shaping and falloff controls:
 *   cuton, cutoff - define the depth range (z range from the origin, in
 *       light coordinates) over which the light is active.  Outside
 *       this range, no energy is transmitted.
 *   nearedge, faredge - define the width of the transition regions
 *       for the cuton and cutoff.  The transitions will be smooth.
 *   falloff - defines the exponent for falloff.  A falloff of 0 (the
 *       default) indicates that the light is the same brightness
 *       regardless of distance from the source.  Falloff==1 indicates
 *       linear (1/r) falloff, falloff==2 indicates 1/r^2 falloff
 *       (which is physically correct for point-like sources, but
 *       sometimes hard to use).
 *   falloffdist - the distance at which the incident energy is actually
 *       equal to intensity*lightcolor.  In other words, the intensity
 *       is actually given by:   I = (falloffdist / distance) ^ falloff
 *   maxintensity - to prevent the light from becoming unboundedly
 *       large when the distance < falloffdist, the intensity is
 *       smoothly clamped to this maximum value.
 *   parallelrays - when 0 (the default), the light appears to eminate
 *       from a single point (i.e. the rays diverge).  When nonzero, 
 *       the light rays are parallel, as if from an infinitely distant
 *       source (like the sun).
 *
 * Shaping of the cross-section.  The cross-section of the light cone
 * is actually described by a superellipse with the following
 * controls:
 *   shearx, sheary - define the amount of shear applied to the light
 *       cone direction.  Default is 0, meaning that the center of the
 *       light cone is aligned with the z axis in local light space.
 *   width, height - define the dimensions of the "barn door" opening.
 *       They are the cross-sectional dimensions at a distance of 1
 *       from the light.  In other words, width==height==1 indicates a
 *       90 degree cone angle for the light.
 *   wedge, hedge - the amount of width and height edge fuzz,
 *       respectively.  Values of 0 will make a sharp cutoff, larger
 *       values (up to 1) will make the edge softer.
 *   roundness - controls how rounded the corners of the superellipse
 *       are.  If this value is 0, the cross-section will be a perfect
 *       rectangle.  If the value is 1, the cross-section will be a
 *       perfect circle.  In between values control the roundness of
 *       the corners in a fairly obvious way.
 *   beamdistribution - controls intensity falloff due to angle.
 *       A value of 0 (the default) means no angle falloff.  A value
 *       of 1 is roughly physically correct for a spotlight, and 
 *       corresponds to a cosine falloff.  For a BMRT area light, the
 *       cosine falloff happens automatically, so 0 is the right physical
 *       value to use.  In either case, you may use larger values to
 *       make the spot more bright in the center than the outskirts.
 *       This parameter has no effect for omni lights.
 *
 * Cookie or slide filter:
 *   slidename - if a filename is supplied, a texture lookup will be
 *       done and the light emitted from the source will be filtered
 *       by that color, much like a slide projector.  If you want to
 *       make a texture map that simply blocks light, just make it
 *       black-and-white, but store it as a RGB texture.  For
 *       simplicity, the shader assumes that the texture file will
 *       have at least three channels.
 *
 * Projected noise on the light.
 *   noiseamp - amplitude of the noise.  A value of 0 (the default) 
 *       means not to use noise.  Larger values increase the blotchiness
 *       of the projected noise.
 *   noisefreq - frequency of the noise.
 *   noiseoffset - spatial offset of the noise.  This can be animated,
 *       for example if you are using the noise to simulate the
 *       attenuation of light as it passes through a window with 
 *       water drops dripping down it.
 * 
 * Shadow mapped shadows.  For PRMan (and perhaps other renderers),
 * shadows are mainly computed by shadow maps.  Please consult the
 * PRMan documentation for more information on the meanings of these
 * parameters.
 *   shadowmap - the name of the texture containing the shadow map.  If
 *       this value is "" (the default), no shadow map will be used.
 *   shadowblur - how soft to make the shadow edge, expressed as a
 *       percentage of the width of the entire shadow map.
 *   shadowbias - the amount of shadow bias to add to the lookup.
 *   shadownsamps - the number of samples to use.
 *
 * Ray-traced shadows.  These options work only for BMRT:
 *   raytraceshadow - if nonzero, cast a ray to see if we are in shadow.
 *       The default is zero, i.e. not to try raytracing.
 *   shadowcheat - add this offset to the light source position.  This
 *       allows you to cause the shadows to eminate as if the light
 *       were someplace else, but without changing the area
 *       illuminated or the appearance of highlights, etc.
 *
 * "Fake" shadows from a blocker object.  A blocker is a superellipse
 * in 3-space which effectively blocks light.  But it's not really
 * geometry, the shader just does the intersection with the
 * superellipse.  The blocker is defined to lie on the x-y plane of
 * its own coordinate system (which obviously needs to be defined in
 * the RIB file using the CoordinateSystem command).
 *   blockercoords - the name of the coordinate system that defines the
 *       local coordinates of the blocker.  If this is "", it indicates 
 *       that the shader should not use a blocker at all.
 *   blockerwidth, blockerheight - define the dimensions of the blocker's
 *       superellipse shape.
 *   blockerwedge, blockerhedge - define the fuzzyness of the edges.
 *   blockerround - how round the corners of the blocker are (same
 *       control as the "roundness" parameter that affects the light
 *       cone shape.
 *
 * Joint shadow controls:
 *   shadowcolor - Shadows (i.e. those regions with "occlusion" as
 *       defined by any or all of the shadow map, ray cast, or
 *       blocker) don't actually have to block light.  In fact, in
 *       this shader, shadowed regions actually just change the color
 *       of the light to "shadowcolor".  If this color is set to
 *       (0,0,0), it effectively blocks all light.  But if you set it
 *       to, say (.25,.25,.25), it will make the shadowed regions lose
 *       their full brightness, but not go completely dark.  Another
 *       use is if you are simulating sunlight: set the lightcolor to
 *       something yellowish, and make the shadowcolor dark but
 *       somewhat bluish.  Another effect of shadows is to set the
 *       __nonspecular flag, so that the shadowed regions are lit only
 *       diffusely, without highlights.
 * 
 * Other controls:
 *   __nonspecular - when set to 1, this light does not create
 *       specular highlights!  The default is 0, which means it makes
 *       highlights just fine (except for regions in shadows, as
 *       explained above).  This is very handy for lights that are
 *       meant to be fill lights, rather than key lights.
 *       NOTE: This depends on the surface shader looking for, and
 *       correctly acting upon, this parameter.  The builtin functions
 *       diffuse(), specular() and phong() all do this, for PRMan 3.5
 *       and later, as well as BMRT 2.3.5 and later.  But if you write
 *       your own illuminance loops in your surface shader, you've got
 *       to account for it yourself The PRMan user manual explains how
 *       to do this.
 *   __nondiffuse - the analog to __nonspecular, if this flag is set to
 *       1, this light will only cast specular highlights, but not
 *       diffuse light.  This is useful for making a light that only
 *       makes specular highlights, without affecting the rest of the
 *       illumination in the scene.  All the same caveats apply with
 *       respect to the surface shader, as described above for
 *       __nonspecular.
 *   __foglight - the "noisysmoke" shader distributed with BMRT will add
 *       atmospheric scattering only for those lights that have this
 *       parameter set to 1 (the default).  In other words, if you use
 *       this light with noisysmoke, you can set this flag to 0 to
 *       make a particular light *not* cause illumination in the fog.
 *       Note that the noisysmoke shader is distributed with BMRT, but
 *       will also work just fine with PRMan (3.7 or later).
 *
 * NOTE: this shader has one each of: blocker, shadow map, slide, and
 * noise texture.  Some advanced users may want more than one of some or
 * all of these.  It is left as an exercise for the reader to make such
 * extensions to the shader.
 *
 ***************************************************************************
 *
 * This shader was written as part of the course notes for ACM
 * SIGGRAPH '98, course 11, "Advanced RenderMan: Beyond the Companion"
 * (co-chaired by Tony Apodaca and Larry Gritz).  Feel free to use and
 * distribute the source code of this shader, but please leave the
 * original attribution and all comments.
 *
 * This shader was tested using Pixar's PhotoRealistic RenderMan 3.7
 * and the Blue Moon Rendering Tools (BMRT) release 2.3.6.  I have
 * tried to avoid Shading Language constructs which wouldn't work on
 * older versions of these renderers, but I do make liberal use of the
 * "vector" type and I often declare variables where they are used,
 * rather than only at the beginning of blocks.  If you are using a
 * renderer which does not support these new language features, just
 * substitute "point" for all occurrances of "vector", and move the
 * variable declarations to the top of the shader.
 *
 * Author: coded by Larry Gritz, 1998
 *         based on paper by Ronen Barzel, 1997
 *
 * Contacts:  {lg|ronen}@pixar.com
 *
 *
 * $Revision$    $Date$
 *
 ****************************************************************************/




/* Superellipse soft clipping
 * Input:
 *   - point Q on the x-y plane
 *   - the equations of two superellipses (with major/minor axes given by
 *        a,b and A,B for the inner and outer ellipses, respectively)
 * Return value:
 *   - 0 if Q was inside the inner ellipse
 *   - 1 if Q was outside the outer ellipse
 *   - smoothly varying from 0 to 1 in between
 */
float
clipSuperellipse (point Q;          /* Test point on the x-y plane */
		  float a, b;       /* Inner superellipse */
		  float A, B;       /* Outer superellipse */
		  float roundness;  /* Same roundness for both ellipses */
		 )
{
    float result;
    float x = abs(xcomp(Q)), y = abs(ycomp(Q));
    if (roundness < 1.0e-6) {
	/* Simpler case of a square */
	result = 1 - (1-smoothstep(a,A,x)) * (1-smoothstep(b,B,y));
    } else {
	/* Harder, rounded corner case */
	float re = 2/roundness;   /* roundness exponent */
	float q = a * b * pow (pow(b*x, re) + pow(a*y, re), -1/re);
	float r = A * B * pow (pow(B*x, re) + pow(A*y, re), -1/re);
	result = smoothstep (q, r, 1);
    }
    return result;
}





/* Volumetric light shaping
 * Inputs:
 *   - the point being shaded, in the local light space
 *   - all information about the light shaping, including z smooth depth
 *     clipping, superellipse x-y shaping, and distance falloff.
 * Return value:
 *   - attenuation factor based on the falloff and shaping
 */
float
ShapeLightVolume (point PL;                      /* Point in light space */
		  string lighttype;              /* what kind of light */
		  vector axis;                   /* light axis */
                  float znear, zfar;             /* z clipping */
		  float nearedge, faredge;
		  float falloff, falloffdist;    /* distance falloff */
		  float maxintensity;
		  float shearx, sheary;          /* shear the direction */
		  float width, height;           /* xy superellipse */
		  float hedge, wedge, roundness;
		  float beamdistribution;        /* angle falloff */
		  )
{	
    /* Examine the z depth of PL to apply the (possibly smooth) cuton and
     * cutoff.
     */
    float atten = 1;
    float PLlen = length(PL);
    float Pz;
    if (lighttype == "spot") {
	Pz = zcomp(PL);
    } else {
	/* For omni or area lights, use distance from the light */
	Pz = PLlen;
    }
    atten *= smoothstep (znear-nearedge, znear, Pz);
    atten *= 1 - smoothstep (zfar, zfar+faredge, Pz);
    
    /* Distance falloff */
    if (falloff != 0) {
	if (PLlen > falloffdist) {
	    atten *= pow (falloffdist/PLlen, falloff);
	} else {
	    float s = log (1/maxintensity);
	    float beta = -falloff/s;
	    atten *= (maxintensity * exp (s * pow(PLlen/falloffdist, beta)));
	}
    }

    /* Clip to superellipse */
    if (lighttype != "omni" && beamdistribution > 0)
	atten *= pow (normalize(vector PL) . axis, beamdistribution);
    if (lighttype == "spot") {
	atten *= 1 - clipSuperellipse (PL/Pz-point(shearx,sheary,0),
				       width, height,
				       width+wedge, height+hedge, roundness);
    }

    return atten;
}




/* Evaluate the occlusion between two points, P1 and P2, due to a fake
 * blocker.  Return 0 if the light is totally blocked, 1 if it totally
 * gets through.
 */
float
BlockerContribution (point P1, P2;
		     string blockercoords;
		     float blockerwidth, blockerheight;
		     float blockerwedge, blockerhedge;
		     float blockerround;
                    )
{
    float unoccluded = 1;
    /* Get the surface and light positions in blocker coords */
    point Pb1 = transform (blockercoords, P1);
    point Pb2 = transform (blockercoords, P2);
    /* Blocker works only if it's straddled by ray endpoints. */
    if (zcomp(Pb2)*zcomp(Pb1) < 0) {
	vector Vlight = (Pb1 - Pb2);
	point Pplane = Pb1 - Vlight*(zcomp(Pb1)/zcomp(Vlight));
	unoccluded *= clipSuperellipse (Pplane, blockerwidth, blockerheight,
					blockerwidth+blockerwedge,
					blockerheight+blockerhedge,
					blockerround);
    }
    return unoccluded;
}




light uberlight (
           /* Basic intensity and color of the light */
           string lighttype = "spot";
           float intensity = 1;
	   color lightcolor = color (1,1,1);
	   /* Z shaping and distance falloff */
	   float cuton = 0.01, cutoff = 1.0e6;
	   float nearedge = 0, faredge = 0;
	   float falloff = 0, falloffdist = 1;
           float maxintensity = 1;
	   float parallelrays = 0;
	   /* xy shaping of the cross-section and angle falloff */
	   float shearx = 0, sheary = 0;
	   float width = 1, height = 1, wedge = .1, hedge = .1;
	   float roundness = 1;
	   float beamdistribution = 0;
	   /* Cookie or slide to control light cross-sectional color */
	   string slidename = "";

	   /* Noisy light */
	   float noiseamp = 0;
	   float noisefreq = 4;
	   vector noiseoffset = 0;
	   /* Shadow mapped shadows */
	   string shadowmap = "";
	   float shadowblur = 0.01;
	   float shadowbias = .01;
	   float shadownsamps = 16;
	   color shadowcolor = 0;
	   /* Ray traced shadows */
	   float raytraceshadow = 0;
	   vector shadowcheat = vector "shader" (0,0,0);
	   /* Fake blocker shadow */
	   string blockercoords = "";
	   float blockerwidth=1, blockerheight=1;
	   float blockerwedge=.1, blockerhedge=.1, blockerround=1;
	   /* Miscellaneous controls */
	   output varying float __nonspecular = 0;
	   output varying float __nondiffuse = 0;
	   output float __foglight = 1;
    )
{
    /* For simplicity, assume that the light is at the origin of shader
     * space, and aimed in the +z direction.  So to move or orient the
     * light, you transform coordinate system in the RIB stream, prior
     * to instancing the light shader.  But that sure simplifies the
     * internals of the light shader!  Anyway, let PL be the position of
     * the surface point we're shading, expressed in the local light
     * shader coordinates.
     */
    point PL = transform ("shader", Ps);
    point from;
    vector axis;
    uniform float angle;
    if (lighttype == "spot") {
	/* Spot light */
	from = point "shader" (0,0,0);
	axis = normalize(vector "shader" (0,0,1));
	uniform float maxradius = 1.4142136 * max(height+hedge+abs(sheary),
						  width+wedge+abs(shearx));
	angle = atan(maxradius);
    } else if (lighttype == "arealight") {
	/* BMRT area light */
	from = P;
	axis = N;
	angle = PI/2;
    } else {
	/* Omnidirectional light */
	from = point "shader" (0,0,0);
	axis = normalize(vector "shader" (0,0,1));
	angle = PI;
    }

    illuminate (from, axis, angle) {

	/* Accumulate attenuation of the light as it is affected by various
	 * blockers and whatnot.  Start with no attenuation (i.e. a 
	 * multiplicitive attenuation of 1.
	 */
	float atten = 1.0;
	color lcol = lightcolor;

	/* Basic light shaping - the volumetric shaping is all encapsulated
	 * in the ShapeLightVolume function.
	 */
	atten *= ShapeLightVolume (PL, lighttype, axis, cuton, cutoff,
				   nearedge, faredge, falloff, falloffdist,
				   maxintensity/intensity, shearx, sheary,
				   width, height, hedge, wedge, roundness,
				   beamdistribution);

	/* If the volume says we aren't being lit, skip the remaining tests */
	if (atten > 0) {
	    /* Project a slide or use a cookie */
	    if (slidename != "") {
		point Pslide = PL / point (width+wedge, height+hedge, 1);
		float zslide = zcomp(Pslide);
		lcol *= color texture (slidename, 0.5*xcomp(Pslide)/zslide+0.5,
				       0.5-0.5*ycomp(Pslide)/zslide);
	    }
	
	    /* Apply noise */
	    if (noiseamp > 0) {
		float n = noise (noisefreq * (PL+noiseoffset) * point(1,1,0));
		n = smoothstep (0, 1, 0.5 + noiseamp * (n-0.5));
		atten *= n;
	    }

	    /* Apply shadow mapped shadows */
	    float unoccluded = 1;
	    if (shadowmap != "") {
		unoccluded *= (1-shadow (shadowmap, Ps, "blur", shadowblur,
				      "samples", shadownsamps,
				      "bias", shadowbias));
	    }
	    point shadoworigin;
	    if (parallelrays == 0) {
		shadoworigin = from;
	    } else {
		shadoworigin = point "shader" (xcomp(PL), ycomp(PL), cuton);
	    }

	    /* Apply blocker fake shadows */
	    if (blockercoords != "") {
		unoccluded *= 
		    BlockerContribution (Ps, shadoworigin, blockercoords,
					 blockerwidth, blockerheight,
					 blockerwedge, blockerhedge,
					 blockerround);
	    }
	    lcol = mix (shadowcolor, lcol, unoccluded);
	    __nonspecular *= (1 - unoccluded);
	}

	Cl = (atten*intensity) * lcol;
	if (parallelrays != 0)
	    L = axis * length(Ps-from);
    }
}