/* **************************************************************************
* OpenSceneGraph loader for Terrapage format database
* by Boris Bralo 2002
*
* based on/modifed  sgl (Scene Graph Library) loader by Brian Walsh
*
* This loader is based on/modified from Terrain Experts Performer Loader,
* and was ported to SGL by Bryan Walsh / bryanw at earthlink dot net
*
* That loader is redistributed under the terms listed on Terrain Experts
* website (www.terrex.com/www/pages/technology/technologypage.htm)
*
* "TerraPage is provided as an Open Source format for use by anyone...
* We supply the TerraPage C++ source code free of charge.  Anyone
* can use it and redistribute it as needed (including our competitors).
* We do, however, ask that you keep the TERREX copyrights intact."
*
* Copyright Terrain Experts Inc. 1999.
* All Rights Reserved.
*
*****************************************************************************/
#include "trpage_sys.h"

#include <osg/AlphaFunc>
#include <osg/Group>
#include <osg/Material>
#include <osg/TexEnv>
#include <osg/LOD>
#include <osg/Geode>
#include <osg/Billboard>
#include <osg/Matrix>
#include <osg/MatrixTransform>
#include <osg/Geometry>
#include <osg/CullFace>
#include <osg/Light>
#include <osg/Notify>
#include <osg/PolygonOffset>
#include <osg/MatrixTransform>

#include <osgSim/LightPointNode>
#include <osg/Point>

#include "TrPageParser.h"
#include "TrPageArchive.h"

#include <algorithm>

using namespace txp;
using namespace osg;
using std::vector;
using std::string;

#if defined(WIN32) && defined(_MSC_VER)
#define strcasecmp stricmp
#endif

//----------------------------------------------------------------------------
// Check if the node is billboard
namespace {
    bool is_billboard (Node* node)
    {
        if (node && (node!=(Node*)1) && (strcmp(node->className(),"GeodeGroup") == 0))
        {
            GeodeGroup* group = static_cast<GeodeGroup*>(node);
            return (group->getNumChildren() && (strcmp(group->getChild(0)->className(),"Billboard") == 0));
        }
        return false;
    };
    
    void check_format(trpgTexture::ImageType type, int depth, GLenum& internalFormat, GLenum& pixelFormat, GLenum&)
    {
        switch(type)
        {
        case trpgTexture::trpg_RGB8:
            internalFormat = GL_RGB;
            pixelFormat    = GL_RGB;
            break;
        case trpgTexture::trpg_RGBA8:
            internalFormat = GL_RGBA;
            pixelFormat    = GL_RGBA;
            break;
        case trpgTexture::trpg_INT8:
            internalFormat = GL_LUMINANCE;
            pixelFormat    = GL_LUMINANCE;
            break;
        case trpgTexture::trpg_INTA8:
            internalFormat = GL_LUMINANCE_ALPHA;
            pixelFormat    = GL_LUMINANCE_ALPHA;
            break;
        case trpgTexture::trpg_FXT1:
        case trpgTexture::trpg_Filler:
        case trpgTexture::trpg_RGBX: // MCM
        case trpgTexture::trpg_Unknown:
            break;
        case trpgTexture::trpg_DDS:
        case trpgTexture::trpg_DXT1:
            if(depth == 3)
            {
                internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
                pixelFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
            }
            else
            {
                internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
                pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
            }
            break;
        case trpgTexture::trpg_DXT3:
            if(depth == 3)
            {
                // not supported.
            }
            else
            {
                internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
                pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
            }
            break;
        case trpgTexture::trpg_DXT5:
            if(depth == 3)
            {
                // not supported.
            }
            else
            {
                internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
                pixelFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
            }
            break;
        }
    }
}

