/*
* Created by erkan612 - 02.12.2020
* * Discord: erkan612#9667
*/

const vec3 BACKGROUND_COLOR = vec3(0.0, 1.0, 0.0);
const vec3 DEFAULT_MESH_COLOR = vec3(0.8);
const int MAX_STEPS = 150;
const float SURFACE_DISTANCE = 0.01;
const float MAX_RENDER_DISTANCE = 40.0;
const int MAX_REFLECTION_STEPS = 20;
const float FOG_START = 20.0;
const float FOG_END = 50.0;
const vec3 FOG_COLOR = vec3(1.0);
const vec3 SKY_DOWN_COLOR = vec3(1.0);
const vec3 SKY_UP_COLOR = vec3(0.7, 0.8, 1.0)*1.15;
const vec3 SEA_BASE_COLOR = vec3(0.1,0.2,0.3)*1.2;
const vec3 SEA_WATER_COLOR = vec3(0.5)*0.5;

// Ocean Displacement Req
float Hash(vec2);
float Noise(vec2);
float SeaHard(vec2, float);
float SeaHeight(vec2, float, int, float);

// Ocean
vec3 GetOceanColor(vec3, vec3, vec3);

// Secene Maps
vec4 MAP_Scene(vec3, vec3);
vec4 MAP_DETAILED_Scene(vec3);
vec4 MAP_REFLECTION_Scene(vec3, vec3);
vec4 MAP_SIMPLE_Scene(vec3);

// N
vec3 GetNormal(vec3);
vec3 GetNormalForReflection(vec3);
vec3 Diffuse(vec3, vec3, int, vec3);
vec3 Specular(vec3, vec3, vec3, vec3, float);

// Ray Tracing
vec3 RayMarch(vec3, vec3);
vec3 REFLECTION_RayMarch(vec3, vec3);

void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
    vec2 uv = (fragCoord-0.5*iResolution.xy)/min(iResolution.x, iResolution.y);
    float t = float(iTime/75.0);
    float d = 12.0;
    //t = 0.0;
    float pitorad = 180.0/3.1415;
    
    vec3 ro = vec3(cos(t*pitorad) * d, 0.3*d, sin(t*pitorad)*d);
    //vec3 ro = vec3(0.0, 1.0, -3.0);
    vec3 ta = vec3(0.0, 0.0, 0.0);
    
    vec3 cz = normalize(ta - ro);
    vec3 cx = normalize(cross(vec3(0.0, 1.0, 0.0), cz));
    vec3 cy = normalize(cross(cz, cx));
    vec3 rd = normalize(cx * uv.x + cy * uv.y + cz * 1.0);
    vec3 color = RayMarch(ro, rd);
    
    fragColor = vec4(color, 1.0);
}

// Ocean Displacement Req
float Hash(vec2 p) {
	return fract(sin(dot(p, vec2(5.5, 8.5)*84.5))*845.8425);
}

float Noise(vec2 p) {
	vec2 lv = smoothstep(0.0, 1.0, fract(p));
    vec2 id = floor(p);
    
    float bl = Hash(id);
    float br = Hash(id+vec2(1.0, 0.0));
    float b = smoothstep(0.0, 1.0, mix(bl, br, lv.x));
    
    float tl = Hash(id+vec2(0.0, 1.0));
    float tr = Hash(id+vec2(1.0, 1.0));
    float t = smoothstep(0.0, 1.0, mix(tl, tr, lv.x));
    
    return mix(b, t, lv.y);
}

float SeaHard(vec2 p, float hard) {
    p += Noise(p);
    vec2 aw = 1.0-abs(sin(p));
    vec2 bw = abs(cos(p));
	aw = mix(aw,bw,aw);
    return pow(1.0-pow(aw.x*aw.y,0.65), hard);
}

float SeaHeight(vec2 p, float timeShift, int detail, float freqSize) {
	float h = 0.0;
    float hard = 4.0;
    float freq = 0.1 * freqSize;
    float hh = 1.0;
    
    for (int i = 0; i < detail; i++) {
    	float d = SeaHard((p+timeShift)*freq, hard);
    	d += SeaHard((p-timeShift)*freq, hard);
        h += d * hh;
        hard = mix(hard, 1.0, 0.2);
        hh *= 0.22;
        freq *= 1.9;
        p *= vec2(1.66, -1.60);
    };
    
    return 1.0-h;
}

// Ocean
vec3 GetOceanColor(vec3 p, vec3 n, vec3 eye) {
    vec3 color = DEFAULT_MESH_COLOR;
    vec3 eyeVec = normalize(eye - p);
    
    float fresnel = 1.0-max(dot(eyeVec, n), 0.0);
    fresnel = pow(fresnel, 3.0)*0.4;
    
    vec3 diff = Diffuse(normalize(vec3(1.0, 1.0, 0.2)), n, 1, vec3(1.0));
    
    vec3 reflected = vec3(1.0);
    vec3 refracted = SEA_BASE_COLOR + diff * SEA_WATER_COLOR * 0.15;
    color = mix(refracted, reflected, fresnel);
    
    color += SEA_WATER_COLOR * p.y * 0.5;
    
    color += Specular(normalize(vec3(1.0, 1.0, 0.2)), n, eyeVec, vec3(1.0), 64.0);
    
	return color;
}

