#version 300 es
precision highp float;

const float CAM_FAR = 20.0;
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);

uniform float time;
uniform vec2 resolution;
uniform bool layer_snow;

in vec4 vColor;
layout (location = 0) out vec4 fragColor;

vec3 artifactOffset;
mat3 artifactRotation;
vec3 artifactAxis;
vec3 camFwd;
vec3 camUp;

float rand(float n) {
  return fract(sin(n) * 43758.5453);
}

float noise(float x) {
  float i = floor(x);
  float f = fract(x);
  return mix(rand(i), rand(i + 1.0), f * f * (3.0 - 2.0 * f));
}

mat4 viewMatrix(vec3 dir, vec3 up) {
  vec3 f = normalize(dir);
  vec3 s = normalize(cross(f, up));
  vec3 u = cross(s, f);

  return mat4(
    vec4(s, 0.0), 
    vec4(u, 0.0), 
    vec4(-f, 0.0), 
    vec4(0.0, 0.0, 0.0, 1.0)
  );
}

mat3 rotationAlign(vec3 d, vec3 z) {
  vec3 v = cross(z, d);
  float c = dot(z, d);
  float k = 1.0 / (1.0 + c);
  
  float k_vx = v.x * k;
  float k_vy = v.y * k;
  float k_vz = v.z * k;

  return mat3(
    k_vx * v.x + c, k_vx * v.y - v.z, k_vx * v.z + v.y,
    k_vy * v.x + v.z, k_vy * v.y + c, k_vy * v.z - v.x,
    k_vz * v.x - v.y, k_vz * v.y + v.x, k_vz * v.z + c
  );
}

float intersectPlane(vec3 origin, vec3 direction, vec3 point, vec3 normal) {
  return clamp(dot(point - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
}

vec3 calcRay(vec2 uv, float fov, float aspect) {
  uv = uv * 2.0 - 1.0;
  float d = 1.0 / tan(radians(fov) * 0.5);
  return normalize(vec3(aspect * uv.x, uv.y, d));
}

float octahedron(vec3 p, float s) {
  const float factor = 0.57735027;
  p = abs(p);
  return (p.x + p.y + p.z - s) * factor;
}

void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
  p -= artifactOffset;
  p = artifactRotation * p;
  float dist = octahedron(p, 0.8);
  const float glowDist = 4.8;
  if (dist < glowDist) {
    float d = dist + rand(dist) * 1.7;
    glowColor += vec3(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
  }
  if (dist < currDist) {
    currDist = dist;
    id = 1;
  }
}

float objects(vec3 p, inout vec3 glowColor, inout int objId) {
  float dist = CAM_FAR;
  artifact(p, dist, glowColor, objId);
  return dist;
}

float artifactDist(vec3 p) {
  p -= artifactOffset;
  p = artifactRotation * p;
  return octahedron(p, 1.2);
}

vec3 objectsNormal(vec3 p, float eps) {
  vec2 h = vec2(eps, 0);
  return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
}

vec3 objectsColor(int id, vec3 normal, vec3 ray) {
  return id == 1 ? vec3(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * 0.5 + 0.5) :
  id == 2 ? vec3(0.85, 0.65, 0.55) * 1.5 :
  vec3(1.0, 1.0, 0.0);
}

void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
  float dist = 0.0;
  int id;
  vec3 rayPos = eye;
  float depth = CAM_FAR;
  for (int i = 0; i < 30; i++) {
    dist = objects(rayPos, color.rgb, id);
    depth = distance(rayPos, eye);
    if (depth > wDepth || dist < 0.01) break;
    rayPos += ray * dist;
  }
  color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color;
}


vec3 march(vec2 uv, vec3 camPos) {
  mat4 vm = viewMatrix(camFwd, camUp);
  vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
  vec4 color = vec4(BACKGROUND, CAM_FAR);
  marchObjects(camPos, ray, color.w, color);
  return color.rgb;
}

float snow(vec2 uv, float scale) {
  float w = smoothstep(1.0, 0.0, -uv.y * (scale * 0.01));
  if (w < 0.1) return 0.0;

  float timeScale = time / scale;
  uv += vec2(timeScale, timeScale);
  uv.x += sin(uv.y + time * 0.125) / scale;
  uv *= scale;

  vec2 s = floor(uv);
  vec2 f = fract(uv);

  float sinValueX = sin((s.x + scale) * 7.0) * 5.0;
  float sinValueY = sin((s.y + scale) * 5.0) * 5.0;
  float combinedSinValue = sinValueX + sinValueY;

  float lengthValue = length(0.5 + 0.5 * sin(11.0 * fract(combinedSinValue)) - f);
  
  return smoothstep(0.0, min(lengthValue, 3.0), sin(f.x + f.y) * 0.01) * w;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord) {
  vec2 uv = fragCoord / resolution.xy;
  float s = sin(time);
  float c = cos(time);

  artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
  artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);

  camFwd = vec3(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
  camUp = vec3(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);

  fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.0);

  if (layer_snow) {
    vec2 uvSnow = (fragCoord * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
    fragColor += vec4(vec3(snow(uvSnow, 4.0)), 0.5) * 0.7;
  }
}

void main() {
  fragColor = vColor;
  mainImage(fragColor, gl_FragCoord.xy);
}