//----------------------------------------------------------------------------
// Get a template texture via the image helper
Texture2D* txp::GetLocalTexture(trpgrImageHelper& image_helper, const trpgTexture* tex)
{
    Texture2D* osg_texture= 0L;

    trpg2iPoint s;
    tex->GetImageSize(s);
    int32 depth;
    tex->GetImageDepth(depth);
    trpgTexture::ImageType type;
    tex->GetImageType(type);
    
    GLenum internalFormat = (GLenum)-1;
    GLenum pixelFormat = (GLenum)-1;
    GLenum dataType = GL_UNSIGNED_BYTE;

    check_format(type,depth,internalFormat , pixelFormat , dataType);
    
    if(pixelFormat!=(GLenum)-1)
    {
        osg_texture = new Texture2D();

        Image* image = new Image;
        char* data = 0L;

        bool bMipmap;
        tex->GetIsMipmap(bMipmap);
        int32 num_mipmaps = bMipmap  ?  tex->CalcNumMipmaps() : 1; // this is currently line 130

        // osg::Image do their own mipmaps
        if(num_mipmaps <= 1)
        {
            int32 size = s.x*s.y*depth; 
            // int32 size = const_cast<trpgTexture*>(tex)->MipLevelSize(1) ;
            data = new char [size];
            image_helper.GetLocalGL(tex,data,size);
            image->setImage(s.x,s.y,1,internalFormat, pixelFormat, dataType,
                    (unsigned char*)data,osg::Image::USE_NEW_DELETE);
        }
        else
        {
            int32 size = tex->CalcTotalSize();
            trpgTexture* tmp_tex = const_cast<trpgTexture*>(tex);

            data = new char [size];
            image_helper.GetLocalGL(tex,data,size);
            // Load entire texture including mipmaps
            image->setImage(s.x,s.y,1,internalFormat, pixelFormat, dataType,
                    (unsigned char*)data,
                    osg::Image::USE_NEW_DELETE);

            // now set mipmap data (offsets into image raw data)
            Image::MipmapDataType mipmaps;
            // number of offsets in osg is one less than num_mipmaps
            // because it's assumed that first offset iz 0 
            mipmaps.resize(num_mipmaps-1);
            for( int k = 1 ; k < num_mipmaps;k++ )
            {
                mipmaps[k-1] = tmp_tex->MipLevelOffset(k);
            }
            image->setMipmapData(mipmaps);

        }

        osg_texture->setImage(image);
    }
    return osg_texture;
}
//----------------------------------------------------------------------------
// Get a locale texture via the image helper
Texture2D* txp::GetTemplateTexture(trpgrImageHelper& image_helper, trpgLocalMaterial* locmat, const trpgTexture* tex, int index)
{
    Texture2D* osg_texture= 0L;

    trpg2iPoint s;
    tex->GetImageSize(s);
    int32 depth;
    tex->GetImageDepth(depth);
    trpgTexture::ImageType type;
    tex->GetImageType(type);
    
    GLenum internalFormat = (GLenum)-1;
    GLenum pixelFormat = (GLenum)-1;
    GLenum dataType = GL_UNSIGNED_BYTE;

    check_format(type,depth,internalFormat , pixelFormat , dataType);
    
    if(pixelFormat!=(GLenum)-1)
    {
        osg_texture = new Texture2D();

        Image* image = new Image;
        char* data = 0L;

        bool bMipmap;
        tex->GetIsMipmap(bMipmap);
        int32 num_mipmaps = bMipmap  ?  tex->CalcNumMipmaps() : 1; // this is currently line 130

        // osg::Image do their own mipmaps
        if(num_mipmaps <= 1)
        {
            int32 size = s.x*s.y*depth; 
            // int32 size = const_cast<trpgTexture*>(tex)->MipLevelSize(1) ;
            data = new char [size];
            image_helper.GetNthImageForLocalMat(locmat,index, data,size);

            image->setImage(s.x,s.y,1,internalFormat, pixelFormat, dataType,
                    (unsigned char*)data,osg::Image::USE_NEW_DELETE);
        }
        else
        {
            int32 size = tex->CalcTotalSize();
            trpgTexture* tmp_tex = const_cast<trpgTexture*>(tex);

            data = new char [size];

            image_helper.GetNthImageForLocalMat(locmat,index, data,size);

            // Load entire texture including mipmaps
            image->setImage(s.x,s.y,1,internalFormat, pixelFormat, dataType,
                    (unsigned char*)data,
                    osg::Image::USE_NEW_DELETE);

            // now set mipmap data (offsets into image raw data)
            Image::MipmapDataType mipmaps;
            // number of offsets in osg is one less than num_mipmaps
            // because it's assumed that first offset iz 0 
            mipmaps.resize(num_mipmaps-1);
            for( int k = 1 ; k < num_mipmaps;k++ )
            {
                mipmaps[k-1] = tmp_tex->MipLevelOffset(k);
            }
            image->setMipmapData(mipmaps);

        }

        osg_texture->setImage(image);
    }
    return osg_texture;
}

//----------------------------------------------------------------------------
//
// Group Reader Class
//
//----------------------------------------------------------------------------
// Apply transformation on geometry
class TransformFunctor : public osg::Drawable::AttributeFunctor
{
    public:
    
        osg::Matrix _m;
        osg::Matrix _im;

        TransformFunctor(const osg::Matrix& m)
        {
            _m = m;
            _im.invert(_m);
        }
            
        virtual ~TransformFunctor() {}

        virtual void apply(osg::Drawable::AttributeType type,unsigned int count,osg::Vec3* begin)
        {
            if (type == osg::Drawable::VERTICES)
            {
                osg::Vec3* end = begin+count;
                for (osg::Vec3* itr=begin;itr<end;++itr)
                {
                    (*itr) = (*itr)*_m;
                }
            }
            else if (type == osg::Drawable::NORMALS)
            {
                osg::Vec3* end = begin+count;
                for (osg::Vec3* itr=begin;itr<end;++itr)
                {
                    // note post mult by inverse for normals.
                    (*itr) = osg::Matrix::transform3x3(_im,(*itr));
                    (*itr).normalize();
                }
            }
        }

        inline void SetMatrix(const osg::Matrix& m)
        {
            _m = m;
            _im.invert(_m);
        }

};

//----------------------------------------------------------------------------
geomRead::geomRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
geomRead::~geomRead()
{
}

