// This is a variation on Remnant X by David Hoskins.
// Original: https://www.shadertoy.com/view/4sjSW1
// 
// Frankeshaders Lair (https://www.shadertoy.com/view/ftVXWw)
// Changes by Synthesoft: 
//     I took core + cinematics from Remnant X and...
//      Added noise to surfaces
//      Swapped out the original map() for two new ones which I mix together in Map() (Weight: fbvar2 & Mode: fbvar5) 
//         See MapA()/MapB() below for info on sources
//         I was curious what it would look like to blend maps and was really pleased with the results
//      Changed the Dizzolve sphere (fbvar3) 0.0-0.5 solid, 0.5..1.0 fuzz 
//      Parameterized FOV (fbvar4)   
//      Replaced original color handling.  Made the colors annoyingly garish (it's a gift.  ...and a curse.)
//      Added mix parameters
//      Cleaned up vars
//      Added some parmsets that produce interesting results 
//      Fixed lighting issues
//      Added light originating from camera position

//#define FLASHBACK
#ifdef FLASHBACK
// tl;dr - These are not the variables you're looking for (/gestures with hand)
//
// These vars are used by a Windows desktop application I'm writing called Flashback.  It 
// has sliders that adjust the values on-the-fly.  I've replaced them below with #defines for 
// some alternate configurations ( USE_PARMSET_[n] ) for ShaderToy
//
//#define FORCE_MISS_TO_BLACK
uniform float iTime;
uniform vec3 iResolution;
uniform vec4 iMouse;

uniform float fbvar1;  // fract iterations -- changes here are interesting
uniform float fbvar2;  // Map weighting (when fbvar5 set to mix()) 
uniform float fbvar3;  // Dizzolve sphere: 0.0-0.5 solid, 0.5..1.0 fuzz 
uniform float fbvar4;  // FOV
uniform float fbvar5;  // less than 0.5 = mix(), greater than 0.5 = min()    (Merge/mix vs Union)
uniform float fbvar6;  // fractal calc tweaks
uniform float fbvar7;  // brightness: light originating from camera position
uniform float fbvar8;  // brightness: light originating from forward probe
uniform float fbvar9;  // brightness: sun
#else // not Flashback

#define USE_PARMSET_1  // <-- Use this to change between parameter sets

#ifdef USE_PARMSET_1  
#define fbvar1 	0.467
#define fbvar2 	0.399
#define fbvar3 	0.822
#define fbvar4 	0.128
#define fbvar5 	0.455
#define fbvar6 	0.115
#define fbvar7 	0.035
#define fbvar8 	0.060
#define fbvar9 	0.075
#endif

#ifdef USE_PARMSET_2
#define fbvar1 	0.536
#define fbvar2 	0.925
#define fbvar3 	0.888
#define fbvar4 	0.358
#define fbvar5 	0.461
#define fbvar6 	0.115
#define fbvar7 	0.027
#define fbvar8 	0.065
#define fbvar9 	0.080
#endif


#endif

// #define STEREO

vec3 sunDir = normalize(vec3(  0.35, 0.1,  0.3 ));

#define pi 3.14159
#define SCALE 2.8
#define MINRAD2 .25
float minRad2 = clamp(MINRAD2, 1.0e-9, 1.0);
#define scale (vec4(SCALE, SCALE, SCALE, abs(SCALE)) / minRad2)
vec3 col1,col2,col3;
float gTime;


//----------------------------------------------------------------------------------------

#define CSize vec3(1., 1.7, 1.)

// From Shane: https://www.shadertoy.com/view/lstGRB
float Noise(vec3 p)
{
	const vec3 s = vec3(7, 157, 113);
	vec3 ip = floor(p);
    vec4 h = vec4(0., s.yz, s.y + s.z) + dot(ip, s);
	p -= ip; 
    p = p*p*(3. - 2.*p);
    h = mix(fract(sin(h)*43758.5453), fract(sin(h + s.x)*43758.5453), p.x);
    h.xy = mix(h.xz, h.yw, p.y);
    return mix(h.x, h.y, p.z); 
}

