in vec2 texCoord;
in vec3 rayDirection;

out vec4 fragColor;

uniform sampler2D texture0;
uniform sampler2D texture2;
uniform float time;

uniform float timeMultiplier;
uniform float invert;
uniform float rotation;
uniform float rotation2;
uniform float rotation3;
uniform float speed;
uniform float MAX_STEPS;
uniform float mengerdivisor;

uniform vec3 inCamPos;
uniform float camFov;
uniform vec3 camDirection;
uniform float camNear;
uniform float camFar;
uniform float outro;
uniform float carrotPosZ; 
uniform float showTunnel;
uniform float tunnelDeformAmount;
uniform float showGround;
uniform float tunnelMinZ;
uniform float tunnelMaxZ;
uniform float showHeartTunnel;
uniform float heartTunnelMinZ;
uniform float heartTunnelMaxZ;
uniform float showRainbowRoad;
uniform float rainbowRoadMinZ;
uniform float rainbowRoadMaxZ;
uniform float tunnel2ndColorMix;
uniform float showFakeShadow;
uniform vec2 fakeShadowPos;
uniform vec2 fakeShadowSize;
uniform float fakeShadowOpacity;
uniform vec2 fakeShadowWarpFreq;
uniform vec2 fakeShadowWarpAmp;
uniform float kick;
vec3 camPos;
vec3 surfacePos;

#define MAX_DIST 2000.0

#define PI 3.14159265359
#define BOTTOMSCALE 0.25
#define SURFACESCALE 0.15
mat2 Rot(float angle) 
{
	float s = sin(angle);
	float c = cos(angle);
	return mat2(c, -s, s, c);
}

float smin(float a, float b, float k) {
    float h = clamp(0.5 + 0.5*(a-b)/k, 0.0, 1.0);
    return mix(a, b, h) - k*h*(1.0-h);
}

float Cone( vec3 pos, vec3 rot, vec2 c, float h )
{
  vec3 p = pos;
  float q = length(p.xz);
  return max(dot(c.xy,vec2(q,p.y)),-h-p.y);
}

float Capsule( vec3 pos, vec3 rot, float h, float r )
{
  pos.y -= clamp( pos.y, 0.0, h );
  return length( pos ) - r;
}


float rand(vec2 p) {
	return fract(sin(dot(p, vec2(12.9898, 4.1414))) * 43758.5453);
}

float noiseTex(vec2 p, float scale) {

	vec2 n = floor(p*(scale));
	vec2 f = fract(p*(scale));
    f = vec2(smoothstep(0.,1.,f.x),smoothstep(0.,1.,f.y));
	float c1 = rand(n), c2 = rand(n+vec2(1.,0.)), c3 = rand(n+vec2(0.,1.)), c4 = rand(n+vec2(1.,1.));
    return mix(mix(c1,c2,f.x), mix(c3,c4,f.x), f.y);
}

vec4 sand(vec3 p) {
    vec3 sandColor = vec3(1.0,.45,.1);
	float sandPos = p.y - 1.6*noiseTex(p.zx, BOTTOMSCALE) + noiseTex(p.xz*vec2(2.5,1.),BOTTOMSCALE)/(100.+25.0*sin(time)) - noiseTex(p.xz*150.,BOTTOMSCALE)/350.;

    return vec4(sandPos, sandColor);
}

vec4 surface(vec3 p) {
    vec3 surfaceColor = vec3(0.0,.0,0.0)*1.7;

    if(camPos.y<surfacePos.y)
    {
        p.yz *= Rot(1.0*PI);
        surfaceColor = vec3(0.2,.25,1.0)*5.5;
    }

	float surfacePos = p.y - 1.6*noiseTex(p.zx-time*5.,SURFACESCALE) + noiseTex((p.xz+time*4.5)*vec2(1.5,1.),SURFACESCALE) ;
    
    return vec4(surfacePos, surfaceColor-vec3(.01*p.z));
}  

float Carrot( vec3 pos, vec3 rot, float r1, float r2, float h )
{
  // sampling independent computations (only depend on shape)

  pos.yz *= Rot(1.5*PI);
  float b = (r1-r2)/h;
  float a = sqrt(1.0-b*b);

  // sampling dependant computations
  vec2 q = vec2( length(pos.xz), pos.y );
  float k = dot(q,vec2(-b,a));
  if( k<0.0 ) return length(q) - r1;
  if( k>a*h ) return length(q-vec2(0.0,h)) - r2;
  return dot(q, vec2(a,b) ) - r1;
}

