#include <osg/Notify>
#include <osg/Group>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/Texture2D>
#include <osg/Material>
#include <osg/TexEnv>
#include <osg/ref_ptr>
#include <osg/MatrixTransform>

#include <osgDB/Registry>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include <osgDB/ReadFile>

#include <osgUtil/TriStripVisitor>

//MIKEC debug only for PrintVisitor
#include <osg/NodeVisitor>


#include "file.h"
#include "mesh.h"
#include "material.h"
#include "vector.h"
#include "matrix.h"
#include "node.h"
#include "quat.h"
#include "readwrite.h"

#include <stdlib.h>
#include <string.h>

#include <set>
#include <map>
#include <iostream>
#include <sstream>

using namespace std;
using namespace osg;

class PrintVisitor : public NodeVisitor
{

   public:
   
        PrintVisitor(std::ostream& out):
            NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
            _out(out)
        {
            _indent = 0;
            _step = 4;
        }
        
        inline void moveIn() { _indent += _step; }
        inline void moveOut() { _indent -= _step; }
        inline void writeIndent() 
        {
            for(int i=0;i<_indent;++i) _out << " ";
        }
                
        virtual void apply(Node& node)
        {
            moveIn();
            writeIndent(); _out << node.className() <<std::endl;
            traverse(node);
            moveOut();
        }

        virtual void apply(Geode& node)         { apply((Node&)node); }
        virtual void apply(Billboard& node)     { apply((Geode&)node); }
        virtual void apply(LightSource& node)   { apply((Group&)node); }
        virtual void apply(ClipNode& node)      { apply((Group&)node); }
        
        virtual void apply(Group& node)         { apply((Node&)node); }
        virtual void apply(Transform& node)     { apply((Group&)node); }
        virtual void apply(Projection& node)    { apply((Group&)node); }
        virtual void apply(Switch& node)        { apply((Group&)node); }
        virtual void apply(LOD& node)           { apply((Group&)node); }

   protected:

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

        std::ostream& _out;
        int _indent;
        int _step;
};

class ReaderWriter3DS : public osgDB::ReaderWriter
{
    public:

        ReaderWriter3DS();

        virtual const char* className() const { return "3DS Auto Studio Reader"; }

        virtual ReadResult readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const;
        virtual ReadResult readNode(std::istream& fin, const Options* options) const;

    protected:
        ReadResult constructFrom3dsFile(Lib3dsFile *f,const std::string& filename, const Options* options) const;


        class ReaderObject
        {
        public:
            ReaderObject();
        
            typedef std::map<std::string,osg::StateSet*> StateSetMap;
            typedef std::vector<int> FaceList;
            typedef std::map<std::string,osg::StateSet*> GeoStateMap;

            osg::Texture2D* createTexture(Lib3dsTextureMap *texture,const char* label,bool& transparancy, const osgDB::ReaderWriter::Options* options);
            osg::StateSet* createStateSet(Lib3dsMaterial *materials, const osgDB::ReaderWriter::Options* options);
            osg::Drawable* createDrawable(Lib3dsMesh *meshes,FaceList& faceList, Lib3dsMatrix* matrix);

            std::string _directory;
            bool _useSmoothingGroups;
            bool _usePerVertexNormals;

            // MIKEC
            osg::Node* processMesh(StateSetMap& drawStateMap,osg::Group* parent,Lib3dsMesh* mesh, Lib3dsMatrix* matrix);
            osg::Node* processNode(StateSetMap drawStateMap,Lib3dsFile *f,Lib3dsNode *node);
        };
};

// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(3ds, ReaderWriter3DS)

ReaderWriter3DS::ReaderWriter3DS()
{
    supportsExtension("3ds","3D Studio model format");

    setByteOrder();

#if 0
    osg::notify(osg::NOTICE)<<"3DS reader sizes:"<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsBool)="<<sizeof(Lib3dsBool)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsByte)="<<sizeof(Lib3dsByte)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsWord)="<<sizeof(Lib3dsWord)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsDword)="<<sizeof(Lib3dsDword)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsIntb)="<<sizeof(Lib3dsIntb)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsIntw)="<<sizeof(Lib3dsIntw)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsIntd)="<<sizeof(Lib3dsIntd)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsFloat)="<<sizeof(Lib3dsFloat)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsDouble)="<<sizeof(Lib3dsDouble)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsVector)="<<sizeof(Lib3dsVector)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsTexel)="<<sizeof(Lib3dsTexel)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsQuat)="<<sizeof(Lib3dsQuat)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsMatrix)="<<sizeof(Lib3dsMatrix)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsRgb)="<<sizeof(Lib3dsRgb)<<std::endl;
    osg::notify(osg::NOTICE)<<"  sizeof(Lib3dsRgba)="<<sizeof(Lib3dsRgba)<<std::endl;
