/****************************************************************************
 * superplank.sl -- another surface shader for wood planks.
 *
 * Description:
 *   Makes texture of wooden planks in s-t space.  This wood looks rather
 *   like varnished oak planks, with staggered planks, rings and grain,
 *   reflections (either traced or via reflection map), and bumps.
 *   It can make the plank pattern as either straight staggered planks
 *   or in a parquet pattern.
 *
 * Parameters:
 *   Ka, Kd, Ks, specularcolor, roughness - work just like the plastic shader
 *   Kr, eta - reflection amount and index of refraction (for fresnel)
 *   Ktrace, Krefl, reflmap - const for trace, for refl map, filename
 *   txtscale - overall scaling factor for the texture
 *   Km - overall scaling factor for bumpiness.
 *   lightwood - wood-like color from which the various shades are derived.
 *   plankwidth - width of each plank (in terms of s/t)
 *   planklength - length of each plank (in terms of s/t)
 *   groovewidth - width of the grooves between the planks (in terms of s/t)
 *   groovedepth - depth of the groove indentations (in shader space units)
 *   groovecolor - the color of the "grooves" between the planks
 *   plankpattern - plank orientation pattern  1=straight, 2=parquet
 *   plankspertile - for parquet, number of sub-planks
 *   plankstagger - for staggered, how much are the rows of planks staggered
 *   plankvary - controls how much wood color varies from plank to plank
 *   ringscale - scaling for the ring spacing
 *   ringwidth - relative width of the dark ring parts
 *   wavy - relative wavyness of the ring pattern
 *   grainy - relative graininess (0 = no fine grain)
 *   grainscale - scaling for the fine grain
 *   graindepth - depth of grain and ring grooves
 *   varnishbumpfreq - frequency of bumps in the varnish coating
 *   varnishbumpamp - height of bumps in the varnish coating
 *
 * Antialiasing: this shader does a pretty good job of antialiasing itself,
 *   even with low sampling densities.
 *
 * Author: written by Larry Gritz
 *          current contact address: gritzl@acm.org
 *
 * $Revision: 1.6 $     $Date: 1997-11-23 17:36:25-08 $
 *
 ****************************************************************************/



#include "noises.h"
#include "patterns.h"


#define MINFILTERWIDTH 1.0e-7

/* Estimate the filter width -- i.e. the change from pixel to pixel -- of
 * float and point values.
 */
#define filterwidth(x)  (abs(Du(x)*du) + abs(Dv(x)*dv))




