// 30 Oct 2002
// AC3D loader for models generated by the AC3D modeller (www.ac3d.org)
// part of this source code were supplied by the AC3D project (Andy Colebourne)
// eg the basic parsing of an AC3D file.
// Conversion from AC3D scenegraph to OSG by GW Michel.


#include <stdio.h>
// Where is malloc.h really needed?
#if !defined(__APPLE__) && !defined(macintosh) && !defined(__FreeBSD__)
#include <malloc.h>
#endif
#include <math.h>

#include <osg/CullFace>
#include <osg/Geode>
#include <osg/Group>
#include <osg/Geometry>  //Set>
#include <osg/Light>
#include <osg/LightSource>
#include <osg/Material>
#include <osg/Texture2D>
#include <osg/TexEnv>
#include <osg/StateSet>
#include <osg/Notify>
#include <osg/Texture2D>
#include <osg/LightModel>


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

#include <osgUtil/Tesselator>
#include <osgUtil/SmoothingVisitor>

#include "osgac3d.h"
#include "Exception.h"
#include "Geode.h"

using namespace osg;
using namespace osgDB;

// define this if you have a recent OSG with pRegistry->getFromObjectCache method
#define USE_TEXTURE_CACHE

class geodeVisitor : public osg::NodeVisitor { // collects geodes from scene sub-graph attached to 'this'
        public:
            geodeVisitor():
                osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {}

            ~geodeVisitor() { _geodelist.clear();}

        // one apply for each type of Node that might be a user transform
         //   virtual void apply(osgAction::ActionHeader& ah);
           // virtual void apply(osg::Drawable& dr);
            virtual void apply(osg::Geode& geode) {
                _geodelist.push_back(&geode);
            }
        //    virtual void apply(osg::Billboard& geode);
            virtual void apply(osg::Group& gp){
                traverse(gp);    // must continue subgraph traversal.
            }
         //   virtual void apply(osg::Switch& sw);
           // virtual void apply(osg::Transform& transform);
            std::vector<const osg::Geode *> getGeodes() {return _geodelist;}
        protected:

            typedef std::vector<const osg::Geode *>    Geodelist;
            Geodelist  _geodelist;
};

class ReaderWriterAC : public osgDB::ReaderWriter
{
    public:
        virtual const char* className() const { return "AC3D Database Reader"; }

        virtual bool acceptsExtension(const std::string& extension) const
        {
            return osgDB::equalCaseInsensitive(extension,"ac");
        }
        virtual ReadResult readNode(const std::string& file,const osgDB::ReaderWriter::Options* options) const
        {
            std::string ext = getFileExtension(file);
            if (!acceptsExtension(ext)) return ReadResult::FILE_NOT_HANDLED;

            osg::Group *grp; // holder for all loaded objects
            // GWM added Dec 2003 - get full path name (change in osgDB handling of files).
            std::string fileName = osgDB::findDataFile( file, options );
            
            // Anders Backmann - correct return if path not found
            if (fileName.empty()) return ReadResult::FILE_NOT_FOUND;

            // code for setting up the database path so that internally referenced file are searched for on relative paths. 
            osg::ref_ptr<Options> local_opt = options ? static_cast<Options*>(options->clone(osg::CopyOp::SHALLOW_COPY)) : new Options;
            local_opt->setDatabasePath(osgDB::getFilePath(fileName));

            grp=ac_load_ac3d(fileName.c_str(), local_opt.get());
            return grp;
        };
        virtual WriteResult writeNode(const Node& node,const std::string& fileName, const osgDB::ReaderWriter::Options* /*options*/) const
        {
            std::string ext = getFileExtension(fileName);
            if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED;
            geodeVisitor vs; // this collects geodes.
            Node *nd=(Node *)(&node);
            std::vector<unsigned int>iNumMaterials;
            nd->accept(vs); // this parses the tree to find Geodes
            std::vector<const osg::Geode *> glist=vs.getGeodes();
            std::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary);
            // Write out the file header
            std::vector<const osg::Geode *>::iterator itr;
            fout << "AC3Db" << std::endl;
            // output the Materials
            for (itr=glist.begin();itr!= glist.end();itr++) {
                iNumMaterials.push_back(const_cast<ac3d::Geode*>(static_cast<const ac3d::Geode*>(*itr))->ProcessMaterial(fout,itr-glist.begin()));
            }
            // output the Geometry
            unsigned int nfirstmat=0;
            fout << "OBJECT world" << std::endl;
            fout << "kids " << (glist.end()-glist.begin()) << std::endl;
            for (itr=glist.begin();itr!= glist.end();itr++) {
                const_cast<ac3d::Geode*>(static_cast<const ac3d::Geode*>(*itr))->ProcessGeometry(fout,nfirstmat);
                nfirstmat+=iNumMaterials[itr-glist.begin()];
            }
            fout.close();
            return WriteResult::FILE_SAVED;
        }
        
        virtual WriteResult writeNode(const Node& node,std::ostream& fout, const osgDB::ReaderWriter::Options* opts) const
        {
            try
            {
                // write ac file.
                if(dynamic_cast<const osg::Group*>(&node)) {
                    const osg::Group *gp=dynamic_cast<const osg::Group*>(&node);
                    const unsigned int nch=gp->getNumChildren();
                    for (unsigned int i=0; i<nch; i++) {
                        writeNode(*(gp->getChild(i)), fout, opts);
                    }
                }
                else
                    osg::notify(osg::WARN)<<"File must start with a geode "<<std::endl;
                fout.flush();
                return WriteResult::FILE_SAVED;
            }
            catch(ac3d::Exception e)
            {
                osg::notify(osg::WARN)<<"Error parsing OSG tree: "<< e.getError() << std::endl;            
            }
            return WriteResult::FILE_NOT_HANDLED;

        }
