//
//  POVWriterNodeVisitor converts OSG scene graph to POV (povray)
//  and writes it to the stream.
//
//
//  Author: PCJohn (peciva _at fit.vutbr.cz)
//                developed for research purposes of Cadwork (c) and
//                Brno University of Technology (Czech Rep., EU)
//
//  License: public domain
//
//
//  THIS SOFTWARE IS NOT COPYRIGHTED
//
//  This source code is offered for use in the public domain.
//  You may use, modify or distribute it freely.
//
//  This source code is distributed in the hope that it will be useful
//  but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED
//  ARE HEREBY DISCLAIMED. This includes but is not limited to
//  warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
//  If you find the source code useful, authors will kindly welcome
//  if you give them credit and keep their names with their source
//  code, but you are not forced to do so.
//


#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Material>
#include <osg/TexEnv>
#include <sstream>
#include <cassert>
#include "POVWriterNodeVisitor.h"

using namespace std;
using namespace osg;



void POVWriterNodeVisitor::apply( Node& node )
{
   POVWriterNodeVisitor::traverse( node );
}


void POVWriterNodeVisitor::traverse( Node& node )
{
   pushStateSet( node.getStateSet() );

   NodeVisitor::traverse( node );

   popStateSet( node.getStateSet() );
}


void POVWriterNodeVisitor::pushStateSet( const StateSet *ss )
{
   if( ss ) {

      // merge state set
      StateSet *mergedSS = new StateSet(
            *_stateSetStack.top().get(), CopyOp::SHALLOW_COPY );
      mergedSS->merge( *ss );

      // push state set
      _stateSetStack.push( mergedSS );

   }
}


void POVWriterNodeVisitor::popStateSet( const StateSet *ss )
{
   if( ss ) {

      // restore the previous stateset
      assert( _stateSetStack.size() > 0 && "_stateSetStack underflow" );
      _stateSetStack.pop();

   }
}


void POVWriterNodeVisitor::apply( Geode& node )
{
   pushStateSet( node.getStateSet() );

   // iterate through drawables
   for(unsigned int i=0; i<node.getNumDrawables(); ++i)
   {
      // get drawable
      const Drawable *d = node.getDrawable(i);
      if (!d) continue;

      // push state set
      const StateSet *ss = d->getStateSet();
      if( ss )  pushStateSet( ss );

      // transformation matrix
      Matrix m = _transformationStack.top();

      // process lights
      processLights( _stateSetStack.top().get(), m );

      // process geometry
      const Geometry *g = d->asGeometry();
      if( g )
         processGeometry( g, _stateSetStack.top().get(), m );

      // pop state set
      if( ss )  popStateSet( ss );
   }

   popStateSet( node.getStateSet() );
}




/**
 * ValueVisitor writes all values of an array out to a stream,
 * applies a matrix beforehand if necessary.
 */
// The code was adapted from the OBJWriterNodeVisitor.cpp.
// See the file for the list of authors and contributors.
class PovVec3WriterVisitor : public ConstValueVisitor
{
public:

   PovVec3WriterVisitor( ostream& fout, const Matrix& m =
         Matrix::identity(), bool isNormal = false)
   : ConstValueVisitor(),
     _fout( fout ),
     _m( m ),
     _isNormal( isNormal )
   {
      _applyMatrix = ( _m != Matrix::identity() );
      if ( _isNormal )
         _origin = Vec3( 0, 0, 0 ) * _m;
   }

   virtual void apply( const Vec2& v )
   {
      apply( Vec3( v[0], v[1], 0. ) );
   }

   virtual void apply( const Vec3& v )
   {
      Vec3 a;
      if (_applyMatrix)
         a = (_isNormal) ? (v * _m) - _origin : v * _m;
      else
         a = v;
      _fout << "      < " << a[0] << ", " << a[1] << ", " << a[2]
            << " >" << endl;
   }

   virtual void apply( const Vec2b& v )
   {
      apply( Vec3b( v[0], v[1], 0 ) );
   }

   virtual void apply( const Vec3b & v )
   {
      apply( Vec3s( v[0], v[1], v[2] ) );
   }

   virtual void apply( const Vec2s& v )
   {
      apply( Vec3s( v[0], v[1], 0 ) );
   }