//----------------------------------------------------------------------------
void* geomRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgGeometry geom;
    if (!geom.Read(buf)) 
        return NULL;
    
    // Get the necessary info out of the geom
    trpgGeometry::PrimType primType;
    int numPrims;
    int numVert;
    int numNorm;
    geom.GetPrimType(primType);
    geom.GetNumPrims(numPrims);
    geom.GetNumVertex(numVert);
    geom.GetNumNormal(numNorm);
    
    // Get vertices
    Vec3Array* vertices = new Vec3Array(numVert);
    geom.GetVertices((float *)&(vertices->front()));
    
    // Turn the trpgGeometry into something Performer can understand
    Geometry *geometry      = 0L;
    
    // Get texture coordinates
    trpgTexData td;
    int num_tex;
    geom.GetNumTexCoordSets(num_tex);
    Vec2Array** tex_coords = new Vec2Array*[num_tex];
    for (int texno = 0; texno < num_tex; texno++)
    {
        tex_coords[texno] = 0L;
        if (geom.GetTexCoordSet(texno,&td))
        {
            tex_coords[texno] = new Vec2Array(numVert); 
            for (int i=0 ;i < numVert; i++)
            {
                (*(tex_coords[texno]))[i].set(td.floatData[2*i+0],td.floatData[2*i+1]);
            }
        }
    }
   
    // The normals
    Vec3Array* normals = 0L;
    if (numNorm == numVert)
    {
        normals = new Vec3Array(numVert);
        geom.GetNormals((float*)&(normals->front()));
    }
    
    // Set up the primitive type
    switch (primType)
    {
    case trpgGeometry::Triangles:
        {
            geometry = new Geometry;
            geometry->addPrimitiveSet(new DrawArrays(PrimitiveSet::TRIANGLES,0,numPrims*3));
        }
        break;
    case trpgGeometry::Quads:
        {
            geometry = new Geometry;
            geometry->addPrimitiveSet(new DrawArrays(PrimitiveSet::QUADS,0,numPrims*4));
        }
        break;
    case trpgGeometry::TriStrips:
        {
            // Need primitive lengths too
            int* primitives = new int[numPrims];
            geom.GetPrimLengths(primitives);
            
            // Define Geomtry
            geometry = new Geometry;
            int first=0;
            for(int i=0;i<numPrims;++i)
            {
                geometry->addPrimitiveSet(new DrawArrays(PrimitiveSet::TRIANGLE_STRIP,first,primitives[i]));
                first += primitives[i];
                
            }
        }
        break;
    case trpgGeometry::TriFans:
        {
            // Need primitive lengths too
            int* primitives = new int[numPrims];
            geom.GetPrimLengths(primitives);


            // create the trifan primitives.            
            geometry = new Geometry;
            int first=0;
            int i;
            for(i=0;i<numPrims;++i)
            {
                geometry->addPrimitiveSet(new DrawArrays(PrimitiveSet::TRIANGLE_FAN,first,primitives[i]));
                first += primitives[i];
            }

            // Need to flip the fans coords.
            int ind = 0;
            for (i=0;i<numPrims;++i)
            {
                int start=ind+1;
                int end=primitives[i]+ind-1;
                // Swap from start+1 to end
                int numSwap = (end-start+1)/2;
                // Swap vertices, texture coords & normals
                for (int j=0; j < numSwap; j++ )
                {
                    std::swap((*vertices)[start], (*vertices)[end]);
                    for(int texno = 0; texno < num_tex; texno ++ )
                    {
                        if( tex_coords[texno] )
                            std::swap((*tex_coords[texno])[start], (*tex_coords[texno])[end]);
                    }
                    if(normals)
                        std::swap((*normals)[start], (*normals)[end]);
                    start++;
                    end--;
                }
                ind += primitives[i];
            }
        }
        break;
    default:
        
        break;
    };
    
    
    // Add it to the current parent group
    GeodeGroup *top = parse->GetCurrTop();
    Geode *geode = top->GetGeode();
    if (geometry)
    {
        // added this set use display list off since terrapage will
        // be creating and deleting these geometry leaves on the fly
        // so we don't want to be creating short lived display lists either.
        geometry->setUseDisplayList(false);

        geometry->setVertexArray(vertices);
        if (normals)
        {
            geometry->setNormalArray(normals);
            geometry->setNormalBinding(Geometry::BIND_PER_VERTEX);
        }
        // Note: Should check number of materials first
        // Note: Should be combining multiple geosets
        bool local;
        int matId;
        int  matNo;
        geom.GetNumMaterial(matNo);
        ref_ptr<StateSet>  sset = 0L;
        for(int n_mat = 0; n_mat < matNo ; ++n_mat)
        {
            ref_ptr<StateSet> tmp_ss = 0L;
            geom.GetMaterial(n_mat,matId,local);
            if( local )
                tmp_ss = (*parse->GetLocalMaterials())[matId];
            else
               tmp_ss = (*parse->GetMaterials())[matId];
            if(sset.valid()) 
            {
                if(tmp_ss.valid()){
                    osg::StateAttribute* texenv0 = tmp_ss->getTextureAttribute(0,StateAttribute::TEXENV);
                    if(texenv0) 
                        sset->setTextureAttribute(n_mat,texenv0);
                    osg::StateAttribute* tex0 = tmp_ss->getTextureAttribute(0,StateAttribute::TEXTURE);
                    if(tex0) 
                        sset->setTextureAttributeAndModes(n_mat,tex0,osg::StateAttribute::ON);
                }
//                sset->merge(*tmp_ss.get());
            }
            else 
                sset = tmp_ss;
        }

        if (tex_coords)
        {
            for (int texno = 0; texno < num_tex; texno++)
                geometry->setTexCoordArray( texno, tex_coords[texno]);
        }

        if ( is_billboard(top) )
        {
            geometry->setStateSet(sset.get());

            Billboard *billboard = static_cast<Billboard*>(top->getChild(0));

            switch (parse->getBillboardType()) {
            case trpgBillboard::Individual:
            {
                // compute center of billboard geometry
                const BoundingBox& bbox = geometry->getBound();
                Vec3 center ((bbox._min + bbox._max) * 0.5f);

                // make billboard geometry coordinates relative to computed center
                Matrix matrix;
                matrix.makeTranslate(-center[0], -center[1], -center[2]);

                TransformFunctor tf(matrix);
                geometry->accept(tf);
                geometry->dirtyBound();

                billboard->addDrawable(geometry);
                billboard->setPos(0, center);
            }
            break;
            case trpgBillboard::Group:
            {
                Vec3 center(parse->getBillboardCenter());

                // make billboard geometry coordinates relative to specified center
                Matrix matrix;
                matrix.makeTranslate(-center[0], -center[1], -center[2]);

                TransformFunctor tf(matrix);
                geometry->accept(tf);
                geometry->dirtyBound();

                billboard->addDrawable(geometry);
                billboard->setPos(0, center);
            }
            break;
            default:
                billboard->addDrawable(geometry);
                notify(WARN) << "TerraPage loader: fell through case: " <<  __FILE__ << " " << __LINE__  << ".\n";
                break;
            }
        }
        else
        {
            // if this is part of the layer we turn the PolygonOffset on
            
            if ( (parse->GetCurrLayer() == top) && geode->getNumDrawables() )
            {
                StateSet* poStateSet = new StateSet;
                PolygonOffset* polyoffset = new PolygonOffset;

                poStateSet->merge(*sset.get());
                polyoffset->setFactor(-1.0f*geode->getNumDrawables());
                polyoffset->setUnits(-20.0f*geode->getNumDrawables());
                poStateSet->setAttributeAndModes(polyoffset,osg::StateAttribute::ON);

                geometry->setStateSet(poStateSet);
                
            }
            else 
            {
                geometry->setStateSet(sset.get());
            }

            geode->addDrawable(geometry);
        }

    }
    return (void *) 1;
}

