#version 330 core

in vec2 texCoord;
in vec4 vertexFragColor;
out vec4 fragColor;
uniform sampler2D texture0;
uniform vec4 color = vec4(1,1,1,1);

//beers and greetings to iq and mercury for giving better comprehension about the subject

#define M_PI 3.1415926535897932384626433832795

uniform vec3 cameraEye = vec3(0,0,-2.5);
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 = 50;
uniform float rayHitThreshold = 0.001f;
uniform float zFar = 10.0f;
uniform float time = 1.0f;
uniform float normalAccuracy = 0.001f;

uniform vec3 displacementMagnitude = vec3(20,20,20);
uniform vec3 displacementMultiplier = vec3(0.01, 0.01, 0.01);
uniform float sphereSize = 0.8f;
uniform float miniSphereSize = 0.4f;
uniform float masterScale = 1.0f;

uniform vec3 spherePos1 = vec3(-0.5,-0.5,1);
uniform vec3 spherePos2 = vec3(0.5,0.5,1);
uniform vec3 spherePos3 = vec3(0.5,-0.5,1);
uniform vec3 spherePos4 = vec3(-0.5,0.5,1);
uniform vec3 spherePos5 = vec3(-0.5,0.5,1);

#define SCENE_METABALLS 1
uniform int scene = SCENE_METABALLS;

float rand(vec2 coord)
{
    return fract(sin(dot(coord.st,vec2(12.9898,78.233)+time)) * 43758.5453);
}

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));
}

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

float smin(float d1, float d2)
{
    float k = 0.6;
    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 udRoundBox(vec3 p, vec3 b, float r)
{
  return length(max(abs(p),0.0))-r;
}

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

float sdSphere1(vec3 p, float r)
{
    return length(p)-r;
    //return sdBox(p, vec3(0.3));
}

float sdSphere(vec3 p, float r)
{
    //return length(p)-r;
    return sdBox(p, vec3(0.3));
}

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

    return d1+displacement;
}

vec3 rot(vec3 p, vec3 sp)
{
    return rotateY(rotateX(p, time*200*p.x+sp.x*10), time*120*sp.y*10+p.y);
}

float calculateDistanceMap(vec3 p)
{
    float result = 0.0f;

    if (scene == SCENE_METABALLS)
    {
        //p = rotateX(rotateY(p, time*10+p.y),time*10+p.x);


        float sphere = sdSphere1(p, sphereSize*masterScale*1);

        float sphere1 = sdSphere1(rot(p+spherePos1, spherePos1), miniSphereSize*masterScale*2.5);
        float sphere2 = sdSphere(rot(p+spherePos2, spherePos2), miniSphereSize*masterScale*3);
        float sphere3 = sdSphere(rot(p+spherePos3, spherePos3), miniSphereSize*masterScale*3);
        float sphere4 = sdSphere(rot(p+p+spherePos4, spherePos4), miniSphereSize*masterScale*3);
        float sphere5 = sdSphere(rot(p+spherePos5, spherePos5), miniSphereSize*masterScale*3);

        result = 
            max(
                max(
                    max(
                        max(
                        max(-sphere, sphere1),
                        sphere2),
                    sphere3),
                sphere4),
            sphere5);

        result = sphere;
        //float sphere1 = sdSphere(rot(p+spherePos1, spherePos1), miniSphereSize*masterScale*2.5);


        //result = sphere1;
        //result = sdSphere(p, sphereSize*masterScale*1);
        vec3 p2 = p+rand(p.xy)*0.005;
        p2 = rotateX(rotateY(p2, time*40+p2.y),time*30+p2.x);
        p2.y += mod(time*4,4)*0.1;
        float result1 = sdBox(p2, vec3(1,0.1,1));
        result1 = min(result1,sdBox(p2+vec3(0,0.4,0), vec3(1,0.1,1)));
        result1 = min(result1,sdBox(p2+vec3(0,0.8,0), vec3(1,0.1,1)));
        result1 = min(result1,sdBox(p2+vec3(0,-0.4,0), vec3(1,0.1,1)));
        result1 = min(result1,sdBox(p2+vec3(0,-0.8,0), vec3(1,0.1,1)));

        //sphere1 = sdSphere(rot(p+spherePos1, spherePos1), vec3(20,20,20))

        result = opDisplace(p, result);
        result = max(result, result1);
        
    }

    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))
    );
}

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

    vec4 color = backgroundColor;

    float rayDistance = 0.0;
    for(int i = 0; i < rayMaxSteps; i++)
    {
        vec3 rayPosition = cameraEye+cameraTarget*rayDistance+rand(texCoord)*0.1;
        float distanceToSolid = calculateDistanceMap(rayPosition);
        if (rayDistance > zFar)
        {
            break;
        }
        else if (distanceToSolid < rayHitThreshold)
        {
            vec3 normal = calculateNormal(rayPosition);
            color = vec4(abs(vec3((normal.r+normal.g+normal.b)/3.0)), 1.0);
            break;
        }

        rayDistance += distanceToSolid;
    }

    return color;
}

void main()
{
    vec2 coord = texCoord;
    vec2 uv = vec2(coord.x*2-1,coord.y*2-1);

    fragColor = raymarch(uv)*color;
    //fragColor.a = texture(texture0, texCoord).a;
    //fragColor = texture(texture0, texCoord)*color;
}
