#version 330 core

in vec2 vUV;
out vec4 FragColor;

uniform vec3 iResolution; // x=width, y=height
uniform float iTime;
uniform float uAlpha;
uniform sampler2D iChannel0; // backbuffer if needed

uniform int uMaxSteps;
uniform float uMaxDistance;
uniform float uSurfaceDistance;
uniform float uTwistAmount;
uniform float uFlowSpeed;
uniform float uYOffset;
uniform float uGyroidScale1;
uniform float uGyroidScale2;
uniform float uGyroidThickness;
uniform float uGyroidBias1;
uniform float uGyroidBias2;
uniform float uSubtractStrength;
uniform float uDistanceScale;
uniform float uNormalEpsilon;
uniform float uCameraOrbitSpeed;
uniform float uCameraPitchAmount;
uniform float uCameraRollOffset;
uniform float uCameraDistance;
uniform float uFocalLength;
uniform float uBackgroundIntensity;
uniform float uFlameFrequency1;
uniform float uFlameFrequency2;
uniform float uFlameFrequency3;
uniform float uFogNear;
uniform float uFogFar;
uniform float uSurfaceContrast;
uniform float uMaskWidth;
uniform float uCrackBaseWidth;
uniform float uCrackNormalWidth;
uniform float uCrackScale1;
uniform float uCrackScale2;
uniform float uCrackSpeed1;
uniform float uCrackSpeed2;
uniform float uCrackIntensity;
uniform float uCrackPulse;
uniform float uGlowScale;
uniform float uGlowSpeed;
uniform float uHeightGlowDepth;
uniform vec3 uBlueColor;
uniform vec3 uHeightColor;

const int MAX_MARCH_STEPS = 256;

#define S(a, b, t) smoothstep(a, b, t)

float smoothMin(float a, float b, float k) {
  float h = clamp(0.5+0.5*(b-a)/k, 0. , 1.);
  return mix(b,a,h)-k*k*(1.0-h);
}

float smoothMax(float a, float b, float k) {
  float h = clamp(0.5+0.5*(b-a)/k, 0. , 1.);
  return max(b,a)-k*k*(1.0-h);
}

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

float sdCapsuel(vec3 p, vec3 a, vec3 b, float r) {
  vec3 ab = b-a;
  vec3 ap = p-a;

  float t = dot(ap, ab)/dot(ab, ab);
  t = clamp(t, 0.0, 1.0);

  vec3 c = a + t*ab;
  float d = length(p-c)-r;

  return d;
}

float sdTorus(vec3 p, vec2 r) {
  float x = length(p.xz)-r.x;
  float y = p.y;
  return length(vec2(x,y))-r.y;
}

float dBox(vec3 p, vec3 size) {
  float d = length(max(abs(p)-size, vec3(0.0)));
  return d;
}

float sdGyroid(vec3 p, float scale, float thickness, float bias) {
  p *= scale;
  float gyroid = abs(dot(sin(p), cos(p.zxy))-bias)/scale-thickness;
  return gyroid;
}

vec3 Transform(vec3 p) {
  p.xy *= Rotate(p.z * uTwistAmount);
  p.z -= iTime * uFlowSpeed;
  p.y -= uYOffset;
  return p;
}

float GetDist(vec3 p) {
  p = Transform(p);
  float dP = p.y;

  float bd = dBox(p, vec3(1.0));

  float gyroid = sdGyroid(p, uGyroidScale1, uGyroidThickness, uGyroidBias1);
  float gyroid2 = sdGyroid(p, uGyroidScale2, uGyroidThickness, uGyroidBias2);
  // mix / subtract
  gyroid -= gyroid2 * uSubtractStrength;
  float d = gyroid * uDistanceScale;
  return abs(d);
}

float RayMarch(vec3 ro, vec3 rd) {
  float dO = 0.0;
  int maxSteps = clamp(uMaxSteps, 1, MAX_MARCH_STEPS);
  for(int i=0; i<MAX_MARCH_STEPS; i++) {
    if (i >= maxSteps) break;
    vec3 p = ro + dO * rd;
    float dS = GetDist(p);
    dO += dS;
    if(dS < uSurfaceDistance || dO > uMaxDistance) break;
  }
  return dO;
}

