#define PROCESSING_COLOR_SHADER

uniform float time;
uniform vec4 resolution;
uniform sampler2D blueSampler;

uniform float moebius;
uniform float moebiusFact;
uniform float moebiusMul;

uniform float panColors;
uniform float panThreshold;

float hash(uint n){
	n = (n << 13U) ^ n;
    n = n * (n * n * 15731U + 789221U) + 1376312589U;
    return float( n & uvec3(0x7fffffffU))/float(0x7fffffff);
}

float hash(vec3 p3){
    p3 = fract(p3*.1031);
	p3 += dot(p3,p3.yzx+19.19);
    return fract((p3.x+p3.y)*p3.z);
}

float sphere(vec3 p, float r){
    return length(p)-r;
}

vec3 cellid;
void repeat(inout vec3 p, vec3 dim){
    cellid = floor((p )/(dim));
    p = mod(p, dim)-dim*0.5;
}

const float spacing = 3.0;
vec3 targetpos;
vec3 targetactual;

float target(vec3 p){
    return sphere(p-targetactual, 1.0);
}

float map(vec3 p){
    float t = target(p);
    repeat(p, vec3(spacing));
    return min(t, sphere(p, 0.5));
}

vec3 normal(vec3 p){
    float c = map(p);
    const float e = 0.001;
    return normalize(vec3(c-map(p-vec3(e,0,0)), c-map(p-vec3(0,e,0)), c-map(p-vec3(0,0,e))));
}

float intersect(vec3 cam, vec3 ray){
    float d = 0.0;
    const int steps = 75;
    for(int i = 0; i < steps; ++i){
        float t = map(cam+ray*d);
        d += t;
        if(t < 0.0001 || d > 50.0)
        	break;
    }
    
    return min(d, 50.0);
}

vec3 shade(vec3 cam, vec3 ray, vec3 pos, vec3 n, vec3 rv, float anim){
    const vec3 rc = pow(vec3(0.9,0.1,0.05), vec3(2.2));
    if(target(pos) < 0.01)
        return rc;
    
    vec3 emit = vec3(0);
    if(hash(cellid) > 0.75)
        emit = vec3(0.9)*(1.0-smoothstep(5.5, 6.0, distance(floor(targetactual), floor(pos))));
    
    vec3 light = targetactual;
    vec3 lv = normalize(light-pos);
#if 1
    float lambert = 0.1*max(0.0, dot(n, lv));
    float spec = 0.5*pow(max(0.0, dot(lv, rv)), 100.0);
    
    float ld = distance(pos, light);
    float falloff = pow(1.0-smoothstep(1.0, 14.0, ld), 6.0);
    vec3 surface = mix(vec3(1), rc, 0.25)*vec3(lambert+spec)*(falloff);
    if(hash(-cellid) > 0.5)
        surface *= 0.0;
# else
    vec3 surface = vec3(0);
#endif
    const vec3 basec = 1.5*pow(normalize(vec3(0.92,0.2,0.84)), vec3(2.2));
    float spacing = 2.0+8.0*hash(cellid+9999.);
    float color = round(3.0*hash(cellid+1999.));
    vec3 colorf;
    switch(int(color)){
        case 0:
            colorf = basec;
            break;
        case 1:
            colorf = basec.grb;
            break;
        case 2:
            colorf = basec.rbg;
            break;
        default:
            colorf = vec3(1);;
    };
    emit += panColors*colorf*step(0.5, mod(pos.y*spacing+3.0*time*(-1.0+2.0*hash(cellid+999.)), 1.0))*falloff * step(panThreshold, hash(-cellid));
    surface += emit;
    
    return surface;
}

vec3 getpoint(uint index){
    float a = floor(0.5+4.5*hash(index));
    float b = floor(0.5+4.5*hash(index+1U));
    float c = floor(0.5+4.5*hash(index+2U));
    
    vec3 p = vec3(a,b,c);
    switch( int(mod(float(index), 3.0)) ){
        case 0:
        	return p.xyz;
        case 1:
        	return p.zxy;
        case 2:
        	return p.yzx;
    }
}

vec3 path(float time){
    uint index = uint(time);
    return spacing*mix(getpoint(index), getpoint(index+1U), smoothstep(0.0, 1.0, mod(time, 1.0)));
}

mat3 lookat(vec3 cam, vec3 target){
	vec3 ww = normalize(target - cam);
	vec3 uu = normalize(cross(ww, normalize(vec3(1e-4,1.0-1e-4,1e-4))));
	vec3 vv = normalize(cross(uu, ww));
	return mat3(uu, vv, ww);
}

void main(){
    vec2 uv = gl_FragCoord.xy/resolution.xy;
    vec2 mv = -1.0+2.0*uv;
    
    vec3 cum = vec3(0);
    const int samples = 3;
    for(int y = 0; y < samples; ++y)
        for(int x = 0; x < samples; ++x){
            vec2 p = -1.0 + 2.0 * (uv + (-1.0+2.0*(vec2(x, y)/float(samples)))/resolution.xy);
            p.y *= resolution.y/resolution.x;
            
            float anim = 10.0+time-texelFetch(blueSampler, ivec2(mod(gl_FragCoord.xy*vec2(samples)+vec2(x,y),1024.)),0).r/48.0;

            p = mix(p, p/(moebiusFact + moebiusMul*dot(p,p)), moebius);

            // cam setup
            vec3 cam = vec3(0,0,0) + path(anim);
            targetpos = path(anim+2.25);
            targetactual = path(anim+2.35);
            float fov = mix(1.0, 6.0, smoothstep(5.0, 20.0, distance(cam, targetpos)));
            vec3 ray = normalize(vec3(p, fov));
            ray = lookat(cam, targetpos) * ray;

            // primary ray and shading
            float dist = intersect(cam, ray);
            
            if(dist < 50.0){
                vec3 pos = cam+ray*dist;
                vec3 n = normal(pos);
                vec3 rv = reflect(ray, n);
                vec3 col = shade(cam, ray, pos, n, rv, time);
				
                // reflection ray
                float rd = intersect(pos+n*0.01, rv);
                vec3 rpos = pos+rv*rd;
                vec3 rn = normal(rpos);
                vec3 rrv = reflect(rv, rn);

                // reflection shading
                vec3 rcol = shade(pos, rv, rpos, rn, rrv, time);
                float fresnel = pow(1.0-max(0.0, dot(n, -ray)), 5.0);
                col = mix(col, rcol, fresnel);
                //col += fresnel/pow(distance(cam, pos), 2.0);
				
                cum += col;
            }
    }
    
    cum /= float(samples*samples);
    
    float hv = hash(vec3(gl_FragCoord.xy, time));
    cum *= mix(cum*1.0+0.3*(-1.0+2.0*hv), cum+0.5*hv, 0.5);
    
    cum = mix(cum, mix(vec3(0.82,0.99,0.82)*0.02, vec3(0.99,0.92,0.85), cum), 0.1);
    cum = pow(cum, vec3(1.0/2.2));
    cum = smoothstep(-0.01, 1.05, cum);
    gl_FragColor.rgb = pow(cum, vec3(1.0/2.2));
    gl_FragColor.a = 1.0;
} 