
/*
TODO:
- fix refraction/reflection
- improve performance
- lighting (?)
*/

#define TIME_SPEED 0.5
#define MAX_RAY_STEPS 640
#define MAX_REFLECTIONS 3
#define GLASS_RI 1.5


// make glass hazy by adding noise to normal. Uncomment to enable
//#define HAZY_GLASS 0.05

const vec4 ONES = vec4(1.0);
const vec4 ZEROS = vec4(0.0);
const vec2 _01 = vec2(0.0, 1.0);


vec2 rotate2d(vec2 v, float a) {
	float sinA = sin(a);
	float cosA = cos(a);
	return vec2(v.x * cosA - v.y * sinA, v.y * cosA + v.x * sinA);
}

float sdSphere(vec3 p, float d) { return length(p) - d; }

float sdBoxRot( vec3 p, vec3 b, float r ) {
  p.xz = rotate2d(p.xz, r);
  vec3 d = abs(p) - b;
  return min(max(d.x,max(d.y,d.z)),0.0) +
         length(max(d,0.0));
}

bool getVoxel(ivec3 c) {
	vec3 p = vec3(c) + vec3(0.5);
    //float d = min(sdBoxRot(p, vec3(3.0), 0.), -sdSphere(p, 25.0));
    float d = min(
        sdSphere(p, 1.0), min(
        max(-sdSphere(p, 3.5), sdBoxRot(p, vec3(3.0), 0.)),
        min(max(-sdSphere(p, 7.5), sdBoxRot(p, vec3(6.0), 0.785398)),-sdSphere(p, 25.0))
    ));
	return d < 0.0;
}



float hash13(vec3 p3) {
	p3 = fract(p3 * .1031);
    p3 += dot(p3, p3.zyx + 31.32);
    return fract((p3.x + p3.y) * p3.z);
}

vec3 hash33(vec3 p3) {
	p3 = fract(p3 * vec3(.1031, .1030, .0973));
    p3 += dot(p3, p3.yxz+33.33);
    return fract((p3.xxy + p3.yxx)*p3.zyx);
}


// using a as alpha, implementing https://en.wikipedia.org/wiki/Alpha_compositing
vec4 blend(vec4 above, vec4 below) {
    float alpha = mix(below.a, 1., above.a);
    return vec4(mix(below*below.a, above, above.a).rgb / alpha, alpha);
}

