#version 330

uniform vec2 resolution;
uniform float currentTime;
uniform vec3 camPos;
uniform vec3 camDir;
uniform vec3 camUp;
uniform sampler2D tex;
uniform bool showStepDepth;

in vec3 pos;

out vec3 color;

#define PI 3.1415926535897932384626433832795
#define RENDER_DEPTH 800
#define CLOSE_ENOUGH 0.00001
#define EPSILON 0.0001

#define BACKGROUND -1
#define BALL 0
#define BASE 1

#define GRADIENT(pt, func) vec3( \
    func(vec3(pt.x + 0.0001, pt.y, pt.z)) - func(vec3(pt.x - 0.0001, pt.y, pt.z)), \
    func(vec3(pt.x, pt.y + 0.0001, pt.z)) - func(vec3(pt.x, pt.y - 0.0001, pt.z)), \
    func(vec3(pt.x, pt.y, pt.z + 0.0001)) - func(vec3(pt.x, pt.y, pt.z - 0.0001)))

const vec3 LIGHT_POS[] = vec3[](vec3(5, 18, 10));

///////////////////////////////////////////////////////////////////////////////

vec3 getBackground(vec3 dir) {
  float u = 0.5 + atan(dir.z, -dir.x) / (2 * PI);
  float v = 0.5 - asin(dir.y) / PI;
  vec4 texColor = texture(tex, vec2(u, v));
  return texColor.rgb;
}

vec3 getRayDir() {
  vec3 xAxis = normalize(cross(camDir, camUp));
  return normalize(pos.x * (resolution.x / resolution.y) * xAxis + pos.y * camUp + 5 * camDir);
}

///////////////////////////////////////////////////////////////////////////////

float sphere(vec3 pt) {
  return length(pt) - 1;
}

float cube(vec3 p) {
	vec3 d = abs(p) - vec3(1); // 1 = radius
	return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
}

float cone(vec3 p, vec2 param)
{
    vec2 q = vec2(length(p.xz), p.y);
    return dot(q, normalize(param));
}


float torus(vec3 p, vec2 t) {
 vec2 q = vec2(
 length(p.xz) - t.x, p.y);
 return length(q) - t.y;
}

// smooth combination
float smin(float a, float b) {
 float k = 0.2;
 float h = clamp(0.5 + 0.5 * (b - a) / k, 0, 1);
 return mix(b, a, h) - k * h * (1 - h);
}


///////////////////////////////////////////////////////////////////////////////
float getSdf(vec3 pt)
{
	//return sphere(pt);
	return cone(pt, vec2(2, 1));
}


///////////////////////////////////////////////////////////////////////////////

vec3 getNormal(vec3 pt) {
  if (pt.y < -0.9999)
  {
    return vec3(0, 1, 0);
  }
  return normalize(GRADIENT(pt, SDF_FUNCTION));
}

vec3 getColor(vec3 pt) {
  if (pt.y < -0.9999)
  {
    float d = SDF_FUNCTION(pt);
    float d5 = mod(d, 5);
    if (d5 > 4.75)
    {
    	return vec3(0, 0, 0);
    }
    d = mod(d, 1);   
    return mix(vec3(0.4, 1, 0.4), vec3(0.4, 0.4, 1), d);
  }
  return vec3(1);
}

///////////////////////////////////////////////////////////////////////////////

float shadow(vec3 pt, vec3 lightDir, float lightDist) {
	float kd = 1;
	int step = 0;
	for (float t = 0.1;t < lightDist && step < RENDER_DEPTH && kd > CLOSE_ENOUGH; ) {
		float d = abs(SDF_FUNCTION(pt + t * lightDir));
		if (d < CLOSE_ENOUGH) {
			kd = 0;
		} else {
			kd = min(kd, 16 * d / t);
		}
		t += d;
		step++;
	}
	return kd;
}


float shade(const vec3 eye, vec3 pt, vec3 n) {
  float val = 0;

  for (int i = 0; i < LIGHT_POS.length(); i++) {
  
    // light intensity
    vec3 l = normalize(LIGHT_POS[i] - pt); 
    vec3 v = normalize(eye - pt);
    vec3 r = n * dot(l, n) * 2 - l;
    val += max(dot(n, l), 0); // diffuse
    val +=  pow(max(dot(v, r), 0), 256); // spec
    
    // shadow
    val = val * shadow(pt + n * EPSILON, l, length(LIGHT_POS[i] - pt));
  	
  }
  val += 0.1;  // Ambient
  return val;
}

vec3 illuminate(vec3 camPos, vec3 rayDir, vec3 pt) {
  vec3 c, n;
  n = getNormal(pt);
  c = getColor(pt);
  return shade(camPos, pt, n) * c;
}

///////////////////////////////////////////////////////////////////////////////

vec3 raymarch(vec3 camPos, vec3 rayDir) {
  int step = 0;
  float t = 0;

  for (float d = 1000; step < RENDER_DEPTH && abs(d) > CLOSE_ENOUGH; t += abs(d)) {
  
    // evaluate sdf at this point
    d = getSdf(camPos + t * rayDir);
    
    // add plane
    d = min(d, (camPos + t * rayDir).y + 1);
    
    step++;
  }

  if (step == RENDER_DEPTH) {
    return getBackground(rayDir);
  } else if (showStepDepth) {
    return vec3(float(step) / RENDER_DEPTH);
  } else {
    return illuminate(camPos, rayDir, camPos + t * rayDir);
  }
}

///////////////////////////////////////////////////////////////////////////////
void main() {
  color = raymarch(camPos, getRayDir());
}