#version 330 core

in vec2 texCoord;
out vec4 fragColor;

uniform sampler2D texture0;
uniform vec4 color = vec4(1,1,1,1);

#define M_PI 3.1415926535897932384626433832795

uniform vec3 cameraEye = vec3(0,0,-3);
uniform vec3 cameraUp = vec3(0,1,0);
uniform vec3 cameraRight = vec3(1,0,0);
uniform vec4 backgroundColor = vec4(0,0,0,0);
uniform int rayMaxSteps = 200;
uniform float rayHitThreshold = 0.01f;
uniform float zFar = 10.0f;
uniform float time = 1.0f;
uniform float normalAccuracy = 0.001f;
uniform vec3 rotateDegrees = vec3(0.0);
uniform vec3 boxRotateDegrees = vec3(0.0);
uniform vec3 box2RotateDegrees = vec3(0.0);
uniform vec3 boxMove = vec3(1.5,0.0,1.5);
uniform vec3 movePosition = vec3(0.0);
uniform float pixelateResolution = 10000.0;
uniform float displacementMagnitude = 10;
uniform float displacementMultiplier = 0.00;

uniform float twistSkew = 0.0;
#define SCENE_TWISTER 1
uniform int scene = 2;
uniform float twisterTwists = 30.0;
uniform float twisterRotateY = 0.0;

uniform vec3 color1 = vec3(1);
uniform vec3 color2 = vec3(1);
uniform float shift = 1.0;
uniform float lightness = 2.0;


vec3 rotateY(vec3 v, float degrees)
{
    float radians = degrees*(M_PI/180.0);
    return vec3(v.x*cos(radians)+v.z*sin(radians), v.y, -v.x*sin(radians)+v.z*cos(radians));
}

float udRoundBox(vec3 p, vec3 b, float r)
{
  return length(max(abs(p)-b,0.0))-r;
}

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

float opDisplace(vec3 p, float d1)
{
    float displacement = (
        sin(displacementMagnitude*p.x)
        * sin(displacementMagnitude*p.y)
        * sin(displacementMagnitude*p.z)
    ) * displacementMultiplier;

    return d1+displacement;
}

mat3 rotateX(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(1, 0, 0),
        vec3(0, c, -s),
        vec3(0, s, c)
    );
}

mat3 rotateY(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, 0, s),
        vec3(0, 1, 0),
        vec3(-s, 0, c)
    );
}

mat3 rotateZ(float theta) {
    float c = cos(theta);
    float s = sin(theta);
    return mat3(
        vec3(c, -s, 0),
        vec3(s, c, 0),
        vec3(0, 0, 1)
    );
}


float opSmoothUnion( float d1, float d2, float k ) {
    float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
    return mix( d2, d1, h ) - k*h*(1.0-h);
}

float smin( float a, float b, float k )
{
    float h = a-b;
    return 0.5*( (a+b) - sqrt(h*h+k) );
}

float sdBox( vec3 p, vec3 b )
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}


