const float pi = 3.14159;
//#define AA

#define STEPS 1000
#define FAR 50.0

const float carSpeed = 1.;
const float trainSpeed = 2.;

const float fov = 35.0;
const float cameraSpeed = 0.1;

const int BALL = 0;
const int LEAVES = 1;
const int LIGHTS = 2;
const int BACK = 3;
const int BERRIES = 4;

struct Material {
    vec3 albedo;
    vec3 specular;
};

int lightNumber = 1;
const vec3 ambient = vec3(0.1);

const vec2 delta = vec2(0.0,0.001);

vec2 noise2(vec2 p, float t) {
    return vec2(texture(iChannel1, .1*p+.004*t).r,texture(iChannel1, .1+.1*p+.004*t).r);
}

float sdBack(vec3 p) {
    return p.y+.15+.01*texture(iChannel1,.1*p.xz).r;
}

float sdLeaf(vec3 p) {
    p.x -= .5;
    // Symmetry
    p.z = abs(p.z);
    // Waves
    p.y -= .002*abs(sin(11.*(p.x-p.z)-.15));
    
    // Slant
    p.y -= .4*p.x;
    // Symmetry
    p.x = abs(p.x);
    float d = abs(p.y-.4*p.z)-.002;
    
    d = max(d,length(p+vec3(0,0,.2))-.5);
    d = max(d,.25-length(p-vec3(.17,.18,.45)));
    d = max(d,.25-length(p-vec3(.5,.12,.3)));
    
    float n = .0002*texture(iChannel1, p.xz).r;
    
    return .7*d-n;
}

mat2 rot(float a) {
    return mat2(cos(a),sin(a),-sin(a),cos(a));
}

// Fold the real line into the interval [0,a]
float fold(float x, float a) {
    return abs(mod(x+a,2.*a)-a);
}

// Folds the plane into the band with normal n and width s
// i.e. the set of p's such that 0 <= dot(p,n) < s
void fold(inout vec2 p, vec2 n, float s) {
    float proj = dot(p,n);
    p = p + (fold(proj,s)-proj)*n;
}

// Periodic tiling of periods u and v.
// Sends each point to the parallelogram tile (0,u,v,u+v)
void tile(inout vec2 p, vec2 u, vec2 v) {
    mat2 m = mat2(u,v);
    vec2 q = inverse(m)*p;
    q = fract(q);
    p = m*q;
}

float sdLeaves(vec3 p, out int id) {
    // Triangular tesselation of the plane.
    // These 4 transformations brings every pointof the plane into a triangle
    // containing a half-leaf.
    tile(p.xz,vec2(0,sqrt(3.)),vec2(1.5,sqrt(3.)/2.)); 
    fold(p.xz,vec2(0,1),sqrt(3.)/2.);
    fold(p.xz,vec2(sqrt(3.)/2.,-.5),sqrt(3.)/2.);
    fold(p.xz,vec2(sqrt(3.)/2.,.5),sqrt(3.)/2.);

    float dLeaf = sdLeaf(p);
    float dBerries = length(p-vec3(.08,-.1,0))-.07;
    float n = .003*texture(iChannel1, p.xz).r;
    float dBall = length(p-vec3(.5,.15,sqrt(3.)/2.))-.3-n;

    id = dLeaf < min(dBerries, dBall) ? LEAVES : dBerries<dBall ? BERRIES : BALL;
    return min(dLeaf, min(dBerries, dBall));
}

