#if !defined PARALLAX_LIB
#define PARALLAX_LIB
    bool raytraceParallax(in vec3 rayDirection, in vec3 rayPosition, in ivec2 tileOffset, in ivec2 tileResolution, in ivec2 rayTexel, in bool checkStart, inout vec3 hitPosition, inout vec3 hitNormal, inout ivec2 hitTexel) {
        #ifdef PARALLAX_MAPPING
            rayDirection.xy *= PARALLAX_DEPTH;
            rayDirection.xy /= abs(rayDirection.z);
            rayDirection.z   = sign(rayDirection.z);

            ivec2 deltaSign  = ivec2(sign(rayDirection.xy));
            vec2 deltaT      = abs(1.0 / (rayDirection.xy * tileResolution));
            vec2 nextT       = deltaT * (max(deltaSign, 0) + deltaSign * rayTexel - deltaSign * tileResolution * rayPosition.xy);

            if(checkStart) {
                float height = texelFetch(atlas_n, rayTexel % tileResolution + tileOffset, 0).a;

                float entryT = 0.0;
                float exitT  = min(nextT.x, nextT.y);

                int stepAxis = 0;

                float hitT = (height - rayPosition.z) / rayDirection.z;
                bool hit   = rayDirection.z < 0.0 ? hitT < exitT : entryT < hitT;
                if(hit) {
                    hitTexel = rayTexel;

                    if(hitT < entryT) {
                        hitPosition = rayPosition + entryT * rayDirection;
                        hitNormal   = vec3(stepAxis == 0 ? -sign(rayDirection.x) : 0.0, stepAxis == 1 ? -sign(rayDirection.y) : 0.0, 0.0);
                    } else {
                        hitPosition = rayPosition +   hitT * rayDirection;
                        hitNormal   = vec3(0.0, 0.0, -sign(rayDirection.z)); 
                    }

                    return true;
                }
            }

            float maxT = rayDirection.z < 0.0 ? (0.0 - rayPosition.z) / rayDirection.z : (1.0 - rayPosition.z) / rayDirection.z;

            for(int i = 0; i < PARALLAX_QUALITY; ++i) {
                int stepAxis = nextT.y < nextT.x ? 1 : 0;
                float entryT = nextT[stepAxis];

                if(maxT < entryT) { break; }

                rayTexel[stepAxis] += deltaSign[stepAxis];
                nextT[stepAxis]    += deltaT[stepAxis];

                float exitT = min(nextT.x, nextT.y);

                float height = texelFetch(atlas_n, rayTexel % tileResolution + tileOffset, 0).a;

                float hitT = (height - rayPosition.z) / rayDirection.z;
                bool hit = rayDirection.z > 0.0 ? hitT > entryT : hitT < exitT;
                if(hit) {
                    hitTexel = rayTexel;

                    if(hitT < entryT) {
                        hitPosition = rayPosition + entryT * rayDirection;
                        hitNormal   = vec3(stepAxis == 0 ? -sign(rayDirection.x) : 0.0, stepAxis == 1 ? -sign(rayDirection.y) : 0.0, 0.0);
                    } else {
                        hitPosition = rayPosition +   hitT * rayDirection;
                        hitNormal   = vec3(0.0, 0.0, -sign(rayDirection.z)); 
                    }

                    return true;
                }
            }

            hitPosition = rayPosition + rayDirection * (1.0 - rayPosition.z) / rayDirection.z;
        #else
            hitNormal = hitNormal;
            hitTexel = rayTexel;
        #endif

        return false;
    }
#endif