/////////////////////////////////////////////////////////////////////////////
// smoke.osl
//
// Description:
//    This is a volume shader for smoke.  Trapezoidal integration is
//    used to find scattering and extinction.
//
// Parameters:
//   opacdensity - overall smoke density control as it affects its ability
//          to block light from behind it.
//   lightdensity - smoke density control as it affects light scattering
//          toward the viewer.
//   scatter - when non-1, can be used to give wavelength-dependent
//          extinction coefficients.
//   integstart, integend - bounds along the viewing ray direction of the
//          integration of atmospheric effects.
//   stepsize - step size for integration
//   maxsteps - maximum number of steps
//   smokefreq, smokeoctaves, smokevary - control the fBm of the noisy smoke
//          If either smokeoctaves or smokevary is 0, there is no noise
//          to the smoke.
//   background_object - when nonzero, just do "volume over surface."  When
//          zero (default), use a formula that avoids double-accounting
//          for volumes overlapped by both this and farther surfaces.
//          You want this to be nonzero only for the farthest visible surface
//          if it happens to be nonopaque (usually a transparent backplane).
//
// Much of this is adapted from Apodaca & Gritz, "Advanced RenderMan",
// Morgan-Kaufmann 1999.
//
/////////////////////////////////////////////////////////////////////////////
// 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)
/////////////////////////////////////////////////////////////////////////////



#include "noises.h"



// For point P (we are passed both the common and shader space
// coordinates), gather illumination from the light sources and
// compute the smoke density at that point.  Do not count lights
// with a "__foglight" parameter that's set to 0.
void
smokedensity (point Pcur, point Pshad, 
	      float smokevary, float smokefreq, 
              float smokeoctaves,
	      float stepsize,
	      output color Lscatter, output float smoke)
{
    Lscatter = 0;
    lights (Pcur) {
	float foglight = 1;
	getmessage ("light", "__foglight",foglight);
        Lscatter += foglight * Cl;
    }
    if (smokeoctaves > 0 && smokevary > 0) {
	point Psmoke = Pshad * smokefreq;
	smoke = snoise (Psmoke);
	// Optimize: one octave only if not lit
	if ((Lscatter[0]+Lscatter[1]+Lscatter[2]) > 0.01)
	    smoke += 0.5 * fBm (Psmoke*2, stepsize*2, smokeoctaves-1, 2, 0.5);
	smoke = smoothstep(-1,1,smokevary*smoke);
    } else {
	smoke = 0.5;
    }
}



// Return a component-by-component exp() of a color
color colorexp (color C)
{
    return color (exp(C[0]), exp(C[1]), exp(C[2]));
}



volume
smoke
    [[ string help = "Smoke volume" ]]
(
    float opacdensity = 1
        [[ string help = "Overall smoke opacity density" ]],
    float lightdensity = 1
        [[ string help = "Overall smoke light scattering density" ]],
    color scatter = 1
        [[ string help = "Wavelength-dependent scattering" ]],

    float integstart = 0
        [[ string help = "Integration start - near" ]],
    float integend = 1000
        [[ string help = "Integration end - far" ]],
    float stepsize = 0.1
        [[ string help = "Integration step size",
           float min = 0 ]],
    float maxsteps = 100
        [[ string help = "Max number of integration steps",
           float UImin = 2 ]],

    float smokeoctaves = 0
        [[ string help = "Smoke variation (noise) octaves (0 = no noise)",
           float min = 0, float max = 8 ]],
    float smokevary = 1
        [[ string help = "Smoke variation (noise) amplitude" ]],
    float smokefreq = 1
        [[ string help = "Frequency of smoke variation" ]],
    float background_object = 0
        [[ string help = "Use 1 for a transparent 'backplane' object, 0 for everything else including transparent foreground objects.  Doesn't matter for opaque objects." ]]
  )
{
    point Worigin = P - I;    // Origin of volume ray
    point origin = transform ("shader", Worigin);
    float dtau, last_dtau;
    color li, last_li;

    // Integrate forwards from the start point
    float d = integstart + random()*stepsize;
    vector IN = normalize (transform ("shader", I));
    vector WIN = transform ("shader", "common", IN);

    // Calculate a reasonable step size
    float end = min (length (I), integend) - 0.0001;
    float minss = (end-d) / maxsteps;
    float ss = min (max (minss, stepsize), end-d);

    // Get the in-scattered light and the local smoke density for the
    // beginning of the ray
    smokedensity (Worigin + d*WIN, origin + d*IN,
		  smokevary, smokefreq, smokeoctaves, ss, last_li, last_dtau);

    color Cv = 0, Ov = 0;   // color & opacity of volume that we accumulate
    while (d < end) {
	// Take a step and get the local scattered light and smoke density
	ss = clamp (ss, 0.005, end-d);
	d += ss;
	smokedensity (Worigin + d*WIN, origin + d*IN,
		      smokevary, smokefreq, smokeoctaves, ss, li, dtau);
	
	// Find the blocking and light scattering contribution of the
	// portion of the volume covered by this step.
	float tau = opacdensity * (ss/2) * (dtau + last_dtau);
	color lighttau = lightdensity * ss/2 * (li*dtau + last_li*last_dtau);
	
	// Composite with exponential extinction of background light
	Cv += (1-Ov) * lighttau * scatter;
	Ov += (1-Ov) * (1 - colorexp (-tau*scatter));
	last_dtau = dtau;
	last_li = li;
    }
    
    // C & opacity are the color and opacity of the background element.
    // Now Cv is the light contributed by the volume itself, and Ov is the
    // opacity of the volume, i.e. (1-Ov)*C is the light from the background
    // which makes it through the volume.
    C = Cv + (1-Ov)*C; 
    opacity = Ov + (1-Ov)*opacity;
}
