/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield 
 *
 * This application is open source and may be redistributed and/or modified   
 * freely and without restriction, both in commericial and non commericial applications,
 * as long as this copyright notice is maintained.
 * 
 * This application 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.
*/

#include <osg/Node>
#include <osg/Geometry>
#include <osg/Geode>
#include <osg/Notify>
#include <osg/MatrixTransform>
#include <osg/Texture2D>
#include <osg/BlendFunc>
#include <osg/Stencil>
#include <osg/ColorMask>
#include <osg/Depth>
#include <osg/ClipNode>
#include <osg/AnimationPath>

#include <osgDB/ReadFile>
#include <osgUtil/Optimizer>
#include <osgProducer/Viewer>

//
// A simple demo demonstrating planar reflections using multiple renderings 
// of a subgraph, overriding of state attribures and use of the stencil buffer.
//
// The multipass system implemented here is a variation if Mark Kilgard's
// paper "Improving Shadows and Reflections via the Stencil Buffer" which
// can be found on the developer parts of the NVidia web site.
//
// The variations comes from the fact that the mirrors stencil values
// are done on the first pass, rather than the second as in Mark's paper.
// The second pass is now Mark's first pass - drawing the unreflected scene,
// but also unsets the stencil buffer.  This variation stops the unreflected
// world poking through the mirror to be seen in the final rendering and
// also obscures the world correctly when on the reverse side of the mirror.
// Although there is still some unresolved issue with the clip plane needing
// to be flipped when looking at the reverse side of the mirror.  Niether
// of these issues are mentioned in the Mark's paper, but trip us up when
// we apply them. 


osg::StateSet* createMirrorTexturedState(const std::string& filename)
{
    osg::StateSet* dstate = new osg::StateSet;
    dstate->setMode(GL_CULL_FACE,osg::StateAttribute::OFF|osg::StateAttribute::PROTECTED);
    
    // set up the texture.
    osg::Image* image = osgDB::readImageFile(filename.c_str());
    if (image)
    {
        osg::Texture2D* texture = new osg::Texture2D;
        texture->setImage(image);
        dstate->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON|osg::StateAttribute::PROTECTED);
    }
    
    return dstate;
}


osg::Drawable* createMirrorSurface(float xMin,float xMax,float yMin,float yMax,float z)
{
    
    // set up the drawstate.

    // set up the Geometry.
    osg::Geometry* geom = new osg::Geometry;

    osg::Vec3Array* coords = new osg::Vec3Array(4);
    (*coords)[0].set(xMin,yMax,z);
    (*coords)[1].set(xMin,yMin,z);
    (*coords)[2].set(xMax,yMin,z);
    (*coords)[3].set(xMax,yMax,z);
    geom->setVertexArray(coords);

    osg::Vec3Array* norms = new osg::Vec3Array(1);
    (*norms)[0].set(0.0f,0.0f,1.0f);
    geom->setNormalArray(norms);
    geom->setNormalBinding(osg::Geometry::BIND_OVERALL);

    osg::Vec2Array* tcoords = new osg::Vec2Array(4);
    (*tcoords)[0].set(0.0f,1.0f);
    (*tcoords)[1].set(0.0f,0.0f);
    (*tcoords)[2].set(1.0f,0.0f);
    (*tcoords)[3].set(1.0f,1.0f);
    geom->setTexCoordArray(0,tcoords);
    
    osg::Vec4Array* colours = new osg::Vec4Array(1);
    (*colours)[0].set(1.0f,1.0f,1.0,1.0f);
    geom->setColorArray(colours);
    geom->setColorBinding(osg::Geometry::BIND_OVERALL);

    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,4));

    return geom;
}

