/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
 * Copyright 2020 Pelican Mapping
 * http://osgearth.org
 *
 * osgEarth is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 */

#ifndef OSGEARTH_DRAW_INSTANCED_H
#define OSGEARTH_DRAW_INSTANCED_H 1

#include <osgEarth/Common>
#include <osgEarth/Containers>
#include <osgEarth/VirtualProgram>
#include <osg/NodeVisitor>
#include <osg/Drawable>
#include <osg/Geometry>
#include <osg/TextureBuffer>

/**
 * Some utilities to support *DrawInstanced rendering.
 */
namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    namespace DrawInstanced
    {
        class OSGEARTH_EXPORT InstanceGeometry : public osg::Geometry
        {
        public:
            InstanceGeometry();
            InstanceGeometry(const osg::Geometry& geometry);
            InstanceGeometry(const InstanceGeometry&, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY);
            META_Node(osgEarth::Util::DrawInstanced, InstanceGeometry);

            const std::vector< osg::Matrixf >& getMatrices() const;
            void setMatrices(const std::vector< osg::Matrixf >& matrices);

            virtual bool supports(const osg::PrimitiveFunctor& f) const { return true; }
            virtual void accept(osg::PrimitiveFunctor& f) const;

            virtual osg::BoundingBox computeBoundingBox() const;

        private:
            std::vector<osg::Matrixf> _matrices;
            std::vector<osg::Vec3> _mesh;
            osg::ref_ptr< osg::DrawElementsUInt > _meshDrawElements;
        };

        /**
            * Visitor that converts all the primitive sets in a graph to use
            * instanced draw calls.
            * Called by convertGraphToUseDrawInstanced().
            */
        class OSGEARTH_EXPORT ConvertToDrawInstanced : public osg::NodeVisitor
        {
        public:
            /**
                * Create the visitor that will convert primitive sets to draw
                * <num> instances.
                */
            ConvertToDrawInstanced(
                unsigned                numInstances,
                bool                    optimize,
                osg::TextureBuffer*     tbo,
                int                     firstTboUnit);

            void apply(osg::Drawable&);
            void apply(osg::LOD&);
            void apply(osg::Node&);

        public:
            int getTextureImageUnit() const { return _tboUnit; }

        protected:
            unsigned _numInstances;
            bool _optimize;
            std::list<osg::PrimitiveSet*> _primitiveSets;
            osg::TextureBuffer* _tbo;
            int _tboUnit;
            osg::ref_ptr<osg::Drawable::ComputeBoundingBoxCallback> _bboxComputer;
        };


        /**
            * Creates a virtual shader program that implements DrawInstanced rendering.
            * You should prepare the scene graph first by calling
            * convertGraphToUseDrawInstanced().
            * @return false If instancing is not available
            */
        extern OSGEARTH_EXPORT bool install(osg::StateSet* stateset);
        extern OSGEARTH_EXPORT void remove (osg::StateSet* stateset);


        /**
            * Processes a scene graph and converts all the top-level MatrixTransform
            * nodes into shader uniforms that can be used with the VirtualProgram
            * created by createDrawInstacedShaders.
            * NOTE: You must also call install(StateSet) to activate instancing.
            * @return false If instancing is not available
            */
        extern OSGEARTH_EXPORT bool convertGraphToUseDrawInstanced(
            osg::Group* graph );
    }
} }

#endif // OSGEARTH_DRAW_INSTANCED_H