surface
superplank (/* Parameters: */
        /* BRDF */
        float Ka = 1, Kd = 1;         /* Overall ambient & diffuse response */
	float Ks = .75, roughness = .02; /* Spec highlight control */
	color specularcolor = 1;      /* Spec reflection color */
	float Kr = 1, eta = 1.5;      /* Mirror refl & index of refr */
	float Ktrace = 1, Krefl = 0;  /* trace and reflection map */
	string reflmap = "";
	/* Overall texturing control */
	float txtscale = 1;           /* Overall scaling factor */
	float Km = 1;                 /* Overall bumpiness factor */
	color lightwood = color (.5, .2, .067);     /* light wood color */
	/* Plank layout */
	float plankpattern = 1;       /* 1=straight, 2=parquet */
	float plankspertile = 4;      /* For parquet */
	float plankwidth = .05;       /* Width of a plank */
	float planklength = .75;      /* Length of a plank */
	float groovewidth = 0.001;    /* Width of the grooves between planks */
	float groovedepth = .0004;    /* Depth of the groove */
	color groovecolor = color (.01, .003, .001);
	float plankstagger = 1;       /* How much should rows be staggered */
	float plankvary = 0.8;        /* Wood color variation plank-to-plank */
	/* Wood appearance */
	float ringscale = 25;         /* Larger makes more, thinner rings */
	float ringwidth = 1;          /* Relative ring width */
	float wavy = 0.08;            /* Larger makes rings more wavy */
	float grainy = 1;             /* Relative graininess */
	float grainscale = 60;        /* Larger makes smaller "grains" */
	float graindepth = 0.0001;    /* Depth of divots where grains are */
	float varnishbumpfreq = 30;   /* Bump freq of the varnish */
	float varnishbumpamp = 0.0003; /* How big are the varnish bumps? */
    )
{
    /* mapping coordinates */
    float ss, tt;
    float grain_s, grain_t, ring_s, ring_t, plank_s, plank_t;
    /* antialiasing */
    float swidth, twidth, fwidth;
    float sw, tw, overallscale;
    /* Planks & grooves */
    uniform float PGWIDTH, PGHEIGHT, GWF, GHF;
    float whichrow, whichplank; /* Index for each row & plank within row */
    float w, h;                 /* temporaries */
    float groovy;               /* 0 in groove, 1 in woody part */
    /* Wood appearance */
    color Ct, woodcolor;        
    float r2;
    float fade, ttt;
    float ring;                 /* 1 in a ring darkening, 0 where not */
    float grain;                /* 1 inside a grain bit, 0 elsewhere */
    /* Illumination model */
    float  adjustedKs;
    vector IN;                  /* normalized I vector */
    normal NN;                  /* normalized N for displacing */
    normal Nf;                  /* forward facing, normalized normal */
    vector R, T_dummy;          /* Refl (and tummy transmit) from fresnel */
    float fresnelKr, fresnelKt; /* Fresnel reflection coefficients */
    float shadlen;              /* length of a unit of shader space */
    float disp;                 /* accumulate displacement here */
    point Pndc;                 /* NDC coordinate of P */
    vector V;
    color Cspec;
    float nonspec;
    float tmp;

    /*
     * 1. Setup.
     *
     * Determine the basic mapping, filter sizes for antialiasing, other
     * values used throughout the shader.
     */
  
    /* First, determine the basic mapping */
    ss = s * txtscale;
    tt = t * txtscale;
    /* Compute the basic filter size for antialiasing */
    swidth = max (filterwidth(ss), MINFILTERWIDTH);
    twidth = max (filterwidth(tt), MINFILTERWIDTH);
    fwidth = max (swidth,twidth);

    /* How much current space corresponds to a unit of s or t?
     * We will use this later to help scale the displacement, this making
     * the bumps scale relative to the overall pattern, rather than being
     * strictly tied to the scale of the object.
     */
    overallscale = (length(Deriv(P,ss)));


    /*
     * 2. Plank pattern.
     *
     * Determine which row and plank we're on, and come up with an
     * antialiased term for whether we're in or out of a groove.
     */
    if (plankpattern == 1) {
	/* Straight, staggered planks */
	PGWIDTH = plankwidth + groovewidth;
	PGHEIGHT = planklength + groovewidth;
	plank_s = ss / PGWIDTH;
	whichrow = floor (plank_s);
	/* Jiggle each row */
	plank_t = tt / PGHEIGHT +  20 * plankstagger * cellnoise(whichrow);
	whichplank = floor(plank_t);
    } else {
	/* Parquet pattern */
	PGWIDTH = plankwidth + groovewidth;
	PGHEIGHT = PGWIDTH * plankspertile;
	plank_s = ss / PGWIDTH;
	whichrow = floor (plank_s);
	plank_t = tt / PGHEIGHT;
	whichplank = floor(plank_t);
	if (mod ((whichrow/plankspertile) + whichplank, 2) >= 1) { 
	    plank_s = tt / PGWIDTH;
	    plank_t = ss / PGHEIGHT;
	    whichrow = floor (plank_s);
	    whichplank = floor(plank_t);
	    tmp = ss;  ss = tt;  tt = tmp;
	    tmp = swidth;  swidth = twidth;  twidth = tmp;
	} 
    }
    /* Now whichplank is a unique integer index for each plank */

    /* Figure out where the grooves are.  The value groovy is 0 where there
     * are grooves, 1 where the wood grain is visible.  Do some simple
     * antialiasing by trying to box filter the edges of the grooves.
     */

    /* compute half width & length of groove as fraction of plank size */
    GWF = groovewidth*0.5/PGWIDTH;
    GHF = groovewidth*0.5/PGHEIGHT;
    sw = swidth / PGWIDTH;
    tw = twidth / PGHEIGHT;
    if (sw >= 1)
	 w = 1-2*GWF;   /* Filter width is wider than the plank itself */
    else w = filteredpulse (whichrow+GWF, whichrow+1-GWF, plank_s, sw);
    if (tw >= 1)
	 h = 1 - 2*GHF;  /* Filter width is longer than the plank itself */
    else h = filteredpulse (whichplank+GHF, whichplank+1-GHF, plank_t, tw);
    groovy = w*h;

    /*
     * 3. Ring and grain patterns, color and specularity adjustment.
     *
     * The wood has rings at one scale, grain at a finer scale.  They
     * interact subtly.
     */

    /* Compute the filter width first, so we don't bother with more if
     * the rings are too small to see.
     */
    fwidth = max (swidth*ringscale, twidth*ringscale);
    fade = smoothstep (.75, 4, fwidth);
    if (fade < 0.999) {
	ring_s = ss * ringscale;
	ring_t = tt * ringscale;
	ttt = ring_t + whichplank*28.38 + wavy * noise (8*ring_s, ring_t);
	ring = ringscale * noise (ring_s-whichplank*4.18, ttt/20);
	ring -= floor (ring);
	ring = 0.3 + 0.7 * smoothstep(.55-.35*ringwidth, 0.55, ring) *
	                   (1 - smoothstep(0.75, 0.8, ring));
	ring = (1-fade)*ring + 0.65*fade;

	/* Grain pattern */
	fwidth = max (swidth*grainscale, twidth*grainscale);
	fade = smoothstep (.75, 4, fwidth);
	if (fade < 0.999) {
	    grain_s = ss * grainscale;
	    grain_t = tt * grainscale;
	    r2 = 1.3 - noise (12*grain_s, grain_t);
	    r2 = grainy * r2*r2 + (1-grainy);
	    grain = (1-fade)*r2 + (0.75*fade);
        }
	else 
	    grain = 0.75;
    }
    else {
	ring = 0.4875;
	grain = 0.75;
    }
    grain *= (.85 + .15*ring);

    /* Start with the light wood color */
    woodcolor = lightwood;
    /* Add some plank-to-plank variation in overall color */
    woodcolor *= 1 - plankvary/2 
	           + plankvary * float cellnoise (whichplank,whichrow);
    /* Darken the wood according to the ring and grain patterns */
    woodcolor *= (1 - 0.25*ring) * (1-.5*grain);
    /* Combine the rings, grain, plank variation into one surface color */
    Ct = mix (groovecolor, woodcolor, groovy);
    /* Less specular in the grooves, more specular in the dark wood. */
    adjustedKs = Ks * (1 + .2*ring) * (1 + .3*grain) * groovy;


    /*
     * 4. Bumps
     *
     * We do some bump mapping to make the grooves and grain depressed,
     * and add some general lumpiness to the varnish layer.
     */
    if (Km > 0) {
	/* Grooves */
	disp = -groovedepth * (1-groovy);
	/* Random bumps on the varnish */
	if (varnishbumpamp > 0)
	    disp += varnishbumpamp * 
		         (2*noise(varnishbumpfreq*ss,varnishbumpfreq*tt) - 1);
	/* Depressions due to grain & rings */
	disp -= graindepth * (.75*grain + ring);
	/* Find out how long a unit of shader space is, in current units */
	NN = normalize(N);
	shadlen = overallscale/length (ntransform("shader",NN));

	/* Recalculate N */
	N = calculatenormal (P + (Km * shadlen * disp) * NN); 
    }

    /*
     * 5. BRDF
     *
     * The wood itself behaves like plastic, the varnish overtop reflects
     * using the fresnel formula (grazing angles reflect like mirrors).
     * Have some subtle interaction between grain and specularity.
     */

    IN = normalize(I);
    V = -IN;
    Nf = faceforward (normalize(N),I);
    fresnel (IN, Nf, 1/eta, fresnelKr, fresnelKt, R, T_dummy);
    fresnelKr *= Kr;
    Cspec = 0;
    illuminance (P, Nf, PI/2) {
	nonspec = 0;
	lightsource ("__nonspecular", nonspec);
	if (nonspec < 1) {
	    vector H = normalize(normalize(L)+V);
	    Cspec += Cl * ((1-nonspec) * smoothstep (.6, .85, pow(max(0,Nf.H), 8/roughness)));
	}
    }
    Ci = (1-fresnelKr) * (Ct * (Ka*ambient() + Kd*diffuse(Nf)))
	   + adjustedKs * (specularcolor * Cspec);

    /* Only compute reflections if Kr > 0 */
    if (fresnelKr > 0.001) {
	if (Krefl > 0 && reflmap != "") {
	    Pndc = transform ("NDC", P);
	    Ci += fresnelKr * specularcolor * 
		    texture (reflmap, xcomp(Pndc), 1-ycomp(Pndc));
	}
	if (Ktrace > 0)
	    Ci += Ktrace * fresnelKr * specularcolor * trace (P, R);
    }

    /* Scale by opacity */
    Oi = Os;
    Ci *= Oi;
}