float Sphere(vec3 point, vec3 pos, float scale)
{ 
	return length(point - pos)-scale;
}

float Tunnel(vec3 p, float radius, float minZ, float maxZ, float deformAmount) {
    float animatedRadius = radius;;
    animatedRadius += sin(p.z * 0.5 - time * 2.0) * 0.3;
    animatedRadius += sin(p.z * 1.0 + time * 3.0) * 0.15;
    
    float circDist = abs(length(p.xy) - animatedRadius + kick*.5);
    
    float boxDist = abs(max(abs(p.x), abs(p.y)) - animatedRadius);
    
    float radialDist = mix(circDist, boxDist, deformAmount);
    
    float distFromCamera = p.z - camPos.z;
    float capNear = minZ - distFromCamera;
    float capFar = distFromCamera - maxZ;
    
    float tunnel = radialDist;
    tunnel = max(tunnel, capNear);
    tunnel = max(tunnel, capFar);
    
    return tunnel;
}


float RainbowRoad(vec3 p, float width, float minZ, float maxZ) {

    float planeDist = p.y;
    

    float distFromCamera = p.z - camPos.z;

    

    float widthDist = abs(p.x) - width;
    

    float capNear = minZ - distFromCamera;
    float capFar = distFromCamera - maxZ;
    
    float road = planeDist;
    road = max(road, widthDist);
    road = max(road, capNear);
    road = max(road, capFar);
    
    return road;
}

vec4 Union(vec4 a, vec4 b)
{
    return a.x < b.x ? a : b; 
}

vec4 GetDist(vec3 point)
{
    vec4 distObjects;

    vec3 objPos = point-vec3(0.0,-5.0,0.0);
    surfacePos = vec3(0.0,18.0,0.0);
    
    distObjects = vec4(MAX_DIST, 0.0, 0.0, 0.0);
    
    // Add tunnel if enabled
    if(showTunnel > 0.5) {
        vec3 tunnelPoint = point;
        tunnelPoint.xy -= inCamPos.xy;
        float tunnelDist = Tunnel(tunnelPoint, 15.0, tunnelMinZ, tunnelMaxZ, tunnelDeformAmount);
        vec3 tunnelColor1 = vec3(0.0, 0.6, 0.2);
        vec3 tunnelColor2 = vec3(1.0, 0.1, 1.0);
        vec3 tunnelColor = mix(tunnelColor1, tunnelColor2, tunnel2ndColorMix*(0.5 + 0.5 * sin(point.z * 0.5 - time * 2.0)));       
        tunnelColor -= vec3(1.0, 1.0, 1.0) * sin(point.z * 2.0 - time * 4.0) * 1.5;

        vec4 tunnel = vec4(tunnelDist, tunnelColor);
        distObjects = Union(distObjects, tunnel);
    }
    
    // Add rainbow road if enabled
    if(showRainbowRoad > 0.5) {
        vec3 roadPoint = point;
        roadPoint.y += 1.5;
        roadPoint.xy -= inCamPos.xy;
        float roadDist = RainbowRoad(roadPoint, 4.0, rainbowRoadMinZ, rainbowRoadMaxZ);

        float hue = fract((roadPoint.x * 0.08 + roadPoint.z * 0.15 - time * 0.5));
        vec3 rainbowColor;
        
        if(hue < 0.166) {
            rainbowColor = mix(vec3(1.0, 0.0, 0.0), vec3(1.0, 1.0, 0.0), hue * 6.0);
        } else if(hue < 0.333) {
            rainbowColor = mix(vec3(1.0, 1.0, 0.0), vec3(0.0, 1.0, 0.0), (hue - 0.166) * 6.0);
        } else if(hue < 0.5) {
            rainbowColor = mix(vec3(0.0, 1.0, 0.0), vec3(0.0, 1.0, 1.0), (hue - 0.333) * 6.0);
        } else if(hue < 0.666) {
            rainbowColor = mix(vec3(0.0, 1.0, 1.0), vec3(0.0, 0.0, 1.0), (hue - 0.5) * 6.0);
        } else if(hue < 0.833) {
            rainbowColor = mix(vec3(0.0, 0.0, 1.0), vec3(1.0, 0.0, 1.0), (hue - 0.666) * 6.0);
        } else {
            rainbowColor = mix(vec3(1.0, 0.0, 1.0), vec3(1.0, 0.0, 0.0), (hue - 0.833) * 6.0);
        }
        
        rainbowColor *= 0.8 + 0.2 * sin(roadPoint.x * 2.0 + roadPoint.z * 1.5);
        
 
            vec2 shadowWorldPos = vec2(fakeShadowPos.x, camPos.z + fakeShadowPos.y);
            
            vec2 warpedSize = fakeShadowSize;
            warpedSize.x *= 1.0 + sin(time * fakeShadowWarpFreq.x) * fakeShadowWarpAmp.x;
            warpedSize.y *= 1.0 + sin(time * fakeShadowWarpFreq.y + 1.3) * fakeShadowWarpAmp.y;
            
            vec2 delta = (roadPoint.xz - shadowWorldPos) / warpedSize;
            float ellipseDist = dot(delta, delta);
            
            float shadow = 1.0 - smoothstep(0.6, 1.0, ellipseDist);
            
            rainbowColor *= 1.0 - shadow * fakeShadowOpacity;

        
        vec4 road = vec4(roadDist, rainbowColor);
        distObjects = Union(distObjects, road);
    }

    return distObjects;
}


