#version 450

#ifdef GL_ES
precision mediump float;
precision mediump int;
#endif

uniform vec2 resolution;
uniform vec3 sphereLocation1;
uniform vec3 sphereLocation2;
uniform vec3 cubeLocation;
uniform float sphereRadius1;
uniform float sphereRadius2;
uniform vec3 cubeSize;
uniform vec3 cubeOrientation;
uniform vec3 lightLocation;
vec3 cameraLocation = vec3(0.0, 0.0, 0.0);
float minDistance = 0.001;
float maxDistance = 40.0;
int maxHits = 10;

out vec4 fragColor;

void distToSphere1(in vec3 loc, out float dist) {
    dist = distance(loc, sphereLocation1) - sphereRadius1;
}

void distToSphere2(in vec3 loc, out float dist) {
    dist = distance(loc, sphereLocation2) - sphereRadius2;
}

void fromWorldtoCubeSpace(in vec3 global, out vec3 local) {
    vec3 r = cubeOrientation;
    mat3 rot = mat3(
        cos(r.y) * cos(r.z), cos(r.x) * sin(r.z) + sin(r.x) * sin(r.y) * cos(r.z), sin(r.x) * sin(r.z) - cos(r.x) * sin(r.y) * cos(r.z),
        -cos(r.y) * sin(r.z), cos(r.x) * cos(r.z) + sin(r.x) * sin(r.y) * sin(r.z), sin(r.x) * cos(r.z) - cos(r.x) * sin(r.y) * sin(r.z),
        sin(r.y), -sin(r.x) * cos(r.y), cos(r.x) * cos(r.y)
    );
    local = global * rot;
}

void fromCubeToWorldSpace(in vec3 local, out vec3 global) {
    vec3 r = cubeOrientation;
    mat3 rot = mat3(
        cos(r.y) * cos(r.z), cos(r.x) * sin(r.z) + sin(r.x) * sin(r.y) * cos(r.z), sin(r.x) * sin(r.z) - cos(r.x) * sin(r.y) * cos(r.z),
        -cos(r.y) * sin(r.z), cos(r.x) * cos(r.z) + sin(r.x) * sin(r.y) * sin(r.z), sin(r.x) * cos(r.z) - cos(r.x) * sin(r.y) * sin(r.z),
        sin(r.y), -sin(r.x) * cos(r.y), cos(r.x) * cos(r.y)
    );
    global = local * transpose(rot);
}

void distToCube(in vec3 loc, out float dist) {
    vec3 l;
    fromWorldtoCubeSpace(loc - cubeLocation, l);
    dist = sqrt(pow(max(0.0, abs(l.x) - cubeSize.x), 2.0) + pow(max(0.0, abs(l.y) - cubeSize.y), 2.0) + pow(max(0.0, abs(l.z) - cubeSize.z), 2.0));
}

void distToLight(in vec3 loc, out float dist) {
    dist = distance(loc, lightLocation) - minDistance * 5.0;
}

void distToGround(in vec3 loc, out float dist) {
    vec3 l = loc - vec3(0.0, -5.0, -20.0);
    dist = sqrt(pow(max(0.0, abs(l.x) - 10.0), 2.0) + pow(l.y, 2.0) + pow(max(0.0, abs(l.z) - 10.0), 2.0));
}

void trace(in vec3 start, in vec3 dir, out vec3 hit, out int obj, in int ignore) {
    obj = 0;
    vec3 ray = start;
    float travel = 0.0;
    do {
        float d = 0.0;
        float t = maxDistance;
        float ground, sphere1, sphere2, cube, light;
        if (ignore != 1) {
            distToGround(ray, ground);
            t = min(t, ground);
        }
        if (ignore != 2) {
            distToSphere1(ray, sphere1);
            t = min(t, sphere1);
        }
        if (ignore != 3) {
            distToSphere2(ray, sphere2);
            t = min(t, sphere2);
        }
        if (ignore != 4) {
            distToCube(ray, cube);
            t = min(t, cube);
        }
        if (ignore != 5) {
            distToLight(ray, light);
            t = min(t, light);
        }
        if (ignore != 1 && ground < minDistance) {
            obj = 1;
            break;
        }
        if (ignore != 2 && sphere1 < minDistance) {
            obj = 2;
            break;
        }
        if (ignore != 3 && sphere2 < minDistance) {
            obj = 3;
            break;
        }
        if (ignore != 4 && cube < minDistance) {
            obj = 4;
            break;
        }
        if (ignore != 5 && light < minDistance) {
            obj = 5;
            break;
        }
        ray += dir * t;
        travel += t;
    } while (travel < maxDistance);
    hit = ray;
}