#endif

}

ReaderWriter3DS::ReaderObject::ReaderObject()
{
    _useSmoothingGroups = true;
    _usePerVertexNormals = true;
}


/**
    These print methods for 3ds hacking
*/
void pad(int level) {
    for(int i=0;i<level;i++) std::cout<<"  ";
}
void print(Lib3dsMesh *mesh,int level);
void print(Lib3dsUserData *user,int level);
void print(Lib3dsNodeData *user,int level);
void print(Lib3dsObjectData *object,int level);
void print(Lib3dsNode *node, int level);

void print(Lib3dsMatrix matrix,int level) {
    pad(level); cout << matrix[0][0] <<" "<< matrix[0][1] <<" "<< matrix[0][2] <<" "<< matrix[0][3] << endl;
    pad(level); cout << matrix[1][0] <<" "<< matrix[1][1] <<" "<< matrix[1][2] <<" "<< matrix[1][3] << endl;
    pad(level); cout << matrix[2][0] <<" "<< matrix[2][1] <<" "<< matrix[2][2] <<" "<< matrix[2][3] << endl;
    pad(level); cout << matrix[3][0] <<" "<< matrix[3][1] <<" "<< matrix[3][2] <<" "<< matrix[3][3] << endl;
}
void print(Lib3dsMesh *mesh,int level) {
    if (mesh) {
        pad(level); cout << "mesh name " << mesh->name  << endl;
        print(mesh->matrix,level);
    } else {
        pad(level); cout << "no mesh " << endl;
    }
}
void print(Lib3dsUserData *user,int level) {
    if (user) {
        pad(level); cout << "user data" << endl;
        //print(user->mesh,level+1);
    } else {
        pad(level); cout << "no user data" << endl;
    }
}
void print(Lib3dsNodeData *node,int level) {
    if (node) {
        pad(level); cout << "node data:" << endl;
        // nodedata->object is a union of many types
        print((Lib3dsObjectData *)&node->object,level+1);
    } else {
        pad(level); cout << "no user data" << endl;
    }
}
void print(Lib3dsObjectData *object,int level) {
    if (object) {
        pad(level); cout << "objectdata instance [" << object->instance << "]" << endl;
        pad(level); cout << "pivot     " << object->pivot[0] <<" "<< object->pivot[1] <<" "<< object->pivot[2] << endl;
        pad(level); cout << "pos       " << object->pos[0] <<" "<< object->pos[1] <<" "<< object->pos[2] << endl;
        pad(level); cout << "scl       " << object->scl[0] <<" "<< object->scl[1] <<" "<< object->scl[2] << endl;
        pad(level); cout << "rot       " << object->rot[0] <<" "<< object->rot[1] <<" "<< object->rot[2] <<" "<< object->rot[3] << endl;
    } else {
        pad(level); cout << "no object data" << endl;
    }
}

void print(Lib3dsNode *node, int level) {
    
    pad(level); cout << "node name [" << node->name << "]" << endl;
    pad(level); cout << "node id    " << node->node_id << endl;
    pad(level); cout << "node parent id " << node->parent_id << endl;
    pad(level); cout << "node matrix:" << endl;
    print(node->matrix,level+1);
    print(&node->data,level);
    print(&node->user,level);
    

    for(Lib3dsNode *child=node->childs; child; child=child->next) {
        print(child,level+1);
    }

}

void copyLib3dsMatrixToOsgMatrix(osg::Matrix& osg_matrix, const Lib3dsMatrix lib3ds_matrix)
{
    osg_matrix.set(
        lib3ds_matrix[0][0],lib3ds_matrix[0][1],lib3ds_matrix[0][2],lib3ds_matrix[0][3],
        lib3ds_matrix[1][0],lib3ds_matrix[1][1],lib3ds_matrix[1][2],lib3ds_matrix[1][3],
        lib3ds_matrix[2][0],lib3ds_matrix[2][1],lib3ds_matrix[2][2],lib3ds_matrix[2][3],
        lib3ds_matrix[3][0],lib3ds_matrix[3][1],lib3ds_matrix[3][2],lib3ds_matrix[3][3]); 
}

