#if !defined MATERIAL_STRUCT_LIB
#define MATERIAL_STRUCT_LIB
    struct Bounce {
        vec3 n;
        vec3 k;
        vec3 normal;
        vec3 flatNormal;
        vec3 albedo;

        vec3 emissive;

        float opacity;
        float ao;
        float sss;
        float f0;
        float roughness;
        float roughnessSquared;
        float id;
        float albedoMetal;
        float isMetal;
    };

    vec3 clampNormal(in vec3 normals, in vec3 viewDirection) {
        float nDotV = saturate(dot(normals, -viewDirection));
        return fNormalize(normals - saturate(dot(viewDirection, normals)) * viewDirection);
    }

    Bounce hitMaterial(in vec4 albedoMap, in vec4 specularMap, in vec3 surfaceNormal, in vec3 hitNormal, in float id, in float ambientOcclusion) {
        Bounce b;

        b.albedo = srgbToLinear(albedoMap.rgb);
        vec3 metalAlbedo = b.albedo;
        //b.albedo *= (1.0 - float(specularMap.g > 0.9));
        b.normal = surfaceNormal;
        if(id == 17) {
            #ifndef WATER_NORMAL
                b.normal = hitNormal;
            #endif
            b.albedo = b.albedo * 0.1;

            specularMap.r = 1.00;
            specularMap.g = 0.02;
        }
        if(id == 160) {
            b.albedo = vec3(1.0);
        }
        b.flatNormal = hitNormal;

        b.isMetal = float(specularMap.g > 0.9);

        b.emissive = vec3(0.0);
        if(id == 18 || id == 240) {
            b.emissive = b.albedo * 3e3;
        }

        if(id == 20 || id >= 147 && id <= 150) {
            b.emissive = blackbody(1400.0) * 555.0;// * pow(max(dot(b.albedo, b.albedo) * 0.7 - 0.7, 0.0), 1.0);
        }

        if(id == 241 || id == 45 || id == 46) {
            b.emissive = b.albedo * 3e3;
        }

        if(id == 242) {
            b.emissive = b.albedo * pow(dot(b.albedo, vec3(0.7)), 2.0) * 1e3;
        }

        if(id == 160) {
            b.emissive = blackbody(1400.0) * 700.0;
        }

        #ifdef LAB_EMISSIVES
            specularMap.a = specularMap.a * 255.0;

            b.emissive = b.albedo * 1e3 * (specularMap.a * float(specularMap.a <= 254.0));
        #endif

        b.opacity = albedoMap.a;
        b.ao = ambientOcclusion;

        if(specularMap.g > 0.9) {
            specularMap.r = 0.75;
        }


        b.sss = 0.0;
        b.roughness = square(1.0 - specularMap.r);
        b.roughness = saturate(b.roughness);
        b.roughnessSquared = square(b.roughness);
        b.f0 = specularMap.g;

        b.n = vec3(f0ToIOR(b.f0));
        b.k = vec3(0.0);

        float iron = 0.0;     //1
        float gold = 0.0;     //2
        float aluminum = 0.0; //3
        float chrome = 0.0;   //4
        float copper = 0.0;   //5
        float lead = 0.0;     //6
        float platinum = 0.0; //7
        float silver = 0.0;   //8

        b.albedoMetal = 0.0;

        if(specularMap.g > 0.900 && specularMap.g < 0.902) {
            iron = 1.0;     //1
        }
        if(specularMap.g > 0.902 && specularMap.g < 0.906) {
            gold = 1.0;     //2
        }
        if(specularMap.g > 0.906 && specularMap.g < 0.910) {
            aluminum = 1.0; //3
        }
        if(specularMap.g > 0.910 && specularMap.g < 0.913) {
            chrome = 1.0;   //4
        }
        if(specularMap.g > 0.914 && specularMap.g < 0.918) {
            copper = 1.0;   //5
        }
        if(specularMap.g > 0.918 && specularMap.g < 0.922) {
            lead = 1.0;     //6
        }
        if(specularMap.g > 0.922 && specularMap.g < 0.926) {
            platinum = 1.0; //7
        }
        if(specularMap.g > 0.926 && specularMap.g < 0.930) {
            silver = 1.0;   //8
        }

        if(iron > 0.9) {
            b.n = vec3(2.9114, 2.9497, 2.5845);
            b.k = vec3(3.0893, 2.9318, 2.7670);
        }
        if(gold > 0.9) {
            b.n = vec3(0.18299, 0.42108, 1.3734);
            b.k = vec3(3.4242, 2.3459, 1.7704);
        }
        if(aluminum > 0.9) {
            b.n = vec3(1.3456, 0.96521, 0.61722);
            b.k = vec3(7.4746, 6.3995, 5.3031);
        }
        if(chrome > 0.9) {
            b.n = vec3(3.1071, 3.1812, 2.3230);
            b.k = vec3(3.3314, 3.3291, 3.1350);
        }
        if(copper > 0.9) {
            b.n = vec3(0.27105, 0.67693, 1.3164);
            b.k = vec3(3.6092, 2.6248, 2.2921);
        }
        if(lead > 0.9) {
            b.n = vec3(1.9100, 1.8300, 1.4400);
            b.k = vec3(3.5100, 3.4000, 3.1800);
        }
        if(platinum > 0.9) {
            b.n = vec3(2.3757, 2.0847, 1.8453);
            b.k = vec3(4.2655, 3.7153, 3.1365);
        }
        if(silver > 0.9) {
            b.n = vec3(0.15943, 0.14512, 0.13547);
            b.k = vec3(3.9291, 3.1900, 2.3808);
        }

        if(specularMap.g > 0.999999) {
            b.n = vec3(10.0);
            b.k = metalAlbedo;
            b.albedoMetal = 1.0;
        }

        return b;
    }

    vec2 textureWrap(in vec2 coord, in mat2 tileInfo) {
        vec2 wrappedCoord = round((tileInfo[1] - coord) / tileInfo[0]) * tileInfo[0] + coord;
        return wrappedCoord;
    }

    float gerstnerWaves(vec2 coord, float time, float waveSteepness, float waveAmplitude, float waveLength, vec2 waveDirection){
        const float g = 9.81;
        
        float k = tau / waveLength;
        float w = sqrt(g * k);

        float x = w * time - k * dot(waveDirection, coord);
        float wave = sin(x) * 0.5 + 0.5;

        float h = waveAmplitude * pow(wave, waveSteepness);

        return h;
    }

    float smoothNoise(vec2 coord) {
        //return textureSmooth(noisetex, coord / 64.0).r; // slightly faster but has banding artifacts

        vec2 floored = floor(coord);
        vec4 samples = textureGather(noisetex, 0.015625 * floored); // textureGather is slightly offset (at least on nvidia) and this offset can change with driver versions, which is why i floor the coords
        vec4 weights = (coord - floored).xxyy * vec4(1,-1,1,-1) + vec4(0,1,0,1);
        weights *= weights * (-2.0 * weights + 3.0);
        return dot(samples, weights.yxxy * weights.zzww);
    }

    float calculateWaves(in vec2 position) {
        float waves = 0.0;
        float steepness = 0.80;
        float amplitude = 0.02;
        float wLength = 2.0;
        vec2 direction = vec2(0.4, 0.6);

        float a = tau / 2.55;
        for(int i = 0; i < 6; ++i) {
            vec2 noise = vec2(smoothNoise(position + i / 6));
            waves += -gerstnerWaves(position + 0.3 * (noise * 2.0 - 1.0) * sqrt(wLength), 1.0, steepness, amplitude, wLength, direction);

            steepness *= 1.02;
            amplitude *= 0.7;
            wLength *= 0.7;
		    direction *= rotate(a);
        }
        return waves;
    }

    vec3 waterNormal(vec2 position) {
        cFloat dist = 4e-3;

        vec2 diffs;
        diffs.x = calculateWaves(position + vec2( dist, -dist));
        diffs.y = calculateWaves(position + vec2(-dist, dist));
        diffs  -= calculateWaves(position + vec2(-dist, -dist));

        vec3 wavenormal = vec3(-2.0 * dist, 4.0 * dist * dist, -2.0 * (dist * dist + dist));
        wavenormal.xz *= diffs;

        return wavenormal;
    }

    Bounce getBounceMaterial(in vec3 position, in ivec2 uv, in vec3 hitNormal, in vec3 viewDirection, in mat3 tbn, in vec4[2] voxel, in int bounceID) {
        float id = int((255.5 - OCTREE_LEVELS) - voxel[0].a * 255.0);

        vec2 tileIdx = 256.0 * 15.0 * UnpackUnorm2x4(voxel[1].y) + 255.0 * voxel[1].zw;

        mat2 tileInfo = mat2(
            exp2(voxel[1].x * 15.0) / textureSize(atlas, 0),
            (tileIdx + 0.5) * (exp2(voxel[1].x * 15.0) / textureSize(atlas, 0))
        );

        //uv = parallaxMapping(uv, normalize(viewDirection * tbn), tileInfo);

        ivec2 atlasSize = ivec2(textureSize(atlas, 0));

        vec4 albedoMap = texelFetch(atlas, uv, 0) * vec4(voxel[0].rgb, 1.0);
        #ifdef WHITE_WORLD
            if(bounceID == 0) {
                albedoMap.rgb = vec3(1.0);
            }
        #endif

        vec4 specularMap = texelFetch(atlas_s, uv, 0);
        vec4 normalMap = texelFetch(atlas_n, uv, 0) * 2.0 - 1.0;

        #ifdef LAB_PBR_13
            float ao = normalMap.z;

            normalMap.xyz = vec3(normalMap.xy, sqrt(saturate(1.0 - dot(normalMap.xy, normalMap.xy))));
        #else
            float ao = 1.0;

            normalMap.xyz = normalMap.xyz;
        #endif
        normalMap.xyz = normalize(tbn * normalMap.xyz);

        #ifdef WATER_NORMAL
            if(id == 17) {
                normalMap.xyz = normalize(tbn * waterNormal((SceneSpaceFromVoxelSpace(position) + cameraPosition).xz).xzy);
            }
        #endif

        #ifndef MC_NORMAL_MAP
            normalMap.xyz = hitNormal;
        #endif

        return hitMaterial(albedoMap, specularMap, normalMap.xyz, hitNormal, id, ao);
    }

    #include "parallax.glsl"
#endif