//
// Regular Menger Sponge formula. Very simple, but if you're not sure, look it
// up on Wikipedia, and look up a Void Cube image.
//
// Pulled from source: https://www.shadertoy.com/view/ldyGWm
//
float MapB(vec3 q)
{
    
    vec3 p;
	// Scale factor, and distance.
    float s = 3., d = 0.;
    
    for(int i=0; i<int(fbvar1*6.+2.0); i++){
 		// Repeat space.
        p = abs(fract(q/s)*s - s/2.); // Equivalent to: p = abs(mod(q, s) - s/2.);
		// Repeat Void Cubes. Cubes with a cross taken out.
 		d = max(d, min(max(p.x, p.y), min(max(p.y, p.z), max(p.x, p.z))) - s/3.);
    	s /= 3.; // Divide space (each dimension) by 3.
    }
 
 	return d;    
}

// IFS, or KIFS in particular. The following explains the process in more detail.
//
// Kaleidoscopic (escape time) IFS - Knighty
// http://www.fractalforums.com/ifs-iterated-function-systems/kaleidoscopic-(escape-time-ifs)/
//
// Here's a quick, rushed expanation. Take an input point, and repeat it. After that, rotate
// it, fold it, stretch and translate it about an offset point. Form an object with it and compare
// it to the object formed in the repeat layer. Repeat ad infinitum...
//
// OK, that was a little vague, but it really is a pretty simple process. Playing around with the 
// code will give it more meaning. Change the rotation angles, iteration number, comment some
// things out, etc. I'd also recommend reading Syntopia's blog. He explains things really well...
// and there's so many pretty pictures. :)
//
// Syntopia - http://blog.hvidtfeldts.net/
//
// Pulled from source: https://www.shadertoy.com/view/XsKXzc
//
float MapA(vec3 p){
    
    // I'm never sure whether I should take constant stuff like the following outside the function, 
    // or not. My 1990s CPU brain tells me outside, but it doesn't seem to make a difference to frame 
    // rate in this environment one way or the other, so I'll keep it where it looks tidy. If a GPU
    // architecture\compiler expert is out there, feel free to let me know.
    
    const vec3 offs = vec3(1, .75, .5); // Offset point.
    const vec2 a = sin(vec2(0, 1.57079632) + 1.57/2.);
    const mat2 m = mat2(a.y, -a.x, a);
    const vec2 a2 = sin(vec2(0, 1.57079632) + 1.57/4.);
    const mat2 m2 = mat2(a2.y, -a2.x, a2);
    
    const float s = 5.; // Scale factor.
    
    const float sz = .0355; // Box size.
    #ifdef WIREFRAME
    const float ew = .015; // Wireframe box edge width.
    #endif
    
    float d = 1e5; // Distance.
    
    
    p  = abs(fract(p*.5)*2. - 1.); // Standard spacial repetition.
     
    
    float amp = 1./s; // Analogous to layer amplitude.
    
   
    // With only two iterations, you could unroll this for more speed,
    // but I'm leaving it this way for anyone who wants to try more
    // iterations.
    for(int i=0; i<int(1.+fbvar1*4.); i++){
        
        // Rotating.
        p.xy = m*p.xy;
        p.yz = m2*p.yz;
        
        p = abs(p);
        //p = sqrt(p*p + .03);
        //p = smin(p, -p, -.5); // Etc.
        
  		// Folding about tetrahedral planes of symmetry... I think, or is it octahedral? 
        // I should know this stuff, but topology was many years ago for me. In fact, 
        // everything was years ago. :)
		// Branchless equivalent to: if (p.x<p.y) p.xy = p.yx;
        p.xy += step(p.x, p.y)*(p.yx - p.xy);
        p.xz += step(p.x, p.z)*(p.zx - p.xz);
        p.yz += step(p.y, p.z)*(p.zy - p.yz);
 
        // Stretching about an offset.
		p = p*s + offs*(1. - s);
        
		// Branchless equivalent to:
        // if( p.z < offs.z*(1. - s)*.5)  p.z -= offs.z*(1. - s);
        p.z -= step(p.z, offs.z*(1. - s)*.5)*offs.z*(1. - s);
        
        // Loosely speaking, construct an object, and combine it with
        // the object from the previous iteration. The object and
        // comparison are a cube and minimum, but all kinds of 
        // combinations are possible.
        p = abs(p);
        vec3 q = p*amp;
        //d = min(d, max(max(p.x, p.y), p.z)*amp - .035);
        
        // The object you draw is up to you. There are countless options.
        float box = max(max(q.x, q.y), q.z) - sz;
        //box = min(box, max(max(q.y, q.z) - sz*.33, q.x - sz*1.1));
        //float box = max(length(q.yz) - sz*1.2, q.x - sz);
        //float box = length(q) - sz; // A very spherical box. :)
        #ifdef WIREFRAME
        box = max(box, -(min(min(max(q.x, q.y), max(q.x, q.z)), max(q.y, q.z)) - sz + ew));
        //box = max(box, -max(length(q.yz) - ew, q.x - sz - ew));
        //box = max(box, -(max(length(q.yz - sz*.5) - ew*.35, q.x - sz - ew*.5)));
        //box = max(box, -(max(q.y, q.z) - sz + ew));
        #endif
        // Vertices, of sorts.
        //q = abs(q) - sz;
        //box = min(box, length(q) - sz/3.);
        d = min(d, box);
        
        
        amp /= s; // Decrease the amplitude by the scaling factor.
        
    }
 
 	return d; // Return the distance.
}
float smin(float a, float b, float k)
{
	float h = clamp( 0.5 + 0.5*(b-a)/k, 0.0, 1.0 );
	return mix( b, a, h ) - k*h*(1.0-h);
}

