//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_BILLBOARD
#define OSG_BILLBOARD 1

#include <osg/Matrix>
#include <osg/Geode>

namespace osg {

/** Billboard - a Geode which orientates its child osg::Drawable's to face
    the eye point.  Typical uses are for trees, or particle explosions.
*/
class SG_EXPORT Billboard : public Geode
{
    public:

        enum Mode {
            POINT_ROT_EYE,
            POINT_ROT_WORLD,
            AXIAL_ROT
        };

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

        META_Node(Billboard);

        /** Set the billboard rotation mode. */
        void setMode(const Mode mode);
        /** Get the billboard rotation mode. */
        inline const Mode getMode() const { return _mode; }

        /** Set the axis about which all the billboard's drawable rotate. */
        void setAxis(const Vec3& axis);
        /** Get the axis about which all the billboard's drawable rotate. */
        inline const Vec3& getAxis() const { return _axis; }


        /** Set the position of specified drawable. */
        inline void setPos(int i,const Vec3& pos)      { _positionList[i] = pos; }
        /** Get the position of specified drawable. */
        inline const Vec3& getPos(int i) const         { return _positionList[i]; }
        
        /** PositionList represents a list of pivot points for each drawable.*/
        typedef std::vector<Vec3> PositionList;
        
        /** Get the PositionList from the billboard.*/
        inline PositionList& getPositionList()             { return _positionList; }
        
        /** Get a const PositionList from the billboard.*/
        inline const PositionList& getPositionList() const { return _positionList; }

        /** Add Drawable to Billboard with default position(0,0,0);
         *  If gset not NULL and is not contained in Billboard then increment its 
         *  reference count, and dirty the bounding box to cause it to recompute on 
         *  next getBound() and return true for success.  Otherwise return false.
         */
        virtual const bool addDrawable( Drawable *gset );

        /** Add Drawable to Geode at position pos.
         *  If gset not NULL and is not contained in Billboard then increment its 
         *  reference count, and dirty the bounding box to cause it to recompute on 
         *  next getBound() and return true for success.  Otherwise return false.
         */
        virtual const bool addDrawable(Drawable *gset,const Vec3& pos);

        /** Remove Drawable and associated position from Billboard.
         *  If gset is contained in Billboard then remove it from the geoset
         *  list and decrement its reference count, and dirty the 
         *  bounding box to cause it to recompute on next getBound() and
         *  return true for success.  If gset is not found then return false
         *  and do not the reference count of gset is left unchanged.
         */
        virtual const bool removeDrawable( Drawable *gset );
                

        /** Callback attached to an Billboard which allows the users to customize the billboard orientation calculation during cull traversal.*/
        struct ComputeBillboardCallback : public osg::Referenced
        {
            /** Get the transformation matrix which moves from local coords to world coords.*/
            virtual const bool computeMatrix(const Matrix& matrix, const Billboard* billboard, const Vec3& eye_local, const Vec3& up_local, const Vec3& pos_local) const;
        };
        
        friend struct osg::Billboard::ComputeBillboardCallback;

        /** Set the ComputeBillboardCallback which allows users to attach custom computation of the local transformation as 
          * seen by cull traversers and alike.*/
        void setComputeBillboardCallback(ComputeBillboardCallback* ctc) { _computeBillboardCallback=ctc; }
        
        /** Get the non const ComputeBillboardCallback.*/
        ComputeBillboardCallback* getComputeBillboardCallback() { return _computeBillboardCallback.get(); }
        
        /** Get the const ComputeBillboardCallback.*/
        const ComputeBillboardCallback* getComputeBillboardCallback() const { return _computeBillboardCallback.get(); }        

        
        inline const bool getMatrix(Matrix& matrix, const Vec3& eye_local, const Vec3& up_local, const Vec3& pos_local) const
        {
            if (_computeBillboardCallback.valid())
                return _computeBillboardCallback->computeMatrix(matrix,this,eye_local,up_local,pos_local);
            else
                return computeMatrix(matrix,eye_local,up_local,pos_local);
        }

    protected:

        virtual ~Billboard();

        virtual const bool computeBound() const;

        virtual const bool computeMatrix(Matrix& matrix, const Vec3& eye_local, const Vec3& up_local, const Vec3& pos_local) const;

        enum AxisAligned
        {
            AXIAL_ROT_X_AXIS=AXIAL_ROT+1,
            AXIAL_ROT_Y_AXIS,
            AXIAL_ROT_Z_AXIS
        };


        Mode                                _mode;
        Vec3                                _axis;
        PositionList                        _positionList;
        ref_ptr<ComputeBillboardCallback>   _computeBillboardCallback;
        
        // used internally as cache of which what _axis is aligned to help
        // deicde which method of rotation to use.
        int              _cachedMode;
        
        void setCachedMode();

};

}

#endif