//----------------------------------------------------------------------------
//
// Group Reader Class
//
//----------------------------------------------------------------------------
groupRead::groupRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* groupRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgGroup group;
    if (!group.Read(buf))
        return NULL;
    // Create a new Performer group
    GeodeGroup* osg_Group = new GeodeGroup();
    // Dump this group into the hierarchy
    parse->AddIntoSceneGraph(osg_Group);
    // Register the group for attachements
    int32 id;
    group.GetID(id);
    parse->AddToGroupList(id,osg_Group);
    return (void *) osg_Group;
}

//----------------------------------------------------------------------------
//
// Layer Reader Class
//
//----------------------------------------------------------------------------
layerRead::layerRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* layerRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgLayer group;
    if (!group.Read(buf))
        return NULL;
    // Create a new Performer group
    Layer* osg_Group = new Layer();
    // Dump this group into the hierarchy
    parse->AddIntoSceneGraph(osg_Group);
    // Register for attachements
    int32 id;
    group.GetID(id);
    parse->AddToGroupList(id,osg_Group);
    return (void *) osg_Group;
}

//----------------------------------------------------------------------------
//
// Attach Reader Class
//
//----------------------------------------------------------------------------
attachRead::attachRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* attachRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgAttach group;
    if (!group.Read(buf))
        return NULL;
    // Create a new Performer group
    GeodeGroup* osg_Group = new GeodeGroup();
    // Dump this group into the hierarchy
    parse->AddIntoSceneGraph(osg_Group);
    // Register for attachements
    int32 id;
    group.GetID(id);
    parse->AddToGroupList(id,osg_Group);
    // This sets the parent ID for the current tile too
    int32 parentID;
    group.GetParentID(parentID);
    parse->SetParentID(parentID);
    return (void *) osg_Group;
}

