#define HASHSCALE3 vec3(.1031, .1030, .0973)

#define reflections 3

#define rots mat3(	\
    -1, 0, 1,		\
     1,-1, 0,		\
     0, 1,-1)
//hash function by Dave_Hoskins https://www.shadertoy.com/view/4djSRW
vec3 hash33(vec3 p3)
{
	p3 = fract(p3 * HASHSCALE3);
    p3 += dot(p3, p3.yxz+19.19);
    return fract((p3.xxy + p3.yxx)*p3.zyx);
}
//sphere intersection by iq
//modified to raycast the outside of a circle
float sphIntersect( in vec2 ro, in vec2 rd, float rad)
{
	float b = dot( ro, rd );
	float c = dot( ro, ro )-rad*rad;
	float h = b*b - c;
	if( h<0.0||b>0.0) return 1e2;
	return -b - sqrt( h );
}

//sphere intersection by iq
//modified to raycast the inside of a circle
float sphIntersect2( in vec2 ro, in vec2 rd, float rad)
{
	float b = dot( ro, rd );
	float c = dot( ro, ro )-rad*rad;
	float h = b*b - c;
	if(h<0.0) return 1e2;
	return -b + sqrt( h );
}

//raycasts the outside of a square torus
float torus(vec3 ro, vec3 rd, vec3 rdnorm, vec2 area, float rad) {
    vec3 normal = vec3(-1);
    float dist;
    float a = -ro.y/rd.y;
    if (a < 0.0 && abs(ro.y) > area.y) return 10.0;
    float b = abs(area.y/rd.y);
    float len = max(a-b,0.0);

    ro += rd*len;
    dist += len;

    float len3 = length(ro.xz)-rad;

    if (len3 > area.x) {
        float len2 = sphIntersect(ro.xz,rd.xz*rdnorm.y, rad+area.x)*rdnorm.y;
        ro += rd*len2;
        dist += len2;
    }

    else if (len3 < -area.x) {
        float len2 = sphIntersect2(ro.xz,rd.xz*rdnorm.y, rad-area.x)*rdnorm.y;
        ro += rd*len2;
        dist += len2;
    }

    if (abs(ro.y) > area.y+0.001) return 10.0;
    return dist;
}

//finds the normal of a square torus
vec3 torusnormal(vec3 ro, vec2 area, float rad) {
    vec2 p = vec2(length(ro.xz)-rad,ro.y)*area;
    vec3 normal = vec3(-1);

    if (abs(p.x) > abs(p.y)) {
        normal = vec3(normalize(ro.xz),0.0).xzy*sign(p.x);
    } else {
        normal = vec3(0,sign(p.y),0);
    }

    return normal;
}

//my shading function, it isn't very effective since i have almost no experience with shading.
vec4 shade(vec3 p, vec3 d, vec3 objnorm, float depth) {
    vec3 sun = normalize(textureLod(iChannel0,vec3(0.0,0.0,iTime*0.01),0.0)).xyz;
    //vec3 sun = normalize(textureLod(iChannel0,vec3(0,0,iTime*0.01),0.0)).xyz;
    vec3 reflectnorm = reflect(d,objnorm);
    vec3 color = clamp(textureLod(iChannel0,p/iChannelResolution[0],0.0).xyz*3.0-1.0,0.0,1.0);

    vec4 fragColor = vec4(color*max(0.2,2.0*dot(objnorm,-sun)),1.0);

    fragColor += clamp((dot(reflectnorm,-sun)-0.9)*12.0,0.0,1.0);

    fragColor = fragColor*clamp(1.4-depth*0.1,0.0,1.0);
    return clamp((fragColor),0.0,1.0);
}

//the main function, here is the voxel traverser
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	vec2 uv = (fragCoord.xy * 2.0 - iResolution.xy) / iResolution.y;
    //if (abs(uv.x-1.0) < 10.0/iResolution.x) uv.x = 1.0;
    fragColor = vec4(0);

    vec3 start = vec3(iTime*0.2);
    vec3 ro = start;
    vec3 rd = normalize(vec3(uv,1));
    vec3 rdnorm = 1.0/vec3(
        length(rd.yz),
        length(rd.xz),
        length(rd.xy));

    vec3 floorro = floor(ro*0.5);
    vec3 signrd = sign(rd);
    vec3 invrd = 1.0/(abs(rd)+0.0001);
    vec3 mask;
    vec3 dists = abs(-signrd*0.5-0.5+ro*0.5-floorro)*invrd;
    float colorsize = 0.0;
    float dist = 0.0;
    float len2;
    bool hit = false;
    vec3 pos;
    vec3 dir;
    vec3 normal;
    int reflects = 0;

    for (int i = 0; i < 100; i++) {
        mask = vec3(lessThanEqual(dists,min(dists.yzx,dists.zxy)));
        if (dist > 20.0) break;
        float len = min(min(dists.x,dists.y),dists.z);
        //float len = dot(dists,mask)/dot(mask,vec3(1.0));

    	vec3 flipping = floor(hash33(floorro)+0.5)*2.0-1.0;

        vec3 d = rd;
        vec3 p = (ro-floorro*2.0-1.0);

        len2 = len*2.0;

        vec3 t = vec3(
        	torus((p-rots[0]*flipping).xyz,rd.xyz,rdnorm.xyz,vec2(0.1),1.0),
        	torus((p-rots[1]*flipping).yzx,rd.yzx,rdnorm.yzx,vec2(0.1),1.0),
        	torus((p-rots[2]*flipping).zxy,rd.zxy,rdnorm.zxy,vec2(0.1),1.0));
        float closest = min(min(t.x,t.y),t.z);
        len2 = min(len2,closest);
        dist += len2;

        ro += len2*rd;

        if (len2 < len*2.0) {
            hit = true;
            p = ro-floorro*2.0-1.0;
            vec3 normal2;
            if (t[0] == closest) normal = torusnormal((p-rots[0]*flipping).xyz,vec2(0.1),1.0).xyz;
            if (t[1] == closest) normal = torusnormal((p-rots[1]*flipping).yzx,vec2(0.1),1.0).zxy;
            if (t[2] == closest) normal = torusnormal((p-rots[2]*flipping).zxy,vec2(0.1),1.0).yzx;

            vec4 col = shade(ro,rd,normal,dist);
        	fragColor = mix(fragColor,col,0.15);
            if (reflects == 0) fragColor = col;

            reflects++;
            if (reflects > reflections) break;
            //dist = 0.0;
            rd = normalize(reflect(rd,normal));
            ro += rd*0.01;
            signrd = sign(rd);
            invrd = 1.0/(abs(rd)+0.0001);
            dists = abs(-signrd*0.5-0.5+ro*0.5-floorro)*invrd;
        } else {
            floorro += mask*signrd;
            dists -= len;
            dists += mask*invrd;
        }
    }
    fragColor = sqrt(fragColor);
}