float smax(float a, float b, float k)
{
    return smin(a, b, -k);
}

vec3 vmax(vec3 a, vec3 b)
{
    return vec3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z));
}


float Map(vec3 v)
{
  if (fbvar5 < 0.5)
		return mix(MapA(v),MapB(v), fbvar2);
  else
		return min(MapA(v),MapB(v));
}

//----------------------------------------------------------------------------------------
float Hash(vec2 p)
{
	return fract(sin(dot(p, vec2(12.9898, 78.233))) * 33758.5453)-.5;
} 

//----------------------------------------------------------------------------------------


vec3 Colour(vec3 pos) 
{
  vec3 ca = (pos * col1);
  vec3 cb = (pos * col2);
  vec3 cc = (pos * col3);

  vec3 c1 = vmax(ca,cb);
  vec3 c2 = Noise(pos*100.) * cc;
c1=abs(c1);
c2=abs(c2);
//  c1 *=c1;
//  c2 *=c2;
  return normalize(c1) + normalize(log(c2));
}



//----------------------------------------------------------------------------------------
vec3 GetNormal(vec3 pos, float distance)
{
    distance *= 0.001+.0001;
	vec2 eps = vec2(distance, 0.0);
	vec3 nor = vec3(
	    Map(pos+eps.xyy) - Map(pos-eps.xyy),
	    Map(pos+eps.yxy) - Map(pos-eps.yxy),
	    Map(pos+eps.yyx) - Map(pos-eps.yyx));
	return normalize(nor);
}

//----------------------------------------------------------------------------------------
float BinarySubdivision(in vec3 rO, in vec3 rD, vec2 t)
{
    float halfwayT;
  
    for (int i = 0; i < 6; i++)
    {

        halfwayT = dot(t, vec2(.5));
        float d = Map(rO + halfwayT*rD); 
        //if (abs(d) < 0.001) break;
        t = mix(vec2(t.x, halfwayT), vec2(halfwayT, t.y), step(0.0005, d));

    }

	return halfwayT;
}