private:
};

class appearance : public osg::Referenced { // how a surface is rendered - basic copy of ACsurface
    // except that as a ref_ptr it will automatically delete itself when the object has loaded
        // and the list of appearances is deleted
public:
    appearance(const int imat=-1) {material=imat;
        type=0x00;
    }
    void setMaterial(const int i) { material=i;}
    inline int getMaterial(void) const { return material;}
    void setSurfaceType(const int i) { type=i;}
    inline int getSurfaceType(void) const { return type;}
protected:
    ~appearance() {
    }
    int material; // index in the ac3d file
    int type; // as used in ac3d, onesided etc are held as 0x20, 0x21...
};

static int line = 0;
static char buff[255];

static std::vector<osg::Material*> palette; // change to dynamic array
static int startmatindex = 0;


osg::Material*ac_palette_get_material(const unsigned int id)
{
    if (id<palette.size()) return(palette[id]);
    else return NULL;
}


Boolean read_line(std::istream &fin)
{
    fin.getline(buff,255);
    return !(fin.eof());
}



int tokc = 0;
char *tokv[30];

Prototype int get_tokens(char *s, int *argc, char *argv[])
/** bung '\0' chars at the end of tokens and set up the array (tokv) and count (tokc)
    like argv argc **/
{
    char *p = s;
    char *st;
    char c;
    //int n;
    int tc;
    
    tc = 0;
    while ((c = *p))
    {
        if ((c != ' ') && (c != '\t') && (c != '\n') && ( c != 13) && ( c != '\0'))
        {
            if (c == '"')
            {
                c = *p++;
                st = p;
                while ((c = *p) && ((c != '"')&&(c != '\n')&& ( c != 13) && ( c != '\0')) )
                {
                    if (c == '\\')
                        strcpy(p, p+1);
                    p++;
                }
                argv[tc++] = st;
            }
            else
            {
                st = p;
                while ((c = *p) && ((c != ' ') && (c != '\t') && (c != '\n') && ( c != 13) && ( c != '\0')) )
                    p++;
                if (c != '"') argv[tc++] = st;
            }            
        }
        if (*p) p++;
    }
    
    *argc = tc;
    return(tc);
}


