/**
 * RCskin.sl
 * 
 * Copyright (C) 2002, Rudy Cortes   rcortes AT hntb DOT com 
 * created  09/16/2002
 * 
 * This software is placed in the public domain and is provided as is 
 * without express or implied warranty. 
 * 
 * Surface shader that implements a shading model that should have a visual 
 * appearence generall similar to that of skin. 
 *
 * Feel free to use this shader to create skin for any character, anywhere and
 * everywhere, Just list me on the credits under "Shading Team"
 *******************************
 * PARAMETERS
 * Kd = the amount of uniform diffusion applyied to the skin
 * skincolor, skinmap = the color of the skin. Using a map overrides original skincolor
 * color value.
 * sheencolor, shinmap = the color of the skin at grazing angles. Using a map overrides
 * original sheencolor color value.
 * blemishfreq, blemishthresh,blemhishopac = control the freqency, threshold and opacity
 * of blemishes on the skin. Use to give skin a little variation.
 * blemishmap = Use a grayscale map to control where the belmishes will be more visible
 * xdir, angle = control the direction of the specular highlights.
 * Oily,oilmap = controls how oily the skin looks. Use a grayscale map to contol
 * oiliness.---Note--- teh oiliy parameter is multiplied with the map.
 * xroughness,yroughness = how rough is the specular highlight on x and Y?
 * poresfreq, poresthresh poresdepth = control the frequency, threshold and depth of the
 * pores.
 **************************************
 *
 * You can replace the header functions by using
 * #include "locilum.h" and #include "noises.h".
 * NOTE- you must copy the entire subsurface skin function to your
 * "locIllum.h" file
 *
 * Enjoy!!
 *
 * Modification history:
 *   9/22/02 Tal Lancaster
 *     Renamed RudyCSkin for RMR.
 *     Made changes so would compile.
 *
 **/




 /* NECESARY FUNCTIONS */


 /*---------FROM BMRT's locillum.h-----
 * Greg Ward Larson's anisotropic specular local illumination model.
 * The derivation and formulae can be found in:  Ward, Gregory J.
 * "Measuring and Modeling Anisotropic Reflection," ACM Computer 
 * Graphics 26(2) (Proceedings of Siggraph '92), pp. 265-272, July, 1992.
 */

color
LocIllumWardAnisotropic (normal N;  vector V;
                         vector xdir;  float xroughness, yroughness;)
{
    float sqr (float x) { return x*x; }

    float cos_theta_r = clamp (N.V, 0.0001, 1);
    vector X = xdir / xroughness;
    vector Y = (N ^ xdir) / yroughness;

    color C = 0;
    extern point P;
    illuminance (P, N, PI/2) {
  /* Must declare extern L & Cl because we're in a function */
  extern vector L;  extern color Cl; 
  float nonspec = 0;
  lightsource ("__nonspecular", nonspec);
  if (nonspec < 1) {
      vector LN = normalize (L);
      float cos_theta_i = LN . N;
      if (cos_theta_i > 0.0) {
    vector H = normalize (V + LN);
    float rho = exp (-2 * (sqr(X.H) + sqr(Y.H)) / (1 + H.N))
        / sqrt (cos_theta_i * cos_theta_r);
    C += Cl * ((1-nonspec) * cos_theta_i * rho);
      }
  }
    }
    return C / (4 * xroughness * yroughness);
}    /*---locillum.h ends---*/


/* ------ ADAPTED FROM Mat Pahr's Skin.sl------
 * --- subsurfaceSkin------
 * Surface shader that implements a shading model that should have a visual 
 * appearence generall similar to that of skin.  Based on phenomenological 
 * information about skin reflectance from Hanrahan and Krueger, 
 * "Reflection from layered surfaces due to subsurface scattering", 
 * proceedings of Siggraph 1993.
 */
/* Evaluate the Henyey-Greenstein phase function for two vectors with
   an asymmetry value g.  v1 and v2 should be normalized and g should 
   be in the range (-1, 1).  Negative values of g correspond to more
   back-scattering and positive values correspond to more forward scattering.
*/
float phase(vector v1, v2; float g) {
  float costheta = -v1 . v2;
  return (1. - g*g) / pow(1. + g*g - 2.*g*costheta, 1.5);
}

/* Compute a the single-scattering approximation to scattering from
   a one-dimensional volumetric surface.  Given incident and outgoing
   directions wi and wo, surface normal n, asymmetry value g (see above),
   scattering albedo (between 0 and 1 for physically-valid volumes),
   and the thickness of the volume, use the closed-form single-scattering
   equation to approximate overall scattering.
*/
float singleScatter(vector wi, wo; normal n; float g, albedo, thickness) {
    float win = abs(wi . n);
    float won = abs(wo . n);

    return albedo * phase(wo, wi, g) / (win + won) *
    (1. - exp(-(1/win + 1/won) * thickness));
}

