#version 120

//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,-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 = 100;
uniform float rayHitThreshold = 0.001f;
uniform float zFar = 10.0f;
uniform float time = 1.0f;
uniform float normalAccuracy = 0.001f;

uniform float displacementMagnitude = 10;
uniform float displacementMultiplier = 0.0;

#define SCENE_TWISTER 1
uniform int scene = SCENE_TWISTER;
uniform float twisterTwists = 30.0;
uniform float twisterRotateY = 0.0;
uniform float twisterRotateX = 0.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));
}

/*
    angle = DEG_TO_RAD(angle);
    float ca = (float)cos(angle);
    float sa = (float)sin(angle);
    point3d->y = point3d->y * ca - point3d->z * sa;
    point3d->z = point3d->y * sa + point3d->z * ca;
*/

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

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

    return d1+displacement;
}

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

    if (scene == SCENE_TWISTER)
    {
        vec3 position = vec3(0.0,0.0,0.0);
        p = rotateY(p, twisterRotateY+p.y*twisterTwists);
        p = rotateX(p, twisterRotateX+p.x*twisterTwists);
        float d2 = udRoundBox(p-position, vec3(1, 1, 1), 0.3);

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

    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;
        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 = gl_TexCoord[0].st;
    vec2 uv = vec2(coord.x*2-1,coord.y*2-1);

    gl_FragColor = raymarch(uv)*gl_Color;
}