   virtual void apply( const Vec3s& v )
   {
      apply( Vec3( v[0], v[1], v[2] ) );
   }

private:

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

   ostream& _fout;
   Matrix   _m;
   bool     _applyMatrix;
   bool     _isNormal;
   Vec3     _origin;
};


class PovVec2WriterVisitor : public ConstValueVisitor
{
public:

   PovVec2WriterVisitor( ostream& fout, const Matrix& m =
         Matrix::identity(), bool isNormal = false)
   : ConstValueVisitor(),
     _fout( fout ),
     _m( m ),
     _isNormal( isNormal )
   {
      _applyMatrix = ( _m != Matrix::identity() );
      if ( _isNormal )
         _origin = Vec3( 0, 0, 0 ) * _m;
   }

   virtual void apply( const Vec2& v )
   {
      Vec2 a;
      if (_applyMatrix) {
         Vec3 b( v[0], v[1], 0. );
         b = (_isNormal) ? (b * _m) - _origin : b * _m;
         a = Vec2( b[0], b[1] );
      } else
         a = v;
      _fout << "      < " << a[0] << ", " << a[1] << " >" << endl;
   }

   virtual void apply( const Vec3& v )
   {
      apply( Vec2( v[0], v[1] ) );
   }

   virtual void apply( const Vec2b& v )
   {
      apply( Vec2( v[0], v[1] ) );
   }

   virtual void apply( const Vec3b & v )
   {
      apply( Vec2b( v[0], v[1] ) );
   }

   virtual void apply( const Vec2s& v )
   {
      apply( Vec2( v[0], v[1] ) );
   }

   virtual void apply( const Vec3s& v )
   {
      apply( Vec2s( v[0], v[1] ) );
   }

private:

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

   ostream& _fout;
   Matrix   _m;
   bool     _applyMatrix;
   bool     _isNormal;
   Vec3     _origin;
};


class ArrayValueFunctor : public ConstArrayVisitor
{
public:

   ArrayValueFunctor( ConstValueVisitor &vv )
      : valueVisitor( &vv )  {}

   template< class ArrayType, class ValueType >
         void visitAll( const ArrayType& a ) {
            const ValueType *v = static_cast< const ValueType* >( a.getDataPointer() );
            unsigned int size = a.getNumElements();
            for( unsigned int i=0; i<size; i++ )
               valueVisitor->apply( v[i] );
         }
   virtual void apply( const ByteArray& a )   { visitAll< ByteArray, GLbyte >( a ); }
   virtual void apply( const ShortArray& a )  { visitAll< ShortArray, GLshort >( a ); }
   virtual void apply( const IntArray& a )    { visitAll< IntArray, GLint >( a ); }
   virtual void apply( const UByteArray& a )  { visitAll< UByteArray, GLubyte >( a ); }
   virtual void apply( const UShortArray& a ) { visitAll< UShortArray, GLushort >( a ); }
   virtual void apply( const UIntArray& a )   { visitAll< UIntArray, GLuint >( a ); }
   virtual void apply( const FloatArray& a )  { visitAll< FloatArray, GLfloat >( a ); }
   virtual void apply( const DoubleArray& a ) { visitAll< DoubleArray, GLdouble >( a ); }
   virtual void apply( const Vec2Array& a )   { visitAll< Vec2Array, Vec2 >( a ); }
   virtual void apply( const Vec3Array& a )   { visitAll< Vec3Array, Vec3 >( a ); }
   virtual void apply( const Vec4Array& a )   { visitAll< Vec4Array, Vec4 >( a ); }
   virtual void apply( const Vec4ubArray& a ) { visitAll< Vec4ubArray, Vec4ub >( a ); }
   virtual void apply( const Vec2bArray& a )  { visitAll< Vec2bArray, Vec2b >( a ); }
   virtual void apply( const Vec3bArray& a )  { visitAll< Vec3bArray, Vec3b >( a ); }
   virtual void apply( const Vec4bArray& a )  { visitAll< Vec4bArray, Vec4b >( a ); }
   virtual void apply( const Vec2sArray& a )  { visitAll< Vec2sArray, Vec2s >( a ); }
   virtual void apply( const Vec3sArray& a )  { visitAll< Vec3sArray, Vec3s >( a ); }
   virtual void apply( const Vec4sArray& a )  { visitAll< Vec4sArray, Vec4s >( a ); }
   virtual void apply( const Vec2dArray& a )  { visitAll< Vec2dArray, Vec2d >( a ); }
   virtual void apply( const Vec3dArray& a )  { visitAll< Vec3dArray, Vec3d >( a ); }
   virtual void apply( const Vec4dArray& a )  { visitAll< Vec4dArray, Vec4d >( a ); }

