#version 460
precision mediump float;
out vec4 o;
const float PI = 3.14159265;
const float TAU = (2. * PI);
const float PHI = sqrt(5.) * 0.5 + 0.5;
layout(location = 0) uniform float syncs[7];
layout(location = 8) uniform float fft_output[512]; // FFT_SIZE / 4
//uniform sampler2D u_fft_texture;
uniform sampler2D u_hexGridTex;

float u_time = syncs[0];
/* uses some snippets from:
 * "Seascape" by Alexander Alekseev aka TDM - 2014
 * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
 * Contact: tdmaav@gmail.com
 */

//precision mediump float;
vec2 u_resolution = vec2(1920,1080);

vec3 no(vec3 v) { return normalize(v); }
float cl(float a, float b, float c) { return clamp(a,b,c); }

// Rotate
mat2 rot2D(float angle) {
    float s = sin(angle), c = cos(angle);
    return mat2(c, -s, s, c);
}

float smax( float a, float b, float k )
{
    float h = max(k-abs(a-b),0.0);
    return max(a, b) + h*h*0.25/k;
}

float hash(vec2 p, int algo)
{
    if (algo == 1) {
        float h = dot(p,vec2(127.1,311.7));
        return fract(sin(h)*43758.5453123);
    }
    p = 50. * fract( p*0.3183099);
    return fract( p.x*p.y*(p.x+p.y) );
}

float noise(vec2 p, float scale, int a)
{
    vec2 i = floor( p ),
    f = fract( p ),
	u = f*f*(3.-2.*f);
    float sc = scale;
    if (a == 1) sc = 2.;
    return -scale+sc*mix( mix( hash( i + vec2(0.), a),
                              hash( i + vec2(1.0,0.0), a ), u.x),
                         mix( hash( i + vec2(0.0,1.0), a),
                              hash( i + vec2(1.), a), u.x), u.y);
}

// Hexagonal prism, circumcircle variant
float fHexagonCircumcircle(vec3 p, vec2 h) {
    vec3 q = abs(p);
    return max(q.y - h.y, max(q.x * sqrt(3.) * 0.5 + q.z * 0.5, q.z) - h.x);
	//this is mathematically equivalent to this line, but less efficient:
	//return max(q.y - h.y, max(dot(vec2(cos(PI/3), sin(PI/3)), q.zx), q.z) - h.x);
}

float sdHex(vec3 pos, vec2 id, float angle) {
    pos.xz *= rot2D(0.1);
    float d1 = fHexagonCircumcircle(pos, vec2(0.86, 1.));
    return d1;
}

float getScaledFFT(int index, float scale, float offset) {
    // Clamp index to valid range
    index = clamp(index, 0, 511);

    // Get raw FFT value
    float raw = fft_output[index];

    // Apply logarithmic scaling: log(1 + value * scale) + offset
    return log(1.0 + raw * scale) + offset;
}

// Return local coordinates inside hex AND axial ID
struct HexData {
    vec3 local;   // Local position inside hex
    vec2 axial;   // Axial ID (q, r)
};

HexData hexTile(vec3 p, float radius) {
    float q = (sqrt(3.0)/3.0 * p.x - 1.0/3.0 * p.z) / radius;
    float r = (2.0/3.0 * p.z) / radius;

    float rq = round(q);
    float rr = round(r);
    float rs = round(-q - r);

    float dq = abs(rq - q);
    float dr = abs(rr - r);
    float ds = abs(rs + q + r);

    if (dq > dr && dq > ds) rq = -rr - rs;
    else if (dr > ds) rr = -rq - rs;

    float hx = radius * sqrt(3.0) * (rq + rr * 0.5);
    float hz = radius * 1.5 * rr;

    HexData outData;
    outData.local = p - vec3(hx, 0.0, hz);
    outData.axial = vec2(rq, rr); // Hex ID
    return outData;
}

struct HexData {
    vec3 local;
    vec2 axial;
};