//----------------------------------------------------------------------------------------
vec2 Scene(in vec3 rO, in vec3 rD, in vec2 fragCoord)
{
  float t;
  if (fbvar3> 0.5)
		t = 0.001 + (1.0-fbvar3) + 0.04 * Hash(fragCoord*rO.x); // Dizzolve 
  else
    t = 0.001 + (fbvar3);  // Clean
	vec3 p = vec3(0.0);
  float oldT = 0.0;
  bool hit = false;
  float glow = 0.0;
  vec2 dist;
 
	for( int j=0; j < 100; j++ )
	{
		if (t > 12.0) break;
        p = rO + t*rD;
       
		float h = Map(p);
        
		if (h  < 0.0005)
		{
       dist = vec2(oldT, t);
       hit = true;
       break;
    }

   	glow += clamp(.05-h, 0.0, .4);
    oldT = t;
  	t +=  h + t*0.001;
 	}

#ifdef FORCE_MISS_TO_BLACK
  if (!hit)
    return vec2(0.0,0.0);
  else     
    t = BinarySubdivision(rO, rD, dist);
#else
	if (!hit)
     t = 1000.0;
  else       
     t = BinarySubdivision(rO, rD, dist);
#endif

  return vec2(t, clamp(glow*.25, 0.0, 1.0));
}

//----------------------------------------------------------------------------------------
vec3 PostEffects(vec3 rgb, vec2 xy)
{
	// Gamma first...
	

	// Then...
	#define CONTRAST 1.0
	#define SATURATION 1.2
	#define BRIGHTNESS 1.4
	rgb = mix(vec3(.5), mix(vec3(dot(vec3(.2125, .7154, .0721), rgb*BRIGHTNESS)), rgb*BRIGHTNESS, SATURATION), CONTRAST);
	// Noise...
	//rgb = clamp(rgb+Hash(xy*iTime)*.1, 0.0, 1.0); 
	// Vignette...
	rgb *= .5 + 0.5*pow(20.0*xy.x*xy.y*(1.0-xy.x)*(1.0-xy.y), 0.2);	

    rgb = pow(rgb, vec3(0.47));
	return rgb;
}

//----------------------------------------------------------------------------------------
float Shadow( in vec3 ro, in vec3 rd)
{
	float res = 1.0;
    float t = 0.05;
	float h;
	
    for (int i = 0; i < 8; i++)
	{
		h = Map( ro + rd*t );
		res = min(6.0*h / t, res);
		t += h;
	}
    return max(res, 0.0);
}

//----------------------------------------------------------------------------------------
mat3 RotationMatrix(vec3 axis, float angle)
{
    axis = normalize(axis);
    float s = sin(angle);
    float c = cos(angle);
    float oc = 1.0 - c;
    
    return mat3(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,
                oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,
                oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c);
}

//----------------------------------------------------------------------------------------
vec3 LightSource(vec3 spotLight, vec3 dir, float dis)
{
    float g = 0.0;
    if (length(spotLight) < dis)
    {
        float a = max(dot(normalize(spotLight), dir), 0.0);
		g = pow(a, 500.0);
        g +=  pow(a, 5000.0)*.2;
    }
   
    return vec3(.6) * g;
}

//----------------------------------------------------------------------------------------
vec3 CameraPath( float t )
{
    vec3 p = vec3(-.78 + 3. * sin(2.14*t),.05+2.5 * sin(.942*t+1.3),.05 + 3.5 * cos(3.594*t) );
	return p;
} 
    
