// Uncomment if your machine is powerful enough.

#define SHADOWS
//#define REFLECTION
//#define AA

#define STEPS 100
#define FAR 30.0

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

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

const int FLOOR = 0;
const int ROAD = 1;
const int RAIL = 2;
const int SLEEPER = 3;
const int RUBBER = 4;
const int RIM = 5;
const int CAR = 10;
const int TRAIN = 20;

struct Material {
    vec3 albedo;
    vec3 specular;
};

int lightNumber = 1;
vec3 ambient = vec3(0.1);
const vec3 lightDirection = normalize(vec3(3,5,-4));
const vec3 lightCol = vec3(2,2,1);

vec2 delta = vec2(0.0,0.001);

float time;

// Hash function from Dave_Hoskins
// https://www.shadertoy.com/view/4djSRW
float hash11(float p)
{
    p = fract(p * .1031);
    p *= p + 33.33;
    p *= p + p;
    return fract(p);
}
// A pair of random bits, each one depending on
// only one coordinate of p.
// The tiles on the horizontal axis are vertically flipped randomly,
// the tiles on the vertical axis are horizontally flipped randomly.
vec2 rdBits(vec2 p) {
    return step(.5,vec2(hash11(p.y), hash11(p.x+138.215)));
}
void flip(inout float x, float bit) {
    x = mix(x,1.-x,bit);
}
void flip(inout vec2 p, vec2 bits) {
    p = mix(p,1.-p,bits);
}
// A grid cell has 4 different types, depending on the
// parities of its coordinates, let's call them 00, 01, 10, 11.
// It turns out that a vehicle alternates between these 4 types.
// - One car goes: 00, 01, 11, 10, and cycles,
// - The other car goes: 00, 10, 11, 01, and cycles.
// So at a given time, we can figure out in which
// type of cell a vehicle must be.
vec2 closestCar(vec2 p, float car, out vec2 dir) {
    // We first find out what is the parity
    // of the cell the car is currently in.
    float t = carSpeed*time;
    // Think of shift as 0, 1, 2, 3
    float shift = floor(t/1.5708);

    vec2 u = vec2(1.-car,car);
    vec2 parity = floor(.5*mod(shift+u,4.));

    // We then find the cell with this parity
    // that is closest to p.
    vec2 g = 2.*floor(.5*(p+.5-parity))+parity;

    // Now we calculate the position of the car in the cell.
    // Each car has a position, a direction and a side.
    // We first calculate them in the abstract tile
    // and apply the flipping at the end.

    // The side of the car is determined by the
    // parity of the random bits.
    vec2 b = rdBits(g);
    float side = 2.*mod(b.x+b.y,2.)-1.;
    side = mix(side, -side, car);

    float direction = 1.-2.*car;
    shift += car;// The cars meet in a more random place
    shift *= 1.5708;

    t = direction*(t-shift);
    vec2 cs = vec2(cos(t), sin(t));
    vec2 pos = (.5+.09*side)*cs;
    dir = direction*vec2(-cs.y,cs.x);

    // Parity determines whether the car enters the tile through
    // a horizontal or vertical edge.
    float par = mod(parity.x+parity.y,2.);
    pos = mix(pos, pos.yx, par);
    dir = mix(dir, dir.yx, par);

    // We finally apply the tile flipping to
    // the car position and direction
    vec2 flipping = mod(g+b,2.);
    flip(pos,flipping);
    dir = mix(dir, -dir, flipping);

    return g+pos;
}
vec2 closestTrain(vec2 p, float wagon, out vec2 dir) {
    float t = trainSpeed*time;
    float shift = floor(t/1.5708-.5*wagon);

    vec2 parity = floor(.5*mod(shift+vec2(1.,0.),4.));

    // Grid cell containing the closest
    // instance of the wagon.
    vec2 g = 2.*floor(.5*(p+.5-parity))+parity;

    shift += .5*wagon;
    shift *= 1.5708;

    t -= shift;
    vec2 cs = vec2(cos(t),sin(t));
    vec2 pos = 1.-.5*cs;
    dir = vec2(cs.y,-cs.x);

    // The parity of the cell determines
    // the direction.
    float par = mod(parity.x+parity.y,2.);
    pos = mix(pos, pos.yx, par);
    dir = mix(dir, dir.yx, par);

    // We finally apply the tile flipping.
    vec2 b = rdBits(g);
    vec2 flipping = mod(g+b, 2.);
    flip(pos,flipping);
    dir = mix(dir, -dir, flipping);


    return g+pos;
}