   virtual void apply( const Array& ) {
      assert( false && "Not using overloaded methods." );
   }

protected:
   ConstValueVisitor *valueVisitor;
};


template < class Type >
class PovArrayWriterFunctor : public ArrayValueFunctor
{
public:
   PovArrayWriterFunctor( ostream& fout, const Matrix& m =
         Matrix::identity(), bool isNormal = false)
      : ArrayValueFunctor( povValueVisitor ),
        povValueVisitor( fout, m, isNormal )  {}

protected:
   Type povValueVisitor;
};


static void writeIndex( ostream& _fout, unsigned int &numTriangles,
                        int i1, int i2, int i3, int &numOnLine )
{
   // produce comma, except the first record
   if( numTriangles != 0 )
      _fout << ",";

   // new line each four triangles
   if( numOnLine == 3 ) {
      _fout << endl;
      _fout << "   ";
      numOnLine = 0;
   }

   _fout << "   < " << i1 << ", " << i2 << ", " << i3 << " >";
   numOnLine++;
}


static void processDrawArrays( ostream& _fout, unsigned int &numTriangles,
                               GLenum mode, int startIndex, int stopIndex )
{
   int numOnLine = 0;

   int i = startIndex;
   switch( mode ) {

      case GL_TRIANGLES:
         for( i += 2; i < stopIndex; i += 3, numTriangles++ )
            writeIndex( _fout, numTriangles, i-2, i-1, i, numOnLine );
         break;

      case GL_TRIANGLE_STRIP:
         for( i += 2; i < stopIndex; i += 1, numTriangles++ )
            writeIndex( _fout, numTriangles, i-2, i-1, i, numOnLine );
         break;

      case GL_TRIANGLE_FAN:
         for( i += 2; i < stopIndex; i += 1, numTriangles++ )
            writeIndex( _fout, numTriangles, startIndex, i-1, i, numOnLine );
         break;

      default:

         OSG_NOTICE<<"pov Plugin : processDrawArrays() can not handle GLenum mode="<<mode<<std::endl;
         return;
   }

   // put new line when primitive set is over
   _fout << endl;
}


class DrawElementsWriter : public  ConstValueVisitor {
public:

   DrawElementsWriter( ostream& fout ) : _fout(fout),
                        numIndices( 0 ), numOnLine( 0 ),
                        numTriangles( 0 )  {}

   virtual void apply( const GLubyte& b )  { processIndex( b ); }
   virtual void apply( const GLushort& s ) { processIndex( s ); }
   virtual void apply( const GLuint& i )   { processIndex( i ); }

   virtual void processIndex( const GLuint i ) = 0;

   inline unsigned int getNumTriangles()  { return numTriangles; }

protected:
   ostream& _fout;
   GLuint indices[3];
   int numIndices;
   int numOnLine;
   unsigned int numTriangles;

   virtual bool processTriangle()
   {
      // don't produce triangle until we have got three vertices
      if( numIndices < 3 )
         return false;

      // produce comma, except the first record
      if( numTriangles != 0 )
         _fout << ",";

      // new line each four triangles
      if( numOnLine == 3 ) {
         _fout << endl;
         _fout << "   ";
         numOnLine = 0;
      }

      // print out indices
      _fout << "   <" << indices[0] << "," << indices[1]
            << "," << indices[2] << ">";

      // increment counters
      numTriangles++;
      numOnLine++;

      // triangle produced => return true
      return true;
   }
};


class TriangleWriter : public DrawElementsWriter {
public:
   TriangleWriter( ostream& fout ) : DrawElementsWriter( fout ) {}

   virtual void processIndex( const GLuint i )
   {
      indices[numIndices++] = i;

      if( processTriangle() )
         numIndices = 0;
   }
};


class TriangleStripWriter : public DrawElementsWriter {
public:
   TriangleStripWriter( ostream& fout ) : DrawElementsWriter( fout ) {}

