//C++ header - Open Scene Graph - Copyright (C) 1998-2001 Robert Osfield
//Distributed under the terms of the GNU Library General Public License (LGPL)
//as published by the Free Software Foundation.

#ifndef OSG_TRANSFORM
#define OSG_TRANSFORM 1

#include <osg/Group>
#include <osg/Matrix>

namespace osg {

/** Transform - is group which all children are transformed by the the Transform's osg::Matrix.  
  * Typical uses
  * of the Transform is for positioning objects within a scene or 
  * producing trackball functionality or for animation.
  * The Transform node can be customized via the ComputeTransfromCallback which can be
  * attached to the node, this might be used to convert internal representations of the transformation
  * into generic osg::Matrix'c which are used during scene grpah traversal, such as CullTraversal and IntersectionTraversal.
  * Note, if the transformation matrix scales the subgraph then the
  * normals of the underlying geometry will need to be renormalized to
  * be unit vectors once more.  One can done transparently through OpenGL's 
  * use of either GL_NORMALIZE and GL_SCALE_NORMALIZE modes.  Further
  * background reading see the glNormalize documentation in the OpenGL Reference 
  * Guide (the blue book). To enable it in the OSG, you simple need to
  * attach a local osg::StateSet to the osg::Transform, and set the appropriate
  * mode to on via stateset->setMode(GL_NORMALIZE,osg::StateAttribute::ON);.
*/
class SG_EXPORT Transform : public Group
{
    public :


        Transform();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        Transform(const Transform&,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        Transform(const Matrix& matix);

        META_Node(Transform);

        /** Range of types that the Transform can be.*/
        enum Type
        {
            DYNAMIC,
            STATIC
        };
        
        /** Set the Transform Type, which can be DYNAMIC - the Matrix
          * value is updated during the main loop, or STATIC - the Matrix
          * is constant throughout the life of the main loop.  STATIC 
          * Transforms can be optimized away is some instances, which
          * can improve performance so unless you plan to modify the
          * Matrix explicitly set the Matrix to STATIC.  The default
          * value is DYNAMIC.*/
        inline void setType(Type type) { _type = type; }

        /** Get the Transform Type.*/
        inline const Type getType() const { return _type; }
        

        /** Set the matrix mode which tells traversers them how to treat this Transform - as Projection, View or Model transformation.*/
        inline void setMatrixMode(MatrixMode mode) { _mode = mode; }
        
        /** Get the transform mode.*/
        inline const MatrixMode getMatrixMode() const { return _mode; }
        
        /** Does the tranform set up the projection matrix.*/
        inline const bool isProjectionTransform() const { return _mode==PROJECTION; }

        /** Does the tranform set up the modelview matrix.*/
        inline const bool isModelViewTransform() const { return _mode!=PROJECTION; }

        /** Callback attached to an Transform to specifiy how to compute the modelview or projection transformation
          * for the transform below the Transform node.*/
        struct ComputeTransformCallback : public osg::Referenced
        {
            /** Get the transformation matrix which moves from local coords to world coords.*/
            virtual const bool computeLocalToWorldMatrix(Matrix& matrix,const Transform* transform, NodeVisitor* nv) const = 0;

            /** Get the transformation matrix which moves from world coords to local coords.*/
            virtual const bool computeWorldToLocalMatrix(Matrix& matrix,const Transform* transform, NodeVisitor* nv) const = 0;
        };
        
        friend struct osg::Transform::ComputeTransformCallback;

        /** Set the ComputerTransfromCallback which allows users to attach custom computation of the local transformation as 
          * seen by cull traversers and alike.*/
        void setComputeTransformCallback(ComputeTransformCallback* ctc) { _computeTransformCallback=ctc; dirtyBound(); }
        
        /** Get the non const ComputerTransfromCallback.*/
        ComputeTransformCallback* getComputeTransformCallback() { return _computeTransformCallback.get(); }
        
        /** Get the const ComputerTransfromCallback.*/
        const ComputeTransformCallback* getComputeTransformCallback() const { return _computeTransformCallback.get(); }



        /** Get the transformation matrix which moves from local coords to world coords.
          * Return true if Matrix passed in has been modified and */
        inline const bool getLocalToWorldMatrix(Matrix& matrix,NodeVisitor* nv) const
        {
            if (_computeTransformCallback.valid())
                return _computeTransformCallback->computeLocalToWorldMatrix(matrix,this,nv);
            else
                return computeLocalToWorldMatrix(matrix,nv);
        }
        

        /** Get the transformation matrix which moves from world coords to local coords.
          * Return true if Matrix passed in has been modified and */
        inline const bool getWorldToLocalMatrix(Matrix& matrix,NodeVisitor* nv) const
        {
            if (_computeTransformCallback.valid())
                return _computeTransformCallback->computeWorldToLocalMatrix(matrix,this,nv);
            else
                return computeWorldToLocalMatrix(matrix,nv);
        }
        

        /** Set the transform's matrix.*/
        void setMatrix(const Matrix& mat) { (*_matrix) = mat; _inverseDirty=true; dirtyBound(); }
        
        /** Get the transform's matrix. */
        inline const Matrix& getMatrix() const { return *_matrix; }

        /** preMult transform.*/
        void preMult(const Matrix& mat) { _matrix->preMult(mat); _inverseDirty=true; dirtyBound(); }
        
        /** postMult transform.*/
        void postMult(const Matrix& mat)  { _matrix->postMult(mat); _inverseDirty=true; dirtyBound(); }
    

    protected :
    
        virtual ~Transform();
        
        /** Override's Group's computeBound. 
          * There is no need to override in subclasses from osg::Transform since this computeBound() uses 
          * the underlying matrix (calling computeMatrix if required.) */
        virtual const bool computeBound() const;
        
        virtual const bool computeLocalToWorldMatrix(Matrix& matrix,NodeVisitor*) const
        {
            if (_mode==VIEW)
            {
                computeInverse();
                matrix = *_inverse;
                return true;
            }
            else
            {
                matrix = *_matrix;
                return true;
            }
        }

        virtual const bool computeWorldToLocalMatrix(Matrix& matrix,NodeVisitor*) const
        {
            if (_mode==VIEW)
            {
                matrix = *_matrix;
                return true;
            }
            else
            {
                computeInverse();
                matrix = *_inverse;
                return true;
            }
        }
        
        inline void computeInverse() const
        {
            if (_inverseDirty)
            {
                _inverse->invert(*_matrix);
                _inverseDirty = false;
            }
        }


        Type                                _type;
        MatrixMode                          _mode;
        ref_ptr<ComputeTransformCallback>   _computeTransformCallback;

        ref_ptr<Matrix>                     _matrix;
        mutable ref_ptr<Matrix>             _inverse;
        mutable bool                        _inverseDirty;        

};

}

#endif
