//


#define MAX_ITERATIONS 60
#define SMALL_DISTANCE 0.01
#define INFINITE_DISTANCE 80.0

struct Surface{
    vec4 color;
    vec4 glow;
    float distance;
};

mat3 getRotationY(float angle) {
    float cosine = cos(angle);
    float sine = sin(angle);
    return mat3(
        cosine, 0.0,   sine,
         -sine, 0.0, cosine,
           0.0, 0.0,     1.0
    );
}

mat3 getRotationX(float angle) {
    float cosine = cos(angle);
    float sine = sin(angle);
    return mat3(
        1.0,    0.0,    0.0,
        0.0, cosine,  -sine,
        0.0,   sine, cosine
    );
}

float regularUnion(vec2 distances){
    return min(distances.x, distances.y);
}

float smoothUnion(vec2 distances, float smoothness)
{
    float degree = max(smoothness-abs(distances.x-distances.y), 0.0);
    float minimalDistance = min(distances.x, distances.y);
    return minimalDistance - pow(degree, 2.0) / smoothness * 0.25;
}

float smoothModifier = 0.8;
float glowDistance = 1.4;
float glowModifier = 0.04;

Surface getDistance(vec3 point){
    float speed = iTime / 2.0;
    vec3 baseCenter = vec3(0.0, 0.0, -16.0);
    float baseRadius = 3.0;
    vec4 baseColor = vec4(0.2, 0.05, 0.7, 1.0);
    vec4 baseGlow = vec4(0.0);
    float smoothDistance = length(point - baseCenter) - baseRadius;
    vec3 unitFromCenter = getRotationX(speed) * getRotationY(speed) * normalize(point - baseCenter) * baseRadius;
    smoothDistance -= baseRadius / 4.0 - cos(unitFromCenter.x) * cos(unitFromCenter.y) * cos(unitFromCenter.z) * 0.3;
    for (int index = 1; index <= 6; index++){
        float state =  speed * float(index) * 0.6;
        float x = sin(state + float(index - 3) * 2.0);
        float y = cos(state + float(index - 3) * 2.0);
        y *= 2.0 * x;
        float z = sin((state + float(index - 3) * 2.0) * 3.14) + 1.0;
        vec3 center = baseCenter + vec3(x * 2.0, y, z) * baseRadius * (cos(speed) * 0.1 + 1.0);
        float radius = baseRadius / 2.0 - float(index) / 8.0;
        vec3 fromCenter = point - center;
        float distanceToSurface = length(fromCenter) - radius;
        vec4 color = mix(vec4(0.5, 0.0, 0.2, 1.0), vec4(0.0, 0.2, 0.5, 1.0), 0.5 + float(index) / 12.0);
        if ((index == 1 || index == 3 || index == 6) && distanceToSurface  < glowDistance){
            vec2 projection = point.xy - center.xy;
            baseGlow = mix(color * glowModifier, baseGlow, abs(distanceToSurface) / glowDistance);
        }else{
            color *= mod(fromCenter.x - fromCenter.y, 0.2) < 0.1 ? 1.0 : 0.5;
        }
        baseColor = mix(baseColor, color, 0.5 + clamp(smoothDistance - distanceToSurface, -0.5, 0.5));
        smoothDistance = smoothUnion(vec2(smoothDistance, distanceToSurface), smoothModifier);
    }

    return Surface(baseColor, baseGlow, smoothDistance);

}

vec3 getNormal(vec3 point){
    vec2 unit = vec2(0.01, 0.0);
    float distanceToSurface = getDistance(point).distance;
    return normalize(vec3(distanceToSurface) - vec3(
    getDistance(point - unit.xyy).distance,
    getDistance(point - unit.yxy).distance,
    getDistance(point - unit.yyx).distance
    ));
}

Surface rayMarch(vec3 view, vec3 direction){
    float distance = 0.0;
    vec4 color = vec4(0.0);
    vec4 glow = vec4(0.0);
    bool trigger = false;
    for (int index = 0; index < MAX_ITERATIONS ; index++){
        vec3 point = view + direction * distance;
        Surface surface = getDistance(point);
        color = surface.color;
        distance += surface.distance;
        if (length(surface.glow) > 0.0){
            glow += surface.glow;
        }
        if (surface.distance < -SMALL_DISTANCE && !trigger){
            trigger = true;
        }
        if (distance > INFINITE_DISTANCE || (trigger && surface.distance > SMALL_DISTANCE)){
            break;
        }
    }
    return Surface(color, glow, distance);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float speed = iTime / 2.0;
    vec3 point = vec3((fragCoord.xy - 0.5 * iResolution.xy)/iResolution.y, -1.0f);
    vec3 view = vec3(vec2(0.0), 0.0);
    vec3 direction = normalize(point - view);
    vec3 lightDirection = normalize(vec3(cos(speed), 2.0, 1.0) * 8.0 - point);
    Surface object = rayMarch(view, direction);
    fragColor = vec4(0.06, 0.0, 0.18, 1.0) * dot(lightDirection, normalize(vec3(point.xy, abs(length(point.xy) + 0.1))));
    if (object.distance < INFINITE_DISTANCE){
        vec3 pointOnSurface = view + direction * object.distance;
        float light = clamp(dot(
        lightDirection,
        getNormal(pointOnSurface)
        ), 0.1, 1.0);
        fragColor = object.color * pow(light, 2.0);
    }
    if (length(object.glow) > 0.0){
        fragColor += object.glow;
    }
}