osg::Node* createMirroredScene(osg::Node* model)
{

    // calculate where to place the mirror according to the
    // loaded models bounding sphere.
    const osg::BoundingSphere& bs = model->getBound();

    float width_factor = 1.5;
    float height_factor = 0.3;
    
    float xMin = bs.center().x()-bs.radius()*width_factor;
    float xMax = bs.center().x()+bs.radius()*width_factor;
    float yMin = bs.center().y()-bs.radius()*width_factor;
    float yMax = bs.center().y()+bs.radius()*width_factor;
    
    float z = bs.center().z()-bs.radius()*height_factor;
    
    
    // create a textured, transparent node at the appropriate place.
    osg::Drawable* mirror = createMirrorSurface(xMin,xMax,yMin,yMax,z);
    

    osg::MatrixTransform* rootNode = new osg::MatrixTransform;
    rootNode->setMatrix(osg::Matrix::rotate(osg::inDegrees(45.0f),1.0f,0.0f,0.0f));
        
    // make sure that the global color mask exists.
    osg::ColorMask* rootColorMask = new osg::ColorMask;
    rootColorMask->setMask(true,true,true,true);        
    
    // set up depth to be inherited by the rest of the scene unless
    // overrideen. this is overridden in bin 3.
    osg::Depth* rootDepth = new osg::Depth;
    rootDepth->setFunction(osg::Depth::LESS);
    rootDepth->setRange(0.0,1.0);

    osg::StateSet* rootStateSet = new osg::StateSet();        
    rootStateSet->setAttribute(rootColorMask);
    rootStateSet->setAttribute(rootDepth);

    rootNode->setStateSet(rootStateSet);


    // bin1  - set up the stencil values and depth for mirror.
    {
    
        // set up the stencil ops so that the stencil buffer get set at
        // the mirror plane 
        osg::Stencil* stencil = new osg::Stencil;
        stencil->setFunction(osg::Stencil::ALWAYS,1,~0u);
        stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::REPLACE);
        
        // switch off the writing to the color bit planes.
        osg::ColorMask* colorMask = new osg::ColorMask;
        colorMask->setMask(false,false,false,false);
        
        osg::StateSet* statesetBin1 = new osg::StateSet();        
        statesetBin1->setRenderBinDetails(1,"RenderBin");
        statesetBin1->setMode(GL_CULL_FACE,osg::StateAttribute::OFF);
        statesetBin1->setAttributeAndModes(stencil,osg::StateAttribute::ON);
        statesetBin1->setAttribute(colorMask);
        
        // set up the mirror geode.
        osg::Geode* geode = new osg::Geode;
        geode->addDrawable(mirror);
        geode->setStateSet(statesetBin1);
        
        rootNode->addChild(geode);
        
    }

    // bin one - draw scene without mirror or reflection, unset 
    // stencil values where scene is infront of mirror and hence
    // occludes the mirror. 
    {        
        osg::Stencil* stencil = new osg::Stencil;
        stencil->setFunction(osg::Stencil::ALWAYS,0,~0u);
        stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::REPLACE);

        osg::StateSet* statesetBin2 = new osg::StateSet();        
        statesetBin2->setRenderBinDetails(2,"RenderBin");
        statesetBin2->setAttributeAndModes(stencil,osg::StateAttribute::ON);
        

        osg::Group* groupBin2 = new osg::Group();
        groupBin2->setStateSet(statesetBin2);
        groupBin2->addChild(model);
        
        rootNode->addChild(groupBin2);
    }
        
    // bin3  - set up the depth to the furthest depth value
    {
    
        // set up the stencil ops so that only operator on this mirrors stencil value.
        osg::Stencil* stencil = new osg::Stencil;
        stencil->setFunction(osg::Stencil::EQUAL,1,~0u);
        stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::KEEP);
        
        // switch off the writing to the color bit planes.
        osg::ColorMask* colorMask = new osg::ColorMask;
        colorMask->setMask(false,false,false,false);

        // set up depth so all writing to depth goes to maximum depth.
        osg::Depth* depth = new osg::Depth;
        depth->setFunction(osg::Depth::ALWAYS);
        depth->setRange(1.0,1.0);

        osg::StateSet* statesetBin3 = new osg::StateSet();
        statesetBin3->setRenderBinDetails(3,"RenderBin");
        statesetBin3->setMode(GL_CULL_FACE,osg::StateAttribute::OFF);
        statesetBin3->setAttributeAndModes(stencil,osg::StateAttribute::ON);
        statesetBin3->setAttribute(colorMask);
        statesetBin3->setAttribute(depth);
        
        // set up the mirror geode.
        osg::Geode* geode = new osg::Geode;
        geode->addDrawable(mirror);
        geode->setStateSet(statesetBin3);
        
        rootNode->addChild(geode);
        
    }

    // bin4  - draw the reflection.
    {
    
        // now create the 'reflection' of the loaded model by applying
        // create a Transform which flips the loaded model about the z axis
        // relative to the mirror node, the loadedModel is added to the
        // Transform so now appears twice in the scene, but is shared so there
        // is negligable memory overhead.  Also use an osg::StateSet 
        // attached to the Transform to override the face culling on the subgraph
        // to prevert an 'inside' out view of the reflected model.
        // set up the stencil ops so that only operator on this mirrors stencil value.



        // this clip plane removes any of the scene which when mirror would
        // poke through the mirror.  However, this clip plane should really
        // flip sides once the eye point goes to the back of the mirror...
        osg::ClipPlane* clipplane = new osg::ClipPlane;
        clipplane->setClipPlane(0.0,0.0,-1.0,z);
        clipplane->setClipPlaneNum(0);

        osg::ClipNode* clipNode = new osg::ClipNode;
        clipNode->addClipPlane(clipplane);


        osg::StateSet* dstate = clipNode->getOrCreateStateSet();
        dstate->setRenderBinDetails(4,"RenderBin");
        dstate->setMode(GL_CULL_FACE,osg::StateAttribute::OVERRIDE|osg::StateAttribute::OFF);

        osg::Stencil* stencil = new osg::Stencil;
        stencil->setFunction(osg::Stencil::EQUAL,1,~0u);
        stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::KEEP);
        dstate->setAttributeAndModes(stencil,osg::StateAttribute::ON);

        osg::MatrixTransform* reverseMatrix = new osg::MatrixTransform;
        reverseMatrix->setStateSet(dstate);
        reverseMatrix->preMult(osg::Matrix::translate(0.0f,0.0f,-z)*
                     osg::Matrix::scale(1.0f,1.0f,-1.0f)*
                     osg::Matrix::translate(0.0f,0.0f,z));

        reverseMatrix->addChild(model);

        clipNode->addChild(reverseMatrix);

        rootNode->addChild(clipNode);
    
    }


    // bin5  - draw the textured mirror and blend it with the reflection.
    {
    
        // set up depth so all writing to depth goes to maximum depth.
        osg::Depth* depth = new osg::Depth;
        depth->setFunction(osg::Depth::ALWAYS);

        osg::Stencil* stencil = new osg::Stencil;
        stencil->setFunction(osg::Stencil::EQUAL,1,~0u);
        stencil->setOperation(osg::Stencil::KEEP, osg::Stencil::KEEP, osg::Stencil::ZERO);

        // set up additive blending.
        osg::BlendFunc* trans = new osg::BlendFunc;
        trans->setFunction(osg::BlendFunc::ONE,osg::BlendFunc::ONE);

        osg::StateSet* statesetBin5 = createMirrorTexturedState("Images/tank.rgb");

        statesetBin5->setRenderBinDetails(5,"RenderBin");
        statesetBin5->setMode(GL_CULL_FACE,osg::StateAttribute::OFF);
        statesetBin5->setAttributeAndModes(stencil,osg::StateAttribute::ON);
        statesetBin5->setAttributeAndModes(trans,osg::StateAttribute::ON);
        statesetBin5->setAttribute(depth);
        
        // set up the mirror geode.
        osg::Geode* geode = new osg::Geode;
        geode->addDrawable(mirror);
        geode->setStateSet(statesetBin5);
        
        rootNode->addChild(geode);

    }
    
    return rootNode;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// create the viewer
