#define PI 3.1415926
#define fdist 0.3
#define iters 30
#define tol 0.005
#define maxdist 10.
#define gradient_eps 0.01

//shape parameters
#define displacement 0.05
#define texturescale 3.
#define texture_start_x 0.
#define texture_start_y 0.
#define texture_end_x 1.
#define texture_end_y 1.
#define ring_count 7
#define levels 5
#define ratio 0.4
#define ring_offset 1.5
#define offsetdiff 0.8
#define indent 0.2
#define ringdiff 3.
#define base_radius 2.
#define ao_radius 0.1
#define ao_min 0.2
#define repeat_offset 8.
#define laplace_factor 100.
vec3 viridis_quintic( float x )
{
	x = clamp( x, 0.,1. );
	vec4 x1 = vec4( 1.0, x, x * x, x * x * x ); // 1 x x2 x3
	vec4 x2 = x1 * x1.w * x; // x4 x5 x6 x7
	return vec3(
		dot( x1.xyzw, vec4( +0.280268003, -0.143510503, +2.225793877, -14.815088879 ) ) + dot( x2.xy, vec2( +25.212752309, -11.772589584 ) ),
		dot( x1.xyzw, vec4( -0.002117546, +1.617109353, -1.909305070, +2.701152864 ) ) + dot( x2.xy, vec2( -1.685288385, +0.178738871 ) ),
		dot( x1.xyzw, vec4( +0.300805501, +2.614650302, -12.019139090, +28.933559110 ) ) + dot( x2.xy, vec2( -33.491294770, +13.762053843 ) ) );
}

vec3 sdTorus( vec3 p, vec2 t)
{
    vec2 q = vec2(length(p.xz)-t.x,p.y);
    float d = length(q)-t.y;

    float phi = atan(q.y, q.x); //inner angle
    float theta = atan(p.x, p.z); //outer angle
    return vec3(d, phi, theta);
}

//p.x: distance in toroidal coordinates
//p.y: inner angle
//p.z: outer angle
//t.x: outer radius
//t.y: inner radius
//t.z: parent outer radius
vec3 innerTorus(vec3 p, vec3 t) {
    //outer torus' inner angle becomes inner torus' outer angle
    float rxy = t.z + p.x*cos(p.y);
    vec3 pos = vec3(rxy*cos(p.z),
                    rxy*sin(p.z),
                    p.x*sin(p.y));
    return sdTorus(pos-vec3(t.z, 0., 0.), t.xy);

}
vec3 map(vec3 p) {
    p = mod(p+0.5*repeat_offset,repeat_offset)-0.5*repeat_offset;
    //time-varying parameters (maybe replace with some inputs, or remove)
    float textureoffset = iTime*0.25;
    float final_offset;
    if (iMouse.w < 1.)
        final_offset = final_offset = offsetdiff*cos(PI*((abs(mod(iTime*0.5+1., 2.)-1.)+iTime*0.5+1.)*0.5-0.5)) + ring_offset;
    else
        final_offset = (iMouse.y/iResolution.y-0.5)*3.+2.;
    float final_ratio = ratio/final_offset;
    float final_ringcount;
    if (iMouse.z < 1.)
    	final_ringcount = ringdiff*cos(PI*((abs(mod(iTime*0.5, 2.)-1.)+iTime*0.5)*0.5-0.5))+float(ring_count);
    else
    	final_ringcount = float(ring_count) + (iMouse.x/iResolution.x-0.5)*2.*ringdiff;
    float sector = 2.*PI/(final_ringcount);
    float outerrad = base_radius;
    float innerrad = outerrad*final_ratio;
    vec3 h = sdTorus(p, vec2(outerrad, innerrad));

    int i;
    float mag = displacement;
    float currindent = indent;
    float disp = texture(iChannel0, vec2(final_ratio*h.y/(2.*PI)*texturescale+textureoffset, h.z/(2.*PI)*texturescale)).x;
    h.x -= (disp-0.5)*mag;
    vec3 minh = h;

    for (i=0; i<levels; i++) {
        float theta = mod(abs(h.z), sector)-sector/2.;
        float newouterrad = innerrad*final_offset;
        float newinnerrad = newouterrad*final_ratio;
        h = innerTorus(vec3(h.x+innerrad, h.y, theta), vec3(newouterrad, newinnerrad, outerrad));
        outerrad = newouterrad;
        innerrad = newinnerrad;
        disp = texture(iChannel0, vec2(final_ratio*h.y/(2.*PI)*texturescale+textureoffset, h.z/(2.*PI)*texturescale)).x;
		h.x -= (disp-0.5)*mag;
        minh.x = max(minh.x, currindent-h.x);
        if (h.x < minh.x) {
            minh = h;
        }
        mag = mag * final_ratio * final_offset;
        currindent = currindent * final_ratio * final_offset;
    }
    return minh;
}