//----------------------------------------------------------------------------
//
// Billboard Reader Class
//
//----------------------------------------------------------------------------
billboardRead::billboardRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* billboardRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    // Read in the txp billboard
    trpgBillboard bill;
    if (!bill.Read(buf))
        return NULL;

    // Create a group with a geometry beneath for the billboard
    GeodeGroup* osg_Group = new GeodeGroup();

    if (parse->inBillboard()) {
        // we don't allow anything under a billboard except geometry
        notify(WARN) << "TerraPage loader: can only have geometry nodes beneath a billboard.\n";
    }
    else
    {
        int type, mode;
        trpg3dPoint center, axis;

        if (bill.GetType(type) && bill.GetMode(mode) && bill.GetCenter(center) && bill.GetAxis(axis)) {
            Billboard* billboard = new Billboard();

            osg_Group->SetGeode(billboard);

            // save this state for processing of the geometry node(s)
            parse->setBillboardType(type);
            parse->setBillboardCenter(center);

            // set the axis
            // NOTE: Needs update, when the billboard implementation for
            // arbitrary axis is ready
            // billboard->setAxis(Vec3(axis.x, axis.y, axis.z));
            billboard->setAxis(Vec3(0.0f,0.0,1.0f) );
            billboard->setNormal(Vec3(0.0f,-1.0,0.0f));
            
            // the mode
            switch (mode) {
            case trpgBillboard::Axial:
                billboard->setMode(Billboard::AXIAL_ROT);
                break;
            case trpgBillboard::World:
                billboard->setMode(Billboard::POINT_ROT_WORLD);
                break;
            case trpgBillboard::Eye:
                billboard->setMode(Billboard::POINT_ROT_EYE);
                break;
            default:
                notify(WARN) << "TerraPage loader: Unknown billboard type.\n";
                notify(WARN) << "TerraPage loader: fell through case: " <<  __FILE__ << " " << __LINE__  << ".\n";
                break;
            }
        }
    }

    // Dump this group into the hierarchy
    parse->AddIntoSceneGraph(osg_Group);
    // Register
    int32 id;
    bill.GetID(id);
    parse->AddToGroupList(id,osg_Group);
    
    return (void *) osg_Group;
}

//----------------------------------------------------------------------------
//
// LOD Reader Class
//
//----------------------------------------------------------------------------
lodRead::lodRead (TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* lodRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgLod lod;
    if (!lod.Read(buf))
        return NULL;
    // Pull out the LOD data we'll need
    trpg3dPoint center;
    lod.GetCenter(center);
    double in,out,width;
    lod.GetLOD(in,out,width);
    double minRange = MIN(in,out);
    double maxRange = MAX(in,out+width);

    // Create a new Performer LOD
    LOD* osg_Lod = new LOD();
    Vec3 osg_Center;
    osg_Center[0] = center.x;  osg_Center[1] = center.y;  osg_Center[2] = center.z;
    osg_Lod->setCenter(osg_Center);
    osg_Lod->setRange(0,minRange, maxRange );
    
    // Our LODs are binary so we need to add a group under this LOD and attach stuff
    //  to that instead of the LOD
    GeodeGroup *osg_LodG = new GeodeGroup();
    osg_Lod->addChild(osg_LodG);
    
    // Dump this group into the hierarchy
    parse->AddIntoSceneGraph(osg_Lod);
    // Register for attachements
    int32 id;
    lod.GetID(id);
    // Add the sub-group to the group list, not the LOD
    parse->AddToGroupList(id,osg_LodG);
    return (void *) osg_LodG;
}

//----------------------------------------------------------------------------
//
// Model Reference Reader Class
//
//----------------------------------------------------------------------------
modelRefRead::modelRefRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void *modelRefRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgModelRef model;
    if (!model.Read(buf))
        return NULL;
    // Get the matrix and pfNode for the model
    int modelID;
    model.GetModel(modelID);
    float64 mat[16];
    model.GetMatrix(mat);
    Matrix osg_Mat(
        (float)mat[0], (float)mat[1], (float)mat[2], (float)mat[3],
        (float)mat[4], (float)mat[5], (float)mat[6], (float)mat[7],
        (float)mat[8], (float)mat[9], (float)mat[10],(float)mat[11],
        (float)mat[12],(float)mat[13],(float)mat[14],(float)mat[15]
        );
    
    // Note: Array check before you do this
    Node *osg_Model = NULL;
    std::vector<osg::ref_ptr<osg::Node> >*modelList = parse->GetModels();
    if( modelList->size() > size_t(modelID) )
    {
    //(*parse->GetModels())[modelID].get();
       osg_Model = (*modelList)[modelID].get();
    // Create the SCS and position the model
    if (osg_Model) {
        MatrixTransform *scs = new MatrixTransform();
        scs->setMatrix(osg_Mat);
        scs->addChild(osg_Model);
        // Add the SCS to the hierarchy
        Group *top = parse->GetCurrTop();
        if (top)
            top->addChild(scs);
    }
    }
    return (void *) 1;
}

//----------------------------------------------------------------------------
//
// Tile Header Reader Class
//
//----------------------------------------------------------------------------
tileHeaderRead::tileHeaderRead(TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* tileHeaderRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgTileHeader *tileHead = parse->GetTileHeaderRef();
    if (!tileHead->Read(buf))
        return NULL;
    parse->LoadLocalMaterials();
    return (void *) 1;
}