// Transforms points by matrix if 'matrix' is not NULL
// Creates a Geode and Geometry (as parent,child) and adds the Geode to 'parent' parameter iff 'parent' is non-NULL
// Returns ptr to the Geode
osg::Node* ReaderWriter3DS::ReaderObject::processMesh(StateSetMap& drawStateMap,osg::Group* parent,Lib3dsMesh* mesh, Lib3dsMatrix* matrix) {
    typedef std::vector<int> FaceList;
    typedef std::map<std::string,FaceList> MaterialFaceMap;
    MaterialFaceMap materialFaceMap;
    for (unsigned int i=0; i<mesh->faces; ++i)
    {
        materialFaceMap[mesh->faceL[i].material].push_back(i);
    }

    if (materialFaceMap.empty())
    {
        osg::notify(osg::NOTICE)<<"Warning : no triangles assigned to mesh '"<<mesh->name<<"'"<< std::endl;
        return NULL;
    }
    else
    {

        osg::Geode* geode = new osg::Geode;
        geode->setName(mesh->name);

        for(MaterialFaceMap::iterator itr=materialFaceMap.begin();
            itr!=materialFaceMap.end();
            ++itr)
        {
            FaceList& faceList = itr->second;
            
            if (_useSmoothingGroups)
            {

                typedef std::map<int,FaceList> SmoothingFaceMap;
                SmoothingFaceMap smoothingFaceMap;
                for (FaceList::iterator flitr=faceList.begin();
                     flitr!=faceList.end();
                     ++flitr)
                {
                    smoothingFaceMap[mesh->faceL[*flitr].smoothing].push_back(*flitr);
                }

                for(SmoothingFaceMap::iterator sitr=smoothingFaceMap.begin();
                    sitr!=smoothingFaceMap.end();
                    ++sitr)
                {
                    // each smoothing group to have its own geom 
                    // to ensure the vertices on adjacent groups
                    // don't get shared.
                    FaceList& smoothFaceMap = sitr->second;

                    osg::Drawable* drawable = createDrawable(mesh,smoothFaceMap,matrix);
                    if (drawable)
                    {
                        drawable->setStateSet(drawStateMap[itr->first]);
                        geode->addDrawable(drawable);
                    }
                }
            }
            else // ignore smoothing groups.
            {
                osg::Drawable* drawable = createDrawable(mesh,faceList,matrix);
                if (drawable)
                {
                    drawable->setStateSet(drawStateMap[itr->first]);
                    geode->addDrawable(drawable);
                }
            }
        }

        if (parent) parent->addChild(geode);
        return geode;
    }
}


/**
How to cope with pivot points in 3ds (short version)

  All object coordinates in 3ds are stored in world space, this is why you can just rip out the meshes and use/draw them without meddeling further
  Unfortunately, this gets a bit wonky with objects with pivot points (conjecture: PP support is retro fitted into the .3ds format and so doesn't fit perfectly?)

  Objects with pivot points have a position relative to their PP, so they have to undergo this transform:

    invert the mesh matrix, apply this matrix to the object. This puts the object back at the origin
    Transform the object by the nodes (negative) pivot point coords, this puts the PP at the origin
    Transform the node by the node matrix, which does the orientation about the pivot point, (and currently) transforms the object back by a translation to the PP.

  */