vec4 gradient(in vec3 pos) {
    vec3 offset = vec3(-gradient_eps, 0.0, gradient_eps);
    float dx0 = map(pos+offset.xyy).x;
    float dxf = map(pos+offset.zyy).x;
    float dy0 = map(pos+offset.yxy).x;
    float dyf = map(pos+offset.yzy).x;
    float dz0 = map(pos+offset.yyx).x;
    float dzf = map(pos+offset.yyz).x;
    float ddd = map(pos).x;
    return vec4(normalize(vec3(dxf - dx0, dyf - dy0, dzf - dz0)), dx0+dxf+dy0+dyf+dz0+dzf-6.*ddd);
}

vec4 raymarch(vec3 pos, vec3 dir) {
    vec4 fac = vec4(maxdist);
    int i;
    float d = 0.;
    for (i=0; i<iters; i++) {
        vec3 dist = map(pos+d*dir);
        d += dist.x;
        fac.x = dist.x;
        if (fac.y < fac.x && fac.y <= fac.z && fac.y < fac.w) {
            fac.w = fac.y;
        }
        fac.zy = fac.yx;
        if (dist.x > maxdist || dist.x < tol) {
            return vec4(d, dist.y, dist.z, fac.w);
            break;
        }
    }
	return vec4(d, 0.,0.,0.);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    //time varying camera parameters
    float h = PI+iTime*0.3;
    float ww = iTime*0.2;

    //camera position
    vec3 ro = 4.*(0.4*cos(2.*h)+0.7)*vec3(sin(ww)*cos(h), sin(h), cos(ww)*cos(h));
    vec3 w = -normalize(ro);
    vec3 u = normalize(cross(w, vec3(10., 0., 0.)));
    vec3 v = cross(u, w);
    vec3 rd = normalize(w*fdist+(fragCoord.x/iResolution.x-0.5)*v+(fragCoord.y-iResolution.y/2.0)/iResolution.x*u);

    vec4 record = raymarch(ro, rd);
    //float disp = texture(iChannel0, vec2(record.y/(2.*PI)+0.5, record.z/(2.*PI)+0.5)).x;
    //if (record.w > 0.5) {
    vec4 n = gradient(ro+record.x*rd);
    //	fragColor = vec4(0.5+0.5*n, 1.);
    //} else {
    //    fragColor = vec4(vec3(0.5), 1.);
    //}
    float edgefac = n.w*laplace_factor;
    //vec3 color = clamp(vec3(edgefac, 1.-edgefac, 0.6), 0.,1.5);
    vec3 color = viridis_quintic(edgefac);
    float fogfac = record.x/40.;
    vec3 shadedcolor = min(1.,ao_min+(record.w > ao_radius ? 1. : record.w/(ao_radius)))*dot(n.xyz,-w)*color;
    vec3 ambient = mix(vec3(0.0, 0.0, 0.1), vec3(0.0, 0.5, 1.0), pow(1.0-abs(rd.y), 7.0));
    shadedcolor += ambient*fogfac;
    fragColor = vec4(shadedcolor, 1.);
}
