void ObjMeshRender::renderGroupEdges(const char * groupName) { //get the group string name(groupName); ObjMesh::Group group = mesh->getGroup(name); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glEnable(GL_POLYGON_OFFSET_LINE); glPolygonOffset(1.0, 1.0); for( unsigned int iFace = 0; iFace < group.getNumFaces(); ++iFace ) { ObjMesh::Face face = group.getFace(iFace); glBegin(GL_POLYGON); for( unsigned int iVertex = 0; iVertex < face.getNumVertices(); ++iVertex ) { const ObjMesh::Vertex * vertexHandle = face.getVertexHandle(iVertex); Vec3d v = mesh->getPosition(*vertexHandle); glVertex3d(v[0], v[1], v[2]); } glEnd(); } glDisable(GL_POLYGON_OFFSET_LINE); }
ObjMesh * ObjMeshOrientable::GenerateOrientedMesh() { ObjMesh * outputObjMesh = new ObjMesh(*objMesh); for(unsigned int i=0; i < outputObjMesh->getNumGroups(); i++) // over all groups { ObjMesh::Group * groupHandle = (ObjMesh::Group*) outputObjMesh->getGroupHandle(i); for (unsigned int j=0; j < groupHandle->getNumFaces(); j++) // over all faces { const ObjMesh::Face * face = groupHandle->getFaceHandle(j); if (face->getNumVertices() < 3) { printf("Warning: encountered a face with fewer than 3 vertices.\n"); continue; } HalfEdge edge = edgeAtFace(i, j); // loop until edge is first edge unsigned int startPosition = face->getVertex(0).getPositionIndex(); while (edgeStartVertex(edge).getPositionIndex() != startPosition) edge = edgeNext(edge); // reverse the face if not correctly oriented if (edgeEndVertex(edge).getPositionIndex() != face->getVertex(1).getPositionIndex()) groupHandle->reverseFace(j); } } return outputObjMesh; }
int main( int argc, char** argv ) { const int numFixedArguments = 3; if( argc < numFixedArguments ) { std::cout << "Merges several obj meshes into one. This routine uses the \"objMesh\" class to load the meshes. It can also merge materials." << std::endl; std::cout << "Usage: " << argv[0] << " [list of obj files] [output file] [-m]" << std::endl; std::cout << " -m : also output materials" << std::endl; std::cout << " -d : (if -m) also remove duplicate materials" << std::endl; return 1; } bool outputMaterials = false; bool removeDuplicatedMaterials = false; char * objListFilename = argv[1]; char * objMeshname = argv[2]; opt_t opttable[] = { { (char*)"m", OPTBOOL, &outputMaterials }, { (char*)"d", OPTBOOL, &removeDuplicatedMaterials }, { NULL, 0, NULL } }; argv += numFixedArguments-1; argc -= numFixedArguments-1; int optup = getopts(argc,argv,opttable); if (optup != argc) { printf("Error parsing options. Error at option %s.\n",argv[optup]); return 1; } ObjMesh outputObjMesh; char s[4096]; FILE * fin; char fileMode[96] = "r"; OpenFile_(objListFilename, &fin, fileMode); while(fgets(s,4096,fin) != 0) { if(s[strlen(s)-1] == '\n') s[strlen(s)-1] = '\0'; printf("%s\n",s); ObjMesh * objMesh = new ObjMesh(s); // add vertices int numVerticesCurrent = outputObjMesh.getNumVertices(); for(unsigned int i=0; i<objMesh->getNumVertices(); i++) outputObjMesh.addVertexPosition(objMesh->getPosition(i)); // add normals int numNormalsCurrent = outputObjMesh.getNumNormals(); for(unsigned int i=0; i<objMesh->getNumNormals(); i++) outputObjMesh.addVertexNormal(objMesh->getNormal(i)); // add texture coordinates int numTextureCoordinatesCurrent = outputObjMesh.getNumTextureCoordinates(); for(unsigned int i=0; i<objMesh->getNumTextureCoordinates(); i++) outputObjMesh.addTextureCoordinate(objMesh->getTextureCoordinate(i)); // add materials int numMaterialsCurrent = outputObjMesh.getNumMaterials(); for(unsigned int i=0; i<objMesh->getNumMaterials(); i++) outputObjMesh.addMaterial(objMesh->getMaterial(i)); for(unsigned int i=0; i<objMesh->getNumGroups(); i++) { const ObjMesh::Group * group = objMesh->getGroupHandle(i); outputObjMesh.addGroup(group->getName()); unsigned int newGroupID = outputObjMesh.getNumGroups() - 1; ObjMesh::Group * newGroup = (ObjMesh::Group*) outputObjMesh.getGroupHandle(newGroupID); newGroup->setMaterialIndex(numMaterialsCurrent + group->getMaterialIndex()); // over all faces in the group of the current obj file for(unsigned int j=0; j<group->getNumFaces(); j++) { const ObjMesh::Face * face = group->getFaceHandle(j); for(unsigned int k=0; k<face->getNumVertices(); k++) { ObjMesh::Vertex * vertex = (ObjMesh::Vertex*) face->getVertexHandle(k); vertex->setPositionIndex(vertex->getPositionIndex() + numVerticesCurrent); if (vertex->hasNormalIndex()) vertex->setNormalIndex(vertex->getNormalIndex() + numNormalsCurrent); if (vertex->hasTextureCoordinateIndex()) vertex->setTextureCoordinateIndex(vertex->getTextureCoordinateIndex() + numTextureCoordinatesCurrent); } outputObjMesh.addFaceToGroup(*face,newGroupID); } } delete(objMesh); } fclose(fin); if (outputMaterials) { if (removeDuplicatedMaterials) { printf("Removing duplicated materials..."); fflush(NULL); int numNewMaterials = outputObjMesh.removeDuplicatedMaterials(); printf(" retained %d materials.\n", numNewMaterials); } outputObjMesh.save(objMeshname, 1); } else { outputObjMesh.save(objMeshname); } return(0); }
// returns the number of edges flipped int ObjMeshOrientable::GenerateHalfEdgeDataStructure() { std::cout << "Building the half edge data structure..." << std::endl; // Step 1: iterate over all faces // for each face, add all the edges onto the list of half-edges std::cout << "Step 1: Generating the list of half edges..." << std::endl; typedef std::vector<ObjMesh::Group> SGroup; int coutCounter = 0; for(unsigned int i = 0; i < objMesh->getNumGroups(); i++ ) { const ObjMesh::Group * currentGroup = objMesh->getGroupHandle(i); std::cout << " Processing obj group '" << currentGroup->getName() << std::endl; std::cout << " Iterating through group faces..." << std::endl; for( unsigned int iFace = 0; iFace < currentGroup->getNumFaces(); ++iFace ) { ObjMesh::Face face = currentGroup->getFace(iFace); // get face whose number is iFace if (coutCounter < 100) { std::cout << face.getNumVertices() ; coutCounter++; } if (coutCounter == 100) { cout << "...[and more]"; coutCounter++; } unsigned int edgesSoFar = halfEdges_.size(); for ( unsigned int iVertex = 0; iVertex < face.getNumVertices(); ++iVertex ) { // create a half edge for each edge, store -1 for half-edge adjacent edge for now // index vertices starting from 0 int nextEdge = edgesSoFar + ((iVertex + 1) % face.getNumVertices()); HalfEdge halfEdge(edgesSoFar + iVertex, face.getVertex(iVertex).getPositionIndex(), face.getVertex((iVertex + 1) % face.getNumVertices()).getPositionIndex(), iVertex, (iVertex + 1) % face.getNumVertices(), i, iFace, -1, nextEdge); halfEdges_.push_back(halfEdge); } } std::cout << std::endl; } /* for (unsigned int i=0; i<halfEdges_.size(); i++) { cout << "Half edge "<< i << " :" << endl; cout << " Opposite edge: " << halfEdges_[i].opposite() << endl; cout << " Next edge: " << halfEdges_[i].next() << endl; cout << " Group: " << halfEdges_[i].groupID() << endl; cout << " Face: " << halfEdges_[i].face() << endl; cout << " Start vertex: " << halfEdges_[i].startVertex() << endl; cout << " End vertex: " << halfEdges_[i].endVertex() << endl; cout << " Start vertex (local): " << halfEdges_[i].startV() << endl; cout << " End vertex (local): " << halfEdges_[i].endV() << endl; cout << " Is boundary: " << halfEdges_[i].isBoundary() << endl; } */ // Step 2: build correspondence among half-dges // for each half-edge, search for the opposite half-edge, if it exists std::cout << "Step 2: Building correspondence among half-edges..." << std::endl; std::cout << "Boundary edges: "; // insert all edges into a binary tree typedef std::multimap<std::pair< unsigned int, unsigned int > , unsigned int> BinaryTree; BinaryTree edges; for (unsigned int i=0; i < halfEdges_.size(); i++) { int vertex1 = halfEdges_[i].startVertex(); int vertex2 = halfEdges_[i].endVertex(); if (vertex1 == vertex2) { std::cout << "Error: encountered a degenerated edge with equal starting and ending vertex." << std::endl; std::cout << " Group:" << halfEdges_[i].groupID() << " Face #: " << halfEdges_[i].face() << "Vertex ID: " << vertex1 << std::endl; exit(1); } if (vertex1 > vertex2) // swap { int buffer = vertex1; vertex1 = vertex2; vertex2 = buffer; } std::pair<unsigned int, unsigned int> vertices(vertex1,vertex2); edges.insert(std::make_pair(vertices,i)); } // retrieve one by one and build correspondence for (unsigned int i=0; i < halfEdges_.size(); i++) { int vertex1 = halfEdges_[i].startVertex(); int vertex2 = halfEdges_[i].endVertex(); if (vertex1 > vertex2) // swap { int buffer = vertex1; vertex1 = vertex2; vertex2 = buffer; } std::pair<unsigned int, unsigned int> vertices(vertex1,vertex2); // search for the edge int hits = 0; int candidates = 0; BinaryTree::iterator pos; for (pos = edges.lower_bound(vertices); pos != edges.upper_bound(vertices); ++pos) { candidates++; // check if we found ourselves if (pos->second != i) { // not ourselves halfEdges_[i].setOpposite(pos->second); hits++; } } if (candidates >= 3) { std::cout << "Error: encountered an edge that appears in more than two triangles. Geometry is non-manifold. Exiting." << std::endl; int faceNum = halfEdges_[i].face(); std::cout << " Group:" << halfEdges_[i].groupID() << std::endl << " Face #: " << faceNum << std::endl; std::cout << " Edge: " << vertex1 << " " << vertex2 << std::endl; std::cout << " Vertices: " << objMesh->getPosition(vertex1) << " " << objMesh->getPosition(vertex2) << std::endl; exit(1); } if (hits == 0) // boundary edge { //std::cout << "B"; std::cout << "B(" << vertex1 << "," << vertex2 << ") "; boundaryEdges_.push_back(i); } } std::cout << " total: " << boundaryEdges_.size() << std::endl; /* for (unsigned int i=0; i<halfEdges_.size(); i++) { cout << "Half edge "<< i << " :" << endl; cout << " Opposite edge: " << halfEdges_[i].opposite() << endl; cout << " Next edge: " << halfEdges_[i].next() << endl; cout << " Group: " << halfEdges_[i].groupID() << endl; cout << " Face: " << halfEdges_[i].face() << endl; cout << " Start vertex: " << halfEdges_[i].startVertex() << endl; cout << " End vertex: " << halfEdges_[i].endVertex() << endl; cout << " Start vertex (local): " << halfEdges_[i].startV() << endl; cout << " End vertex (local): " << halfEdges_[i].endV() << endl; cout << " Is boundary: " << halfEdges_[i].isBoundary() << endl; } */ // now, each half-edge knows its mirror edge, but orientations of faces might be inconsistent // orient all half-edges consistently std::cout << "Step 3: Attempting to orient the faces coherently..." << std::endl; // generate marks for all the edges std::vector<int> marks(halfEdges_.size(), 0); // initialize queue std::set<int> queue; connectedComponents = 0; int numOrientationFlips = 0; while(1) // breakable { // find the first un-marked edge and queue it unsigned int unmarkedEdge; for (unmarkedEdge = 0; unmarkedEdge < halfEdges_.size(); unmarkedEdge++) { if (marks[unmarkedEdge] == 0) break; // found an unmarked edge } if (unmarkedEdge == halfEdges_.size()) // no unmarked edge was found { break; // out of while; we are done } else { cout << "Found a new connected component. Seed half-edge is: " << unmarkedEdge << endl; connectedComponents++; queue.insert(unmarkedEdge); while(queue.size() > 0) { int edge = *(queue.begin()); queue.erase(queue.begin()); //std::cout << "Retrieved edge from queue: " << edge << " Queue size: " << queue.size() << std::endl; //cout << "The edge is boundary: " << halfEdges_[edge].isBoundary() << endl; //std::cout << "Marking all the edges on this face: "; // first, mark all the edges on this face as visited int loop = edge; do { marks[loop] = 1; //std::cout << loop << " "; loop = halfEdges_[loop].next(); } while (loop != edge); //std::cout << std::endl; // check if edge is consistent with the opposite edge orientation // careful: edge might be on the boundary // find a non-boundary edge on the same face (if it exists) //std::cout << "Seeking for a non-boundary edge on this face..."; loop = edge; int exitFlag = 0; while ((halfEdges_[loop].isBoundary()) && (exitFlag == 0)) { //cout << loop << " "; loop = halfEdges_[loop].next(); if (loop == edge) // all edges are boundary exitFlag = 1; } if (exitFlag == 1) // all edges are boundary; this is an isolated face { cout << "Encountered an isolated face." << endl; //cout << "none found." << endl; continue; // no need to queue anything or flip anything, this was an isolated face // also, this case can only happen during the first iteration of the while loop, which will also be the last one } edge = loop; // now, edge is a non-boundary halfedge //std::cout << "found non-boundary edge: " << edge << std::endl; //std::cout << "opposite edge is: " << halfEdges_[edge].opposite() << std::endl; bool orientationFlipNecessary = (marks[halfEdges_[edge].opposite()] == 1) && (halfEdges_[edge].startVertex() == (edgeOpposite(halfEdges_[edge])).startVertex()); //std::cout << "Orientation flip necessary for this face: " << orientationFlipNecessary << std::endl; if (orientationFlipNecessary) { // flip all edges along this face //cout << "Orientation flip" << endl; numOrientationFlips++; loop = edge; int cache = 0; do { int nextO = halfEdges_[loop].next(); halfEdges_[loop].setNext(cache); cache = loop; halfEdges_[loop].flipOrientation(); // flip orientation loop = nextO; } while (loop != edge); halfEdges_[loop].setNext(cache); int groupID = halfEdges_[loop].groupID(); int faceID = halfEdges_[loop].face(); ObjMesh::Group * currentGroup = (ObjMesh::Group*) objMesh->getGroupHandle(groupID); currentGroup->getFace(faceID).printVertices(); currentGroup->reverseFace(faceID); currentGroup->getFace(faceID).printVertices(); } // check if new orientation is consistent eveywhere along the face // if not, surface is not orientable // at the same time, queue the opposite edges if they are not marked already loop = edge; do { if (!halfEdges_[loop].isBoundary()) // skip boundary edges { // if opposite unmarked, queue the opposite edge if (marks[halfEdges_[loop].opposite()] == 0) { queue.insert(halfEdges_[loop].opposite()); //marks[halfEdges_[loop].opposite()] = 1; // JNB, 2008 //std::cout << "visiting edge: " << loop << " pushing opposite: " << halfEdges_[loop].opposite() << std::endl; } else { // opposite edge is marked as already visited // if orientation consistent, do nothing // if orientation not consistent, surface is not orientable bool orientationConsistent = (halfEdges_[loop].startVertex() == (edgeOpposite(halfEdges_[loop])).endVertex()); //std::cout << "visiting edge: " << loop << " opposite marked " << std::endl; if (!orientationConsistent) { std::cout << "Error: surface is non-orientable. Offending edge: [" << halfEdges_[loop].startVertex() << "," << halfEdges_[loop].endVertex() << "]" << std::endl; exit(1); } } } loop = halfEdges_[loop].next(); } while (loop != edge); } } } // end of while printf("Consistent orientation generated. Performed %d orientation flips.\n", numOrientationFlips); //PrintHalfEdges(); // step 4: for every vertex, find a half-edge emanating out of it std::cout << "Step 4: For every vertex, caching a half-edge emanating out of it..." << std::endl; std::cout << " For every face, caching a half-edge on it..." << std::endl; for (unsigned int i=0; i< objMesh->getNumVertices(); i++) edgesAtVertices_.push_back(-1); // value of -1 corresponds to no edge (i.e. isolated vertex) for (unsigned int i=0; i < halfEdges_.size(); i++) { //cout << i << " " << halfEdges_[i].startVertex() << " " << halfEdges_[i].endVertex() << endl; edgesAtVertices_[halfEdges_[i].endVertex()] = i; } // if vertex is on the boundary, rotate the edge until it is an incoming boundary edge // rotate edge until it is either on the boundary, or we come around to the same edge int numIsolatedVertices = 0; for (unsigned int i=0; i < objMesh->getNumVertices(); i++) { if (isIsolatedVertex(i)) { numIsolatedVertices++; continue; } HalfEdge * loop = &edgeAtVertex(i); HalfEdge * start = loop; do { if (loop->isBoundary()) { // set the edge to the current edge edgesAtVertices_[i] = loop->position(); break; } loop = &edgePrevious(edgeOpposite(*loop)); } while (*loop != *start); // if we came around, no need to change edgeAtVertices[i] } if (numIsolatedVertices > 0) printf("Warning: mesh has %d isolated vertices.\n", numIsolatedVertices); // build the cache for faces, first reset to -1 for (unsigned int i=0; i < objMesh->getNumGroups(); i++) { const ObjMesh::Group * currentGroup = objMesh->getGroupHandle(i); std::vector<int> dataForThisGroup; dataForThisGroup.clear(); for (unsigned int j=0; j < currentGroup->getNumFaces(); j++) { dataForThisGroup.push_back(-1); } edgesAtFaces_.push_back(dataForThisGroup); } for (unsigned int i=0; i < halfEdges_.size(); i++) edgesAtFaces_[halfEdges_[i].groupID()][halfEdges_[i].face()] = i; // sanity check: none of the face entries should be -1 for (unsigned int i=0; i < objMesh->getNumGroups(); i++) { const ObjMesh::Group * currentGroup = objMesh->getGroupHandle(i); for (unsigned int j=0; j < currentGroup->getNumFaces(); j++) if (edgesAtFaces_[i][j] == -1) cout << "Warning: face on group " << i << "(" << currentGroup->getName() << "), position " << j << " has no edges." << endl; } determineIfSurfaceHasBoundary(); // testing: previous edge capability /* cout << "Testing previous edge capability..." << endl; for (unsigned int i=0; i < halfEdges_.size(); i++) { cout << i << ": " << edgePrevious(halfEdges_[i]).position() << endl; } // testing: print out associated edges for every vertex for (unsigned int i=0; i < vertexPositions_.size(); i++) { cout << "Halfedge into vertex " << i << ": " << edgeAtVertex(i).position() << endl; } // testing: print out associated edges for every face for (unsigned int i=0; i < groups_.size(); i++) for (unsigned int j=0; j < groups_[i].getNumFaces(); j++) { cout << "Halfedge on face " << i << " " << j << ": " << edgeAtFace(i,j).position() << endl; } // testing: loop around every vertex for (unsigned int i=0; i < vertexPositions_.size(); i++) { cout << "Looping around vertex " << i << ":"; int flag = 0; HalfEdge * start = &edgeAtVertex(i); HalfEdge * loop = start; do { cout << loop->position() << " "; if (flag != 0) // boundary edge, exit the loop { cout << " B"; break; } loop = &loopVertex(*loop,flag); } while (loop->position() != start->position()); cout << endl; } */ std::cout << "Half-edge datastructure constructed successfully." << std::endl; std::cout << "Statistics: " << std::endl; std::cout << " Half-edges: " << halfEdges_.size() << std::endl; std::cout << " Boundary half-edges: " << boundaryEdges_.size() << std::endl; std::cout << " Connected components: " << connectedComponents << std::endl; return numOrientationFlips; }
int ObjMeshClose::Close(ObjMesh * objMesh) { ObjMeshOrientable * objMeshOrientable = NULL; try { int * numOrientationFlips = NULL; int verbose=0; objMeshOrientable = new ObjMeshOrientable(objMesh, 1, numOrientationFlips, verbose); } catch (int) { printf("Mesh is non-orientable.\n"); return 1; } int numBoundaryEdges = (int)objMeshOrientable->numBoundaryEdges(); set<int> boundaryEdges; for(int i=0; i<numBoundaryEdges; i++) boundaryEdges.insert(objMeshOrientable->boundaryEdge(i)); while (boundaryEdges.size() > 0) { int firstEdge = *(boundaryEdges.begin()); vector<int> boundaryVertices; // iterate around the boundary loop int edge = firstEdge; do { //printf("edge %d ", edge); // push vertices into "boundaryVertices", and erase edges from boundaryEdges ObjMeshOrientable::HalfEdge halfEdge = objMeshOrientable->halfEdge(edge); boundaryVertices.push_back(halfEdge.startVertex()); boundaryEdges.erase(edge); // find the next boundary edge ObjMeshOrientable::HalfEdge nextHalfEdge = halfEdge; do { nextHalfEdge = objMeshOrientable->edgeNext(nextHalfEdge); if (nextHalfEdge.isBoundary()) break; nextHalfEdge = objMeshOrientable->edgeOpposite(nextHalfEdge); } while (nextHalfEdge != halfEdge); if (nextHalfEdge == halfEdge) { printf("Mesh is non-orientable (cannot detect next edge along the hole).\n"); return 2; } edge = nextHalfEdge.position(); } while (edge != firstEdge); //printf("\n"); // compute centroid Vec3d avg(0,0,0); for(unsigned int i=0; i<boundaryVertices.size(); i++) avg += objMesh->getPosition(boundaryVertices[i]); avg /= boundaryVertices.size(); int numVertices = objMesh->getNumVertices(); objMesh->addVertexPosition(avg); // find group containing edge "firstEdge" ObjMeshOrientable::HalfEdge firstHalfEdge = objMeshOrientable->halfEdge(firstEdge); unsigned int group = firstHalfEdge.groupID(); ObjMesh::Group * groupHandle = (ObjMesh::Group*) objMesh->getGroupHandle(group); // add triangles to close the face (a "tent") //printf("Adding triangles:\n"); for(unsigned int i=0; i<boundaryVertices.size(); i++) { int vtxIndex[3] = { numVertices, boundaryVertices[(i+1) % boundaryVertices.size()], boundaryVertices[i] }; //printf("[%d %d %d] ", vtxIndex[0], vtxIndex[1], vtxIndex[2]); // compute flat normal Vec3d normal = norm(cross(objMesh->getPosition(vtxIndex[1]) - objMesh->getPosition(vtxIndex[0]), objMesh->getPosition(vtxIndex[2]) - objMesh->getPosition(vtxIndex[0]) )); // add the normal int numNormals = objMesh->getNumNormals(); objMesh->addVertexNormal(normal); // create and add new face ObjMesh::Vertex vertex0(vtxIndex[0]); vertex0.setNormalIndex(numNormals); ObjMesh::Vertex vertex1(vtxIndex[1]); vertex1.setNormalIndex(numNormals); ObjMesh::Vertex vertex2(vtxIndex[2]); vertex2.setNormalIndex(numNormals); ObjMesh::Face face(vertex0, vertex1, vertex2); groupHandle->addFace(face); } //printf("\n"); } return 0; }