// Signed distance fields
float sdFloor(vec3 p) {
    vec2 q = abs(mod(p.xz,.1)-.05);
    float depth = .3*max(.025-length(q),0.);//.01*texture(iChannel1,.3*p.xz).r;
    return p.y+depth;
}
float sdRoad(vec3 p) {

   // return length(max(
   //     vec2(abs(length(p.xz)-.5)-.17,abs(p.y-.05)-.02),
   //     0.))-.03;
    float a = length(max(
        vec2(abs(abs(length(p.xz)-.5)-.09)-.07,abs(p.y-.05)-.03),
        0.))-.02;
    float b = length(max(
        vec2(abs(abs(length(p.xz)-.5)-.19)-.01,abs(p.y-.05)-.05),
        0.))-.01;
    return min(a,b);
}
float sdRail(vec3 p, out int id) {
    p.xz = 1.-p.xz;
    float l = length(p.xz);
    // Rails
    float dr = length(vec2(max(abs(abs(l-.5)-.1)-.01,0.),p.y-.02))-.02;
    float a = atan(p.z,p.x)/1.5708;
    // Sleepers
    float ds = length(vec2(max(abs(mod(a,.1)-.05),max(l-.65,.35-l)),p.y-.015))-.015;

    id = dr<ds ? RAIL : SLEEPER;
    return min(dr,ds);
}
// A truchet tile contains a road section and a rail section
float sdTile(vec3 p, out int id) {
    // Flip the tile
    vec2 g = floor(p.xz);
    vec3 f = p;
    f.xz = fract(f.xz);
    vec2 b = rdBits(g);
    vec2 flipping = mod(g+b,2.);
    flip(f.xz,flipping);

    float d, minD = 1e6;

    d = sdRoad(f);
    if(d<minD) {
        id = ROAD;
        minD = d;
    }
    int tmp;
    d = sdRail(f,tmp);
    if(d<minD) {
        id = tmp;
        minD = d;
    }
    return minD;
}
// From iq: https://www.shadertoy.com/view/ftVXRc
// r=radius, h=height
float sdCutDisk(in vec2 p)
{
    const float r = .15;
    const float h = .05;
    const float w = .1414; //qrt(r*r-h*h); // constant for a given shape

    p.x = abs(p.x);

    // select circle or segment
    float s = max( (h-r)*p.x*p.x+w*w*(h+r-2.0*p.y), h*p.x-w*p.y );

    return (s<0.0) ? length(p)-r :        // circle
           (p.x<w) ? h - p.y     :        // segment line
                     length(p-vec2(w,h)); // segment corner
}
float sdCar(vec3 p, int i, out int id) {
    vec3 q = p;

    // Car body
    q.y = p.y - .09;
    float d = length(max(vec2(sdCutDisk(q.xy),abs(q.z)-.02),0.))-.03;

    // Wheels (= two cylinders)
    q.y = p.y - .15;
    q = abs(q);
    q.x -= .09;
    float l = length(q.xy);
    vec2 r = vec2(l-.04,q.z-.05);
    float dw = length(max(r,0.))-.02;

    id = d<dw ? CAR+i : l<.02 ? RIM : RUBBER;
    return min(d,dw);
}
float sdTrain(vec3 p, int i, out int id) {
    vec3 q = p;

    // Wagon body
    p.y -= .12;
    vec2 r = vec2(length(p.yz)-.07,abs(p.x)-.15);
    float dt = length(max(r,0.))-.02;

    // Wheels (= two cylinders)
    q.y -=.08;
    q = abs(q);
    q.x -= .1;
    float l = length(q.xy);
    r = vec2(l-.04,q.z-.1);
    float dw = length(max(r,0.))-.02;

    id = dt < dw ? TRAIN+i : l < .02 ? RIM : RUBBER;

    return min(dt,dw);
}
float sdLoco(vec3 p, out int id) {
    vec3 q = p;

    // Loco body
    p.y -= .12;
    vec2 r = vec2(length(p.yz)-.07,abs(p.x)-.15);
    float dt = length(max(r,0.))-.02;
    p = q - vec3(.1,.2,0.);
    r = vec2(length(p.xz)-.03,abs(p.y)-.15);
    dt = min(dt,length(max(r,0.))-.02);

    // Wheels (= two cylinders)
    q.y -=.08;
    q = abs(q);
    q.x -= .1;
    float l = length(q.xy);
    r = vec2(l-.04,q.z-.1);
    float dw = length(max(r,0.))-.02;

    id = dt < dw ? TRAIN : l<.02 ? RIM : RUBBER;

    return min(dt,dw);
}
// pos = car position
// dir = car direction
// i = car number (0 or 1)
float sdCar(vec3 p, vec2 pos, vec2 dir, int i, out int id) {
    p.xz -= pos;
    p.xz = mat2(dir.x,-dir.y,dir.y,dir.x)*p.xz;
    return sdCar(p,i,id);
}
// pos = car position
// dir = car direction
// i = wagon number (0 to 4)
float sdTrain(vec3 p, vec2 pos, vec2 dir, int i, out int id) {
    p.xz -= pos;
    p.xz = mat2(dir.x,-dir.y,dir.y,dir.x)*p.xz;
    return sdTrain(p,i,id);
}
// pos = car position
// dir = car direction
float sdLoco(vec3 p, vec2 pos, vec2 dir, out int id) {
    p.xz -= pos;
    p.xz = mat2(dir.x,-dir.y,dir.y,dir.x)*p.xz;
    return sdLoco(p,id);
}

