/**
 * =========================================================================
 * File        : Bound.cpp
 * Project     : 0 A.D.
 * Description : Axis-aligned bounding box
 * =========================================================================
 */

#include "precompiled.h"

// necessary includes

#include "lib/ogl.h"

#include <float.h>
#include "Bound.h"
#include "graphics/Frustum.h"
#include "Brush.h"


///////////////////////////////////////////////////////////////////////////////
// operator+=: extend this bound to include given bound
CBound& CBound::operator+=(const CBound& b)
{
#define CMPT(c) \
    if (b[0].c < m_Data[0].c) m_Data[0].c = b[0].c; \
    if (b[1].c > m_Data[1].c) m_Data[1].c = b[1].c
    CMPT(X);
    CMPT(Y);
    CMPT(Z);
#undef CMPT

    return *this;
}

///////////////////////////////////////////////////////////////////////////////
// operator+=: extend this bound to include given point
CBound& CBound::operator+=(const CVector3D& pt)
{
#define CMPT(c) \
    if (pt.c < m_Data[0].c) m_Data[0].c = pt.c; \
    if (pt.c > m_Data[1].c) m_Data[1].c = pt.c
    CMPT(X);
    CMPT(Y);
    CMPT(Z);
#undef CMPT

    return *this;
}

///////////////////////////////////////////////////////////////////////////////
// RayIntersect: intersect ray with this bound; return true
// if ray hits (and store entry and exit times), or false
// otherwise
// note: incoming ray direction must be normalised
bool CBound::RayIntersect(const CVector3D& origin,const CVector3D& dir,
            float& tmin,float& tmax) const
{
    float t1,t2;
    float tnear,tfar;

    if (dir[0]==0) {
        if (origin[0]<m_Data[0][0] || origin[0]>m_Data[1][0])
            return false;
        else {
            tnear=(float) -FLT_MAX;
            tfar=(float) FLT_MAX;
        }
    } else {
        t1=(m_Data[0][0]-origin[0])/dir[0];
        t2=(m_Data[1][0]-origin[0])/dir[0];

        if (dir[0]<0) {
            tnear = t2;
            tfar = t1;
        } else {
            tnear = t1;
            tfar = t2;
        }

        if (tfar<0)
            return false;
    }

    if (dir[1]==0 && (origin[1]<m_Data[0][1] || origin[1]>m_Data[1][1]))
        return false;
    else {
        t1=(m_Data[0][1]-origin[1])/dir[1];
        t2=(m_Data[1][1]-origin[1])/dir[1];

        if (dir[1]<0) {
            if (t2>tnear)
                tnear = t2;
            if (t1<tfar)
                tfar = t1;
        } else {
            if (t1>tnear)
                tnear = t1;
            if (t2<tfar)
                tfar = t2;
        }

        if (tnear>tfar || tfar<0)
            return false;
    }

    if (dir[2]==0 && (origin[2]<m_Data[0][2] || origin[2]>m_Data[1][2]))
        return false;
    else {
        t1=(m_Data[0][2]-origin[2])/dir[2];
        t2=(m_Data[1][2]-origin[2])/dir[2];

        if (dir[2]<0) {
            if (t2>tnear)
                tnear = t2;
            if (t1<tfar)
                tfar = t1;
        } else {
            if (t1>tnear)
                tnear = t1;
            if (t2<tfar)
                tfar = t2;
        }

        if (tnear>tfar || tfar<0)
        return false;
    }

    tmin=tnear;
    tmax=tfar;

    return true;
}

///////////////////////////////////////////////////////////////////////////////
// SetEmpty: initialise this bound as empty
void CBound::SetEmpty()
{
    m_Data[0]=CVector3D( FLT_MAX, FLT_MAX, FLT_MAX);
    m_Data[1]=CVector3D(-FLT_MAX,-FLT_MAX,-FLT_MAX);
}

///////////////////////////////////////////////////////////////////////////////
// IsEmpty: tests whether this bound is empty
bool CBound::IsEmpty()
{
    return (m_Data[0].X ==  FLT_MAX && m_Data[0].Y ==  FLT_MAX && m_Data[0].Z ==  FLT_MAX
         && m_Data[1].X == -FLT_MAX && m_Data[1].Y == -FLT_MAX && m_Data[1].Z == -FLT_MAX);
}

///////////////////////////////////////////////////////////////////////////////
// Transform: transform this bound by given matrix; return transformed bound
// in 'result' parameter - slightly modified version of code in Graphic Gems
// (can't remember which one it was, though)
void CBound::Transform(const CMatrix3D& m,CBound& result) const
{
    debug_assert(this!=&result);

    for (int i=0;i<3;++i) {
        // handle translation
        result[0][i]=result[1][i]=m(i,3);

        // Now find the extreme points by considering the product of the
        // min and max with each component of matrix
        for(int j=0;j<3;j++) {
            float a=m(i,j)*m_Data[0][j];
            float b=m(i,j)*m_Data[1][j];

            if (a<b) {
                result[0][i]+=a;
                result[1][i]+=b;
            } else {
                result[0][i]+=b;
                result[1][i]+=a;
            }
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
// Intersect with the given frustum in a conservative manner
void CBound::IntersectFrustumConservative(const CFrustum& frustum)
{
    CBrush brush(*this);
    CBrush buf;

    brush.Intersect(frustum, buf);

    buf.Bounds(*this);
}


///////////////////////////////////////////////////////////////////////////////
// Render the bounding box
void CBound::Render()
{
    glBegin(GL_QUADS);
        glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[0].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[1].Y, m_Data[0].Z);
        glVertex3f(m_Data[0].X, m_Data[1].Y, m_Data[0].Z);

        glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[0].Z);
        glVertex3f(m_Data[0].X, m_Data[1].Y, m_Data[0].Z);
        glVertex3f(m_Data[0].X, m_Data[1].Y, m_Data[1].Z);
        glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[1].Z);

        glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[0].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[0].Y, m_Data[1].Z);
        glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[1].Z);

        glVertex3f(m_Data[0].X, m_Data[0].Y, m_Data[1].Z);
        glVertex3f(m_Data[1].X, m_Data[0].Y, m_Data[1].Z);
        glVertex3f(m_Data[1].X, m_Data[1].Y, m_Data[1].Z);
        glVertex3f(m_Data[0].X, m_Data[1].Y, m_Data[1].Z);

        glVertex3f(m_Data[1].X, m_Data[0].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[1].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[1].Y, m_Data[1].Z);
        glVertex3f(m_Data[1].X, m_Data[0].Y, m_Data[1].Z);

        glVertex3f(m_Data[0].X, m_Data[1].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[1].Y, m_Data[0].Z);
        glVertex3f(m_Data[1].X, m_Data[1].Y, m_Data[1].Z);
        glVertex3f(m_Data[0].X, m_Data[1].Y, m_Data[1].Z);
    glEnd();
}