/////////////////////////////////////////////////////////////////////////////
// uberlight.osl - a light with many fun controls.
//
// Description:
//
//   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.
//   zreverse - if nonzero, will throw the light in the -z direction rather
//       then in the +z direction.  This is helpful when using the light
//       with certain modeling systems (such as Maya) whose UI's insist
//       that lights point in their local -z direction.
//
// 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 emanate
//       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 ellipse.  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 an 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 an 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, you can use the noise to simulate the
//       attenuation of light as it passes through a window with 
//       water drops dripping down it.
// 
// Shadows:
//   shadowname - the name of the texture containing the shadow map, or
//       the name of a geometry set for ray traced shadows.
//       this value is "" (the default), the light will not cast shadows.
//   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.  A
//       value < 0 indicates to use the global default (set by 
//       Option "shadow:bias").
//   shadowsamples - the number of samples to use.
//
// "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 scene file using SaveAttributes).
//   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 built-in functions
//       diffuse() and specular() all do this, but if you write
//       your own lights loop in your surface shader, you've got
//       to account for it yourself.
//   __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 - many volumetric or smoke shaders 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: 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.
//
/////////////////////////////////////////////////////////////////////////////
//
// History:
//   Based on Ronen Barzel's paper "Lighting Controls for Computer
//   Cinematography" (in Journal of Graphics Tools, vol. 2, no. 1: 1-20).
//
//   Coded in SL by Larry Gritz, published in SIGGRAPH '98 course notes #11,
//   revised and published in "Advanced RenderMan: CGI for Motion Pictures"
//   (Apodaca & Gritz, Morgan-Kaufmann 1999).
//
//   Revised further for inclusion in NVIDIA's Gelato renderer.
//
/////////////////////////////////////////////////////////////////////////////
// Copyright 2004 NVIDIA Corporation.  All Rights Reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// 
// * Redistributions of source code must retain the above copyright
//   notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
// * Neither the name of NVIDIA nor the names of its contributors
//   may be used to endorse or promote products derived from this software
//   without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// (This is the Modified BSD License)
/////////////////////////////////////////////////////////////////////////////



