// cut a chunk of the lattice out to roughly match the shape static void cut_lattice(const SDF& sdf, TetMesh& mesh, std::vector<float>& vphi) // phi evaluated at vertices { std::map<Vec3i,int> lattice_map; // loop over tiles that overlap bounding box int i, j, k; for (k=0; k<sdf.phi.nk; k+=4) for (j=0; j<sdf.phi.nj; j+=4) for (i=0; i<sdf.phi.ni; i+=4) { for (int t=0; t<num_lattice_tets; ++t) { // check this tet is entirely inside the grid & has a negative phi bool good_tet = true; bool negative_phi = false; for (int u=0; u<4; ++u) { int a = lattice_node[lattice_tet[t][u]][0]+i; if (a >= sdf.phi.ni) { good_tet=false; break; } int b = lattice_node[lattice_tet[t][u]][1]+j; if (b >= sdf.phi.nj) { good_tet=false; break; } int c = lattice_node[lattice_tet[t][u]][2]+k; if (c >= sdf.phi.nk) { good_tet=false; break; } if (sdf.phi(a,b,c) < 0) negative_phi = true; } if (good_tet && negative_phi) { // add vertices if they don't exist yet, find actual tet to add Vec4i actual_tet(-1,-1,-1,-1); for (int u=0; u<4; ++u) { Vec3i vertex=lattice_node[lattice_tet[t][u]]+Vec3i(i,j,k); std::map<Vec3i,int>::iterator p = lattice_map.find(vertex); if (p == lattice_map.end()) { actual_tet[u] = mesh.vSize(); lattice_map[vertex] = actual_tet[u]; mesh.verts().push_back(sdf.origin+sdf.dx*Vec3f(vertex)); vphi.push_back(sdf.phi(vertex[0],vertex[1],vertex[2])); } else { actual_tet[u]=p->second; } } mesh.tets().push_back(actual_tet); } } } }
// assuming vphi[i] and vphi[j] have different signs, find or create a new // vertex on the edge between them where phi interpolates to zero. static int cut_edge(int i, int j, TetMesh& mesh, std::vector<float> &vphi, std::map<Vec2i,int> &cut_map) { assert((vphi[i]<0 && vphi[j]>0) || (vphi[i]>0 && vphi[j]<0)); Vec2i edge(min(i,j), max(i,j)); std::map<Vec2i,int>::iterator p = cut_map.find(edge); if (p == cut_map.end()) { // this edge hasn't been cut yet? int v = (int)mesh.vSize(); float alpha = vphi[i]/(vphi[i]-vphi[j]); mesh.verts().push_back((1-alpha)*mesh.V(i) + alpha*mesh.V(j)); vphi.push_back(0); cut_map[edge] = v; return v; } else { // already done this edge return p->second; } }
void make_tet_mesh(TetMesh& mesh, const SDF& sdf, FeatureSet& featureSet, bool optimize, bool intermediate, bool unsafe) { // Initialize exact arithmetic initialize_exact(); // Initialize mesh from acute lattice. std::vector<float> vphi; // SDF value at each lattice vertex. mesh.verts().resize(0); mesh.tets().resize(0); std::cout<<" cutting from lattice"<<std::endl; cut_lattice(sdf, mesh, vphi); if (intermediate) { static const char* cutLatticeFile = "1_cut_lattice.tet"; static const char* cutLatticeInfo = "1_cut_lattice.info"; mesh.writeToFile(cutLatticeFile); mesh.writeInfoToFile(cutLatticeInfo); std::cout << "Mesh written to " << cutLatticeFile << std::endl; } // Warp any vertices close enough to phi=0 to fix sign crossings. static const float WARP_THRESHOLD = 0.3; std::cout<<" warping vertices"<<std::endl; warp_vertices(WARP_THRESHOLD, mesh, vphi); if (intermediate) { static const char* warpVerticesFile = "2_warp_vertices.tet"; static const char* warpVerticesInfo = "2_warp_vertices.info"; mesh.writeToFile(warpVerticesFile); mesh.writeInfoToFile(warpVerticesInfo); std::cout << "Mesh written to " << warpVerticesFile << std::endl; } // Cut through tets that still poke out of the level set std::cout<<" trimming spikes"<<std::endl; trim_spikes(mesh, vphi); if (intermediate) { static const char* trimSpikesFile = "3_trim_spikes.tet"; static const char* trimSpikesInfo = "3_trim_spikes.info"; mesh.writeToFile(trimSpikesFile); mesh.writeInfoToFile(trimSpikesInfo); std::cout << "Mesh written to " << trimSpikesFile << std::endl; } // At this point, there could be some bad tets with all four vertices on // the surface but which lie outside the level set. Get rid of them. std::cout<<" removing exterior tets"<<std::endl; remove_exterior_tets(mesh, vphi, sdf); // Compact mesh and clear away unused vertices. std::cout<<" compacting mesh"<<std::endl; mesh.compactMesh(); if (intermediate) { static const char* removeExtFile = "4_remove_exterior.tet"; static const char* removeExtInfo = "4_remove_exterior.info"; static const char* removeExtObj = "4_remove_exterior.obj"; mesh.writeToFile(removeExtFile); mesh.writeInfoToFile(removeExtInfo); std::vector<Vec3f> objVerts; std::vector<Vec3i> objTris; mesh.getBoundary(objVerts, objTris); write_objfile(objVerts, objTris, removeExtObj); std::cout << "Mesh written to " << removeExtFile << std::endl; } // Compute maximum dihedral angle of mesh (unoptimized, no features). std::cout << " Maximum dihedral angle = " << max_dihedral_angle(mesh) << std::endl; if (featureSet.numFeatures() > 0 || optimize) { // Identify boundary vertices std::vector<int> boundary_verts; std::vector<Vec3i> boundary_tris; std::cout << " identifying boundary" << std::endl; mesh.getBoundary(boundary_verts, boundary_tris); assert(!boundary_verts.empty()); // Snap vertices to given features std::vector<int> feature_endpoints; std::map<int, int> vertex_feature_map; if (featureSet.numFeatures() > 0) { // Move vertices to match desired features std::cout << " matching features" << std::endl; clock_t featureTime = clock(); match_features(mesh, featureSet, sdf.dx, boundary_verts, boundary_tris, feature_endpoints, vertex_feature_map, unsafe); featureTime = clock() - featureTime; std::cout << " features matched in " << ((float)featureTime)/CLOCKS_PER_SEC << " seconds." << std::endl; std::cout << " Maximum dihedral angle = " << max_dihedral_angle(mesh) << std::endl; if (optimize && intermediate) { static const char* matchFeaturesFile = "5_match_features.tet"; static const char* matchFeaturesInfo = "5_match_features.info"; mesh.writeToFile(matchFeaturesFile); mesh.writeInfoToFile(matchFeaturesInfo); std::cout << "Mesh written to " << matchFeaturesFile << std::endl; } } // Finish by optimizing the tetmesh, if desired. if (optimize) { std::cout << " optimizing mesh" << std::endl; clock_t optTime = clock(); optimize_tet_mesh(mesh, sdf, boundary_verts, featureSet, feature_endpoints, vertex_feature_map); optTime = clock() - optTime; std::cout << " Mesh optimization completed in " << ((float)optTime)/CLOCKS_PER_SEC << " seconds." << std::endl; std::cout<< " Maximum dihedral angle = " << max_dihedral_angle(mesh) << std::endl; } // DEBUGGING // Check for inverted tets for (size_t t = 0; t < mesh.tSize(); ++t) { Tet tet = mesh.getTet(t); float signedVolume = tet.volume(); if (signedVolume <= 0.0) { std::cerr << "Tet #" << t << " is inverted! " << "Volume = " << signedVolume << "; " << "Aspect = " << tet.aspectRatio() << std::endl << "{" << tet[0] << "} {" << tet[1] << "} {" << tet[2] << "} {" << tet[3] << "}" << std::endl; } } } }