/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
 * Copyright 2008-2012 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_CULLING_UTILS_H
#define OSGEARTH_CULLING_UTILS_H 1

#include <osgEarth/Common>
#include <osg/NodeCallback>
#include <osg/ClusterCullingCallback>
#include <osg/CoordinateSystemNode>
#include <osg/Vec3d>
#include <osg/Vec3>
#include <osgUtil/CullVisitor>

namespace osgEarth
{    
    /**
     * A customized CCC that works correctly under an RTT camera and also with 
     * an orthographic projection matrix.
     */
    class SuperClusterCullingCallback : public osg::ClusterCullingCallback
    {
    public:
        bool cull(osg::NodeVisitor* nv, osg::Drawable* , osg::State*) const;
    };


    /**
     * Utility functions for creating cluster cullers
     */
    class OSGEARTH_EXPORT ClusterCullingFactory
    {
    public:
        /**
         * Creates a cluster culling callback based on the data in a node graph.
         * NOTE! Never put a CCC somewhere where it will be under a transform. They
         * only work in absolute world space.
         */
        static osg::NodeCallback* create( osg::Node* node, const osg::Vec3d& ecefControlPoint );

        /**
         * Same as above, but uses another method to compute the parameters. There should only
         * be one, but we need to investigate to see which is the better algorithm. Keeping this
         * for now since it works with the feature setup..
         */
        static osg::NodeCallback* create2( osg::Node* node, const osg::Vec3d& ecefControlPoint );

        /**
         * Creates a cluster culling callback and installs it on the node. If the node is
         * a transform, it will create a group above the transform and install the callback
         * on that group instead.
         */
        static osg::Node* createAndInstall( osg::Node* node, const osg::Vec3d& ecefControlPoint );

        /**
         * Creates a cluster culling callback with the standard parameters.
         */
        static osg::NodeCallback* create(const osg::Vec3& controlPoint, const osg::Vec3& normal, float deviation, float radius);
    };

    
    /**
     * A simple culling-plane callback (a simpler version of ClusterCullingCallback)
     */
    struct OSGEARTH_EXPORT CullNodeByNormal : public osg::NodeCallback {
        osg::Vec3d _normal;
        CullNodeByNormal( const osg::Vec3d& normal );
        void operator()(osg::Node* node, osg::NodeVisitor* nv);
    };

    struct CullDrawableByNormal : public osg::Drawable::CullCallback {
        osg::Vec3d _normal;
        CullDrawableByNormal( const osg::Vec3d& normal ) : _normal(normal) { }
        bool cull(osg::NodeVisitor* nv, osg::Drawable* drawable, osg::State* state) const {
            return nv && nv->getEyePoint() * _normal <= 0;
        }
    };

    struct OSGEARTH_EXPORT CullNodeByHorizon : public osg::NodeCallback {
        osg::Vec3d _world;
        double _r, _r2;
        CullNodeByHorizon( const osg::Vec3d& world, const osg::EllipsoidModel* model );
        void operator()(osg::Node* node, osg::NodeVisitor* nv );
    };

    struct OSGEARTH_EXPORT CullNodeByFrameNumber : public osg::NodeCallback {
        unsigned _frame;
        CullNodeByFrameNumber() : _frame(0) { }
        void operator()( osg::Node* node, osg::NodeVisitor* nv ) {
            if ( nv->getFrameStamp()->getFrameNumber() - _frame <= 1 )
                traverse(node, nv);
        }
    };

    struct DisableSubgraphCulling : public osg::NodeCallback {
        void operator()(osg::Node* n, osg::NodeVisitor* v);
    };

    struct StaticBound : public osg::Node::ComputeBoundingSphereCallback {
        osg::BoundingSphere _bs;
        StaticBound(const osg::BoundingSphere& bs) : _bs(bs) { }
        osg::BoundingSphere computeBound(const osg::Node&) const { return _bs; }
    };

    /**
     * Simple occlusion culling callback that does a ray interseciton between the eyepoint
     * and a world point and doesn't draw if there are intersections with the node.
     */
    struct OSGEARTH_EXPORT OcclusionCullingCallback : public osg::NodeCallback {
        OcclusionCullingCallback( const osg::Vec3d& world, osg::Node* node);

        const osg::Vec3d& getWorld() const;
        void setWorld( const osg::Vec3d& world);
        double getMaxRange() const;
        void setMaxRange( double maxRange);
        void operator()(osg::Node* node, osg::NodeVisitor* nv);

        osg::ref_ptr< osg::Node > _node;
        osg::Vec3d _world;
        osg::Vec3d _prevWorld;
        osg::Vec3d _prevEye;
        bool _visible;
        double _maxRange;
        double _maxRange2;
    };


}

#endif // OSGEARTH_CULLING_UTILS_H
