#version 330 core

uniform vec3 iResolution;
uniform float iTime;
uniform float uAlpha;
out vec4 FragColor;

const float MAX_TRACE_DISTANCE = 10.0;
const float INTERSECTION_PRECISION = 0.001;
const float FUDGE_FACTOR = 0.4;
const float PI = 3.14159;

const vec2 thetorus = vec2(1.0, 0.7);
const float scale   = 1.0;

vec2 rotate(vec2 p, float t) {
  return p * cos(t) + vec2(p.y, -p.x) * sin(t);
}

float torus(in vec3 p, in vec2 t, float A, float B, out vec2 uv) {
  float rho = atan(A, B);

  float theta = atan(p.y, p.x);
  float phi   = atan(p.z, length(p.xy) - t.x);
  uv = vec2(theta, phi);
  uv /= 2.0 * PI;

  uv = rotate(uv, rho);
  uv += 0.2 * iTime;
  float K    = sqrt(A * A + B * B);
  vec2  grid = vec2(K);
  uv *= grid;
  uv.y = round(uv.y);
  uv /= grid;
  uv -= 0.2 * iTime;
  uv = rotate(uv, -rho);
  theta = 2.0 * PI * uv.x;
  phi   = 2.0 * PI * uv.y;
  vec2 rad = vec2(cos(theta), sin(theta));
  vec3 r   = vec3(t.x * rad + t.y * cos(phi) * rad, t.y * sin(phi));
  return distance(p, r) - 0.1;
}

vec3 invertP(vec3 p, vec3 centre, float r2, inout float k) {
  p -= centre;
  float r = dot(p, p);
  p *= r2 / r;
  p += centre;
  k *= r / r2;
  return p;
}

float map(in vec3 p, float A, float B, out vec2 uv) {
  float k = 1.0;
  vec3 centre = vec3(sin(0.5 * iTime), 0.0, 0.0);
  p = invertP(p, centre, 1.0, k);
  float d = torus(p, thetorus, A, B, uv);
  return k * d;
}

vec3 transformP(in vec3 p) {
  p.yz = rotate(p.yz, iTime * 0.125);
  p.zx = rotate(p.zx, iTime * 0.2);
  return p;
}

vec3 h2rgb(float hue) {
  vec3 rgb = clamp(abs(mod(hue * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
  return rgb * (3.0 - 2.0 * rgb);
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  vec2 uvv = scale * (2.0 * fragCoord.xy - iResolution.xy) / iResolution.y;

  // Knot number cycles automatically with iTime
  float A = max(0.0, floor(8.0 - 4.0 * cos(0.2 * iTime)));
  float B = A + 1.0;

  vec3 ro = vec3(0.0, 0.0, 4.0);
  vec3 rd = normalize(vec3(uvv, -2.0));

  ro = transformP(ro);
  rd = transformP(rd);
  rd = normalize(rd);

  vec3  col  = pow(vec3(0.15, 0.0, 0.2), vec3(2.2));
  int   iter = int(20.0 / FUDGE_FACTOR);
  float t    = 0.0;
  vec2  uv;
  for (int i = 0; i < iter; i++) {
    if (t > MAX_TRACE_DISTANCE) break;
    float d = map(ro + rd * t, A, B, uv);
    vec3  c = h2rgb(uv.x + uv.y);
    float h = abs(d);
    t += max(INTERSECTION_PRECISION, h * FUDGE_FACTOR);
    col += c * pow(max(0.0, (0.02 - h) * 19.5), 10.0) * 150.0;
    col += c * 0.001 * FUDGE_FACTOR;
  }
  col = pow(col, vec3(1.0 / 1.8)) * 1.5;
  col = pow(col, vec3(1.5));
  col *= 3.0;
  fragColor = vec4(col, 1.0);
}

void main() {
  vec4 outCol;
  mainImage(outCol, gl_FragCoord.xy);
  outCol.a *= uAlpha;
  FragColor = outCol;
}