// Scene Maps
vec4 MAP_REFLECTION_Scene(vec3 p, vec3 eye, int mode) { // Main Reflection Scene
	float fulldist = 9999.0;
    float dist = fulldist;
    vec3 color = DEFAULT_MESH_COLOR;
    vec3 n = mode == 1 ? GetNormalForReflection(p) : vec3(0.0);
    vec3 diff = mode == 1 ? Diffuse(normalize(vec3(1.0, 1.0, 0.2)), n, 1, vec3(1.0)) : vec3(1.0);
    
    // Red Sphere
    {
        vec3 sphereColor = vec3(1.0, 0.0, 0.0);
        float sphereRadius = 1.0;
        vec3 spherePosition = vec3(0.0, 1.0, 0.0);
        float sphereDist = length(p-spherePosition)-sphereRadius;
        if (sphereDist < dist) {
            color = sphereColor*diff;
            dist = sphereDist;
        }
    }
    
    // Ocean
    {
        vec3 oceanColor = mode == 1 ? GetOceanColor(p, n, eye) : vec3(1.0);
        float oceanHeight = 0.0;
        vec3 oceanP = p + SeaHeight(p.xz, float(iTime), 1, 1.0);
        float oceanDist = dot(oceanP, vec3(0.0, 1.0, 0.0))-oceanHeight;
        if (oceanDist < dist) {
            color = oceanColor;
            dist = oceanDist;
        }
    }
    
    return vec4(color, dist);
}

vec4 MAP_Scene(vec3 p, vec3 eye, int mode) { // Main Scene
	float fulldist = 9999.0;
    float dist = fulldist;
    vec3 color = DEFAULT_MESH_COLOR;
    vec3 n = mode == 1 ? GetNormal(p) : vec3(0.0);
    vec3 diff = mode == 1 ? Diffuse(normalize(vec3(1.0, 1.0, 0.2)), n, 1, vec3(1.0)) : vec3(1.0);
    
    // Red Sphere
    {
        vec3 sphereColor = vec3(1.0, 0.0, 0.0);
        float sphereRadius = 1.0;
        vec3 spherePosition = vec3(0.0, 1.0, 0.0);
        float sphereDist = length(p-spherePosition)-sphereRadius;
        if (sphereDist < dist) {
            vec3 reflected = mode == 1 ? REFLECTION_RayMarch(p+n*SURFACE_DISTANCE, reflect(-normalize(eye-p), n)) : vec3(1.0);
            color = mix(sphereColor, reflected, 0.5)*diff;
            dist = sphereDist;
        }
    }
    
    // Ocean
    {
        vec3 oceanColor = mode == 1 ? GetOceanColor(p, n, eye) : vec3(1.0);
        float oceanHeight = 0.0;
        vec3 oceanP = p + SeaHeight(p.xz, float(iTime), 2, 1.0);
        float oceanDist = dot(oceanP, vec3(0.0, 1.0, 0.0))-oceanHeight;
        if (oceanDist < dist) {
            vec3 reflected = mode == 1 ? REFLECTION_RayMarch(oceanP+n*SURFACE_DISTANCE, reflect(-normalize(eye-oceanP), n)) : vec3(1.0);
            color = mix(oceanColor, reflected, max(dot(normalize(eye-p), n), 0.0)/5.0);
            dist = oceanDist;
        }
    }
    
    return vec4(color, dist);
}

vec4 MAP_DETAILED_Scene(vec3 p) { // Detailed Main Secene
	float fulldist = 9999.0;
    float dist = fulldist;
    vec3 color = DEFAULT_MESH_COLOR;
    
    // Red Sphere
    {
        vec3 sphereColor = vec3(1.0, 0.0, 0.0);
        float sphereRadius = 1.0;
        vec3 spherePosition = vec3(0.0, 1.0, 0.0);
        float sphereDist = length(p-spherePosition)-sphereRadius;
        if (sphereDist < dist) {
            color = sphereColor;
            dist = sphereDist;
        }
    }
    
    // Ocean
    {
        vec3 oceanColor = vec3(1.0);
        float oceanHeight = 0.0;
        vec3 oceanP = p + SeaHeight(p.xz, float(iTime), 5, 1.0);
        float oceanDist = dot(oceanP, vec3(0.0, 1.0, 0.0))-oceanHeight;
        if (oceanDist < dist) {
            color = oceanColor;
            dist = oceanDist;
        }
    }
    
    return vec4(color, dist);
}

