#define DEPTH_LIMIT 30.0
#define TAU 6.283185
#define PI 3.141592
#define BPM 174.0

struct Material {
    float depth;
    vec3 color;
    float id;
};

// GPU Gems 3 - Ryan Geiss: https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch01.html
vec3 tex3D(sampler2D t, in vec3 p, in vec3 n ){

    n = max(abs(n), 0.001);
    n /= dot(n, vec3(1));
	vec3 tx = texture(t, p.yz).xyz;
    vec3 ty = texture(t, p.zx).xyz;
    vec3 tz = texture(t, p.xy).xyz;

    return (tx*tx*n.x + ty*ty*n.y + tz*tz*n.z);

}

vec3 rotatePoint(vec3 p, vec3 axis, float angle) {
    float cosA = cos(angle);
    float sinA = sin(angle);
    float oneMinusCosA = 1.0 - cosA;

    mat3 rotationMatrix = mat3(
        cosA + axis.x*axis.x*oneMinusCosA,     axis.x*axis.y*oneMinusCosA - axis.z*sinA, axis.x*axis.z*oneMinusCosA + axis.y*sinA,
        axis.y*axis.x*oneMinusCosA + axis.z*sinA, cosA + axis.y*axis.y*oneMinusCosA,     axis.y*axis.z*oneMinusCosA - axis.x*sinA,
        axis.z*axis.x*oneMinusCosA - axis.y*sinA, axis.z*axis.y*oneMinusCosA + axis.x*sinA, cosA + axis.z*axis.z*oneMinusCosA
    );

    return rotationMatrix * p;
}

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

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

Material opU(in Material d1, in Material d2) {
	if (d2.depth < d1.depth) {
        return d2;
    } else {
        return d1;
    }
}

Material sMin(Material a, Material b, float k) {
    float h = clamp(0.5 + 0.5 * (b.depth - a.depth) / k, 0.0, 1.0);
    Material result;
    result.depth = mix(b.depth, a.depth, h) - k * h * (1.0 - h);
    result.color = mix(b.color, a.color, h);
    return result;
}


Material map(in vec3 pos) {
    float d1 = 0.0;
    d1 = pos.y;
    Material ground = Material(d1, vec3(0.682,0.682,0.682), 0.5);

    float d2 = 0.0;
    d2 = sdSphere(fract(pos) - 0.5 + sin((iTime*PI*BPM/60.0)) / 60., 0.15);
    vec3 id1 = floor(pos);
    vec3 col = vec3(0.3,0.906,0.784);
    col += 0.36*cos((id1.x*14.1+id1.y*32.341+id1.z*15.28) + vec3(0.0,1.0,2.0));

    float d3 = 0.0;
    vec3 p = vec3(pos.x, pos.y, pos.z - 0.5);
    d3 = sdSphere(fract(p) - 0.5, 0.25);
    vec3 id2 = floor(p);
    vec3 col2 = vec3(0.906,0.3,0.784);
    col2 += 0.2*cos((id2.y*14.1+id2.z*32.341+id2.x*15.28) + vec3(1.0,2.0,0.0));

    float d4 = 0.0;
    p = vec3(pos.x, pos.y - 0.5, pos.z) ;
    d4 = sdSphere(fract(p) - 0.5, 0.125);
    vec3 id3 = floor(p);
    vec3 col3 = vec3(0.784, 0.906,0.3);
    col3 += 0.6*sin((id3.y*14.1+id3.x*32.341+id3.z*15.28) + vec3(1.0,1.5,0.5));

    Material s1 = Material(d2, col, 1.5);
    Material s2 = Material(d3, col2, 1.5);
    Material s3 = Material(d4, col3, 1.5);

    p = vec3(pos.x, pos.y -2.5, pos.z - -3.);
    float torusX = sdTorus(fract(p) - 0.5, vec2(0.09, 0.09 + abs(cos(iTime * 2.0)) / 50.));

    vec3 link1 = floor(p);
    vec3 colLink1 = vec3(0.306,0.5,0.784);
    colLink1 += 0.36*cos((link1.z*1.1+link1.x*3.341+link1.y*0.28) + vec3(1.0,0.3,1.0));

    Material t1 = Material(torusX, colLink1, 1.5); // vec3(0.290,0.290,0.290)

    p = vec3(pos.x, pos.y - 2., pos.z /2. - -2.);
    float torusXX = sdTorus(fract(p) - 0.5, vec2(0.15, 0.12 + abs(sin((0.5*iTime*PI*BPM/60.0))/20.))); // vec2(0.15, 0.12 * sin((iTime*PI*BPM/60.0))/0.5)

    vec3 link2 = floor(p);
    vec3 colLink2 = vec3(0.406,0.9,0.284);
    colLink2 += 0.4*cos((link2.x*4.1+link2.y*2.341+link2.z*1.28) + vec3(0.5,0.5,1.5));

    Material t2 = Material(torusXX, colLink2, 1.5); // vec3(0.102,0.275,0.212)



    //Material res = sMin(s1, s2, 0.75-clamp(fract(0.5*iTime*BPM/60.0), 0., 0.5));
    Material res = sMin(s1, s2, 0.45-sin((0.5*iTime*PI*BPM/60.0)) / 20.);
    res = sMin(res, s3, 0.5);
    res = opU(res, t1);
    res = opU(res, t2);
    res = opU(ground, res);
    return res;
}

Material rayCast(in vec3 ro, in vec3 rd) {
    float t = 0.0;
    Material mat;

    for(int i = 0; i < 100; ++i) {
        vec3 pos = ro + rd * t;
        mat = map(pos);
        if(abs(mat.depth) < 0.001 || mat.depth > DEPTH_LIMIT) break;
        t+= mat.depth * 0.8;
    }

    if (t < DEPTH_LIMIT) return Material(t, mat.color, mat.id);

    return Material(DEPTH_LIMIT, mat.color, mat.id);
}