   virtual void processIndex( const GLuint i )
   {
      indices[0] = indices[1];
      indices[1] = indices[2];
      indices[2] = i;
      numIndices++;

      processTriangle();
   }
};


class TriangleFanWriter : public DrawElementsWriter {
public:
   TriangleFanWriter( ostream& fout ) : DrawElementsWriter( fout ) {}

   virtual void processIndex( const GLuint i )
   {
      if( numIndices == 0 ) {
         indices[0] = i;
         numIndices++;
         return;
      }

      indices[1] = indices[2];
      indices[2] = i;
      numIndices++;

      processTriangle();
   }
};


template < class Type, class IterType >
static void processDrawElements( const PrimitiveSet *pset, DrawElementsWriter *w )
{
   const Type *drawElements = dynamic_cast< const Type* >( pset );
   if (!drawElements) return;

   for( IterType primItr = drawElements->begin();
         primItr != drawElements->end();
         ++primItr )
      w->apply( *primItr );
}


class Vec4ConvertVisitor : public ConstValueVisitor
{
public:
   //virtual void apply( const GLbyte& v )
   //virtual void apply( const GLshort& v )
   //virtual void apply( const GLint& v )
   //virtual void apply( const GLushort& v )
   //virtual void apply( const GLubyte& v )
   //virtual void apply( const GLuint& v )
   //virtual void apply( const GLfloat& v )
   //virtual void apply( const GLdouble& v )
   virtual void apply( const Vec4ub& v ) { r = Vec4( v[0], v[1], v[2], v[3] ); }
   //virtual void apply( const Vec2& v )
   virtual void apply( const Vec3& v )   { r = Vec4( v[0], v[1], v[2], 1. ); }
   virtual void apply( const Vec4& v )   { r = v; }
   //virtual void apply( const Vec2b& v )
   virtual void apply( const Vec3b& v )  { r = Vec4( v[0], v[1], v[2], 1. ); }
   virtual void apply( const Vec4b& v )  { r = Vec4( v[0], v[1], v[2], v[3] ); }
   //virtual void apply( const Vec2s& v )
   virtual void apply( const Vec3s& v )  { r = Vec4( v[0], v[1], v[2], 1. ); }
   virtual void apply( const Vec4s& v )  { r = Vec4( v[0], v[1], v[2], v[3] ); }
   //virtual void apply( const Vec2d& v )
   virtual void apply( const Vec3d& v )  { r = Vec4( v[0], v[1], v[2], 1. ); }
   virtual void apply( const Vec4d& v )  { r = Vec4( v[0], v[1], v[2], v[3] ); }

   const Vec4& getResult() const  { return r; }

protected:
   Vec4 r;
};