float sd(vec3 p, out int id) {
    float d,minD = 1e6;
    vec2 pos,dir;
    int tmp;

    // Floor
    d = sdFloor(p);
    if(d<minD) {
        id = FLOOR;
        minD = d;
    }

    // Tile
    d = sdTile(p,tmp);
    if(d<minD) {
        id = tmp;
        minD = d;
    }

    // Car 0
    pos = closestCar(p.xz,0.,dir);
    d = sdCar(p, pos, dir, 0, tmp);
    if(d<minD) {
        id = tmp;
        minD = d;
    }
    // Car 1
    pos = closestCar(p.xz,1.,dir);
    d = sdCar(p, pos, dir, 1, tmp);
    if(d<minD) {
        id = tmp;
        minD = d;
    }
    // Loco
    pos = closestTrain(p.xz,0.,dir);
    d = sdLoco(p, pos, dir, tmp);
    if(d<minD) {
        id = tmp;
        minD = d;
    }
    // Wagons
    for(int i=1; i<5; i++) {
        pos = closestTrain(p.xz,float(i),dir);
        d = sdTrain(p, pos, dir, i, tmp);
        if(d<minD) {
            id = tmp;
            minD = d;
        }
    }

    return minD;
}

vec3 roadColor(vec2 p) {
    float xx = .4*floor(p.x/.4);
    float yy = .2+.7*floor((p.y-xx)/.7);
    vec2 pp = vec2(xx,yy);
//    const vec2 vector = vec2(.2,.5);
//    vec2 pp = vector*floor(p/vector);
    vec2 uu = p-pp;
    float h = hash11(63.4*pp.x+135.8*pp.y);
    vec3 tex = texture(iChannel0, uu+h).rgb;
    tex = mix(vec3(1),tex,.8+.2*h);
    tex = tex*tex;
    return tex;

    vec2 g = floor(p);
    vec2 f = fract(p);
    vec2 b = rdBits(g);
    vec2 flipping = mod(g+b, 2.);
    flip(f,flipping);
   // mix(f,f.yx,mod(g.x+g.y,2.));
    //f = f+(2.*mod(flipping.x+flipping.y,2.)-1.)*f.yx;

    f = f+vec2(-f.y,f.x);
    f.x += hash11(g.x+12.*g.y);
/*
    vec3 light = vec3(.89,.6,.28);
    vec3 dark = vec3(.21,.1,.02);
    return mix(dark, light, smoothstep(.4,.5,length(mod(20.*f,2.)-1.)));
  */
    float l = length(f)-.5;
    vec2 uv = vec2(atan(f.y,f.x)*1.2732-1.,5.093*l);
    vec3 col = texture(iChannel0,f).rgb;
  //  col = col*col;
   // col = clamp(.4+(col-.4)*2.,0.,1.);
    col = mix(vec3(1.),col,.9);


    return col;
}