vector efresnel(vector II; normal NN; float eta; output float Kr, Kt;) {
    vector R, T;
    fresnel(II, NN, eta, Kr, Kt, R, T);
    Kr = smoothstep(0., .5, Kr);
    Kt = 1. - Kr;
    return normalize(T);
}

/* Implements overall skin subsurface shading model.  Takes viewing and
   surface normal information, the base color of the skin, a
   color for an oily surface sheen.
*/
color subsurfaceSkin(vector Vf; normal Nn; color skinColor, sheenColor;
                     float eta, thickness) {
  extern point P;
  float Kr, Kt, Kr2, Kt2;
  color C = 0;

  vector T = efresnel(-Vf, Nn, eta, Kr, Kt);
      
  illuminance(P, Nn, PI/2) {
      vector Ln = normalize(L);

      vector H = normalize(Ln + Vf);
      if (H . Nn > 0)
    C += Kr * sheenColor * Cl * (Ln . Nn) * pow(H . Nn, 4.);
      C += Kr * sheenColor * Cl * (Ln . Nn) * .2;

      vector T2 = efresnel(-Ln, Nn, eta, Kr2, Kt2);
      C += skinColor * Cl * (Ln . Nn) * Kt * Kt2 * 
    (singleScatter(T, T2, Nn, .8, .8, thickness) +
     singleScatter(T, T2, Nn, .3, .5, thickness) +
     singleScatter(T, T2, Nn, 0., .4, thickness));
  }
  return C;
}    /* subsurfaceSkin --ends */



#define snoise(p) (2 * (float noise(p)) - 1)

/*
 *shader beggins here.
 */

surface RudyCSkin(
  float Kd = .1;
  color skincolor = color (0.7, 0.56, 0.52);
  string skinmap = "";
  color sheenColor = 1.;
  string sheenmap = "";
  float blemishfreq  = 12,
        belmishthresh = 4,
        blemishopac = 1;
  string blemishmap = "";
  float eta = 1.01,
        thickness = 1;
  varying vector xdir = dPdu;
  float angle = 180;
  float oily = 1.5;
  string oilmap = "";
  float xroughness = .3,
        yroughness = .5;
  float poresfreq = 60,
        poresthresh = 3,
        poresdepth = -0.001;)


{
  /* initialize local variables*/

  normal Nf, NN;
  vector Vf = -normalize(I);
  color sc , lc;
  float lo;
  point PP;
  float turb, f;
  float maxfreq = 8;

  color Cskin = skincolor;
  color Csheen = sheenColor;
  float oilVal = oily;


  /*--- layer 1 - add pores to the skin*/

  PP = transform ("shader",P) * poresfreq;
  turb = 0;
  for (f = 1; f< maxfreq; f *= 2)
  turb += abs(snoise(PP * f)) / f;

  turb = pow(turb,poresthresh) * poresdepth;


  /* displace normals*/
  NN = calculatenormal(P + turb * normalize(N));
  Nf = faceforward(normalize(NN),I);

  /* layer 1 .- apply a subsurface scattered BRDF to the surface.
   * you can use an image map for the color of the skin and the sheen.
   * If the map is not provided use defined colors*/

  if (skinmap != "")
      Cskin = color texture(skinmap);

  if ( sheenmap != "")
      Csheen = color texture(sheenmap);

  sc = Cskin;

  /* layer 2 - create small skin blemishes over the skin. Use a map to control
   * where the blemishes are visible */

  PP = transform ("shader",P) * blemishfreq;
  turb = 0;
  for (f = 1; f< maxfreq; f *= 2)
  turb += abs(snoise(PP * f)) / f;

  turb = pow(turb, belmishthresh );

  color red01 = Cskin + 0.05,
        red02 = red01 - 0.05,
        darkred01 = Cskin - 0.05,
        darkred02 = darkred01 - 0.05;


  color blemishcol = color spline ( turb,
                                    red01,
                                    red02,
                                    darkred01,
                                    red01,
                                    darkred01,
                                    red02,
                                    darkred01,
                                    darkred02,
                                    darkred01,
                                    darkred02,
                                    red01);

  lc =  subsurfaceSkin(Vf, Nf, blemishcol, Csheen, 1/eta, thickness);
  lo = 1 * (blemishopac) ;

  if (blemishmap != "")
      lo *= float texture (blemishmap);

  sc = mix (sc,lc,lo);

  /* --Layer 3-- apply an Anisotropic BRDF to simulate the oil layer that lies
  * outside the skin. Oilyness is controlled by the oily, xrougness and yroughness.
  * */

  if (oily > 0.01){
  vector anisoDir = xdir;
  if (angle !=0)
  {
    matrix rot = rotate(matrix 1, radians(angle),Nf);
    anisoDir = vtransform(rot,anisoDir);
    }
  lc = LocIllumWardAnisotropic(Nf,Vf,anisoDir,xroughness,yroughness);
 
  if (oilmap != "")
      oilVal *= float texture(oilmap);

  sc += (lc * (oilVal * 0.1) );
  }



  /* output*/

  Oi = Os;
  Ci = sc + Kd * diffuse(Nf) ;
}




