// 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 // Where is malloc.h really needed? #if !defined(__APPLE__) && !defined(macintosh) && !defined(__FreeBSD__) #include #endif #include #include #include #include #include //Set> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "osgac3d.h" #include "Exception.h" #include "Geode.h" using namespace osg; using namespace osgDB; 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 getGeodes() {return _geodelist;} protected: typedef std::vector 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) { return osgDB::equalCaseInsensitive(extension,"ac"); } virtual ReadResult readNode(const std::string& file,const osgDB::ReaderWriter::Options* options) { 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 local_opt = options ? static_cast(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*/) { std::string ext = getFileExtension(fileName); if (!acceptsExtension(ext)) return WriteResult::FILE_NOT_HANDLED; geodeVisitor vs; // this collects geodes. Node *nd=(Node *)(&node); std::vectoriNumMaterials; nd->accept(vs); // this parses the tree to find Geodes std::vector glist=vs.getGeodes(); std::ofstream fout(fileName.c_str(), std::ios::out | std::ios::binary); // Write out the file header std::vector::iterator itr; fout << "AC3Db" << std::endl; // output the Materials for (itr=glist.begin();itr!= glist.end();itr++) { iNumMaterials.push_back(const_cast(static_cast(*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(static_cast(*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) { try { // write ac file. if(dynamic_cast(&node)) { const osg::Group *gp=dynamic_cast(&node); const unsigned int nch=gp->getNumChildren(); for (unsigned int i=0; igetChild(i)), fout, opts); } } //const_cast(static_cast(&node))->Process(fout, options); // else if(dynamic_cast(&node)) // const_cast(static_cast(&node))->Process(fout); else osg::notify(osg::WARN)<<"File must start with a geode "< palette; // change to dynamic array //static int num_palette = 0; static int startmatindex = 0; osg::Material*ac_palette_get_material(const unsigned int id) { if (id0) buff[nread-1]='\0'; // null terminate and remove training blank return nread>0;*/ // fgets(buff, 255, f); line++; // return(TRUE); } 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++; 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); } /*void ac_object_calc_vertex_normals(ACObject *ob) { int s, v, vr; / ** for each vertex in this object ** / for (v = 0; v < ob->num_vert; v++) { ACNormal n = {0, 0, 0}; int found = 0; / ** go through each surface ** / for (s = 0; s < ob->num_surf; s++) { ACSurface *surf = &ob->surfaces[s]; / ** check if this vertex is used in this surface ** / / ** if it is, use it to create an average normal ** / for (vr = 0; vr < surf->num_vertref; vr++) if (surf->vertref[vr] == v) { n.x+=surf->normal.x; n.y+=surf->normal.y; n.z+=surf->normal.z; found++; } } if (found > 0) { n.x /= found; n.y /= found; n.z /= found; } ob->vertices[v].normal = n; } } */ 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; osg::Vec3Array *normals = NULL; // new osg::Vec3Array; // NULL; ACObject ob; // local storage for stuff taken from AC's loader 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) 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 ' 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 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. } osg::Image *ctx= osgDB::readImageFile(tokv[1], options); if (ctx) { // image coukd 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); } } } else if (streq(t, "texrep")) { if (get_tokens(buff, &tokc, tokv) != 3) printf("expected 'texrep ' 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 ' 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) { // finish off the geode by making sure there are no concave polys osgUtil::Tesselator tesselator; for(unsigned int i=0;igetNumDrawables();++i) { osg::Geometry* geom = dynamic_cast(geode->getDrawable(i)); if (geom) tesselator.retesselatePolygons(*geom); } } 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"); } } normals = new osg::Vec3Array; } 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 ia; // list of materials required- generate one geode per material typedef std::vector geomlist; geomlist glist; typedef std::vector vertslist; vertslist vlists; // list of vertices for each glist element typedef std::vector 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++) { osg::Geometry *geom=NULL; // the surface will be addded to this geometry osg::Vec3Array *vgeom=NULL; // vertices corresponding to geom taken from vertexpool osg::Vec2Array *tgeom=NULL; // texture coords corresponding to geom taken from vertexpool ACSurface asurf; osg::UShortArray *nusidx = new osg::UShortArray; // indices into the vertices osg::Vec2Array *tcs=new osg::Vec2Array; // texture coordinates for this object ACSurface *news = read_surface(f, &asurf, nusidx, tcs); if (news == NULL) { printf("error whilst reading surface at line: %d\n", line); return(NULL); } else { int i=0; for (std::vector::iterator itr= ia.begin(); itrsetNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE); geom->setNormalArray(normals); geom->setVertexArray(verts); if (ob.texture.valid()) { geom->setTexCoordArray(0,tgeom); // share same set of TexCoords } osg::StateSet *dstate = new osg::StateSet; osg::Material*mat=ac_palette_get_material(asurf.mat); if (mat) { 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) 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); ia.push_back(asurf.mat); } osg::Vec3Array* normals = geom->getNormalArray(); /** calc surface normal **/ if (asurf.num_vertref >= 3) { osg::Vec3 norm; unsigned short i1=(*nusidx)[0]; unsigned short i2=(*nusidx)[1]; unsigned short i3=(*nusidx)[2]; osgtri_calc_normal((*vertpool)[i1], (*vertpool)[i2], (*vertpool)[i3], norm); normals->push_back(norm); } int nstart=(*vgeom).size(); for (i=0; iaddPrimitiveSet(new osg::DrawArrays(poltype,nstart,asurf.num_vertref)); if (asurf.flags & 0x10) needSmooth++; } } for (geomlist::iterator itr= glist.begin(); itraddDescription(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(k); if (ls) { osg::StateSet* lightStateSet = gp->getOrCreateStateSet(); gp->setStateSet(lightStateSet); gp->setCullingActive(false); ls->setStateSetModes(*lightStateSet,osg::StateAttribute::ON); } gp->addChild(k); } } } if (geode) { osgUtil::Tesselator tesselator; for(unsigned int i=0;igetNumDrawables();++i) { osg::Geometry* geom = dynamic_cast(geode->getDrawable(i)); if (geom) tesselator.retesselatePolygons(*geom); } } 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); } } if (geode) { osgUtil::Tesselator tesselator; for(unsigned int i=0;igetNumDrawables();++i) { osg::Geometry* geom = dynamic_cast(geode->getDrawable(i)); if (geom) tesselator.retesselatePolygons(*geom); } } 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 g_readerWriter_AC_Proxy;