//----------------------------------------------------------------------------
//
// light Reader Class
//
//----------------------------------------------------------------------------
lightRead::lightRead (TrPageParser *in_parse)
{
    parse = in_parse;
}

//----------------------------------------------------------------------------
void* lightRead::Parse(trpgToken /*tok*/,trpgReadBuffer &buf)
{
    trpgLight light;
    if (!light.Read(buf))
        return NULL;
	int attr_index;
	light.GetAttrIndex(attr_index);

	DefferedLightAttribute& dla = parse->GetLightAttribute(attr_index); 
	osgSim::LightPointNode* node = dla.lightPoint.get();

	uint32 nvert;
	light.GetNumVertices(nvert);

	if( node->getLightPoint(0)._sector.valid() ) // osgSim::LigthPoint is a must
	{
		for(unsigned int i = 0; i < nvert; i++){
			trpg3dPoint pt;
			light.GetVertex(i, pt);
			osg::Matrix matrix;
	//        matrix.makeTranslate(pt.x,pt.y,pt.z);
			matrix.makeRotate(osg::Quat(0.0,dla.attitude));
			matrix.setTrans(pt.x,pt.y,pt.z);
			osg::MatrixTransform* trans = new osg::MatrixTransform();
			trans->setMatrix(matrix);
			trans->addChild(node);
			parse->AddIntoSceneGraph(trans);
		}
	}
	else { //Fall back to osg::Points
	    Vec3Array* vertices = new Vec3Array(nvert);
	    Vec4Array* colors = new Vec4Array(nvert);

		for(unsigned int i = 0; i < nvert; i++){
			trpg3dPoint pt;
			light.GetVertex(i, pt);
			(*vertices)[i] = osg::Vec3(pt.x,pt.y,pt.z);
			(*colors)[i] = node->getLightPoint(0)._color;
		}

		osg::Geometry* geom = new osg::Geometry();
        geom->addPrimitiveSet(new DrawArrays(PrimitiveSet::POINTS,0,nvert));
		geom->setVertexArray(vertices);
		geom->setColorArray(colors);
		geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

		geom->setUseDisplayList(false);
		geom->setStateSet(dla.fallback.get());

	    GeodeGroup *top = parse->GetCurrTop();
		Geode *geode = top->GetGeode();
		geode->addDrawable(geom);
		
	}

    return (void *) 1;
}


/* ********************************** */

//----------------------------------------------------------------------------
// Constructor for scene graph parser
TrPageParser::TrPageParser(TrPageArchive* parent)
{
    parent_ = parent;
    currTop = NULL;
    top = NULL;
    layerDepth = 0;
    currLayer = NULL;
    in_billboard = false;
    
    // Register the readers
    AddCallback(TRPG_GEOMETRY,new geomRead(this));
    AddCallback(TRPG_GROUP,new groupRead(this));
    AddCallback(TRPG_ATTACH,new attachRead(this));
    AddCallback(TRPG_BILLBOARD,new billboardRead(this));
    AddCallback(TRPG_LOD,new lodRead(this));
    AddCallback(TRPG_MODELREF,new modelRefRead(this));
    AddCallback(TRPG_LAYER,new layerRead(this));
    AddCallback(TRPGTILEHEADER,new tileHeaderRead(this));
    AddCallback(TRPG_LIGHT,new lightRead(this));
//    AddCallback(TRPGLIGHTATTR,new lightAttrRead(this));
//    AddCallback(TRPGLIGHTTABLE,new lightTableRead(this));
}

//----------------------------------------------------------------------------
// Destructor for scene graph parser
TrPageParser::~TrPageParser()
{
}

//----------------------------------------------------------------------------
// Return a reference to the Tile Header
// Doesn't do much if you haven't just read a tile
trpgTileHeader *TrPageParser::GetTileHeaderRef()
{
    return &tileHead;
}

//----------------------------------------------------------------------------
// Parse a buffer and return a (chunk of) Performer
//  scene graph.
Group *TrPageParser::ParseScene(trpgReadBuffer &buf,vector<ref_ptr<StateSet> > &in_mat,vector<ref_ptr<Node> > &in_model)
{
    top = currTop = new GeodeGroup();
    materials = &in_mat;
    local_materials.clear();
    models = &in_model;
    parentID = -1;
    
    // All the setup is handled in the constructor.
    // Just parse and return the top
    if (!Parse(buf))
    {
        notify(WARN) << "trpgFPParser::ParseScene failed to parse tile.\n";
        return NULL;
    }
    
    Group *ret = top;
    top = currTop = NULL;
    return ret;
}