// Approximate function finding the closest center in a hexogonal grid
// Only correct for points that are sufficiently close to such a center,
// which is good enough here (we want to move the lights that come close
// to the center of a ball).
vec3 closestBall(vec3 p) {
    float col = round((p.x-.5)*(1./1.5));
    float row = mod(col,2.) == 0. ? .5+floor(p.z*(1./sqrt(3.))) : round(p.z*(1./sqrt(3.)));
    
    return vec3(.5+1.5*col,.15,sqrt(3.)*row);
}
// Two grids of lights with some noise, and avoiding collisions with balls
// The two types of lights are j=0 and 1
vec3 closestLight(vec3 p, float i, out vec3 color) {
    vec2 displ = .5*mix(vec2(1,.5),vec2(-.3,-.8),i)*iTime;
    vec2 id = round((p.xz+.5*i+displ))-.5*i;
    
    color = (.6+.4*cos(16.*iTime+12.3*id.x+423.8*id.y))*mix(vec3(1,.7,.1),vec3(1.5,.5,0),i);
    id += .4*noise2(id+123.54*i, iTime)-.2-displ;
    vec3 q = vec3(id.x,.2,id.y);
    
    vec3 b = closestBall(q);
    vec2 u = q.xz-b.xz;
    float l = length(u);
    
    q.xz = b.xz + max(.35/l,1.)*u;
    return q;
}
// There are two types of lights, j=0 or 1
float sdLights(vec3 p, float j) {
    vec3 color;
    vec3 light = closestLight(p, j, color);
    return length(p-light)-.01;
}
float sd(vec3 p, out int id, out vec3 glow) {
    float d,minD = 1e6;
    vec2 pos,dir;
    int tmp;
    
    // Leaves
    d = sdLeaves(p, tmp);
    if(d<minD) {
        id = tmp;
        minD = d;
    }
    // Lights
    d = sdLights(p,0.);
    glow = vec3(1,.7,.1)/(1.+3e4*d*d);
    if(d<minD) {
        id = LIGHTS;
        minD = d;
    }
    d = sdLights(p,1.);
    glow += vec3(1.5,.5,0)/(1.+3e4*d*d);
    if(d<minD) {
        id = LIGHTS;
        minD = d;
    }
    // Background
    d = sdBack(p);
    if(d<minD) {
        id = BACK;
        minD = d;
    }
    return minD;
}

float sdWithoutLights(vec3 p) {
    int id;
    return min(sdBack(p),sdLeaves(p,id));
}
Material material(vec3 p, int id) {
    vec3 alb, spe = vec3(.01);
    switch(id) {
        case BALL:
            float col = round((p.x-.5)*(1./1.5));
            float row = mod(col,2.) == 0. ? floor(p.z*(1./sqrt(3.))) : round(p.z*(1./sqrt(3.)));

            spe = mix(vec3(1,0,0),vec3(1,.7,0),mod(col+row,2.));
            break;
        case LEAVES:
            alb = vec3(0,.082,.017);
            break;
        case BACK:
            spe = vec3(.2,.1,.02);
            break;
        case BERRIES:
            alb = vec3(1,0,0);
            break;
    }
    return Material(alb,spe);
}
vec3 env(vec3 dir) {
    vec3 tex = texture(iChannel0, dir).rgb;
    return 3.*pow(tex, vec3(2.2));
}

vec3 normal(vec3 p) {
    float d = sdWithoutLights(p);
    return normalize(vec3(
        sdWithoutLights(p+delta.yxx),
        sdWithoutLights(p+delta.xyx),
        sdWithoutLights(p+delta.xxy))-d);
}

float march(vec3 start, vec3 dir, out int id, out vec3 glow) {
	float total = 0.0, d = 1.0;
    float epsilon = 0.5/iResolution.y;
    int i=0;
    glow = vec3(0);
    
    for(; i<STEPS; i++) {
        if(d<epsilon*total || total>FAR) break;
        vec3 c;
        d = sd(start + total*dir,id,c);
        glow += c;
        total += d;
    }
    if(total>FAR || i==STEPS) id = -100;
    return total;
}

