/*

	Round Voronoi Border Refinement
	-------------------------------

	Testing Tomkh's refinements on IQ's straight-edged Voronoi formula to produce detailed,
	rounded cell borders on hexagonal Voronoi - Basically, just another excuse to code something
	shiny. :)

	Thanks to Tomkh, rounded Voronoi borders with continuous isolines are possible - or close enough
	to enable nice equidistant contour lines that line up at the edges. His solution was based on
	previous work by IQ, Abje, Dr2 and others. My contribution was to sit on the sidelines and wait
	for someone else to do the work. :D

	Anyway, this is a simple bump mapped example. The cliche faux chromatic look is achieved by
	lighting the surface with a red-tinged light and a blue-tinged light. The individual specular
	components have more prominent red and blue coloring. For anyone wondering, you can usually fake
	a metallic look by increasing the intensity and ramping up the diffuse lighting power. Metallic
	texturing can help too.

	I'm not entirely sure why the hardened metal substance is moving so fluidly, but I'm pretty sure
	it can all be explained with Star Trek physics. :)

	Based on:

	A Voronoi Edge distance function that is continuous across
	the whole domain and smooth at the non-zero distance.
	Rounder Voronoi Edge Distance - tomkh
	https://www.shadertoy.com/view/ld3yRn


*/

// Very unimaginative cell color options. :)
// Grey: 0, Brown: 1, Blue: 2., 3: Green.
#define CELL_COLOR 0

// Scene object ID, and individual cell IDs. Used for coloring.
float objID; // The rounded web lattice, or the individual Voronoi cells.
//vec2 cellID; // Individual Voronoi cell IDs.


// Standard 2D rotation formula.
mat2 r2(in float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }

/*
// IQ's esmooth minimum function.
float smin( float a, float b, float k ){

    float h = clamp(.5 + .5*(b - a)/k, 0., 1.);
    return mix(b, a, h) - k*h*(1. - h);
}


// IQ's exponential-based smooth minimum function. Unlike the polynomial-based
// smooth minimum, this one is associative and commutative.
float sminExp(float a, float b, float k)
{
    float res = exp(-k*a) + exp(-k*b);
    return -log(res)/k;
}
*/

// Commutative smooth minimum function. Provided by Tomkh and
// taken from Alex Evans's (aka Statix) talk:
// http://media.lolrus.mediamolecule.com/AlexEvans_SIGGRAPH-2015.pdf
// Credited to Dave Smith @media molecule.
float smin2(float a, float b, float r)
{
   float f = max(0., 1. - abs(b - a)/r);
   return min(a, b) - r*.25*f*f;
}

// vec2 to vec2 hash.
vec2 hash22H(vec2 p) {

    // Faster, but doesn't disperse things quite as nicely. However, when framerate
    // is an issue, and it often is, this is a good one to use. Basically, it's a tweaked
    // amalgamation I put together, based on a couple of other random algorithms I've
    // seen around... so use it with caution, because I make a tonne of mistakes. :)
    float n = sin(dot(p, vec2(41, 289)));
    //return fract(vec2(262144, 32768)*n)*.8660254;

    // Animated.
    p = fract(vec2(262144, 32768)*n);
    // Slightly lower number, so as not to overshoot the hexagonal bounds.
    return sin( p*6.2831853 + iTime )*.3660254 + .5;

}


// Converting to the hexagonal grid.
vec2 pixToHex(vec2 p){

    return floor(vec2(p.x + .57735*p.y, 1.1547*p.y) + 1./3.);
}


// Randomized hexagonal offset point.
vec2 hexPt(vec2 p) {

    // The offset value is restricted to the radius of the incircle of the
    // hexagon, or apothem as it's technically known.
    return vec2(p.x - p.y*.5, .866025*p.y) + (hash22H(p) - .5)*.866025/2.;

}


