/*
"Shapes Morphing" by Emmanuel Keller aka Tambako - January 2016
License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Contact: tamby@tambako.ch
*/

#define pi 3.141593

struct Lamp
{
  vec3 position;
  vec3 color;
  float intensity;
  float attenuation;
};

Lamp lamps[3];

struct RenderData
{
  vec3 col;
  vec3 pos;
  vec3 norm;
  int objnr;
};

vec3 campos = vec3(0., 0.5, 5.);
vec3 camdir = vec3(0., -0.1, -1.);
float eye_dist = 0.25;
bool stereo = false;
float fov = 2.8;

const vec3 ambientColor = vec3(0.1, 0.4, 0.9);
const float ambientint = 0.08;

vec3 colors[3];

//#define shadow
#define ambocc
#define specular
const float specint = 0.2;
const float specshin = 30.;
const float aoint = 0.6;
const float shi = 0.8;
const float shf = 0.5;

const float normdelta = 0.0001;
const float maxdist = 55.;

// Antialias. Change from 1 to 2 or more AT YOUR OWN RISK! It may CRASH your browser while compiling!
const float aawidth = 0.9;
const int aasamples = 2;

const int KEY_S = 83;

// From https://www.shadertoy.com/view/4dsGRl
bool ReadKey( int key, bool toggle )
{
	float keyVal = texture( iChannel2, vec2( (float(key)+.5)/256.0, toggle?.75:.25 ) ).x;
	return (keyVal>.5)?true:false;
}

vec2 rotateVec(vec2 vect, float angle)
{
    vec2 rv;
    rv.x = vect.x*cos(angle) + vect.y*sin(angle);
    rv.y = vect.x*sin(angle) - vect.y*cos(angle);
    return rv;
}

float map(vec3 pos)
{
    float angle = mod(iTime*.8, 2.*pi);
    vec3 posr = vec3(pos.x*cos(angle) + pos.z*sin(angle), pos.y, pos.x*sin(angle) - pos.z*cos(angle));

    float d1 = length(posr) - 1.35;
    float d2 = pow(1.1 - sqrt(pow(posr.x, 2.)+pow(posr.y, 2.)), 2.) + pow(posr.z, 2.) - 0.1;
    float d3 = max(max(abs(posr.y), abs(posr.z)), abs(posr.x)) - 1.;
    float mx = iMouse.x/iResolution.x*1.2 - 0.3;
    float my = iMouse.y/iResolution.y*1.6 -  0.3;
    return mix(mix(d1, d2, mx), d3, my);
    return d3;
}

float trace(vec3 cam, vec3 ray, float maxdist)
{
    float t = 3.2;
  	for (int i = 0; i < 180; ++i)
    {
    	vec3 pos = ray*t + cam;
    	float dist = map(pos);
        if (dist>maxdist || abs(dist)<0.0003)
            break;
        t+= dist*0.4;
  	}
  	return t;
}

// From https://www.shadertoy.com/view/MstGDM
vec3 getNormal(vec3 pos, float e)
{
    vec2 q = vec2(0, e);
    return normalize(vec3(map(pos + q.yxx) - map(pos - q.yxx),
                          map(pos + q.xyx) - map(pos - q.xyx),
                          map(pos + q.xxy) - map(pos - q.xxy)));
}

vec3 obj_color(vec3 norm, vec3 pos)
{
    return mix(vec3(1., 0.5, 0.2), vec3(0.5, 0.1, 0.9), dot(cross(norm.xyz, norm.zxy), norm.yzx));
}

vec3 sky_color(vec3 ray)
{
    float elev = atan(ray.y);

    const float cloudsize = 0.25;
    vec3 sky = ambientColor + vec3(0.4, 0.3, 0.05)*2.8*(0.65-elev);
    float cloudst = smoothstep(-0.2, 0.5, elev)*texture(iChannel0, cloudsize*ray.xy).r;
    sky = mix(sky, 0.45 + 0.6*vec3(cloudst), smoothstep(0.12, 0.5, cloudst)) + 0.3*vec3(smoothstep(0.2, 0.8, cloudst));

    vec3 grass = vec3(0.0, 0.35, 0.25) + vec3(0.22, 0.16, -0.03)*2.8*(0.65-elev);
    grass = grass*(0.6 + 2.*abs(elev)*texture(iChannel1, 4.*ray.xy).rgb);

    return mix(grass, sky, smoothstep(-0.001, 0.001, elev));
}

// From https://www.shadertoy.com/view/Xds3zN;
float softshadow(vec3 ro, vec3 rd, float mint, float tmax)
{
	float res = 1.0;
    float t = mint;
    for(int i=0; i<16; i++)
    {
    	float h = map(ro + rd*t);
        res = min( res, 8.0*h/t );
        t += clamp( h, 0.02, 0.10 );
        if( h<0.001 || t>tmax ) break;
    }
    return clamp( res, 0.0, 1.0 );
}

// From https://www.shadertoy.com/view/Xds3zN;
float calcAO(vec3 pos, vec3 nor)
{
	float occ = 0.0;
    float sca = 0.8;
    for( int i=0; i<5; i++ )
    {
        float hr = 0.01 + 0.12*float(i)/4.0;
        vec3 aopos =  nor * hr + pos;

    	float dd = map(aopos);

        occ += -(dd-hr)*sca;
        sca *= 0.95;
    }
    return clamp( 1.0 - 2.5*occ, 0.0, 1.0 );
}

