#define R (iResolution.xy)
#define T (iTime)
#define M_PI (3.14159265359)
#define TAU (M_PI * 2.0)
#define ZERO (min(0, int(iTime)))
#define adot(a,b) (abs(dot(a, b)))
#define cdot(a,b) (max(0.0, dot(a, b)))
#define rgb(a,b,c) (vec3(a, b, c) / 255.0)
#define SAMPLE(var, id_) if (var < dist) { data.id = id_; dist = var; }
#define TEXTURE_SIGNATURE in vec2 uv, in vec3 p, inout Material m

#define EST_NORM_FOR(TEXFUNC, UV, POINT, N, M, COMPFUNC, RADI, Z, CLR, MIX, VAR)\
{\
    float a = COMPFUNC(CLR);\
    float e = RADI;\
    VAR = fixNormal(N, normalize(a - vec3(\
        COMPFUNC(TEXFUNC(UV + vec2(e, 0), POINT, M)),\
        COMPFUNC(TEXFUNC(UV + vec2(0, e), POINT, M)),\
        Z\
    )), MIX);\
}

vec3 fixNormal(vec3 wn, vec3 n, float mixf) {
    vec3 t = cross(wn, vec3(0, 1, 0));
    vec3 b = vec3(0.0);
    if (abs(length(t)) == 0.0) t = cross(wn, vec3(0, 0, 1));
    t = normalize(t);
    b = normalize(cross(wn, t));
    mat3 tbn = mat3(t, b, -wn);
    n = normalize((tbn)*n);
    return normalize(mix(wn, n, mixf*max(0.1, dot(wn, n))));
}

mat2 rot(in float a) { float c = cos(a); float s = sin(a); return mat2(c, s, -s, c); }

float luma(vec3 color) { return dot(color, vec3(0.299, 0.587, 0.114)); }
float invLuma(vec3 color) { return distance(color, vec3(0.299, 0.587, 0.114)); }

struct Material {
    float rough;
    float spec;
    float h;
};

#define NEW_MATERIAL Material(1.0, 1.0, 0.0)

struct Data {
    vec3 p;
    vec3 n;
    vec2 uv;
    float d;
    int id;
    Material m;
};

#define NEW_DATA Data(vec3(0.0), vec3(0.0), vec2(0.0), FAR, 0, NEW_MATERIAL)

struct Light {
    vec3 p;
    vec3 d;
    vec3 c;
    float s;
    int type;
};

#define LIGHT_AMBIENT 0
#define LIGHT_POINT 1
#define LIGHT_DIR 2

vec3 getLightDir(in Light light, in vec3 p) {
    switch (light.type) {
        case LIGHT_AMBIENT: return normalize(light.p); break;
        case LIGHT_POINT: return normalize(light.p - p); break;
        case LIGHT_DIR: return normalize(light.d); break;
    }
    return vec3(0.0);
}

vec3 getLightAtt(in Light light, in vec3 p, in vec3 n) {
    vec3 L = getLightDir(light, p);
    float NdotL = cdot(n, L);

    switch (light.type) {
        case LIGHT_AMBIENT: return light.c * light.s * NdotL; break;
        case LIGHT_POINT: {
            return light.c * NdotL * (pow(light.s, 2.0) / max(0.001, pow(distance(light.p, p), 2.0)));
        }; break;
        case LIGHT_DIR: {
            return light.c * NdotL * smoothstep(0.8, 1.0, dot(
                normalize(light.p - p),
                L
            ));
        }; break;
    }

    return vec3(0.0);
}

vec2 boxUv(in vec3 p, in vec3 n) {
    vec2 uv = vec2(0.0);
    uv = mix(uv, p.xy, round(cdot(n, vec3(0, 0, 1))));
    uv = mix(uv, p.xz, round(cdot(n, vec3(0, 1, 0))));
    uv = mix(uv, p.yz, round(cdot(n, vec3(1, 0, 0))));
    return uv;
}
#define NEAR 0.003
#define FAR 100.0
#define STEPS 100

#define REGION_NOISE 1.392991, 0.9

#define ID_NONE 0
#define ID_GROUND 1
#define ID_POLE 2