// This is a hexagonal variation on a regular 2-pass Voronoi traversal that produces
// a Voronoi pattern based on the interior cell point to the nearest cell edge (as opposed
// to the nearest offset point). It uses elements from an old hexagonal Voronoi example,
// and is based on IQ's original example. It was inspired by Dr2's hexagonal examples.
// The links are below:
//
// On a side note, I have no idea whether a faster solution is possible, but when I
// have time, I'm going to attemp to find one anyway.
//
// Voronoi distances - iq
// https://www.shadertoy.com/view/ldl3W8
//
// Here's IQ's well written article that describes the process in more detail.
// https://iquilezles.org/articles/voronoilines
//
// Desert Town - dr2
// https://www.shadertoy.com/view/XslBDl
vec3 Voronoi(vec2 p){

    // Convert to the hexagonal grid.
    vec2 pH = pixToHex(p); // Map the pixel to the hex grid.

    // There'd be a heap of ways to get rid of this array and speed things up. The
    // most obvious, would be unrolling the loops, but there'd be more elegant ways.
    // Either way, I've left it this way just to make the code easier to read.

    // Hexagonal grid offsets. "vec2(0)" represents the center, and the other offsets effectively circle it.
    // Thanks, Abje. Hopefully, the compiler will know what to do with this. :)
	const vec2 hp[7] = vec2[7](vec2(-1), vec2(0, -1), vec2(-1, 0), vec2(0), vec2(1), vec2(1, 0), vec2(0, 1));


    // Voronoi cell ID containing the minimum offset point distance. The nearest
    // edge will be one of the cell's edges.
    vec2 minCellID = vec2(0); // Redundant initialization, but I've done it anyway.

    // As IQ has commented, this is a regular Voronoi pass, so it should be
    // pretty self explanatory.
    //
    // First pass: Regular Voronoi.
	vec2 mo, o;

    // Minimum distance, "smooth" distance to the nearest cell edge, regular
    // distance to the nearest cell edge, and a line distance place holder.
    float md = 8., lMd = 8., lMd2 = 8., lnDist, d;

    for (int i=0; i<7; i++){

        // Determine the offset hexagonal point.
        vec2 h = hexPt(pH + hp[i]) - p;
        // Determine the distance metric to the point.
    	d = dot(h, h);
    	if( d<md ){ // Perform updates, if applicable.

            md = d;  // Update the minimum distance.
            // Keep note of the position of the nearest cell point - with respect
            // to "p," of course. It will be used in the second pass.
            mo = h;
            //cellID = h + p; // For cell coloring.
            minCellID = hp[i]; // Record the minimum distance cell ID.
        }
    }

    // Second pass: Point to nearest cell-edge distance.
    //
    // With the ID of the cell containing the closest point, do a sweep of all the
    // surrounding cell edges to determine the closest one. You do that by applying
    // a standard distance to a line formula.
    for (int i=0; i<7; i++){

         // Determine the offset hexagonal point in relation to the minimum cell offset.
        vec2 h = hexPt(pH + hp[i] + minCellID) - p - mo; // Note the "-mo" to save some operations.

        // Skip the same cell.
        if(dot(h, h)>.00001){

            // This tiny line is the crux of the whole example, believe it or not. Basically, it's
            // a bit of simple trigonometry to determine the distance from the cell point to the
            // cell border line. See IQ's article (link above) for a visual representation.
            lnDist = dot(mo + h*.5, normalize(h));

            // Abje's addition. Border distance using a smooth minimum. Insightful, and simple.
            //
            // On a side note, IQ reminded me that the order in which the polynomial-based smooth
            // minimum is applied effects the result. However, the exponentional-based smooth
            // minimum is associative and commutative, so is more correct. In this particular case,
            // the effects appear to be negligible, so I'm sticking with the cheaper polynomial-based
            // smooth minimum, but it's something you should keep in mind. By the way, feel free to
            // uncomment the exponential one and try it out to see if you notice a difference.
            //
            // Polynomial-based smooth minimum. The last factor controls the roundness of the
            // edge joins. Zero gives you sharp joins, and something like ".25" will produce a
            // more rounded look. Tomkh noticed that a variable smoothing factor - based on the
            // line distance - produces continuous isolines.
            lMd = smin2(lMd, lnDist, (lnDist*.5 + .5)*.15);
            //lMd = smin2(lMd, lnDist, .1);
            // Exponential-based smooth minimum.
            //lMd = sminExp(lMd, lnDist, 20.);
            //lMd = sminExp(lMd, lnDist, (lnDist*.5 + .5)*50.);

            // Minimum regular straight-edged border distance. If you only used this distance,
            // the web lattice would have sharp edges.
            lMd2 = min(lMd2, lnDist);

        }

    }

    // Return the smoothed and unsmoothed distance. I think they need capping at zero... but I'm not
    // positive. Although not used here, the standard minimum point distance is returned also.
    return max(vec3(lMd, lMd2, md), 0.);


}