void initobject(ACObject *ob)
{
    ob->loc[0] = ob->loc[1] = ob->loc[2] = 0.0;
    ob->name = ob->url = NULL;
    ob->data = NULL;
    ob->num_vert = 0;
    ob->num_surf = 0;
    ob->texture = NULL;
    ob->texture_repeat_x = ob->texture_repeat_y = 1.0;
    ob->texture_offset_x = ob->texture_offset_y = 0.0;
    ob->kids = NULL;
    ob->num_kids = 0;
    ob->matrix[0] = 1;
    ob->matrix[1] = 0;
    ob->matrix[2] = 0;
    ob->matrix[3] = 0;
    ob->matrix[4] = 1;
    ob->matrix[5] = 0;
    ob->matrix[6] = 0;
    ob->matrix[7] = 0;
    ob->matrix[8] = 1;
    ob->type=OBJECT_WORLD;
}


void init_surface(ACSurface *s)
{
    s->num_vertref = 0;
    s->flags = 0;
    s->mat = 0;
//    s->normal.x = 0.0; s->normal.z = 0.0; s->normal.z = 0.0; 
}


void osgtri_calc_normal(osg::Vec3 &v1, osg::Vec3 &v2, osg::Vec3 &v3, osg::Vec3 &n)
{
    osg::Vec3 side1=v2-v1;
    osg::Vec3 side2=v3-v2;
    n=side1^side2;
    n.normalize();

}

ACSurface *read_surface(std::istream &f, ACSurface *s,osg::UShortArray *nusidx,
                        osg::Vec2Array *tcs)
{
    char t[20];
    init_surface(s);

    while (!f.eof()) //feof(f))
    {
    read_line(f);
    sscanf(buff, "%s", t);

    if (streq(t, "SURF"))
        {
        int flgs;

        if (get_tokens(buff, &tokc, tokv) != 2)
            {
            printf("SURF should be followed by one flags argument\n");
            }
        else
            {
            flgs = strtol(tokv[1], NULL, 0);
            s->flags = flgs;
            }
        }
    else
    if (streq(t, "mat"))
        {
        int mindx;

        sscanf(buff, "%s %d", t, &mindx);
        s->mat = mindx+startmatindex;
        }
    else
    if (streq(t, "refs"))
        {
        int num, n;
        int ind;
        osg::Vec2 tx;
  
          sscanf(buff, "%s %d", t, &num);        

        s->num_vertref = num;

        for (n = 0; n < num; n++)
           {
            read_line(f);
          sscanf(buff, "%d %f %f\n", &ind, &tx[0], &tx[1]); line++;
            nusidx->push_back(ind);
            if (tcs) tcs->push_back(tx);
           }

        return(s);
        }
    else
        printf("ignoring %s\n", t);

    }
   return(NULL);
}

int string_to_objecttype(char *s)
{
    if (streq("world", s))
        return(OBJECT_WORLD);
    if (streq("poly", s))
        return(OBJECT_NORMAL);
    if (streq("group", s))
        return(OBJECT_GROUP);
    if (streq("light", s))
        return(OBJECT_LIGHT);
    return(OBJECT_NORMAL);
}

void
protate(osg::Vec3 p, float m[9])
{ // p-> m*p, m is 3.3 matrix
    osg::Vec3 t=p;
    p[0]=m[0]*t[0]+m[1]*t[1]+m[2]*t[3];
    p[1]=m[3]*t[0]+m[4]*t[1]+m[5]*t[3];
    p[2]=m[6]*t[0]+m[7]*t[1]+m[8]*t[3];
}