float calculateDistanceMap(vec3 p)
{
    float result = 0.0f;
    p += movePosition;
    p = p*rotateX(radians(rotateDegrees.x))*rotateY(radians(rotateDegrees.y))*rotateZ(radians(rotateDegrees.z));

    if (scene == 1) {
        p = rotateY(p, twisterRotateY+p.y*twistSkew);

        float centreBall = length(p)-0.6f;
        result = length(p)-0.01f;
        for(float a = 0.0; a < 360.0; a+=45.0) {
            result = min(result, sdCone(rotateZ(radians(a))*(p)+vec3(0,-2.0,0), vec2(1.0,0.1), 1.5));
            result = min(result, sdCone((rotateX(radians(a))*(p))+vec3(0,-2.0,0), vec2(1.0,0.1), 1.5));
        }

        //float h = clamp(0.5f+0.5f*(centreBall-result)/metaStickyness,0.0f,1.0f)*1.0;
        //result = mix(centreBall,result,h)-metaStickyness*h*(1.0f-h);
        result = smin(centreBall, result, 0.01);
        vec3 position = vec3(0.0);
        result = opDisplace(p-position, result);
    } else if (scene == 2) {
        //p = vec3(0.0,0.0,-2.0);
        result = sdBox(rotateZ(radians(boxRotateDegrees.z))*p, vec3(0.5));
        result = min(result, sdBox(rotateZ(radians(boxRotateDegrees.z+box2RotateDegrees.z))*p+vec3(0.0,0.0,-boxMove.z), vec3(0.5)));
        result = min(result, sdBox(rotateZ(radians(boxRotateDegrees.z+box2RotateDegrees.z))*p+vec3(0.0,0.0,boxMove.z), vec3(0.5)));
        for(int i = 0; i < 4; i++) {
            result = min(result, sdBox(rotateZ(radians(boxRotateDegrees.z+box2RotateDegrees.z))*p+rotateZ(radians(90.0*i))*vec3(boxMove.x,0.0,0.0), vec3(0.5)));

            result = min(result, sdBox(rotateZ(radians(boxRotateDegrees.z+box2RotateDegrees.z))*p+rotateZ(radians(90.0*i))*vec3(boxMove.x,0.0,0.0)+vec3(0.0,0.0,-boxMove.z), vec3(0.5)));

            result = min(result, sdBox(rotateZ(radians(boxRotateDegrees.z+box2RotateDegrees.z))*p+rotateZ(radians(90.0*i))*vec3(boxMove.x,0.0,0.0)+vec3(0.0,0.0,boxMove.z), vec3(0.5)));
        }

    } else if (scene == 3) {
        /*if (scene == SCENE_TWISTER)
        {
            vec3 position = vec3(0.0,0.0,0.0);
            p = rotateY(p, twisterRotateY+p.y*twisterTwists);
            float d2 = udRoundBox(p-position, vec3(0.5, 10, 0.5), 0.3);

            result = opDisplace(p-position, d2);
        }*/

        float metaStickyness = 0.4f;
        float sphereSize = 0.4f;

        vec3 spheres[2];
        spheres[0].x = cos(time)*0.5f+1.5f;
        spheres[0].y = sin(time)*0.5f+1.5f;
        spheres[0].z = sin(time)*0.5f;
        spheres[1].x = sin(time)*0.5f-1.5f;
        spheres[1].y = cos(time)*0.5f-1.5f;
        spheres[1].z = sin(time)+0.5f;
        //spheres[2].x = cos(time)*0.5f-0.5f;
        //spheres[2].y = sin(time)*0.5f-0.5f;
        //spheres[2].z = cos(time)*0.5f+0.5f;
        //spheres[3].x = cos(time)*0.5f;
        //spheres[3].y = sin(time)*0.5f;
        //spheres[3].z = cos(time)*0.5f;
        //spheres[4].x = sin(time)*0.5f;
        //spheres[4].y = cos(time)*0.5f;
        //spheres[4].z = sin(time)*0.5f;

        float centreBall = length(p)-0.1f;
        float distanceToSolid = length(p)-0.2f;

        for(int i = 0; i < spheres.length(); i++)
        {
            float sa = length(p+spheres[i])-sphereSize;
            //metaball glue
            float h = clamp(0.5f+0.5f*(sa-distanceToSolid)/metaStickyness,0.0f,1.0f)*1.0;
            distanceToSolid = mix(sa,distanceToSolid,h)-metaStickyness*h*(1.0f-h);
        }
        result = distanceToSolid;

    }

    return result;
}

vec3 calculateNormal(vec3 p)
{
    vec3 vecX = vec3(normalAccuracy,0,0);
    vec3 vecY = vec3(0,normalAccuracy,0);
    vec3 vecZ = vec3(0,0,normalAccuracy);

    return normalize(vec3(
        calculateDistanceMap(p+vecX) - calculateDistanceMap(p-vecX),
        calculateDistanceMap(p+vecY) - calculateDistanceMap(p-vecY),
        calculateDistanceMap(p+vecZ) - calculateDistanceMap(p-vecZ))
    );
}

vec3 pixelate(vec3 coord, vec2 uv)
{
    float resolution = pixelateResolution*((1.0*distance(vec2(0.5),uv)))+2.0;
    vec3 d = vec3(1.0,1.0,1.0)/(vec3(resolution,resolution,resolution));
    coord.x = floor(coord.x/d.x)*d.x;
    coord.y = floor(coord.y/d.y)*d.y;
    coord.z = floor(coord.z/d.z)*d.z;
    return coord;    
}

vec4 raymarch(vec2 uv)
{
    vec3 cameraTarget = normalize(cross(cameraRight, cameraUp) + cameraRight*uv.s + cameraUp*uv.t);

    vec4 col = backgroundColor;

    float rayDistance = 0.0;
    for(int i = 0; i < rayMaxSteps; i++)
    {
        vec3 rayPosition = pixelate(cameraEye+cameraTarget*rayDistance, uv);
        float distanceToSolid = calculateDistanceMap(rayPosition);
        if (rayDistance > zFar)
        {
            break;
        }
        else if (distanceToSolid < rayHitThreshold)
        {
            vec3 normal = calculateNormal(rayPosition);
            col = vec4(abs(vec3((normal.r+normal.g+normal.b)/3.0)), 1.0);
            col.rgb *= mix(color1, color2, mod(shift, 1.0))*lightness;
            col.rgb = min(col.rgb, vec3(1.0));

            break;
        }

        rayDistance += distanceToSolid;
    }

    return col;
}

void main()
{
    vec2 coord = texCoord.st;
    vec2 uv = vec2(coord.x*2-1,coord.y*2-1);
    uv.s *= 1920.0/1080.0;

    fragColor = raymarch(uv)*color;
}