#define CENTER vec3(16., 8.0, 16.)

vec3 noise(in vec2 p, in float seed) {
    p = (p + (seed * 16.03922156));
    return textureLod(iChannel3, p / 256.0, 0.0).xyz;
}

vec3 snoiseX(vec2 p, float seed) {
    return textureLod(iChannel3, (vec2(p.x, p.y) + seed) /256., 0.0).xyz;
}

vec3 snoise(in vec2 p, in float seed) {
    vec2 id = floor(p);
    vec2 lv = smoothstep(0.0, 1.0, fract(p));

    #define GET(UV) snoiseX(UV, seed)  //textureLod(iChannel3,  UV / 256., 0.0).xyz

    vec3 a = GET(id);
    vec3 b = GET(id+vec2(1., 0));
    vec3 c = GET(id+vec2(0, 1.));
    vec3 d = GET(id+vec2(1., 1.));

    vec3 ab = mix(a, b, lv.x);
    vec3 cd = mix(c, d, lv.x);
    vec3 n = mix(ab, cd, lv.y);

    #undef GET

    return n;
}

vec3 noise(in vec2 p, in float seed, in float freq) {
    float div = 0.0;
    float amp = 1.0;
    vec3 n = vec3(0.0);
    #define ADV n += amp * noise(p * freq, seed); div += amp; amp /= 2.0; freq *= 2.0;
    ADV; ADV; ADV; ADV;
    #undef ADV
    return n / div;
}

vec3 noise6(in vec2 p, in float seed, in float freq) {
    float div = 0.0;
    float amp = 1.0;
    vec3 n = vec3(0.0);
    #define ADV n += amp * noise(p * freq, seed); div += amp; amp /= 2.0; freq *= 2.0;
    ADV; ADV; ADV; ADV; ADV; ADV;
    #undef ADV
    return n / div;
}

vec3 snoise6(in vec2 p, in float seed, in float freq) {
    float div = 0.0;
    float amp = 1.0;
    vec3 n = vec3(0.0);
    #define ADV n += amp * snoise(p * freq, seed); div += amp; amp /= 2.0; freq *= 2.0;
    ADV; ADV; ADV; ADV; ADV; ADV;
    #undef ADV
    return n / div;
}

float getRoad(in vec3 p) {

    vec3 pp = p - CENTER;

    float width = 7.0;
    float sm = 1.5;

    float road = smoothstep(width-1., width, pp.x);//float(pp.x > -CENTER.x/2.0 && pp.x < CENTER.x/2.);
    road =  max(
        (smoothstep(-(1.0+sm), 1.0+sm, pp.x+width) -
        smoothstep(-(1.0+sm), 1.0+sm, pp.x-width)),
    0.0);

    return road;
}

float cylSDF(in vec3 p, in vec3 a, in vec3 b, in float r) {
    vec3 ab = b - a;
    vec3 ap = p - a;
    float t = dot(ab, ap) / dot(ab, ab);
    vec3 c = a + t * ab;
    float x = length(p - c) - r;
    float y = (abs(t - 0.5) - 0.5) * length(ab);
    float e = length(max(vec2(x, y), 0.0));
    float i = min(max(x, y), 0.0);
    return e + i;
}

float groundSDF(in vec3 p) {
    if (p.y > 13.) return p.y;
    vec2 uv = p.xz;
    vec3 region = noise6(uv, REGION_NOISE);

    float rockRegion = region.x;
    float grassRegion = region.y;
    rockRegion *= (0.5+(0.5*abs(rockRegion - grassRegion)));


    vec3 m = snoise6(uv, 0.00321592, 0.04);
    vec3 v = snoise6(uv, 4.2777721, 0.04);

    float mountains = m.x;

    float valleys = v.x * max(0.0, 1.0 - (mountains*0.1));


    float rockBump = pow(rockRegion, 2.0)*0.7;
    float h = rockBump;

    h += smoothstep(0.5, 20., pow(mountains, 5.0)*90.)*20.;
    h -= smoothstep(1., 10., pow(valleys, 2.0)*20.)*9.0;
    h += v.y*9.;

    float road = getRoad(p);

    h *= max(0.0, 1.0 - road);

    h -= road*10.;

   h *= (smoothstep(6., 20.0, abs(p.x-CENTER.x)));
    h += rockBump*max(0.0, 1.0 - road);


    float mag = abs(h);
    float d = p.y - h*0.5;

    return d / 1.6;
}