vec4 blend(vec4 above, vec3 below) {
    return vec4(mix(below, above.rgb, above.a), 1.0);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 screenPos = (fragCoord.xy / iResolution.xy) * 2.0 - 1.0;
	vec3 cameraDir = vec3(0.0, 0.0, 0.8);
	vec3 cameraPlaneU = vec3(1.0, 0.0, 0.0);
	vec3 cameraPlaneV = vec3(0.0, 1.0, 0.0) * iResolution.y / iResolution.x;
	vec3 rayDir = normalize(cameraDir + screenPos.x * cameraPlaneU + screenPos.y * cameraPlaneV);
    vec3 rayPos = vec3(0.0, 1.0 * sin(iTime * 2.7 * TIME_SPEED), -12.0);

	rayPos.xz = rotate2d(rayPos.xz, iTime * TIME_SPEED);
	rayDir.xz = rotate2d(rayDir.xz, iTime * TIME_SPEED);

	ivec3 mapPos = ivec3(floor(rayPos));
	vec3 deltaDist = 1. / abs(rayDir);
	ivec3 rayStep = ivec3(sign(rayDir));
    vec3 sideDist = (sign(rayDir) * (0.5 - fract(rayPos)) + 0.5) * deltaDist;


    float refractive_index = 1.0;
	bvec3 mask;

    vec4 color;
    fragColor = vec4(0.);

    //float depth = 0.;
    int reflections = 0;

	for (int i = 0; i < MAX_RAY_STEPS; i++) {
		if (getVoxel(mapPos)) {

            if (mapPos == clamp(mapPos, -5, 4)) {
                // calculate end of ray
                float m = dot(vec3(mask), sideDist - deltaDist);
                rayPos = rayPos + rayDir * m;

                // apply white and dullness based on dist from origin
                color = vec4(min(2.5 / length(rayPos), 1.0));
                fragColor = blend(fragColor, color);

                // reflect ray
                if (reflections < MAX_REFLECTIONS) {
                    vec3 normal = vec3(mask) * sign(rayDir);
                    rayDir = reflect(rayDir, normal);
                    reflections++;
                } else {
                    break;
                }

                // "restart" raycasting
                mapPos = ivec3(floor(rayPos + 0.));
                deltaDist = 1. / abs(rayDir);
                rayStep = ivec3(sign(rayDir));
                sideDist = (sign(rayDir) * (0.5 - fract(rayPos)) + 0.5) * deltaDist;
            } else if (mapPos == clamp(mapPos, -12, 11)) {
                float m = dot(vec3(mask), sideDist - deltaDist) + 0.001;
                vec3 hit = (rayPos + rayDir * m);

                // refract ray
                float eta = refractive_index / GLASS_RI;
                if (eta != 1.0) {
                    vec3 normal = -vec3(mask) * sign(rayDir);

                    #ifdef HAZY_GLASS
                        normal = normalize(normal + (hash33(hit*100.) - 0.5)*HAZY_GLASS);
                    #endif
                    rayDir = refract(rayDir, normal, eta);

                    // scuffed attempt at making the glass a bit reflective
                    //float R_0 = pow((refractive_index - GLASS_RI) /
                    //                (refractive_index + GLASS_RI), 2.);
                    //float cos_i = dot(-normal, rayDir);
                    //float R = R_0 + (1. - R_0)*pow(1. - cos_i, 5.);

                    //if (hash13(hit*100. + iTime) > R*2.) {
                    //    rayDir = refract(rayDir, normal, eta);
                    //} else {
                    //    rayDir = reflect(rayDir, normal);
                    //}
                }
                refractive_index = GLASS_RI;

                // "restart" raycasting
                rayPos = hit;
                mapPos = ivec3(floor(rayPos + 0.));
                deltaDist = 1. / abs(rayDir);
                rayStep = ivec3(sign(rayDir));
                sideDist = (sign(rayDir) * (0.5 - fract(rayPos)) + 0.5) * deltaDist;

                // alpha is proportional to amount of ray within the voxel,
                // seems to make it mostly seamless between two transparent voxels.
                float t = min(min(sideDist.x, sideDist.y), sideDist.z);
                color = vec4(abs(hit) / 10., t*0.3);
                fragColor = blend(fragColor, color);
            } else {
                break;
            }
        } else if (refractive_index != 1.0) {
            // refract/reflect when light exits the glass
            vec3 m = vec3(mask) * (sideDist - deltaDist) + 0.001;
            vec3 hit = (rayPos + rayDir * dot(m, ONES.xyz));

            vec3 normal = -vec3(mask) * sign(rayDir);
            #ifdef HAZY_GLASS
                normal = normalize(normal + (hash33(hit*100.) - 0.5)*HAZY_GLASS);
            #endif
            vec3 r = refract(rayDir, normal, refractive_index);
            if (r == ZEROS.xyz) {
                // reflect if total internal reflection
                if (reflections < MAX_REFLECTIONS) {
                    rayDir = reflect(rayDir, normal);
                    reflections++;
                } else {
                    break;
                }
            } else {
                // otherwise use refraction ray
                rayDir = r;
                refractive_index = 1.0;
            }

            rayPos = hit;
            mapPos = ivec3(floor(rayPos));
            deltaDist = 1. / abs(rayDir);
            rayStep = ivec3(sign(rayDir));
            sideDist = (sign(rayDir) * (0.5 - fract(rayPos)) + 0.5) * deltaDist;
        }


        mask = lessThanEqual(sideDist.xyz, min(sideDist.yzx, sideDist.zxy));

        sideDist += vec3(mask) * deltaDist;
        mapPos += ivec3(mask) * rayStep;

        //depth += 1.;

	}

    // needs +0.001 to definitely be in the hit block
    float m = dot(vec3(mask), sideDist - deltaDist) + 0.001;
    vec3 hit = rayPos + rayDir * m;


    color.xyz = hash13(vec3(mapPos))*0.2 + fract(hit)*0.2 + 0.5;
    fragColor = blend(fragColor, color.xyz);

    //fragColor = vec4(depth / float(MAX_RAY_STEPS));
}
