//
// Created by sebastian on 04.08.26.
//

#include "AABB.h"

#include "glm/glm.hpp"

void engine::AABB::expandToInclude(const glm::vec3 &point) {
    m_min = glm::min(m_min, point);
    m_max = glm::max(m_max, point);
}

void engine::AABB::expandToInclude(const AABB &other) {
    m_min = glm::min(m_min, other.getMin());
    m_max = glm::max(m_max, other.getMax());
}

engine::AABB engine::AABB::transformed(const glm::mat4 &transform) const {
    glm::vec3 corners[8] = {
        {m_min.x, m_min.y, m_min.z}, {m_max.x, m_min.y, m_min.z},
        {m_min.x, m_max.y, m_min.z}, {m_max.x, m_max.y, m_min.z},
        {m_min.x, m_min.y, m_max.z}, {m_max.x, m_min.y, m_max.z},
        {m_min.x, m_max.y, m_max.z}, {m_max.x, m_max.y, m_max.z},
    };

    glm::vec3 newMin(std::numeric_limits<float>::max());
    glm::vec3 newMax(std::numeric_limits<float>::lowest());

    for (const auto& corner : corners) {
        glm::vec3 transformed = glm::vec3(transform * glm::vec4(corner, 1.0f));
        newMin = glm::min(newMin, transformed);
        newMax = glm::max(newMax, transformed);
    }

    return {newMin, newMax};
}

std::optional<engine::RayHit> engine::AABB::intersects(const Ray &ray, float maxDistance) const {
    // Slab-Methode: Prüft für jede Achse (x, y, z) das Intervall, in dem der Ray
    // innerhalb der beiden "Slabs" (Ebenenpaare) dieser Achse liegt. Der Ray trifft
    // die AABB genau dann, wenn sich alle drei Achsen-Intervalle überschneiden.
    float tMin = 0.0f;
    float tMax = maxDistance;

    for (int axis = 0; axis < 3; ++axis) {
        float origin = ray.origin[axis];
        float dir = ray.direction[axis];
        float minBound = m_min[axis];
        float maxBound = m_max[axis];

        if (std::abs(dir) < 1e-8f) {
            // Ray verläuft parallel zu dieser Achse - kein Treffer, falls Origin außerhalb des Slabs liegt
            if (origin < minBound || origin > maxBound) {
                return std::nullopt;
            }
        } else {
            float invDir = 1.0f / dir;
            float t1 = (minBound - origin) * invDir;
            float t2 = (maxBound - origin) * invDir;
            if (t1 > t2) std::swap(t1, t2);

            tMin = std::max(tMin, t1);
            tMax = std::min(tMax, t2);

            if (tMin > tMax) {
                return std::nullopt; // Intervalle überschneiden sich nicht -> kein Treffer
            }
        }
    }

    RayHit hit;
    hit.distance = tMin;
    hit.intersectionPoint = ray.origin + ray.direction * tMin;
    return hit;
}
