#if !defined PATH_TRACED_LIGHTING_LIB
#define PATH_TRACED_LIGHTING_LIB
    void calculateRefractionPath(inout vec3 contribution, inout vec3 rayDirection, in vec3 oldRayDirection, in vec3 hitNormal, in Bounce pathMaterial) {
        rayDirection = refract(oldRayDirection, hitNormal, 1.00029/1.3333);

        float nDotV = dot(hitNormal, -oldRayDirection);
        float nDotL = saturate(dot(hitNormal, rayDirection));

        contribution *= pathMaterial.albedo;
        if(dot(hitNormal, rayDirection) > 0.0) contribution *= smithGGXMaskingShadowing(nDotV, nDotL, 0.0) / smithGGXMasking(nDotV, 0.0);
    }

    vec3 calculateLightPath(in vec3 hitNormal, in vec3 hitPosition, in vec3 parallaxHitPosition, in vec3 parallaxStartPosition, in vec3 rayDirection, in ivec3 hitIndex, in vec2 uv, in ivec2 hitTexel, in mat3 tbn, in vec4[2] voxelData, in Bounce initialMaterial) {

        Bounce pathMaterial = initialMaterial;

        vec3 contribution = vec3(1.0);
        vec3 pathColor = vec3(0.0);

        pathColor += initialMaterial.emissive;

        bool skyPath = false;

        float celestialProbability = CELESTIAL_BOUNCE_PROBABILITY;
        float moonPathProbability = MOON_BOUNCE_PROBABILITY;

        bool moonPath;

        bool celestialPath;

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

        bool diffusePath = true;

        int b = 0;
        while(b < MAX_BOUNCES) {
            ++b;

            vec3 oldRayDirection = rayDirection;

            celestialPath = randNextF() < celestialProbability;

            if(!celestialPath) {
                contribution /= 1.0 - celestialProbability;

                vec3 facetNormal = generateMicrofacetNormal(pathMaterial.normal, oldRayDirection, pathMaterial.roughness);

                vec3 fresnel = fresnelFunction(dot(facetNormal, -oldRayDirection), pathMaterial.n, pathMaterial.k, pathMaterial.albedoMetal);

                float fresnelLum = dot(fresnel, lumacoeff_rec709);
                float albedoLum = dot(pathMaterial.albedo, lumacoeff_rec709);
                float totalLum = albedoLum * (1.0 - fresnelLum) + fresnelLum; //I think this is named correctly? Not entirely sure.
                float specBounceProbability = fresnelLum / totalLum;

                #ifdef PROBABILISTIC_PATH_TERMINATION
                    //This helps performance a bit. Not sure what I'd call this though.
                    if(randNextF() < 1.0 - totalLum) { break; }
                    contribution /= totalLum;
                #endif

                mat3 tbn2 = (tbn * getRotationMatrix(tbn[2], pathMaterial.normal));

                vec3 v = oldRayDirection * tbn2;
                     v.z = -abs(v.z);

                rayDirection = tbn2 * sampleMicrosurfaceBSDF(
                    v,
                    contribution, 
                    pathMaterial
                );
                contribution = max(contribution, 0.0);

                #ifdef RUSSIAN_ROULETTE_RENDERING
                    float p = saturate(max(contribution.x, max(contribution.y, contribution.z)));
                    if(randNextF() > p) {
                        break;
                    }

                    contribution /= p;
                #endif

                if(dot(pathMaterial.flatNormal, rayDirection) <= 0.0) { celestialPath = false; continue; }
            } else {
                contribution /= celestialProbability;

                mat3 tbn2 = (tbn * getRotationMatrix(tbn[2], pathMaterial.normal));

                vec3 v = oldRayDirection * tbn2;
                     v.z = -abs(v.z);

                if(moonPath) {
                    rayDirection = generateConeVector(moonVector, randNext2F(), moonAngularRadius);
                } else {
                    rayDirection = generateConeVector(sunVector, randNext2F(), sunAngularRadius);
                }

                contribution *= dot(pathMaterial.normal, rayDirection) * estimateMicrosurfaceBSDF(v, rayDirection * tbn2, b, pathMaterial);

                if(dot(pathMaterial.flatNormal, rayDirection) <= 0.0) { celestialPath = false; break; }
            }
            vec2 tileIdx = 256.0 * 15.0 * UnpackUnorm2x4(voxelData[1].y) + 255.0 * voxelData[1].zw;
            ivec2 tileSize = ivec2(exp2(voxelData[1].x * 15.0));
            ivec2 tileOffset = ivec2(tileIdx * tileSize);

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

            bool hitLight = raytraceParallax(rayDirection * tbn,   parallaxHitPosition, tileOffset, tileSize, hitTexel, false, parallaxHitPosition, hitNormal, hitTexel);
            #ifdef PARALLAX_MAPPING
                hitNormal = tbn * hitNormal;

                vec3 offset = parallaxHitPosition - parallaxStartPosition;
                //hitPosition += tbn * offset;
            #endif

            bool hit = RaytraceVoxelsAlphatest(
                hitPosition, hitIndex, rayDirection,
                hitPosition, hitIndex, hitNormal,
                voxelData, uv, tbn
            );
            if (hitLight) { hit = true; }

            tileIdx = 256.0 * 15.0 * UnpackUnorm2x4(voxelData[1].y) + 255.0 * voxelData[1].zw;
            tileSize = ivec2(exp2(voxelData[1].x * 15.0));
            tileOffset = ivec2(tileIdx * tileSize);

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

            parallaxStartPosition = vec3((uv - tileInfo[1]) / tileInfo[0], 1.0);

            bool hitView = raytraceParallax(rayDirection * tbn, parallaxStartPosition, tileOffset, tileSize, ivec2(parallaxStartPosition.xy * tileSize), true, parallaxHitPosition, hitNormal, hitTexel);
            #ifdef PARALLAX_MAPPING
                hitNormal = tbn * hitNormal;
            #endif

            if(celestialPath) {
                if(hit) {
                    celestialPath = false;
                    skyPath = false;
                }

                break;
            } else {
                if(hit) {
                    pathMaterial = getBounceMaterial(hitPosition, hitTexel % tileSize + tileOffset, hitNormal, rayDirection, tbn, voxelData, b);

                    pathColor += contribution * pathMaterial.emissive;

                    skyPath = false;
                } else {
                    skyPath = true;

                    break;
                }
            }
        }

        if(celestialPath) {
            vec3 directLightColor = vec3(0.0);
            if(moonPath) {
                directLightColor = atmosphereTransmittance(vec3(0.0, planetRadius, 0.0), rayDirection) * moonIlluminance;
                directLightColor /= moonPathProbability;
            } else {
                directLightColor = atmosphereTransmittance(vec3(0.0, planetRadius, 0.0), rayDirection) * sunIlluminance;
                directLightColor /= 1.0 - moonPathProbability;
            }
            pathColor += directLightColor * contribution;
        }

        if(skyPath) {
            pathColor += contribution * atmosphericScattering(vec3(0.0, planetRadius, 0.0), rayDirection);
        }

        return max(pathColor, 0.0);
    }
#endif