/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Defines a sub-graph (partition/subtree).
 *//*
 * 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 GRAPH_SUBGRAPH_CC 1
#include "base/module.hh"
#include "base/stream.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
#include "math/matrix.hh"
#include "math/matrix_funcs.hh"
using namespace math;
#include "object/module.hh"
using namespace object;
#include "graph/module.hh"
#include "graph/osg.hh"
#include "graph/graph_visitors.hh"
#include "graph/subgraph.hh"

namespace graph {

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  Subgraph  //////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * The subgraph is found by a Visitor that searches for a Group node's name.
 * Then a MatrixTransform is spliced over the found node.
 *****************************************************************************/
Subgraph::Subgraph( shptr<Graph> graph, const string& name )
:   mSwitchNode(new osg::Switch),
    mTransformNode(new osg::MatrixTransform),
    mLimitedRotation()
{
CHECK_TYPESIG(graph,TYPESIG_GRAPH);

    SET_TYPESIG(this,TYPESIG_SUBGRAPH);

    mLimitedRotation.mEnabled = false;

    // Find the subgraph.

    // Create a Visitor to find the node (it is a Group).
    graph::VisitorFindNamedNode visitor( name );

    // Traverse the visitor thru the model's nodes.
    RefPtr<Node> root = graph->GetRootNode();
    root->accept( visitor );

    // Splice a Switch and a MatrixTransform node:
    //
    // Parent --> Switch node --> MatrixTransform node --> Model's Group node

    RefPtr0<Node> node = visitor.GetNode();
    if ( EX( node != NULL ) )
    {
        // Try to cast it as a Group.
        if ( node->asGroup() != NULL )
        {
            RefPtr<osg::Group> group = node->asGroup();
            RefPtr<osg::Group> parent = group->getParent( 0 );
            parent->removeChild( group.get() );
            parent->addChild( mSwitchNode.get() );
            mSwitchNode->addChild( mTransformNode.get() );
            mTransformNode->addChild( group.get() );
        }
    }
    else
    {
        CERROR << "WARNING: cannot find subgraph " << name << std::endl;
    }
}

Subgraph::~Subgraph()
{
    INVALIDATE_TYPESIG(this,TYPESIG_SUBGRAPH);
}

/*****************************************************************************
 * Enable/disable rendering a Subgraph.
 *****************************************************************************/
void
Subgraph::Enable( const bool enable )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    GraphEnablement::Enable( *mSwitchNode, enable );  // method of private base class
}

/*****************************************************************************
 * @return True if Subgraph is enabled/drawable.
 *****************************************************************************/
bool
Subgraph::IfEnabled( void )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    return GraphEnablement::IfEnabled( *mSwitchNode );  // method of private base class
}

/*****************************************************************************
 * Reset matrix of subgraph (undo rotation and translation).
 *****************************************************************************/
void
Subgraph::Reset( void )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    PERSISTENT const Matrix m;
    SetMatrix( m );
}

/*****************************************************************************
 * Rotate the subgraph.
 * Rotation will be limited if SetLimitedRotation() was called.
 *****************************************************************************/
void
Subgraph::Rotate( uint axis, Radian radian )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);
ASSERT_AXIS3( axis );

    // Rotation may be limited (landing gear).
    if ( IfAllowRotation( axis, radian )  // might update mLimitedRotation
     and ABS(radian) > 0.0001f )  // zero angle is passed sometimes
    {
        MatrixRotateLocal( *mTransformNode, axis, radian );  // non-template func
    }
}

/*****************************************************************************
 * Move subgraph along one of its local axis.
 *****************************************************************************/
void
Subgraph::Translate( uint axis, fp inc )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);
ASSERT_AXIS3( axis );

    Matrix m = GetMatrix();
    MatrixTranslateLocal<>( m, axis, inc );
    SetMatrix( m );
}

void
Subgraph::Translate( const Vector3& v )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    Matrix m = GetMatrix();
    MatrixTranslateLocal<>( m, v );
    SetMatrix( m );
}

/*****************************************************************************
 * Set transform node of subgraph.
 *****************************************************************************/
void
Subgraph::SetMatrix( const Matrix& m )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    mTransformNode->setMatrix( m );
}

Matrix
Subgraph::GetMatrix( void )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    return mTransformNode->getMatrix();
}

/*****************************************************************************
 * Set the range of limited rotation.
 *****************************************************************************/
void
Subgraph::SetLimitedRotation( uint axis, const Degree degreeMin, const Degree degreeMax )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    mLimitedRotation.mEnabled   = true;
    mLimitedRotation.mAxis      = axis;
    mLimitedRotation.mDegree    = 0.0f;
    mLimitedRotation.mDegreeMin = degreeMin;
    mLimitedRotation.mDegreeMax = degreeMax;
}

/*****************************************************************************
 * Test whether to allow rotation.
 *****************************************************************************/
bool
Subgraph::IfAllowRotation( uint axis, Radian radian )
{
CHECK_TYPESIG(this,TYPESIG_SUBGRAPH);

    if ( not mLimitedRotation.mEnabled )
    {
        return true;
    }
    else
    {
        // LimitedRotation applies to one axis.
        ASSERT( mLimitedRotation.mAxis == axis );

        // Would this rotation be outside the range?
        Degree degree = Rad2Deg( radian );
        Degree degreeNext = mLimitedRotation.mDegree + degree;
        if ( (degreeNext < mLimitedRotation.mDegreeMin)
          or (degreeNext > mLimitedRotation.mDegreeMax) )
        {
            return false;  // don't rotate
        }
        else
        {
            // Commit rotation.
            mLimitedRotation.mDegree = degreeNext;
            return true;
        }
    }
}

} // namespace graph