void main() {
    vec2 uv = (gl_FragCoord.xy / resolution.yy) - vec2(resolution.x / resolution.y / 2.0, 0.5);
    vec3 color = vec3(1.0, 1.0, 1.0);
    vec3 dir = normalize(vec3(uv.xy, -1.0));
    
    vec3 start = cameraLocation, direction = dir;
    int hits = 0;
    int ignore = 0;
    float totalDistance = 0.0;
    do {
        int obj;
        vec3 hit;
        trace(start, direction, hit, obj, ignore);
        if (obj != 0) {
            hits++;
        }
        /*if (obj == 2) {
            color *= vec3((1.0 - hits * 0.1), 0.0, 0.0);
        } else if (obj == 3) {
            color *= vec3(0.0, (1.0 - hits * 0.1), 0.0);
        } else if (obj == 4) {
            color *= vec3(0.0, 0.0, (1.0 - hits * 0.1));
        } else {
            color *= (1.0 - hits * 0.1);
        }*/
        color *= (1.0 - float(hits) * 0.1);
        ignore = obj;
        totalDistance += distance(start, hit);
        start = hit;
        vec3 lightDir = normalize(lightLocation - hit);
        if (obj == 1) {
            vec3 normal = vec3(0.0, 1.0, 0.0);
            int shadowTest;
            vec3 tmp;
            trace(hit, lightDir, tmp, shadowTest, obj);
            if (shadowTest != 5) {
                color *= 0.4;
            } else {
                color *= (0.5 + (max(0.0, dot(lightDir, normal)))) * 0.8;
            }
            direction = reflect(direction, normal);
        } else if (obj == 2) {
            vec3 normal = normalize(hit - sphereLocation1);
            int shadowTest;
            vec3 tmp;
            trace(hit, lightDir, tmp, shadowTest, obj);
            if (shadowTest != 5) {
                color *= 0.4;
            } else {
                color *= (0.5 + (max(0.0, dot(lightDir, normal)))) * 0.8;
            }
            direction = reflect(direction, normal);
        } else if (obj == 3) {
            vec3 normal = normalize(hit - sphereLocation2);
            int shadowTest;
            vec3 tmp;
            trace(hit, lightDir, tmp, shadowTest, obj);
            if (shadowTest != 5) {
                color *= 0.4;
            } else {
                color *= (0.5 + (max(0.0, dot(lightDir, normal)))) * 0.8;
            }
            direction = reflect(direction, normal);
        } else if (obj == 4) {
            vec3 normal;
            vec3 l;
            fromWorldtoCubeSpace(hit - cubeLocation, l);
            l /= cubeSize;
            vec3 ld;
            fromWorldtoCubeSpace(direction, ld);
            if (abs(l.x) > abs(l.y) && abs(l.x) > abs(l.z)) {
                normal = -vec3(sign(ld.x), 0.0, 0.0);
            } else if (abs(l.y) > abs(l.x) && abs(l.y) > abs(l.z)) {
                normal = -vec3(0.0, sign(ld.y), 0.0);
            } else {
                normal = -vec3(0.0, 0.0, sign(ld.z));
            }
            vec3 tmp;
            fromCubeToWorldSpace(normal, tmp);
            normal = tmp;
            int shadowTest;
            trace(hit, lightDir, tmp, shadowTest, obj);
            if (shadowTest != 5) {
                color *= 0.4;
            } else {
                color *= (0.5 + (max(0.0, dot(lightDir, normal)))) * 0.8;
            }
            direction = reflect(direction, normal);
        } else if (obj == 5) {
            color *= 0.0;
            break;
        } else {
            break;
        }
    } while (hits < maxHits && totalDistance < maxDistance);
    
	fragColor = vec4(color, 1.0);
}