#define lin2sRGB(x) ( x <= .0031308 ? (x)*12.92 : 1.055*pow(x,1./2.4) - .055 )


#define NO_DFDX_

const float ANIM_SPEED=2.;

//need to be multiple of 4
const int NB_TOTAL_SPHERES=32;


const float SEED=42.42;


const int NB_SPHERES=NB_TOTAL_SPHERES/4;
const int PLANE_ID=NB_SPHERES*4+1;

const float sqrt_sp=sqrt(float(NB_SPHERES));
const int MAX_STEP=100;
const float MIN_DIST=.001;
const float MAX_DIST=30.;

const float planeZ=-4.5;




const int KEY_LEFT  = 37;
const int KEY_UP    = 38;
const int KEY_RIGHT = 39;

struct Light{
    vec3 pos;
    float intensity;
    vec3 color;
};

struct Hit{
    float dist;
    vec4 objId;
    vec3 pos;
    vec3 normal;    
};

//  from DAVE HOSKINS
vec3 N13(float p) {
    p=p*SEED;
    vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.xxy+p3.yzz)*p3.zyx);
}


float map01(float min,float max, float val){
    return val=clamp((val-min)/(max-min),0.,1.);
}

vec4 getSphere(int nb, float t){
    float a=(7./sqrt_sp);
    float ox=-a*(sqrt_sp/2.)+a/2.;
    float b=(5./sqrt_sp);
    float oy=-b*(sqrt_sp/2.)+b/2.;
    
    float x_offset=mod(float(nb),sqrt_sp);
    float y_offset=floor(float(nb)/sqrt_sp);
    float x=ox+x_offset*a;
    float y=oy+y_offset*b;
    float z=-6.+3.*(abs(sin(t*2.+b*float(nb))));
    float w=min(b/1.5,1.2)*pow((1.-(-z-3.)/3.),2.);
    return vec4(x,y,z,w);
}


vec4 getSphere(int nb){
    return getSphere(nb,iTime/ANIM_SPEED);
}

//from https://iquilezles.org/articles/smin
vec2 sminN( float a, float b, float k, float n )
{
    float h = max( k-abs(a-b), 0.0 )/k;
    float m = pow(h, n)*0.5;
    float s = m*k/n; 
    return (a<b) ? vec2(a-s,m) : vec2(b-s,m-1.0);
}




Hit getDist(vec3 p){
    int idOffset=0;
    if(p.x<0. && p.y>0.)
        idOffset=NB_SPHERES;
    if(p.x>0. && p.y>0.)
        idOffset=NB_SPHERES*2;
    if(p.x>0. && p.y<0.)
        idOffset=NB_SPHERES*3;
        
    p.x=abs(p.x)-4.;
    p.y=abs(p.y)-3.;
    
    float dist=MAX_DIST;
    int objId=-1;
    int sphereId=0;
    vec2 smoothDist;
    float sphereSmoothMix=0.;
    for(int i=0;i<NB_SPHERES;i++){
        vec4 sphere=getSphere(i);
        float d=length(p-sphere.xyz)-sphere.w;
        smoothDist=sminN(dist,d,1.,2.);
        
         if(d<.5 ){
            // if(sphereId!=-1)
            sphereSmoothMix=smoothDist.y;


            sphereId=objId;           

        }

        if(d<dist){
            objId=i+idOffset;

        }
        
        dist=smoothDist.x;

    }
    float dPlane=p.z-planeZ;
    smoothDist=sminN(p.z-planeZ,dist,.5,2.);
    vec2 smoothDistMask=sminN(p.z-planeZ,dist,1.4,3.);

    sphereId=objId;           


    if(dPlane<dist){
        objId=PLANE_ID;
    }

    dist=smoothDist.x;

    return Hit(dist,vec4(objId,sphereId,smoothDistMask.y,sphereSmoothMix),vec3(0),vec3(0));
}


//from https://iquilezles.org/articles/rmshadows
float softshadow( in vec3 ro, in vec3 rd, float mint, float maxt, float k )
{
    float res = 1.0;
    for( float t=mint; t<maxt; )
    {
        float d = getDist(ro + rd*t).dist;
        if( d<MIN_DIST )
            return 0.0;
        res = min( res, k*(d*.05)/t );
        t += d;
    }
    return res;
}


vec3 getNormal(vec3 pos){
    vec2 e=vec2(.01,0);
    float dist=getDist(pos).dist;
    vec3 n=vec3(
        dist-getDist(pos-e.xyy).dist,
        dist-getDist(pos-e.yxy).dist,
        dist-getDist(pos-e.yyx).dist);
    return normalize(n);
}

Hit rayMarch(vec3 o,vec3 ray){
    float totalDist=0.;
    Hit hit;
    for(int i=0;i<MAX_STEP;i++){
        vec3 p=o+totalDist*ray;
        hit=getDist(p);
        totalDist+=hit.dist;
        if(hit.dist<MIN_DIST||totalDist>MAX_DIST) break ;
    }
    if(totalDist<MAX_DIST){
        vec3 pos=o+ray*totalDist;
        return Hit(totalDist,hit.objId,pos,getNormal(pos));
    }
    else{
        return Hit(totalDist,vec4(-1),vec3(0),vec3(MAX_DIST));
    }
    
}