float boxSDF(in vec3 p, in vec3 s) {
    p = abs(p) - s;
    return length(max(p, 0.0)) + min(max(p.x, max(p.y, p.z)), 0.0);
}

float getDist(inout Data data, in vec3 p) {
    p.y -= CENTER.y;
    float dist = FAR;
    float ground = groundSDF(p);

    SAMPLE(ground, ID_GROUND);


    p.x -= CENTER.x;
    p.z -= CENTER.z;


    float dd = 32.;
    p.z = mod(p.z + (dd/2.), dd) - (dd/2.);
    p.x = abs(p.x) - 6.3;
    float lamp = max(length(p.xz) - 0.1, p.y - 12.);
    float lampHead = boxSDF(p - vec3(-0.3, 12., 0), vec3(0.5, 0.1, 0.2))-(0.1*(
        max(0.0, 1.-smoothstep(0.2, 0.5, max(0.0, p.y-11.7)))
    ));
    lamp = min(lamp, lampHead);

    SAMPLE(lamp, ID_POLE);
    return dist;
}

bool march(inout Data data, in vec3 ro, in vec3 rd) {
    float dist = 0.0;

    for (int i = ZERO; i < STEPS; i++) {
        vec3 p = ro+rd*dist;
        float next = getDist(data, p);
        dist += next;


        if (abs(next) <= (NEAR * (1.0 + (dist*0.65)))) break;
        if (abs(dist) >= FAR) return false;
    }

    vec3 p = ro+rd*dist;
    float f = data.id == ID_GROUND ? 0.1 : 0.001;
    vec2 e = vec2(f, 0);
    vec3 n = normalize(getDist(data, p) - vec3(
        getDist(data, p - e.xyy),
        getDist(data, p - e.yxy),
        getDist(data, p - e.yyx)
    ));
    data.p = p;
    data.n = n;
    data.d = dist;
    data.uv = boxUv(data.p, data.n);

    return true;
}

float getShadow(in vec3 ro, in vec3 rd, in float near, in float far) {
    float dist = 0.0;

    Data data = NEW_DATA;

    for (int i = ZERO; i < STEPS; i++) {
        vec3 p = ro+rd*dist;
        float next = getDist(data, p);
        dist += next;
        if (abs(next) <= (NEAR * (1.0+ (dist*0.5)))) break;
        if (abs(dist) >= min(far, FAR)) return 1.0;
    }


    return clamp(abs(dist) / (0.02+abs(near)), 0.0, 0.77);
}

vec3 getSky(in vec3 ro, in vec3 rd, in Light light) {
     vec3 col = vec3(0.0);
     vec2 uv = rd.xz / rd.y;
     vec3 blue = vec3(0.3, 0.56, 0.8);

     float dotup = cdot(rd, vec3(0, 1, 0));

     col = pow(blue, vec3(1.0 + (dotup*2.)));

     float time = T*0.2;

     vec2 shift = vec2(cos(time), sin(time));
     uv += sin(shift*6.)*0.01;

     vec3 lf1 = noise6(uv + shift, 0.03291895, 0.9);
     vec3 hf1 = noise6(uv - (shift*0.2), 1.2321956, 16.0 + mix(0.0, 1., pow(lf1.z, 2.0)));
     vec3 mixer = noise6(uv + sin(shift), 0.11111223, 0.9);

     time *= mix(1.0, 0.9, smoothstep(0.3, 1., mixer.y*mixer.x));


     time += adot(lf1, hf1);

     float mixf = mix(mixer.x, mixer.y, 0.5+(0.5*sin(time)));

     float low = mix(lf1.x, lf1.y, 0.5+(0.5*cos(time)));
     float high = mix(hf1.x, hf1.y, 0.5+(0.5*sin(time)));

     vec3 lfi = noise6(uv + shift, 0.53928111, 1.0);
     float inv = pow(mix(lfi.x, lfi.y, 0.5+(0.5*cos(time))), 2.0);

     float clouds = mix(low, high, mixf);

     clouds = mix(clouds, 0.0, inv);
     clouds = pow(clouds, 3.0);

     col = mix(col + clouds, mix(col, vec3(1.0), clouds), 0.5+(0.5*cos(time+low+high+inv)));

     return col;
}