void POVWriterNodeVisitor::processGeometry( const Geometry *g,
                                            const StateSet *ss,
                                            const Matrix &m )
{
   // ignore empty geometries because they cause povray to fail loading the model
   // (seen on POV-Ray 3.6.1)
   if( g->getVertexArray() == NULL || g->getVertexArray()->getNumElements() == 0 )
      return;

   // mesh2
   _fout << "mesh2" << endl;
   _fout << "{" << endl;

   // Convert coordinates
   // OSG represents coordinates by: Vec2, Vec3, Vec4
   if( g->getVertexArray() ) {

      _fout << "   vertex_vectors" << endl;
      _fout << "   {" << endl;
      _fout << "      " << g->getVertexArray()->getNumElements() << "," << endl;

      PovArrayWriterFunctor< PovVec3WriterVisitor > povArrayWriter( _fout, m, false );
      g->getVertexArray()->accept( povArrayWriter );

      _fout << "   }" << endl;
   }

   // reuse coord indices for normal and texCoord
   bool needNormalIndices = true;
   bool needTexCoordIndices = true;

   // Convert normals
   // OSG represents normals by: Vec3,Vec3s,Vec3b
   // and can handle: Vec4s,Vec4b by truncating them to three components
   if( g->getNormalArray() ) {

      _fout << "   normal_vectors" << endl;
      _fout << "   {" << endl;
      _fout << "      " << g->getNormalArray()->getNumElements() << "," << endl;

      PovArrayWriterFunctor< PovVec3WriterVisitor > povArrayWriter( _fout, m, true );
      g->getNormalArray()->accept( povArrayWriter );

      _fout << "   }" << endl;

      // need normal indices?
      if( g->getVertexArray() )
         if( g->getVertexArray()->getNumElements() == g->getNormalArray()->getNumElements() )
            needNormalIndices = false;
   }

   // Convert texture coordinates
   if( g->getTexCoordArray( 0 ) ) {

      _fout << "   uv_vectors" << endl;
      _fout << "   {" << endl;
      _fout << "      " << g->getTexCoordArray( 0 )->getNumElements() << "," << endl;

      // texture coordinates
      PovArrayWriterFunctor< PovVec2WriterVisitor > povArrayWriter( _fout );
      g->getTexCoordArray( 0 )->accept( povArrayWriter );

      _fout << "   }" << endl;

      // need texCoord indices?
      if( g->getVertexArray() )
         if( g->getVertexArray()->getNumElements() == g->getTexCoordArray( 0 )->getNumElements() )
            needTexCoordIndices = false;
   }

   // indices string stream
   stringstream indicesStream;
   indicesStream << "   ";
   unsigned int numTriangles = 0;

   // process primitive sets
   int psetIndex, numPsets = g->getNumPrimitiveSets();
   for( psetIndex = 0; psetIndex < numPsets; psetIndex++ ) {

      // get primitive set
      const PrimitiveSet *pset = g->getPrimitiveSet( psetIndex );
      PrimitiveSet::Type type = pset->getType();
      GLenum mode = pset->getMode();

      // skip not supported primitives
      switch( mode ) {
         case GL_TRIANGLES:
         case GL_TRIANGLE_STRIP:
         case GL_TRIANGLE_FAN:
         case GL_QUADS:
         case GL_QUAD_STRIP:
         case GL_POLYGON:
            break; // only supported types
         default:
            continue; // skip primitive set
                      // Povray does not support lines and points
      };

      switch( type ) {
         case PrimitiveSet::DrawArraysPrimitiveType:
         {
            const DrawArrays *drawArrays = dynamic_cast< const DrawArrays* >( pset );
            if (drawArrays)
            {
                int startIndex = drawArrays->getFirst();
                int stopIndex = startIndex + drawArrays->getCount();

                // FIXME: Am I using startIndex for all bundles that are PER_VERTEX?

                processDrawArrays( indicesStream, numTriangles,
                                mode, startIndex, stopIndex );
            }
            break;
         }

         case PrimitiveSet::DrawArrayLengthsPrimitiveType:
         {
            const DrawArrayLengths *drawArrayLengths = dynamic_cast< const DrawArrayLengths* >( pset );
            if (drawArrayLengths)
            {
                int startIndex = drawArrayLengths->getFirst();
                DrawArrayLengths::vector_type::const_iterator itr = drawArrayLengths->begin();

                for( ; itr != drawArrayLengths->end(); itr++ ) {
                processDrawArrays( indicesStream, numTriangles,
                                    mode, startIndex, *itr );
                startIndex += *itr;
                }
            }
            break;
         }

         case PrimitiveSet::DrawElementsUBytePrimitiveType:
         case PrimitiveSet::DrawElementsUShortPrimitiveType:
         case PrimitiveSet::DrawElementsUIntPrimitiveType:
         {
            DrawElementsWriter *w;
            switch( mode ) {
               case GL_TRIANGLES:
                  w = new TriangleWriter( indicesStream );
                  break;
               case GL_TRIANGLE_STRIP:
                  w = new TriangleStripWriter( indicesStream );
                  break;
               case GL_TRIANGLE_FAN:
                  w = new TriangleFanWriter( indicesStream );
                  break;
               default:
                  assert( false && "Not implemented yet." );
                  goto skip;
            }

            switch( type ) {

               case PrimitiveSet::DrawElementsUBytePrimitiveType:
                  processDrawElements< DrawElementsUByte,
                        DrawElementsUByte::const_iterator >( pset, w );
                  break;

               case PrimitiveSet::DrawElementsUShortPrimitiveType:
                  processDrawElements< DrawElementsUShort,
                        DrawElementsUShort::const_iterator >( pset, w );
                  break;

               case PrimitiveSet::DrawElementsUIntPrimitiveType:
                  processDrawElements< DrawElementsUInt,
                        DrawElementsUInt::const_iterator >( pset, w );
                  break;

               default:
                  assert( false );
            }

            numTriangles += w->getNumTriangles();

            delete w;
         }
         skip:
         break;

         default:
            assert( false && "Primitive set not handled." );
      }
   }

   const Texture *texture = dynamic_cast< const Texture* >(ss->getTextureAttribute( 0, StateAttribute::TEXTURE ));
   const Image *image = ( texture ? texture->getImage( 0 ) : NULL );
   // TexEnv not used yet
   //const TexEnv *texEnv =  dynamic_cast< const TexEnv* >(
   //      ss->getTextureAttribute( 0, StateAttribute::TEXENV ));
   bool texturing2D = ss->getTextureMode( 0, GL_TEXTURE_2D ) != 0;

   _fout << "   face_indices" << endl
         << "   {" << endl
         << "      " << numTriangles << "," << endl
         << indicesStream.str()
         << "   }" << endl;

   if( needNormalIndices )
      _fout << "   normal_indices" << endl
            << "   {" << endl
            << "      " << numTriangles << "," << endl
            << indicesStream.str()
            << "   }" << endl;

   if( needTexCoordIndices )
      _fout << "   uv_indices" << endl
            << "   {" << endl
            << "      " << numTriangles << "," << endl
            << indicesStream.str()
            << "   }" << endl;

   // POV-Ray's surface properties
   _fout << "   texture {" << endl;

#if 1

   // color variables
   Vec4f ambient( 0.2f, 0.2f, 0.2f, 1.f );
   Vec4f diffuse( 0.8f, 0.8f, 0.8f, 1.f );
   Vec4f specular( 0.f, 0.f, 0.f, 1.f );
   Vec4f pigmentColor;

   // get ambient and diffuse
   const Material *material = dynamic_cast< const Material* >(
         ss->getAttribute( StateAttribute::MATERIAL ) );
   if( !material ) {
      const Array *a = g->getColorArray();
      if( a && a->getNumElements() > 0 ) {
         Vec4ConvertVisitor cv;
         g->getColorArray()->accept( 0, cv );
         diffuse = cv.getResult();
      }
   } else {
      diffuse = material->getDiffuse( Material::FRONT );
      ambient = material->getAmbient( Material::FRONT );
      specular = material->getSpecular( Material::FRONT );
   }

   // compute intensities
   float diffuseIntensity  = diffuse.r() + diffuse.g() + diffuse.b();
   float ambientIntensity  = ambient.r() + ambient.g() + ambient.b();
   float specularIntensity = specular.r() + specular.g() + specular.b();

   // pigment color defaults to diffuse
   pigmentColor = diffuse;
   float pigmentIntensity = diffuseIntensity;

   // if diffuse is too dark and ambient is strong, use it instead
   if( diffuseIntensity < 2 * ambientIntensity ) {
      pigmentColor = ambient;
      pigmentIntensity = ambientIntensity;
   }

#else
   Vec4ConvertVisitor cv;
   g->getColorArray()->accept( 0, cv );
   Vec4 pigmentColor = cv.getResult();
#endif

   // has 2D texture => produce image_map
   if( texturing2D && texture && image ) {
      string fileName = image->getFileName();

      // replace '\' by '/'
      // ('\' does not work on Linux POV-Ray)
      // FIXME: is '/' working on Windows?
      string::size_type i=0;
      while( ( i = fileName.find( '\\', i ) ) != string::npos )
         fileName[i] = '/';

      _fout << "      uv_mapping pigment {" << endl
            << "         image_map { png \"" << fileName << "\" }" << endl
            << "      }" << endl;

   // no 2D texture => produce single color
   } else {

      // rgb and f as filter (for transparency, 0 - opaque, 1 - transparent)
      _fout << "      pigment {" << endl
            << "         color rgbf < " << pigmentColor[0] << ", "
                                        << pigmentColor[1] << ", "
                                        << pigmentColor[2] << ", 0 >" << endl
            << "      }" << endl;
   }

   // POV-Ray's finish
   float ai = (pigmentIntensity != 0) ? ambientIntensity/pigmentIntensity : 0.f;
   float di = (pigmentIntensity != 0) ? diffuseIntensity/pigmentIntensity : 0.f;
   _fout << "      finish { ambient " << ai << endl
         << "               diffuse " << di << endl
         << "               specular " << specularIntensity/3. << endl
         << "               reflection " << specularIntensity/3. << endl
         << "             }" << endl
         << "   }" << endl;

   _fout << "}" << endl;

   numProducedTriangles += numTriangles;
}