vec4 RayMarch(vec3 rayOrigin, vec3 rayDir)
{
    float distOrigin = camNear;
    vec3 distColor = vec3(.0,.0,.0);
	float SURFACE_DIST = .05; // this should be uniform calculated using camera fov
    vec3 bgColor = vec3(1.0,.0,.0);

    for(float i=0.; i<MAX_STEPS;i++)
    {
        vec3 pointOnRay = rayOrigin+rayDir*distOrigin;        
        vec4 distScene = GetDist(pointOnRay);
        distOrigin += distScene.x;
        if(distOrigin>=MAX_DIST) discard;
        if(distScene.x<SURFACE_DIST) break;

        distColor = mix (distScene.yzw, bgColor, distOrigin/MAX_DIST );

    }
    
    return vec4(distOrigin, distColor);
}

vec3 GetNormal(vec3 point)
{
    vec4 distColor = GetDist(point);
    float dist = distColor.x;
    vec2 e = vec2(.05,0.);
    vec3 normal = dist - vec3(
        GetDist(point-e.xyy).x, //e.xyy = 0.1,0,0
        GetDist(point-e.yxy).x,
        GetDist(point-e.yyx).x); 
        
    return normalize(normal);
}

float GetLight(vec3 point)
{
    
    vec3 lightPos = camPos+vec3(0.0,15.0,.0);
    //lightPos.xz-=vec2(sin(time),cos(time))*11.;
    vec3 light = normalize(lightPos-point);

    vec3 normal = GetNormal(point);
    float diffuse = clamp(dot(normal, light),0.0,1.0);
    diffuse +=0.5;
    

    return diffuse;
}




 
void main()
{
    camPos = inCamPos;
    if(showTunnel > 0.5)
    {
        camPos.z -= time*32.0;
    }
    vec4 dist = RayMarch(camPos, rayDirection); 
    vec3 point = camPos + rayDirection * dist.r;
    
    float diffuse = GetLight(point);
    vec3 col = vec3(dist.y*diffuse, dist.z*diffuse, dist.w*diffuse);

    float z =  (dist.x * dot(camDirection, rayDirection));
    float ndcDepth = -((camFar + camNear) / (camNear - camFar)) + ((2.0 * (camFar) * camNear) / (camNear - camFar)) / z;
    gl_FragDepth = ((gl_DepthRange.diff * ndcDepth) + gl_DepthRange.near + gl_DepthRange.far) / 2.;
    
    vec3 bgCol = vec3(0.12,0.22,0.62);
    if(outro > 0.5)
    {
        bgCol = vec3(1.5,0.9,0.0);
        col = mix(col, bgCol, (dist.x*dist.x)/(MAX_DIST*MAX_DIST));
    }
    else
    {
        col = mix(col, bgCol, (dist.x)/(MAX_DIST));
    }
    fragColor = vec4(col,1.0);
}