//#define DEBUG_TEXTURE asphaltTexture

vec3 asphaltTexture(TEXTURE_SIGNATURE) {
    vec3 col = vec3(0.0);

    vec3 c1 = rgb(27, 30, 41);
    vec3 c2 = rgb(61, 59, 57);
    vec3 c3 = rgb(81, 84, 82);
    vec3 c4 = rgb(46, 50, 55);

    vec3 y1 = rgb(191, 165, 79);
    vec3 y2 = rgb(168, 145, 81);
    vec3 y3 = rgb(221, 191, 87);

    vec3 grain = textureLod(iChannel3, uv/2., 0.0).xyz;
    vec3 hf = noise(uv, 1.0019231, 16.0);

    vec3 asphalt = mix(mix(mix(c1, c2, grain.x), c3, grain.y), c4, grain.z);

    vec3 yellow = mix(mix(y1, y2, hf.x), y3, hf.y);

    col += asphalt;

    float cracks = max(0.0, 1.0-smoothstep(0.03, 0.1, abs(hf.x-hf.y)));


    col = mix(col, ((0.5*c3)+(0.5*grain.x)), 0.5*cracks*cracks*(0.2+(0.5*grain.y)));

    p = p - CENTER;


    float rad = (0.5 + (cracks*0.1))*0.36;
    float fe = (rad*(0.2));
    float x = p.x;
    float line1 = smoothstep(rad-(fe*2.), rad+(fe*2.), (-p.x)) *
        smoothstep(rad+(fe*2.), rad-(fe*2.), (-p.x));
    float line2 = smoothstep(rad-(fe*2.), rad+(fe*2.), max(0.0, p.x-rad/2.)) *
        smoothstep(rad+(fe*2.), rad-(fe*2.), max(0.0, p.x-rad/2.));
    float line = max(line1, line2);
    col = mix(col, yellow*2., line);

    float specF = clamp((1.0-smoothstep(3.9, 4.6, abs(p.x)))*2., 0.0, 1.0);

    m.spec = (pow(hf.x, 2.0) * max(0.0, 1.0 - cracks) * max(0.0, 1.0 - (line*2.)))*0.5 *
        specF;

    return col;
}

vec3 metalTexture(TEXTURE_SIGNATURE) {
    vec3 col = vec3(0.0);

    vec3 alt = noise(uv, 5.4591, 9.4);
    vec3 grain = textureLod(iChannel3, uv*2., 0.0).rgb;

    vec3 c1 = rgb(77, 103, 132);
    vec3 c2 = rgb(117, 115, 116);
    vec3 c3 = rgb(149, 144, 150);

    col = mix(col, c1, pow(alt.x, 2.0));
    col = mix(col, c2, pow(alt.y, 2.0));
    col = mix(col, c3, pow(alt.z, 2.0));

    col = mix(col, max(c1, max(c2, c3)), pow(grain.x, 3.0));

    m.spec = alt.x;
    m.rough = alt.y;

    return col;
}