void POVWriterNodeVisitor::processLights( const StateSet *ss, const Matrix &m )
{
   const StateSet::AttributeList &attributeList = ss->getAttributeList();
   StateSet::AttributeList::const_iterator it = attributeList.begin();
   for( ; it!=attributeList.end(); ++it )
      if( it->first.first == StateAttribute::LIGHT ) {
         Light *l = dynamic_cast< Light* >( it->second.first.get() );
         if( l &&
             ss->getMode( GL_LIGHT0 + l->getLightNum() ) & StateAttribute::ON &&
             lights.find( l ) == lights.end() ) {

            // append the light into the map
            lights[ l ] = 1; // use any number (the number is not used)

            // directional light requires special treatment
            Vec4 pos4 = l->getPosition();
            Vec3 pos3( pos4.x(), pos4.y(), pos4.z() );
            bool directional = (pos4.w() == 0.);
            bool spot = false;
            if( directional ) {
               pos3.normalize();
               pos3 = bound.center() + ( pos3 * bound.radius() * 1.01f );
            } else {
               pos3 /= pos4.w();
               spot = !equivalent( l->getSpotCutoff(), 180.f );
            }

            // create the light code
            _fout << "light_source {" << endl;

            // position
            PovVec3WriterVisitor povVec3Writer( _fout, m );
            povVec3Writer.apply( pos3 );

            // color
            // note: transmit and filter color values are ignored
            // by POV-Ray for light sources
            // (see POV-Ray's (version 3.6.1, chapter 2.4.7) doc:
            // http://www.povray.org/documentation/view/3.6.1/308/)
            _fout << "   color rgb";
            PovVec3WriterVisitor povColorWriter( _fout );
            const Vec3::value_type *f = l->getDiffuse().ptr();
            povColorWriter.apply( Vec3( f[0], f[1], f[2] ) );

            // directional light
            if( directional ) {
               _fout << "   parallel" << endl
                     << "   point_at";
               povVec3Writer.apply( bound.center() );
            }

            // spot light
            if( spot ) {
               _fout << "   spotlight" << endl
                     << "   point_at";
               povVec3Writer.apply( pos3 + l->getDirection() );
               // FIXME: radius and tightness models the light distribution
               // differently than OpenGL's shininess. So, different
               // visual results are produced. The difference can be lowered
               // much by computing radius.
               _fout << "   falloff " << l->getSpotCutoff() << endl
                     << "   radius 0" << endl
                     << "   tightness " << l->getSpotExponent() / 128.f * 100.f << endl;
            }

            // light source end
            _fout << "}" << endl;
         }
      }
}