osg::Group *ac_load_object(std::istream &f,const ACObject *parent,const osgDB::ReaderWriter::Options* options)
{
    // most of this logic came from Andy Colebourne (developer of the AC3D editor) so it had better be right!
    char t[20];
    osg::Group *gp=NULL;
    osg::Geode *geode=NULL;

    ACObject ob; // local storage for stuff taken from AC's loader
    osg::ref_ptr<osg::Vec3Array> vertpool = new osg::Vec3Array;
    initobject(&ob); // zero data for object

    if (parent)
        ob.loc=parent->loc; // copy loc
    while (!f.eof())
    {
        read_line(f);

        sscanf(buff, "%s", t);

        if (streq(t, "MATERIAL"))
        {
            if (get_tokens(buff, &tokc, tokv) != 22)
            {
                printf("expected 21 params after \"MATERIAL\" - line %d\n", line);
            }
            else
            {
                osg::Material *numat=new osg::Material();
                osg::Vec4 cdiff((float)atof(tokv[3]), (float)atof(tokv[4]), 
                                (float)atof(tokv[5]), 1.0-(float)atof(tokv[21]));
                numat->setDiffuse(osg::Material::FRONT_AND_BACK,cdiff);
                osg::Vec4 camb((float)atof(tokv[7]),(float)atof(tokv[8]),
                                (float)atof(tokv[9]),1.0-(float)atof(tokv[21]));
                numat->setAmbient(osg::Material::FRONT_AND_BACK,camb);
                osg::Vec4 cspec((float)atof(tokv[15]), (float)atof(tokv[16]),
                                (float)atof(tokv[17]),1.0-(float)atof(tokv[21]));
                numat->setSpecular(osg::Material::FRONT_AND_BACK,cspec);
                // addition 1 Nov 2003 - use shininess (was defaulted)
                float shininess=atof(tokv[19]);
                numat->setShininess(osg::Material::FRONT_AND_BACK,shininess);
                osg::Vec4 cemm((float)atof(tokv[11]),(float)atof(tokv[12]),(float)atof(tokv[13]),1.0-(float)atof(tokv[21]));
                // correction 1 Nov 2003 - from Marcio Ferraz - use emission colour for emission
                numat->setEmission(osg::Material::FRONT_AND_BACK,cemm);
                //numat->setTransparency(osg::Material::FRONT_AND_BACK, 1.0-(float)atof(tokv[21]));

                palette.push_back(numat); // [num_palette++] = numat;

            }
        }
        else if (streq(t, "OBJECT"))
        {
            char type[20];
            char str[20];
            osg::Vec3 loc=ob.loc;
            if (gp!=NULL)
            {
                osg::notify(osg::NOTICE)<<"Warning: Ac3d loader - possible internal error."<<std::endl;
            }
            gp = new osg::Group();
            initobject(&ob); // rezero data for object
            ob.loc=loc;

            sscanf(buff, "%s %s", str, type);

            ob.type = string_to_objecttype(type);
        }
        else if (streq(t, "data"))
        {
            if (get_tokens(buff, &tokc, tokv) != 2)
                printf("expected 'data <number>' at line %d\n", line);
            else
            {
                char *str;
                int len;

                len = atoi(tokv[1]);
                if (len > 0)
                {
                    str = (char *)myalloc(len+1);
                    f>>str; //fread(str, len, 1, f);
                    str[len] = 0;
                    //fscanf(f, "\n");
                    line++;
                    ob.data = STRING(str);
                    myfree(str);
                }
            }
        }
        else if (streq(t, "name"))
        {
            int numtok = get_tokens(buff, &tokc, tokv);
            if (numtok != 2)
            {
                printf("expected quoted name at line %d (got %d tokens)\n", line, numtok);
            }
            else {
                char *endquot=strrchr(tokv[1],'\"');
                if (endquot>0) {
                    *endquot='\0';
                }
                if (gp) gp->setName(tokv[1]);
            }
        }
        else if (streq(t, "texture"))
        {
            if (get_tokens(buff, &tokc, tokv) != 2)
                printf("expected quoted texture name at line %d\n", line);

            else
            {
                char *ctmp=tokv[1]; // upate Jul 19 2004 - for texture file names in quotes
                while (*ctmp) {
                    ctmp++;
                    if (*ctmp == '"') *ctmp='\0'; // latest ac3d seems toa dd more quotes than older versions.
                }
                osgDB::Registry* pRegistry = osgDB::Registry::instance();
                osg::Texture2D* pTexture = dynamic_cast<osg::Texture2D*>(pRegistry->getFromObjectCache(std::string(tokv[1]) + "TEXTURE2D"));
                if (NULL != pTexture)
                {
                    // Use cached texture
                    ob.texture = pTexture;
                    osg::notify(osg::INFO) << "Using cached texture "  << pTexture->getImage()->getFileName() <<std::endl;
                }
                else
                {
                    // Try to load the texture
                    osg::Image *ctx= osgDB::readImageFile(tokv[1], options);
                    if (ctx)
                    { // image could be read
                        ob.texture = new osg::Texture2D;// ac_load_texture(tokv[1]);
                        ob.texture->setImage(ctx);
                        ob.texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
                        ob.texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
                        pRegistry->addEntryToObjectCache(std::string(tokv[1]) + "TEXTURE2D", ob.texture.get());
                    }
                }
            }
        }
        else if (streq(t, "texrep"))
        {
            if (get_tokens(buff, &tokc, tokv) != 3)
                printf("expected 'texrep <float> <float>' at line %d\n", line);
            else
            {
                ob.texture_repeat_x = atof(tokv[1]);
                ob.texture_repeat_y = atof(tokv[2]);
            }
        }
        else if (streq(t, "texoff"))
        {
            if (get_tokens(buff, &tokc, tokv) != 3)
                printf("expected 'texoff <float> <float>' at line %d\n", line);
            else
            {
                ob.texture_offset_x = atof(tokv[1]);
                ob.texture_offset_y = atof(tokv[2]);
            }
        }
        else if (streq(t, "rot"))
        {
            float r[9];
            char str2[5];
            int n;

            sscanf(buff, "%s %f %f %f %f %f %f %f %f %f", str2, &r[0], &r[1], &r[2], &r[3], &r[4], &r[5], &r[6], &r[7], &r[8] );

            for (n = 0; n < 9; n++)
                ob.matrix[n] = r[n];

        }
        else if (streq(t, "loc"))
        {
            char str[5];
            osg::Vec3 loc;
            sscanf(buff, "%s %f %f %f", str, &loc[0], &loc[1], &loc[2]);
            ob.loc+=loc;
        }
        else if (streq(t, "url"))
        {
            int ret;
            if ((ret = get_tokens(buff, &tokc, tokv)) != 2)
                printf("expected one arg to url at line %d (got %d)\n", line, ret); 
            else
                ob.url = STRING(tokv[1]);
        }
        else if (streq(t, "numvert"))
        {
            int num, n;
            char str[10];
            if (ob.type == OBJECT_GROUP || ob.type == OBJECT_WORLD)
            {
            }
            else if (ob.type == OBJECT_NORMAL)
            {
                if (geode!=NULL)
                {
                    osg::notify(osg::NOTICE)<<"Warning: Ac3d loader - possible internal error."<<std::endl;
                }
                geode = new osg::Geode();
                gp->addChild(geode);
                geode->setName(gp->getName());
                if (ob.data) { // GWM March 8 2004 - turn comment data into descriptors
                    char *ctmp=strtok(ob.data,"\n");
                    while (ctmp) {
                        if (gp) gp->addDescription(std::string(ctmp));
                        else geode->addDescription(std::string(ctmp));
                        ctmp=strtok(NULL,"\n");
                    }
                }
            }

            sscanf(buff, "%s %d", str, &num);

            if (num > 0)
            {
                ob.num_vert = num;

                for (n = 0; n < num; n++)
                {
                    osg::Vec3 p;
                    read_line(f);
                    sscanf(buff, "%f %f %f\n", &p[0], &p[1], &p[2]); line++;
                    protate(p, ob.matrix);
                    vertpool->push_back(p+ob.loc);
                }

            }
        }
        else if (streq(t, "numsurf"))
        {
            int num, n;
            char str[10];
            // this is not obvious (what is?).  Each set of surfaces can have different material on each surface.
            // so I set up a set of 'bins' for 
            //    the primitives (geometry list, geomlist)
            //    the coords array of each geometry (Vec3Array list, vertslist)
            //    the tx coords array for each geometry (Vec2Array list, texslist)
            // then I add a new geometry to the current Geode for each new material as it is found.

            std::vector< osg::ref_ptr<appearance> > ia; // list of materials required- generate one geode per material
             typedef std::vector<osg::ref_ptr<osg::Geometry> > geomlist;
            geomlist glist;
             typedef std::vector<osg::ref_ptr<osg::Vec3Array> > vertslist;
            vertslist vlists; // list of vertices for each glist element
             typedef std::vector<osg::ref_ptr<osg::Vec2Array> > texslist;
            texslist txlists; // list of texture coords for each glist element

            sscanf(buff, "%s %d", str, &num);
            if (num > 0)
            {
                int needSmooth=0; // flat shaded
                ob.num_surf = num;

                for (n = 0; n < num; n++)
                { // add each surface (poly, 2 sided poly, line, polyline) to a geometry suitable for its:
                    // material index
                    // lighting (off for line, polyline).
                      osg::ref_ptr<osg::Geometry> geom=NULL; // the surface will be addded to this geometry
                     osg::ref_ptr<osg::Vec3Array> vgeom=NULL; // vertices corresponding to geom taken from vertexpool
                    osg::ref_ptr<Vec2Array> tgeom=NULL; // texture coords corresponding to geom taken from vertexpool
                    ACSurface asurf;
                     osg::ref_ptr<UShortArray> nusidx = new osg::UShortArray; // indices into the vertices
                    osg::ref_ptr<Vec2Array> tcs=new osg::Vec2Array; // texture coordinates for this object
                    ACSurface *news = read_surface(f, &asurf, nusidx.get(), tcs.get());
                    if (news == NULL)
                    {
                        printf("error whilst reading surface at line: %d\n", line);
                        return(NULL);
                    }
                    else
                    {
                        int i=0;
                        // check for shared material and texture... and lighting mode
                        // since lines are not affected by light.
                        for (std::vector< osg::ref_ptr<appearance> >::iterator itr= ia.begin(); itr<ia.end(); itr++, i++)
                        {
                            if ((*itr)->getMaterial()==asurf.mat)
                            { // shares material
                                // check the line type.
                                if ((*itr)->getSurfaceType()==asurf.flags) {
                                    geom=glist[i];
                                    vgeom=vlists[i];
                                    tgeom=txlists[i]; // what is current texture array
                                }
                            }
                        }
                        if (!geom.valid())
                        { // then we need a new geometry
                            osg::Vec3Array *verts = new osg::Vec3Array;
                            osg::Vec3Array *normals = new osg::Vec3Array; // 31.03.05 create a new array for each geometry
//                            fprintf(stdout,"New geom in %s nrms %x are %d nrms\n",gp->getName().c_str(),normals,normals->size());

                            osg::ref_ptr<Vec2Array> tcrds=new osg::Vec2Array; // texture coordinates for this object
                            vgeom=verts;
                            tgeom=tcrds; // what is current texture array
                            vlists.push_back(verts);
                            txlists.push_back(tcrds);
                            geom=new osg::Geometry();
                            geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
                            geom->setNormalArray(normals);
                            geom->setVertexArray(verts);
                            if (ob.texture.valid())
                            {
                                geom->setTexCoordArray(0,tgeom.get()); // share same set of TexCoords
                            }
                            osg::StateSet *dstate = new osg::StateSet;
                            osg::Material*mat=ac_palette_get_material(asurf.mat);
                            if (mat) {
                                if (asurf.flags & SURFACE_TYPE_CLOSEDLINE || asurf.flags & SURFACE_TYPE_LINE) {
                                    dstate->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
                                } else {
                                    dstate->setMode( GL_LIGHTING, osg::StateAttribute::ON );
                                }
                                dstate->setAttribute(mat);
                                const osg::Vec4 cdiff =mat->getDiffuse(osg::Material::FRONT_AND_BACK);
                                if (cdiff[3]<0.99)
                                {
                                    dstate->setMode(GL_BLEND,osg::StateAttribute::ON);
                                    dstate->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
                                } 
                                else
                                {
                                    dstate->setMode(GL_BLEND,osg::StateAttribute::OFF);
                                }
                            }
                            if (ob.texture.valid()) dstate->setTextureMode(0,GL_TEXTURE_2D,osg::StateAttribute::OFF);
                            if (ob.texture.valid())
                            {
                                osg::TexEnv* texenv = new osg::TexEnv;
                                texenv->setMode(osg::TexEnv::MODULATE);
                                dstate->setTextureAttribute(0, texenv );
                                dstate->setTextureAttributeAndModes(0,ob.texture.get(),osg::StateAttribute::ON);
                            }
                            if (asurf.flags & SURFACE_TWOSIDED)
                            {
                                osg::LightModel *pLightModel = new osg::LightModel;
                                pLightModel->setTwoSided(true);
                                dstate->setAttributeAndModes(pLightModel);
                                dstate->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
                            }
                            else
                                dstate->setMode( GL_CULL_FACE, osg::StateAttribute::ON );

                            geom->setStateSet( dstate );
                            glist.push_back(geom);
                            geode->addDrawable(geom.get());
                            osg::ref_ptr<appearance> app=new appearance(asurf.mat);
                            app->setSurfaceType(asurf.flags);
                            ia.push_back(app.get());
                        }

                        osg::Vec3Array* normals = dynamic_cast<osg::Vec3Array*>(geom->getNormalArray());
                        if (normals)
                        {
                            /** calc surface normal **/
                            if (asurf.num_vertref >= 3)
                            {
                                osg::Vec3 norm;
                                // Handle surfaces with duplicate vertex indices
                                unsigned short i1=(*nusidx)[0];
                                unsigned short i2=i1;
                                unsigned short i3=i1;
                                int iIndex = 1;
                                while ((iIndex < asurf.num_vertref) && (i1 == (i2 = (*nusidx)[iIndex])))
                                    iIndex++;
                                    
                                if (iIndex < asurf.num_vertref)
                                {
                                    iIndex = 1;
                                    while ((iIndex < asurf.num_vertref) && ((i1 == (i3 = (*nusidx)[iIndex])) || (i2 == i3)))
                                            iIndex++;
                                            
                                    if (iIndex < asurf.num_vertref)
                                    {
                                        osgtri_calc_normal((*vertpool)[i1], (*vertpool)[i2], (*vertpool)[i3], norm);
                                        normals->push_back(norm);
                                    }
                                    else
                                    {
                                        // Generate a dummy normal
                                        osg::Vec3 norm(0, 1, 0);
                                        normals->push_back(norm);
                                    }
                                }
                                else
                                {
                                    // Generate a dummy normal
                                    osg::Vec3 norm(0, 1, 0);
                                    normals->push_back(norm);
                                }
                            }
                            else
                            {
                                // Generate a dummy normal
                                osg::Vec3 norm(0, 1, 0);
                                normals->push_back(norm);
                            }
                        }
                        int nstart=(*vgeom).size();
                        for (i=0; i<asurf.num_vertref; i++)
                        {
                            osg::Vec2 TextureCoordinate;

                            int i1=(*nusidx)[i];
                            (*vgeom).push_back((*vertpool)[i1]);
                            TextureCoordinate = (*tcs)[i];
                            TextureCoordinate._v[0] = ob.texture_offset_x + TextureCoordinate._v[0] * ob.texture_repeat_x;
                            TextureCoordinate._v[1] = ob.texture_offset_y + TextureCoordinate._v[1] * ob.texture_repeat_y;
                            (*tgeom).push_back(TextureCoordinate);
                        }
                        GLenum poltype=osg::PrimitiveSet::POLYGON;
                        if (asurf.flags & SURFACE_TYPE_CLOSEDLINE) poltype=osg::PrimitiveSet::LINE_LOOP;
                        if (asurf.flags & SURFACE_TYPE_LINE) poltype=osg::PrimitiveSet::LINE_STRIP;
                        geom->addPrimitiveSet(new osg::DrawArrays(poltype,nstart,asurf.num_vertref));
                        if (asurf.flags & 0x10) needSmooth++;
                    }
                }
                for (geomlist::iterator itr= glist.begin(); itr<glist.end(); itr++)
                { // for each geometry 'bin', collection of facets sharing same state:
                    osgUtil::Tesselator tesselator;
                    if (itr->valid()) tesselator.retesselatePolygons(**itr);
                    if (needSmooth)
                    {
                        osgUtil::SmoothingVisitor smoother;
                        smoother.smooth(**itr);
                    }
                }
            }
        }
        else if (streq(t, "kids")) /** 'kids' is the last token in an object **/
        {
            int num, n;

            sscanf(buff, "%s %d", t, &num);

            if (num != 0)
            {
                // ob.kids = (ACObject **)myalloc(num * sizeof(ACObject *) );
                ob.num_kids = num;

                if (gp && ob.data) { // GWM March 8 2004 - turn comment data into descriptors
                    char *ctmp=strtok(ob.data,"\n");
                    while (ctmp) {
                        gp->addDescription(std::string(ctmp));
                        ctmp=strtok(NULL,"\n");
                    }
                }
                for (n = 0; n < num; n++)
                {
                    osg::Group *k = ac_load_object(f,&ob, options); 
                    if (k == NULL)
                    {
                        printf("error reading expected child object %d of %d at line: %d\n", n+1, num, line);
                        return(gp);
                    }
                    else {
                        osg::LightSource *ls=dynamic_cast<osg::LightSource*>(k);
                        if (ls) {
                            osg::StateSet* lightStateSet = gp->getOrCreateStateSet();
                            gp->setStateSet(lightStateSet);
                            gp->setCullingActive(false);
                            ls->setStateSetModes(*lightStateSet,osg::StateAttribute::ON);
                        }
                        
                        gp->addChild(k);
                    }
                }

            }
            else if (ob.type == OBJECT_LIGHT)
            { // add a light source to the scene 1 Nov 2003
                static int nlight=1;
                osg::Light* ac3dLight = new osg::Light;
                ac3dLight->setLightNum(nlight++);
                ac3dLight->setPosition(osg::Vec4(ob.loc[0],ob.loc[1],ob.loc[2],0.0f));
                ac3dLight->setAmbient(osg::Vec4(0.5f,0.5f,0.5f,1.0f));
                ac3dLight->setDiffuse(osg::Vec4(0.5f,0.5f,0.5f,1.0f));
                ac3dLight->setSpecular(osg::Vec4(1.0f,1.0f,0.5f,1.0f));
                
                osg::LightSource* ac3dLightSource = new osg::LightSource;    
                ac3dLightSource->setLight(ac3dLight);
                ac3dLightSource->setLocalStateSetModes(osg::StateAttribute::ON); 
                
                // for some mad reason, you need to set this so that the light works.  WHY?
                return ac3dLightSource;
            }
            return(gp);
        }
    }
    return(gp);
}