float hexDistance(vec2 axial) {
    float q = axial.x;
    float r = axial.y;
    float s = -q - r;
    return max(abs(q), max(abs(r), abs(s)));
}

// Modify your mapScene function
vec3 mapScene(vec3 p) {
    float d = 1e9;
    float mat = 0.;
    float hexRadius = 0.83;
    vec3 hexpos = vec3(p.x, p.y - 10.0, p.z);

    HexData hex = hexTile(hexpos, 1.0);

    // Use axial coordinates as a stable hex ID
    float distFromCenter = hexDistance(hex.axial);
    int fftIndex = int(clamp(distFromCenter * 1.0, 0.0, 511.0)); // tweak 15.0 to taste

    float fftVal = fft_output[fftIndex];
    float hexHeight = 1.0 + fftVal * 1.0;

    // Rotate individual hex tiles if needed
    vec3 r = hex.local;
    r.xz *= rot2D(0.5);

    float d1 = fHexagonCircumcircle(r, vec2(hexRadius, hexHeight));
    d = min(d,d1);

    return vec3(d, 0.0, 0.0);
}

////////////////
//   DRAWING  //
////////////////

float rayMarch(vec3 ro, vec3 rd, int a) {
    vec3 d;
    float t = 0.; // total distance travelled
    // Raymarching
    for (int i = 0; i < 100; i++) {
        d = mapScene(ro + rd * t); // Get distance to objects
        t += d.x; // "march" the ray
        if (d.x < 1e-3 || t > 500.) break;
    }
    return t;
}

vec3 getNormal(vec3 p) {
    vec2 e = vec2(.01, 0.);
    vec3 n = mapScene(p).x - vec3(
        mapScene(p-e.xyy).x,
        mapScene(p-e.yxy).x,
        mapScene(p-e.yyx).x);
    return no(n);
}

float getLight(vec3 p, vec3 lightPos, float intensity, float shadow, vec3 n, float atte) {
    vec3 l = no(lightPos - p);
    float len = length( lightPos - p ); // Distance from the light to the surface point.
    float dif = cl(dot(n, l)*intensity, 0., intensity) * 1.0 / (1.0 + atte*len),
    d = rayMarch(p+n*.0025, l, 1);
    if(d<length(lightPos-p)) dif *= shadow;
    return dif;
}

// lighting
float diffuse(vec3 n,vec3 l,float p) {
    return pow(dot(n,l) * 0.4 + 0.6,p);
}

float specular(vec3 normal,vec3 lightPos,vec3 rayOrigin,float specular) {
    float nrm = (specular + 8.0) / (PI * 8.0);
    return pow(max(dot(reflect(rayOrigin,normal),lightPos),0.0),specular) * nrm;
}

vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye) {

    vec3 color = vec3(0.0, 0.1, 0.3) + diffuse(n,l,60.0) * vec3(0.11, 0.16, 0.18) * 0.3;
    color -= vec3(0.73, 0.15, 0.66) * pow(cl(1.-dot(n, -eye), 0., 1.), .7);
    color += vec3(specular(n,l,eye,60.0))*0.2;
    return color;
}


vec3 applyFog(vec3 col, float t, vec3 rd, vec3  lightDir, float b ) {
    vec3 fogColor = mix( vec3(0.34, 0.11, 0.34), // blue
                         vec3(0.93, 0.37, 0.16), // yellow
                         pow(max( dot(rd, lightDir), 0.) ,8.));
    return mix( col, fogColor, 1.0 - exp(-t*b) );
}

vec3 getCameraRayDir(vec2 uv, vec3 p, vec3 l, float z)
{
    vec3 f = no(l-p),
        r = no(cross(vec3(0.,1.,0.), f)),
        u = cross(f,r),
        c = f*z,
        i = c + uv.x*r + uv.y*u,
        d = no(i);
    return d;
}

