// Created by mrange/2020
// License Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Unported License.

// The Live Coders Conference - Online on twitch.tv April 9, 2020 - https://captcalli.github.io/LiveCodersConf/
// Created a few shaders inspired by the online event, these shaders are not officially adopted just made for fun and to promote the event.

// 2D shape created by combining 2D primitives from IQ's blog:
//  https://iquilezles.org/articles/distfunctions2d
// pmin from IQ's blog on smooth minimum functions:
//  https://iquilezles.org/articles/smin


#define TOLERANCE       0.0001
#define MAX_RAY_LENGTH  5.0
#define MAX_BOUNCES     5
#define MAX_RAY_MARCHES 65

#define PI              3.141592654
#define TAU             (2.0*PI)

#define AA              0

#define TTIME           (TAU*iTime)

#define SCA(a) vec2(sin(a), cos(a))

const vec2 sca          = SCA(PI*4.5/6.0);
const vec3 lightPos1    = 100.0*vec3(-1.0, 0.0, 0.0);
const vec3 lightCol1    = vec3(0.63, 0.63, 1.0);
const vec3 spokeColor   = vec3(0.5, 0.5, 1.0);
const vec3 meepleColor  = vec3(0.8);
const vec3 bgColor      = vec3(0.1, 0.1, 0.25);


vec2 toPolar(vec2 p) {
  return vec2(length(p), atan(p.y, p.x));
}

vec2 toRect(vec2 p) {
  return vec2(p.x*cos(p.y), p.x*sin(p.y));
}

float mod1(inout float p, float size) {
  float halfsize = size*0.5;
  float c = floor((p + halfsize)/size);
  p = mod(p + halfsize, size) - halfsize;
  return c;
}

float pmin(float a, float b, float k) {
  float h = max(k-abs(a-b), 0.0)/k;
  return min(a, b) - h*h*k*(1.0/4.0);
}

float maxComp(vec3 p) {
  return max(p.x,max(p.y,p.z));
}

vec3 saturate(in vec3 a)   { return clamp(a, 0.0, 1.0); }
float saturate(in float a) { return clamp(a, 0.0, 1.0); }

float cylinder(vec3 p, float ra, float rb, float h) {
  h -= rb;
  vec2 d = vec2( length(p.xz)-2.0*ra+rb, abs(p.y) - h );
  return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rb;
}

float box(vec3 p, vec3 b, float r) {
  b -= r;
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}

float torus(in vec3 p, in vec2 sc, in float ra, in float rb) {
  p.x = abs(p.x);
  float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy);
  return sqrt( dot(p,p) + ra*ra - (2.0 - 0.000001)*ra*k ) - rb;
}

float spokes(vec3 p, float s) {
  vec2 pp = toPolar(p.xz);
  pp.y += TTIME/14.0;
  mod1(pp.y, TAU/10.0);
  pp.y += PI/2.0;
  p.xz = toRect(pp.xy);
  float ds = box(p, s*vec3(0.075, 0.125, 0.5), s*0.04);
  return ds;
}

float arcs(vec3 p, float s) {
  p.xyz = p.zxy*vec3(1.0, -1.0, 1.0);
  p.z += -0.25;
  float d1 = torus(p, sca, 0.275*s, 0.025*s);
  float d2 = torus(p, sca, 0.18*s, 0.025*s);
  return min(d1, d2);
}

float meeple(vec3 p, float s) {
  p.xyz = p.zxy*vec3(1.0, -1.0, 1.0);
  float dh = box(p - s*vec3(0.0, -0.035, 0.125), s*vec3(0.07, 0.1, 0.05), s*0.065);
  float dc = box(p - s*vec3(0.0, -0.22, 0.125), s*vec3(0.15, 0.04, 0.075), s*0.05);

  return pmin(dh, dc, s*0.115);
}

float intersectionRound(float a, float b, float r) {
  vec2 u = max(vec2(r + a,r + b), vec2(0));
  return min(-r, max (a, b)) + length(u);
}

float differenceRound(float a, float b, float r) {
  return intersectionRound(a, -b, r);
}

