void tracePlanar( in float pos, in float dir, vec4 colour, vec4 normal,
                  inout float setDist, inout vec4 setColour, inout vec4 setNormal )
{
    if (dir == 0.0)
        return;

    float dist = pos / dir;

    if (dist > setDist)
        return;

    setDist = dist;
    setColour = colour;
    setNormal = normal;
}

void traceSphere( in vec4 pos, in vec4 dir, float radius, vec4 colour, inout float setDist,
                  inout vec4 setColour, inout vec4 setNormal)
{
    pos /= radius;

    float dirDot = -dot(dir, pos);
    if (dirDot < 0.)
		return;

    vec4  inPos = pos + dir * dirDot;
    float inner = length(inPos);

    if (inner > 1.)
        return;

    vec4  hit   = inPos - dir * sqrt(1. - inner * inner);

    float dist = length(pos - hit) * radius;
    if (dist >= setDist)
        return;

    setColour = colour;
    setDist   = dist;
    setNormal = normalize(hit);
}

void traceCube( in vec4 pos, in vec4 dir, mat4x4 align, vec4 cubDim, inout float setDist,
                inout vec4 setColour, inout vec4 setNormal)
{
    vec4 relPos = pos * align;
    vec4 relDir = dir * align;

    if (abs(relPos.x) > cubDim.x) {
        vec4 tmpDir = relDir;
        tmpDir.x *= -sign(relPos.x);
        if (tmpDir.x > 0.) {
            float travel = (abs(relPos.x) - cubDim.x) / tmpDir.x;
            if (travel < setDist) {
                vec4  chk    = abs(relPos + tmpDir * travel);
                if (chk.y < cubDim.y && chk.z < cubDim.z && chk.w < cubDim.w) {
                    setColour = vec4(1., 1., 1., 0.);
                    setDist   = travel;
                    setNormal = align[0] * sign(relPos.x);
                    return;
                }
            }
        }
    }
    if (abs(relPos.y) > cubDim.y) {
        vec4 tmpDir = relDir;
        tmpDir.y *= -sign(relPos.y);
        if (tmpDir.y > 0.) {
            float travel = (abs(relPos.y) - cubDim.y) / tmpDir.y;
            if (travel < setDist) {
                vec4  chk    = abs(relPos + tmpDir * travel);
                if (chk.x < cubDim.x && chk.z < cubDim.z && chk.w < cubDim.w) {
                    setColour = vec4(1., 1., 1., 0.);
                    setDist   = travel;
                    setNormal = align[1] * sign(relPos.y);
                    return;
                }
            }
        }
    }
    if (abs(relPos.z) > cubDim.z) {
        vec4 tmpDir = relDir;
        tmpDir.z *= -sign(relPos.z);
        if (tmpDir.z > 0.) {
            float travel = (abs(relPos.z) - cubDim.z) / tmpDir.z;
            if (travel < setDist) {
                vec4  chk    = abs(relPos + tmpDir * travel);
                if (chk.x < cubDim.x && chk.y < cubDim.y && chk.w < cubDim.w) {
                    setColour = vec4(1., 1., 1., 0.);
                    setDist   = travel;
                    setNormal = align[2] * sign(relPos.z);
                    return;
                }
            }
        }
    }
    if (abs(relPos.w) > cubDim.w) {
        vec4 tmpDir = relDir;
        tmpDir.w *= -sign(relPos.w);
        if (tmpDir.w > 0.) {
            float travel = (abs(relPos.w) - cubDim.w) / tmpDir.w;
            if (travel < setDist) {
                vec4  chk    = abs(relPos + tmpDir * travel);
                if (chk.x < cubDim.x && chk.y < cubDim.y && chk.z < cubDim.z) {
                    setColour = vec4(1., 1., 1., 0.);
                    setDist   = travel;
                    setNormal = align[3] * sign(relPos.w);
                    return;
                }
            }
        }
    }
}

