#define PI 3.14159265359

mat2 rmatrix(float a)    //Rotation matrix;
{
	float c = cos(a);
	float s = sin(a);

	return mat2(c, -s, s, c);
}

vec3 getRayDir(vec3 cameraDir, float cameraAngle)
{
	vec2 coord = ((gl_FragCoord.xy / iResolution.xy) * 2.0 - 1.0);
	coord.y /= iResolution.x / iResolution.y;
	vec3 xAxis = normalize(vec3(-cameraDir.z, 0, cameraDir.x)) * tan(cameraAngle / 2.0);
	vec3 yAxis = normalize(cross(cameraDir, xAxis)) * tan(cameraAngle / 2.0) * -1.0;
	vec3 result = normalize(cameraDir + xAxis * coord.x + yAxis * coord.y);

	return (result);
}

vec4 map(vec3 p)    //plane and cube map
{
    vec2 uv = vec2(0.0);
    float mat = 0.0;
    float t = 1000000.0;

    float cube = length(max(abs(p) - vec3(0.5), vec3(0))) - 0.1;

    float plane = p.y + 1.5;

    t = min(cube, plane);

    if (t == plane)
    {
        uv = p.xz;
        mat = 0.0;
    }
    else if (t == cube)
        mat = 2.0;

	return vec4(t, uv, mat);
}

vec4 map2(vec3 p)   //torus map
{
    vec2 uv = vec2(0.0);
    float mat = 0.0;
    float t = 1000000.0;

    float tr1 = 0.35;
    float tr2 = 0.15;

	float torus = length(normalize(vec3(p.x, 0, p.z)) * tr1 - p) - tr2;

    t = torus;

    uv.x = atan(p.x, p.z) / PI * 0.5 + 0.5;

    vec3 d = p - normalize(vec3(p.x, 0, p.z)) * tr1;
    float s = dot(normalize(vec2(p.x, p.z)), normalize(d.xz));
    vec2 coord2d = vec2(length(d.xz) * s, d.y);

    uv.y = atan(coord2d.x, coord2d.y) / PI * 0.5 + 0.5;


    mat = 1.0;

	return vec4(t, uv, mat);
}

vec4 marchRay(vec3 rayOrigin, vec3 rayDir, bool space2)  //almost classic raymarching
{
	float t;
	float d = 0.0;
	float e = 0.00001;
	float maxRange = 50.0;
	vec3 pos;
    vec4 info;

	for (t = 0.0; t <= maxRange; t += d)
	{
		pos = rayOrigin + rayDir * t;

        if (!space2)   //space2 tells witch map to use
            info = map(pos);
        else
            info = map2(pos);

        d = info.x;

		if (d < e)
			break;
	}
	if (t > maxRange)
		return vec4(-1.0);

    info.x = t;
	return info;
}

vec3 getNorm(vec3 pos)
{
	vec2 e = vec2(0.0001, 0);
	float tp = map(pos).x;

	vec3 norm = -normalize(vec3(map(pos - e.xyy).x - tp,
							   map(pos - e.yxy).x - tp,
							   map(pos - e.yyx).x - tp));
	return (norm);
}

vec3 getColor(vec4 info)
{
   float mat = info.w;
   vec2 uv = info.yz;

   if (mat == 1.0)
   {
       vec3 c1 = vec3(0, 98, 255) / 255.0;
       vec3 c2 = vec3(255, 21, 0) / 255.0;

       float k = sin(fract(uv.y + uv.x * 1.0) * PI);

       vec3 color = mix(c1, c2, k);

       color *= pow(abs(sin((uv.y + uv.x * 1.0) * PI / 2.0 * 32.0)), 10.0);

       return (color);
   }
   if (mat == 0.0)
   {
       vec2 id = floor(uv);
       if (mod((id.x + id.y), 2.0) == 0.0)
           return (vec3(0.5));
       return vec3(1.0);
   }
   return (vec3(0));
}

float getShadow(vec3 rayOrigin, vec3 rayDir)
{
	float t;
	float d = 0.0;
	float e = 0.00001;
	float maxRange = 10.0;
    vec4 info;
	vec3 pos;

    float res = 1.0;

	for (t = 0.0; t <= maxRange; t += d)
	{
		pos = rayOrigin + rayDir * t;
        info = map(pos);
		d = info.x;
        res = min(res, d / t * 8.0);
		if (d < e)
			break;
	}

	return res;
}

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    float camPitch = 0.0;
    float camYaw = iTime / 5.0;
    float cameraDist = 2.0;

    camPitch += (iMouse.xy / iResolution.xy + 0.5).y * PI;
    camYaw += (iMouse.xy / iResolution.xy + 0.5).x * PI * 2.0;

    camPitch = min(max(PI - 1.2, camPitch), PI + 1.2);

    vec3 cameraPos = cameraDist * vec3(cos(camYaw) * cos(camPitch), sin(camPitch), sin(camYaw) * cos(camPitch));
    vec3 cameraDir = -normalize(cameraPos);

    cameraPos.y = max(-1.49, cameraPos.y);

	vec3 rayDir = getRayDir(cameraDir, PI / 2.0);
	vec3 rayOrigin = cameraPos;

    vec4 info = marchRay(rayOrigin, rayDir, false);
	float t = info.x;

	vec3 color = vec3(0);

	if (t != -1.0)
	{
		vec3 pos = rayOrigin + rayDir * t;
		vec3 lightDir = normalize(vec3(-4, 4, 4));
		vec3 norm = getNorm(pos);
		float l = 0.1;

        color = getColor(info);

        lightDir = normalize(vec3(5) - pos);

        float mat = info.w;

        if (mat == 2.0)
        {
            float k = 1.33;
            vec3 pos = rayOrigin + rayDir * info.x;

            float ra = acos(dot(rayDir, norm) / (length(rayDir) * length(norm)));
            float ca = asin(k);

            vec3 refrColor;
            vec3 reflColor = vec3(0);

            vec3 rd2 = refract(rayDir, getNorm(pos), 1.0 / k - 0.01);
            vec4 i = marchRay(pos, rd2, true);
            refrColor.r = getColor(i).r;

            rd2 = refract(rayDir, getNorm(pos), 1.0 / k);
            i = marchRay(pos, rd2, true);
            refrColor.g = getColor(i).g;

            rd2 = refract(rayDir, getNorm(pos), 1.0 / k + 0.01);
            i = marchRay(pos, rd2, true);
            refrColor.b = getColor(i).b;

            rd2 = reflect(rayDir, getNorm(pos));
            i = marchRay(pos + norm * 0.01, rd2, false);
            if (i.x != -1.0)
            {
                reflColor = getColor(i);

                vec3 pos2 = pos + rd2 * i.x;
                vec3 norm2 = getNorm(pos2);
                float st = getShadow(pos2 + norm2 * 0.01, lightDir);

                l = 0.1;

                //st = 0.5;

                l += max(0.0, dot(lightDir, norm2)) * 0.9 * st;

                reflColor *= l;
            }

            color = mix(refrColor, reflColor, pow(1.0 + dot(rayDir, norm), 5.0));
            //color = reflColor;
        }
        else
        {
            float st = getShadow(pos + norm * 0.001, lightDir);

            l += max(0.0, dot(lightDir, norm)) * 0.9 * st;

            color *= l;
        }
	}

	fragColor = vec4(color, 1.0);
}