vec3 GetNormal(vec3 p) {
  float d = GetDist(p);
  vec2 e = vec2(uNormalEpsilon, 0.0);
  vec3 n = d - vec3(
    GetDist(p - vec3(e.x, e.y, e.y)),
    GetDist(p - vec3(e.y, e.x, e.y)),
    GetDist(p - vec3(e.y, e.y, e.x))
  );
  return normalize(n);
}

float GetLight(vec3 p) {
  vec3 lightPos = vec3(3.0, 5.0, 4.0);
  vec3 l = normalize(lightPos - p);
  vec3 n = GetNormal(p);

  float diff = clamp(dot(n, l) * 0.5 + 0.5, 0.0, 1.0);
  return diff;
}

vec3 R(vec2 uv, vec3 p, vec3 l, float z) {
    vec3 f = normalize(l - p);
    vec3 r = normalize(cross(vec3(0.0,1.0,0.0), f));
    vec3 u = cross(f, r);
    vec3 c = p + f * z;
    vec3 i = c + uv.x * r + uv.y * u;
    vec3 d = normalize(i - p);
    return d;
}

vec3 Background(vec3 rd) {
  vec3 col = vec3(0.0);
  float y = rd.y * 0.5 + 0.5;
  float t = iTime;
  col += (1.0 - y) * vec3(1.0, 1.0, 1.0) * uBackgroundIntensity;
  float a = atan(rd.x, rd.z);
  float flames = sin(a * uFlameFrequency1 + t) * sin(a * uFlameFrequency2 - t) * sin(a * uFlameFrequency3 - t);
  flames *= S(0.8, 0.5, y);
  col += flames;
  col = max(col, vec3(0.0));
  col += S(0.5, 0.0, y);
  return col;
}

void mainImage(out vec4 O, in vec2 fragCoord) {
    vec2 uv = (fragCoord * 2.0 - iResolution.xy) / min(iResolution.x, iResolution.y);
    vec3 col = vec3(0.0);
    float t = iTime;

    vec3 ro = vec3(0.0, 0.0, uCameraDistance);
    ro.yz *= Rotate((-iTime * uCameraOrbitSpeed) * 3.141592 + uCameraPitchAmount);
    ro.xz *= Rotate((iTime * uCameraOrbitSpeed) * 6.2831853 + uCameraRollOffset);

    vec3 lookat = vec3(0.0);
    vec3 rd = R(uv, ro, lookat, uFocalLength);

    float d = RayMarch(ro, rd);

    if (d < uMaxDistance) {
      vec3 p = ro + rd * d;
      vec3 n = GetNormal(p);

      float height = p.y;
      p = Transform(p);

      float diff = n.y * 0.5 + 0.5;
      col += pow(diff, uSurfaceContrast);

      float g2 = sdGyroid(p, uGyroidScale2, uGyroidThickness, uGyroidBias2);
      col *= S(-uMaskWidth, uMaskWidth, g2);

      float crackWidth = uCrackBaseWidth + S(0.0, -0.5, n.y) * uCrackNormalWidth;
      float cracks = S(crackWidth, -0.03, g2);

      float g3 = sdGyroid(p + vec3(t * uCrackSpeed1), uCrackScale1, uGyroidThickness, 0.0);
      float g4 = sdGyroid(p - vec3(t * uCrackSpeed2), uCrackScale2, uGyroidThickness, 0.0);
      cracks *= g3 * g4 * uCrackIntensity + 0.2 * S(0.2, 0.0, n.y);

      col += cracks * uBlueColor * uCrackPulse * sin(iTime);

      float g5 = sdGyroid(p - vec3(0.0, t * uGlowSpeed, 0.0), uGlowScale, uGyroidThickness, 0.0);
      col += g5 * uBlueColor;

      col += S(0.0, -uHeightGlowDepth, height) * uHeightColor * vec3(1.0, sin(iTime), cos(iTime));
    }

    col = mix(col, Background(rd), S(uFogNear, uFogFar, d));
    O = vec4(col, 1.0);
}

void main() {
    vec2 fragCoord = vUV * iResolution.xy;
    vec4 outColor;
    mainImage(outColor, fragCoord);
    outColor.a *= uAlpha;
    FragColor = outColor;
}
