#define M_PI (3.1416)
#define TAU (M_PI * 2.0)

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

vec3 saturate(vec3 color, float amnt) {
  float l = color.r * 0.2 + color.g * 0.7 + color.b * 0.1;
  vec3 gray = vec3(l,l,l);
  vec3 diff = color.rgb - gray.rgb;
  diff *= (1.0 + amnt);
  vec3 saturated = clamp(gray + diff,0.0,1.0);
  return mix(color, saturated, clamp(amnt, 0.0, 1.0));
}

vec3 extractBump(sampler2D tex, vec2 uv, vec3 worldNormal, float z, float mi) {
  vec3 color = texture(tex, uv).rgb;
  vec2 texelSize = 1.0 / vec2(textureSize(tex, 0));
  vec3 dx = texture(tex, uv + vec2(texelSize.x, 0)).rgb - color;
  vec3 dy = texture(tex, uv + vec2(0, texelSize.y)).rgb - color;
  float l = (abs(dy.r - dx.r) + abs(dx.g - dy.g)) / 2.0;
  vec3 a = normalize(vec3(dy.r - dx.r, dx.g - dy.g, z));
  vec2 aspect = 1.0 / vec2(textureSize(tex, 0));
  vec3 px = color;
  vec2 value = vec2(0.0);
  for (float i = 0.0; i < TAU; i += TAU / 16.0) {
    vec2 dir = vec2(sin(i), cos(i));
    vec3 next = texture(tex, uv + dir * aspect * 6.0).rgb;
    value += dir * distance(px, next);
  }
  vec3 b = vec3(value, z);
  vec3 n = normalize(mix(a, b, 0.5));

  return mix(worldNormal, n + worldNormal, mi);
}
#define R iResolution.xy
#define T (iTime)

#define ZERO (min(0, int(iTime)))

#define STEPS 100
#define NEAR 0.003
#define FAR 100.0

#define RGB(v) (v / 255.)

#define BLUE0   RGB(vec3(15,94,156))
#define BLUE1   RGB(vec3(35,137,218))
#define BLUE2   RGB(vec3(28,163,236))
#define BLUE3   RGB(vec3(90,188,216))
#define BLUE4   RGB(vec3(116,204,244))

vec3 noise(vec3 p, float freq, float lod) {
    vec3 n = vec3(0.0);
    n += textureLod(iChannel2, p*freq, lod).xyz; freq *= 2.0;
    n += textureLod(iChannel2, p*freq, lod).xyz; freq *= 2.0;
    n += textureLod(iChannel2, p*freq, lod).xyz; freq *= 2.0;
    return n / 3.0;
}

float noise(vec2 p, float freq) {
    float n = 0.0;
    n += texture(iChannel1, p*freq).r; freq *= 2.0;
    n += texture(iChannel1, p*freq).r; freq *= 2.0;
    n += texture(iChannel1, p*freq).r; freq *= 2.0;
    n += texture(iChannel1, p*freq).r; freq *= 2.0;
    return n /4.0;
}

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

float smin(float a, float b, float k) {
    float h = clamp(0.5 + 0.5*(a-b)/k, 0.0, 1.0);
    return mix(a, b, h) - k*h*(1.0-h);
}

#define ENTITY_NONE 0
#define ENTITY_HULL 1
#define ENTITY_PILLAR 2
#define ENTITY_SAIL 3
#define ENTITY_PLANE 4

struct Data {
    vec3 point;
    vec3 normal;
    vec2 uv;
    float dist;
    int entity;
    vec3 entPos;
    float depth;
    vec3 reflection_dir;
    bool hit;
    int skip;
};

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

void dataInit(inout Data data) {
    data.point = vec3(0.);
    data.normal = vec3(0.);
    data.uv = vec2(0.);
    data.dist = FAR;
    data.entPos = vec3(0.);
    data.depth = 0.0;
    data.reflection_dir = vec3(0.0);
    data.hit = false;
    data.skip = -1;
}

float sphereSDF(vec3 p, vec3 spherePos, float radius) {
    return length(p - spherePos) - radius;
}

float coneSDF( vec3 p, vec2 c ) {
    float q = length(p.xz);
    return dot(c,vec2(q,p.y));
}