vec4 MAP_SIMPLE_Scene(vec3 p) {
	float fulldist = 9999.0;
    float dist = fulldist;
    vec3 color = DEFAULT_MESH_COLOR;
    
    // Red Sphere
    {
        vec3 sphereColor = vec3(1.0, 0.0, 0.0);
        float sphereRadius = 1.0;
        vec3 spherePosition = vec3(0.0, 1.0, 0.0);
        float sphereDist = length(p-spherePosition)-sphereRadius;
        if (sphereDist < dist) {
            color = sphereColor;
            dist = sphereDist;
        }
    }
    
    // Ocean
    {
        vec3 oceanColor = vec3(1.0);
        float oceanHeight = 0.0;
        vec3 oceanP = p + SeaHeight(p.xz, float(iTime), 1, 1.0);
        float oceanDist = dot(oceanP, vec3(0.0, 1.0, 0.0))-oceanHeight;
        if (oceanDist < dist) {
            color = oceanColor;
            dist = oceanDist;
        }
    }
    
    return vec4(color, dist);
}

// N
vec3 GetNormal(vec3 p) {
	vec2 e = vec2(SURFACE_DISTANCE, 0.0);
    float dist = MAP_DETAILED_Scene(p).w;
    return normalize(dist-vec3(
    	MAP_DETAILED_Scene(p-e.xyy).w,
        MAP_DETAILED_Scene(p-e.yxy).w,
        MAP_DETAILED_Scene(p-e.yyx).w
    ));
}
vec3 GetNormalForReflection(vec3 p) {
	vec2 e = vec2(SURFACE_DISTANCE, 0.0);
    float dist = MAP_SIMPLE_Scene(p).w;
    return normalize(dist-vec3(
    	MAP_SIMPLE_Scene(p-e.xyy).w,
        MAP_SIMPLE_Scene(p-e.yxy).w,
        MAP_SIMPLE_Scene(p-e.yyx).w
    ));
}

vec3 Diffuse(vec3 l, vec3 n, int normalized, vec3 color) {
	return (normalized == 1 ? dot(l,n)*0.5+0.5 : max(dot(l,n), 0.0)) * color;
}

vec3 Specular(vec3 l, vec3 n, vec3 eyeVec, vec3 color, float s) {
	return pow(max(dot(normalize(reflect(-eyeVec, n)), l), 0.0), s)*color;
}

// Ray Tracing
vec3 REFLECTION_RayMarch(vec3 ro, vec3 rd) {
	vec3 color = vec3(0.5);
    float st = 1.0/float(MAX_REFLECTION_STEPS);
    vec3 p = vec3(0.0);
    
    for (int i = 0; i < MAX_REFLECTION_STEPS; i++) {
    	p = ro + rd*st;
        vec4 mesh = MAP_REFLECTION_Scene(p, ro, 0);
        
        // Scene Color
        if (mesh.w <= SURFACE_DISTANCE) {
            mesh = MAP_REFLECTION_Scene(p, ro, 1);
            vec3 sceneColor = mesh.xyz;
        	
             if (st>=FOG_START) {
            	float nrmlz = st - FOG_START;
                nrmlz /= FOG_END - FOG_START;
                sceneColor = mix(sceneColor, FOG_COLOR, pow(nrmlz, 1.0));
            }
            
            color = sceneColor;
            break;
        }
        
        if (st >= MAX_RENDER_DISTANCE) {
        	break;
        }
        
        st += mesh.w;
    }
    
    // Sky(Background) Color
    if (st >= MAX_RENDER_DISTANCE) {
    	color = mix(SKY_DOWN_COLOR, SKY_UP_COLOR, pow(abs(p.y), 0.1));
    }
    
    return color;
}

vec3 RayMarch(vec3 ro, vec3 rd) {
	vec3 color = BACKGROUND_COLOR;
    float st = 0.0;
    vec3 p = vec3(0.0);
    
    for (int i = 0; i < MAX_STEPS; i++) {
    	p = ro + rd*st;
        vec4 mesh = MAP_Scene(p, ro, 0);
        
        // Scene Color
        if (mesh.w <= SURFACE_DISTANCE) {
            mesh = MAP_Scene(p, ro, 1);
            vec3 sceneColor = mesh.xyz;
        	
             if (st>=FOG_START) {
            	float nrmlz = st - FOG_START;
                nrmlz /= FOG_END - FOG_START;
                sceneColor = mix(sceneColor, FOG_COLOR, pow(nrmlz, 1.0));
            }
            
            color = sceneColor;
            break;
        }
        
        if (st >= MAX_RENDER_DISTANCE) {
        	break;
        }
        
        st += mesh.w;
    }
    
    // Sky(Background) Color
    if (st >= MAX_RENDER_DISTANCE) {
    	color = mix(SKY_DOWN_COLOR, SKY_UP_COLOR, pow(abs(p.y), 0.1));
    }
    
    return color;
}