float theLiveCoder(vec3 p, float s, out float oda, out float odm, out vec3 col, out float ref, out float trans, out vec3 absorb) {
  float dr = cylinder(p, 0.375*0.5*s, 0.075*s, 0.125*s);
  float ds = spokes(p, s);
  float da = arcs(p, s);
  float dm = meeple(p, s);

  float nda = (da - 0.0125*s);
  float ndm = (dm - 0.0125*s);
  float nd  = min(nda, ndm);

  float d = dr;
  d = min(d, ds);
  float dw = d;
  d = differenceRound(d, nd, 0.02);
  d = min(d, da);
  d = min(d, dm);

  oda = da;
  odm = dm;

  vec2 pp = toPolar(p.xz);

  vec3 acol = saturate(spokeColor + 0.5*vec3(1.0, 0.0, 0.0)*sin(-TTIME/7.0 + PI*pp.x + 0.5) +  0.5*vec3(0.0, 1.0, 0.0)*sin(-TTIME/6.0 + 1.0*pp.y + 0.0));

  if (d == da) {
    col = acol;
    trans = 0.70;
  } else if (d == dm) {
    col = meepleColor;
    trans = 0.70;
  } else if (d != dw) {
    col = acol;
    trans = 0.5;
  } else {
    col = vec3(0.25, 0.25, 0.5);
    trans = 0.5;
  }

  ref = 0.25;
  absorb = mix(vec3(1.0), vec3(-5.0), col*col);

  return d;
}

float distanceField(in vec3 p, out float oda, out float odm, out vec3 col, out float ref, out float trans, out vec3 absorb) {
  p.xy = p.yx*vec2(-1.0, 1.0);

  float d = theLiveCoder(p, 2.0, oda, odm, col, ref, trans, absorb);

  return d;
}

vec3 getSkyColor(vec3 rayDir) {
  vec3 lightDir1 = normalize(lightPos1);

  float ld1      = max(dot(lightDir1, rayDir), 0.0);
  vec3 final     = 0.1*lightCol1;

  if ((rayDir.y > abs(rayDir.x)*1.0) && (rayDir.y > abs(rayDir.z*0.25))) final = vec3(2.0)*rayDir.y;
  float roundBox = length(max(abs(rayDir.xz/max(0.0,rayDir.y))-vec2(0.9, 4.0),0.0))-0.1;
  final += vec3(0.8)* pow(saturate(1.0 - roundBox*0.5), 6.0);

  vec3 light = 1.0*lightCol1;

  final += light*pow(ld1, 20.0);
  return final;
}

vec3 normal(in vec3 pos) {
  vec3 eps = vec3(.0001,0.0,0.0);
  vec3 col;
  float oda;
  float odm;
  float ref;
  float trans;
  vec3 nor;
  vec3 absorb;
  nor.x = distanceField(pos+eps.xyy, oda, odm, col, ref, trans, absorb) - distanceField(pos-eps.xyy, oda, odm, col, ref, trans, absorb);
  nor.y = distanceField(pos+eps.yxy, oda, odm, col, ref, trans, absorb) - distanceField(pos-eps.yxy, oda, odm, col, ref, trans, absorb);
  nor.z = distanceField(pos+eps.yyx, oda, odm, col, ref, trans, absorb) - distanceField(pos-eps.yyx, oda, odm, col, ref, trans, absorb);
  return normalize(nor);
}

float rayMarch(in float dmod, in vec3 ro, inout vec3 rd, float mint, float minstep, out int rep, out float oda, out float odm, out vec3 col, out float ref, out float trans, out vec3 absorb) {
  float t = mint;
  float distance;
  for (int i = 0; i < MAX_RAY_MARCHES; i++)
  {
    float distance_ = distanceField(ro + rd*t, oda, odm, col, ref, trans, absorb);
    distance = dmod*distance_;
    if (distance < TOLERANCE || t > MAX_RAY_LENGTH) break;
    t += max(distance, minstep);
    rep = i;
  }

  if (distance > TOLERANCE) return MAX_RAY_LENGTH;

  return t;
}

vec3 postProcess(in vec3 col, in vec2 p) {
  col=pow(clamp(col,0.0,1.0),vec3(.75));
  col=col*0.6+0.4*col*col*(3.0-2.0*col);
  col=mix(col, vec3(dot(col, vec3(0.33))), -0.4);
  return col;
}