float cylinderSDF(vec3 p, vec3 a, vec3 b, 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 boatHullSDF(vec3 p) {
    float sphere = sphereSDF(p, vec3(0.), 1.);
    p.z *= 0.45;
   
    vec3 conePos = p;
    conePos.xy *= rot(radians(180.));
    
    conePos.y -= 1.5;
    float cone = coneSDF(conePos, vec2(0.7, 0.5));
    float slice = p.y-0.1;
    
    cone = max(cone, slice);
    
    return max(smin(sphere, cone, 0.3), slice) / (1.0+0.45);
}

float boatSDF(vec3 p, inout float hull2) {

    p.z *= 0.5;
    float hull = boatHullSDF(p);
    
    p.y += 0.4;
    p.z *= 1.0;
    p.x *= 1.1;
    hull2 = boatHullSDF(p) / (1.05);
    return max(-hull, hull2);
}

float boxSDF(vec3 p, vec3 s) {
    return length(max(abs(p) - s, 0.0));
}

float sailSDF(vec3 p) {
    float windT = T*0.05;
    p.xy -= max(0.0, 1.0 - p.z)*0.25*(noise((p*0.01) + vec3(cos(windT), sin(windT), sin(windT+0.215)), 1., 0.).xy*2.0-1.0);
    p.z += 2.6;
    p.x *= 5.;
    p.z *= 0.5;
    float slice = p.z-2.;
    p.zy *= rot(radians(90.));
    
    float cone = coneSDF(p, vec2(1., 0.5));
    cone = max(cone, slice);
    
    return cone / 6.;
}

float getDistWater(vec3 p, inout float waterHeight) {

    float offsetT = T*0.1;
    vec3 offset = vec3(cos(offsetT), sin(offsetT), sin(offsetT+0.2915)) * 0.1;
    p.z += T;
    vec3 n0 = noise(p/FAR, 0.5, 0.);
    vec3 n1 = noise(((p/FAR)+(n0*0.1))+offset, 1., 0.);
    vec3 n2 = noise(((p/FAR)*2.) - offset, 1., 1.);
    
    vec3 n = (n0 + n1 + n2) / 3.;
    
    waterHeight = n.y*2.5;
    
    p.y += 0.2;
    p.y -= waterHeight;
    
    return p.y+1.;
}

float getDist(vec3 p, inout int entity, inout vec3 entPos, int skip) {
    
    float waterHeight = 0.0;
    float plane = getDistWater(p, waterHeight);
    vec3 boatPos = vec3(0.0, -1.0, 0.0);
    
    boatPos.y += 0.25*(0.5+(0.5*cos(T)));
   
    float boatInside = 0.0;
    float boat = boatSDF(p + boatPos, boatInside);
    float cyl = cylinderSDF(p + boatPos, vec3(0.0, -1.5, 0.0), vec3(0.0, 3.0, 0.0), 0.1);
    float sail = sailSDF((p + boatPos) + vec3(0.0, -2.2, 1.3));
    
    plane = max(-boatInside, plane);
    
    float minDist = FAR;
    
    entity = ENTITY_NONE;
    
    if (cyl < minDist && skip != ENTITY_PILLAR) { entPos = -boatPos; entity = ENTITY_PILLAR; minDist = cyl; }
    if (sail < minDist && skip != ENTITY_SAIL) { entPos = -boatPos; entity = ENTITY_SAIL; minDist = sail; }
    if (boat < minDist && skip != ENTITY_HULL) { entPos = -boatPos; entity = ENTITY_HULL; minDist = boat; }
    if (plane < minDist && skip != ENTITY_PLANE) { entity = ENTITY_PLANE; minDist = plane; }
    
    return minDist;
}

vec3 getNormal(vec3 p, inout int entity, inout vec3 entPos, int skip) {
    vec2 e = vec2(0.1, 0.0);
    
    return normalize(getDist(p, entity, entPos, skip) - vec3(
        getDist(p + e.xyy, entity, entPos, skip),
        getDist(p + e.yxy, entity, entPos, skip),
        getDist(p + e.yyx, entity, entPos, skip)
    ));
}

vec2 getUv(in Data data) {
    vec3 p = data.point - data.entPos;
    vec3 n = data.normal;
    
    float up = abs(dot(n, vec3(0.0, 1.0, 0.0)));
    float left = abs(dot(n, vec3(1.0, 0.0, 0.0)));
    float front = abs(dot(n, vec3(0.0, 0.0, 1.0)));
    
    // unrolled "sort"
    int idx = 0;
    float maxDist = -100.;
    if (up > maxDist) { maxDist = up; idx = 0; }
    if (left > maxDist) { maxDist = left; idx = 1; }
    if (front > maxDist) { maxDist = front; idx = 2; }
    
    switch (idx) {
        case 0: return p.xz; break;
        case 1: return p.zy; break;
        case 2: return p.xy; break;
    }
    
    return p.xz;
}

vec3 getSailDiffuse(in Data data) {
    float n = texture(iChannel1, data.uv*0.09).r;
    float n2 = texture(iChannel1, n+(data.uv*0.09)).r;
    return clamp((vec3(n2 + n) / 3.0) + 0.45, 0.0, 1.0);
}

vec3 getPlaneDiffuse(in Data data) {
    data.point.z += T;
    vec3 n = noise(data.point*0.2, 1., 0.);
    vec3 a = mix(BLUE0, BLUE1, n.x);
    vec3 b = mix(BLUE1, BLUE2, n.y);
    vec3 c = mix(BLUE2, BLUE3, n.z);
    vec3 d = mix(c, BLUE4, (n.x+n.y+n.z)/3.);
    return (a+b+c+d)/3.0;
}

vec3 getDiffuse(in Data data) {
    switch (data.entity) {
        case ENTITY_HULL: return texture(iChannel0, data.uv).rgb; break;
        case ENTITY_SAIL: return getSailDiffuse(data); break;
        case ENTITY_PILLAR: return texture(iChannel0, data.uv).rgb; break;
        case ENTITY_PLANE: return getPlaneDiffuse(data); break;
        default: return vec3(0.); break;
    }
    return vec3(0.);
}

vec3 getBump(in Data data) {
    if (data.entity != ENTITY_HULL && data.entity != ENTITY_PILLAR) return data.normal;
    return extractBump(iChannel0, data.uv, data.normal, 0.008, 0.2);
}

bool march(vec3 ro, vec3 rd, inout Data data) {
    float dist = 0.0;
    data.dist = FAR;
    data.depth = 1.0;
    
    for (int i = ZERO; i < STEPS; i++) {
        vec3 p = ro+rd*dist;
        float next = getDist(p, data.entity, data.entPos, data.skip);
        dist += next;
        if (abs(next) <= NEAR || abs(dist) >= FAR) break;
    }

    if (dist >= FAR) return false;
    
    vec3 p = ro+rd*dist;
    data.point = p;
    data.dist = min(FAR, abs(dist));
    data.normal = getNormal(p, data.entity, data.entPos, data.skip);
    data.uv = getUv(data);
    data.depth = data.dist / FAR;
    data.hit = true;
    
    return true;
}

vec3 sky(vec3 ro, vec3 rd) {
    vec2 uv = rd.xz / rd.y;
    uv.xy += T*0.1;
    float cloud = noise(uv, 0.1);
    vec3 blue = mix(BLUE0, BLUE1, cloud);
    return blue+cloud;
}

vec3 render(vec3 ro, vec3 rd, inout Data data, float fog) {
    vec3 col = vec3(0.);
    
    Light light = Light(vec3(1., -1.0, -2.), vec3(1.), 8.);
    
    if (march(ro, rd, data)) {
        vec3 normal = getBump(data);
        vec3 L = normalize(light.pos);
        vec3 viewdir = normalize(data.point - ro);
        vec3 reflection_dir = normalize(reflect(viewdir, normal) * -1.);
        data.reflection_dir = reflection_dir;
        vec3 light_reflect_dir = reflect(-L, normal);
        float VdotR = max(dot(viewdir, light_reflect_dir), 0.0);
        vec3 albedo = vec3(1.);
        vec3 diffuse = getDiffuse(data);
        float NdotL = max(0.0, dot(normal, L));
        float lightDist = distance(data.point, light.pos);
        float spec = pow(VdotR,16.0);
        float att = NdotL;
        col += (diffuse + spec) * att;
    } else {
        col += sky(ro, rd);
    }
    
    return col + (data.depth*fog)*2.*BLUE2;
}

vec3 look(vec2 uv, vec3 point, vec3 ro) {
    vec3 lookAt = point;
    float zoom = 1.;
    vec3 camForward = normalize(lookAt - ro);
    vec3 camRight = -cross(camForward, vec3(0, 1, 0));
    vec3 camUp = -cross(camRight, camForward);
    vec3 screenCenter = ro + camForward * zoom;
    vec3 screenIntersection = screenCenter + uv.x * camRight + uv.y * camUp;
    return normalize(screenIntersection - ro);
}

void mainImage( out vec4 O, in vec2 fc )
{
    vec3 col = vec3(0.0);
    vec2 uv = (fc-0.5*R.xy)/R.y;
    vec2 m = (iMouse.xy-0.5*R.xy)/R.y;
    vec3 ro = vec3(0.0, 1.0, -10.0);
    vec3 rd = normalize(vec3(uv.xy, 1.0));
    
    if (iMouse.z > 0.01) {
        ro.yz *= rot(m.y*M_PI);
        rd.yz *= rot(m.y*M_PI);
        ro.xz *= rot(m.x*M_PI);
        rd.xz *= rot(m.x*M_PI);
    } else {
        float moveT = T*0.5;
        ro.y += (0.5+(0.5*(sin(moveT)))) * TAU;
        ro.x -= ((cos(moveT-0.5))) * TAU;
        rd = look(uv, vec3(0.0), ro);
    }
    
    ro.y = max(0.5, ro.y);
    rd = normalize(rd);
    
    Data data;
    dataInit(data);
    
    col += render(ro, rd, data, 1.0);
    
    if (data.hit && data.entity == ENTITY_PLANE) {
        Data refdata;
        dataInit(refdata);
        refdata.skip = data.entity;
        col /= M_PI;
        vec3 refCol = render(data.point, -data.reflection_dir, refdata, 0.0);
        col += refCol / M_PI;
    }
    
    col /= vec3(0.86238) + col;
    col = saturate(col, 1.1);
    col += luma(col) / TAU;
    col = pow(col, vec3(1.0 / 1.6));
    O = vec4(col, 1.0);
}