float lightMarch(vec3 start, vec3 dir, float dLight) {
	float d = 1.0, total = 0.0;
    float epsilon = 0.5/iResolution.y;
    float minD = 1000.0;
    for(int i=0; i<STEPS; i++) {
        if(d<epsilon*total || total>dLight) break;
        d = sdWithoutLights(start + total*dir);
        total += d;
        minD = min(minD,d);
    }
    return minD;
}
float shadow(vec3 p, vec3 toLight, float dLight) {
    float minD = lightMarch(p+0.1*toLight, toLight, dLight);
    return smoothstep(0.0,0.02,minD);
}

vec3 rayColor(vec3 start, vec3 dir) {
    vec3 coef = vec3(1);
    vec3 color = vec3(0);
    vec3 glow;
    
    int id;
    for(int i=0; i<2; i++) {
        float d = march(start, dir,id, glow);

        if(id == -100) {//No ray intersection
            return color + 2.*coef*env(dir);
        } else if(id == LIGHTS) {
            vec3 lightCol;
            vec3 p = start + d * dir;
            closestLight(p,0.,lightCol);
            return color + coef*(20.*lightCol+glow);
        } else {
            vec3 p = start + d * dir;

            vec3 normal = normal(p);
            Material mat = material(p,id);

            color += coef*(ambient*mat.albedo+glow);

            
            for(float j=0.; j<=1.; j++) {
                vec3 lightColor;
                vec3 light = closestLight(p, j, lightColor);
                vec3 toLight = light-p;
                float dLight = length(toLight);
                float dLightxz = length(toLight.xz);
                toLight = normalize(toLight);

                vec3 diff = mat.albedo * max(dot(toLight, normal), 0.0);
                // Specular
                vec3 h = normalize(toLight-dir);
                vec3 spec = mat.specular * pow(max(dot(h,normal),0.0), 50.0);

                // Shadow
                float sh = shadow(p, toLight, dLight);

                color += 2.*coef*sh*(diff + spec)*lightColor*smoothstep(.3,0.,dLightxz);
            }


            dir = reflect(dir, normal);
            start = p + .01*dir;
            coef *= mat.specular;
            if(dot(coef,vec3(1))<.001) return color;
        }
    }
    return color;
}


mat3 setupCamera(vec3 cam, vec3 center, vec3 up) {
 	vec3 w = normalize(cam-center);
    vec3 u = normalize(cross(up, w));
    vec3 v = cross(w, u);
    
    return mat3(u,v,w);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
    vec3 center = vec3(1.*cameraSpeed*iTime,0,2.*cameraSpeed*iTime);
    vec3 cam = vec3(0,3,-1.5)+center;

    if(iMouse.z>0.0) {
        cam.y -= 5.*(iMouse.y-abs(iMouse.w))/iResolution.y;
        cam.x -= 5.*(iMouse.x-abs(iMouse.z))/iResolution.x;
    }
  
    mat3 m = setupCamera(cam, center, vec3(0,1,0));
    
    vec3 color = vec3(0.0);

    vec2 uv;
    #ifdef AA
    for(float i=-0.25; i<0.5; i+=0.5) {
        for(float j=-0.25; j<0.5; j+=0.5) {
            uv = 2.0*(fragCoord + vec2(i,j) - 0.5 * iResolution.xy)/iResolution.y;
            vec3 pix = vec3(tan(0.5*fov*0.01745)*uv,-1.0);
    
            vec3 dir = normalize(m*pix);
    
            color += rayColor(cam, dir);
        }
    }
    color /= 4.0;
    #else
    uv = 2.0*(fragCoord - 0.5 * iResolution.xy)/iResolution.y;
    vec3 pix = vec3(tan(0.5*fov*0.01745)*uv,-1.0);
    vec3 dir = normalize(m*pix);

    color = rayColor(cam, dir);
    #endif
    
    
    // Gamma
    color = pow(color,vec3(1./2.2));
        
    // Vignette
    uv = fragCoord.xy / iResolution.xy;
    uv *=  1. - uv.yx;
    color *= pow(uv.x*uv.y * 16.0, 0.25);

    fragColor = vec4(color,1.0);
}