Material material(vec3 p, int id) {
    vec3 alb, spe = vec3(.04);
    switch(id) {
        case FLOOR:
            alb = vec3(.15,.25,.03);
            break;
        case ROAD:
            alb = roadColor(p.xz);
            break;
        case RAIL:
            spe = vec3(1);
            break;
        case SLEEPER:
            break;
        case CAR:
            alb = vec3(1,1,0);
            break;
        case CAR+1:
            alb = vec3(1,0,0);
            break;
        case RUBBER:
            spe = vec3(0);
            break;
        case RIM:
            #ifdef REFLECTION
            spe = vec3(1);
            #else
            alb = vec3(1);
            #endif
            break;
        default:// Train
            #ifdef REFLECTION
            spe = vec3(0,float(id-TRAIN)/5.,1);
            #else
            alb = vec3(0,float(id-TRAIN)/5.,1);
            spe = vec3(.1);
            #endif
    }
    return Material(alb,spe);
}

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

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

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

vec3 rayColorWithoutReflection(vec3 start, vec3 dir) {
    vec3 color = vec3(0);

    int id;

    float d = march(start, dir,id);

    if(id == -100) {//No ray intersection
        vec3 tex = texture(iChannel1,dir).rgb;
        return tex*tex;
    } else {
        vec3 p = start + d * dir;

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

        color += ambient*mat.albedo;

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

        #ifdef SHADOWS
        // Shadow
        float sh = shadow(p, lightDirection);
        #else
        float sh = 1.;
        #endif

        color += sh*(diff + spec)*lightCol;

        // Reflection of environment
        vec3 ref = reflect(dir, normal);
        vec3 env = texture(iChannel1,ref).rgb;
        env = env*env;// Gamma

        #ifdef SHADOWS
        // Shadow
        sh = shadow(p, ref);
        #else
        sh = 1.;
        #endif

        color += sh * vec3(mat.specular) * env;
    }
    return color;
}
vec3 rayColorWithReflection(vec3 start, vec3 dir) {
    vec3 coef = vec3(1.0);
    vec3 color = vec3(0);

    int id;
    for(int i=0; i<2; i++) {
        float d = march(start, dir,id);

        if(id == -100) {//No ray intersection
            vec3 tex = texture(iChannel1,dir).rgb;
            return color + coef*tex*tex;
        } else {
            vec3 p = start + d * dir;

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

            color += coef*ambient*mat.albedo;

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

            #ifdef SHADOWS
            // Shadow
            float sh = shadow(p, lightDirection);
            #else
            float sh = 1.;
            #endif

            color += coef*sh*(diff + spec)*lightCol;

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

vec3 rayColor(vec3 start, vec3 dir) {
    #ifdef REFLECTION
    return rayColorWithReflection(start, dir);
    #else
    return rayColorWithoutReflection(start, dir);
    #endif
}

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 ) {
    time = iTime;
    vec3 center = vec3(cameraSpeed*iTime,0,-2.*cameraSpeed*iTime);
    vec3 cam = vec3(-1,3,3)+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;
        //time = 10.0*iMouse.x/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

    // Vignette
    uv = fragCoord.xy / iResolution.xy;
    uv *=  1. - uv.yx;
    color *= pow(uv.x*uv.y * 15.0, 0.25);

    // Gamma
    color = sqrt(color);

    fragColor = vec4(color,1.0);
}