vec3 rockyGrassTexture(TEXTURE_SIGNATURE) {
    vec3 col = vec3(0.0);

    vec3 grass1 = rgb(127, 108, 49);
    vec3 grass2 = rgb(133, 124, 53);
    vec3 grass3 = rgb(84, 70, 31);

    vec3 grain = textureLod(iChannel3, uv/M_PI, 0.0).rgb;
    vec3 lf1 = noise6(uv, 3.333333, 0.3);

    vec3 grass = mix(mix(grass1, grass2, grain.x), grass3, grain.y);

    vec3 rock1 = rgb(136, 120, 105);
    vec3 rock2 = rgb(155, 146, 128);
    vec3 rock3 = rgb(118, 101, 80);

    vec3 rock = mix(mix(rock1, rock2, grain.x), rock3, grain.y);

    vec3 region = noise6(uv, REGION_NOISE);

    float rockRegion = region.x;
    float grassRegion = region.y;

    float h = smoothstep(0.0, 3.0, m.h);
    float hh = h * region.x*(region.x+grain.x*0.5);

    grassRegion = max(0.0, grassRegion - (hh/2.));
    rockRegion += hh/1.2;
    rockRegion = clamp(rockRegion, 0.0, 1.0);
    rockRegion *= (0.5+(0.5*abs(rockRegion - grassRegion)));

    col = mix(col, grass, grassRegion);
    col = mix(col, rock, rockRegion);

    float edge = abs(rockRegion - grassRegion);
    edge = smoothstep(0.1, 0.01, edge);

    rockRegion = clamp((rockRegion+edge)/2., rockRegion, 1.0);
    col += pow(rockRegion, 7.0);

    grassRegion *= max(0.0, 1.0 - pow(rockRegion, 2.0)*2.);

    float thick = 0.1 + region.z*0.3;

    uv.xy *= rot(grassRegion*6.);

    float linesX = fract(uv.x*16.);
    float linesY = fract(uv.y*16.);

    linesX = max(0.0, 1.0 - ceil(linesX-thick)) * grassRegion;
    linesY = max(0.0, 1.0 - ceil(linesY-thick)) * grassRegion;

    float lines = mix(linesX, linesY, region.z);

    col = mix(col, grass1*grass3*grass2*grass2, lines);

    m.spec = clamp(pow(rockRegion, 2.0), lines*0.2, 1.0);
    m.spec *= max(0.0, 1.0 - grassRegion);

    vec3 lf2 = noise6(uv, 7.123456, 0.4);

    col += rock*lf2.x*pow(lf1.x, 2.0);
    col = mix(col, grass*grass, pow(lf1.y*max(0.0, 1.0-rockRegion), 3.0));
    col = mix(col, rock1*rock1*vec3(0.48), clamp((h*rockRegion*lf1.y)*(0.5+grain.x), 0.0, 1.0));

    return col;
}

vec3 getAlbedoGround(inout Data data) {
    vec2 uv = data.p.xz;
    vec3 col = vec3(0.0);
    vec3 p = data.p;

    float road = getRoad(data.p);
    vec3 n = data.n;
    float roadF = clamp(pow(road, 40.0)*4., 0.0, 1.0);
    roadF = clamp(roadF*4., 0.0, 1.0);
    data.m.h = max(0.0, data.p.y - CENTER.y) * max(0.0, 1.0 - roadF);
    col = rockyGrassTexture(uv, p, data.m);


    vec3 bumpGrass = vec3(0.0);
    EST_NORM_FOR(rockyGrassTexture, uv, p, data.n, data.m, luma, 0.02, 0.5, col, 0.5, bumpGrass);
    data.n = bumpGrass;

    Material mAsphalt = NEW_MATERIAL;
    vec3 asphalt = asphaltTexture(uv, p, mAsphalt);


    vec3 bumpAsphalt = vec3(0.0);
    EST_NORM_FOR(asphaltTexture, uv, p, n, mAsphalt, luma, 0.02, 0.5, col, 0.5, bumpAsphalt);

    data.n = normalize(mix(data.n, bumpAsphalt, roadF));

    col = mix(col, asphalt, roadF);

    data.m.spec = mix(data.m.spec, mAsphalt.spec, roadF);

    return col;
}

vec3 getAlbedoPole(inout Data data) {
    vec2 uv = data.uv;

    vec3 p = data.p;
    vec3 col = metalTexture(uv, p, data.m);

    return col;
}

vec3 getAlbedo(inout Data data) {
    switch (data.id) {
        case ID_GROUND: return getAlbedoGround(data); break;
        case ID_POLE: return getAlbedoPole(data); break;
    }

    return vec3(0.77);
}

