/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Dyna class.
 * @verbatim
 *
 * Translate() vs. SetMatrix():
 * ----------------------------
 * Calling Translate() or SetMatrix(m,TRANSLATION) is considered movement.
 * Calling SetMatrix(void) is considered placement.
 * Collision-detection is tested while moving only.
 *
 * Collision-detection:
 * --------------------
 * Dyna is the class of Objects that are dynamically animated.
 * Therefore, Dyna (not Object) tests if it has collided into another Object.
 * Object has the collision flag (along with other flags in Object::mBits).
 *
 * @endverbatim
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define OBJECT_DYNA_CC 1
#include <utility>
#include "base/module.hh"
using namespace base;
#include "math/module.hh"
#include "math/matrix.hh"
using namespace math;
#include "world/module.hh"
using namespace world;
#include "collision/module.hh"
using namespace collision;
#include "object/module.hh"
#include "object/defs.hh"
#include "object/dyna.hh"

namespace object {

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////  Dyna  //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
Dyna::Dyna( shptr<Graph> graph, const WorldVertex& pos, const bool collidable )
:   Parent(graph,pos),
    mTickFunctor(new Dyna::TickFunctor(*this)),
    mTranslationBegin(0,0,0),
    mTranslationEnd(0,0,0)
{
    SET_TYPESIG(this,TYPESIG_DYNA);

    // Register a timer-tick handler to pulse animation.
    GET_TIMER().Register( mTickFunctor, defs::DYNA_TICK_FREQ );

    // Most Dynas are collidable.
    if ( collidable )
        SetCollidable( true );  // SUBTLE: indirectly passes this as shptr<Dyna>
}

Dyna::~Dyna()
{
    // Tell Timer to stop calling this Dyna which will be destroyed.
    GET_TIMER().Unregister( mTickFunctor );

    INVALIDATE_TYPESIG(this,TYPESIG_DYNA);
}

/*****************************************************************************
 * Reset.
 *****************************************************************************/
void
Dyna::Reset( void )
{
CHECK_TYPESIG(this,TYPESIG_DYNA);

    Parent::Reset();

    // Forget previous translation.
    mTranslationBegin = WorldVertex(0,0,0);
    mTranslationEnd   = WorldVertex(0,0,0);

    // Leave mCollision unchanged (let derivative control it).
  //SetCollision( Object::COLLISION_NONE );
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  Dyna : transformation  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * If SetMatrix() is called for movement (not placement, not rotation),
 * then this is a translation action, so mTranslationBegin/End must be updated
 * for collision-detection.
 *****************************************************************************/
void
Dyna::SetMatrix( const Matrix& matrix, Dyna::eMatrixChange change )
{
CHECK_TYPESIG(this,TYPESIG_DYNA);

    if ( change == Dyna::TRANSLATION )
    {
        mTranslationBegin = GetPosition();
        Parent::SetMatrix( matrix );  // changes position
        mTranslationEnd = GetPosition();
    }
    else
    {
        Parent::SetMatrix( matrix );
    }
}

/*****************************************************************************
 * @return The first and last points of the current movement (step) of this Dyna.
 *****************************************************************************/
const std::pair<WorldVertex,WorldVertex>
Dyna::GetTranslationStep( void )
{
CHECK_TYPESIG(this,TYPESIG_DYNA);

    return std::make_pair<WorldVertex,WorldVertex>( mTranslationBegin, mTranslationEnd );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  Dyna : animation  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Animation.
 * @param   millisecElapsed
 *          Total milliseconds between now and when program was started.
 *****************************************************************************/
void
Dyna::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(this,TYPESIG_DYNA);

    DetectCollision();  // virtual
}

void
Dyna::TickFunctor::Tick( const Milliseconds millisecElapsed )
{
CHECK_TYPESIG(&mDyna,TYPESIG_DYNA);

    // Reflect to Dyna::Tick().
    mDyna.Tick( millisecElapsed );
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////  Dyna : collision-detection  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Detect collision.
 *
 * This is a Template Method.
 * Delegates to derivative's HandleCollision() for determining whether
 * the collision was safe or fatal.
 *****************************************************************************/
void
Dyna::DetectCollision( void )
{
CHECK_TYPESIG(this,TYPESIG_DYNA);

    // Bypass if this was set non-collidable or collision already occurred.
    if ( EX( (not IfCollision()) and IfCollidable() ) )
    {
        // Do collision-detection test.
        // This Dyna could collide into zero or more Objects.
        const Collision::Colliders colliders = GET_COLLISION().GetCollisions( this );
        if ( colliders.empty() )
        {
            // No collision.

            // Clear the Dyna's collision flag.
            // A reason is if Dyna was on contact with runway (safe collision).
            SetCollision( Object::COLLISION_NONE );
        }
        else
        {
            // One or more collisions occurred.

            // Set the Dyna's collision flag.
          //SetCollision( Object::COLLISION );  // let HandleCollisions() decide

            // Template Method.
            HandleCollisions( colliders );
        }
    }
}

} // namespace object