//----------------------------------------------------------------------------
// Read local materials
void TrPageParser::LoadLocalMaterials()
{
    // new to 2.0 LOCAL materials
    trpgrImageHelper image_helper(parent_->GetEndian(),parent_->getDir(),*parent_->GetMaterialTable(),*parent_->GetTexTable());
    trpgTileHeader* tile_head = GetTileHeaderRef();

    int n_materials;
    tile_head->GetNumLocalMaterial(n_materials);

    int n_mat;
    tile_head->GetNumMaterial(n_mat);

    local_materials.clear();
    local_materials.resize(n_materials);
    {
        for (int i = 0; i < n_materials; i++)
        {
            StateSet* osg_state_set = new StateSet;
            
            trpgLocalMaterial locmat;
            tile_head->GetLocalMaterial(i,locmat);
            
            const trpgMaterial* mat;
            const trpgTexture *tex;

            int32 size;
            image_helper.GetImageInfoForLocalMat(&locmat, &mat,&tex,size);

            int num_tex;
            mat->GetNumTexture(num_tex);
            for (int texNo = 0 ; texNo < num_tex; ++texNo)
            {
                int texId;
                trpgTextureEnv texEnv;
                mat->GetTexture(texNo,texId,texEnv);

                // Set up texture environment
                TexEnv       *osg_texenv       = new TexEnv();
                int32 te_mode;
                texEnv.GetEnvMode(te_mode);
                switch( te_mode )
                {
                case trpgTextureEnv::Alpha :
                    osg_texenv->setMode(TexEnv::REPLACE);
                break;
                case trpgTextureEnv::Decal:
                    osg_texenv->setMode(TexEnv::DECAL);
                    break;
                case trpgTextureEnv::Blend :
                    osg_texenv->setMode(TexEnv::BLEND);
                    break;
                case trpgTextureEnv::Modulate :
                    osg_texenv->setMode(TexEnv::MODULATE);
                    break;
                }
            
                osg_state_set->setTextureAttribute(texNo,osg_texenv);

                image_helper.GetNthImageInfoForLocalMat(&locmat, texNo, &mat,&tex,size);
            
                trpgTexture::ImageMode mode;
                tex->GetImageMode(mode);
                Texture2D* osg_texture = 0L;
                if(mode == trpgTexture::Template) 
                    osg_texture = GetTemplateTexture(image_helper,&locmat, tex, texNo);
                else if(mode == trpgTexture::Local) 
                    osg_texture = GetLocalTexture(image_helper,tex);
                else if(mode == trpgTexture::Global)
                    osg_texture = parent_->getGlobalTexture(texId);

                if(osg_texture)
                {
                    if(osg_texture->getImage())
                    {
                        GLenum gltype = osg_texture->getImage()->getPixelFormat();
                        if( gltype == GL_RGBA || gltype == GL_LUMINANCE_ALPHA )
                        {
                            osg_state_set->setMode(GL_BLEND,StateAttribute::ON);
                            osg_state_set->setRenderingHint(StateSet::TRANSPARENT_BIN);
                        }
                    }
                    else
                    {
                        notify(WARN) << "No image\n";
                    }
                    osg_state_set->setTextureAttributeAndModes(texNo,osg_texture, StateAttribute::ON);

                    int wrap_s, wrap_t;   
                    texEnv.GetWrap(wrap_s, wrap_t);
                    osg_texture->setWrap(Texture2D::WRAP_S, wrap_s == trpgTextureEnv::Repeat ? Texture2D::REPEAT: Texture2D::CLAMP );
                    osg_texture->setWrap(Texture2D::WRAP_T, wrap_t == trpgTextureEnv::Repeat ? Texture2D::REPEAT: Texture2D::CLAMP );
                }
                else
                {
                    notify(WARN) << "No texture\n";
                }
            }

            Material     *osg_material     = new Material;
            
            float64 alpha;
            mat->GetAlpha(alpha);
            
            trpgColor color;
            mat->GetAmbient(color);
            osg_material->setAmbient( Material::FRONT_AND_BACK , 
                Vec4(color.red, color.green, color.blue, alpha));
            mat->GetDiffuse(color);
            osg_material->setDiffuse(Material::FRONT_AND_BACK , 
                Vec4(color.red, color.green, color.blue, alpha));
            
            mat->GetSpecular(color);
            osg_material->setSpecular(Material::FRONT_AND_BACK , 
                Vec4(color.red, color.green, color.blue, alpha));
            mat->GetEmission(color);
            osg_material->setEmission(Material::FRONT_AND_BACK , 
                Vec4(color.red, color.green, color.blue, alpha));
            
            float64 shinines;
            mat->GetShininess(shinines);
            osg_material->setShininess(Material::FRONT_AND_BACK , (float)shinines);
            
            osg_material->setAlpha(Material::FRONT_AND_BACK ,(float)alpha);
            osg_state_set->setAttributeAndModes(osg_material, StateAttribute::ON);
            
            if( alpha < 1.0f )
            {
                osg_state_set->setMode(GL_BLEND,StateAttribute::ON);
                osg_state_set->setRenderingHint(StateSet::TRANSPARENT_BIN);
            }
            
            
        /* This controls what alpha values in a texture mean.  It can take the values:
            None,Always,Equal,GreaterThanOrEqual,GreaterThan,
            LessThanOrEqual,LessThan,Never,NotEqual
        */
            int alphaFunc;
            mat->GetAlphaFunc(alphaFunc);
            if( alphaFunc>=GL_NEVER && alphaFunc<=GL_ALWAYS)
            {
                float64 ref;
                mat->GetAlphaRef(ref);
                AlphaFunc *osg_alpha_func = new AlphaFunc;
                osg_alpha_func->setFunction((AlphaFunc::ComparisonFunction)alphaFunc,(float)ref);
                osg_state_set->setAttributeAndModes(osg_alpha_func, StateAttribute::ON);
            }

            int cullMode;
            mat->GetCullMode(cullMode);
            
            // Culling mode in txp means opposite from osg i.e. Front-> show front face
            if( cullMode != trpgMaterial::FrontAndBack)
            {
                CullFace* cull_face = new CullFace;
                switch (cullMode)
                {
                case trpgMaterial::Front:
                    cull_face->setMode(CullFace::BACK);
                    break;
                case trpgMaterial::Back:
                    cull_face->setMode(CullFace::FRONT);
                    break;
                }
                osg_state_set->setAttributeAndModes(cull_face, StateAttribute::ON);
            }
            local_materials[i] = osg_state_set;
        }
     }
}
//----------------------------------------------------------------------------
// Start Children
// This is called when the parser hits a push.
// We'll want to make the node it's handing us the "top" node
bool TrPageParser::StartChildren(void *in_node)
{
    // Make a node
    GeodeGroup *node = (GeodeGroup *)in_node;

    // If we are under layer we need to drop all groups. Then
    // the current parent is still the layer node
    if (layerDepth==0)
    {
        // Set it as current parent
        currTop = node;
    }

    // 
    // track whether we are under a billboard in the scene graph
    //
    if (is_billboard(currTop))
        in_billboard = true;

    // Chek if it's layer 
    if (node && (in_node != (void*)1) && (strcasecmp(node->className(),"Layer") == 0))
    {
        if (layerDepth==0)
            currLayer = (Layer*)node;
        layerDepth++;
    }
    
    return true;
}