osg::Node* ReaderWriter3DS::ReaderObject::processNode(StateSetMap drawStateMap,Lib3dsFile *f,Lib3dsNode *node) {
    
    osg::Group* group=NULL;// created on demand if we find we have children to group together
    

    // Handle all children of this node for hierarchical assemblies
    Lib3dsNode *p;
    for (p=node->childs; p!=0; p=p->next) {
        if (!group) {
            group =new osg::Group;
            if (strcmp(node->name, "$$$DUMMY") == 0) {
                group->setName(node->data.object.instance);
            } else {
                group->setName(node->name);
            }
        }
        group->addChild(processNode(drawStateMap,f,p));
    }
    
    // MIKEC: possible BUG - 3ds files APPEAR to enforce unqiue names, so this is OK, but I am not 100% sure
    // failed to find any alternative way to do it in lib3ds though, and lib3ds player.c application uses this method
    Lib3dsMesh *mesh=lib3ds_file_mesh_by_name(f,node->name);
    if (mesh) {
        Lib3dsObjectData* object=&node->data.object;
        Lib3dsMatrix mesh_inverse;
        osg::Matrix osgmatrix;
        
        lib3ds_matrix_copy(mesh_inverse,mesh->matrix); 
        lib3ds_matrix_inv(mesh_inverse);
        
        Lib3dsMatrix M,N;
        lib3ds_matrix_identity(M);
        lib3ds_matrix_identity(N);
        lib3ds_matrix_copy(M,node->matrix);
        N[3][0]=-object->pivot[0];
        N[3][1]=-object->pivot[1];
        N[3][2]=-object->pivot[2];

        bool pivoted=false;
        if ( (object->pivot[0]!=0.0) || (object->pivot[1]!=0.0) || (object->pivot[2]!=0.0) ) {
            pivoted=true; // there is a pivot point, so we must use it
        }

        /*cout<<"M"<<node->name<<endl;
        print(M,0);
        cout<<"N"<<endl;
        print(N,0);*/

        if (pivoted) {
            // Transform object's pivot point to the world origin
            osg::MatrixTransform* T=new osg::MatrixTransform;
            copyLib3dsMatrixToOsgMatrix(osgmatrix, N);
            T->setMatrix(osgmatrix);
            T->setName("3DSPIVOTPOINT: Translate pivotpoint to (world) origin");
            //cout<<"Translation for "<<node->name<<" is "<<osgmatrix<<endl;

            // rotate about "origin" (after the transform this is the world origin)
            // BUG this matrix also contains the translation to the pivot point - we should plit that out (maybe)
            osg::MatrixTransform* R=new osg::MatrixTransform;
            copyLib3dsMatrixToOsgMatrix(osgmatrix, M);
            R->setMatrix(osgmatrix);
            R->setName("3DSPIVOTPOINT: Rotate");
                
            /*
            cout<<"Rotation for "<<node->name<<" is "<<osgmatrix<<endl;
            osg::Quat quat;
            quat.set(osgmatrix);
            osg::Vec3 axis;
            float angle;
            quat.getRotate(angle,axis);
            cout<<"which is "<<osg::RadiansToDegrees(angle)<<" degrees around "<<axis<<endl;
            */
            /*
            printf("%s---------------\n",node->name);
            printf("mesh matrix :\n");         print(mesh->matrix,1);
            printf("mesh inverse:\n");         print(mesh_inverse,1);
            printf("node matrix :\n");         print(matrix,1);
            printf("pivot=%f,%f,%f pos=%f,%f,%f\n",object->pivot[0],object->pivot[1],object->pivot[2],object->pos[0],object->pos[1],object->pos[2]);
            */

            if (group) {
                // Always in reverse order...
                group->addChild(R); 
                R->addChild(T);
                processMesh(drawStateMap,T,mesh,&mesh_inverse); // creates geometry under modifier node
                return group;
            } else {
                // We are a pivoted node with no children
                R->addChild(T);
                processMesh(drawStateMap,T,mesh,&mesh_inverse); // creates geometry under modifier node
                return R;
            }
        } else {
            if(group) {
                // add our geometry to group (where our children already are)
                processMesh(drawStateMap,group,mesh,NULL); // creates geometry under modifier node
                return group;
            } else {
                // didnt use group for children
                // return a ptr directly to the Geode for this mesh
                return processMesh(drawStateMap,NULL,mesh,NULL); 
            }    
        }

    } else {
        // no mesh for this node - probably a camera or something of that persuasion
        //cout << "no mesh for object " << node->name << endl;
        return group; // we have no mesh, but we might have children
    }
}

osgDB::ReaderWriter::ReadResult ReaderWriter3DS::readNode(std::istream& fin,  const osgDB::ReaderWriter::Options* options) const
{
    osgDB::ReaderWriter::ReadResult result = ReadResult::FILE_NOT_HANDLED;

    std::string optFileName = "";
    if (options)
    {
        optFileName = options->getPluginStringData("STREAM_FILENAME");
    }

    Lib3dsFile *f = lib3ds_stream_load((iostream *) &fin);
    if (f)
    {
        result = constructFrom3dsFile(f,optFileName,options);
        lib3ds_file_free(f);
    }

    return(result);
}