void traceObjects( inout vec4 pos, inout vec4 dir,
                   inout float setDist, out vec4 colour, out vec4 normal, float time )
{
//    traceSphere(pos + vec4(0.5, 0.65, .3, .3), dir, .35, vec4(0.5, 0.,0.5, 1.0),
//                setDist, colour, normal);
    traceSphere(pos + vec4(-0.5, 0.6, -.2, .4), dir, .4, vec4(0.5, 0.5,0.5, 1.0),
                setDist, colour, normal);
//    traceSphere(pos + vec4(-0.15, 0.8, 0, 0), dir, .2, vec4(0.1, 0.,0.9, 1.0),
//                setDist, colour, normal);

    mat4x4 cubeAngA, cubeAngB;

    float cubRot = 0.;

    cubeAngA[0] = vec4(cos(cubRot), 0., sin(cubRot), 0.);
    cubeAngA[1] = vec4(0., 1., 0., 0.);
    cubeAngA[2] = vec4(-sin(cubRot), 0., cos(cubRot), 0.);
    cubeAngA[3] = vec4(0., 0., 0., 1.);

    cubRot = 0.6;

    cubeAngB[0] = vec4(cos(cubRot), 0., 0., sin(cubRot));
    cubeAngB[1] = vec4(0., 1., 0., 0.);
    cubeAngB[2] = vec4(0., 0., 1., 0.);
    cubeAngB[3] = vec4(-sin(cubRot), 0., 0., cos(cubRot));

    traceCube(pos + vec4(0.6, 0.35, -0.35, 0.), dir, cubeAngA, vec4(.3, .7, .3, .2),
              setDist, colour, normal);

    traceCube(pos + vec4(-0.6, 0.35, 0.35, 0.), dir, cubeAngB, vec4(.2, .7, .2, .3),
              setDist, colour, normal);

    cubRot = 0.0;

    cubeAngB[0] = vec4(cos(cubRot), 0., 0., sin(cubRot));
    cubeAngB[1] = vec4(0., 1., 0., 0.);
    cubeAngB[2] = vec4(0., 0., 1., 0.);
    cubeAngB[3] = vec4(-sin(cubRot), 0., 0., cos(cubRot));

    traceCube(pos + vec4(0.6, -0.35, 0.35, -0.75), dir, cubeAngB, vec4(.2, .2, .2, .25),
              setDist, colour, normal);
}

