// Ray vs AABB (slab test). Guards division by zero: a ray axis-aligned with a
// box side used to produce NaN here, which silently skipped real hits. The
// returned value is only used as a boolean, and the boxes here are always
// tight, so this stays cheap.
bool intersectsAABB(vec3 ro, vec3 rd, vec3 aabbMin, vec3 aabbMax) {
    vec3 invRd = mix(vec3(1e6), 1.0 / rd, greaterThanEqual(abs(rd), vec3(1e-6)));

    vec3 t0 = (aabbMin - ro) * invRd;
    vec3 t1 = (aabbMax - ro) * invRd;

    vec3 tMin = min(t0, t1);
    vec3 tMax = max(t0, t1);

    float m0 = max(max(tMin.x, tMin.y), tMin.z);
    float m1 = min(min(tMax.x, tMax.y), tMax.z);

    return m1 > max0(m0);
}

bool intersectsParallelogram(vec3 ro, vec3 rd, vec3 v0, vec3 v1, vec3 v2, float tMin, out float t, out vec2 uv) {
    vec3 a = v1 - v0, n = cross(a, v2 - v0);

    // Guard division by zero: rays running parallel to the quad's plane used to
    // produce inf/NaN t which could leak through the bounds check below.
    float denom = dot(n, rd);
    if (abs(denom) < 1e-6) return false;
    t = dot(v0 - ro, n) / denom;
    if (t < 0.0 || t > tMin) return false;

    vec3 b = v2 - v1;
    vec3 c = ro + rd * t - v0;

    uv = vec2(dot(c, a) / dot(a, a), dot(c, b) / dot(b, b));
    if (uv.x < 0.0 || uv.y < 0.0 || uv.x > 1.0 || uv.y > 1.0) return false;

    return true;
}

void CheckQuadAt(int i, vec3 playerPos, vec3 rayDir, inout vec3 albedo, inout float tMin) {
    int i0 = 3 * i, i1 = 3 * i + 1, i2 = 3 * i + 2;

    vec3 v0 = playerVerticesSSBO.vertexPositions[i0];
    vec3 v1 = playerVerticesSSBO.vertexPositions[i1];
    vec3 v2 = playerVerticesSSBO.vertexPositions[i2];

    float t;
    vec2 uv;
    if (intersectsParallelogram(playerPos, rayDir, v0, v1, v2, tMin, t, uv)) {
        vec2 texCoord0 = playerVerticesSSBO.vertexData[i0];
        vec2 texCoord1 = playerVerticesSSBO.vertexData[i1];
        vec2 texCoord2 = playerVerticesSSBO.vertexData[i2];

        vec2 quadTexCoord = mix(texCoord0, texCoord1, uv.x) + uv.y * (texCoord2 - texCoord1);
        // Clamp to the 64x64 atlas: an interpolated uv of exactly 1.0 used to
        // index texel 64, which is out of bounds.
        vec4 playerAtlasSample = texelFetch(playerAtlas_sampler, ivec2(clamp(quadTexCoord * 64.0, vec2(0.0), vec2(63.0))), 0);

        if (playerAtlasSample.a > 0.05) {albedo = playerAtlasSample.rgb; tMin = t;}
    }
}

bool rayTracePlayer(vec3 playerPos, vec3 rayDir, float wsrTraceLength, out vec3 albedo) {
    float tMin = wsrTraceLength;
    vec3 aabbPos = playerPos * 1000.0;

    // Single combined AABB test first: most reflection rays never come anywhere
    // near the player model, so reject them with ONE slab test instead of the
    // six per-body-part tests below. The combined bounds are filled in by the
    // shadow pass (UpdatePlayerVertexList) and reset each frame in clearSSBOs.
    if (!intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.playerMin, playerVerticesSSBO.bounds.playerMax)) {
        return false;
    }

    // Head
    if (intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.headMin, playerVerticesSSBO.bounds.headMax)) {
        for (int i = 0; i < 12; i++) {
            CheckQuadAt(i, playerPos, rayDir, albedo, tMin);
        }
    }
    // Right Hand
    if (intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.rightHandMin, playerVerticesSSBO.bounds.rightHandMax)) {
        for (int i = 12; i < 24; i++) {
            CheckQuadAt(i, playerPos, rayDir, albedo, tMin);
        }
    }
    // Left Leg
    if (intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.leftLegMin, playerVerticesSSBO.bounds.leftLegMax)) {
        for (int i = 24; i < 36; i++) {
            CheckQuadAt(i, playerPos, rayDir, albedo, tMin);
        }
    }
    // Left Hand
    if (intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.leftHandMin, playerVerticesSSBO.bounds.leftHandMax)) {
        for (int i = 36; i < 48; i++) {
            CheckQuadAt(i, playerPos, rayDir, albedo, tMin);
        }
    }
    // Right leg
    if (intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.rightLegMin, playerVerticesSSBO.bounds.rightLegMax)) {
        for (int i = 48; i < 60; i++) {
            CheckQuadAt(i, playerPos, rayDir, albedo, tMin);
        }
    }
    // Torso
    if (intersectsAABB(aabbPos, rayDir, playerVerticesSSBO.bounds.torsoMin, playerVerticesSSBO.bounds.torsoMax)) {
        for (int i = 60; i < 72; i++) {
            CheckQuadAt(i, playerPos, rayDir, albedo, tMin);
        }
    }

    return tMin < wsrTraceLength;
}