osgDB::ReaderWriter::ReadResult ReaderWriter3DS::readNode(const std::string& file, const osgDB::ReaderWriter::Options* options) const
{
    osgDB::ReaderWriter::ReadResult result = ReadResult::FILE_NOT_HANDLED;

    std::string ext = osgDB::getLowerCaseFileExtension(file);
    if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;

    std::string fileName = osgDB::findDataFile( file, options );
    if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;

    Lib3dsFile *f = lib3ds_file_load(fileName.c_str(),options);

    if (f)
    {
        osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
        local_opt->getDatabasePathList().push_front(osgDB::getFilePath(fileName));

        result = constructFrom3dsFile(f,file,local_opt.get());
        lib3ds_file_free(f);
    }

    return result;
}

osgDB::ReaderWriter::ReadResult ReaderWriter3DS::constructFrom3dsFile(Lib3dsFile *f,const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
{
    if (f==NULL) return ReadResult::FILE_NOT_HANDLED;

    // MIKEC
    // This appears to build the matrix structures for the 3ds structure
    // It wasn't previously necessary because all the meshes are stored in world coordinates
    // but is VERY necessary if you want to use pivot points...
    lib3ds_file_eval(f,0.0f); // second param is time 't' for animated files

    ReaderObject reader;

    reader._directory = options->getDatabasePathList().empty() ? osgDB::getFilePath(fileName) : options->getDatabasePathList().front();

    osg::Group* group = new osg::Group;
    group->setName(fileName);

    ReaderObject::StateSetMap drawStateMap;

    for (Lib3dsMaterial *mat=f->materials; mat; mat=mat->next)
    {
        drawStateMap[mat->name] = reader.createStateSet(mat, options);
    }
    
    if (osg::getNotifyLevel()>=osg::INFO)
    {
        int level=0;
        std::cout << "NODE TRAVERSAL of 3ds file "<<f->name<<std::endl;
        for(Lib3dsNode *node=f->nodes; node; node=node->next) {
            print(node,level+1);
        }
        std::cout << "MESH TRAVERSAL of 3ds file "<<f->name<<std::endl;
        for(Lib3dsMesh *mesh=f->meshes; mesh; mesh=mesh->next) {
            print(mesh,level+1);
        }
    }

    // We can traverse by meshes (old method, broken for pivot points, but otherwise works), or by nodes (new method, not so well tested yet)
    // if your model is broken, especially wrt object positions try setting this flag. If that fixes it,
    // send me the model
    bool traverse_nodes=false;
    
    // MIKEC: have found 3ds files with NO node structure - only meshes, for this case we fall back to the old traverse-by-meshes code
    // Loading and re-exporting these files from 3DS produces a file with correct node structure, so perhaps these are not 100% conformant?
    if (f->nodes == NULL) {
        osg::notify(osg::WARN)<<"Warning: in 3ds loader: file has no nodes, traversing by meshes instead"<< std::endl;
        traverse_nodes=true;
    }

    if (traverse_nodes) { // old method
        for (Lib3dsMesh *mesh=f->meshes; mesh; mesh=mesh->next) {
            reader.processMesh(drawStateMap,group,mesh,NULL);
        }
    } else { // new method
        for(Lib3dsNode *node=f->nodes; node; node=node->next) {
            group->addChild(reader.processNode(drawStateMap,f,node));
        }
    } 

    if (osg::getNotifyLevel()>=osg::INFO)
    {
        osg::notify(osg::NOTICE) << "Final OSG node structure looks like this:"<< endl;
        PrintVisitor pv(osg::notify(osg::NOTICE));
        group->accept(pv);
    }    
    
    return group;
}

/**
use matrix to pretransform geometry, or NULL to do nothing
*/
osg::Drawable*   ReaderWriter3DS::ReaderObject::createDrawable(Lib3dsMesh *m,FaceList& faceList,Lib3dsMatrix *matrix)
{

    osg::Geometry* geom = new osg::Geometry;

    unsigned int i;
    
    std::vector<int> orig2NewMapping;
    for(i=0;i<m->points;++i) orig2NewMapping.push_back(-1);

    unsigned int noVertex=0;
    FaceList::iterator fitr;
    for (fitr=faceList.begin();
        fitr!=faceList.end();
        ++fitr)
    {

        Lib3dsFace& face = m->faceL[*fitr];

        if (orig2NewMapping[face.points[0]]<0)
            orig2NewMapping[face.points[0]] = noVertex++;

        if (orig2NewMapping[face.points[1]]<0)
            orig2NewMapping[face.points[1]] = noVertex++;

        if (orig2NewMapping[face.points[2]]<0)
            orig2NewMapping[face.points[2]] = noVertex++;

    }

    // create vertices.
    
    osg::Vec3Array* osg_coords = new osg::Vec3Array(noVertex);
    geom->setVertexArray(osg_coords);

    Lib3dsVector c;
       
    for (i=0; i<m->points; ++i)
    {
        if (orig2NewMapping[i]>=0)
        {
            if (matrix)
            {
                lib3ds_vector_transform(c,*matrix, m->pointL[i].pos);
                (*osg_coords)[orig2NewMapping[i]].set(c[0],c[1],c[2]);
            }
            else
            {
                // original no transform code.
                (*osg_coords)[orig2NewMapping[i]].set(m->pointL[i].pos[0],m->pointL[i].pos[1],m->pointL[i].pos[2]);
            }
        }
    }

    // create texture coords if needed.
    if (m->texels>0)
    {
        if (m->texels==m->points)
        {
            osg::Vec2Array* osg_tcoords = new osg::Vec2Array(noVertex);
            geom->setTexCoordArray(0,osg_tcoords);
            for (i=0; i<m->texels; ++i)
            {
                if (orig2NewMapping[i]>=0) (*osg_tcoords)[orig2NewMapping[i]].set(m->texelL[i][0],m->texelL[i][1]);
            }
        }
        else
        {
            osg::notify(osg::WARN)<<"Warning: in 3ds loader m->texels ("<<m->texels<<") != m->points ("<<m->points<<")"<< std::endl;
        }
    }

    // create normals.
    if (_usePerVertexNormals)
    {
        osg::Vec3Array* osg_normals = new osg::Vec3Array(noVertex);
        
        // initialize normal list to zero's.
        for (i=0; i<noVertex; ++i)
        {
            (*osg_normals)[i].set(0.0f,0.0f,0.0f);
        }

        for (fitr=faceList.begin();
            fitr!=faceList.end();
            ++fitr)
        {
            Lib3dsFace& face = m->faceL[*fitr];

            (*osg_normals)[orig2NewMapping[face.points[0]]] += osg::Vec3(face.normal[0],face.normal[1],face.normal[2]);;
            (*osg_normals)[orig2NewMapping[face.points[1]]] += osg::Vec3(face.normal[0],face.normal[1],face.normal[2]);;
            (*osg_normals)[orig2NewMapping[face.points[2]]] += osg::Vec3(face.normal[0],face.normal[1],face.normal[2]);;

        }

        if (matrix)
        {
            osg::Matrix osg_matrix;
            copyLib3dsMatrixToOsgMatrix(osg_matrix, *matrix);
            for (i=0; i<noVertex; ++i)
            {
                (*osg_normals)[i] = osg::Matrix::transform3x3((*osg_normals)[i], osg_matrix);
            }
        }

        // normalize the normal list to unit length normals.
        for (i=0; i<noVertex; ++i)
        {
            (*osg_normals)[i].normalize();
        }

        geom->setNormalArray(osg_normals);
        geom->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

    }
    else 
    {
        osg::Vec3Array* osg_normals = new osg::Vec3Array(faceList.size());
        osg::Vec3Array::iterator normal_itr = osg_normals->begin();
        for (fitr=faceList.begin();
            fitr!=faceList.end();
            ++fitr)
        {
            Lib3dsFace& face = m->faceL[*fitr];
            *(normal_itr++) =  osg::Vec3(face.normal[0],face.normal[1],face.normal[2]);
        }
        geom->setNormalArray(osg_normals);
        geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
    }
    
    osg::Vec4ubArray* osg_colors = new osg::Vec4ubArray(1);
    (*osg_colors)[0].set(255,255,255,255);
    geom->setColorArray(osg_colors);
    geom->setColorBinding(osg::Geometry::BIND_OVERALL);
    

    // create primitives
    int numIndices = faceList.size()*3;
    DrawElementsUShort* elements = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,numIndices);
    DrawElementsUShort::iterator index_itr = elements->begin();

    for (fitr=faceList.begin();
        fitr!=faceList.end();
        ++fitr)
    {
        Lib3dsFace& face = m->faceL[*fitr];

        *(index_itr++) = orig2NewMapping[face.points[0]];
        *(index_itr++) = orig2NewMapping[face.points[1]];
        *(index_itr++) = orig2NewMapping[face.points[2]];
    }
   
    geom->addPrimitiveSet(elements);

#if 0
    osgUtil::TriStripVisitor tsv;
    tsv.stripify(*geom);
#endif

    return geom;
}