/*void ac_calc_vertex_normals(ACObject *ob)
{
int n;

    ac_object_calc_vertex_normals(ob);
    if (ob->num_kids)
        for (n = 0; n < ob->num_kids; n++)
            ac_calc_vertex_normals(ob->kids[n]);
}*/


osg::Group *ac_load_ac3d(const char *fname,const osgDB::ReaderWriter::Options* options)
{
    osg::Group *ret = NULL;
    if (strlen(fname)>0) {
    std::ifstream fin(fname, std::ios::in);
//    FILE *f = fopen(fname, "r");

        if (!fin.is_open())
        {
            printf("can't open %s for loading\n", fname);
            return(NULL);
        }

        read_line(fin);

        if (strncmp(buff, "AC3D", 4))
        {
            printf("ac_load_ac '%s' is not a valid AC3D file.", fname);
            fin.close(); // fclose(f);
            return(0);
        }


        startmatindex = palette.size(); //num_palette;


        ret = ac_load_object(fin,NULL, options); 

        
        fin.close();

        // ac_calc_vertex_normals(ret);
        // here I need to calculate nromals for this object
    }

    return(ret);
}







void ac_dump(ACObject *ob)
{ // not yet finished option ot output AC3D file from OSG scene.
int n;

    printf("OBJECT name %s\nloc %f %f %f\nnum_vert %d\nnum_surf %d\n",
        ob->name, ob->loc[0], ob->loc[1], ob->loc[2], ob->num_vert, ob->num_surf);


//    for (n=0; n < ob->num_vert; n++)
//        printf("\tv %f %f %f\n", ob->vertices[n].x, ob->vertices[n].y, ob->vertices[n].z);

    for (n=0; n < ob->num_surf; n++)
        {
////        ACSurface *s = &ob->surfaces[n];
//        printf("surface %d, %d refs, mat %d\n", n, s->num_vertref, s->mat);
        }

/* this structure not used in OSG
    if (ob->num_kids)
        for (n = 0; n < ob->num_kids; n++)
            ac_dump(ob->kids[n]); 
 */            
}



// now register with osg::Registry to instantiate the above
// reader/writer.
osgDB::RegisterReaderWriterProxy<ReaderWriterAC> g_readerWriter_AC_Proxy;




