vec3 getNormal(in vec3 pos, float t) {
    vec2 e = vec2(0.0001, 0.0);
    return normalize(vec3(map(pos+e.xyy).depth - map(pos-e.xyy).depth,
                          map(pos+e.yxy).depth - map(pos-e.yxy).depth,
                          map(pos+e.yyx).depth - map(pos-e.yyx).depth));
}


////////// https://www.shadertoy.com/view/cdjGWy
float softShadow(vec3 ro, vec3 lp, vec3 n, float k){
    const int maxIterationsShad = 32;

    ro += n*.0015;
    vec3 rd = lp - ro;

    float shade = 1.;
    float t = 0.;
    float end = max(length(rd), .0001);

    rd /= end;


    for (int i = min(iFrame, 0); i<maxIterationsShad; i++){

        float d = map(ro + rd*t).depth;
        shade = min(shade, k*d/t);

        t += clamp(d, .005, .15);

        if (d<0. || t>end) break;
    }

    return max(shade, 0.);
}

float calcAO(in vec3 p, in vec3 n){

	float sca = 3., occ = 0.;
    for( int i = 0; i<5; i++ ){

        float hr = float(i + 1)*.15/5.;
        float d = map(p + n*hr).depth;
        occ += (hr - d)*sca;
        sca *= .7;
    }

    return clamp(1. - occ, 0., 1.);
}
//////////


vec3 render(in vec3 ro, in vec3 rd, in Material tm, in vec3 lp) {
    vec3 col = vec3(0.4, 0.75, 1.0) - 0.7 * rd.y;
    float t = tm.depth;
    if(t < DEPTH_LIMIT) {
        // Surface position and surface normal
	    vec3 sp = ro + rd*t;
        vec3 sn = getNormal(sp, t);

        // Light direction vector
	    vec3 ld = lp - sp;


            // Distance from respective light to the surface point.
            float lDist = max(length(ld), .001);

            // Normalize the light direction vector.
            ld /= lDist;

            // Light attenuation, based on the distances above.
            float atten = 1./(1. + lDist*.05);

            float sh = softShadow(sp, lp, sn, 16.);
            float ao = calcAO(sp, sn);


        // Diffuse lighting
        float diff = max(dot(sn, ld), 0.0);
        // Specular lighting
	    float spec = pow(max(dot(reflect(ld, sn), rd ), 0.), 32.);

            // Fresnel term. Good for giving a surface a bit of a reflective glow.
            //float fre = pow(clamp(1. - abs(dot(sn, rd))*.5, 0., 1.), 2.);

            // Schlick approximation. I use it to tone down the specular term. It's pretty subtle,
            // so could almost be aproximated by a constant, but I prefer it. Here, it's being
            // used to give a hard clay consistency... It "kind of" works.
            float Schlick = pow( 1. - max(dot(rd, normalize(rd + ld)), 0.), 5.);
            float freS = mix(.15, 1., Schlick);  //F0 = .2 - Glass... or close enough.

        col = tm.color;

        if(tm.id < 1.5){
            col = dot(col, tex3D(iChannel0, sp, sn)) *.7 + col*.5;
            col = col*(diff*sh + .3 + vec3(1, .97, .92)*spec*freS*2.0*sh);

        } else if (tm.id < 2.5) {
            col = dot(col, tex3D(iChannel1, sp*2., sn)) *.9 + col*.25;
            col = col*(5.*diff*sh + .3 + vec3(1, .97, .92)*5.*spec*freS*2.0*sh);
        }



        //col = col*(diff*sh + .3 + vec3(1, .97, .92)*spec*freS*2.0*sh);

        col *= ao*atten;
        //col = col*diff;
    }
    //return col;
    return mix(col, mix(col, vec3(0.7, 0.75, 0.8), exp(-1.0*rd.y)) / 8., smoothstep(0., .5, t/DEPTH_LIMIT));
}

vec3 calculateRayDirection(vec3 ro, vec3 lookAtPos, float FOV, vec2 uv) {
    // Forward
    vec3 fwd = normalize(lookAtPos - ro);
    // Right
    vec3 rgt = normalize(cross(vec3(0, 1, 0), fwd));
    // Up
    vec3 up = cross(fwd, rgt);
    // Calculate the ray direction
    vec3 rd = normalize(uv.x * rgt + uv.y * up + fwd / FOV);
    return rd;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord - iResolution.xy*.5)/iResolution.y;

    float time = iTime - 34.5;
    float speed = time * 2. * min(time, 3.);
    if(iTime < 34.5)
        speed = 1.;

    vec3 ro = vec3(0.0 + cos(iTime * 0.83 + PI/2.) / 8.19 , 2.0 + sin(iTime) + sin((0.5*iTime*PI*BPM/60.0)) / 6., -8.0 + speed + sin((0.5*iTime*PI*BPM/60.0)) / 30.);
    vec3 lookAtPos = vec3(0.0 + sin(iTime * 0.63) * 4. , -0.06, 0.0 + speed);  // Point vers lequel la caméra regarde
    float FOV = 1.0;  // Ajustez le champ de vision selon vos besoins
    vec3 rd = calculateRayDirection(ro, lookAtPos, FOV, uv);  // Calcul de la direction du rayon

    Material tm = rayCast(ro, rd);

    // Light position
    vec3 lp = vec3(2.0, 2.0, 0.0 + speed + sin(iTime/ 2.) * 6.);

    vec3 col = render(ro, rd, tm, lp);

    // sqrt gamma correction
    fragColor = vec4(sqrt(max(col , 0.)), 1.0);
}