osg::Texture2D*  ReaderWriter3DS::ReaderObject::createTexture(Lib3dsTextureMap *texture,const char* label,bool& transparancy, const osgDB::ReaderWriter::Options* options)
{
    if (texture && *(texture->name))
    {
        osg::notify(osg::NOTICE)<<"texture->name="<<texture->name<<", _directory="<<_directory<<std::endl;


        std::string fileName = osgDB::findFileInDirectory(texture->name,_directory,osgDB::CASE_INSENSITIVE);
        if (fileName.empty()) 
        {
            // file not found in .3ds file's directory, so we'll look in the datafile path list.
            fileName = osgDB::findDataFile(texture->name,options, osgDB::CASE_INSENSITIVE);
        }

        if (fileName.empty())
        {
            if (osgDB::containsServerAddress(_directory))
            {
                // if 3DS file is loaded from http, just attempt to load texture from same location.
                fileName = _directory + "/" + texture->name;
            } else {
                osg::notify(osg::WARN) << "texture '"<<texture->name<<"' not found"<< std::endl;
                return NULL;
            }
        }

        if (label) osg::notify(osg::DEBUG_INFO) << label;
        else osg::notify(osg::DEBUG_INFO) << "texture name";
        osg::notify(osg::DEBUG_INFO) << " '"<<texture->name<<"'"<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    texture flag        "<<texture->flags<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_DECALE       "<<((texture->flags)&LIB3DS_DECALE)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_MIRROR       "<<((texture->flags)&LIB3DS_MIRROR)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_NEGATE       "<<((texture->flags)&LIB3DS_NEGATE)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_NO_TILE      "<<((texture->flags)&LIB3DS_NO_TILE)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_SUMMED_AREA  "<<((texture->flags)&LIB3DS_SUMMED_AREA)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_ALPHA_SOURCE "<<((texture->flags)&LIB3DS_ALPHA_SOURCE)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_TINT         "<<((texture->flags)&LIB3DS_TINT)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_IGNORE_ALPHA "<<((texture->flags)&LIB3DS_IGNORE_ALPHA)<< std::endl;
        osg::notify(osg::DEBUG_INFO) << "    LIB3DS_RGB_TINT     "<<((texture->flags)&LIB3DS_RGB_TINT)<< std::endl;

        osg::ref_ptr<osg::Image> osg_image = osgDB::readRefImageFile(fileName.c_str());
        if (!osg_image)
        {
            osg::notify(osg::NOTICE) << "Warning: Cannot create texture "<<texture->name<< std::endl;
            return NULL;
        }

        osg::Texture2D* osg_texture = new osg::Texture2D;
        osg_texture->setImage(osg_image.get());

        // does the texture support transparancy?
        transparancy = ((texture->flags)&LIB3DS_ALPHA_SOURCE)!=0;

        // what is the wrap mode of the texture.
        osg::Texture2D::WrapMode wm = ((texture->flags)&LIB3DS_NO_TILE) ?
                osg::Texture2D::CLAMP :
                osg::Texture2D::REPEAT;
        osg_texture->setWrap(osg::Texture2D::WRAP_S,wm);
        osg_texture->setWrap(osg::Texture2D::WRAP_T,wm);
        osg_texture->setWrap(osg::Texture2D::WRAP_R,wm);
                                 // bilinear.
        osg_texture->setFilter(osg::Texture2D::MIN_FILTER,osg::Texture2D::LINEAR_MIPMAP_NEAREST);

        return osg_texture;
    }
    else
        return NULL;
}