// The bump function. Used for bump mapping, coloring and shading.
float bumpFunc(vec2 p){

	// Voronoi vector. It holds the rounded edge value, straight edge value,
    // and a dummy value.
    vec3 v = Voronoi(p);

    float c = v.x; // Rounded edge value.


    float ew = .07; // Border threshold value. Bigger numbers mean thicker borders.

    // If the Voronoi value is under the threshold, produce a web like contoured border.
    if(c<ew){

        objID = 1.; // Voronoi web border ID.

        c = abs(c - ew)/ew; // Normalize the domain to a range of zero to one.

        // Add the contoured pattern to the web border.
        c = smoothstep(0., .25, c)/4. + clamp(-cos(c*6.283*1.5) - .5, 0., 1.);

    }
    else { // Over the threshold? Use the regular Voronoi cell value.

        objID = 0.;
        c = mix(v.x,  v.y, .75); // A mixture of rounded and straight edge values.
        c = (c - ew)/(1. - ew); // Normalize the domain to a range of zero to one.
        c = clamp(c + cos(c*6.283*24.)*.002, 0., 1.); // Add some ridges.
    }

    return c; // Return the object (bordered Voronoi) value.

}


void mainImage( out vec4 fragColor, in vec2 fragCoord ){


    //SETUP.
    //
    // Aspect correct screen coordinates.
	vec2 uv = (fragCoord - iResolution.xy*.5)/min(iResolution.y, 800.);

    // Very subtle screen warping, for that bulbous fish-eye look.
    vec2 aspect = vec2(iResolution.y/iResolution.x, 1);
    uv *= 1. + dot(uv*aspect, uv*aspect)*.05;

    // Unit direction ray.
    vec3 r = normalize(vec3(uv.xy, 1.));

    // Scaling and movement.
    vec2 p = uv*3.5 + vec2(0, iTime*.5);

    // The webbed Voronoi value.
    float c = bumpFunc(p);

    // Saving the ID.
    float svObjID = objID;

    // 3D screen hit point. Just a flat plane at the zero point on the Z-axix.
    vec3 sp = vec3(p, 0.);

    // Two lights, set back from the plane, and rotating about the XY plane on
    // opposite sides of an ellipse, or something to that effect.
    vec3 lp = sp + vec3(-1.3*sin(iTime/2.), .8*cos(iTime/2.), -.5);
    vec3 lp2 = sp + vec3(1.3*sin(iTime/2.), -.8*cos(iTime/2.), -.5);

    // Fake hit-point height value. Normally, you'd cast a ray to the hit point, but
    // since it's a simple bump mapped example, we're estimating it.
    sp.z -= c*.1;


    // BUMP MAPPING AND EDGING. Pretty standard stuff.
    //
    vec2 e = vec2(8./iResolution.y, 0); // Sample spred.
    float bf = .4; // Bump factor.

    // If we hit the webbing section, reduce the sample spread. It's a bit of fake
    // trickery to reduce artifacts on the webbing portion.
    if (svObjID>.5) { e.x = 2./iResolution.y; }

    float fx = (bumpFunc(p - e) - bumpFunc(p + e)); // Nearby horizontal samples.
    float fy = (bumpFunc(p - e.yx) - bumpFunc(p + e.yx)); // Nearby vertical samples.
	vec3 n = normalize(vec3(fx, fy, -e.x/bf)); // Bumped normal.

    float edge = abs(c*2. - fx) + abs(c*2. - fy); // Edge value.


    // TEXTURE AND COLORING.
    //
    // Texture sample with fake height information added.
    vec3 tx = texture(iChannel0, (p + n.xy*.125)*.25).xyz; tx *= tx; // sRGB to linear.
    tx = smoothstep(0., .5, tx); // Accentuating the color a bit.

    // Object color. Initialize to the texture value.
    vec3 oCol = tx;


    if(svObjID>.5){ // The webbing portion.

        //oCol *= vec3(1.2, .8, .4); // Uncomment for gold webbing.
        //oCol *= vec3(1.4, 1, .7);
        oCol *= .9;

    }
    #if CELL_COLOR > 0
    else { // The cell portion. Do what you want here.

	    // Uncomment for colored cells, etc.
        #if CELL_COLOR == 1
        oCol *= vec3(1.4, 1, .7);
        #elif CELL_COLOR == 2
        oCol *= vec3(.9, 1.1, 1.3);
        #else
        oCol *= vec3(1.1, 1.4, .7);
        #endif

    }
	#endif


    //oCol *= vec3(1.2, 1, .84); // Warmer coloring.
    //oCol *= vec3(.9, 1.1, 1.3); // Cooler coloring.


	// LIGHTING.
    //
    float lDist = length(lp - sp); // Light distance one.
    float atten = 1./(1. + lDist*lDist*.5); // Light one attenuation.
    vec3 l = (lp - sp)/max(lDist, .001); // Light one direction (normalized).
	float diff = max(max(dot(l, n), 0.), 0.); // Diffuse value one.
    float spec = pow(max(dot(reflect(l, n), r), 0.), 64.); // Specular value one.


    float lDist2 = length(lp2 - sp); // Light distance two.
    float atten2 = 1./(1. + lDist2*lDist2*.5); // Light two attenuation.
    vec3 l2 = (lp2 - sp)/max(lDist2, .001); // Light two direction (normalized).
	float diff2 = max(max(dot(l2, n), 0.), 0.); // Diffuse value two.
    float spec2 = pow(max(dot(reflect(l2, n), r), 0.), 64.); // Specular value twp.


    // Ramping up the power and increasing the intensity of the diffuse values to
    // give more of a metallic look.
    diff = pow(diff, 4.)*2.;
    diff2 = pow(diff2, 4.)*2.;


 	// Combining the texture and lighting information above.

    // Light one.
    vec3 col = oCol*(diff*vec3(.5, .7, 1) + .25 + vec3(.25, .5, 1)*spec*32.)*atten*.5;

    // Adding light two.
    col += oCol*(diff2*vec3(1, .7, .5) + .25 + vec3(1, .3, .1)*spec2*32.)*atten2*.5;

    // Apply the edging. This provides fake AO, depth information, etc. Comment it out, and
    // the example becomes very 2-dimensional.
    col *= edge;

    // POSTPROCESSING, SCREEN PRESENTATION, ETC.
    //
    //col *= vec3(1.5, 1., .6); // Warmer coloring.
    //col *= vec3(.9, 1.2, 1.4); // Cooler coloring.

	// Subtle vignette.
    vec2 u = fragCoord/iResolution.xy;
    col *= pow(16.*u.x*u.y*(1. - u.x)*(1. - u.y) , .125);
    // Colored variation.
    //col = mix(pow(min(vec3(1.5, 1, 1)*col, 1.), vec3(1, 3, 16)), col,
            //pow(16.*u.x*u.y*(1. - u.x)*(1. - u.y) , .125)*.75 + .25);

    // Rough gamma correction.
    fragColor = vec4(sqrt(max(col, 0.)), 1);

}
