#include "object.hpp"


#ifndef STATIC_LIGHT_RES
#define STATIC_LIGHT_RES 1.0
#endif


Object::Object(Texture* t, bool s, const vertex_t bmin, const vertex_t bmax):
    lightmap(NULL),
    tex(t),
    sided(s),
    bound_min(bmin),
    bound_max(bmax),
    bound_center(bmin.x+((bmax.x-bmin.x)/2.0),bmin.y+((bmax.y-bmin.y)/2.0),bmin.z+((bmax.z-bmin.z)/2.0)),
    bound_cdiag(bound_max - bound_center) {
}


Object::~Object() {
    if (tex) {
        delete tex;
    }
    if (lightmap) {
        delete lightmap;
    }
}


void Object::set_lightmap(LightMap* l) const {
    assert(!lightmap); // "const"
    lightmap = l;
}


bool Object::quadfunc(const coord_t& a, const coord_t& b, const coord_t& c, coord_t& t) { // returns smallest result, if any
    if (unlikely(a == 0.0)) { // not actual quadratic: b*t + c == 0
        if (unlikely(b == 0.0)) {
            return false;
        } else {
            t = -c / b;
            return true;
        }
    }
    coord_t discr = pow2(b) - (4.0 * a * c);
    if (discr < 0.0) {
        return false;
    } else if (discr == 0.0) {
        t = -0.5 * b / a;
    } else {
        const coord_t q = (b > 0)? (-0.5 * (b + sqrt(discr))): (-0.5 * (b - sqrt(discr)));
        const coord_t t1 = q / a;
        const coord_t t2 = c / q;
        t = MIN(t1, t2);
    }
    return true;
}


void Object::lincomb(const vertex_t& P, coord_t& a, const vertex_t& A, coord_t& b, const vertex_t& B) {
    /*
     * P.x = a * A.x + b * B.x
     * P.y = a * A.y + b * B.y
     * P.z = a * A.z + b * B.z
     */
    /*
     * a = (Px - (b * Bx)) / Ax
     * b = (Py - (a * Ay)) / By
     * a = (Px - (((Py - (a * Ay)) / By) * Bx)) / Ax
     * a = (Px/Ax - Py*Bx/By*Ax) / (1 - (Ay*Bx/By*Ax))
    */
    coord_t ac, ab, ap;
    coord_t bc, ba, bp;
    if (A.x != 0.0 && B.y != 0.0) { // We must not choose the same row for A and B (e.g. for 0,1,1 and 0,1,0): TODO: programmatic approach?
        ac = A.x; ab = B.x; ap = P.x;
        bc = B.y; ba = A.y; bp = P.y;
    } else if (A.x != 0.0 && B.z != 0.0) {
        ac = A.x; ab = B.x; ap = P.x;
        bc = B.z; ba = A.z; bp = P.z;
    } else if (A.y != 0.0 && B.x != 0.0) {
        ac = A.y; ab = B.y; ap = P.y;
        bc = B.x; ba = A.x; bp = P.x;
    } else if (A.y != 0.0 && B.z != 0.0) {
        ac = A.y; ab = B.y; ap = P.y;
        bc = B.z; ba = A.z; bp = P.z;
    } else if (A.z != 0.0 && B.x != 0.0) {
        ac = A.z; ab = B.z; ap = P.z;
        bc = B.x; ba = A.x; bp = P.x;
    } else { //if (A.z != 0.0 && B.y != 0.0) {
        ac = A.z; ab = B.z; ap = P.z;
        bc = B.y; ba = A.y; bp = P.y;
    }
    assert(ac != 0.0 && bc != 0.0); // vectors are independent (and thus != 0^3)

    const coord_t acbc = ac*bc;
    const coord_t F = (ap/ac) - ((ab*bp) / acbc);
    const coord_t G = 1.0 - ((ab*ba) / acbc);
    a = F / G;
    b = (bp - (a*ba)) / bc;
}


bool Object::intersect_box(const vertex_t& amin, const vertex_t& amax, const vertex_t& bmin, const vertex_t& bmax) {
    // el cheapo: intersetcs our box with the full ray's box? TODO: some better solution?
    return amin.x <= bmax.x &&
           amax.x >= bmin.x &&
           amin.y <= bmax.y &&
           amax.y >= bmin.y &&
           amin.z <= bmax.z &&
           amax.z >= bmin.z;
}


bool Object::intersect_box(const vertex_t& rmin, const vertex_t& rmax) const {
    return intersect_box(rmin, rmax, bound_min, bound_max);
}


bool Object::intersect_box(const vertex_t& rmin, const vertex_t& rmax, coord_t margin) const {
    return intersect_box(
        rmin, rmax,
        vertex_t(bound_min.x - margin, bound_min.y - margin, bound_min.z - margin),
        vertex_t(bound_max.x + margin, bound_max.y + margin, bound_max.z + margin)
    );
}


vertex_t Object::from_uv(coord_t u, coord_t v) const {
    vertex_t rv, nrm;
    from_uv(u, v, rv, nrm);
    return rv;
}


void Object::max_dim(unsigned& w, unsigned& h) const {
    if (!tex->max_dim(w, h)) {
        coord_t max_u, max_v;
        max_uv(max_u, max_v);
        if (max_u < STATIC_LIGHT_RES || max_v < STATIC_LIGHT_RES) {
            w = 0;
            h = 0;
            return;
        }
        w = floor(max_u / STATIC_LIGHT_RES);
        h = floor(max_v / STATIC_LIGHT_RES);
        assert(max_u >= 1 && max_v >= 1);
    }
}