void traceScene( inout vec4 pos, inout vec4 dir, out vec4 colour,
                 out vec3 pureColour, out vec3 transfer, out vec3 atmos, out vec4 normal,
                 float time, float noise )
{
    float nxDist   = 99999.0;
    vec4  nxColour = vec4(0., 0., 0., 0.);
    vec4  nxNormal = vec4(0., 0., 0., 0.);

    if (dir.x < 0.) {
        tracePlanar( pos.x+1., -dir.x, vec4(0.2, 0.9, 0.2, 0.0), vec4(1., 0., 0., 0.),
                     nxDist, nxColour, nxNormal );
    }
    else {
        tracePlanar( pos.x-1., -dir.x, vec4(0.9, 0.2, 0.2, 0.0), vec4(-1., 0., 0., 0.),
                     nxDist, nxColour, nxNormal );
    }
    if (dir.y < 0.) {
        tracePlanar( pos.y+1., -dir.y, vec4(0.8, 0.79, 0.78, 0.85), vec4(0., 1., 0., 0.),
                     nxDist, nxColour, nxNormal );
    }
    else {
        tracePlanar( pos.y-1., -dir.y, vec4(0.8, 0.79, 0.78, 0.0), vec4(0., -1., 0., 0),
                     nxDist, nxColour, nxNormal );
    }
    if (dir.z < 0.) {
        tracePlanar( pos.z+1., -dir.z, vec4(0.8, 0.79, 0.78, 0.0), vec4(0., 0., 1., 0.),
                     nxDist, nxColour, nxNormal );
    }
    else {
        tracePlanar( pos.z-1., -dir.z, vec4(0.8, 0.79, 0.78, 0.0), vec4(0., 0., -1., 0.),
                     nxDist, nxColour, nxNormal );
    }
    if (dir.w < 0.) {
        tracePlanar( pos.w+1., -dir.w, vec4(0.28, 0.29, 0.78, .5), vec4(0., 0., 0., 1.),
                     nxDist, nxColour, nxNormal );
    }
    else {
        tracePlanar( pos.w-1., -dir.w, vec4(0.9, 0.9, 0.0, 0.5), vec4(0., 0., 0., -1.),
                     nxDist, nxColour, nxNormal );
    }

    traceObjects(pos, dir, nxDist, nxColour, nxNormal, time);

    vec4 lightPos = vec4(0, 0.8, 0., -0.8);

    traceSphere(pos - lightPos, dir, .1, vec4(10., 10.0, 10.0, -1.0),
                nxDist, nxColour, nxNormal);

    pos += dir * nxDist;

    if (pos.y <= -0.99999) {
        float check = (floor(mod(pos.x * 5., 2.)) == floor(mod(pos.z * 5., 2.))) ? 1. : 0.;
        check = mod(pos.w * 5., 2.) < 1. ? (1. - check) : check;

        nxColour.xyz *= vec3(0.2, 0.1, 0.05) * check + vec3(0.9, 0.87, 0.85) * (1. - check);

        nxColour.w *= 1. - (1. - check * 0.7);

        float tileA = mod((0.5 + pos.x) * 5., 1.);
        float tileB = mod((0.5 + pos.z) * 5., 1.);
        float tileC = mod((0.5 + pos.w) * 5., 1.);

        float fade  = max(0., 1. - (min(abs(0.5 - tileC), min(abs(0.5 - tileA), abs(0.5 - tileB)))) * 17.);
        fade = min(1., fade * 4.);

        nxNormal.x -= (-.5 + tileA * 1.) * max(0., 1. - abs(0.5 - tileA) * 5.);
        nxNormal.z -= (-.5 + tileB * 1.) * max(0., 1. - abs(0.5 - tileB) * 5.);
        nxNormal.w -= (-.5 + tileC * 1.) * max(0., 1. - abs(0.5 - tileC) * 5.);

        nxColour = nxColour * (1. - fade) + vec4(0.9, 0.9, 0.9, .0) * fade;

        nxNormal = normalize(nxNormal);
    }
    if (pos.z <= -0.999999) {
        //nxColour.xyz = vec3(999.);

        vec3 norm = texture(iChannel2, pos.xyw * 2.5).xyz;

        nxNormal.xyw += (-.5 + norm * .5);
        nxNormal = normalize(nxNormal);
    }

    vec4 lightDir = lightPos - pos;
    float lightDist = length(lightDir);
    lightDir /= lightDist;
    float lightEff = max(0., dot(lightDir, nxNormal));

    float lightTstDist = 9999.;
    vec4 dummy;

    vec4 ref = nxNormal * max(0., dot(nxNormal, dir));
    ref += (dir - ref);

    float spec = pow(max(0., dot(ref, lightDir)), 1. - dot(nxNormal, dir) * 2.);

    traceObjects(pos, lightDir, lightTstDist, dummy, dummy, time);
    if (lightTstDist < lightDist || dot(nxNormal, lightDir) < 0.0) {
        lightEff *= 0.;
        spec *= 0.;
    }

    float atmosWeight = 1.;

    float atmosStep = (nxDist / 17.0);
    float atmosNoise = noise / 17.0;
    for (int i = 0; i < 16; i++) {
        vec4 tmpPos = pos - dir * (float(i) * atmosStep) + atmosNoise;

        lightDir = normalize(lightPos - tmpPos);

        float lightTstDist = 9999.;
        vec4 dummy;

        traceObjects(tmpPos, lightDir, lightTstDist, dummy, dummy, time);
        if (lightTstDist < lightDist) {
            atmosWeight -= 1. / 16.;
        }
    }

    vec3 lighCol = vec3(8.0, 7.9, 6.6) * 3.;

    float lightAng = 0.5 - (0.4 * acos(lightDir.y) / 3.1415);

    lighCol *= texelFetch(iChannel1, ivec2(lightAng, .5), 0).xxx;

    pureColour = nxColour.xyz;
    colour.xyz = pureColour * lightEff * pow(0.33, lightDist) * lighCol;
    colour.xyz += lighCol * pow(0.33, lightDist) * spec * 1.;
    colour.w = nxColour.w;

    if (nxColour.w < 0.) {
        colour = vec4(lighCol.x, lighCol.y, lighCol.z, 0.);
    }

    normal = nxNormal;

    //pureColour.x = 1. / nxDist;

    atmos    = 1. - pow(vec3(0.8, 0.85, 0.9), vec3(nxDist*2.));
    transfer = pow(vec3(0.8, 0.85, 0.9), vec3(nxDist*2.));


    atmos *= atmosWeight;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float noise = texelFetch(iChannel0, ivec2(mod(fragCoord.x + iTime * 1038., 256.), mod(fragCoord.y, 256.)), 0).x;


    vec2 aspect = vec2(iResolution.x/iResolution.y, 1.0);
	vec2 uv = fragCoord.xy / iResolution.xy;
	uv = (2.0 * uv - 1.0) * aspect * 0.5;


    float rotRef = min(iTime * iTime * iTime * 0.1, iTime * .5) * 4.;
    rotRef += sin(rotRef * 0.01) * 20.;

    float rotAs = sin(rotRef / 13.);
    float rotAc = cos(rotRef / 13.);
    float rotBs = cos(0. + rotRef / 27.);
    float rotBc = sin(0. + rotRef / 27.);
    float rotCs = sin(0.0 + rotRef / 11.);
    float rotCc = cos(0.0 + rotRef / 11.);
    float rotDs = sin(0.0 + rotRef / 23.);
    float rotDc = cos(0.0 + rotRef / 23.);
    float rotEs = sin(0.0 + rotRef / 37.);
    float rotEc = cos(0.0 + rotRef / 37.);

    vec4  off   = vec4(sin(rotRef / 31.), sin(rotRef / 15.), sin(rotRef / 17.), sin(rotRef / 7.));

    mat4x4 cameraRotA = mat4x4( rotAs, 0, -rotAc, 0,
                                0,     1,  0,     0,
                                rotAc, 0,  rotAs, 0,
                                0,     0,  0,     1  );

    mat4x4 cameraRotB = mat4x4( rotBs, -rotBc, 0, 0,
                                rotBc, rotBs,  0, 0,
                                0, 0,  1, 0,
                                0, 0,  0, 1  );

    mat4x4 cameraRotC = mat4x4( rotCc, 0, 0, -rotCs,
                                0,     1, 0, 0,
                                0,     0, 1, 0,
                                rotCs, 0, 0, rotCc  );

    mat4x4 cameraRotD = mat4x4( 1, 0, 0, 0,
                                0, 1, 0, 0,
                                0, 0, rotDc, -rotDs,
                                0, 0, rotDs, rotDc  );

    mat4x4 cameraRotE = mat4x4( 1, 0, 0, 0,
                                0, rotEc, 0, -rotEs,
                                0, 0, 1, 0,
                                0, rotEs, 0, rotEc  );

    mat4x4 cameraRot = cameraRotC * cameraRotB * cameraRotA * cameraRotD * cameraRotE;

    vec4 cameraFw    = vec4(1, 0, 0, 0) * cameraRot;
    vec4 cameraUp    = vec4(0, 1, 0, 0) * cameraRot;
    vec4 cameraRight = vec4(0, 0, 1, 0) * cameraRot;
    vec4 camera4d    = vec4(0, 0, 0, 1) * cameraRot;

    cameraUp.x  = min(0.5, max(-0.5, cameraUp.x));
    cameraUp.z  = min(0.5, max(-0.5, cameraUp.z));
    cameraUp.y += 0.9;
    cameraUp    = normalize(cameraUp);

    cameraFw    = normalize(cameraFw - cameraUp * dot(cameraUp, cameraFw));
    cameraRight = normalize(cameraRight - cameraUp * dot(cameraUp, cameraRight));
    cameraRight = normalize(cameraRight - cameraFw * dot(cameraFw, cameraRight));

    vec4 cameraPos   = (-cameraFw * 3.) + off * .2;

    // Enable this to smear vaseline in the 4th dimension
    //float noise4 = texelFetch(iChannel3, ivec2(mod(fragCoord.x, 256.), mod(fragCoord.y, 256.)), 0).x;
    //cameraFw += camera4d * (-0.5 + noise4) * 0.1;

    // Sine wave trippy-maker
    // cameraFw.w += sin(length(fragCoord - 200.0) * .01) * 0.4;

    vec4 cameraDir   = normalize(cameraFw + cameraRight * uv.x + cameraUp * uv.y);

    vec4 dummy;
    float dist = 999999.;
    mat4x4 id = mat4x4(1, 0, 0, 0,
                       0, 1, 0, 0,
                       0, 0, 1, 0,
                       0, 0, 0, 1 );

    traceCube(cameraPos, cameraDir, id, vec4(1.1, 1.1, 1.1, 1.1),
              dist, dummy, dummy);
    cameraPos += cameraDir * dist;

    vec4 outColour   = vec4(0, 0, 0, 1);
    vec4 normal		 = vec4(0, 0, 0, 0);

    vec3 addOpac     = vec3(1, 1, 1);
    vec3 transfer;
    vec3 atmos;

    int maxLoop = 15;
    for (int i = 0; i < maxLoop; i++) {
        vec4 addColour;
        vec3 pureColour;

    	traceScene(cameraPos, cameraDir, addColour, pureColour, transfer, atmos, normal, iTime, noise);

        if (abs(cameraPos.x) > 1.01 || abs(cameraPos.y) > 1.01 ||
            abs(cameraPos.z) > 1.01 || abs(cameraPos.w) > 1.01) {
            float len = .5 / length(uv);
            len += texture(iChannel2, vec3(uv.x, uv.y, iTime * 0.05)).x * 0.1;
            outColour.x = pow(0.10, len);
            outColour.y = pow(0.25, len);
            outColour.z = pow(0.30, len);
            outColour.xyz *= 0.5;
            break;
        }

        // Override reflectivity!
        //addColour.w = .9;

        if (i+1 == maxLoop) {
            addColour.w = 0.;
        }

        outColour.xyz += atmos * addOpac.xyz;
        addOpac *= transfer;

        if (addColour.w > 0.) {
            addColour.w *= 0.4 + 0.6 * pow(max(0., 1. + dot(cameraDir, normal)), 0.5);
            addColour.w = min(1., addColour.w);

            outColour.xyz += addColour.xyz * (1. - addColour.w) * addOpac.xyz;
            addOpac *= addColour.w;

            cameraDir *= -1.;
            normal *= -1.;

            vec4 mid = normal * dot(normal, cameraDir);
            cameraDir = mid + (mid - cameraDir);

            addColour = vec4(0., 0., 0., 0.);
            pureColour = vec3(0., 0., 0.);

            noise = texelFetch(iChannel0, ivec2(mod(fragCoord.x + noise * 13., 256.), mod(fragCoord.y + noise * 23., 256.)), 0).x;

            continue;
        }

        outColour.xyz += addColour.xyz * addOpac.xyz;
        break;
    }

    fragColor.xyz = outColour.xyz;
    fragColor.w = 1.;
}