// 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, float b, // Inner superellipse
		  float A, float B, // Outer superellipse
		  float roundness   // Same roundness for both ellipses
		 )
{
    float result = 0;
    float x = abs(Q[0]), y = abs(Q[1]);
    if (x != 0 || y != 0) {  // avoid degenerate case
	if (roundness < 1.0e-6) {
	    // Simpler case of a square
	    result = 1 - (1-smoothstep(a,A,x)) * (1-smoothstep(b,B,y));
	} else if (roundness > 0.9999) {
	    // Simple case of a circle
	    float sqr (float x) { return x*x; }
	    float q = a * b / sqrt (sqr(b*x) + sqr(a*y));
	    float r = A * B / sqrt (sqr(B*x) + sqr(A*y));
	    result = smoothstep (q, r, 1);
	} 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, float zfar,          // z clipping
		  float nearedge, float faredge,
		  float falloff, float falloffdist, // distance falloff
		  float maxintensity,
		  float shearx, float sheary,       // shear the direction
		  float width, float height,        // xy superellipse
		  float hedge, float wedge,
                  float 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 = PL[2];
    } 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) {
	vector PLN = normalize((vector)PL);
	atten *= pow (PLN[2], 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, point P2,
		     string blockercoords,
		     float blockerwidth, float blockerheight,
		     float blockerwedge, float 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 (Pb2[2]*Pb1[2] < 0) {
	vector Vlight = (Pb1 - Pb2);
	point Pplane = Pb1 - Vlight*(Pb1[2]/Vlight[2]);
	unoccluded *= clipSuperellipse (Pplane, blockerwidth, blockerheight,
					blockerwidth+blockerwedge,
					blockerheight+blockerhedge,
					blockerround);
    }
    return unoccluded;
}




light
uberlight
    [[ string help = "Mother of all lights" ]]

          (
           // Basic intensity and color of the light
           string lighttype = "spot"  
                   [[
                       string help = "Light type"
                   ]],
           float intensity = 1  
                   [[
                       string help = "Overall light intensity",
                       float min=0
                   ]],
	   color lightcolor = color (1,1,1) 
                   [[ 
                       string help = "Overall light color"
                    ]],
	   // Z shaping and distance falloff
	   float falloff = 0 
                   [[
                       string help = "Distance falloff exponent",
                       float min = 0, float max = 3
                   ]],
           float falloffdist = 1  
                   [[
                       string help = "Scale for distance falloff",
                       float min = 0
                   ]],
           float maxintensity = 2  
                   [[
                       string help = "Maximum intensity clamp",
                       float min = 0
                   ]],
	   float cuton = 0.01 
                   [[
                       string help = "Depth at which light is fully on",
                       float min = 0
                   ]],
           float cutoff = 1.0e6 
                   [[
                       string help = "Depth at which light starts turning off",
                        float min = 0
                   ]],
           float nearedge = 0 
                   [[
                       string help = "Depth at which light starts turning on",
                       float min = 0
                   ]],
           float faredge = 0 
                   [[
                       string help = "Depth at which light is fully off",
                       float min = 0
                   ]],
	   float parallelrays = 0  
                   [[
                       string help = "Are rays parallel?"
                   ]],
           float zreverse = 0 
                   [[
                       string help = "Send light to -z instead of +z?"
                   ]],
	   // xy shaping of the cross section and angle falloff
	   float shearx = 0  
                   [[
                       string help = "x shear of light frustum",
                        float min=-1, float max=1
                   ]],
           float sheary = 0  
                   [[
                       string help = "y shear of light frustum",
                       float min=-1, float max=1
                   ]],
	   float width = 1  
                   [[
                       string help = "x dimension of barn door",
                       float min=0
                   ]],
           float height = 1  
                   [[
                       string help = "y dimension of barn door",
                       float min=0
                   ]],
           float wedge = .1  
                   [[
                       string help = "width of barn door edge fuzz",
                      float min=0, float max=1
                   ]],
           float hedge = .1  
                   [[
                       string help = "height of barn door edge fuzz",
                       float min=0, float max=1
                   ]],
	   float roundness = 1  
                   [[
                       string help = "Roundness of light cone shape",
                       float min=0, float max=1
                   ]],
	   float beamdistribution = 0  
                   [[
                       string help = "Angular falloff exponent",
                        float min=0
                   ]],
	   // Cookie or slide to control light cross-sectional color
	   string slidename = ""  
                   [[
                       string help="Name of texture for slide projector"
                   ]],
	   // Noisy light
	   float noiseamp = 0  
                   [[
                       string help = "Noise amplitude",
                       float min=0
                   ]],
           float noisefreq = 4  
                   [[
                       string help = "Noise frequency",
                       float min=0
                   ]],
	   vector noiseoffset = 0  
                   [[
                       string help = "Noise spatial offset"
                   ]],
	   // Shadows
	   string shadowname = ""  
                   [[
                       string help = "Shadow name (map or geomset)"
                   ]],
	   float shadowblur = 0  
                   [[
                       string help = "Shadow blur",
                       float min=0, float max=1
                   ]],
           float shadowsamples = 1  
                   [[
                       string help = "Shadow samples",
                       int min=1, int max=256
                   ]],
	   float shadowbias = -1  
                   [[
                       string help = "Shadow bias (-1 means default)",
                       float min=-1, float max=1
                   ]],
	   color shadowcolor = 0  
                   [[
                       string help = "Shadow tint"
                   ]],
	   // Fake blocker shadow
	   string blockercoords = ""  
                   [[
                       string help = "Blocker coordinate system"
                   ]],
	   float blockerwidth = 1  
                   [[
                       string help = "Blocker width"
                   ]],
           float blockerheight = 1  
                   [[
                       string help = "Blocker height",
                       float min=0
                   ]],
	   float blockerwedge = .1  
                   [[
                       string help = "Blocker width fuzz",
                       float min=0
                   ]],
           float blockerhedge = .1  
                   [[
                       string help = "Blocker height fuzz",
                       float min=0, float max=1]],
           float blockerround = 1  
                   [[
                       string help = "Blocker roundness",
                       float min=0, float max=1
                   ]],
	   // Miscellaneous controls
	   output float __nonspecular = 0,
	   output 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 the 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);
    if (zreverse)
        PL *= vector(1,1,-1);

    // If it's an area light, we want the point and normal of the
    // light geometry.  If not an area light,
    // P,N will be the origin and z-axis of shader space.
    point from;
    vector axis;

    float angle;
    if (lighttype == "spot") {                  // Spot light
    from = point ("shader", 0, 0, 0);
    axis = vector ("shader", 0, 0, zreverse ? -1 : 1);
	float maxradius = M_SQRT2 * max(height+hedge+abs(sheary),
                                        width+wedge+abs(shearx));
	angle = atan(maxradius);
    } else if (lighttype == "arealight") {	// Area (geometry) light
	from = P;
	axis = N;
	angle = M_PI_2;
    } else {                                    // Omnidirectional light
    from = point ("shader", 0, 0, 0);
    axis = vector ("shader", 0, 0, zreverse ? -1 : 1);
	angle = M_PI;
    }
    emit (from, axis, angle) {
	// Accumulate attenuation of the light as it is affected by various
	// blockers and whatnot.  Start with no attenuation (i.e., a 
	// multiplicative attenuation of 1.
	float atten;
	color lcol = lightcolor;

	// Basic light shaping - the volumetric shaping is all encapsulated
	// in the ShapeLightVolume function.
	if (intensity == 0)  // Guard against divide-by-zero errors
	    atten = 0;
	else
	    atten = ShapeLightVolume (PL, lighttype, axis, cuton, cutoff,
				      nearedge, faredge, falloff, falloffdist,
				      max(1.01,maxintensity/intensity),
                                      shearx, sheary,
				      width, height, hedge, wedge, roundness,
				      beamdistribution);

	// Project a slide or use a cookie
	if (slidename) {
	    point Pslide = PL / point (width+wedge, height+hedge, 1);
	    float zslide = Pslide[2];
	    float xslide = 0.5+0.5*Pslide[0]/zslide;
	    float yslide = 0.5-0.5*Pslide[1]/zslide;
	    lcol *= (color) texture (slidename, xslide, yslide);
	}
	
	// If the volume says we aren't being lit, skip the remaining tests
	if (atten > 0) {
	    // 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 shadows
	    float unoccluded = 1;
	    if (shadowname)
		unoccluded *= transmission (Ps, Ps + L, "traceset", shadowname, "blur", shadowblur,
					  "samples", (int)shadowsamples,
					  "bias", shadowbias);
	    point shadoworigin;
	    if (parallelrays == 0)
		 shadoworigin = from;
	    else shadoworigin = point ("shader", PL[0], PL[1],
                                       zreverse ? -cuton : 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 * (1 - __nonspecular);
	}
	Cl = (atten*intensity) * lcol;
	if (parallelrays)
	    L = -axis * length(Ps-from);
    }
	setmessage ("__foglight", __foglight);
	setmessage ("__nonspecular", __nonspecular);
	setmessage ("__nondiffuse", __nondiffuse);
}