// Fresnel reflectance factor through Schlick's approximation: https://en.wikipedia.org/wiki/Schlick's_approximation
float fresnel(vec3 ray, vec3 norm, float n2)
{
   float n1 = 1.; // air
   float angle = clamp(acos(-dot(ray, norm)), -pi/2.15, pi/2.15);
   float r0 = pow((n1-n2)/(n1+n2), 2.);
   float r = r0 + (1. - r0)*pow(1. - cos(angle), 5.);
   return clamp(0., 0.9, r);
}

vec3 lampShading(Lamp lamp, vec3 norm, vec3 pos, vec3 ocol)
{
	vec3 pl = normalize(lamp.position - pos);
    float dlp = distance(lamp.position, pos);
    vec3 pli = pl/pow(1. + lamp.attenuation*dlp, 2.);

    // Diffuse shading
    float diff = clamp(dot(norm, pli), 0., 1.);
    vec3 col = ocol*normalize(lamp.color)*lamp.intensity*smoothstep(0., 1.04, pow(diff, 0.78));
    //vec3 col = ocol*normalize(lamp.color)*lamp.intensity*diff;

    // Specular shading
    #ifdef specular
    if (dot(norm, lamp.position - pos) > 0.0)
        col+= vec3(1., 0.7, 0.3)*normalize(lamp.color)*lamp.intensity*specint*pow(max(0.0, dot(reflect(pl, norm), normalize(pos - campos))), specshin);
    #endif

    // Softshadow
    #ifdef shadow
    col*= shi*softshadow(pos, normalize(lamp.position-pos), shf, 50.) + 1. - shi;
    #endif

    return col;
}

vec3 lampsShading(vec3 norm, vec3 pos, vec3 ocol)
{
    vec3 col = vec3(0.);
    for (int l=0; l<3; l++) // lamps.length()
        col+= lampShading(lamps[l], norm, pos, ocol);

    return col;
}

// From https://www.shadertoy.com/view/lsSXzD, modified
vec3 GetCameraRayDir(vec2 vWindow, vec3 vCameraDir, float fov)
{
	vec3 vForward = normalize(vCameraDir);
	vec3 vRight = normalize(cross(vec3(0.0, 1.0, 0.0), vForward));
	vec3 vUp = normalize(cross(vForward, vRight));

	vec3 vDir = normalize(vWindow.x * vRight + vWindow.y * vUp + vForward * fov);

	return vDir;
}

RenderData trace0(vec3 tpos, vec3 ray)
{
    float tx = trace(tpos, ray, maxdist);
    vec3 col;
    int objnr;

    vec3 pos = tpos + tx*ray;
    vec3 norm;
    if (tx<10.)
    {
        norm = getNormal(pos, normdelta);

        // Coloring
        col = obj_color(norm, pos);
        objnr = 1;

        // Shading
        col = ambientColor*ambientint + lampsShading(norm, pos, col);

        // Sky reflection
        float r = fresnel(ray, norm, 2.3);
        col = mix(col, sky_color(reflect(ray, norm)), r);

        // Ambient occlusion
        #ifdef ambocc
        col*= 1. - aoint + 1.25*aoint*vec3(calcAO(pos, norm));
        //col = vec3(calcAO(pos, norm));
        #endif
  }
  else
  {
      // Sky
      col = sky_color(ray);
      objnr = 3;
  }
  return RenderData(col, pos, norm, objnr);
}

vec4 render(vec2 fragCoord, vec3 campos)
{
  lamps[0] = Lamp(vec3(0., 4.5, 10.), vec3(1., 1., 1.), 6., 0.1);
  lamps[1] = Lamp(vec3(12., -0.5, 6.), vec3(.9, 0.98, 1.), 4., 0.1);
  lamps[2] = Lamp(vec3(-9., 1.8, -5. + iTime), vec3(1.0, 0.7, 0.7), 3., 0.1);

  vec2 uv = fragCoord.xy / iResolution.xy;
  uv = uv*2.0 - 1.0;
  uv.x*= iResolution.x / iResolution.y;

  vec3 ray = GetCameraRayDir(uv, camdir, fov);

  RenderData traceinf = trace0(campos, ray);
  vec3 col = traceinf.col;

  return vec4(col, 1.0);
}

vec4 render_aa(vec2 fragCoord, vec3 campos)
{
    // Antialiasing
    vec4 vs = vec4(0.);
    for (int j=0;j<aasamples ;j++)
    {
       float oy = float(j)*aawidth/max(float(aasamples-1), 1.);
       for (int i=0;i<aasamples ;i++)
       {
          float ox = float(i)*aawidth/max(float(aasamples-1), 1.);
          vs+= render(fragCoord + vec2(ox, oy), campos);
       }
    }
    return vs/vec4(aasamples*aasamples);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    if (ReadKey(83, true))
        stereo = !stereo;

    if (stereo)
    {
       vec4 im_l = render_aa(fragCoord, campos + vec3(eye_dist/2., 0., 0.));
       vec4 im_r = render_aa(fragCoord, campos + vec3(-eye_dist/2., 0., 0.));
       fragColor = vec4(im_l.r, 0, 0, 0) + vec4(0, im_r.gb, 0);
    }
    else
       fragColor = render_aa(fragCoord, campos);
}