#include "precompiled.h"

#include "BoundingObjects.h"
#include "lib/ogl.h"
#include "maths/MathUtil.h"

bool CBoundingObject::Intersects( CBoundingObject* obj )
{
    CVector2D delta = m_pos -obj->m_pos;

    if( !delta.within( m_radius + obj->m_radius ) )
        return( false );

    if( obj->m_type > m_type )  // More complex types get the burden of processing.
    {
        return( obj->LooselyIntersects( this, delta ) );
    }
    else
        return( LooselyIntersects( obj, delta ) );
}

bool CBoundingObject::Contains( const CVector2D& point )
{
    CVector2D delta = m_pos -point;

    if( !delta.within( m_radius ) )
        return( false );

    return( LooselyContains( point, delta ) );
}

void CBoundingObject::SetPosition( float x, float y )
{
    m_pos.x = x; m_pos.y = y;
}

void CBoundingObject::SetHeight( float height )
{
    m_height = height;
}

CBoundingCircle::CBoundingCircle( float x, float y, float radius, float height )
{
    m_type = BOUND_CIRCLE;
    SetPosition( x, y );
    SetRadius( radius );
    SetHeight( height );
}

CBoundingCircle::CBoundingCircle( float x, float y, CBoundingCircle* copy )
{
    m_type = BOUND_CIRCLE;
    SetPosition( x, y );
    SetRadius( copy->m_radius );
    SetHeight( copy->m_height );
}

void CBoundingCircle::SetRadius( float radius )
{
    m_radius = radius;
}

bool CBoundingCircle::LooselyIntersects( CBoundingObject* obj, const CVector2D& UNUSED(delta) )
{
    debug_assert( obj->m_type == BOUND_CIRCLE );
    // Easy enough. The only time this gets called is a circle-circle collision,
    // but we know the circles collide (they passed the trivial rejection step)
    return( true );
}

bool CBoundingCircle::LooselyContains( const CVector2D& UNUSED(point), const CVector2D& UNUSED(delta) )
{
    return( true );
}

void CBoundingCircle::Render( float height )
{
    glBegin( GL_LINE_LOOP );

    for( int i = 0; i < 10; i++ )
    {
        float ang = i * 2 * PI / 10.0f;
        float x = m_pos.x + m_radius * sin( ang );
        float y = m_pos.y + m_radius * cos( ang );
        glVertex3f( x, height, y );
    }

    glEnd();
}

CBoundingBox::CBoundingBox( float x, float y, const CVector2D& u, float width, float depth, float height )
{
    m_type = BOUND_OABB;
    SetPosition( x, y );
    SetDimensions( width, depth );
    SetHeight( height );
    SetOrientation( u );
}

CBoundingBox::CBoundingBox( float x, float y, const CVector2D& u, CBoundingBox* copy )
{
    m_type = BOUND_OABB;
    SetPosition( x, y );
    SetDimensions( copy->GetWidth(), copy->GetDepth() );
    SetHeight( copy->m_height );
    SetOrientation( u ); 
}

CBoundingBox::CBoundingBox( float x, float y, float orientation, float width, float depth, float height )
{
    m_type = BOUND_OABB;
    SetPosition( x, y );
    SetDimensions( width, depth );
    SetHeight( height );
    SetOrientation( orientation );
}

CBoundingBox::CBoundingBox( float x, float y, float orientation, CBoundingBox* copy )
{
    m_type = BOUND_OABB;
    SetPosition( x, y );
    SetDimensions( copy->GetWidth(), copy->GetDepth() );
    SetHeight( copy->m_height );
    SetOrientation( orientation ); 
}

void CBoundingBox::SetDimensions( float width, float depth )
{
    m_w = width / 2.0f;
    m_d = depth / 2.0f;
    m_radius = sqrt( ( m_w * m_w ) + ( m_d * m_d ) );
}

void CBoundingBox::SetOrientation( float orientation )
{
    m_u.x = sin( orientation );
    m_u.y = cos( orientation );
    m_v.x = m_u.y;
    m_v.y = -m_u.x;
}

void CBoundingBox::SetOrientation( const CVector2D& u )
{
    m_u = u;
    m_v.x = m_u.y;
    m_v.y = -m_u.x;
}