vec3 forEachLight(inout Data data, in Light light, in vec3 ro, in vec3 rd) {
    vec3 albedo = getAlbedo(data);
    vec3 diffuse = albedo / M_PI;
    vec3 p = data.p;
    vec3 n = data.n;
    vec3 L = getLightDir(light, p);
    vec3 ref = reflect(L, n);
    float VdotR = cdot(rd, ref);
  //  float NdotV = abs(dot(n, rd));
   // float NdotL = cdot(n, L);
   // vec3 H = normalize(L + rd);
  //  float NdotH = clamp(dot(n, H), 0.00001, 1.0);
    float spec = data.m.spec * pow(VdotR, 32.0);
   // float spec2 = data.m.spec * pow(NdotH * VdotR, 2.);
    vec3 att = getLightAtt(light, p, n);
    float shadow = getShadow(p+(n*NEAR*2.0), L, data.d, light.type == LIGHT_AMBIENT ? FAR :
        distance(light.p, p));

    return (diffuse + spec) * att * shadow;
}

#define NUM_LIGHTS 1

vec3 render(inout Data data, in vec3 ro, in vec3 rd) {
    vec3 col = vec3(0.0);

    float dist = FAR;
    float dotup = cdot(rd, vec3(0, 1, 0));

    Light lights[NUM_LIGHTS];
    lights[0] = Light(vec3(1, 2, 3), vec3(0.0), vec3(0.97, 0.79, 0.69), 3.0, LIGHT_AMBIENT);

    if (march(data, ro, rd)) {
        dist = min(dist, data.d);
        for (int i = ZERO; i < NUM_LIGHTS; i++) {
            Light light = lights[i];
            col += forEachLight(data, light, ro, rd);
        }
    } else {
        col += getSky(ro, rd, lights[0]);
    }

    float depth = dist / FAR;

    col += smoothstep(0.3, 1.0, depth) * max(0.0, 1.0 - smoothstep(-1., 0.9, dotup));

    col += (depth*depth*depth*depth) * (1.0-smoothstep(0., 0.1, abs(rd.y+0.1)));

    return col;
}

void getRay(in vec2 uv, in vec4 m, inout vec3 ro, inout vec3 rd) {
    ro = vec3(0, 1, -0.1);
    rd = normalize(vec3(uv.xy, 1.0));
    float mt = T*0.6;

    if ((abs(m.x) > 0.0001 || abs(m.y) > 0.0001) && m.z > 0.001) {
        ro.yz *= rot(m.y*TAU);
        ro.xz *= rot(m.x*TAU);

        rd.yz *= rot(m.y*TAU);
        rd.xz *= rot(m.x*TAU);
    } else if (iFrame > 3 && iTime > 0.00002) {
        ro.yz *= rot(radians(mix(6., -30., 0.5+(0.5*sin(mt)))));
        rd.yz *= rot(radians(mix(6., -30., 0.5+(0.5*sin(mt)))));

        ro.xz *= rot(cos(mt)*0.5);
        rd.xz *= rot(cos(mt)*0.5);
    } else {
        ro.y -= 5.5;
    }

    ro.y += (0.5+(0.5*cos(mt)))*M_PI;
    ro += CENTER + vec3(0, 4., 0);
    ro.z += mt*10.;
    ro.y = max(CENTER.y+1.0, ro.y);
}

void mainImage( out vec4 O, in vec2 fc )
{
    vec3 col = vec3(0.0);
    vec4 m = iMouse;
    vec2 uv = (fc-0.5*R.xy)/R.y;
    m.xy = (m.xy-0.5*R.xy)/R.y;

    vec3 ro = vec3(0.0);
    vec3 rd = vec3(0.0);
    Data data = NEW_DATA;
    getRay(uv, m, ro, rd);


    #ifdef DEBUG_TEXTURE
    Material ma = NEW_MATERIAL;
    col += DEBUG_TEXTURE(fc.xy/R.xy, ro+rd, ma);
    #else
    col += render(data, ro, rd);

    col += (col*col*luma(col));

    col /= vec3(1.0) + col;
    col = pow(col, vec3(1.0 / 2.2));
    #endif


    O = vec4(col, 1.0);
}