void POVWriterNodeVisitor::apply( Transform& node )
{
   // push new transformation on transformation stack
   Matrix m = _transformationStack.top();
   node.computeLocalToWorldMatrix( m, this );
   _transformationStack.push( m );

   // traverse the node
   apply( (Group&)node );

   // pop transformation
   _transformationStack.pop();
}


POVWriterNodeVisitor::POVWriterNodeVisitor( ostream& fout, const BoundingSphere& b )
   : NodeVisitor(TRAVERSE_ALL_CHILDREN),
     _fout( fout ),
     bound( b ),
     numProducedTriangles( 0 )
{
   _stateSetStack.push( new StateSet() );
   _transformationStack.push( Matrix( 1., 0.,0.,0.,
                                      0., 0.,1.,0.,
                                      0., 1.,0.,0.,
                                      0., 0.,0.,1. ) );
}


POVWriterNodeVisitor::~POVWriterNodeVisitor()
{
   assert( _stateSetStack.size() >= 1 && "_stateSetStack underflow." );
   assert( _stateSetStack.size() <= 1 && "_stateSetStack overflow." );
   assert( _transformationStack.size() >= 1 && "_transformationStack underflow." );
   assert( _transformationStack.size() <= 1 && "_transformationStack overflow." );
   _stateSetStack.pop();
   _transformationStack.pop();
}