// load a model
// decoate the model so it renders using a multipass stencil buffer technique for planar reflections.
// release the viewer
// run main loop.
//
int main( int argc, char **argv )
{

    // use an ArgumentParser object to manage the program arguments.
    osg::ArgumentParser arguments(&argc,argv);
    
    // set up the usage document, in case we need to print out how to use this program.
    arguments.getApplicationUsage()->setDescription(arguments.getApplicationName()+" is the example which demonstrates the use multi-pass rendering, stencil buffer to create a planer reflection effect.");
    arguments.getApplicationUsage()->setCommandLineUsage(arguments.getApplicationName()+" [options] filename ...");
    arguments.getApplicationUsage()->addCommandLineOption("-h or --help","Display this information");
    

    // construct the viewer.
    osgProducer::Viewer viewer(arguments);

    // set up the value with sensible default event handlers.
    viewer.setUpViewer(osgProducer::Viewer::STANDARD_SETTINGS);

    // get details on keyboard and mouse bindings used by the viewer.
    viewer.getUsage(*arguments.getApplicationUsage());

    // if user request help write it out to cout.
    if (arguments.read("-h") || arguments.read("--help"))
    {
        arguments.getApplicationUsage()->write(std::cout);
        return 1;
    }

    // any option left unread are converted into errors to write out later.
    arguments.reportRemainingOptionsAsUnrecognized();

    // report any errors if they have occured when parsing the program aguments.
    if (arguments.errors())
    {
        arguments.writeErrorMessages(std::cout);
        return 1;
    }
    
    if (arguments.argc()<=1)
    {
        arguments.getApplicationUsage()->write(std::cout,osg::ApplicationUsage::COMMAND_LINE_OPTION);
        return 1;
    }


    // read the scene from the list of file specified commandline args.
    osg::ref_ptr<osg::Node> loadedModel = osgDB::readNodeFiles(arguments);

    // if no model has been successfully loaded report failure.
    if (!loadedModel) 
    {
        std::cout << arguments.getApplicationName() <<": No data loaded" << std::endl;
        return 1;
    }


    // optimize the scene graph, remove rendundent nodes and state etc.
    osgUtil::Optimizer optimizer;
    optimizer.optimize(loadedModel.get());

    // add a transform with a callback to animate the loaded model.
    osg::ref_ptr<osg::MatrixTransform> loadedModelTransform = new osg::MatrixTransform;
    loadedModelTransform->addChild(loadedModel.get());

    osg::ref_ptr<osg::NodeCallback> nc = new osg::AnimationPathCallback(loadedModelTransform->getBound().center(),osg::Vec3(0.0f,0.0f,1.0f),osg::inDegrees(45.0f));
    loadedModelTransform->setUpdateCallback(nc.get());


    // finally decorate the loaded model so that it has the required multipass/bin scene graph to do the reflection effect.
    osg::ref_ptr<osg::Node> rootNode = createMirroredScene(loadedModelTransform.get());


    // set the scene to render
    viewer.setSceneData(rootNode.get());

    // create the windows and run the threads.
    viewer.realize();

    while( !viewer.done() )
    {
        // wait for all cull and draw threads to complete.
        viewer.sync();

        // update the scene by traversing it with the the update visitor which will
        // call all node update callbacks and animations.
        viewer.update();
         
        // fire off the cull and draw traversals of the scene.
        viewer.frame();
        
    }
    
    // wait for all cull and draw threads to complete.
    viewer.sync();

    // run a clean up frame to delete all OpenGL objects.
    viewer.cleanup_frame();

    // wait for all the clean up frame to complete.
    viewer.sync();
    
    return 0;

}
