/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * OpenSceneGraph Public License for more details.
*/

#ifndef OSG_ImpostorSprite
#define OSG_ImpostorSprite 1

#include <osg/Vec2>
#include <osg/BoundingSphere>
#include <osg/Drawable>
#include <osg/AlphaFunc>
#include <osg/TexEnv>
#include <osg/Texture2D>
#include <osg/Camera>

#include <osgSim/Export>

namespace osgSim {

class Impostor;
class ImpostorSpriteManager;

/** An ImposterSprite is a textured quad which is rendered in place of
  * 3D geometry. The ImposterSprite is generated by rendering the original
  * 3D geometry to a texture as an image cache. The ImpostorSprite is
  * automatically generated by the osgUtil::CullVisitor so it not
  * necessary to deal with it directly.
*/
class OSGSIM_EXPORT ImpostorSprite : public osg::Drawable
{
    public:

        ImpostorSprite();

        /** Clone an object of the same type as an ImpostorSprite. */
        virtual osg::Object* cloneType() const { return new ImpostorSprite(); }

        /** Clone on ImpostorSprite just returns a clone of type,
          * since it is not appropriate to share data of an ImpostorSprite.
        */
        virtual osg::Object* clone(const osg::CopyOp&) const { return new ImpostorSprite(); }
        virtual bool isSameKindAs(const osg::Object* obj) const { return dynamic_cast<const ImpostorSprite*>(obj)!=NULL; }
        virtual const char* libraryName() const { return "osgSim"; }
        virtual const char* className() const { return "ImpostorSprite"; }

        /** Set the parent, which must be an Impostor.
          * Unlike conventional Drawables, ImpostorSprites can only ever have
          * one parent.
        */
        void setParent(Impostor* parent) { _parent = parent; }

        /** Get the parent, which is an Impostor. */
        Impostor* getParent() { return _parent; }

        /** Get the const parent, which is an Impostor. */
        const Impostor* getParent() const { return _parent; }

        /** Set the eye point for when the ImpostorSprite was snapped. */
        inline void setStoredLocalEyePoint(const osg::Vec3& v) { _storedLocalEyePoint=v; }

        /** Get the eye point for when the ImpostorSprite was snapped. */
        inline const osg::Vec3& getStoredLocalEyePoint() const { return _storedLocalEyePoint; }

        /** Set the frame number for when the ImpostorSprite was last used in rendering. */
        inline void setLastFrameUsed(unsigned int frameNumber) { _lastFrameUsed = frameNumber; }

        /** Get the frame number for when the ImpostorSprite was last used in rendering. */
        inline unsigned int getLastFrameUsed() const { return _lastFrameUsed; }


        /** Get the coordinates of the corners of the quad.
          * Stored in the order, [0] - top_left, [1] - bottom_left, [2] - bottom_right, [3] - top_left.
        */
        inline osg::Vec3* getCoords() { return _coords; }

        /** Get the const coordinates of the corners of the quad. */
        inline const osg::Vec3* getCoords() const { return _coords; }



        /** Get the texture coordinates of the corners of the quad.
          * Stored in the order, [0] - top_left, [1] - bottom_left, [2] - bottom_right, [3] - top_left.
        */
        inline osg::Vec2* getTexCoords() { return _texcoords; }

        /** Get the const texture coordinates of the corners of the quad. */
        inline const osg::Vec2* getTexCoords() const { return _texcoords; }

        /** Get the control coordinates of the corners of the quad.
          * The control coordinates are the corners of the quad projected
          * out onto the front face of bounding box which enclosed the impostor
          * geometry when it was pre-rendered into the impostor sprite's texture.
          * At the point of creation/or update of the impostor sprite the control
          * coords will lie on top of the corners of the quad in screen space - with a pixel error
          * of zero. Once the camera moves relative to the impostor sprite the
          * control coords will no longer lie on top of the corners of the quad in
          * screen space - a pixel error will have accumulated. This pixel error
          * can then be used to determine whether the impostor needs to be updated.
          * Stored in the order, [0] - top_left, [1] - bottom_left, [2] - bottom_right, [3] - top_left.
        */
        inline osg::Vec3* getControlCoords() { return _controlcoords; }