vec3 render(in vec3 ro, in vec3 rd) {
  vec3 lightPos = 1.5*vec3(1.0, 3.0, 1.0);

  vec3 final  = vec3(0.0);

  vec3 ragg   = vec3(1.0);

  float tdist = 0.0;

  float refraction = 1.3;

  bool inside = false;

  const float mint    = 0.05;
  const float minstep = 0.00025;

  for (int i = 0; i < MAX_BOUNCES; ++i)
  {
    if (maxComp(ragg) <  0.01) break;
    float dmod  = inside ? -1.0 : 1.0;
    vec3 absorb ;
    float oda   ;
    float odm   ;
    vec3 col    ;
    float ref   ;
    float trans ;
    int rep     ;
    float t     = rayMarch(dmod, ro, rd, mint, minstep, rep, oda, odm, col, ref, trans, absorb);
    tdist       += t;

    vec3 pos    = ro + t*rd;

    vec3 nor = vec3(0.0, 1.0, 0.0);

    if (t < MAX_RAY_LENGTH) {
      nor = normal(pos);
    } else {
      final += ragg*getSkyColor(rd);
      break;
    }

    float fresnel = pow(1.0 - abs(dot(nor, rd)), 2.0);

    ref = mix(ref, 1.0, fresnel);
    trans = mix(trans, 0.0, fresnel);

    float mref = refraction;

    if (inside) {
      nor = -nor;
      mref = 1.0/refraction;
    }

    vec3 refl = reflect(rd, nor);
    vec3 refr = refract(rd, nor, mref);

    vec3  lv  = lightPos - pos;
    vec3  ld  = normalize(lv);
    float ll  = length(lv);

    float dif = max(dot(nor,ld),0.0);
    float occ = 1.0 - float(rep)/float(MAX_RAY_MARCHES);
    float l   = dif*occ;

    vec3 lr   = vec3(0.0);

    float lin = mix(0.2, 1.0, l);

    vec3 sky  = getSkyColor(refl);
    vec3 mcol = mix(lin*col + lr, sky, ref);
    mcol += 0.5*spokeColor*tanh(1.0/(20.0*(max(oda, 0.05))));
    mcol += meepleColor*0.35*tanh(1.0/(100.0*(pow(max(odm, 0.025), 1.5))));

    vec3 beer = vec3(1.0, 1.0, 1.0);

    if (inside) {
      beer = exp(-absorb*t);
    }

    final      += (1.0 - trans)*ragg*beer*mcol;
    ragg       *= trans*beer;

    ro        = pos;

    if (refr == vec3(0.0)) {
        rd = refl;
    } else {
      rd = refr;
      inside = !inside;
    }
  }

  return final;
}

vec3 getSample(in vec2 p) {
  vec3 ro  = vec3(2.5, 0.0, 0.0) + 0.5*vec3(0.0, sin(TTIME/33.0), cos(TTIME/36.0));;

  vec3 la  = vec3(0.0) + 0.125*vec3(0.0, sin(TTIME/30.0), cos(TTIME/27.0));

  vec3 ww = normalize(la - ro);
  vec3 uu = normalize(cross(vec3(0.0,1.0,0.0), ww ));
  vec3 vv = normalize(cross(ww,uu));
  vec3 rd = normalize( p.x*uu + p.y*vv + 2.0*ww );

  vec3 col = render(ro, rd);

  return col;
}


float box2(vec2 p, vec2 b, float r) {
  b -= r;
  vec2 d = abs(p)-b;
  return length(max(d,0.0)) + min(max(d.x,d.y),0.0) - r;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  vec2 q=fragCoord.xy/iResolution.xy;
  vec2 p = -1.0 + 2.0*q;
  p.x *= iResolution.x/iResolution.y;

  float db = box2(p, vec2(0.88), 0.3);

  if (db > 0.0) {
    float m = smoothstep(0.1, 0.105, db);
    fragColor = mix(vec4(1.0, 1.0, 1.0, 1.0), vec4(bgColor, 1.0), m);
    return;
  }

#if AA == 0
  vec3 col = getSample(p);
#elif AA == 1
  vec3 col  = vec3(0.0);
  vec2 unit = 1.0/iResolution.xy;
  for(int y = 0; y < 2; ++y)
  {
    for(int x = 0; x < 2; ++x)
    {
      col += getSample(p - 0.5*unit + unit*vec2(x, y));
    }
  }

  col /= 4.0;
#endif
  col = postProcess(col, p);
  float bb = smoothstep(0.005, 0.125, abs(db));
  col = mix(vec3(0.125), col, pow(bb, 0.350));
  float wb = smoothstep(0.0, 0.005, abs(db));
  col = mix(vec3(1.0), col, wb);
  col = mix(bgColor, col, smoothstep(0.0, 16.0, iTime*iTime));
  fragColor = vec4(col, 1.0);
}
