#include "stdafx.h" struct vertex { int pos, tc, normal, colour; bool operator<(const vertex &v) const { if (pos < v.pos) return true; if (pos > v.pos) return false; if (normal < v.normal) return true; if (normal > v.normal) return false; if (tc < v.tc) return true; if (tc > v.tc) return false; return colour < v.colour; } }; // VRML face struct face { std::vector indices; }; // OGRE face struct triangle { int vertices[3]; }; typedef std::vector Vec3Vec; typedef std::vector VertVec; typedef std::vector FaceVec; typedef std::vector TriVec; typedef std::vector IntVec; typedef std::map VertMap; // traverse the scene graph looking for Shapes void parseFile(Mesh *, const vrmllib::file &); void parseNode(Mesh *, const vrmllib::node *, Matrix4 = Matrix4::IDENTITY); // generate a SubMesh from a Shape void parseShape(Mesh *, const Shape *, Matrix4); // helpers: Ogre::MaterialPtr parseMaterial(const Appearance *, const String &name); void parseFaces(FaceVec &, const IndexedFaceSet *); void triangulateAndExpand(TriVec &, VertVec &, const FaceVec &, const Shape *); void copyToSubMesh(SubMesh *, const TriVec &, const VertVec &, const Shape *, const Matrix4 &); // get the array index for a VRML vertex property int getIndex(const IntVec &coordIndex, const IntVec &, bool perVertex, int facenr, int vertnr); // used by findName* typedef std::map NameMap; NameMap gNameMap; // find name of DEFined node const String *findName(const vrmllib::node *n); const String *findNameRecursive(const vrmllib::node *n); String gBaseName; // base name of the input file (no path or extension) // conversion functions: inline Vector3 vec(const vrmllib::vec3 &v) { return Vector3(v.x, v.y, v.z); } inline ColourValue col(const vrmllib::col3 &c) { return ColourValue(c.r, c.g, c.b); } inline void copyVec(Real *d, const Vector3 &s) { d[0] = s.x; d[1] = s.y; d[2] = s.z; } inline void copyVec(Real *d, const vec2 &s) { d[0] = s.x; d[1] = s.y; } Matrix4 transMat(vrmllib::vec3, bool inverse = false); Matrix4 scaleMat(vrmllib::vec3, bool inverse = false); Matrix4 rotMat(vrmllib::rot, bool inverse = false); int main(int argc, char **argv) { try { String inname, outname, path; if (argc != 2 && argc != 3) throw "Wrong number of arguments.\n" " Usage: VRML2mesh [output mesh file]"; inname = argv[1]; // get base name gBaseName = inname; size_t p = gBaseName.find_last_of("/\\"); if (p != gBaseName.npos) { path.assign(gBaseName, 0, p+1); gBaseName.erase(0, p+1); } p = gBaseName.rfind('.'); if (p != gBaseName.npos) gBaseName.erase(p); if (argc == 3) outname = argv[2]; else outname = path + gBaseName + ".mesh"; LogManager log; log.createLog(path + "VRML2mesh.log"); Math math; ResourceGroupManager resGrpMgr; MaterialManager materialMgr; MeshSerializer meshSer; Mesh mesh("conversionTarget"); try { log.logMessage("Reading " + inname); // read VRML file std::ifstream infile(inname.c_str()); if (!infile.is_open()) throw "failed to open input file"; vrmllib::file vfile(infile); log.logMessage("Finished parsing VRML file"); // populate name map for (vrmllib::file::defs_t::iterator i=vfile.defs.begin(), e=vfile.defs.end(); i!=e; ++i) gNameMap[i->second] = i->first; // search from SubMeshes parseFile(&mesh, vfile); if (mesh.getNumSubMeshes() == 0) throw "No SubMeshes were generated, aborting."; log.logMessage("Exporting Mesh"); meshSer.exportMesh(&mesh, outname, true); log.logMessage("Done."); } catch (const char *e) { log.logMessage(LML_NORMAL, "Error: %s", e); return 1; } catch (std::exception &e) { log.logMessage(LML_NORMAL, "Exception: %s", e.what()); return 1; } catch (Exception &e) { log.logMessage("Exception: " + e.getFullDescription()); return 1; } } catch (Exception &e) { std::cerr << "Exception: " << e.getFullDescription() << std::endl; return 1; } catch (const char *e) { std::cerr << e << std::endl; return 1; } } void parseFile(Mesh *mesh, const vrmllib::file &file) { for (vrmllib::file::roots_t::const_iterator i=file.roots.begin(), e=file.roots.end(); i!=e; ++i) parseNode(mesh, *i); } void parseNode(Mesh *mesh, const vrmllib::node *n, Matrix4 m) { if (const Transform *tr = dynamic_cast(n)) { // TODO: handle center, scaleOrientation Matrix4 trans; trans.makeTrans(vec(tr->translation)); Matrix4 scale = Matrix4::IDENTITY; scale[0][0] = tr->scale.x; scale[1][1] = tr->scale.y; scale[2][2] = tr->scale.z; Matrix3 rot3; rot3.FromAxisAngle(vec(tr->rotation.vector), tr->rotation.radians); Matrix4 rot = Matrix4::IDENTITY; rot = rot3; m = m * transMat(tr->translation) * transMat(tr->center) * rotMat(tr->rotation) * rotMat(tr->scaleOrientation) * scaleMat(tr->scale) * rotMat(tr->scaleOrientation, true) * transMat(tr->center, true); } if (const grouping_node *gn = dynamic_cast(n)) { for (std::vector::const_iterator i=gn->children.begin(), e=gn->children.end(); i!=e; ++i) parseNode(mesh, *i, m); } else if (const Shape *sh = dynamic_cast(n)) parseShape(mesh, sh, m); } void parseShape(Mesh *mesh, const Shape *sh, Matrix4 mat) { try { LogManager &log = LogManager::getSingleton(); log.logMessage("Found a Shape..."); IndexedFaceSet *ifs = dynamic_cast(sh->geometry); if (!ifs) throw "Geometry was not an IndexedFaceSet, keep looking"; Coordinate *coord = dynamic_cast(ifs->coord); if (!coord) throw "Invalid Coordinate node"; if (coord->point.empty()) throw "No coordinates found, ignoring this Shape"; SubMesh *sub; if (const String *name = findNameRecursive(sh)) { log.logMessage("Creating SubMesh: " + *name); sub = mesh->createSubMesh(*name); } else { log.logMessage("Creating unnamed SubMesh"); sub = mesh->createSubMesh(); } Appearance *app = dynamic_cast(sh->appearance); TextureCoordinate *tcs = dynamic_cast(ifs->texCoord); Normal *norm = dynamic_cast(ifs->normal); Color *color = dynamic_cast(ifs->color); String message = "Found: geometry"; if (tcs) message += ", texcoords"; if (norm) message += ", normals"; if (color) message += ", colours"; log.logMessage(message); if (!tcs && !norm && !color) log.logMessage("Warning: OGRE will refuse to render SubMeshes that have neither\n" "\ttexture coordinates, normals or vertex colours."); if (!norm) { log.logMessage("Warning: No normals found.\n" "\tVRML dictates that normals should be generated, but this program\n" "\tdoes not do so. If you want the resulting mesh to contain normals,\n" "\tmake sure they are exported."); } // process material static std::map matMap; Ogre::MaterialPtr material = matMap[app]; if (!material->isNull() && app) { log.logMessage("Using material " + material->getName()); sub->setMaterialName(material->getName()); } else { String matName; const String *mn; if (mn = findName(app)) matName = *mn; else if (app && (mn = findName(app->material))) { static std::map postfix; std::stringstream ss; int &num = postfix[*mn]; ss << *mn << '/' << num++; matName = ss.str(); } else { static int matNum; std::stringstream ss; ss << gBaseName << '/' << matNum++; matName = ss.str(); log.logMessage("No material name found, using " + matName); } log.logMessage("Reading material " + matName); material = parseMaterial(app, matName); sub->setMaterialName(matName); } FaceVec faces; parseFaces(faces, ifs); VertVec vertices; TriVec triangles; log.logMessage("Processing geometry..."); triangulateAndExpand(triangles, vertices, faces, sh); copyToSubMesh(sub, triangles, vertices, sh, mat); log.logMessage("Done with this SubMesh."); } catch (const char *e) { LogManager::getSingleton().logMessage(e); } } void copyToSubMesh(SubMesh *sub, const TriVec &triangles, const VertVec &vertices, const Shape *sh, const Matrix4 &mat) { IndexedFaceSet *ifs = dynamic_cast(sh->geometry); Coordinate *coord = dynamic_cast(ifs->coord); TextureCoordinate *tcs = dynamic_cast(ifs->texCoord); Normal *norm = dynamic_cast(ifs->normal); Color *color = dynamic_cast(ifs->color); int nvertices = vertices.size(); int nfaces = triangles.size(); GeometryData &geom = sub->geometry; sub->useSharedVertices = false; sub->numFaces = nfaces; sub->faceVertexIndices = new unsigned short[nfaces*3]; geom.hasColours = color; geom.hasNormals = norm; geom.numTexCoords = tcs ? 1 : 0; geom.numTexCoordDimensions[0] = 2; geom.numVertices = nvertices; geom.pVertices = new Real[nvertices*3]; if (tcs) geom.pTexCoords[0] = new Real[nvertices*2]; if (norm) geom.pNormals = new Real[nvertices*3]; if (color) geom.pColours = new unsigned long[nvertices]; Matrix3 normMat; mat.extract3x3Matrix(normMat); normMat = normMat.Inverse().Transpose(); // populate face list for (int i=0; i!=nfaces; ++i) { unsigned short *f = sub->faceVertexIndices + i*3; f[0] = triangles[i].vertices[0]; f[1] = triangles[i].vertices[1]; f[2] = triangles[i].vertices[2]; } // populate vertex arrays for (int i=0; i!=nvertices; ++i) { const vertex &v = vertices[i]; Real *pos = geom.pVertices + i*3; Real *tc = geom.pTexCoords[0] + i*2; Real *n = geom.pNormals + i*3; unsigned long *col = geom.pColours + i; copyVec(pos, mat * vec(coord->point[v.pos])); if (norm) { Vector3 t = normMat * vec(norm->vector[v.normal]); t.normalise(); copyVec(n, t); } if (tcs) copyVec(tc, tcs->point[v.tc]); if (color) { col3 c = color->color[v.colour]; ColourValue cv(c.r, c.g, c.b); *col = cv.getAsLongRGBA(); } } } void triangulateAndExpand(TriVec &triangles, VertVec &vertices, const FaceVec &faces, const Shape *sh) { IndexedFaceSet *ifs = dynamic_cast(sh->geometry); Coordinate *coord = dynamic_cast(ifs->coord); TextureCoordinate *tcs = dynamic_cast(ifs->texCoord); Normal *norm = dynamic_cast(ifs->normal); Color *color = dynamic_cast(ifs->color); VertMap vertexMap; // triangulate and expand vertices for (FaceVec::const_iterator f=faces.begin(), e=faces.end(); f!=e; ++f) { int faceNr = f - faces.begin(); int triVertNr = 0; int triVerts[2] = { -1, -1 }; for (IntVec::const_iterator i = f->indices.begin(), e=f->indices.end(); i!=e; ++i, ++triVertNr) { int triVertNr = i - f->indices.begin(); int index = *i; vertex vert; // get full indices for vertex data vert.pos = ifs->coordIndex[index]; vert.normal = norm ? getIndex(ifs->coordIndex, ifs->normalIndex, ifs->normalPerVertex, faceNr, index) : 0; vert.colour = color ? getIndex(ifs->coordIndex, ifs->colorIndex, ifs->colorPerVertex, faceNr, index) : 0; vert.tc = tcs ? getIndex(ifs->coordIndex, ifs->texCoordIndex, true, faceNr, index) : 0; // avoid duplication int nvert = vertexMap.size(); int &vpos = vertexMap[vert]; if (nvert != vertexMap.size()) { vpos = vertices.size(); vertices.push_back(vert); } // emit triangle (maybe) if (triVertNr == 0) triVerts[0] = vpos; else if (triVertNr == 1) triVerts[1] = vpos; else { triangle t; t.vertices[0] = triVerts[0]; t.vertices[1] = triVerts[1]; t.vertices[2] = vpos; if (!ifs->ccw) std::swap(t.vertices[1], t.vertices[2]); triangles.push_back(t); triVerts[1] = vpos; } } } } int getIndex(const IntVec &coordIndex, const IntVec &vec, bool perVertex, int facenr, int index) { if (!perVertex) { if (!vec.empty()) return vec[facenr]; else return facenr; } else { if (!vec.empty()) return vec[index]; else return coordIndex[index]; } } const String *findName(const vrmllib::node *n) { NameMap::const_iterator i = gNameMap.find(n); if (i == gNameMap.end()) return 0; else return &i->second; } const String *findNameRecursive(const vrmllib::node *n) { if (const String *name = findName(n)) return name; else if (n->parent) return findNameRecursive(n->parent); else return 0; } void parseFaces(FaceVec &faces, const IndexedFaceSet *ifs) { face f; for (IntVec::const_iterator i=ifs->coordIndex.begin(), e=ifs->coordIndex.end(); i!=e; ++i) { if (*i == -1) { faces.resize(faces.size()+1); faces.back().indices.swap(f.indices); } else f.indices.push_back(i - ifs->coordIndex.begin()); } if (!f.indices.empty()) { faces.resize(faces.size()+1); faces.back().indices.swap(f.indices); } } Ogre::MaterialPtr parseMaterial(const Appearance *app, const String &name) { vrmllib::Material *vm = app ? dynamic_cast(app->material) : 0; vrmllib::ImageTexture *texture = app ? dynamic_cast(app->texture) : 0; Ogre::MaterialPtr m = MaterialManager::getSingleton().create(name, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); ColourValue diffuse = texture ? ColourValue::White : col(vm->diffuseColor); // diffuse colour is unused by VRML when a texture is avaliable, // set to white to give the same effect in OGRE ColourValue a = diffuse; a.r *= vm->ambientIntensity; a.g *= vm->ambientIntensity; a.b *= vm->ambientIntensity; m->setAmbient(a); m->setDiffuse(diffuse); m->setSelfIllumination(col(vm->emissiveColor)); m->setShininess(vm->shininess); m->setSpecular(col(vm->specularColor)); m->setLightingEnabled(app); if (texture && !texture->url.empty()) { String texName = texture->url.front(); size_t p = texName.find_last_of("/\\"); if (p != texName.npos) { LogManager::getSingleton().logMessage("Stripping path from texture " + texName); texName.erase(0, p+1); } LogManager::getSingleton().logMessage("Adding texture layer for " + texName); Ogre::TextureUnitState *l = m->addTextureLayer(texName); l->setTextureAddressingMode(texture->repeatS ? Ogre::TextureUnitState::TAM_WRAP : Ogre::TextureUnitState::TAM_CLAMP); } return m; } Matrix4 transMat(vrmllib::vec3 v, bool inverse) { if (inverse) return Matrix4::getTrans(-v.x, -v.y, -v.z); else return Matrix4::getTrans(v.x, v.y, v.z); } Matrix4 scaleMat(vrmllib::vec3 v, bool inverse) { if (inverse) return Matrix4::getScale(1/v.x, 1/v.y, 1/v.z); else return Matrix4::getScale(v.x, v.y, v.z); } Matrix4 rotMat(vrmllib::rot r, bool inverse) { Matrix3 rot3; rot3.FromAxisAngle(vec(r.vector), inverse ? -r.radians : r.radians); Matrix4 rot = Matrix4::IDENTITY; rot = rot3; return rot; }