        /** Get the const control coordinates of the corners of the quad. */
        inline const osg::Vec3* getControlCoords() const { return _controlcoords; }


        /** Calculate the pixel error value for passing in the ModelViewProjectionWindow transform,
          * which transform local coords into screen space.
        */
        float calcPixelError(const osg::Matrix& MVPW) const;

        void setTexture(osg::Texture2D* tex,int s,int t);
        osg::Texture2D* getTexture() { return _texture; }
        const osg::Texture2D* getTexture() const { return _texture; }

        int s() const { return _s; }
        int t() const { return _t; }

        /** Draw ImpostorSprite directly. */
        virtual void drawImplementation(osg::RenderInfo& renderInfo) const;

        /** Return true, osg::ImpostorSprite does support accept(Drawable::AttributeFunctor&). */
        virtual bool supports(const Drawable::AttributeFunctor&) const { return true; }

        /** Accept an Drawable::AttributeFunctor and call its methods to tell it about the internal attributes that this Drawable has. */
        virtual void accept(Drawable::AttributeFunctor& af);

        /** Return true, osg::ImpostorSprite does support accept(Drawable::ConstAttributeFunctor&). */
        virtual bool supports(const Drawable::ConstAttributeFunctor&) const { return true; }

        /** Accept a Drawable::ConstAttributeFunctor and call its methods to tell it about the internal attributes that this Drawable has. */
        virtual void accept(Drawable::ConstAttributeFunctor& af) const;

        /** Return true, osg::ImpostorSprite does support accept(PrimitiveFunctor&). */
        virtual bool supports(const osg::PrimitiveFunctor&) const { return true; }

        /** Accept a PrimtiveFunctor and call its methods to tell it about the internal primitives that this Drawable has. */
        virtual void accept(osg::PrimitiveFunctor& pf) const;

        // for debugging purposes.
        osg::Vec4 _color;

        virtual osg::BoundingBox computeBoundingBox() const;

        /** Set the camera node to use for pre rendering the impostor sprite's texture.*/
        void setCamera(osg::Camera* camera) { _camera = camera; }

        /** Get the camera node to use for pre rendering the impostor sprite's texture.*/
        osg::Camera* getCamera() { return _camera.get(); }

        /** Get the const camera node to use for pre rendering the impostor sprite's texture.*/
        const osg::Camera* getCamera() const { return _camera.get(); }

    protected:

        ImpostorSprite(const ImpostorSprite&);
        ImpostorSprite& operator = (const ImpostorSprite&) { return *this;}

        virtual ~ImpostorSprite();

        Impostor* _parent;

        friend class osgSim::ImpostorSpriteManager;

        // camera node for doing the pre rendering.
        osg::ref_ptr<osg::Camera> _camera;

        // support for a double linked list managed by the
        // ImposotorSpriteManager.
        ImpostorSpriteManager*  _ism;
        ImpostorSprite*         _previous;
        ImpostorSprite*         _next;

        unsigned int _lastFrameUsed;

        osg::Vec3 _storedLocalEyePoint;

        osg::Vec3 _coords[4];
        osg::Vec2 _texcoords[4];
        osg::Vec3 _controlcoords[4];

        osg::Texture2D* _texture;
        int _s;
        int _t;


};

/** Helper class for managing the reuse of ImpostorSprite resources. */
class OSGSIM_EXPORT ImpostorSpriteManager : public osg::Referenced
{
    public:

        ImpostorSpriteManager();

        bool empty() const { return _first==0; }

        ImpostorSprite* first() { return _first; }

        ImpostorSprite* last() { return _last; }

        void push_back(ImpostorSprite* is);

        void remove(ImpostorSprite* is);

        ImpostorSprite* createOrReuseImpostorSprite(int s,int t,unsigned int frameNumber);

        osg::StateSet* createOrReuseStateSet();

        void reset();

    protected:


        ~ImpostorSpriteManager();

        osg::ref_ptr<osg::TexEnv>       _texenv;
        osg::ref_ptr<osg::AlphaFunc>    _alphafunc;

        ImpostorSprite*                 _first;
        ImpostorSprite*                 _last;

        typedef std::vector< osg::ref_ptr<osg::StateSet> > StateSetList;
        StateSetList                    _stateSetList;
        unsigned int                    _reuseStateSetIndex;


};

}

#endif