//----------------------------------------------------------------------------------------
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // three ever-changing colors used in
    float fTime = iTime * 2.5;
    col1 = vec3(sin(fTime/3.1), sin(fTime/4.3), sin(fTime/5.9)) + 2.1;  
    col2 = vec3(cos(fTime/10.7), cos(fTime/8.9), cos(fTime/7.3)) + 2.1;  
    col3 = vec3(sin(fTime/7.7), sin(fTime/9.3), sin(fTime/13.3)) + 2.1;  
	    
    float m = (iMouse.x/iResolution.x)*300.0;
	gTime = (iTime+m)*.01 + 15.00;
    vec2 xy = fragCoord.xy / iResolution.xy;
	vec2 uv = (-1.0 + 2.0 * xy) * (vec2(iResolution.x/iResolution.y, 1.0) * (0.1+(fbvar4*5.0)));
	
	
	#ifdef STEREO
	float isRed = mod(fragCoord.x + mod(fragCoord.y, 2.0),2.0);
	#endif

	vec3 cameraPos	= CameraPath(gTime);
  vec3 camTar		= CameraPath(gTime + .01);

	float roll = 13.0*sin(gTime*.5+.4);
	vec3 cw = normalize(camTar-cameraPos);

	vec3 cp = vec3(sin(roll), cos(roll),0.0);
	vec3 cu = normalize(cross(cw,cp));

	vec3 cv = normalize(cross(cu,cw));
  cw = RotationMatrix(cv, sin(-gTime*20.0)*.7) * cw;
	vec3 dir = normalize(uv.x*cu + uv.y*cv + 1.3*cw);

	#ifdef STEREO
	cameraPos += .008*cu*isRed; // move camera to the right
	#endif

    vec3 spotLight = CameraPath(gTime + .05) + vec3(sin(gTime*19.4), cos(gTime*17.58), sin(gTime * 15.53))*.02;
		vec3 col = vec3(0.0);
		vec2 ret = Scene(cameraPos, dir,fragCoord);
#ifdef FORCE_MISS_TO_BLACK
    if (ret == vec2(0.0)) {
				fragColor=vec4(0.0,0.0,0.0,0.0);  // We missed so force to black
			return;
		}
#endif    
		if (ret.x < 900.0)
    {
				vec3 p = cameraPos + ret.x*dir; 
				vec3 nor = GetNormal(p, ret.x);
        
       	vec3 spotDir = spotLight - p;
        vec3 camDir = cameraPos - p;

    		float attenSpot = length(spotDir);
    		float attenCam = length(camDir);

        spotDir /= attenSpot;
        camDir /= attenCam;
        
        float shaSpot = Shadow(p, spotDir);
        float shaSun = Shadow(p, sunDir);
        float shaCam = 1.0;  // No need to calc shadows from light originating at the camera position
        
       	float briCam = (max(dot(camDir, nor), 0.0) / pow(attenCam, 0.5)) * fbvar7;
       	float briSpot = (max(dot(spotDir, nor), 0.0) / pow(attenSpot, 1.5)) * fbvar8;
        float briSun = max(dot(sunDir, nor), 0.0) * (fbvar9*2.);
        
       col = Colour(p) * 2.0;
       col = (col * briSpot * shaSpot) + (col * briSun * shaSun)  + (col * briCam * shaCam);
        
       vec3 ref = reflect(dir, nor);
       col += pow(max(dot(camDir,  ref), 0.0), 10.0) * 2.0 * shaCam * briCam;
       col += pow(max(dot(spotDir,  ref), 0.0), 10.0) * 2.0 * shaSpot * briSpot;
       col += pow(max(dot(sunDir, ref), 0.0), 10.0) * 2.0 * shaSun  * briSun;
    }
    
    //col = mix(sky, col, min(exp(-ret.x+1.5), 1.0));
    col += vec3(pow(abs(ret.y), 2.)) * vec3(.02, .04, .1); 
    col += LightSource(spotLight-cameraPos, dir, ret.x);
	col = PostEffects(col, xy);	

	
	#ifdef STEREO	
	col *= vec3( isRed, 1.0-isRed, 1.0-isRed );	
	#endif

	col = max(col, vec3(0.01,0.01,0.01)); // Force any hit to non-black
	
	fragColor=vec4(col,1.0);
}
// --------[ Original ShaderToy ends here ]---------- //

#ifdef FLASHBACK
void main(void)
{
	 mainImage(gl_FragColor, gl_FragCoord.xy);
} 
#endif