//----------------------------------------------------------------------------
// This is called when the parser hits a pop.
// We'll want to look on the parent list (in trpgSceneParser)
// for the parent above the current one.
// If there isn't one, we'll just stick things in our top group.
bool TrPageParser::EndChildren(void *in_node)
{
    // Chek if it's layer
    osg::Node* node = (osg::Node*)in_node;
    if (node && (in_node != (void*)1) && (strcasecmp(node->className(),"Layer") == 0))
    {
        if (layerDepth > 0)
            layerDepth--;
        else
        {
            notify(WARN) << "Layer depth < 0 ???\n";
        }
        if (layerDepth==0)
        {
            currLayer = NULL;
            for (unsigned int i = 0; i < deadNodes.size(); i++)
            {
                osg::ref_ptr<Node> deadNode = deadNodes[i];
            }
            deadNodes.clear();
        }
    }

    // 
    // track whether we are under a billboard in the scene graph
    //
    if (is_billboard(node))
        in_billboard = false;

    // if we are under layer all the groups are dropped out, so no need to do this
    if (layerDepth==0)
    {
        // Get the parent above the current one
        int pos = parents.size()-2;
        if (pos < 0)
        {
            // Nothing above the current one.  Fall back on our top group
            currTop = top;
        }
        else
        {
            currTop = (GeodeGroup *)parents[pos];
        }
    }

    return true;
}

//----------------------------------------------------------------------------
// Return the current top node
GeodeGroup *TrPageParser::GetCurrTop()
{
    if (currTop)
    {
        return currTop;
    }
    else
    {
        return top;
    }
}

//----------------------------------------------------------------------------
// Add the given pfGroup to the group list at position ID
bool TrPageParser::AddToGroupList(int ID,GeodeGroup *group)
{
    // we dont do this if we are under layer
    if (layerDepth==0)
    {
        // Note: check bounds
        groupList[ID] = group;
    }
    
    return true;
}

//----------------------------------------------------------------------------
// Initialize the group list
void TrPageParser::SetMaxGroupID(int maxGroupID)
{
    //notify(WARN) << "trpgFPParser: max group ID = " << maxGroupID << std::endl;
    // Initialize the group list with -1's
    groupList.resize(0);
    // Note: Fix this
    for (int i=0;i<maxGroupID;i++)
        groupList.push_back(NULL);
}

//----------------------------------------------------------------------------
// Use this to add nodes into the scenegraph
void TrPageParser::AddIntoSceneGraph(osg::Node* node)
{
    // chek first if we are under some layer
    if (layerDepth)
    {
        deadNodes.push_back(node);
    }
    else
    {
        Group *top = GetCurrTop();
        if (top)
            top->addChild(node);
    }
}

//----------------------------------------------------------------------------
// Return the current layer node (also used during parsing)
Layer *TrPageParser::GetCurrLayer()
{
    return currLayer;
}

DefferedLightAttribute& TrPageParser::GetLightAttribute(int idx)
{
	return parent_->GetLightAttribute(idx);
}