vec3 postProcess(vec3 col) {
    // Colour mapping
    col *= vec3(.9, 0.8, 0.7);
    // gamma
    col = pow( col, vec3(.45) );
    // Contrast = a
    col = smoothstep(0., 1., col);

    // fade out at the end
    //col += 1.0 - vec3(cl((46. - u_time)*.5, 0., 1.0));
    return col;
}

vec3 cameraPos()
{
    // first zoom in to the gate
    vec3 cPos = vec3(0., 25., 25.);
    
    return cPos;
}

vec3 cameraPointAt() {
    vec3 p = vec3(0., 0.0, -5.);
    return p;
}

vec3 addSpecular(vec3 nor, vec3 rod, float amount, float phong)
{
    return vec3(specular(nor,no(vec3(0.0,0.3,0.8)),no(rod),pow(10.,phong)))*amount;
}

vec3 sceneGate(vec2 uv)
{
    // Initialization
    vec3 ro = cameraPos(),
    rd = getCameraRayDir(uv, ro, cameraPointAt(), cl(1.0,2.,25.)),
    col = vec3(0.);
    float d = rayMarch(ro, rd,0), mat = 0.;

    if (d < 500.) {
        // Lighting
        vec3 p = ro + rd * d,
        n = getNormal(p);
        mat = mapScene(p).y;
        // Light 1 Arguments
        // 1: Ray starting point
        // 2: Light position
        // 3: Light intensity
        // 4: Shadow intensity

        // Lights
        col += vec3(0.82, 0.5, 0.9) * getLight(p,  vec3( 10., 15., 25.), 1., .2,n,1e-10);
        col += vec3(0.79, 0.66, 0.43) * getLight(p,  vec3( 4., 2., -15.), 1., 1.,n,1e-10);
        col += vec3(0.0, 0.06, 0.7) * getLight(p,  vec3( 0., 0., 5.),cl((u_time-29.0)*100.,0.,50.), 0.0,n,3.1);
        // indirect lightning -> vec3 in normalize is light direction
        col += vec3(0.29, 0.28, 0.33) * cl( dot( n, no(vec3(0. , 1., 10.))), 0., 1.);

        if(mat==0.)
            col *= vec3(0.2, 0.3, 0.3) + addSpecular(n,rd,.5, 2.);
        if(mat==1.)
            col *= getSeaColor(p, n, no(vec3(0.0,0.3,0.8)),no(rd));
        if(mat==2.)
            col *= vec3(0.7, 0.7, 0.4) + noise (p.xz*3.+1.5, 0.1,0);
        if(mat==3.)
            col *= vec3(.8, .8, .5) + noise(p.xz*500.+1e5, .3,0);
        if(mat==4.)
            col *= vec3(0.0, 0.08, 0.11) + addSpecular(n,rd,0.3,0.7);
        if(mat ==5.)
           col = vec3(0.0, 0.08, 0.11);// * pow(cl(1. -dot(n, -rd), 0., 1.), .3);
        if(mat == 6.)
            col *= vec3(0.01, 0.04, 0.06) + addSpecular(n,rd,.1, 2.5);
   }
   return postProcess(applyFog(col, d, rd, vec3(0., -.1, -1.), .01));
}

void main() {
    // Determine FFT bin index for current x position
//    vec2 uv = (gl_FragCoord.xy * 2. - u_resolution.xy) / u_resolution.y;
//    int index = int(floor((1.0+uv.x)*128.0));
//    index = clamp(index, 0, 255);
//
//    // Get the FFT energy (clamped to avoid NaNs or overflow)
//    float energy = clamp(texture(u_fft_texture, vec2(index/255., 0.0)).r, 0.0, 1.0);
//
//    float bar_height = energy;
//    float fade = smoothstep(bar_height, bar_height + 0.02, 1.0 - uv.y);
//
//    vec3 color = vec3(fade);
//
//    o = vec4(color, 1.0);

    o = vec4(sceneGate( (gl_FragCoord.xy * 2. - u_resolution.xy) / u_resolution.y), 1.);
}