osg::StateSet* ReaderWriter3DS::ReaderObject::createStateSet(Lib3dsMaterial *mat, const osgDB::ReaderWriter::Options* options)
{
    if (mat==NULL) return NULL;

    osg::StateSet* stateset = new osg::StateSet;

    osg::Material* material = new osg::Material;

    float transparency = mat->transparency;
    float alpha = 1.0f-transparency;

    osg::Vec4 ambient(mat->ambient[0],mat->ambient[1],mat->ambient[2],alpha);
    osg::Vec4 diffuse(mat->diffuse[0],mat->diffuse[1],mat->diffuse[2],alpha);
    osg::Vec4 specular(mat->specular[0],mat->specular[1],mat->specular[2],alpha);
    specular *= mat->shin_strength;

    float shininess = mat->shininess;
    material->setName(mat->name);
    material->setAmbient(osg::Material::FRONT_AND_BACK,ambient);
    material->setDiffuse(osg::Material::FRONT_AND_BACK,diffuse);
    material->setSpecular(osg::Material::FRONT_AND_BACK,specular);
    material->setShininess(osg::Material::FRONT_AND_BACK,shininess*128.0f);

    stateset->setAttribute(material);

    bool textureTransparancy=false;
    osg::Texture2D* texture1_map = createTexture(&(mat->texture1_map),"texture1_map",textureTransparancy, options);
    if (texture1_map)
    {
        stateset->setTextureAttributeAndModes(0,texture1_map,osg::StateAttribute::ON);
        
        if (!textureTransparancy)
        {        
            // from an email from Eric Hamil, September 30, 2003.
            // According to the 3DS spec, and other
            // software (like Max, Lightwave, and Deep Exploration) a 3DS material that has
            // a non-white diffuse base color and a 100% opaque bitmap texture, will show the
            // texture with no influence from the base color.
            
            // so we'll override material back to white.
            // and no longer require the decal hack below...
#if 0
            // Eric original fallback
            osg::Vec4 white(1.0f,1.0f,1.0f,alpha);
            material->setAmbient(osg::Material::FRONT_AND_BACK,white);
            material->setDiffuse(osg::Material::FRONT_AND_BACK,white);
            material->setSpecular(osg::Material::FRONT_AND_BACK,white);
#else
            // try alternative to avoid saturating with white
            // setting white as per OpenGL defaults.
            material->setAmbient(osg::Material::FRONT_AND_BACK,osg::Vec4(0.2f,0.2f,0.2f,alpha));
            material->setDiffuse(osg::Material::FRONT_AND_BACK,osg::Vec4(0.8f,0.8f,0.8f,alpha));
            material->setSpecular(osg::Material::FRONT_AND_BACK,osg::Vec4(0.0f,0.0f,0.0f,alpha));
#endif            
        }
        
// no longer required...        
//         bool decal = false;
//         
//         // not sure exactly how to interpret what is best for .3ds
//         // but the default text env MODULATE doesn't work well, and
//         // DECAL seems to work better.
//         osg::TexEnv* texenv = new osg::TexEnv;
//         if (decal)
//         {
//             texenv->setMode(osg::TexEnv::DECAL);
//         }
//         else
//         {
//             texenv->setMode(osg::TexEnv::MODULATE);
//         }
//        stateset->setTextureAttribute(0,texenv);
    }

    if (transparency>0.0f || textureTransparancy)
    {
        stateset->setMode(GL_BLEND,osg::StateAttribute::ON);
        stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
    }

/*
    osg::ref_ptr<osg::Texture> texture1_mask = createTexture(&(mat->texture1_mask),"texture1_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> texture2_map = createTexture(&(mat->texture2_map),"texture2_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> texture2_mask = createTexture(&(mat->texture2_mask),"texture2_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> opacity_map = createTexture(&(mat->opacity_map),"opacity_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> opacity_mask = createTexture(&(mat->opacity_mask),"opacity_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> bump_map = createTexture(&(mat->bump_map),"bump_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> bump_mask = createTexture(&(mat->bump_mask),"bump_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> specular_map = createTexture(&(mat->specular_map),"specular_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> specular_mask = createTexture(&(mat->specular_mask),"specular_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> shininess_map = createTexture(&(mat->shininess_map),"shininess_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> shininess_mask = createTexture(&(mat->shininess_mask),"shininess_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> self_illum_map = createTexture(&(mat->self_illum_map),"self_illum_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> self_illum_mask = createTexture(&(mat->self_illum_mask),"self_illum_mask",textureTransparancy);
    osg::ref_ptr<osg::Texture> reflection_map = createTexture(&(mat->reflection_map),"reflection_map",textureTransparancy);
    osg::ref_ptr<osg::Texture> reflection_mask = createTexture(&(mat->reflection_mask),"reflection_mask",textureTransparancy);
*/
    return stateset;
}