bool CBoundingBox::LooselyIntersects( CBoundingObject* obj, const CVector2D& delta )
{
    if( obj->m_type == BOUND_CIRCLE )
    {
        // Imperfect but quick...

        CBoundingCircle* c = (CBoundingCircle*)obj;
        float deltad = fabs( delta.Dot( m_u ) );
        if( deltad > ( m_d + c->m_radius ) ) return( false );
        float deltaw = fabs( delta.Dot( m_v ) );
        if( deltaw > ( m_w + c->m_radius ) ) return( false );
        return( true );
    }
    else
    {
        // Another OABB:
    
        // Seperable axis theorem. (Optimizations: Algorithmic done, low-level stuff not.)
        // Debugging this can often be quite entertaining.
    
        CBoundingBox* b = (CBoundingBox*)obj;

        // SAT in a nutshell: If two boxes are disjoint, there's an axis where their projections don't overlap
        // That axis (in 2D) will run parallel to a side of one of the boxes

        // This code computes projections of boxes onto each axis in turn - hopefully quickly
        // then does simple max/min checks to determine overlap

        // uv, vu, uu, vv are dot-products of our u- and v- axes of each box.
        // prj1, prj2 are two coordinates locating the projections of two corners of a box onto an axis.
        // dm is the distance between the boxes, projected onto the axis.
    
        // Lots of nice symmetries used to help speed things up

        // Note that a trivial-rejection test has already taken place by this point.

        float uv, vu, uu, vv, prj1, prj2, dm;
        uv = m_u.Dot( b->m_v );
        vu = m_v.Dot( b->m_u );
        uu = m_u.Dot( b->m_u );
        vv = m_v.Dot( b->m_v );

        // Project box 2 onto v-axis of box 1

        prj1 = fabs( vu * b->m_d + vv * b->m_w );
        prj2 = fabs( vu * b->m_d -vv * b->m_w );
        dm = delta.Dot( m_v );

        if( prj1 > prj2 )
        {
            if( ( dm -prj1 ) > m_w ) return( false );
        }
        else
            if( ( dm -prj2 ) > m_w ) return( false );
        
        // Project box 2 onto u-axis of box 1

        prj1 = fabs( uu * b->m_d + uv * b->m_w );
        prj2 = fabs( uu * b->m_d -uv * b->m_w );
        dm = delta.Dot( m_u );

        if( prj1 > prj2 ) 
        {
            if( ( dm -prj1 ) > m_d ) return( false );
        }
        else
            if( ( dm -prj2 ) > m_d ) return( false );

        // Project box 1 onto v-axis of box 2

        prj1 = fabs( uv * m_d + vv * m_w );
        prj2 = fabs( uv * m_d -vv * m_w );
        dm = delta.Dot( b->m_v );

        if( prj1 > prj2 )
        {
            if( ( dm -prj1 ) > b->m_w ) return( false );
        }
        else
            if( ( dm -prj2 ) > b->m_w ) return( false );

        // Project box 1 onto u-axis of box 2

        prj1 = fabs( uu * m_d + vu * m_w );
        prj2 = fabs( uu * m_d -vu * m_w );
        dm = delta.Dot( b->m_u );

        if( prj1 > prj2 )
        {
            if( ( dm -prj1 ) > b->m_d ) return( false );
        }
        else
            if( ( dm -prj2 ) > b->m_d ) return( false );

        return( true );

    }
}

bool CBoundingBox::LooselyContains( const CVector2D& UNUSED(point), const CVector2D& delta )
{
    float deltad = fabs( delta.Dot( m_u ) );
    if( deltad > m_d ) return( false );
    float deltaw = fabs( delta.Dot( m_v ) );
    if( deltaw > m_w ) return( false );
    return( true );
}

void CBoundingBox::Render( float height )
{
    glBegin( GL_LINE_LOOP );

    CVector2D p;

    p = m_pos + m_u * m_d + m_v * m_w;
    glVertex3f( p.x, height, p.y );

    p = m_pos + m_u * m_d -m_v * m_w;
    glVertex3f( p.x, height, p.y );

    p = m_pos -m_u * m_d -m_v * m_w;
    glVertex3f( p.x, height, p.y );

    p = m_pos -m_u * m_d + m_v * m_w;
    glVertex3f( p.x, height, p.y );

    glEnd();

}