vec3 shadePixel(vec3 pos, vec3 n, Light light,vec3 viewDir){

    float intensity=1./pow(length(pos-light.pos),2.)*light.intensity;
    float diffuse=dot(normalize(light.pos-pos),n);
    float shadow=softshadow( pos, light.pos, 0.01, 1., 5.);
    diffuse=clamp(diffuse,0.,1.)*shadow;
    float specLvl = 65.;


    vec3 r=normalize(light.pos-viewDir-pos);
    float spec=pow(max(dot(n, r),0.), specLvl)*shadow;
    
    float ret=diffuse;
    ret +=  spec;

    return ret*intensity*light.color;
}


#ifndef NO_DFDX
vec2 texNormalMap(vec2 uv,sampler2D tex,float intensity)
{
    vec2 texelPixelRatio=iResolution.xy/iChannelResolution[0].xy;
    float height = length(texture(tex, uv))*intensity;
    return vec2(dFdx(height), dFdy(height))*texelPixelRatio;

}

#else

vec2 texNormalMap(vec2 uv, sampler2D tex, float intensity)
{
    intensity=1.01;
    float e = .004;
    float dist=length(texture(tex, uv))*intensity;
    float dist_nx=length(texture(tex, uv+vec2(1.,0.)*e));
    float dist_px=length(texture(tex, uv+vec2(-1.,0.)*e));

    float distx=mix(dist_nx,dist_px,.5)-dist;
    
    float dist_ny=length(texture(tex, uv+vec2(0,1)*e));
    float dist_py=length(texture(tex, uv+vec2(0,-1.)*e));

    
    float disty=mix(dist_ny,dist_px,.5)-dist;    
    return normalize(
    vec2(distx, disty));
}

#endif


vec3 computeLighting(Light[3] lights, vec3 hitPos,vec3 n,vec3 ray){
    vec3 col=vec3(0.);
    for(int i=0;i<3;i++){
        vec3 shadingColor=shadePixel(hitPos,n,lights[i],ray);
        
        col+=shadingColor;
   }
   return col;
}


void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord-.5*iResolution.xy)/iResolution.y;
    float t=iTime/ANIM_SPEED;
    
    vec2 mousePos=((iMouse.xy-.5*iResolution.xy)/iResolution.y);

    //camera model from https://www.youtube.com/watch?v=PBxuVlp7nuM
    vec3 camera=vec3(mousePos*2.,8);
    vec3 lookAt=vec3(0);
    float zoom=1.;
    vec3 f=normalize(camera-lookAt);
    vec3 r=cross(vec3(0,1.,0),f);
    vec3 u=cross(f,r);
    
    vec3 c=camera-f*zoom;
    vec3 i=c+uv.x*r+uv.y*u;
    vec3 ray=normalize(i-camera);
    
    
    
    bool disableLight1=texelFetch( iChannel1, ivec2(KEY_LEFT,2),0 ).x>0.;
    bool disableLight2=texelFetch( iChannel1, ivec2(KEY_RIGHT,2),0 ).x>0.;
    bool disableLight3=texelFetch( iChannel1, ivec2(KEY_UP,2),0 ).x>0.;

    Light lights[]=Light[](Light(vec3(mousePos*20.,10.),disableLight3?0.:100.,vec3(1.,1.,1.)),
                           Light(vec3(-8,3.,8.),disableLight1?0.:100.,vec3(1.,.8,0.)),
                           Light(vec3(8,-3.,8.),disableLight2?0.:150.,vec3(0.,0.,1.)));


     
    Hit hit=rayMarch(camera,ray);
    vec4 colPlan,colSphere;
    if(int(hit.objId.x)==PLANE_ID){
        vec3 n=normalize(vec3(texNormalMap(hit.pos.xy/10.,iChannel3,30.*(1.+pow(hit.objId.z*2.,2.))),1.));
        vec3 col=computeLighting(lights,hit.pos,n,ray);
        vec4 maskColor=vec4(vec3(clamp(hit.objId.z,0.,1.)),1.)*2.;
        vec4 mixColor=vec4(N13(float(hit.objId.y)+1.),1.)*maskColor+(vec4(length(texture(iChannel0,hit.pos.xy/10.).rgb)*(1.-maskColor)));
        colPlan =vec4(col.xyz*mixColor.xyz,1.0);

    }
    if(int(hit.objId.y)!=-1 && int(hit.objId.x)!=PLANE_ID){
        vec3 col=computeLighting(lights,hit.pos,hit.normal,ray);
        vec3 colsp1=N13(float(hit.objId.x)+1.);
        vec3 colsp2=N13(float(hit.objId.y)+1.);
        vec3 colsp=colsp2+clamp(hit.objId.w,0.,1.);
        colSphere = vec4(col.xyz*length(texture(iChannel2,hit.normal.xy).rgb)*colsp,1.0);
        //colSphere=vec4(vec3(hit.objId.w),1.);
    }
    
    fragColor=colPlan+colSphere;
   //gamma correction
    fragColor.r=lin2sRGB(fragColor.r);
    fragColor.g=lin2sRGB(fragColor.g);
    fragColor.b=lin2sRGB(fragColor.b);
    
    
}
