├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── app ├── edit_labeling.cpp ├── evolabel.cpp ├── figure_generator.cpp ├── flagging_viewer.cpp ├── igl_core.cpp ├── init_from_folder.cpp ├── jacob_test.cpp ├── measurement.cpp ├── polycube_withHexEx.cpp ├── supplemental_generator.cpp ├── synthesize_data.cpp ├── test_scaled_jacobian.cpp └── viz_tet.cpp ├── data ├── grey_checkerboard.png └── moai.obj ├── images └── lowres_evocube_teaser.png ├── include ├── HexExWrapper.h ├── MyEmbreeRenderer.h ├── archive.h ├── chart.h ├── disjointset.h ├── distortion.h ├── evaluator.h ├── evocube.h ├── flagging_utils.h ├── graphcut_labeling.h ├── labeling_individual.h ├── labeling_ops.h ├── latex.h ├── logging.h ├── mesh_io.h ├── minSJ.txt ├── quick_label_ev.h ├── scaled_jacobian.h └── tet_boundary.h ├── lib ├── OpenNL_psm │ ├── CMakeLists.txt │ ├── OpenNL_example.c │ ├── OpenNL_psm.c │ ├── OpenNL_psm.h │ └── README.txt └── gco │ ├── CMakeLists.txt │ ├── GCO_README.TXT │ └── include │ ├── GCoptimization.cpp │ ├── GCoptimization.h │ ├── LinkedBlockList.cpp │ ├── LinkedBlockList.h │ ├── block.h │ ├── energy.h │ ├── example.cpp │ ├── graph.cpp │ ├── graph.h │ └── maxflow.cpp ├── scripts ├── obj_to_stl.py ├── plot_SJ_table.ipynb ├── step_to_tet.py └── stl_to_tet.py ├── src ├── HexExWrapper.cpp ├── MyEmbreeRenderer.cpp ├── chart.cpp ├── distortion.cpp ├── flagging_utils.cpp ├── graphcut_labeling.cpp ├── labeling_ops.cpp ├── latex.cpp ├── logging.cpp ├── quick_label_ev.cpp └── tet_boundary.cpp └── supplemental └── cover_page.tex /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | .vscode/* 3 | data/* 4 | .ipynb_checkpoints 5 | # in supplemental/, include only the cover page 6 | # https://riptutorial.com/git/example/911/exceptions-in-a--gitignore-file 7 | !supplemental/ 8 | supplemental/* 9 | !supplemental/cover_page.tex -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/libigl"] 2 | path = lib/libigl 3 | url = https://github.com/libigl/libigl.git 4 | [submodule "lib/json"] 5 | path = lib/json 6 | url = https://github.com/nlohmann/json.git 7 | [submodule "lib/libHexEx"] 8 | path = lib/libHexEx 9 | url = https://gitlab.vci.rwth-aachen.de:9000/HexEx/libHexEx.git 10 | [submodule "lib/OpenVolumeMesh"] 11 | path = lib/OpenVolumeMesh 12 | url = https://gitlab.vci.rwth-aachen.de:9000/OpenVolumeMesh/OpenVolumeMesh.git 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.1) 3 | project(evocube) 4 | set(CMAKE_CXX_STANDARD 17) 5 | file(GLOB SRCFILES src/*.cpp) 6 | 7 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} 8 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/libHexEx/cmake) 9 | set(OPENVOLUMEMESH_INCLUDE_DIR 10 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/OpenVolumeMesh/src) 11 | 12 | add_definitions(-DIGL_VIEWER_VIEWER_QUIET) 13 | set(LIBIGL_USE_STATIC_LIBRARY ON) 14 | add_subdirectory(lib/libigl) 15 | add_subdirectory(lib/gco) 16 | add_subdirectory(lib/OpenNL_psm) 17 | add_subdirectory(lib/OpenVolumeMesh) 18 | add_subdirectory(lib/libHexEx) 19 | 20 | find_package(OpenMP REQUIRED) 21 | 22 | include_directories(include) 23 | include_directories(lib/json/include) 24 | include_directories(lib/gco/include) 25 | include_directories(lib/OpenNL_psm) 26 | include_directories(lib/OpenVolumeMesh) 27 | include_directories(lib/libHexEx/src) 28 | 29 | SET(IGL_VIEWER_LIBS 30 | igl::core 31 | igl::opengl_glfw 32 | igl::opengl_glfw_imgui 33 | ) 34 | 35 | SET(EVOCUBE_LIBS 36 | gco 37 | OpenMP::OpenMP_CXX 38 | OpenNL_psm 39 | OpenVolumeMesh 40 | HexEx 41 | igl::embree 42 | ) 43 | 44 | add_executable(viz_tet app/viz_tet.cpp src/flagging_utils.cpp src/tet_boundary.cpp src/logging.cpp) 45 | target_link_libraries(viz_tet ${IGL_VIEWER_LIBS} igl::tetgen igl::png) 46 | 47 | add_executable(jacob_test app/jacob_test.cpp ${SRCFILES}) 48 | target_link_libraries(jacob_test ${IGL_VIEWER_LIBS} ${EVOCUBE_LIBS}) 49 | 50 | #add_executable(flagging_viewer app/flagging_viewer.cpp ${SRCFILES}) 51 | #target_link_libraries(flagging_viewer ${IGL_VIEWER_LIBS} OpenMP::OpenMP_CXX) 52 | 53 | add_executable(edit_labeling app/edit_labeling.cpp ${SRCFILES}) 54 | target_link_libraries(edit_labeling ${EVOCUBE_LIBS} ${IGL_VIEWER_LIBS}) 55 | 56 | add_executable(evolabel app/evolabel.cpp ${SRCFILES}) 57 | target_link_libraries(evolabel ${IGL_VIEWER_LIBS} ${EVOCUBE_LIBS} igl::tetgen) 58 | 59 | add_executable(init_from_folder app/init_from_folder.cpp ${SRCFILES}) 60 | target_link_libraries(init_from_folder igl::core igl::tetgen ${EVOCUBE_LIBS}) 61 | 62 | add_executable(polycube_withHexEx app/polycube_withHexEx.cpp ${SRCFILES}) 63 | target_link_libraries(polycube_withHexEx igl::core igl::tetgen ${EVOCUBE_LIBS}) 64 | 65 | add_executable(test_scaled_jacobian app/test_scaled_jacobian.cpp ${SRCFILES}) 66 | target_link_libraries(test_scaled_jacobian igl::core igl::tetgen ${EVOCUBE_LIBS}) 67 | 68 | add_executable(measurement app/measurement.cpp ${SRCFILES}) 69 | target_link_libraries(measurement igl::core igl::tetgen ${EVOCUBE_LIBS}) 70 | 71 | add_executable(figure_generator ${SRCFILES} app/figure_generator.cpp) 72 | include_directories(include) 73 | target_link_libraries(figure_generator igl::core igl::tetgen igl::opengl_glfw igl::png ${EVOCUBE_LIBS}) 74 | 75 | add_executable(supplemental_generator ${SRCFILES} app/supplemental_generator.cpp) 76 | include_directories(include) 77 | target_link_libraries(supplemental_generator igl::core igl::tetgen ${EVOCUBE_LIBS}) 78 | 79 | add_executable(synthesize_data ${SRCFILES} app/synthesize_data.cpp) 80 | include_directories(include) 81 | target_link_libraries(synthesize_data igl::core igl::tetgen ${EVOCUBE_LIBS}) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Evocube: a Genetic Labeling Framework for Polycube-Maps 4 | 5 | [Corentin Dumery](https://corentindumery.github.io), 6 | [François Protais](https://www.linkedin.com/in/françois-protais-78b9ab13b/), 7 | [Sébastien Mestrallet](https://sebastienmestrallet.fr/), 8 | [Christophe Bourcier](https://www.researchgate.net/profile/Christophe-Bourcier), 9 | [Franck Ledoux](https://www.linkedin.com/in/franck-ledoux-32b99646/) 10 |
11 |
12 | 13 | Watch the video presentation on the [project page](https://corentindumery.github.io/projects/evocube.html). 14 | 15 | ![alt text](images/lowres_evocube_teaser.png) 16 | 17 | ## Abstract 18 | Polycube-maps are used as base-complexes in various fields of computational geometry, including the generation of regular all-hexahedral meshes free of internal singularities. However, the strict alignment constraints behind polycube-based methods 19 | make their computation challenging for CAD models used in numerical simulation via Finite Element Method (FEM). We 20 | propose a novel approach based on an evolutionary algorithm to robustly compute polycube-maps in this context. 21 | 22 | We address the labeling problem, which aims to precompute polycube alignment by assigning one of the base axes to each 23 | boundary face on the input. Previous research has described ways to initialize and improve a labeling via greedy local fixes. 24 | However, such algorithms lack robustness and often converge to inaccurate solutions for complex geometries. Our proposed 25 | framework alleviates this issue by embedding labeling operations in an evolutionary heuristic, defining fitness, crossover, and 26 | mutations in the context of labeling optimization. We evaluate our method on a thousand smooth and CAD meshes, showing 27 | Evocube converges to valid labelings on a wide range of shapes. The limitations of our method are also discussed thoroughly. 28 | 29 | 30 | ## BUILD 31 | 32 | ### Download 33 | ``` 34 | git clone https://github.com/LIHPC-Computational-Geometry/evocube.git 35 | git submodule update --init --recursive 36 | ``` 37 | 38 | ### Compile 39 | 40 | ``` 41 | mkdir build 42 | cd build 43 | cmake .. 44 | make -j4 evolabel 45 | ``` 46 | 47 | 48 | ### Run 49 | To compute a labeling on a triangle mesh and visualize it, simply run: 50 | ``` 51 | ./evolabel path/to/boundary.obj 52 | ``` 53 | 54 | Paste for a quick test: `./evolabel ../data/moai.obj` 55 | 56 | To go further, advanced tools used in our paper are showcased in `init_from_folder.cpp`. 57 | 58 | ### Test data 59 | 60 | * [Mambo](https://gitlab.com/franck.ledoux/mambo) 61 | * [OM Smooth & CAD](https://cims.nyu.edu/gcl/papers/2019-OctreeMeshing.zip) 62 | * [ABC](https://deep-geometry.github.io/abc-dataset/) 63 | 64 | ### Useful repositories 65 | 66 | * [Advanced polycube hex-meshing pipeline (our labelings can be used as input)](https://github.com/fprotais/robustPolycube) 67 | * [Simpler hex-meshing pipeline with libHexEx](https://github.com/fprotais/polycube_withHexEx) 68 | * [Tet mesh preprocessing](https://github.com/fprotais/preprocess_polycube) 69 | 70 | ## Citation 71 | ``` 72 | @article{dumery:evocube, 73 | title = {{Evocube: a Genetic Labeling Framework for Polycube-Maps}}, 74 | author = {Dumery, Corentin and Protais, Fran{\c c}ois and Mestrallet, S{\'e}bastien and Bourcier, Christophe and Ledoux, Franck}, 75 | url = {https://doi.org/10.1111/cgf.14649}, 76 | journal = {{Computer Graphics Forum}}, 77 | publisher = {{Wiley}}, 78 | year = {2022}, 79 | month = Aug, 80 | doi = {10.1111/cgf.14649}, 81 | volume = {41}, 82 | number = {6}, 83 | pages = {467--479}, 84 | } 85 | ``` 86 | ## License 87 | [GPL3](LICENSE) license 88 | ([FAQ](https://www.gnu.org/licenses/gpl-faq.html)) 89 | 90 | 110 | 111 | -------------------------------------------------------------------------------- /app/edit_labeling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "flagging_utils.h" 11 | #include "labeling_individual.h" 12 | #include "evocube.h" 13 | #include "quick_label_ev.h" 14 | #include "evaluator.h" 15 | 16 | int main(int argc, char *argv[]){ 17 | 18 | Eigen::MatrixXd V; 19 | Eigen::MatrixXi F; 20 | std::string input_path = argv[1]; 21 | igl::readOBJ(input_path, V, F); 22 | double l_avg = igl::avg_edge_length(V, F); 23 | 24 | Eigen::VectorXi labeling = Eigen::VectorXi::Constant(F.rows(), 1); 25 | Eigen::MatrixXd colors; 26 | 27 | std::shared_ptr evo = std::make_shared(Evocube(V, F)); 28 | std::shared_ptr qle = std::make_shared(QuickLabelEv(V, F)); 29 | const Evaluator evaluator(evo, qle); 30 | std::shared_ptr indiv = std::make_shared(LabelingIndividual(evo, qle, labeling)); 31 | 32 | double labeling_score = -1.0; 33 | double fast_poly_score, invalid_score; 34 | int n_fail_invert; 35 | auto updateLabeling = [&](){ 36 | indiv = std::make_shared(LabelingIndividual(evo, qle, labeling)); 37 | indiv->updateChartsAndTPs(true); 38 | labeling_score = evaluator.evaluate(*indiv, fast_poly_score, invalid_score, n_fail_invert); 39 | }; 40 | 41 | 42 | // --- VISUALIZATION --- 43 | 44 | int insert_label = 0; 45 | float insert_size = 1.0; 46 | 47 | igl::opengl::glfw::Viewer viewer; 48 | viewer.data().set_mesh(V, F); 49 | 50 | igl::opengl::glfw::imgui::ImGuiMenu menu; 51 | menu.callback_draw_viewer_window = []() {}; 52 | viewer.plugins.push_back(&menu); 53 | 54 | auto updateViz = [&](){ 55 | viewer.data().clear_edges(); 56 | viewer.data().clear_points(); 57 | colors = colorsFromFlagging(labeling); 58 | viewer.data().set_colors(colors); 59 | 60 | }; 61 | 62 | //helper function for menu 63 | auto make_checkbox = [&](const char *label, unsigned int &option) { 64 | return ImGui::Checkbox( 65 | label, 66 | [&]() { return viewer.core().is_set(option); }, 67 | [&](bool value) { return viewer.core().set(option, value); }); 68 | }; 69 | 70 | menu.callback_draw_custom_window = [&]() { 71 | ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); 72 | ImGui::SetNextWindowSize(ImVec2(350, -1), ImGuiCond_FirstUseEver); 73 | if (ImGui::Begin("IGL")) { 74 | if (ImGui::Button("Quick open labeling")){ 75 | std::string file = input_path.substr(0, input_path.find_last_of("/\\") + 1) + "labeling.txt"; 76 | std::cout << "Attempting to open: " << file << std::endl; 77 | labeling = openFlagging(file, F.rows()); 78 | updateLabeling(); 79 | updateViz(); 80 | } 81 | 82 | if (ImGui::Button("Open labeling")){ 83 | std::string file = igl::file_dialog_open(); 84 | labeling = openFlagging(file, F.rows()); 85 | updateLabeling(); 86 | updateViz(); 87 | } 88 | 89 | if (ImGui::Button("Save labeling")){ 90 | std::string file = igl::file_dialog_save(); 91 | saveFlagging(file, labeling); 92 | } 93 | 94 | if (ImGui::Button("Measure labeling")){ 95 | std::cout << "Measuring" << std::endl; 96 | } 97 | 98 | ImGui::Text("Total labeling score: %f", labeling_score); 99 | ImGui::Text("\tFastPoly score: %f", fast_poly_score); 100 | ImGui::Text("\tInvalid score: %f", invalid_score); 101 | ImGui::Text("\t# inverted tris: %i", n_fail_invert); 102 | 103 | ImGui::SliderInt("Insert label", &insert_label, 0, 5); 104 | ImGui::SliderFloat("Insert size", &insert_size, 0.0*l_avg, 10.0*l_avg); 105 | ImGui::End(); 106 | } 107 | }; 108 | 109 | viewer.callback_key_down = [&](igl::opengl::glfw::Viewer& viewer, unsigned char key, int modifier){ 110 | if (key == ' '){ 111 | int fid; 112 | Eigen::Vector3f bc; 113 | // Cast a ray in the view direction starting from the mouse position 114 | double x = viewer.current_mouse_x; 115 | double y = viewer.core().viewport(3) - viewer.current_mouse_y; 116 | if(igl::unproject_onto_mesh(Eigen::Vector2f(x,y), viewer.core().view, 117 | viewer.core().proj, viewer.core().viewport, V, F, fid, bc)){ 118 | //labeling(fid) = insert_label; 119 | Eigen::VectorXi old_labeling = labeling; 120 | growFromTri(old_labeling, labeling, evo->TT_, evo->dists_, insert_label, fid, insert_size); 121 | 122 | updateLabeling(); 123 | updateViz(); 124 | return true; 125 | } 126 | } 127 | return false; 128 | }; 129 | 130 | updateViz(); 131 | 132 | viewer.core().lighting_factor = 0.0; 133 | viewer.core().set_rotation_type(igl::opengl::ViewerCore::ROTATION_TYPE_TRACKBALL); 134 | //viewer.core().background_color = Eigen::Vector4f(202.0/255.0, 190.0/255.0, 232.0/255.0, 1.0); 135 | viewer.core().background_color = Eigen::Vector4f(255.0/255.0, 255.0/255.0, 255.0/255.0, 1.0); 136 | viewer.launch(); 137 | } -------------------------------------------------------------------------------- /app/flagging_viewer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* 9 | #include 10 | #include 11 | #include 12 | #include 13 | */ 14 | 15 | #include "flagging_utils.h" 16 | 17 | int main(int argc, char *argv[]){ 18 | igl::opengl::glfw::Viewer viewer; 19 | viewer.append_mesh(); 20 | int orig_id = viewer.data_list[0].id; 21 | int hud_id = viewer.data_list[1].id; 22 | Eigen::MatrixXd V; 23 | Eigen::MatrixXi F; 24 | 25 | /*std::string input_name = "bunny"; //"cow"; 26 | std::string input_off = "../data/"+input_name+".off"; 27 | igl::readOFF(input_off, V, F);*/ 28 | 29 | std::string input_obj = "../data/S1/boundary.obj"; 30 | std::string input_labeling = "../data/S1/labeling.txt"; 31 | 32 | igl::readOBJ(input_obj, V, F); 33 | 34 | viewer.data(orig_id).set_mesh(V, F); 35 | 36 | 37 | Eigen::VectorXi flagging = openFlagging(input_labeling, F.rows()); 38 | Eigen::MatrixXd flagging_colors = colorsFromFlagging(flagging); 39 | viewer.data(orig_id).set_colors(flagging_colors); 40 | 41 | igl::opengl::glfw::imgui::ImGuiMenu menu; 42 | menu.callback_draw_viewer_window = []() {}; 43 | viewer.plugins.push_back(&menu); 44 | 45 | menu.callback_draw_custom_window = [&]() { 46 | bool show = true; 47 | ImGui::SetNextWindowPos(ImVec2(0.f * menu.menu_scaling(), 0), 48 | ImGuiCond_FirstUseEver); 49 | ImGui::SetNextWindowSize(ImVec2(350, 400)); 50 | if (ImGui::Begin("Test flagging")){ 51 | 52 | ImGui::SliderFloat("Lighting", &viewer.core().lighting_factor, 0.0f, 5.0f, "%.3f"); 53 | 54 | ImGui::End(); 55 | } 56 | }; 57 | 58 | viewer.data(orig_id).line_width = 8; 59 | viewer.data(hud_id).line_width = 1; 60 | viewer.data(orig_id).point_size = 4; 61 | 62 | 63 | viewer.data(orig_id).show_lines = false; 64 | viewer.core().background_color.setZero(); 65 | viewer.core().lighting_factor=0.5; 66 | viewer.launch(); 67 | } -------------------------------------------------------------------------------- /app/igl_core.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]){ 7 | 8 | Eigen::MatrixXd V; 9 | Eigen::MatrixXi F; 10 | 11 | igl::readOBJ("../data/cross/cross_flat.obj", V, F); 12 | 13 | 14 | // --- VISUALIZATION --- 15 | 16 | igl::opengl::glfw::Viewer viewer; 17 | viewer.data().set_mesh(V, F); 18 | 19 | igl::opengl::glfw::imgui::ImGuiMenu menu; 20 | menu.callback_draw_viewer_window = []() {}; 21 | viewer.plugins.push_back(&menu); 22 | 23 | auto updateViz = [&](){ 24 | viewer.data().clear_edges(); 25 | viewer.data().clear_points(); 26 | 27 | }; 28 | 29 | //helper function for menu 30 | auto make_checkbox = [&](const char *label, unsigned int &option) { 31 | return ImGui::Checkbox( 32 | label, 33 | [&]() { return viewer.core().is_set(option); }, 34 | [&](bool value) { return viewer.core().set(option, value); }); 35 | }; 36 | 37 | menu.callback_draw_custom_window = [&]() { 38 | ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver); 39 | ImGui::SetNextWindowSize(ImVec2(350, -1), ImGuiCond_FirstUseEver); 40 | if (ImGui::Begin("IGL")) { 41 | 42 | 43 | ImGui::End(); 44 | } 45 | 46 | }; 47 | 48 | updateViz(); 49 | 50 | viewer.core().set_rotation_type(igl::opengl::ViewerCore::ROTATION_TYPE_TRACKBALL); 51 | viewer.core().background_color = Eigen::Vector4f(202.0/255.0, 190.0/255.0, 232.0/255.0, 1.0); 52 | viewer.launch(); 53 | } -------------------------------------------------------------------------------- /app/jacob_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "distortion.h" 11 | 12 | int main(int argc, char *argv[]){ 13 | using std::chrono::steady_clock; 14 | using std::chrono::duration_cast; 15 | using std::chrono::microseconds; 16 | 17 | Eigen::MatrixXd V, V_def; 18 | Eigen::MatrixXi F; 19 | 20 | igl::readOBJ("../data/DATASET2/medium_mambo/M1/boundary.obj", V, F); 21 | igl::readOBJ("../data/DATASET2/medium_mambo/M1/fast_polycube_surf.obj", V_def, F); 22 | 23 | Eigen::MatrixXd N; 24 | igl::per_face_normals(V, F, N); 25 | Eigen::MatrixXd N_def; 26 | igl::per_face_normals(V_def, F, N_def); 27 | Eigen::VectorXd A; 28 | igl::doublearea(V, F, A); 29 | 30 | auto time_disto_start = steady_clock::now(); 31 | Eigen::VectorXd disto; 32 | computeDisto(V, V_def, F, N, N_def, disto); 33 | auto time_disto_end = steady_clock::now(); 34 | std::cout << "Total disto time (μs): "<< duration_cast(time_disto_end - time_disto_start).count() << std::endl; 35 | 36 | std::cout << "disto.mean(): " << disto.mean() << std::endl; 37 | double final_disto = integrateDistortion(A, disto); 38 | std::cout << "final_disto: " << final_disto << std::endl; 39 | 40 | // --- VISUALIZATION --- 41 | 42 | igl::opengl::glfw::Viewer viewer; 43 | viewer.data().set_mesh(V_def, F); 44 | 45 | Eigen::MatrixXd colors = Eigen::MatrixXd::Constant(disto.rows(), 3, 1.0); 46 | colors.col(1) = 1.0 - disto.array(); 47 | colors.col(2) = 1.0 - disto.array(); 48 | viewer.data().set_colors(colors); 49 | 50 | igl::opengl::glfw::imgui::ImGuiMenu menu; 51 | menu.callback_draw_viewer_window = []() {}; 52 | viewer.plugins.push_back(&menu); 53 | menu.callback_draw_custom_window = [&](){}; 54 | 55 | viewer.data().show_lines = true; 56 | viewer.core().lighting_factor = 0.0; 57 | viewer.core().set_rotation_type(igl::opengl::ViewerCore::ROTATION_TYPE_TRACKBALL); 58 | viewer.core().background_color = Eigen::Vector4f(202.0/255.0, 190.0/255.0, 232.0/255.0, 1.0); 59 | viewer.launch(); 60 | } -------------------------------------------------------------------------------- /app/measurement.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "distortion.h" 14 | #include "mesh_io.h" 15 | #include "logging.h" 16 | #include "flagging_utils.h" 17 | #include "tet_boundary.h" 18 | 19 | const std::map polycube_filename_to_JSON_tag { 20 | {"fast_polycube_surf.obj", "FastPolycubeFloat"}, 21 | {"polycube_surf_int.obj", "FastPolycubeInt"}, 22 | {"polycube_final.obj", "polycube_final.obj"} // oops, TODO fix this 23 | }; 24 | 25 | #define DEFAULT_TRI_INPUT "../data/DATASET2/OM_smooth/bunny_input_tri/boundary.obj" 26 | #define DEFAULT_POLYCUBE "../data/DATASET2/OM_smooth/bunny_input_tri/fast_polycube_surf.obj" 27 | #define LABELING_GRAPHCUT_FILENAME "/labeling_init.txt" //expected to be in the same folder as polycube_filepath 28 | #define LABELING_FINAL_FILENAME "/labeling.txt" //expected to be in the same folder as polycube_filepath 29 | 30 | 31 | 32 | int main(int argc, char *argv[]){ 33 | 34 | // read input name 35 | 36 | std::string input_filepath = DEFAULT_TRI_INPUT; 37 | if (argc > 1) input_filepath = argv[1]; 38 | 39 | std::string polycube_filepath = DEFAULT_POLYCUBE, polycube_filename; 40 | if (argc > 2) polycube_filepath = argv[2]; 41 | polycube_filename = std::filesystem::path(polycube_filepath).filename(); 42 | 43 | std::string JSON_tag; 44 | try { 45 | JSON_tag = polycube_filename_to_JSON_tag.at(polycube_filename); 46 | } 47 | catch (const std::out_of_range&) { 48 | JSON_tag = polycube_filename; 49 | } 50 | 51 | std::string save_path = std::filesystem::path(polycube_filepath).parent_path().string();//by default, write the logs inside the same folder as the polycube file 52 | if (argc > 3) save_path = argv[3]; 53 | 54 | 55 | Eigen::MatrixXi F, F2; 56 | Eigen::MatrixXd V1, V2; // V1 reference triangle mesh, V2 deformed 57 | 58 | igl::readOBJ(input_filepath, V1, F); 59 | igl::readOBJ(polycube_filepath, V2, F2); 60 | 61 | //* 62 | // Checking input validity 63 | if (V1.rows() != V2.rows()) coloredPrint("ERROR: vertex sizes don't match", "red"); 64 | if (F.rows() != F2.rows()) coloredPrint("ERROR: face sizes don't match", "red"); 65 | else { 66 | for (int i=0; i> per_tri_singular_values; 97 | per_tri_singular_values.resize(F.rows()); 98 | std::vector jacobians; 99 | computeJacobians(V1, V2, F, N, N_def, jacobians); 100 | 101 | for (int f_id = 0; f_id < F.rows(); f_id++){ 102 | Eigen::JacobiSVD svd(jacobians[f_id], Eigen::ComputeThinU | Eigen::ComputeThinV); 103 | per_tri_singular_values[f_id].first = svd.singularValues()(0); 104 | per_tri_singular_values[f_id].second = svd.singularValues()(1); 105 | } 106 | 107 | double stretch = computeStretch(A1, A_m, A_d, per_tri_singular_values); 108 | double area_disto = computeAreaDisto(A1, per_tri_singular_values); 109 | double angle_disto = computeAngleDisto(A1, per_tri_singular_values); 110 | double isometric_disto = computeIsometricDisto(A1, per_tri_singular_values); 111 | 112 | std::cout << "Area:\t reference " << A_m << " vs deformed " << A_d << std::endl; 113 | std::cout << "Stretch:\t" << stretch << std::endl; 114 | std::cout << "AreaDisto:\t" << area_disto << std::endl; 115 | std::cout << "AngleDisto:\t" << angle_disto << std::endl; 116 | std::cout << "IsometricDisto:\t" << isometric_disto << std::endl; 117 | 118 | Eigen::VectorXd disto; 119 | computeDisto(V1, V2, F, N, N_def, disto); 120 | std::cout << "Integrated:\t" << integrateDistortion(A1, disto) << std::endl; 121 | 122 | // fill logs 123 | std::string logs_path = save_path + "/logs.json"; 124 | std::stringstream stretch_rounded, area_disto_rounded, angle_disto_rounded; 125 | fillLogInfo(JSON_tag, "ReferenceArea", logs_path, A_m); 126 | fillLogInfo(JSON_tag, "DeformedArea", logs_path, A_d); 127 | //reduce the precision : the values will be printed in the supplemental and too many digits is superfluous 128 | stretch_rounded << std::fixed << std::setprecision(3) << stretch; 129 | area_disto_rounded << std::fixed << std::setprecision(3) << area_disto; 130 | angle_disto_rounded << std::fixed << std::setprecision(3) << angle_disto; 131 | fillLogInfo(JSON_tag, "Stretch", logs_path, stretch_rounded.str()); 132 | fillLogInfo(JSON_tag, "AreaDistortion", logs_path, area_disto_rounded.str()); 133 | fillLogInfo(JSON_tag, "AngleDistortion", logs_path, angle_disto_rounded.str()); 134 | 135 | //compute the similarity between the graphcut and the final labeling 136 | std::string labeling_graphcut_filepath = save_path + LABELING_GRAPHCUT_FILENAME, 137 | labeling_final_filepath = save_path + LABELING_FINAL_FILENAME; 138 | Eigen::VectorXi labeling_graphcut = openFlagging(labeling_graphcut_filepath,F.rows()), 139 | labeling_final = openFlagging(labeling_final_filepath,F.rows()); 140 | if(labeling_graphcut.isZero() || labeling_final.isZero()) { 141 | //case one of the labeling file don't have the expected size 142 | coloredPrint("Error : invalid number of labels in one of the labeling files","red"); 143 | } 144 | else { 145 | double similarity = flaggingSimilarity(labeling_graphcut,labeling_final); 146 | if(similarity >= 0.0) { 147 | std::cout << "LabelingSimilarity:\t" << similarity << std::endl; 148 | std::stringstream similarity_rounded; 149 | similarity_rounded << std::fixed << std::setprecision(3) << similarity; 150 | fillLogInfo("LabelingSimilarity", logs_path, similarity_rounded.str()); 151 | } 152 | else { 153 | coloredPrint("Error : flaggingSimilarity() says invalid vector lengths","red"); 154 | } 155 | } 156 | 157 | std::cout << logs_path << " updated" << std::endl; 158 | } 159 | -------------------------------------------------------------------------------- /app/polycube_withHexEx.cpp: -------------------------------------------------------------------------------- 1 | // polycube_withHexEx.cpp 2 | // https://github.com/fprotais/polycube_withHexEx with Eigen instead of ultimaille 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "HexExWrapper.h" 16 | #include "mesh_io.h" 17 | #include "tet_boundary.h" 18 | #include "disjointset.h" 19 | #include "logging.h" 20 | #include "tet_boundary.h" 21 | 22 | #define DEFAULT_SCALE 1.0 23 | 24 | #define CHECK_IF_FILE_EXISTS(filepath) if(!std::filesystem::exists(filepath)) { coloredPrint((filepath) + " does not exist","red"); return 1; } 25 | 26 | void float_cubecover(const Eigen::MatrixXi& tets, const Eigen::MatrixXd& V, const Eigen::VectorXi& tets_labeling, Eigen::MatrixXd& U) { 27 | U=V; 28 | std::cerr << "Integrating with float boundary...\n"; 29 | DisjointSet ds(V.rows() * 3); 30 | for(int c = 0; c < tets.rows(); c++) { //for each cell 31 | for(int cf = 0; cf < 4; cf++) { //for each face 32 | if(tets_labeling(4 * c + cf) != -1) { 33 | int d = tets_labeling(4 * c + cf) / 2;//get direction 34 | for(int cfv = 0; cfv < 3; cfv++) { //for each vertex 35 | ds.merge(d * V.rows() + TETRAHEDRON_TO_VERTEX(tets, c, cf, cfv), d * V.rows() + TETRAHEDRON_TO_VERTEX(tets, c, cf, (cfv + 1) % 3)); 36 | } 37 | } 38 | } 39 | } 40 | std::vector idmap; 41 | int nb_var = ds.get_sets_id(idmap); 42 | 43 | auto context = nlNewContext(); 44 | nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE); 45 | nlSolverParameteri(NL_NB_VARIABLES, NLint(nb_var)); 46 | 47 | nlBegin(NL_SYSTEM); 48 | nlEnable(NL_VERBOSE); 49 | nlBegin(NL_MATRIX); 50 | for(int c = 0; c < tets.rows(); c++) { //for each cell (tetrahedron) 51 | int v[4] = { tets(c,0) , tets(c,1), tets(c,2), tets(c,3) };//get the 4 vertices of c 52 | Eigen::Matrix3d M; 53 | M.row(0) = V.row(v[1]) - V.row(v[0]); 54 | M.row(1) = V.row(v[2]) - V.row(v[0]); 55 | M.row(2) = V.row(v[3]) - V.row(v[0]); 56 | Eigen::Matrix3d invM = M.inverse(); 57 | invM.transposeInPlace(); 58 | Eigen::MatrixXd grad_coef(4,3); 59 | grad_coef.row(0) = -invM.row(0) - invM.row(1) - invM.row(2); 60 | grad_coef.row(1) = invM.row(0); 61 | grad_coef.row(2) = invM.row(1); 62 | grad_coef.row(3) = invM.row(2); 63 | for(int dim = 0; dim < 3; dim++) { 64 | for(int dim2 = 0; dim2 < 3; dim2++) { 65 | Eigen::Vector3i e(0,0,0); 66 | e(dim2) = 1; 67 | nlBegin(NL_ROW); 68 | for(int dim_e = 0; dim_e < 3; dim_e++) { 69 | for(int point = 0; point < 4; point++) { 70 | nlCoefficient(idmap[dim * V.rows() + v[point]], e(dim_e) * grad_coef(point,dim_e)); 71 | } 72 | } 73 | if (dim == dim2) nlRightHandSide(1); 74 | nlEnd(NL_ROW); 75 | } 76 | } 77 | } 78 | 79 | nlEnd(NL_MATRIX); 80 | nlEnd(NL_SYSTEM); 81 | nlSolve(); 82 | 83 | for(int v = 0; v < V.rows(); v++) { 84 | for(int dim = 0; dim < 3; dim++) { 85 | U(v,dim) = nlGetVariable(idmap[V.rows() * dim + v]); 86 | } 87 | } 88 | 89 | nlDeleteContext(context); 90 | std::cerr << " Done.\n"; 91 | 92 | } 93 | 94 | void integer_cubecover(const Eigen::MatrixXi& tets, const Eigen::MatrixXd& V, const Eigen::VectorXi& tets_labeling, const Eigen::MatrixXd& U, Eigen::MatrixXd& int_U) { 95 | 96 | int_U.resize(V.rows(),3); 97 | 98 | std::cerr << "Integrating with int boundary...\n"; 99 | DisjointSet ds(V.rows() * 3); 100 | for(int c = 0; c < tets.rows(); c++) { //for each cell 101 | for(int cf = 0; cf < 4; cf++) { //for each face 102 | if(tets_labeling(4 * c + cf) != -1) { 103 | int d = tets_labeling(4 * c + cf) / 2;//get direction 104 | for(int cfv = 0; cfv < 3; cfv++) { //for each vertex 105 | ds.merge(d * V.rows() + TETRAHEDRON_TO_VERTEX(tets, c, cf, cfv), d * V.rows() + TETRAHEDRON_TO_VERTEX(tets, c, cf, (cfv + 1) % 3)); 106 | } 107 | } 108 | } 109 | } 110 | std::vector idmap; 111 | int nb_var = ds.get_sets_id(idmap); 112 | 113 | auto context = nlNewContext(); 114 | nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE); 115 | nlSolverParameteri(NL_NB_VARIABLES, NLint(nb_var)); 116 | 117 | nlBegin(NL_SYSTEM); 118 | for(int c = 0; c < tets.rows(); c++) { //for each cell 119 | for(int cf = 0; cf < 4; cf++) { //for each face 120 | if(tets_labeling(4 * c + cf) != -1) { 121 | int d = tets_labeling(4 * c + cf) / 2;//get direction 122 | for(int cfv = 0; cfv < 3; cfv++) { //for each vertex 123 | nlSetVariable(idmap[d * V.rows() + TETRAHEDRON_TO_VERTEX(tets, c, cf, cfv)], std::round(U(TETRAHEDRON_TO_VERTEX(tets, c, cf, cfv),d))); 124 | nlLockVariable(idmap[d * V.rows() + TETRAHEDRON_TO_VERTEX(tets, c, cf, cfv)]); 125 | } 126 | } 127 | } 128 | } 129 | nlEnable(NL_VERBOSE); 130 | nlBegin(NL_MATRIX); 131 | for(int c = 0; c < tets.rows(); c++) { //for each cell (tetrahedron) 132 | int v[4] = { tets(c,0) , tets(c,1), tets(c,2), tets(c,3) };//get the 4 vertices of c 133 | Eigen::Matrix3d M; 134 | M.row(0) = V.row(v[1]) - V.row(v[0]); 135 | M.row(1) = V.row(v[2]) - V.row(v[0]); 136 | M.row(2) = V.row(v[3]) - V.row(v[0]); 137 | Eigen::Matrix3d invM = M.inverse(); 138 | invM.transposeInPlace(); 139 | 140 | Eigen::MatrixXd grad_coef(4,3); 141 | grad_coef.row(0) = -invM.row(0) - invM.row(1) - invM.row(2); 142 | grad_coef.row(1) = invM.row(0); 143 | grad_coef.row(2) = invM.row(1); 144 | grad_coef.row(3) = invM.row(2); 145 | for(int dim = 0; dim < 3; dim++) { 146 | for(int dim2 = 0; dim2 < 3; dim2++) { 147 | Eigen::Vector3i e(0,0,0); 148 | e(dim2) = 1; 149 | nlBegin(NL_ROW); 150 | for(int dim_e = 0; dim_e < 3; dim_e++) { 151 | for(int point = 0; point < 4; point++) { 152 | nlCoefficient(idmap[dim * V.rows() + v[point]], e(dim_e) * grad_coef(point,dim_e)); 153 | } 154 | } 155 | if (dim == dim2) nlRightHandSide(1); 156 | nlEnd(NL_ROW); 157 | } 158 | } 159 | } 160 | 161 | nlEnd(NL_MATRIX); 162 | nlEnd(NL_SYSTEM); 163 | nlSolve(); 164 | 165 | for(int v = 0; v < V.rows(); v++) { 166 | for(int dim = 0; dim < 3; dim++) { 167 | int_U(v,dim) = nlGetVariable(idmap[V.rows() * dim + v]); 168 | } 169 | } 170 | 171 | nlDeleteContext(context); 172 | std::cerr << " Done.\n"; 173 | } 174 | 175 | double rescaling(const Eigen::MatrixXi& tets, Eigen::MatrixXd& V, double scale) { 176 | double size = 0.0; 177 | for(int c = 0; c < tets.rows(); c++) { //for each tetrahedron 178 | for(int cf = 0; cf < 4; cf++) { //for each face of the current tetrahedron 179 | for(int cfv = 0; cfv < 3; cfv++) { //for each vertex of the current face 180 | //distance between vertex cfv and the next one 181 | Eigen::RowVector3d v1 = V.row(TETRAHEDRON_TO_VERTEX(tets,c,cf,cfv)); 182 | Eigen::RowVector3d v2 = V.row(TETRAHEDRON_TO_VERTEX(tets,c,cf,(cfv + 1) % 3)); 183 | size += (v1 - v2).norm(); 184 | } 185 | } 186 | } 187 | size /= tets.rows() * 12 * scale; 188 | for(int vertex = 0; vertex < V.rows(); vertex++) { 189 | V.row(vertex) /= size; 190 | } 191 | return size; 192 | } 193 | 194 | void revert_rescaling(Eigen::MatrixXd& V, double sizing) { 195 | for(int vertex = 0; vertex < V.rows(); vertex++) { 196 | V.row(vertex) *= sizing; 197 | } 198 | } 199 | 200 | int main(int argc, char* argv[]) { 201 | 202 | std::string folder, tetra_file, tets_labeling_file, hex_mesh_file; 203 | double hexscale = DEFAULT_SCALE; 204 | 205 | if (argc == 3){ 206 | folder = argv[1]; 207 | tetra_file = folder + "/tetra.mesh"; 208 | tets_labeling_file = folder + "/labeling_on_tets.txt"; 209 | hex_mesh_file = folder + "/hexes.mesh"; 210 | hexscale = std::stod(argv[2]); 211 | } 212 | else if (argc == 5){ 213 | folder = ""; 214 | tetra_file = argv[1]; 215 | tets_labeling_file = argv[2]; 216 | hex_mesh_file = argv[3]; 217 | hexscale = std::stod(argv[4]); 218 | } 219 | else { 220 | std::cout << "Usage is: " << argv[0] << " tetra.mesh labeling_on_tets.txt output_hexes.mesh scale" << std::endl; 221 | std::cout << "exemple: " << argv[0] << " ../data/S1/tetra.mesh ../data/S1/labeling_on_tets.txt ../data/S1/hexes.mesh 1." << std::endl; 222 | return 1; 223 | } 224 | 225 | CHECK_IF_FILE_EXISTS(tetra_file) 226 | CHECK_IF_FILE_EXISTS(tets_labeling_file) 227 | 228 | Eigen::MatrixXd V_tets, V_boundary; 229 | Eigen::MatrixXi tets, F_boundary; 230 | Eigen::VectorXi tri_labeling, tets_labeling; 231 | 232 | //read tets 233 | readDotMeshTet(tetra_file, V_tets, tets); 234 | 235 | //read labels (flags) 236 | tets_labeling = Eigen::VectorXi::Constant(4 * tets.rows(), -1); 237 | std::ifstream ifs(tets_labeling_file); 238 | if (!ifs.is_open()) { 239 | std::cerr << "Failed opening of flags at : " << tets_labeling_file << std::endl; 240 | abort(); 241 | } 242 | for(int tet_face = 0; tet_face < 4 * tets.rows(); tet_face++) { //for each face of each tetrahedron 243 | if (ifs.eof()) tets_labeling(tet_face) = -1; 244 | else ifs >> tets_labeling(tet_face); 245 | } 246 | ifs.close(); 247 | 248 | Eigen::MatrixXd U, int_U; 249 | double sizing = rescaling(tets, V_tets, hexscale); 250 | 251 | // writeDotMeshTet("tetra_scaled.mesh",V_tets,tets); 252 | 253 | float_cubecover(tets, V_tets, tets_labeling, U); 254 | 255 | integer_cubecover(tets, V_tets, tets_labeling, U, int_U); 256 | 257 | revert_rescaling(V_tets, sizing); 258 | 259 | 260 | Eigen::MatrixXd corner_param(tets.rows()*4,3); 261 | for(int c=0; c < tets.rows(); c++) { //for each cell (tetrahedron) 262 | for(int cc = 0; cc < 4; cc++) { //for each vertex of the cell c 263 | corner_param.row(4 * c + cc) = int_U.row(tets(c,cc)); 264 | } 265 | } 266 | 267 | Eigen::MatrixXi hexes; 268 | Eigen::MatrixXd V_hexes; 269 | run_HexEx(tets, V_tets, corner_param, hexes, V_hexes); 270 | writeDotMeshHex(hex_mesh_file, V_hexes, hexes); 271 | 272 | // Save final polycube boundary 273 | if (folder != ""){ 274 | writeDotMeshTet(folder + "/polycube_tets_int.mesh", int_U, tets); 275 | Eigen::MatrixXd Vbi, Vbf; // initial vs final boundary 276 | Eigen::MatrixXi Fb; 277 | igl::readOBJ(folder + "/boundary.obj", Vbi, Fb); 278 | Vbf.resize(Vbi.rows(), Vbi.cols()); 279 | 280 | BndToTetConverter conv(folder + "/tris_to_tets.txt"); 281 | 282 | for (int i=0; i> corres = { 288 | {1, 3, 2}, 289 | {0, 2, 3}, 290 | {0, 3, 1}, 291 | {0, 1, 2} 292 | }; 293 | Vbf.row(Fb(i, 0)) = int_U.row(tets(tet_id, corres[f_in_tet][0])); 294 | Vbf.row(Fb(i, 1)) = int_U.row(tets(tet_id, corres[f_in_tet][1])); 295 | Vbf.row(Fb(i, 2)) = int_U.row(tets(tet_id, corres[f_in_tet][2])); 296 | } 297 | 298 | igl::writeOBJ(folder + "/polycube_surf_int.obj", Vbf, Fb); 299 | 300 | } 301 | 302 | return 0; 303 | } 304 | 305 | -------------------------------------------------------------------------------- /app/supplemental_generator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "latex.h" 9 | #include "logging.h" 10 | 11 | #define DEFAULT_INPUT_PATH "../data/DATASET_12_Avril/basic_mambo/" 12 | #define INPUT_COVER_PAGE "../supplemental/cover_page.tex" 13 | //#define INPUT_POLYCUBE_TAGNAME "/FastPolycubeFloat" 14 | #define INPUT_POLYCUBE_TAGNAME "/polycube_final.obj" //which polycube distortions (from the the log file) to insert. "/FastPolycubeFloat" or "/FastPolycubeInt" 15 | #define DEFAULT_OUTPUT_PATH "../supplemental/" 16 | 17 | //usage : ./supplemental_generator [output] [input1] [input2 input3 ...] 18 | // 19 | // output is where the files will be written. Default is DEFAULT_OUTPUT_PATH 20 | // input1 is the path to the fist dataset to include. default is DEFAULT_INPUT_PATH 21 | // other datasets can be included to assemble an unique supplemental document 22 | // 23 | // ex : ./supplemental_generator ../paper/supplemental ../data/first_dataset_CAD ../data/first_dataset_Smooth ../data/second_dataset ../another_dataset 24 | 25 | int main(int argc, char** argv) { 26 | 27 | // get arguments 28 | 29 | std::string output_path = DEFAULT_OUTPUT_PATH; 30 | std::vector input_paths; 31 | 32 | if(argc >= 2) 33 | output_path = argv[1]; 34 | 35 | for(int arg_parser = 2; arg_parser < argc; arg_parser++) { 36 | input_paths.push_back(argv[arg_parser]); 37 | } 38 | if(input_paths.empty()) 39 | input_paths.push_back(DEFAULT_INPUT_PATH); 40 | 41 | // create output file 42 | 43 | if(!std::filesystem::exists(output_path)) 44 | std::filesystem::create_directory(output_path); 45 | 46 | std::vector no_logs_meshes, invalid_labeling_meshes, missing_figs_meshes; 47 | 48 | LatexDoc latex(std::string(output_path) + "/supplemental.tex"); 49 | latex.add_subpage(std::filesystem::relative(INPUT_COVER_PAGE,output_path));//argument = where is the cover page relative to the output folder 50 | 51 | // parse input datasets 52 | 53 | int success_count = 0, total_meshes_number = 0; 54 | time_plot_entry cpu; 55 | INIT_TIME_PLOT_ENTRY(cpu); 56 | 57 | for(const std::string input_path: input_paths) { //for each given input dataset 58 | std::string dataset_short_name = std::filesystem::path(input_path).parent_path().filename(); 59 | coloredPrint("~~~~~~ DATASET " + dataset_short_name + " ~~~~~~","cyan"); 60 | 61 | std::set entries_sorted_by_name; 62 | for(const std::filesystem::directory_entry& dir_entry : std::filesystem::directory_iterator(input_path))//interators have no order -> sort by alphabetical order with a set 63 | entries_sorted_by_name.insert(dir_entry); 64 | 65 | for(const std::filesystem::directory_entry& dir_entry : entries_sorted_by_name) { 66 | if(!dir_entry.is_directory()) continue; 67 | total_meshes_number++; 68 | std::string current_filepath = dir_entry.path().string(); 69 | std::cout << "Working on " << current_filepath << " : "; 70 | int status_code = latex.add_mesh(dir_entry.path(), INPUT_POLYCUBE_TAGNAME, cpu); 71 | if (status_code == 1) { 72 | coloredPrint("Some/all figures are missing","red"); 73 | missing_figs_meshes.push_back(current_filepath); 74 | } 75 | else if (status_code == 2) { 76 | coloredPrint("The final labeling is invalid","red"); 77 | invalid_labeling_meshes.push_back(current_filepath); 78 | } 79 | else if (status_code == 3) { 80 | coloredPrint("No log file, skipped","red"); 81 | no_logs_meshes.push_back(current_filepath); 82 | } 83 | else if (status_code != 0) { 84 | coloredPrint(std::string("Unknown status code of LatexDoc::add_mesh() : ") + std::to_string(status_code) + " with " + current_filepath, "red"); 85 | return 1; 86 | } 87 | else { 88 | std::cout << "Done" << std::endl; 89 | success_count ++; 90 | } 91 | } 92 | } 93 | 94 | LatexDoc timeplot_doc(std::string(output_path) + "/time_plot.tex"); 95 | double parallel_speedup = ( cpu.insertion_in_archive + 96 | cpu.charts_and_turning_points + 97 | cpu.individual_selection + 98 | cpu.crossing + 99 | cpu.fitness_evaluation + 100 | cpu.individual_mutations 101 | ) / cpu.genetics; 102 | time_plot_entry real = cpu; 103 | real.individual_selection /= parallel_speedup; 104 | real.individual_mutations /= parallel_speedup; 105 | real.charts_and_turning_points /= parallel_speedup; 106 | real.fitness_evaluation /= parallel_speedup; 107 | real.crossing /= parallel_speedup; 108 | real.insertion_in_archive /= parallel_speedup; 109 | //seconds to hours 110 | cpu /= 3600.0; 111 | real /= 3600.0; 112 | timeplot_doc.add_time_plot(cpu,real); 113 | 114 | std::cout << std::endl << "-- SUMMARY ----------------" << std::endl; 115 | if(!missing_figs_meshes.empty()) { 116 | coloredPrint(std::to_string(missing_figs_meshes.size()) + " mesh(es) have some figures missing:", "red"); 117 | for(auto& name : missing_figs_meshes) 118 | std::cout << "\t" << name << std::endl; 119 | } 120 | if(!invalid_labeling_meshes.empty()) { 121 | coloredPrint(std::to_string(invalid_labeling_meshes.size()) + " mesh(es) have an invalid final labeling:", "red"); 122 | for(auto& name : invalid_labeling_meshes) 123 | std::cout << "\t" << name << std::endl; 124 | } 125 | if(!no_logs_meshes.empty()) { 126 | coloredPrint(std::to_string(no_logs_meshes.size()) + " mesh(es) don't have a log file:", "red"); 127 | for(auto& name : no_logs_meshes) 128 | std::cout << "\t" << name << std::endl; 129 | } 130 | if(missing_figs_meshes.empty() && invalid_labeling_meshes.empty() && no_logs_meshes.empty()) 131 | coloredPrint("No figure missing", "green"); 132 | coloredPrint("Success: " + std::to_string(success_count) + " /" 133 | + std::to_string(total_meshes_number), "green"); 134 | std::cout << "---------------------------" << std::endl; 135 | return 0; 136 | } 137 | 138 | -------------------------------------------------------------------------------- /app/test_scaled_jacobian.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "scaled_jacobian.h" 9 | #include "logging.h" 10 | #include "mesh_io.h" 11 | 12 | int main(int argc, char* argv[]) { 13 | 14 | if (argc < 3) { 15 | std::cout << "Usage is: " << argv[0] << " path/to/hexes.mesh path/to/SJ.csv" << std::endl; 16 | return 1; 17 | } 18 | 19 | std::string hex_mesh_file = argv[1], csv_file = argv[2]; 20 | 21 | if(!std::filesystem::exists(hex_mesh_file)) { 22 | coloredPrint((hex_mesh_file) + " does not exist","red"); 23 | return 1; 24 | } 25 | 26 | Eigen::MatrixXi hexes; 27 | Eigen::MatrixXd V_hexes; 28 | Eigen::VectorXd min_sj; 29 | double overall_min_sj; 30 | 31 | readDotMeshHex(hex_mesh_file, V_hexes, hexes); 32 | 33 | overall_min_sj = compute_min_scaled_jacobian(hexes,V_hexes,min_sj); 34 | 35 | std::cout << "Overall minSJ = " << overall_min_sj << std::endl; 36 | 37 | //export as CSV 38 | std::ofstream ofs(csv_file.c_str(),std::ofstream::out); 39 | ofs << "hex index,SJ" << std::endl; 40 | for(int h = 0; h < min_sj.rows(); h++) { 41 | ofs << h << "," << min_sj(h) << std::endl; 42 | } 43 | ofs.close(); 44 | 45 | std::cout << "table write into " << csv_file << std::endl; 46 | 47 | return 0; 48 | } -------------------------------------------------------------------------------- /data/grey_checkerboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LIHPC-Computational-Geometry/evocube/b4c4dfc66f5701505b42ea7368461907979e4ade/data/grey_checkerboard.png -------------------------------------------------------------------------------- /images/lowres_evocube_teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LIHPC-Computational-Geometry/evocube/b4c4dfc66f5701505b42ea7368461907979e4ade/images/lowres_evocube_teaser.png -------------------------------------------------------------------------------- /include/HexExWrapper.h: -------------------------------------------------------------------------------- 1 | // HexExWrapper.h 2 | // https://github.com/fprotais/polycube_withHexEx with Eigen instead of ultimaille 3 | 4 | #pragma once 5 | #include 6 | 7 | extern const Eigen::MatrixXi TETRAHEDRON_TO_FACES; 8 | 9 | //get the vertex id of the v_th vertex of the f_th face of the t_th tetrahedron 10 | #define TETRAHEDRON_TO_VERTEX(tets,t,f,v) (tets(t,TETRAHEDRON_TO_FACES(f,v))) 11 | 12 | bool run_HexEx(const Eigen::MatrixXi& tets, const Eigen::MatrixXd& V_tets, const Eigen::MatrixXd& corner_param, Eigen::MatrixXi& hexes, Eigen::MatrixXd& V_hexes); 13 | -------------------------------------------------------------------------------- /include/MyEmbreeRenderer.h: -------------------------------------------------------------------------------- 1 | 2 | // MODIFIED VERSION OF LIBIGL'S 3 | 4 | // This file is part of libigl, a simple c++ geometry processing library. 5 | // 6 | // 7 | // Copyright (C) 2020 Vladimir Fonov 8 | // 2013 Alec Jacobson 9 | // 2014 Christian Schüller 10 | // 11 | // This Source Code Form is subject to the terms of the Mozilla Public License 12 | // v. 2.0. If a copy of the MPL was not distributed with this file, You can 13 | // obtain one at http://mozilla.org/MPL/2.0/. 14 | // 15 | 16 | #ifndef IGL_EMBREE_EMBREE_RENDERER_H 17 | #define IGL_EMBREE_EMBREE_RENDERER_H 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | 32 | namespace igl 33 | { 34 | namespace embree 35 | { 36 | // embree-based mesh renderer 37 | class EmbreeRenderer 38 | { 39 | public: 40 | typedef Eigen::RowVector3f Vec3f; 41 | 42 | struct Hit 43 | { 44 | int id; // primitive id 45 | int gid; // geometry id 46 | float u,v; // barycentric coordinates 47 | float t; // distance = direction*t to intersection 48 | Vec3f N; // element normal 49 | }; 50 | 51 | public: 52 | typedef Eigen::Matrix PointMatrixType; 53 | typedef Eigen::Matrix ColorMatrixType; 54 | typedef Eigen::Matrix FaceMatrixType; 55 | 56 | typedef Eigen::Matrix PixelMatrixType; 57 | 58 | public: 59 | EmbreeRenderer(); 60 | private: 61 | // Copying and assignment are not allowed. 62 | EmbreeRenderer(const EmbreeRenderer & that); 63 | EmbreeRenderer & operator=(const EmbreeRenderer &); 64 | public: 65 | virtual ~EmbreeRenderer(); 66 | 67 | // Specify mesh, this call reinitializes embree structures 68 | // Inputs: 69 | // V #V x dim matrix of vertex coordinates 70 | // F #F x simplex_size matrix of indices of simplex corners into V 71 | // is_static - optimize for static thene (HQ rendering) 72 | void set_mesh(const Eigen::Matrix & V, 73 | const Eigen::Matrix & F, 74 | bool is_static=true); 75 | 76 | void set_edges(const Eigen::MatrixXf& begs, const Eigen::MatrixXf& ends, const Eigen::MatrixXf& edge_colors){ 77 | begs_ = begs; 78 | ends_ = ends; 79 | edge_colors_ = edge_colors; 80 | } 81 | 82 | void set_edges_per_tri(const std::vector>& edges_per_tri){ 83 | edges_per_tri_ = edges_per_tri; 84 | } 85 | 86 | // Specify per-vertex or per-face color 87 | // Inputs: 88 | // C #V x 3 matrix of vertex colors 89 | // or #F x 3 matrix of face colors 90 | // or 1 x 3 matrix of uniform color 91 | void set_colors(const Eigen::MatrixXd & C); 92 | 93 | 94 | // Use min(D) and max(D) to set caxis. 95 | void set_data(const Eigen::VectorXd & D, 96 | igl::ColorMapType cmap = igl::COLOR_MAP_TYPE_VIRIDIS); 97 | 98 | // Specify per-vertex or per-face scalar field 99 | // that will be converted to color using jet color map 100 | // Inputs: 101 | // caxis_min caxis minimum bound 102 | // caxis_max caxis maximum bound 103 | // D #V by 1 list of scalar values 104 | // cmap colormap type 105 | // num_steps number of intervals to discretize the colormap 106 | void set_data( 107 | const Eigen::VectorXd & D, 108 | double caxis_min, 109 | double caxis_max, 110 | igl::ColorMapType cmap = igl::COLOR_MAP_TYPE_VIRIDIS); 111 | 112 | // Specify mesh rotation 113 | // Inputs: 114 | // r 3 x 3 rotaton matrix 115 | void set_rot(const Eigen::Matrix3d &r); 116 | 117 | // Specify mesh magnification 118 | // Inputs: 119 | // z magnification ratio 120 | void set_zoom(double z); 121 | 122 | // Specify mesh translation 123 | // Inputs: 124 | // tr translation vector 125 | void set_translation(const Eigen::Vector3d &tr); 126 | 127 | // Specify that color is face based 128 | // Inputs: 129 | // f - face or vertex colours 130 | void set_face_based(bool f); 131 | 132 | // Use orthographic projection 133 | // Inputs: 134 | // f - orthographic or perspective projection 135 | void set_orthographic(bool f ); 136 | 137 | // render full buffer 138 | // Outputs: 139 | // all outputs should have the same size (size of the output picture) 140 | // area outside of the visible object will have zero alpha component (transparant) 141 | // R - red channel 142 | // G - green channel 143 | // B - blue channel 144 | // A - alpha channel 145 | void render_buffer(PixelMatrixType &R, 146 | PixelMatrixType &G, 147 | PixelMatrixType &B, 148 | PixelMatrixType &A); 149 | 150 | // Given a ray find the first hit 151 | // 152 | // Inputs: 153 | // origin 3d origin point of ray 154 | // direction 3d (not necessarily normalized) direction vector of ray 155 | // tnear start of ray segment 156 | // tfar end of ray segment 157 | // mask a 32 bit mask to identify active geometries. 158 | // Output: 159 | // hit information about hit 160 | // Returns true if and only if there was a hit 161 | bool intersect_ray( 162 | const Eigen::RowVector3f& origin, 163 | const Eigen::RowVector3f& direction, 164 | Hit& hit, 165 | float tnear = 0, 166 | float tfar = std::numeric_limits::infinity(), 167 | int mask = 0xFFFFFFFF) const; 168 | 169 | 170 | Eigen::Vector3f camera_base_translation; 171 | Eigen::Vector3f camera_translation; 172 | Eigen::Vector3f camera_eye; 173 | Eigen::Vector3f camera_up; 174 | Eigen::Vector3f camera_center; 175 | 176 | private: 177 | 178 | // Initialize with a given mesh. 179 | // 180 | // Inputs: 181 | // V #V by 3 list of vertex positions 182 | // F #F by 3 list of Oriented triangles 183 | // isStatic scene is optimized for static geometry 184 | // Side effects: 185 | // The first time this is ever called the embree engine is initialized. 186 | void init( 187 | const PointMatrixType& V, 188 | const FaceMatrixType& F, 189 | bool isStatic = false); 190 | 191 | // Initialize embree with a given mesh. 192 | // 193 | // Inputs: 194 | // V vector of #V by 3 list of vertex positions for each geometry 195 | // F vector of #F by 3 list of Oriented triangles for each geometry 196 | // masks a 32 bit mask to identify active geometries. 197 | // isStatic scene is optimized for static geometry 198 | // Side effects: 199 | // The first time this is ever called the embree engine is initialized. 200 | void init( 201 | const std::vector& V, 202 | const std::vector& F, 203 | const std::vector& masks, 204 | bool isStatic = false); 205 | 206 | 207 | // Deinitialize embree datasctructures for current mesh. Also called on 208 | // destruction: no need to call if you just want to init() once and 209 | // destroy. 210 | void deinit(); 211 | // initialize view parameters 212 | void init_view(); 213 | 214 | // scene data 215 | PointMatrixType V; // vertices 216 | FaceMatrixType F; // faces 217 | ColorMatrixType C; // colours 218 | Eigen::MatrixXf begs_; 219 | Eigen::MatrixXf ends_; 220 | Eigen::MatrixXf edge_colors_; 221 | std::vector> edges_per_tri_; 222 | 223 | Eigen::RowVector3f uC; // uniform color 224 | 225 | bool face_based; 226 | bool uniform_color; 227 | 228 | // Camera parameters 229 | float camera_base_zoom; 230 | float camera_zoom; 231 | 232 | float camera_view_angle; 233 | float camera_dnear; 234 | float camera_dfar; 235 | 236 | // projection matrixes 237 | Eigen::Matrix4f view; 238 | Eigen::Matrix4f proj; 239 | Eigen::Matrix4f norm; 240 | 241 | Eigen::Matrix3f rot_matrix; 242 | 243 | bool orthographic; 244 | 245 | // embree data 246 | RTCScene scene; 247 | unsigned geomID; 248 | bool initialized; 249 | 250 | RTCDevice device; 251 | 252 | void create_ray( 253 | RTCRayHit& ray, 254 | const Eigen::RowVector3f& origin, 255 | const Eigen::RowVector3f& direction, 256 | float tnear, 257 | float tfar, 258 | int mask) const; 259 | 260 | }; 261 | } 262 | } 263 | 264 | #ifndef IGL_STATIC_LIBRARY 265 | # include "EmbreeRenderer.cpp" 266 | #endif 267 | #endif //IGL_EMBREE_EMBREE_RENDERER_H 268 | -------------------------------------------------------------------------------- /include/archive.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | template 11 | class Archive { 12 | private: 13 | std::deque> archive_; 14 | int archive_max_size_; 15 | double max_accepted_score_ = 10e10; 16 | 17 | public: 18 | Archive(int archive_size); 19 | Archive(){}; 20 | double requiredScore() const; 21 | bool insert(T individual, double score); 22 | void print() const; 23 | void clear(); 24 | int probabilisticIndividual() const; 25 | void insertAll(const Archive other_archive); 26 | 27 | // getters & setters 28 | T bestIndividual() const; 29 | double bestScore() const; 30 | int getSize() const; 31 | T getIndiv(int id) const; 32 | std::pair popIndiv(int id); 33 | double getScore(int id) const; 34 | void setMaxAcceptScore(double max_accepted_score); 35 | int maxSize() const {return archive_max_size_;}; 36 | }; 37 | 38 | template 39 | Archive::Archive(int archive_size) 40 | : archive_max_size_(archive_size) { 41 | 42 | } 43 | 44 | template 45 | void Archive::setMaxAcceptScore(double max_accepted_score){ 46 | max_accepted_score_ = max_accepted_score; 47 | } 48 | 49 | template 50 | double Archive::requiredScore() const { 51 | if (archive_.size() < archive_max_size_) return max_accepted_score_; //TODO 52 | return archive_.front().first; 53 | } 54 | 55 | template 56 | bool Archive::insert(T individual, double score){ 57 | if (score >= requiredScore() && !(archive_.size() == 0)) return false; 58 | 59 | auto it = archive_.begin(); 60 | while (it != archive_.end() && score <= (*it).first){ 61 | if (individual == (*it).second) return false; 62 | it ++; 63 | } 64 | archive_.insert(it, std::pair(score, individual)); 65 | 66 | if (archive_.size() > archive_max_size_) archive_.pop_front(); 67 | return true; 68 | } 69 | 70 | template 71 | void Archive::print() const { 72 | for (int i=0; i 79 | void Archive::insertAll(const Archive other_archive){ 80 | int s = other_archive.getSize(); 81 | for (int i=0; i 87 | void Archive::clear() { 88 | archive_.clear(); 89 | } 90 | 91 | template 92 | T Archive::bestIndividual() const { 93 | if (archive_.size() < 1) coloredPrint("Error: trying to get best individual from empty archive", "red"); 94 | return archive_.back().second; 95 | } 96 | 97 | template 98 | double Archive::bestScore() const { 99 | if (archive_.size() < 1) coloredPrint("Error: trying to get best score from empty archive", "red"); 100 | return archive_.back().first; 101 | } 102 | 103 | template 104 | int Archive::probabilisticIndividual() const { 105 | // Inspiration: Roulette wheel selection section from 106 | // "Towards Automatic Blocking of Shapes using Evolutionary Algorithm" 107 | 108 | if (archive_.size() == 0) { 109 | coloredPrint("Error: called probabilisticIndividual but archive is empty", "red"); 110 | } 111 | 112 | double total_props = 0; 113 | int PN = archive_.size(); 114 | for (int i=1; i0; i--) { 119 | if (random < prob_sum + static_cast(i + 1) / total_props){ 120 | return i; 121 | //return archive_[i].second; 122 | } 123 | else { 124 | prob_sum += static_cast(i + 1) / total_props; 125 | } 126 | } 127 | 128 | return 0; 129 | //return archive_.front().second; 130 | } 131 | 132 | 133 | template 134 | int Archive::getSize() const{ 135 | return archive_.size(); 136 | } 137 | 138 | template 139 | T Archive::getIndiv(int id) const { 140 | return archive_[id].second; 141 | } 142 | 143 | template 144 | std::pair Archive::popIndiv(int id) { 145 | if (id >= archive_.size()){ 146 | coloredPrint("Error: archive popping id too large", "red"); 147 | return std::pair(); 148 | } 149 | std::pair res = std::make_pair(archive_[id].second, archive_[id].first); 150 | archive_.erase(archive_.begin() + id); 151 | return res; 152 | } 153 | 154 | template 155 | double Archive::getScore(int id) const { 156 | return archive_[id].first; 157 | } -------------------------------------------------------------------------------- /include/chart.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | int updateCharts(const Eigen::MatrixXi& TT, const Eigen::VectorXi& labeling, Eigen::VectorXi& charts); 6 | 7 | Eigen::VectorXi perChartLabels(const Eigen::VectorXi& charts, const Eigen::VectorXi& labeling); 8 | 9 | std::vector perBorderAxis(const std::vector>& patches_per_border, 10 | const Eigen::VectorXi& per_chart_labels); 11 | 12 | std::vector> chartAdjacency(const Eigen::MatrixXi& TT, const Eigen::VectorXi& charts); 13 | 14 | void computeBoundaries(const Eigen::MatrixXi& TT, const Eigen::MatrixXi& F, 15 | const Eigen::VectorXi& charts, 16 | std::vector>& ordered_borders, 17 | std::vector>& patches_per_border, 18 | std::vector& border_triangles); -------------------------------------------------------------------------------- /include/disjointset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // based on ultimaille/ultimaille/helpers/disjointset.h 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | // see https://en.wikipedia.org/wiki/Disjoint-set_data_structure#Disjoint-set_forests 11 | 12 | struct DisjointSet { 13 | // constructor knowing the number of elements 14 | DisjointSet(const int n) : m_ids(n), m_size(n, 1) { 15 | std::iota(m_ids.begin(), m_ids.end(), 0); // initialize the union-find data structure (m_ids[i]=i) 16 | } 17 | 18 | void merge(const int a, const int b) { 19 | assert(a>=0 && b>=0 && a=0 && i=0 && i=0 && b>=0 && a &id2setid) { 70 | id2setid.resize(m_ids.size()); // prepare the correspondance root id => set id 71 | int nsets = 0; 72 | for (int i=0; i<(int)m_ids.size(); i++) { 73 | if (i != root(i)) continue; 74 | id2setid[i] = nsets; 75 | nsets++; 76 | } 77 | for (int i=0; i<(int)m_ids.size(); i++) // fill the rest 78 | id2setid[i] = id2setid[m_ids[i]]; 79 | return nsets; 80 | } 81 | 82 | std::vector m_ids; 83 | std::vector m_size; 84 | }; 85 | -------------------------------------------------------------------------------- /include/distortion.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | Eigen::RowVector3d crossProd(const Eigen::RowVector3d& v1, 6 | const Eigen::RowVector3d& v2); 7 | 8 | void localTransfo(const Eigen::MatrixXd& V, 9 | const Eigen::MatrixXi& F, 10 | const Eigen::MatrixXd& N, 11 | std::vector& vecA); 12 | 13 | void computeJacobians(const Eigen::MatrixXd& V1, 14 | const Eigen::MatrixXd& V2, 15 | const Eigen::MatrixXi& F, 16 | const Eigen::MatrixXd& N, 17 | const Eigen::MatrixXd& N_def, 18 | std::vector& jacobians); 19 | 20 | void computeDisto(const Eigen::MatrixXd& V1, 21 | const Eigen::MatrixXd& V2, 22 | const Eigen::MatrixXi& F, 23 | const Eigen::MatrixXd& N, 24 | const Eigen::MatrixXd& N_def, 25 | Eigen::VectorXd& disto); 26 | 27 | double integrateDistortion(const Eigen::VectorXd& A, 28 | const Eigen::VectorXd& disto); 29 | 30 | // --- Stand-alone functions for polycube metrics --- // 31 | 32 | // Spherical Parametrization and Remeshing 33 | // Praun & Hoppe 34 | double computeStretch(const Eigen::VectorXd& A, double A_m, double A_d, 35 | std::vector> per_tri_singular_values); 36 | 37 | // PolyCube-Maps 38 | // Tarini & Hormann & Cignoni & Montani 39 | double computeAreaDisto(const Eigen::VectorXd& A, std::vector> per_tri_singular_values); 40 | double computeAngleDisto(const Eigen::VectorXd& A, std::vector> per_tri_singular_values); 41 | 42 | // Computing Surface PolyCube-Maps by Constrained Voxelization 43 | // Yang, Fu, Liu 44 | double computeIsometricDisto(const Eigen::VectorXd& A, std::vector> per_tri_singular_values); -------------------------------------------------------------------------------- /include/evaluator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "labeling_individual.h" 5 | #include "evocube.h" 6 | #include "quick_label_ev.h" 7 | #include "logging.h" 8 | 9 | 10 | class Evaluator { 11 | public: 12 | Evaluator(std::shared_ptr evo, 13 | std::shared_ptr qle) 14 | : evo_(evo), qle_(qle){}; 15 | 16 | double evaluate(const LabelingIndividual& indiv, double& fast_poly_score, double& invalid_score, int& n_fail_invert) const { 17 | fast_poly_score = qle_->evaluate(indiv.getLabeling(), n_fail_invert); // avg disto + flipped triangles 18 | invalid_score = indiv.invalidityScore(); 19 | double compact_score = static_cast(indiv.countCorners()); 20 | double fidelity_score = indiv.fidelityScore(); 21 | return fast_poly_score + 100.0 * invalid_score + compact_score + 10000.0 * fidelity_score; 22 | } 23 | 24 | double evaluate(const LabelingIndividual& indiv) const { 25 | double fast_poly_score, invalid_score; 26 | int n_fail_invert; 27 | return evaluate(indiv, fast_poly_score, invalid_score, n_fail_invert); 28 | } 29 | 30 | void fillIndivLogInfo(std::string logs_path, const LabelingIndividual& indiv, std::string indiv_name) const { 31 | double fast_poly_score, invalid_score; 32 | int n_fail_invert; 33 | double score = evaluate(indiv, fast_poly_score, invalid_score, n_fail_invert); 34 | 35 | fillLogInfo(indiv_name, "ScoreInvalid", logs_path, std::to_string(invalid_score)); 36 | fillLogInfo(indiv_name, "ScoreFastPoly", logs_path, std::to_string(fast_poly_score)); 37 | fillLogInfo(indiv_name, "ScoreFinal", logs_path, std::to_string(score)); 38 | fillLogInfo(indiv_name, "InvertedInFastPoly", logs_path, std::to_string(n_fail_invert)); 39 | } 40 | private: 41 | std::shared_ptr evo_; 42 | std::shared_ptr qle_; 43 | }; -------------------------------------------------------------------------------- /include/evocube.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "logging.h" 9 | 10 | // might want to add normal angle to distance 11 | 12 | // Note: such a distance will lead to "round" regions, also it's not mesh independent 13 | // but we're doing polycubes 14 | // how to generate square regions? 15 | // Maybe by considering max(dist_x, dist_y, dist_z) instead? 16 | void triangle_triangle_dist(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 17 | const Eigen::MatrixXi& TT, 18 | Eigen::MatrixXd& dists){ 19 | dists.resize(F.rows(), 3); 20 | 21 | dists = Eigen::MatrixXd::Constant(F.rows(), 3, -1); 22 | 23 | for (int f_id = 0; f_id < F.rows(); f_id++){ 24 | Eigen::RowVector3d G1 = (V.row(F(f_id, 0)) + V.row(F(f_id, 1)) + V.row(F(f_id, 2))) / 3.0; 25 | for (int neigh = 0; neigh < 3; neigh ++){ 26 | int neigh_id = TT(f_id, neigh); 27 | if (neigh_id == -1) { 28 | coloredPrint("Triangle doesn't have a neighbor", "red"); 29 | continue; 30 | } 31 | Eigen::RowVector3d G2 = (V.row(F(neigh_id, 0)) + V.row(F(neigh_id, 1)) + V.row(F(neigh_id, 2))) / 3.0; 32 | // middle point on shared edge: 33 | Eigen::RowVector3d mp = (V.row(F(f_id, neigh)) + V.row(F(f_id, (neigh + 1) % 3))) / 2.0; 34 | dists(f_id, neigh) = (G1 - mp).norm() + (mp - G2).norm(); 35 | } 36 | } 37 | } 38 | 39 | class Evocube { 40 | public: 41 | Evocube(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F) 42 | : V_(V), F_(F){ 43 | 44 | igl::triangle_triangle_adjacency(F_, TT_); 45 | igl::vertex_triangle_adjacency(V_.rows(), F_, VT_, VI_); 46 | igl::per_face_normals(V_, F_, N_); 47 | triangle_triangle_dist(V, F, TT_, dists_); 48 | l_avg_ = igl::avg_edge_length(V, F); 49 | 50 | } 51 | 52 | virtual ~Evocube(){ 53 | coloredPrint("An Evocube bites the dust...", "yellow"); 54 | } 55 | 56 | void fillMeshLogInfo(std::string logs_path){ 57 | fillLogInfo("InputTris", "vertices", logs_path, std::to_string(V_.rows())); 58 | fillLogInfo("InputTris", "faces", logs_path, std::to_string(F_.rows())); 59 | fillLogInfo("InputTris", "AvgEdgeLength", logs_path, l_avg_); 60 | } 61 | 62 | //private: 63 | // Set by constructor 64 | const Eigen::MatrixXd V_; 65 | const Eigen::MatrixXi F_; 66 | Eigen::MatrixXi TT_; 67 | Eigen::MatrixXd N_; 68 | Eigen::MatrixXd dists_; 69 | std::vector> VT_, VI_; 70 | double l_avg_; 71 | 72 | int timestamp_ = 0; 73 | }; -------------------------------------------------------------------------------- /include/flagging_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "logging.h" 9 | 10 | Eigen::MatrixXd axesMatrix(); 11 | 12 | inline int flagToAxis(const int flag){ //just use flag/2 instead 13 | assert(( (0<=flag) && (flag<=5) )); 14 | // flag | axis 15 | // +X=0 | X=0 16 | // -X=1 | X=0 17 | // +Y=2 | Y=1 18 | // -Y=3 | Y=1 19 | // +Z=4 | Z=2 20 | // -Z=5 | Z=2 21 | return flag/2;//integer division 22 | } 23 | 24 | int oppositeLabel(int label); 25 | 26 | Eigen::VectorXi openFlagging(std::string file_name, int expected_size); 27 | 28 | Eigen::MatrixXd colorsFromFlagging(const Eigen::VectorXi& flagging); 29 | 30 | void saveFlagging(std::string file_name, const Eigen::VectorXi& assignment); 31 | 32 | void saveFlaggingOnTets(std::string file_name, std::string tris_to_tets_path, const Eigen::VectorXi& assignment); 33 | 34 | Eigen::VectorXi normalFlagging(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F); 35 | 36 | // % of same labels between 2 labeling vectors 37 | // if different lengths, return -1.0 38 | double flaggingSimilarity(const Eigen::VectorXi& labeling1, const Eigen::VectorXi& labeling2); -------------------------------------------------------------------------------- /include/graphcut_labeling.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | Eigen::VectorXi graphcutFlagging(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 6 | const Eigen::MatrixXd& N, const Eigen::MatrixXi& TT, 7 | const Eigen::VectorXi& locked_flags, const Eigen::VectorXi& forbidden_flags, 8 | int compact_coeff, int fidelity_coeff); 9 | 10 | Eigen::VectorXi graphcutFlagging(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 11 | const Eigen::MatrixXd& N, const Eigen::MatrixXi& TT, 12 | int compact_coeff, int fidelity_coeff); 13 | 14 | std::vector graphcutTurningPoints(const std::vector& bnd, const Eigen::MatrixXd& V, 15 | const Eigen::RowVector3d& desired_dir); -------------------------------------------------------------------------------- /include/labeling_ops.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "quick_label_ev.h" 5 | 6 | void fixHighValenceCorners(const Eigen::MatrixXd& dists, 7 | const Eigen::MatrixXi& TT, 8 | const std::vector>& VT, 9 | const Eigen::VectorXi& charts, 10 | const std::vector>& borders, 11 | double l_avg, 12 | const QuickLabelEv& qle, 13 | Eigen::VectorXi& labeling); 14 | 15 | void directionalPathMutation(const std::vector>& VT, 16 | const Eigen::MatrixXi& TT, 17 | const Eigen::MatrixXd& V, 18 | const Eigen::MatrixXi& F, 19 | const Eigen::MatrixXd& dists, 20 | const std::vector>& borders, 21 | const std::vector>& patches_per_border, 22 | const Eigen::VectorXi& charts, 23 | const Eigen::RowVector3d& direction, 24 | int starting_vertex, 25 | int chart_id, 26 | double distance_thresold, 27 | int introduced_label, 28 | Eigen::VectorXi& labeling); 29 | 30 | void unspikeLabeling(const Eigen::MatrixXi& TT, const std::vector& boundary_triangles, 31 | const Eigen::VectorXi& charts, Eigen::VectorXi& labeling); 32 | 33 | void growFromTris(const Eigen::VectorXi& old_labeling, Eigen::VectorXi& new_labeling, 34 | const Eigen::MatrixXi& TT, const Eigen::MatrixXd& dists, 35 | int introduced_label, const std::vector& start_tri_ids, 36 | double threshold_dist, bool single_chart = true); 37 | 38 | // Remove this one ? 39 | /** 40 | * @brief Same as previously but with a single starting triangle 41 | * 42 | * @param old_labeling 43 | * @param new_labeling 44 | * @param TT 45 | * @param dists 46 | * @param introduced_label 47 | * @param start_tri_id 48 | * @param threshold_dist 49 | */ 50 | void growFromTri(const Eigen::VectorXi& old_labeling, Eigen::VectorXi& new_labeling, 51 | const Eigen::MatrixXi& TT, const Eigen::MatrixXd& dists, 52 | int introduced_label, int start_tri_id, 53 | double threshold_dist); 54 | 55 | void growAroundBorder(const Eigen::MatrixXd& dists, 56 | const Eigen::MatrixXi& TT, 57 | const std::vector>& VT, 58 | const Eigen::VectorXi& charts, 59 | const std::vector& border, 60 | double threshold_dist, 61 | int chart_id, 62 | int introduced_label, 63 | Eigen::VectorXi& labeling); 64 | 65 | void fixOppositeLabels(const Eigen::MatrixXd& dists, 66 | const Eigen::MatrixXi& TT, 67 | const std::vector>& VT, 68 | const Eigen::VectorXi& charts, 69 | const std::vector>& borders, 70 | const std::vector>& patches_per_border, 71 | const Eigen::VectorXi& per_chart_labels, 72 | double l_avg, 73 | const QuickLabelEv& qle, 74 | Eigen::VectorXi& labeling); 75 | 76 | void vertexGrowMutation(const Eigen::VectorXi& old_labeling, Eigen::VectorXi& new_labeling, 77 | const Eigen::VectorXi& charts, 78 | const Eigen::MatrixXi& TT, const std::vector>& VT, 79 | const Eigen::MatrixXd& dists, double threshold_dist, 80 | const std::vector>& borders, 81 | const std::vector>& patches_per_border, 82 | const Eigen::VectorXi& per_chart_labels, 83 | int border_id, 84 | int vertex_start); 85 | 86 | void removeChartMutation(const Eigen::VectorXi& old_labeling, Eigen::VectorXi& new_labeling, 87 | const Eigen::VectorXi& charts, 88 | const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 89 | const Eigen::MatrixXd& N, 90 | const Eigen::MatrixXi& TT, int chart_id); -------------------------------------------------------------------------------- /include/latex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define REMOVE_INPUT_TRI_SUFFIX 10 | 11 | struct time_plot_entry { 12 | double pre_optimization; 13 | double individual_selection; 14 | double individual_mutations; 15 | double charts_and_turning_points; 16 | double fitness_evaluation; 17 | double crossing; 18 | double insertion_in_archive; 19 | double post_optimization; 20 | double genetics; 21 | 22 | //divide all the values by the same number 23 | time_plot_entry &operator/=(double divisor) { 24 | pre_optimization /= divisor; 25 | individual_selection /= divisor; 26 | individual_mutations /= divisor; 27 | charts_and_turning_points /= divisor; 28 | fitness_evaluation /= divisor; 29 | crossing /= divisor; 30 | insertion_in_archive /= divisor; 31 | post_optimization /= divisor; 32 | genetics /= divisor; 33 | return *this; 34 | } 35 | 36 | //sum of plotted values only (all except genetics) 37 | double sum() const { 38 | return ( 39 | pre_optimization + 40 | individual_selection + 41 | individual_mutations + 42 | charts_and_turning_points + 43 | fitness_evaluation + 44 | crossing + 45 | insertion_in_archive + 46 | post_optimization 47 | ); 48 | } 49 | }; 50 | #define INIT_TIME_PLOT_ENTRY(struct) \ 51 | (struct).pre_optimization = 0.0; \ 52 | (struct).individual_selection = 0.0; \ 53 | (struct).individual_mutations = 0.0; \ 54 | (struct).charts_and_turning_points = 0.0; \ 55 | (struct).fitness_evaluation = 0.0; \ 56 | (struct).crossing = 0.0; \ 57 | (struct).insertion_in_archive = 0.0; \ 58 | (struct).post_optimization = 0.0; \ 59 | (struct).genetics = 0.0; 60 | 61 | class LatexDoc { 62 | 63 | public: 64 | /** 65 | * @brief Create a LatexDoc object, wrapping a LaTeX file 66 | * @param filename Path and name of the LaTeX file to create 67 | */ 68 | LatexDoc(std::string filename); 69 | ~LatexDoc(); 70 | 71 | /** 72 | * @brief Insert a LaTeX file with the \c \\input command 73 | * @param path_to_subpage Path to the LaTeX file to insert 74 | */ 75 | void add_subpage(std::filesystem::path path_to_subpage); 76 | 77 | /** 78 | * @brief Insert all the figures of a mesh (input, labelling, polycube) on a new page 79 | * @param path_to_mesh_folder Path to the mesh folder containing the pictures 80 | * @param polycube_tagname Name of the JSON tag in which the distortion measures will be read. Must start with '/'. 81 | * For now, should be "/FastPolycubeFloat" or "/FastPolycubeInt" 82 | * @param timings Per-function timing counter that will be updated 83 | * @return 0 if good -> mesh added 84 | * 1 if some pictures are missing -> mesh added anyway 85 | * 2 if labeling invalid (given the log file) -> create a page saying no valid labeling was found 86 | * 3 if the log file is not found -> mesh skipped 87 | */ 88 | int add_mesh(std::filesystem::path path_to_mesh_folder, std::string polycube_tagname, time_plot_entry& timings); 89 | 90 | /** 91 | * @brief Insert a bar plot with the time spent in each part of the algorithm 92 | * @param cpu List of CPU durations (hours) 93 | * @param real List of Real time durations (hours) 94 | */ 95 | void add_time_plot(const time_plot_entry& cpu, const time_plot_entry& real); 96 | 97 | private: 98 | std::ofstream ofs; 99 | 100 | /** 101 | * @brief Insert 4 subfigures of a mesh. \e figure_id select the type of pictures. 102 | * @param path_to_mesh_folder Path to the mesh folder containing the pictures 103 | * @param figure_id See \c PATH_TO_FIGURE in \c latex.cpp and the \c figure_generator app 104 | * @param caption The caption to put bellow the figure 105 | * @return False if the figure (= 4 subfigures) is complete, True if at least 1 subfigure is missing 106 | */ 107 | bool add_pictures(std::filesystem::path path_to_mesh_folder, int figure_id, std::string caption); 108 | 109 | /** 110 | * @brief Insert a 2 by \c values.size() table 111 | * @param values List of rows. A row being a list of strings 112 | * All sub-vectors should have the same size 113 | * If a string contains '#' or '_', they will be escaped 114 | */ 115 | void add_table(const std::vector>& values); 116 | 117 | }; 118 | 119 | //replace '#' with '\\#' and '_' with '\\_' 120 | std::string escape_special_chars(const std::string input); 121 | std::string remove_special_chars(const std::string input); 122 | 123 | std::string double2string(double value, int precision); -------------------------------------------------------------------------------- /include/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * @brief Logging utilities 5 | * @date 2021-10-12 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using std::chrono::milliseconds; 15 | using std::chrono::duration_cast; 16 | 17 | void coloredPrint(std::string text, std::string color); 18 | 19 | void resetLogFile(std::string filepath); 20 | 21 | std::string readLogsValue(std::string tag1, std::string tag2, std::string filepath); 22 | void fillLogInfo(std::string tag, std::string filepath, std::string value); 23 | void fillLogInfo(std::string tag1, std::string tag2, std::string filepath, std::string value); 24 | void fillLogInfo(std::string tag1, std::string tag2, std::string filepath, double value); 25 | void fillLogInfo(std::string tag1, std::string tag2, std::string filepath, int value); 26 | 27 | void removeLogInfo(std::string tag, std::string filepath); 28 | 29 | std::string readLogInfo(std::string tag, std::string filepath); 30 | 31 | std::map readAllLogInfo(std::string filepath); 32 | 33 | std::vector getLogTags(std::string filepath); 34 | 35 | void printLog(std::string filepath); 36 | 37 | double measureTime(std::chrono::time_point t1, 38 | std::chrono::time_point t2); 39 | 40 | class Chronometre { 41 | public: 42 | Chronometre(std::string log_file) 43 | : log_file_(log_file) { 44 | initial_time_ = std::chrono::steady_clock::now(); 45 | last_lap_ = std::chrono::steady_clock::now(); 46 | }; 47 | 48 | void newTimestamp(std::string lap_name){ 49 | std::chrono::steady_clock::time_point new_lap = std::chrono::steady_clock::now(); 50 | int64_t lap_time = duration_cast(new_lap - last_lap_).count(); 51 | fillLogInfo(lap_name, log_file_, std::to_string(lap_time)); 52 | last_lap_ = new_lap; 53 | } 54 | 55 | void totalTime(){ 56 | std::chrono::steady_clock::time_point new_lap = std::chrono::steady_clock::now(); 57 | int64_t total_time = duration_cast(new_lap - initial_time_).count(); 58 | fillLogInfo("TimeTotal", log_file_, std::to_string(total_time)); 59 | } 60 | 61 | private: 62 | std::string log_file_; 63 | std::chrono::steady_clock::time_point last_lap_; 64 | std::chrono::steady_clock::time_point initial_time_; 65 | }; -------------------------------------------------------------------------------- /include/mesh_io.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "logging.h" 9 | 10 | // .mesh documentation : https://www.ljll.math.upmc.fr/frey/logiciels/Docmedit.dir/index.html 11 | // (spelling mistake in HexaHedra) 12 | 13 | void writeDotMeshTet(std::string filename, const Eigen::MatrixXd& V, const Eigen::MatrixXi& tets){ 14 | // tets : matrix of size (n_tets, 4) 15 | 16 | std::ofstream out_mesh; 17 | out_mesh.open (filename); 18 | 19 | out_mesh << "MeshVersionFormatted 1\n"; 20 | out_mesh << "Dimension\n3\n"; 21 | 22 | out_mesh << "Vertices\n"; 23 | out_mesh << V.rows() << "\n"; 24 | for (int i=0; i> header; 51 | std::cout << header <<" "; 52 | } 53 | std::cout << std::endl; 54 | 55 | std::string section_name; 56 | input_file >> section_name; 57 | 58 | if (section_name != "Vertices"){ 59 | coloredPrint("Error : expected Vertices", "red"); 60 | } 61 | 62 | int n_vertices; 63 | input_file >> n_vertices; 64 | std::cout << n_vertices << " vertices" << std::endl; 65 | V.resize(n_vertices, 3); 66 | for (int i=0; i> a; 69 | input_file >> b; 70 | input_file >> c; 71 | V.row(i) = Eigen::RowVector3d(a, b, c); 72 | input_file >> a; // discard ref element 73 | } 74 | 75 | int discarded = 0; 76 | input_file >> section_name; 77 | while (section_name != "Tetrahedra" && input_file >> section_name){ 78 | discarded ++; 79 | } 80 | 81 | std::cout << discarded << " elements discarded" << std::endl; 82 | 83 | if (section_name != "Tetrahedra"){ 84 | coloredPrint("Error : expected Tetrahedra", "red"); 85 | } 86 | else { 87 | int n_tets; 88 | input_file >> n_tets; 89 | std::cout << n_tets << " tets" << std::endl; 90 | tets.resize(n_tets, 4); 91 | 92 | for (int i=0; i> a; 95 | input_file >> b; 96 | input_file >> c; 97 | input_file >> d; 98 | tets.row(i) = Eigen::RowVector4i(a-1, b-1, c-1, d-1); 99 | input_file >> a; // discard ref element 100 | } 101 | } 102 | input_file.close(); 103 | } 104 | 105 | void triObjtoMeshTet(std::string input_path, std::string output_path, 106 | Eigen::MatrixXd& TV, Eigen::MatrixXi& TT){ 107 | Eigen::MatrixXd V; 108 | Eigen::MatrixXi F, TF; 109 | igl::readOBJ(input_path, V, F); 110 | 111 | V.rowwise() -= V.colwise().minCoeff(); 112 | V /= V.maxCoeff() / 10.0; 113 | std::cout << "min: " << std::endl; 114 | std::cout << V.colwise().minCoeff() << std::endl; 115 | std::cout << "max: " << std::endl; 116 | std::cout << V.colwise().maxCoeff() << std::endl; 117 | 118 | 119 | // Mesh the interior of a surface mesh (V,F) using tetgen 120 | // 121 | // Inputs: 122 | // V #V by 3 vertex position list 123 | // F #F list of polygon face indices into V (0-indexed) 124 | // switches string of tetgen options (See tetgen documentation) e.g. 125 | // "pq1.414a0.01" tries to mesh the interior of a given surface with 126 | // quality and area constraints 127 | // "" will mesh the convex hull constrained to pass through V (ignores F) 128 | // Outputs: 129 | // TV #V by 3 vertex position list 130 | // TT #T by 4 list of tet face indices 131 | // TF #F by 3 list of triangle face indices 132 | // Returns status: 133 | // 0 success 134 | // 1 tetgen threw exception 135 | // 2 tetgen did not crash but could not create any tets (probably there are 136 | // holes, duplicate faces etc.) 137 | // -1 other error 138 | 139 | // tetgen manual https://wias-berlin.de/software/tetgen/1.5/doc/manual/manual.pdf 140 | // -p Tetrahedralizes a piecewise linear complex (PLC) 141 | // -q Refines mesh, see 4.2.3 142 | // -a Applies a maximum tetrahedron volume constraint. 143 | // -Y keep boundary mesh from input 144 | 145 | int success = igl::copyleft::tetgen::tetrahedralize(V, F, "pqa", TV, TT, TF); 146 | writeDotMeshTet(output_path, TV, TT); 147 | if (TV.rows() > 70000) std::cout << "Warning! Generated mesh is really large." << std::endl; 148 | } 149 | 150 | void triObjtoMeshTet(std::string input_path, std::string output_path){ 151 | Eigen::MatrixXd TV; 152 | Eigen::MatrixXi TT; 153 | triObjtoMeshTet(input_path, output_path, TV, TT); 154 | } 155 | 156 | void readDotMeshTri(std::string input_path, Eigen::MatrixXd &V, Eigen::MatrixXi &F){ 157 | std::ifstream input_file(input_path); 158 | 159 | if (input_file.fail()){ 160 | std::cout << "\033[31mWarning\033[0m: no triangles\n" << std::endl; 161 | V = Eigen::MatrixXd(0, 3); 162 | F = Eigen::MatrixXi(0, 3); 163 | } 164 | 165 | // TODO vertices part is a copy past from readTet function, factorize ? 166 | std::string header; 167 | for (int i=0; i<4; i++){ 168 | input_file >> header; 169 | } 170 | 171 | std::string section_name; 172 | input_file >> section_name; 173 | 174 | if (section_name != "Vertices"){ 175 | std::cout << "Error : expected Vertices" << std::endl; 176 | } 177 | 178 | int n_vertices; 179 | input_file >> n_vertices; 180 | std::cout << n_vertices << " vertices" << std::endl; 181 | V.resize(n_vertices, 3); 182 | for (int i=0; i> a; 185 | input_file >> b; 186 | input_file >> c; 187 | V.row(i) = Eigen::RowVector3d(a, b, c); 188 | input_file >> a; // discard ref element 189 | } 190 | 191 | int discarded = 0; 192 | input_file >> section_name; 193 | while (section_name != "Triangles" && input_file >> section_name){ 194 | discarded ++; 195 | } 196 | 197 | std::cout << discarded << " elements discarded" << std::endl; 198 | 199 | if (section_name != "Triangles"){ 200 | std::cout << "Error : expected Triangles" << std::endl; 201 | } 202 | else { 203 | int n_tri; 204 | input_file >> n_tri; 205 | std::cout << n_tri << " tri" << std::endl; 206 | F.resize(n_tri, 3); 207 | 208 | for (int i=0; i> a; 211 | input_file >> b; 212 | input_file >> c; 213 | F.row(i) = Eigen::RowVector3i(a-1, b-1, c-1); 214 | input_file >> a; // discard ref element 215 | } 216 | } 217 | input_file.close(); 218 | } 219 | 220 | void writeDotMeshHex(std::string path, Eigen::MatrixXd V, Eigen::MatrixXi hexahedra) { 221 | // hexahedra : matrix of size (n_hexa, 8) 222 | 223 | std::cout << "Saving hex mesh: " << path << std::endl; 224 | 225 | std::ofstream out_mesh; 226 | out_mesh.open(path); 227 | 228 | out_mesh << "MeshVersionFormatted 1\n"; 229 | out_mesh << "Dimension\n3\n"; 230 | 231 | out_mesh << "Vertices\n"; 232 | out_mesh << V.rows() << "\n"; 233 | for (int i=0; i> header; 260 | //std::cout << header <<" "; 261 | } 262 | //std::cout << std::endl; 263 | 264 | std::string section_name; 265 | input_file >> section_name; 266 | 267 | if (section_name != "Vertices"){ 268 | std::cout << "Error : expected Vertices" << std::endl; 269 | } 270 | 271 | int n_vertices; 272 | input_file >> n_vertices; 273 | std::cout << n_vertices << " vertices" << std::endl; 274 | V.resize(n_vertices, 3); 275 | for (int i=0; i> a; 278 | input_file >> b; 279 | input_file >> c; 280 | V.row(i) = Eigen::RowVector3d(a, b, c); 281 | input_file >> a; // discard ref element 282 | } 283 | 284 | int discarded = 0; 285 | input_file >> section_name; 286 | while (section_name != "Hexahedra" && input_file >> section_name){ 287 | discarded ++; 288 | } 289 | 290 | std::cout << discarded << " elements discarded" << std::endl; 291 | 292 | if (section_name != "Hexahedra"){ 293 | std::cout << "Error : expected Hexahedra" << std::endl; 294 | } 295 | else { 296 | int n_hex; 297 | input_file >> n_hex; 298 | std::cout << n_hex << " hexes" << std::endl; 299 | hexes.resize(n_hex, 8); 300 | 301 | for (int i=0; i> a; 304 | input_file >> b; 305 | input_file >> c; 306 | input_file >> d; 307 | input_file >> e; 308 | input_file >> f; 309 | input_file >> g; 310 | input_file >> h; 311 | Eigen::RowVectorXi row(8); 312 | row << a-1, b-1, c-1, d-1, e-1, f-1, g-1, h-1; 313 | hexes.row(i) = row; 314 | input_file >> a; // discard ref element 315 | } 316 | } 317 | input_file.close(); 318 | } 319 | 320 | void readDotMeshEdges(std::string input_edges, Eigen::MatrixXd &V, Eigen::MatrixXi &edges) { 321 | std::ifstream input_file(input_edges); 322 | 323 | if (input_file.fail()){ 324 | std::cout << "\033[31mWarning\033[0m: no feature edges.\n" << std::endl; 325 | V = Eigen::MatrixXd(0, 3); 326 | edges = Eigen::MatrixXi(0, 2); 327 | } 328 | 329 | // TODO vertices part is a copy past from readTet function, factorize ? 330 | std::string header; 331 | for (int i=0; i<4; i++){ 332 | input_file >> header; 333 | } 334 | 335 | std::string section_name; 336 | input_file >> section_name; 337 | 338 | if (section_name != "Vertices"){ 339 | std::cout << "Error : expected Vertices" << std::endl; 340 | } 341 | 342 | int n_vertices; 343 | input_file >> n_vertices; 344 | std::cout << n_vertices << " vertices" << std::endl; 345 | V.resize(n_vertices, 3); 346 | for (int i=0; i> a; 349 | input_file >> b; 350 | input_file >> c; 351 | V.row(i) = Eigen::RowVector3d(a, b, c); 352 | input_file >> a; // discard ref element 353 | } 354 | 355 | int discarded = 0; 356 | input_file >> section_name; 357 | while (section_name != "Edges" && input_file >> section_name){ 358 | discarded ++; 359 | } 360 | 361 | std::cout << discarded << " elements discarded" << std::endl; 362 | 363 | if (section_name != "Edges"){ 364 | std::cout << "Error : expected Edges" << std::endl; 365 | } 366 | else { 367 | int n_edges; 368 | input_file >> n_edges; 369 | std::cout << n_edges << " edges" << std::endl; 370 | edges.resize(n_edges, 2); 371 | 372 | for (int i=0; i> a; 375 | input_file >> b; 376 | edges.row(i) = Eigen::RowVector2i(a-1, b-1); 377 | input_file >> a; // discard ref element 378 | } 379 | } 380 | input_file.close(); 381 | } -------------------------------------------------------------------------------- /include/minSJ.txt: -------------------------------------------------------------------------------- 1 | // Code from Francois 2 | // TODO compute SJ stats from a .mesh hex 3 | 4 | 5 | constexpr int HEX_CORNER_SPLITTING[8][4] = { 6 | {0,1,2,4}, 7 | {1,3,0,5}, 8 | {2,0,3,6}, 9 | {3,2,1,7}, 10 | {4,6,5,0}, 11 | {5,4,7,1}, 12 | {6,7,4,2}, 13 | {7,5,6,3}, 14 | }; 15 | double compute_scaled_jacobian(const UM::Hexahedra& hex, UM::CellAttribute& cell_min_sj) { 16 | double glob_min = 1; 17 | for (int h : range(hex.ncells())) { 18 | double min_sj = 1; 19 | for (int hv : range(8)) { 20 | std::array v; 21 | for (int i : range(4)) v[i] = hex.points[hex.vert(h, HEX_CORNER_SPLITTING[hv][i])]; 22 | vec3 n1 = v[1] - v[0]; n1.normalize(); 23 | vec3 n2 = v[2] - v[0]; n2.normalize(); 24 | vec3 n3 = v[3] - v[0]; n3.normalize(); 25 | min_sj = std::min(min_sj, n3 * cross(n1, n2)); 26 | } 27 | cell_min_sj[h] = min_sj; 28 | glob_min = std::min(glob_min, min_sj); 29 | } 30 | return glob_min; 31 | } -------------------------------------------------------------------------------- /include/quick_label_ev.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // see https://github.com/fprotais/fastbndpolycube 4 | 5 | #include 6 | #include 7 | 8 | #include "logging.h" 9 | 10 | class QuickLabelEv { 11 | public: 12 | QuickLabelEv(){} 13 | QuickLabelEv(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F) : V_(V), F_(F) { 14 | igl::per_face_normals(V_, F_, N_); 15 | igl::doublearea(V_, F_, A_); 16 | A_ /= 2.0; 17 | } 18 | 19 | double evaluate(const Eigen::VectorXi& labeling) const; 20 | double evaluate(const Eigen::VectorXi& labeling, int& n_fail_invert) const; 21 | 22 | Eigen::MatrixXd computeDeformedV(const Eigen::VectorXi& labeling) const; 23 | 24 | 25 | //Eigen::MatrixXd getDefV(){return def_V_;} 26 | 27 | Eigen::MatrixXd distoAboveThreshold(const Eigen::VectorXi& labeling, double threshold) const; 28 | 29 | /*void copyData(Eigen::MatrixXd& V, Eigen::MatrixXi& F, 30 | Eigen::MatrixXd& N, Eigen::VectorXd& A, 31 | Eigen::MatrixXd& def_V) const { 32 | V = V_; 33 | F = F_; 34 | N = N_; 35 | A = A_; 36 | def_V = def_V_; 37 | };*/ 38 | 39 | virtual ~QuickLabelEv(){ 40 | #ifdef VERBOSE_BIRTH_DEATH 41 | coloredPrint("An Evaluator kicked the bucket...", "yellow"); 42 | #endif 43 | } 44 | 45 | private: 46 | // Set by constructor 47 | const Eigen::MatrixXd V_; 48 | const Eigen::MatrixXi F_; 49 | Eigen::MatrixXd N_; 50 | Eigen::VectorXd A_; 51 | 52 | // Each evaluation computes a new deformation 53 | // Eigen::MatrixXd def_V_; 54 | 55 | Eigen::MatrixXd LDLTDeformation(const Eigen::VectorXi& labeling) const; 56 | Eigen::MatrixXd hardDeformation(const Eigen::VectorXi& labeling) const; 57 | 58 | }; -------------------------------------------------------------------------------- /include/scaled_jacobian.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | // /!\ WARNING : MEDIT convention here (.mesh). Different from UM, OVM conventions 7 | // 5-------6 8 | // /| /| 9 | // / | / | 10 | // 1-------2 | 11 | // | 4----|--7 12 | // | / | / 13 | // |/ |/ 14 | // 0-------3 15 | constexpr int HEX_CORNER_SPLITTING[8][4] = { 16 | {0,3,4,1},//bottom-front-left corner 17 | {3,7,0,2},//bottom-front-right corner 18 | {4,0,7,5},//bottom-back-left corner 19 | {7,4,3,6},//bottom-back-right corner 20 | {1,5,2,0},//top-front-left corner 21 | {2,1,6,3},//top-front-right corner 22 | {5,6,1,4},//top-back-left corner 23 | {6,2,5,7},//top-back-right corner 24 | }; 25 | 26 | /** 27 | * @brief Compte the min Scaled Jacobian on an hexahedral mesh 28 | * @param hexes #hexes by 8 matrix of vertex id (integers). MEDIT vertex ordering. 29 | * @param V_hexes #vertices by 3 matrix of 3D coordinates (doubles) 30 | * @param per_cell_min_sj Output. #hexes by 1 vector of min Scaled Jacobian values (doubles) 31 | * @return the global min Scaled Jacobian 32 | */ 33 | double compute_min_scaled_jacobian(const Eigen::MatrixXi& hexes, const Eigen::MatrixXd& V_hexes, Eigen::VectorXd& per_cell_min_sj) { 34 | 35 | assert(( hexes.cols() == 8 )); 36 | assert(( hexes.maxCoeff() <= V_hexes.rows() )); 37 | 38 | per_cell_min_sj.resize(hexes.rows()); 39 | double glob_min = 1; 40 | double min_sj = 1; 41 | Eigen::Vector3d n1, n2, n3; 42 | 43 | for (int h = 0; h < hexes.rows(); h++) { //for each hexahedron 44 | 45 | min_sj = 1; 46 | for (int hv = 0; hv < 8; hv++) { //for each vertex of the current hexahedron 47 | 48 | //get the coordinates of the 4 vertices constituting the corner n°hv 49 | Eigen::MatrixXd v(4,3); 50 | for (int i = 0; i < 4; i++) { 51 | v.row(i) = V_hexes.row(hexes(h,HEX_CORNER_SPLITTING[hv][i])); 52 | } 53 | 54 | n1 = (v.row(1) - v.row(0)).normalized(); 55 | n2 = (v.row(2) - v.row(0)).normalized(); 56 | n3 = (v.row(3) - v.row(0)).normalized(); 57 | min_sj = std::min(min_sj, n3.dot(n1.cross(n2))); 58 | } 59 | per_cell_min_sj(h) = min_sj; 60 | glob_min = std::min(glob_min, min_sj); 61 | } 62 | 63 | return glob_min; 64 | } -------------------------------------------------------------------------------- /include/tet_boundary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class BndToTetConverter { 6 | public: 7 | BndToTetConverter(std::string input_file); 8 | BndToTetConverter(const Eigen::VectorXi table, int n_tets); 9 | void writeTo(std::string write_path); 10 | 11 | int n_tris_; 12 | int n_tets_; 13 | Eigen::VectorXi table_; 14 | }; 15 | 16 | void computeTetMeshBoundary(const Eigen::MatrixXi& tets, const Eigen::MatrixXd& V, 17 | std::string output_tris_to_tets, std::string output_bnd); 18 | 19 | // "inverse" operation: From tet mesh, compute boundary, matching the order of (Fb, Vb) 20 | // and the correspondences defined in corres_path 21 | void tetVerticesToBoundaryVertices(const Eigen::MatrixXd& Vb, const Eigen::MatrixXi& Fb, 22 | const Eigen::MatrixXd& V_tets, const Eigen::MatrixXi& tets, 23 | std::string corres_path, 24 | Eigen::MatrixXd& Vf); 25 | 26 | // TODO avoid duplicate with computeTetMeshBoundary 27 | void tetToBnd(const Eigen::MatrixXd& V_tets, const Eigen::MatrixXi& tets, 28 | Eigen::MatrixXd& NV, Eigen::MatrixXi& NF); -------------------------------------------------------------------------------- /lib/OpenNL_psm/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (BUILD_SHARED_LIBS) 2 | add_definitions(-DGEO_DYNAMIC_LIBS) 3 | endif () 4 | 5 | add_library(OpenNL_psm OpenNL_psm.c) 6 | target_link_libraries(OpenNL_psm ${CMAKE_DL_LIBS}) 7 | find_package(OpenMP) 8 | if(OpenMP_CXX_FOUND) 9 | target_link_libraries(OpenNL_psm OpenMP::OpenMP_CXX) 10 | endif() 11 | set_target_properties(OpenNL_psm PROPERTIES SOVERSION 1.7) 12 | 13 | 14 | -------------------------------------------------------------------------------- /lib/OpenNL_psm/OpenNL_example.c: -------------------------------------------------------------------------------- 1 | /* 2 | * To compile under Linux: 3 | * gcc -O3 -fopenmp -frounding-math -ffp-contract=off --std=c++11 OpenNL_example.c OpenNL_psm.c -o OpenNL_example -ldl -lm 4 | */ 5 | 6 | /* 7 | * Copyright (c) 2004-2014, Bruno Levy 8 | * All rights reserved. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * * Redistributions of source code must retain the above copyright notice, 14 | * this list of conditions and the following disclaimer. 15 | * * Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * * Neither the name of the ALICE Project-Team nor the names of its 19 | * contributors may be used to endorse or promote products derived from this 20 | * software without specific prior written permission. 21 | * 22 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 26 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | * POSSIBILITY OF SUCH DAMAGE. 33 | * 34 | * If you modify this software, you should include a notice giving the 35 | * name of the person performing the modification, the date of modification, 36 | * and the reason for such modification. 37 | * 38 | * Contact: Bruno Levy 39 | * 40 | * Bruno.Levy@inria.fr 41 | * http://www.loria.fr/~levy 42 | * 43 | * ALICE Project 44 | * LORIA, INRIA Lorraine, 45 | * Campus Scientifique, BP 239 46 | * 54506 VANDOEUVRE LES NANCY CEDEX 47 | * FRANCE 48 | * 49 | */ 50 | #include "OpenNL_psm.h" 51 | 52 | #include 53 | #include 54 | 55 | /** 56 | * \brief Tests OpenNL solve with 57 | * a simple linear system. 58 | * \details 59 | * Solve \f$ \left[ \begin{array}{ll} 1 & 2 \\ 3 & 4 \end{array} \right] 60 | * \left[ \begin{array}{l} x \\ y \end{array} \right] 61 | * = \left[ \begin{array}{l} 5 \\ 6 \end{array} \right] \f$ 62 | */ 63 | static void test_simple_linear_solve(NLint solver) { 64 | NLboolean symmetric = NL_FALSE; 65 | 66 | printf("\n"); 67 | printf("Testing linear solve\n"); 68 | printf("====================\n"); 69 | 70 | 71 | 72 | switch(solver) { 73 | case NL_SOLVER_DEFAULT: 74 | printf("Using default solver (BiCGStab)\n"); 75 | break; 76 | case NL_CG: 77 | printf("Using CG\n"); 78 | symmetric = NL_TRUE; 79 | break; 80 | case NL_GMRES: 81 | printf("Using GMRES\n"); 82 | break; 83 | case NL_BICGSTAB: 84 | printf("Using BiCGSTAB\n"); 85 | break; 86 | case NL_PERM_SUPERLU_EXT: 87 | printf("(with permutation) "); 88 | /* Fall through */ 89 | case NL_SUPERLU_EXT: 90 | printf("Using SUPERLU\n"); 91 | if(nlInitExtension("SUPERLU")) { 92 | printf("...SUPERLU extension successfully initialized\n"); 93 | } else { 94 | printf("...failed to initialize SUPERLU extension\n"); 95 | printf("Needs Linux/shared librariess/-DGEO_DYNAMIC_LIBS\n"); 96 | return; 97 | } 98 | break; 99 | case NL_CHOLMOD_EXT: 100 | symmetric = NL_TRUE; 101 | printf("using CHOLMOD\n"); 102 | if(nlInitExtension("CHOLMOD")) { 103 | printf("...CHOLMOD extension successfully initialized\n"); 104 | }else { 105 | printf("...failed to initialize CHOLMOD extension\n"); 106 | printf("Needs Linux/shared librariess/-DGEO_DYNAMIC_LIBS\n"); 107 | return; 108 | } 109 | break; 110 | } 111 | 112 | if(symmetric) { 113 | printf("Creating linear system:\n"); 114 | printf(" 1.0*x0 - 5.0*x1 = 5.0\n"); 115 | printf(" -5.0*x0 + 4.0*x1 = 6.0\n"); 116 | } else { 117 | printf("Creating linear system:\n"); 118 | printf(" 1.0*x0 + 2.0*x1 = 5.0\n"); 119 | printf(" 3.0*x0 + 4.0*x1 = 6.0\n"); 120 | } 121 | 122 | /* Create and initialize OpenNL context */ 123 | nlNewContext(); 124 | nlSolverParameteri(NL_NB_VARIABLES, 2); 125 | nlSolverParameteri(NL_SOLVER, solver); 126 | 127 | /* Build system */ 128 | nlBegin(NL_SYSTEM); 129 | nlBegin(NL_MATRIX); 130 | nlBegin(NL_ROW); 131 | nlCoefficient(0, 1.0); 132 | nlCoefficient(1, symmetric ? -5.0 : 2.0); 133 | nlRightHandSide(5.0); 134 | nlEnd(NL_ROW); 135 | nlBegin(NL_ROW); 136 | nlCoefficient(0, symmetric ? -5.0 : 3.0); 137 | nlCoefficient(1, 4.0); 138 | nlRightHandSide(6.0); 139 | nlEnd(NL_ROW); 140 | nlEnd(NL_MATRIX); 141 | nlEnd(NL_SYSTEM); 142 | 143 | /* Solve and get solution */ 144 | printf("Solving...\n"); 145 | nlSolve(); 146 | 147 | 148 | printf("Solution: x0=%f x1=%f\n", nlGetVariable(0), nlGetVariable(1)); 149 | 150 | if(symmetric) { 151 | printf("Verifying:\n"); 152 | printf( 153 | " 1.0*x0 - 5.0*x1 = %f\n", 154 | 1.0 * nlGetVariable(0) - 5.0 * nlGetVariable(1) 155 | ); 156 | printf( 157 | " -5.0*x0 + 4.0*x1 = %f\n", 158 | -5.0 * nlGetVariable(0) + 4.0 * nlGetVariable(1) 159 | ); 160 | } else { 161 | printf("Verifying:\n"); 162 | printf( 163 | " 1.0*x0 + 2.0*x1 = %f\n", 164 | 1.0 * nlGetVariable(0) + 2.0 * nlGetVariable(1) 165 | ); 166 | printf( 167 | " 3.0*x0 + 4.0*x1 = %f\n", 168 | 3.0 * nlGetVariable(0) + 4.0 * nlGetVariable(1) 169 | ); 170 | } 171 | 172 | /* Cleanup */ 173 | nlDeleteContext(nlGetCurrent()); 174 | } 175 | 176 | static void test_least_squares_regression( 177 | NLboolean origin, NLboolean use_SSOR_precond 178 | ) { 179 | NLint nb_pts = 7, k; 180 | NLdouble XY[7][2] = { 181 | {1.0, 3.5}, 182 | {2.0, 3.8}, 183 | {3.0, 5.5}, 184 | {4.0, 5.4}, 185 | {5.0, 6.3}, 186 | {6.0, 8.2}, 187 | {7.0, 9.5}, 188 | }; 189 | 190 | printf("\n"); 191 | if(origin) { 192 | printf("Testing constrained least-squares regression\n"); 193 | printf("============================================\n"); 194 | } else { 195 | printf("Testing least-squares regression\n"); 196 | printf("================================\n"); 197 | } 198 | 199 | 200 | nlNewContext(); 201 | nlSolverParameteri(NL_NB_VARIABLES, 2); 202 | nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE); 203 | 204 | if(use_SSOR_precond) { 205 | printf("Using SSOR preconditioner\n"); 206 | nlSolverParameteri(NL_PRECONDITIONER, NL_PRECOND_SSOR); 207 | } else { 208 | printf("Using default preconditioner (Jacobi)\n"); 209 | } 210 | 211 | nlBegin(NL_SYSTEM); 212 | if(origin) { 213 | nlLockVariable(1); 214 | nlSetVariable(1,0.0); 215 | } 216 | nlBegin(NL_MATRIX); 217 | for(k=0; k 108 | 109 | #ifdef __cplusplus 110 | extern "C" { 111 | #endif 112 | 113 | #define NL_VERSION_4_0 1 114 | 115 | #define NLAPI 116 | 117 | /* 118 | * Deactivate warnings about documentation 119 | * We do that, because CLANG's doxygen parser does not know 120 | * some doxygen commands that we use (retval, copydoc) and 121 | * generates many warnings for them... 122 | */ 123 | 124 | #if defined(__clang__) 125 | #pragma clang diagnostic ignored "-Wunknown-pragmas" 126 | #pragma clang diagnostic ignored "-Wdocumentation" 127 | #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" 128 | #endif 129 | 130 | 131 | 132 | typedef unsigned int NLenum; 133 | 134 | typedef unsigned char NLboolean; 135 | 136 | typedef unsigned int NLbitfield; 137 | 138 | typedef void NLvoid; 139 | 140 | typedef signed char NLbyte; 141 | 142 | typedef short NLshort; 143 | 144 | typedef int NLint; 145 | 146 | typedef unsigned char NLubyte; 147 | 148 | typedef unsigned short NLushort; 149 | 150 | typedef unsigned int NLuint; 151 | 152 | typedef long NLlong; 153 | 154 | typedef unsigned long NLulong; 155 | 156 | typedef int NLsizei; 157 | 158 | typedef float NLfloat; 159 | 160 | typedef double NLdouble; 161 | 162 | typedef void(*NLfunc)(void); 163 | 164 | typedef void* NLContext; 165 | 166 | #define NL_FALSE 0x0 167 | #define NL_TRUE 0x1 168 | 169 | 170 | #define NL_SOLVER 0x100 171 | 172 | #define NL_NB_VARIABLES 0x101 173 | 174 | #define NL_LEAST_SQUARES 0x102 175 | 176 | #define NL_MAX_ITERATIONS 0x103 177 | 178 | #define NL_THRESHOLD 0x104 179 | 180 | #define NL_OMEGA 0x105 181 | 182 | #define NL_SYMMETRIC 0x106 183 | 184 | #define NL_USED_ITERATIONS 0x107 185 | 186 | #define NL_ERROR 0x108 187 | 188 | #define NL_INNER_ITERATIONS 0x109 189 | 190 | #define NL_ELAPSED_TIME 0x10a 191 | 192 | #define NL_PRECONDITIONER 0x10b 193 | 194 | #define NL_GFLOPS 0x10c 195 | 196 | #define NL_NNZ 0x10d 197 | 198 | 199 | #define NL_NB_SYSTEMS 0x10e 200 | 201 | 202 | #define NL_SOLVER_DEFAULT 0x000 203 | 204 | #define NL_CG 0x200 205 | 206 | #define NL_BICGSTAB 0x201 207 | 208 | #define NL_GMRES 0x202 209 | 210 | 211 | #define NL_PRECOND_NONE 0x000 212 | 213 | #define NL_PRECOND_JACOBI 0x300 214 | 215 | #define NL_PRECOND_SSOR 0x301 216 | 217 | #define NL_PRECOND_USER 0x303 218 | 219 | 220 | #define NL_NORMALIZE_ROWS 0x400 221 | 222 | #define NL_VERBOSE 0x401 223 | 224 | 225 | #define NL_NO_VARIABLES_INDIRECTION 0x402 226 | 227 | 228 | NLAPI NLContext NLAPIENTRY nlNewContext(void); 229 | 230 | NLAPI void NLAPIENTRY nlDeleteContext(NLContext context); 231 | 232 | NLAPI void NLAPIENTRY nlMakeCurrent(NLContext context); 233 | 234 | NLAPI NLContext NLAPIENTRY nlGetCurrent(void); 235 | 236 | NLAPI NLboolean NLAPIENTRY nlInitExtension(const char* extension); 237 | 238 | NLAPI NLboolean NLAPIENTRY nlExtensionIsInitialized(const char* extension); 239 | 240 | NLAPI void NLAPIENTRY nlInitialize(int argc, char** argv); 241 | 242 | 243 | NLAPI void NLAPIENTRY nlSolverParameterd(NLenum pname, NLdouble param); 244 | 245 | NLAPI void NLAPIENTRY nlSolverParameteri(NLenum pname, NLint param); 246 | 247 | NLAPI void NLAPIENTRY nlGetBooleanv(NLenum pname, NLboolean* params); 248 | 249 | NLAPI void NLAPIENTRY nlGetDoublev(NLenum pname, NLdouble* params); 250 | 251 | NLAPI void NLAPIENTRY nlGetIntegerv(NLenum pname, NLint* params); 252 | 253 | NLAPI void NLAPIENTRY nlGetIntegervL(NLenum pname, NLlong* params); 254 | 255 | 256 | NLAPI void NLAPIENTRY nlEnable(NLenum pname); 257 | 258 | NLAPI void NLAPIENTRY nlDisable(NLenum pname); 259 | 260 | NLAPI NLboolean nlIsEnabled(NLenum pname); 261 | 262 | 263 | #define NL_FUNC_SOLVER 0x600 264 | 265 | #define NL_FUNC_MATRIX 0x601 266 | 267 | #define NL_FUNC_PRECONDITIONER 0x602 268 | 269 | #define NL_FUNC_PROGRESS 0x603 270 | 271 | NLAPI void NLAPIENTRY nlSetFunction(NLenum pname, NLfunc param); 272 | 273 | NLAPI void NLAPIENTRY nlGetFunction(NLenum pname, NLfunc* param); 274 | 275 | 276 | NLAPI void NLAPIENTRY nlSetVariable(NLuint i, NLdouble value); 277 | 278 | 279 | NLAPI void NLAPIENTRY nlMultiSetVariable( 280 | NLuint i, NLuint k, NLdouble value 281 | ); 282 | 283 | NLAPI NLdouble NLAPIENTRY nlGetVariable(NLuint i); 284 | 285 | NLAPI NLdouble NLAPIENTRY nlMultiGetVariable(NLuint i, NLuint k); 286 | 287 | NLAPI void NLAPIENTRY nlLockVariable(NLuint index); 288 | 289 | NLAPI void NLAPIENTRY nlUnlockVariable(NLuint index); 290 | 291 | NLAPI NLboolean NLAPIENTRY nlVariableIsLocked(NLuint index); 292 | 293 | 294 | #define NL_SYSTEM 0x0 295 | 296 | #define NL_MATRIX 0x1 297 | 298 | #define NL_ROW 0x2 299 | 300 | #define NL_MATRIX_PATTERN 0x3 301 | 302 | NLAPI void NLAPIENTRY nlBegin(NLenum primitive); 303 | 304 | NLAPI void NLAPIENTRY nlEnd(NLenum primitive); 305 | 306 | 307 | NLAPI void NLAPIENTRY nlSetRowLength(NLuint i, NLuint n); 308 | 309 | NLAPI void NLAPIENTRY nlCoefficient(NLuint i, NLdouble value); 310 | 311 | 312 | 313 | NLAPI void NLAPIENTRY nlAddIJCoefficient( 314 | NLuint i, NLuint j, NLdouble value 315 | ); 316 | 317 | 318 | NLAPI void NLAPIENTRY nlAddIRightHandSide(NLuint i, NLdouble value); 319 | 320 | NLAPI void NLAPIENTRY nlMultiAddIRightHandSide( 321 | NLuint i, NLuint k, NLdouble value 322 | ); 323 | 324 | NLAPI void NLAPIENTRY nlRightHandSide(NLdouble value); 325 | 326 | 327 | NLAPI void NLAPIENTRY nlMultiRightHandSide(NLuint k, NLdouble value); 328 | 329 | NLAPI void NLAPIENTRY nlRowScaling(NLdouble value); 330 | 331 | 332 | NLAPI NLboolean NLAPIENTRY nlSolve(void); 333 | 334 | 335 | NLAPI void NLAPIENTRY nlUpdateRightHandSide(NLdouble* values); 336 | 337 | 338 | #define NL_VARIABLES_BUFFER 0x1000 339 | 340 | NLAPI void NLAPIENTRY nlBindBuffer( 341 | NLenum buffer, NLuint k, void* addr, NLuint stride 342 | ); 343 | 344 | 345 | 346 | #define NL_STIFFNESS_MATRIX 0x3001 347 | 348 | #define NL_MASS_MATRIX 0x3002 349 | 350 | NLAPI void NLAPIENTRY nlMatrixMode(NLenum matrix); 351 | 352 | #define NL_NB_EIGENS NL_NB_SYSTEMS 353 | 354 | #define NL_EIGEN_MAX_ITERATIONS NL_MAX_ITERATIONS 355 | 356 | #define NL_EIGEN_THRESHOLD NL_THRESHOLD 357 | 358 | #define NL_EIGEN_SOLVER 0x2000 359 | 360 | #define NL_EIGEN_SHIFT 0x2001 361 | 362 | #define NL_EIGEN_SHIFT_INVERT 0x2002 363 | 364 | NLAPI void NLAPIENTRY nlEigenSolverParameterd( 365 | NLenum pname, NLdouble val 366 | ); 367 | 368 | NLAPI void NLAPIENTRY nlEigenSolverParameteri( 369 | NLenum pname, NLint val 370 | ); 371 | 372 | NLAPI void NLAPIENTRY nlEigenSolve(void); 373 | 374 | 375 | NLAPI double NLAPIENTRY nlGetEigenValue(NLuint i); 376 | 377 | 378 | typedef int (*NLprintfFunc)(const char* format, ...); 379 | 380 | typedef int (*NLfprintfFunc)(FILE* out, const char* format, ...); 381 | 382 | NLAPI void NLAPIENTRY nlPrintfFuncs(NLprintfFunc f1, NLfprintfFunc f2); 383 | 384 | 385 | 386 | #ifdef __cplusplus 387 | } 388 | #endif 389 | 390 | 391 | 392 | 393 | #endif 394 | 395 | /******* extracted from nl_ext.h *******/ 396 | 397 | #ifndef OPENNL_EXT_H 398 | #define OPENNL_EXT_H 399 | 400 | #ifdef __cplusplus 401 | extern "C" { 402 | #endif 403 | 404 | 405 | #define NL_SUPERLU_EXT 0x210 406 | #define NL_PERM_SUPERLU_EXT 0x211 407 | #define NL_SYMMETRIC_SUPERLU_EXT 0x212 408 | #define NL_SOLVER_USER 0x213 409 | #define NL_CHOLMOD_EXT 0x214 410 | #define NL_ARPACK_EXT 0x215 411 | 412 | #define NL_CUDA_PRECOND_ILU_EXT 0x220 413 | 414 | #ifdef __cplusplus 415 | } 416 | #endif 417 | 418 | #endif 419 | 420 | 421 | /******* extracted from nl_64.h *******/ 422 | 423 | /* 424 | * Wrappers to avoid warnings when using OpenNL functions with 64 bit 425 | * indices. Note: dimension of matrix still limited to 32 bit indices 426 | * (but not NNZ). 427 | */ 428 | 429 | #ifndef NL_64_H 430 | #define NL_64_H 431 | 432 | #if defined(__cplusplus) && defined(GARGANTUA) 433 | 434 | #include 435 | #include 436 | 437 | inline NLuint nlTo32(NLulong x) { 438 | assert(x <= NLulong(std::numeric_limits::max())); 439 | return NLuint(x); 440 | } 441 | 442 | inline double nlGetVariable(NLulong i) { 443 | return nlGetVariable(nlTo32(i)); 444 | } 445 | 446 | inline void nlSetVariable(NLulong i, double a) { 447 | nlSetVariable(nlTo32(i), a); 448 | } 449 | 450 | inline void nlLockVariable(NLulong i) { 451 | nlLockVariable(nlTo32(i)); 452 | } 453 | 454 | inline NLboolean nlVariableIsLocked(NLulong index) { 455 | return nlVariableIsLocked(nlTo32(index)); 456 | } 457 | 458 | inline void nlCoefficient(NLulong i, double a) { 459 | nlCoefficient(nlTo32(i), a); 460 | } 461 | 462 | inline void nlAddIJCoefficient(NLulong i, NLulong j, double a) { 463 | nlAddIJCoefficient(nlTo32(i), nlTo32(j), a); 464 | } 465 | 466 | inline double nlGetEigenValue(int i) { 467 | return nlGetEigenValue(NLuint(i)); 468 | } 469 | 470 | inline double nlGetEigenValue(NLulong i) { 471 | return nlGetEigenValue(nlTo32(i)); 472 | } 473 | 474 | inline double nlMultiGetVariable(NLulong i, NLulong j) { 475 | return nlMultiGetVariable(nlTo32(i),nlTo32(j)); 476 | } 477 | 478 | #endif 479 | 480 | #endif 481 | 482 | -------------------------------------------------------------------------------- /lib/OpenNL_psm/README.txt: -------------------------------------------------------------------------------- 1 | This is OpenNL Pluggable Software Module, extracted 2 | from GEOGRAM source tree. It contains a standalone .cpp/.h 3 | pair that can be used in any program and that does not have 4 | any dependency. 5 | 6 | It may also contain an example program that can be compiled by using: 7 | g++ --std=c++11 OpenNL_psm.cpp OpenNL_example.cpp -o OpenNL_example 8 | (or gcc if it is plain C, as in OpenNL) 9 | 10 | Some examples may require additional compilation flags (see comments at the beginning 11 | of the example source, e.g. Delaunay_example.cpp). 12 | -------------------------------------------------------------------------------- /lib/gco/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | project(gco) 3 | 4 | include_directories(include) # TODO improve 5 | add_library(gco include/block.h include/energy.h include/GCoptimization.h include/GCoptimization.cpp include/graph.cpp include/graph.h include/LinkedBlockList.cpp include/LinkedBlockList.h include/maxflow.cpp) -------------------------------------------------------------------------------- /lib/gco/include/LinkedBlockList.cpp: -------------------------------------------------------------------------------- 1 | #include "LinkedBlockList.h" 2 | #include 3 | #include 4 | 5 | /*********************************************************************/ 6 | 7 | void LinkedBlockList::addFront(ListType item) { 8 | 9 | if ( m_head_block_size == GCLL_BLOCK_SIZE ) 10 | { 11 | LLBlock *tmp = (LLBlock *) new LLBlock; 12 | if ( !tmp ) {printf("\nOut of memory");exit(1);} 13 | tmp -> m_next = m_head; 14 | m_head = tmp; 15 | m_head_block_size = 0; 16 | } 17 | 18 | m_head ->m_item[m_head_block_size] = item; 19 | m_head_block_size++; 20 | } 21 | 22 | /*********************************************************************/ 23 | 24 | ListType LinkedBlockList::next() 25 | { 26 | ListType toReturn = m_cursor -> m_item[m_cursor_ind]; 27 | 28 | m_cursor_ind++; 29 | 30 | if ( m_cursor == m_head && m_cursor_ind >= m_head_block_size ) 31 | { 32 | m_cursor = m_cursor ->m_next; 33 | m_cursor_ind = 0; 34 | } 35 | else if ( m_cursor_ind == GCLL_BLOCK_SIZE ) 36 | { 37 | m_cursor = m_cursor ->m_next; 38 | m_cursor_ind = 0; 39 | } 40 | return(toReturn); 41 | } 42 | 43 | /*********************************************************************/ 44 | 45 | bool LinkedBlockList::hasNext() 46 | { 47 | if ( m_cursor != 0 ) return (true); 48 | else return(false); 49 | } 50 | 51 | 52 | /*********************************************************************/ 53 | 54 | LinkedBlockList::~LinkedBlockList() 55 | { 56 | LLBlock *tmp; 57 | 58 | while ( m_head != 0 ) 59 | { 60 | tmp = m_head; 61 | m_head = m_head->m_next; 62 | delete tmp; 63 | } 64 | }; 65 | 66 | /*********************************************************************/ 67 | 68 | -------------------------------------------------------------------------------- /lib/gco/include/LinkedBlockList.h: -------------------------------------------------------------------------------- 1 | /* Singly Linked List of Blocks */ 2 | // This data structure should be used only for the GCoptimization class implementation 3 | // because it lucks some important general functions for general list, like remove_item() 4 | // The head block may be not full 5 | // For regular 2D grids, it's better to set GCLL_BLOCK_SIZE to 2 6 | // For other graphs, it should be set to the average expected number of neighbors 7 | // Data in linked list for the neighborhood system is allocated in blocks of size GCLL_BLOCK_SIZE 8 | 9 | #ifndef __LINKEDBLOCKLIST_H__ 10 | #define __LINKEDBLOCKLIST_H__ 11 | 12 | #define GCLL_BLOCK_SIZE 4 13 | // GCLL_BLOCKSIZE should "fit" into the type BlockType. That is 14 | // if GCLL_BLOCKSIZE is larger than 255 but smaller than largest short integer 15 | // then BlockType should be set to short 16 | typedef char BlockType; 17 | 18 | //The type of data stored in the linked list 19 | typedef void * ListType; 20 | 21 | class LinkedBlockList{ 22 | 23 | public: 24 | void addFront(ListType item); 25 | inline bool isEmpty(){if (m_head == 0) return(true); else return(false);}; 26 | inline LinkedBlockList(){m_head = 0; m_head_block_size = GCLL_BLOCK_SIZE;}; 27 | ~LinkedBlockList(); 28 | 29 | // Next three functins are for the linked list traversal 30 | inline void setCursorFront(){m_cursor = m_head; m_cursor_ind = 0;}; 31 | ListType next(); 32 | bool hasNext(); 33 | 34 | private: 35 | typedef struct LLBlockStruct{ 36 | ListType m_item[GCLL_BLOCK_SIZE]; 37 | struct LLBlockStruct *m_next; 38 | } LLBlock; 39 | 40 | LLBlock *m_head; 41 | // Remembers the number of elements in the head block, since it may not be full 42 | BlockType m_head_block_size; 43 | // For block traversal, points to current element in the current block 44 | BlockType m_cursor_ind; 45 | // For block traversal, points to current block in the linked list 46 | LLBlock *m_cursor; 47 | }; 48 | 49 | #endif 50 | 51 | -------------------------------------------------------------------------------- /lib/gco/include/block.h: -------------------------------------------------------------------------------- 1 | /* block.h */ 2 | /* 3 | Template classes Block and DBlock 4 | Implement adding and deleting items of the same type in blocks. 5 | 6 | If there there are many items then using Block or DBlock 7 | is more efficient than using 'new' and 'delete' both in terms 8 | of memory and time since 9 | (1) On some systems there is some minimum amount of memory 10 | that 'new' can allocate (e.g., 64), so if items are 11 | small that a lot of memory is wasted. 12 | (2) 'new' and 'delete' are designed for items of varying size. 13 | If all items has the same size, then an algorithm for 14 | adding and deleting can be made more efficient. 15 | (3) All Block and DBlock functions are inline, so there are 16 | no extra function calls. 17 | 18 | Differences between Block and DBlock: 19 | (1) DBlock allows both adding and deleting items, 20 | whereas Block allows only adding items. 21 | (2) Block has an additional operation of scanning 22 | items added so far (in the order in which they were added). 23 | (3) Block allows to allocate several consecutive 24 | items at a time, whereas DBlock can add only a single item. 25 | 26 | Note that no constructors or destructors are called for items. 27 | 28 | Example usage for items of type 'MyType': 29 | 30 | /////////////////////////////////////////////////// 31 | #include "block.h" 32 | #define BLOCK_SIZE 1024 33 | typedef struct { int a, b; } MyType; 34 | MyType *ptr, *array[10000]; 35 | 36 | ... 37 | 38 | Block *block = new Block(BLOCK_SIZE); 39 | 40 | // adding items 41 | for (int i=0; i New(); 44 | ptr -> a = ptr -> b = rand(); 45 | } 46 | 47 | // reading items 48 | for (ptr=block->ScanFirst(); ptr; ptr=block->ScanNext()) 49 | { 50 | printf("%d %d\n", ptr->a, ptr->b); 51 | } 52 | 53 | delete block; 54 | 55 | ... 56 | 57 | DBlock *dblock = new DBlock(BLOCK_SIZE); 58 | 59 | // adding items 60 | for (int i=0; i New(); 63 | } 64 | 65 | // deleting items 66 | for (int i=0; i Delete(array[i]); 69 | } 70 | 71 | // adding items 72 | for (int i=0; i New(); 75 | } 76 | 77 | delete dblock; 78 | 79 | /////////////////////////////////////////////////// 80 | 81 | Note that DBlock deletes items by marking them as 82 | empty (i.e., by adding them to the list of free items), 83 | so that this memory could be used for subsequently 84 | added items. Thus, at each moment the memory allocated 85 | is determined by the maximum number of items allocated 86 | simultaneously at earlier moments. All memory is 87 | deallocated only when the destructor is called. 88 | */ 89 | 90 | #ifndef __BLOCK_H__ 91 | #define __BLOCK_H__ 92 | 93 | #include 94 | 95 | /***********************************************************************/ 96 | /***********************************************************************/ 97 | /***********************************************************************/ 98 | 99 | template class Block 100 | { 101 | public: 102 | /* Constructor. Arguments are the block size and 103 | (optionally) the pointer to the function which 104 | will be called if allocation failed; the message 105 | passed to this function is "Not enough memory!" */ 106 | Block(int size, void (*err_function)(const char *) = NULL) { first = last = NULL; block_size = size; error_function = err_function; } 107 | 108 | /* Destructor. Deallocates all items added so far */ 109 | ~Block() { while (first) { block *next = first -> next; delete first; first = next; } } 110 | 111 | /* Allocates 'num' consecutive items; returns pointer 112 | to the first item. 'num' cannot be greater than the 113 | block size since items must fit in one block */ 114 | Type *New(int num = 1) 115 | { 116 | Type *t; 117 | 118 | if (!last || last->current + num > last->last) 119 | { 120 | if (last && last->next) last = last -> next; 121 | else 122 | { 123 | block *next = (block *) new char [sizeof(block) + (block_size-1)*sizeof(Type)]; 124 | if (!next) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 125 | if (last) last -> next = next; 126 | else first = next; 127 | last = next; 128 | last -> current = & ( last -> data[0] ); 129 | last -> last = last -> current + block_size; 130 | last -> next = NULL; 131 | } 132 | } 133 | 134 | t = last -> current; 135 | last -> current += num; 136 | return t; 137 | } 138 | 139 | /* Returns the first item (or NULL, if no items were added) */ 140 | Type *ScanFirst() 141 | { 142 | for (scan_current_block=first; scan_current_block; scan_current_block = scan_current_block->next) 143 | { 144 | scan_current_data = & ( scan_current_block -> data[0] ); 145 | if (scan_current_data < scan_current_block -> current) return scan_current_data ++; 146 | } 147 | return NULL; 148 | } 149 | 150 | /* Returns the next item (or NULL, if all items have been read) 151 | Can be called only if previous ScanFirst() or ScanNext() 152 | call returned not NULL. */ 153 | Type *ScanNext() 154 | { 155 | while (scan_current_data >= scan_current_block -> current) 156 | { 157 | scan_current_block = scan_current_block -> next; 158 | if (!scan_current_block) return NULL; 159 | scan_current_data = & ( scan_current_block -> data[0] ); 160 | } 161 | return scan_current_data ++; 162 | } 163 | 164 | /* Marks all elements as empty */ 165 | void Reset() 166 | { 167 | block *b; 168 | if (!first) return; 169 | for (b=first; ; b=b->next) 170 | { 171 | b -> current = & ( b -> data[0] ); 172 | if (b == last) break; 173 | } 174 | last = first; 175 | } 176 | 177 | /***********************************************************************/ 178 | 179 | private: 180 | 181 | typedef struct block_st 182 | { 183 | Type *current, *last; 184 | struct block_st *next; 185 | Type data[1]; 186 | } block; 187 | 188 | int block_size; 189 | block *first; 190 | block *last; 191 | 192 | block *scan_current_block; 193 | Type *scan_current_data; 194 | 195 | void (*error_function)(const char *); 196 | }; 197 | 198 | /***********************************************************************/ 199 | /***********************************************************************/ 200 | /***********************************************************************/ 201 | 202 | template class DBlock 203 | { 204 | public: 205 | /* Constructor. Arguments are the block size and 206 | (optionally) the pointer to the function which 207 | will be called if allocation failed; the message 208 | passed to this function is "Not enough memory!" */ 209 | DBlock(int size, void (*err_function)(const char *) = NULL) { first = NULL; first_free = NULL; block_size = size; error_function = err_function; } 210 | 211 | /* Destructor. Deallocates all items added so far */ 212 | ~DBlock() { while (first) { block *next = first -> next; delete first; first = next; } } 213 | 214 | /* Allocates one item */ 215 | Type *New() 216 | { 217 | block_item *item; 218 | 219 | if (!first_free) 220 | { 221 | block *next = first; 222 | first = (block *) new char [sizeof(block) + (block_size-1)*sizeof(block_item)]; 223 | if (!first) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 224 | first_free = & (first -> data[0] ); 225 | for (item=first_free; item next_free = item + 1; 227 | item -> next_free = NULL; 228 | first -> next = next; 229 | } 230 | 231 | item = first_free; 232 | first_free = item -> next_free; 233 | return (Type *) item; 234 | } 235 | 236 | /* Deletes an item allocated previously */ 237 | void Delete(Type *t) 238 | { 239 | ((block_item *) t) -> next_free = first_free; 240 | first_free = (block_item *) t; 241 | } 242 | 243 | /***********************************************************************/ 244 | 245 | private: 246 | 247 | typedef union block_item_st 248 | { 249 | Type t; 250 | block_item_st *next_free; 251 | } block_item; 252 | 253 | typedef struct block_st 254 | { 255 | struct block_st *next; 256 | block_item data[1]; 257 | } block; 258 | 259 | int block_size; 260 | block *first; 261 | block_item *first_free; 262 | 263 | void (*error_function)(const char *); 264 | }; 265 | 266 | 267 | #endif 268 | 269 | -------------------------------------------------------------------------------- /lib/gco/include/energy.h: -------------------------------------------------------------------------------- 1 | /* energy.h */ 2 | /* Vladimir Kolmogorov (vnk@cs.cornell.edu), 2003. */ 3 | 4 | /* 5 | This software implements an energy minimization technique described in 6 | 7 | What Energy Functions can be Minimized via Graph Cuts? 8 | Vladimir Kolmogorov and Ramin Zabih. 9 | To appear in IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI). 10 | Earlier version appeared in European Conference on Computer Vision (ECCV), May 2002. 11 | 12 | More specifically, it computes the global minimum of a function E of binary 13 | variables x_1, ..., x_n which can be written as a sum of terms involving 14 | at most three variables at a time: 15 | 16 | E(x_1, ..., x_n) = \sum_{i} E^{i} (x_i) 17 | + \sum_{i,j} E^{i,j} (x_i, x_j) 18 | + \sum_{i,j,k} E^{i,j,k}(x_i, x_j, x_k) 19 | 20 | The method works only if each term is "regular". Definitions of regularity 21 | for terms E^{i}, E^{i,j}, E^{i,j,k} are given below as comments to functions 22 | add_term1(), add_term2(), add_term3(). 23 | 24 | This software can be used only for research purposes. IF YOU USE THIS SOFTWARE, 25 | YOU SHOULD CITE THE AFOREMENTIONED PAPER IN ANY RESULTING PUBLICATION. 26 | 27 | In order to use it, you will also need a MAXFLOW software which can be 28 | obtained from http://www.cs.cornell.edu/People/vnk/software.html 29 | 30 | 31 | Example usage 32 | (Minimizes the following function of 3 binary variables: 33 | E(x, y, z) = x - 2*y + 3*(1-z) - 4*x*y + 5*|y-z|): 34 | 35 | /////////////////////////////////////////////////// 36 | 37 | #include 38 | #include "energy.h" 39 | 40 | void main() 41 | { 42 | // Minimize the following function of 3 binary variables: 43 | // E(x, y, z) = x - 2*y + 3*(1-z) - 4*x*y + 5*|y-z| 44 | 45 | Energy::Var varx, vary, varz; 46 | Energy *e = new Energy(); 47 | 48 | varx = e -> add_variable(); 49 | vary = e -> add_variable(); 50 | varz = e -> add_variable(); 51 | 52 | e -> add_term1(varx, 0, 1); // add term x 53 | e -> add_term1(vary, 0, -2); // add term -2*y 54 | e -> add_term1(varz, 3, 0); // add term 3*(1-z) 55 | 56 | e -> add_term2(x, y, 0, 0, 0, -4); // add term -4*x*y 57 | e -> add_term2(y, z, 0, 5, 5, 0); // add term 5*|y-z| 58 | 59 | Energy::TotalValue Emin = e -> minimize(); 60 | 61 | printf("Minimum = %d\n", Emin); 62 | printf("Optimal solution:\n"); 63 | printf("x = %d\n", e->get_var(varx)); 64 | printf("y = %d\n", e->get_var(vary)); 65 | printf("z = %d\n", e->get_var(varz)); 66 | 67 | delete e; 68 | } 69 | 70 | /////////////////////////////////////////////////// 71 | */ 72 | 73 | #ifndef __ENERGY_H__ 74 | #define __ENERGY_H__ 75 | 76 | #include 77 | #include "graph.h" 78 | 79 | template class Energy: public Graph 80 | { 81 | typedef Graph GraphT; 82 | public: 83 | typedef typename GraphT::node_id Var; 84 | 85 | /* Types of energy values. 86 | Value is a type of a value in a single term 87 | TotalValue is a type of a value of the total energy. 88 | By default Value = short, TotalValue = int. 89 | To change it, change the corresponding types in graph.h */ 90 | typedef captype Value; 91 | typedef flowtype TotalValue; 92 | 93 | /* interface functions */ 94 | 95 | /* Constructor. Optional argument is the pointer to the 96 | function which will be called if an error occurs; 97 | an error message is passed to this function. If this 98 | argument is omitted, exit(1) will be called. */ 99 | Energy(int var_num_max, int edge_num_max, void (*err_function)(const char *) = NULL); 100 | 101 | /* Destructor */ 102 | ~Energy(); 103 | 104 | /* Adds a new binary variable */ 105 | Var add_variable(int num=1); 106 | 107 | /* Adds a constant E to the energy function */ 108 | void add_constant(Value E); 109 | 110 | /* Adds a new term E(x) of one binary variable 111 | to the energy function, where 112 | E(0) = E0, E(1) = E1 113 | E0 and E1 can be arbitrary */ 114 | void add_term1(Var x, 115 | Value E0, Value E1); 116 | 117 | /* Adds a new term E(x,y) of two binary variables 118 | to the energy function, where 119 | E(0,0) = E00, E(0,1) = E01 120 | E(1,0) = E10, E(1,1) = E11 121 | The term must be regular, i.e. E00 + E11 <= E01 + E10 */ 122 | void add_term2(Var x, Var y, 123 | Value E00, Value E01, 124 | Value E10, Value E11); 125 | 126 | /* Adds a new term E(x,y,z) of three binary variables 127 | to the energy function, where 128 | E(0,0,0) = E000, E(0,0,1) = E001 129 | E(0,1,0) = E010, E(0,1,1) = E011 130 | E(1,0,0) = E100, E(1,0,1) = E101 131 | E(1,1,0) = E110, E(1,1,1) = E111 132 | The term must be regular. It means that if one 133 | of the variables is fixed (for example, y=1), then 134 | the resulting function of two variables must be regular. 135 | Since there are 6 ways to fix one variable 136 | (3 variables times 2 binary values - 0 and 1), 137 | this is equivalent to 6 inequalities */ 138 | void add_term3(Var x, Var y, Var z, 139 | Value E000, Value E001, 140 | Value E010, Value E011, 141 | Value E100, Value E101, 142 | Value E110, Value E111); 143 | 144 | /* After the energy function has been constructed, 145 | call this function to minimize it. 146 | Returns the minimum of the function */ 147 | TotalValue minimize(); 148 | 149 | /* After 'minimize' has been called, this function 150 | can be used to determine the value of variable 'x' 151 | in the optimal solution. 152 | Returns either 0 or 1 */ 153 | int get_var(Var x); 154 | 155 | /***********************************************************************/ 156 | /***********************************************************************/ 157 | /***********************************************************************/ 158 | 159 | private: 160 | /* internal variables and functions */ 161 | 162 | TotalValue Econst; 163 | void (*error_function)(const char *); /* this function is called if a error occurs, 164 | with a corresponding error message 165 | (or exit(1) is called if it's NULL) */ 166 | }; 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | /***********************************************************************/ 183 | /************************ Implementation ******************************/ 184 | /***********************************************************************/ 185 | 186 | template 187 | inline Energy::Energy(int var_num_max, int edge_num_max, void (*err_function)(const char *)) : Graph(var_num_max, edge_num_max, err_function) 188 | { 189 | Econst = 0; 190 | error_function = err_function; 191 | } 192 | 193 | template 194 | inline Energy::~Energy() {} 195 | 196 | template 197 | inline typename Energy::Var Energy::add_variable(int num) 198 | { return GraphT::add_node(num); } 199 | 200 | template 201 | inline void Energy::add_constant(Value A) { Econst += A; } 202 | 203 | template 204 | inline void Energy::add_term1(Var x, 205 | Value A, Value B) 206 | { 207 | this->add_tweights(x, B, A); 208 | } 209 | 210 | template 211 | inline void Energy::add_term2(Var x, Var y, 212 | Value A, Value B, 213 | Value C, Value D) 214 | { 215 | /* 216 | E = A A + 0 B-A 217 | D D C-D 0 218 | Add edges for the first term 219 | */ 220 | this->add_tweights(x, D, A); 221 | B -= A; C -= D; 222 | 223 | /* now need to represent 224 | 0 B 225 | C 0 226 | */ 227 | 228 | assert(B + C >= 0); /* check regularity */ 229 | if (B < 0) 230 | { 231 | /* Write it as 232 | B B + -B 0 + 0 0 233 | 0 0 -B 0 B+C 0 234 | */ 235 | this->add_tweights(x, 0, B); /* first term */ 236 | this->add_tweights(y, 0, -B); /* second term */ 237 | this->add_edge(x, y, 0, B+C); /* third term */ 238 | } 239 | else if (C < 0) 240 | { 241 | /* Write it as 242 | -C -C + C 0 + 0 B+C 243 | 0 0 C 0 0 0 244 | */ 245 | this->add_tweights(x, 0, -C); /* first term */ 246 | this->add_tweights(y, 0, C); /* second term */ 247 | this->add_edge(x, y, B+C, 0); /* third term */ 248 | } 249 | else /* B >= 0, C >= 0 */ 250 | { 251 | this->add_edge(x, y, B, C); 252 | } 253 | } 254 | 255 | template 256 | inline void Energy::add_term3(Var x, Var y, Var z, 257 | Value E000, Value E001, 258 | Value E010, Value E011, 259 | Value E100, Value E101, 260 | Value E110, Value E111) 261 | { 262 | register Value pi = (E000 + E011 + E101 + E110) - (E100 + E010 + E001 + E111); 263 | register Value delta; 264 | register Var u; 265 | 266 | if (pi >= 0) 267 | { 268 | Econst += E111 - (E011 + E101 + E110); 269 | 270 | add_tweights(x, E101, E001); 271 | add_tweights(y, E110, E100); 272 | add_tweights(z, E011, E010); 273 | 274 | delta = (E010 + E001) - (E000 + E011); /* -pi(E[x=0]) */ 275 | assert(delta >= 0); /* check regularity */ 276 | add_edge(y, z, delta, 0); 277 | 278 | delta = (E100 + E001) - (E000 + E101); /* -pi(E[y=0]) */ 279 | assert(delta >= 0); /* check regularity */ 280 | add_edge(z, x, delta, 0); 281 | 282 | delta = (E100 + E010) - (E000 + E110); /* -pi(E[z=0]) */ 283 | assert(delta >= 0); /* check regularity */ 284 | add_edge(x, y, delta, 0); 285 | 286 | if (pi > 0) 287 | { 288 | u = add_variable(); 289 | add_edge(x, u, pi, 0); 290 | add_edge(y, u, pi, 0); 291 | add_edge(z, u, pi, 0); 292 | add_tweights(u, 0, pi); 293 | } 294 | } 295 | else 296 | { 297 | Econst += E000 - (E100 + E010 + E001); 298 | 299 | add_tweights(x, E110, E010); 300 | add_tweights(y, E011, E001); 301 | add_tweights(z, E101, E100); 302 | 303 | delta = (E110 + E101) - (E100 + E111); /* -pi(E[x=1]) */ 304 | assert(delta >= 0); /* check regularity */ 305 | add_edge(z, y, delta, 0); 306 | 307 | delta = (E110 + E011) - (E010 + E111); /* -pi(E[y=1]) */ 308 | assert(delta >= 0); /* check regularity */ 309 | add_edge(x, z, delta, 0); 310 | 311 | delta = (E101 + E011) - (E001 + E111); /* -pi(E[z=1]) */ 312 | assert(delta >= 0); /* check regularity */ 313 | add_edge(y, x, delta, 0); 314 | 315 | u = add_variable(); 316 | add_edge(u, x, -pi, 0); 317 | add_edge(u, y, -pi, 0); 318 | add_edge(u, z, -pi, 0); 319 | this->add_tweights(u, -pi, 0); 320 | } 321 | } 322 | 323 | template 324 | inline typename Energy::TotalValue Energy::minimize() { 325 | return Econst + GraphT::maxflow(); } 326 | 327 | template 328 | inline int Energy::get_var(Var x) { return (int) this->what_segment(x); } 329 | 330 | #endif 331 | -------------------------------------------------------------------------------- /lib/gco/include/graph.cpp: -------------------------------------------------------------------------------- 1 | /* graph.cpp */ 2 | 3 | 4 | #include 5 | #include 6 | #include 7 | #include "graph.h" 8 | 9 | 10 | template 11 | Graph::Graph(int node_num_max, int edge_num_max, void (*err_function)(const char *)) 12 | : node_num(0), 13 | nodeptr_block(NULL), 14 | error_function(err_function) 15 | { 16 | if (node_num_max < 16) node_num_max = 16; 17 | if (edge_num_max < 16) edge_num_max = 16; 18 | 19 | nodes = (node*) malloc(node_num_max*sizeof(node)); 20 | arcs = (arc*) malloc(2*edge_num_max*sizeof(arc)); 21 | if (!nodes || !arcs) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 22 | 23 | node_last = nodes; 24 | node_max = nodes + node_num_max; 25 | arc_last = arcs; 26 | arc_max = arcs + 2*edge_num_max; 27 | 28 | maxflow_iteration = 0; 29 | flow = 0; 30 | } 31 | 32 | template 33 | Graph::~Graph() 34 | { 35 | if (nodeptr_block) 36 | { 37 | delete nodeptr_block; 38 | nodeptr_block = NULL; 39 | } 40 | free(nodes); 41 | free(arcs); 42 | } 43 | 44 | template 45 | void Graph::reset() 46 | { 47 | node_last = nodes; 48 | arc_last = arcs; 49 | node_num = 0; 50 | 51 | if (nodeptr_block) 52 | { 53 | delete nodeptr_block; 54 | nodeptr_block = NULL; 55 | } 56 | 57 | maxflow_iteration = 0; 58 | flow = 0; 59 | } 60 | 61 | template 62 | void Graph::reallocate_nodes(int num) 63 | { 64 | int node_num_max = (int)(node_max - nodes); 65 | node* nodes_old = nodes; 66 | 67 | node_num_max += node_num_max / 2; 68 | if (node_num_max < node_num + num) node_num_max = node_num + num; 69 | nodes = (node*) realloc(nodes_old, node_num_max*sizeof(node)); 70 | if (!nodes) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 71 | 72 | node_last = nodes + node_num; 73 | node_max = nodes + node_num_max; 74 | 75 | if (nodes != nodes_old) 76 | { 77 | arc* a; 78 | for (a=arcs; ahead = (node*) ((char*)a->head + (((char*) nodes) - ((char*) nodes_old))); 81 | } 82 | } 83 | } 84 | 85 | template 86 | void Graph::reallocate_arcs() 87 | { 88 | int arc_num_max = (int)(arc_max - arcs); 89 | int arc_num = (int)(arc_last - arcs); 90 | arc* arcs_old = arcs; 91 | 92 | arc_num_max += arc_num_max / 2; if (arc_num_max & 1) arc_num_max ++; 93 | arcs = (arc*) realloc(arcs_old, arc_num_max*sizeof(arc)); 94 | if (!arcs) { if (error_function) (*error_function)("Not enough memory!"); exit(1); } 95 | 96 | arc_last = arcs + arc_num; 97 | arc_max = arcs + arc_num_max; 98 | 99 | if (arcs != arcs_old) 100 | { 101 | node* i; 102 | arc* a; 103 | for (i=nodes; ifirst) i->first = (arc*) ((char*)i->first + (((char*) arcs) - ((char*) arcs_old))); 106 | } 107 | for (a=arcs; anext) a->next = (arc*) ((char*)a->next + (((char*) arcs) - ((char*) arcs_old))); 110 | a->sister = (arc*) ((char*)a->sister + (((char*) arcs) - ((char*) arcs_old))); 111 | } 112 | } 113 | } 114 | 115 | -------------------------------------------------------------------------------- /scripts/obj_to_stl.py: -------------------------------------------------------------------------------- 1 | # OBJ -> STL 2 | # Use this script from Blender>Scripting 3 | # don't forget to change input path 4 | 5 | 6 | import bpy 7 | import os 8 | 9 | path = '/data/' # set this path 10 | 11 | for root, dirs, files in os.walk(path): 12 | for f in files: 13 | print(f) 14 | if f.endswith('.obj') : 15 | mesh_file = os.path.join(path, f) 16 | obj_file = os.path.splitext(mesh_file)[0] + ".stl" 17 | 18 | bpy.ops.object.select_all(action='SELECT') 19 | bpy.ops.object.delete() 20 | bpy.ops.import_scene.obj(filepath=mesh_file) # change this line 21 | bpy.ops.object.select_all(action='SELECT') 22 | bpy.ops.export_mesh.stl(filepath=obj_file) -------------------------------------------------------------------------------- /scripts/plot_SJ_table.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Goal : extract the same statistics as [HexaLab](https://www.hexalab.net/) to compare the computation of the Scaled Jacobian over the same hexahedral mesh" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "import pandas as pd\n", 17 | "import numpy as np\n", 18 | "import matplotlib.pyplot as plt\n", 19 | "import plotly.express as px" 20 | ] 21 | }, 22 | { 23 | "cell_type": "markdown", 24 | "metadata": {}, 25 | "source": [ 26 | "# Open CSV file\n", 27 | "These files are generated with `test_scaled_jacobian`. See `../app/test_scaled_jacobian.cpp` for more information" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "CSV_file = '../data/S1/SJ.csv'\n", 37 | "df = pd.read_csv(CSV_file)" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "df.head(10)" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "metadata": {}, 52 | "source": [ 53 | "# Some stats" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": {}, 60 | "outputs": [], 61 | "source": [ 62 | "print(\"Min = \",round(df['minSJ'].min(),3))\n", 63 | "print(\"Max = \",round(df['minSJ'].max(),3))\n", 64 | "print(\"Avg = \",round(df['minSJ'].mean(),3))\n", 65 | "print(\"Var = \",round(df['minSJ'].var(),3))" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "metadata": { 71 | "tags": [] 72 | }, 73 | "source": [ 74 | "# Histogram\n", 75 | "## Compute the bins" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": {}, 82 | "outputs": [], 83 | "source": [ 84 | "bins = pd.DataFrame()\n", 85 | "bins['count'], bin_edges = np.histogram(df['minSJ'], bins=100, range=(0,1))\n", 86 | "bins['value_min'] = bin_edges[:-1]\n", 87 | "bins['value_avg'] = 0.5 * (bin_edges[:-1] + bin_edges[1:])\n", 88 | "bins['value_max'] = bin_edges[1:]\n", 89 | "bins" 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "How to use the same bins as HexaLab ?
\n", 97 | "also 100 bins but weird ticks values" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "## Plot with matplotlib (static)" 105 | ] 106 | }, 107 | { 108 | "cell_type": "code", 109 | "execution_count": null, 110 | "metadata": {}, 111 | "outputs": [], 112 | "source": [ 113 | "#recomputing the histogram\n", 114 | "#df['minSJ'].plot(kind='hist',bins=100, xticks=np.arange(0,1,0.1), xlim=(1,0), figsize=(16,8))\n", 115 | "\n", 116 | "#using the bins dataframe\n", 117 | "bins_sorted = bins.sort_values('value_avg',ascending=False)#eq to flip x axis\n", 118 | "bins_sorted['value_avg'] = bins_sorted['value_avg'].round(3)\n", 119 | "ax = bins_sorted.plot.bar(x='value_avg',y='count',figsize=(16,8),rot=90)\n", 120 | "#remove some x labels\n", 121 | "for i, t in enumerate(ax.get_xticklabels()):\n", 122 | " if (i % 10) != 0:\n", 123 | " t.set_visible(False)" 124 | ] 125 | }, 126 | { 127 | "cell_type": "markdown", 128 | "metadata": {}, 129 | "source": [ 130 | "## Plot with Plotly (interactive)" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": null, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "#recomputing the histogram\n", 140 | "#fig = px.histogram(df['minSJ'], nbins=100)\n", 141 | "\n", 142 | "#using the bins dataframe\n", 143 | "fig = px.bar(x=bins['value_avg'], y=bins['count'], labels={'x':'Scaled Jacobian', 'y':'count'})\n", 144 | "fig.update_xaxes(range=[1, 0])#flip x axis\n", 145 | "fig.show()" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [] 154 | } 155 | ], 156 | "metadata": { 157 | "kernelspec": { 158 | "display_name": "Python 3", 159 | "language": "python", 160 | "name": "python3" 161 | }, 162 | "language_info": { 163 | "codemirror_mode": { 164 | "name": "ipython", 165 | "version": 3 166 | }, 167 | "file_extension": ".py", 168 | "mimetype": "text/x-python", 169 | "name": "python", 170 | "nbconvert_exporter": "python", 171 | "pygments_lexer": "ipython3", 172 | "version": "3.8.10" 173 | } 174 | }, 175 | "nbformat": 4, 176 | "nbformat_minor": 5 177 | } 178 | -------------------------------------------------------------------------------- /scripts/step_to_tet.py: -------------------------------------------------------------------------------- 1 | # from https://gitlab.com/franck.ledoux/mambo/-/blob/master/Scripts/gen_tet_mesh.py 2 | 3 | import gmsh 4 | import sys 5 | 6 | model = gmsh.model 7 | factory = model.occ 8 | 9 | def check_param(): 10 | #=========================================================================================== 11 | # Parsing of the input parameters 12 | #=========================================================================================== 13 | if(len(sys.argv)<3 or len(sys.argv)>4): 14 | print("Wrong usage, it should be:") 15 | print("\t python gen_tet_mesh.py in.step out.vtk [mesh_size]") 16 | print("where:") 17 | print("\t - in.step is the input geometry file (step format),") 18 | print("\t - out.vtk contains the generated tet mesh (vtk format),") 19 | print("\t - mesh_size is the expected element size factor in ]0,1].") 20 | exit(1) 21 | #========================================================================================== 22 | # Check step file suffix 23 | #=========================================================================================== 24 | 25 | step_file = sys.argv[1] 26 | #if(not step_file.endswith(".step") and not step_file.endswith(".stp")): 27 | #print("ERROR: the input geometry file must be at the step format (.stp or .step)") 28 | #exit(1) 29 | 30 | #=========================================================================================== 31 | # Check vtk file suffix 32 | #=========================================================================================== 33 | 34 | mesh_file = sys.argv[2] 35 | #if(not mesh_file.endswith(".vtk")): 36 | #print("ERROR: the output mesh file must be a vtk legacy file (.vtk)") 37 | #exit(1) 38 | #=========================================================================================== 39 | # Check mesh size value 40 | #=========================================================================================== 41 | mesh_size = 0.05 42 | if(len(sys.argv)==4): 43 | mesh_size = float(sys.argv[3]) 44 | if(mesh_size<=0 or mesh_size>1): 45 | print("ERROR: the mesh size must be in ]0,1]") 46 | exit(1) 47 | 48 | params = [step_file, mesh_file, mesh_size] 49 | return params 50 | 51 | def process(step_file, mesh_file, mesh_size): 52 | #====================================================================================== 53 | # Conversion process 54 | #====================================================================================== 55 | print("> Geometry import of "+step_file) 56 | model.add("step_to_tet") 57 | factory.importShapes(step_file) 58 | gmsh.option.setNumber("Mesh.CharacteristicLengthFactor", mesh_size) 59 | print("> Tet mesh generation with element size factor "+str(mesh_size)) 60 | 61 | factory.synchronize() 62 | model.mesh.generate(3) 63 | print("> Writing mesh into file "+mesh_file) 64 | gmsh.write(mesh_file) 65 | 66 | 67 | if __name__=="__main__": 68 | gmsh.initialize(sys.argv) 69 | params = check_param() 70 | process(params[0],params[1],params[2]) 71 | gmsh.finalize() 72 | -------------------------------------------------------------------------------- /scripts/stl_to_tet.py: -------------------------------------------------------------------------------- 1 | # surface STL -> Tet .mesh 2 | 3 | # https://gitlab.onelab.info/gmsh/gmsh/-/issues/1598 4 | 5 | import gmsh 6 | import sys 7 | 8 | gmsh.initialize() 9 | 10 | stl = gmsh.merge(sys.argv[1]) #3D STL file of a cylinder 11 | s = gmsh.model.getEntities(2) 12 | surf = gmsh.model.geo.addSurfaceLoop([e[1] for e in s]) 13 | 14 | gmsh.model.geo.add_volume([surf]) 15 | gmsh.model.geo.synchronize() 16 | 17 | 18 | #gmsh.option.setNumber('Mesh.Algorithm', 1) 19 | #gmsh.option.setNumber('Mesh.MeshSizeMax', 1.0) 20 | #gmsh.option.setNumber('Mesh.MeshSizeMin', 1.0) 21 | gmsh.model.mesh.generate(3) 22 | gmsh.write(sys.argv[2]) -------------------------------------------------------------------------------- /src/HexExWrapper.cpp: -------------------------------------------------------------------------------- 1 | // HexExWrapper.cpp 2 | // https://github.com/fprotais/polycube_withHexEx with Eigen instead of ultimaille 3 | 4 | #include 5 | // #include //OpenVolumeMesh::VertexHandle 6 | 7 | #include "HexExWrapper.h" 8 | 9 | //preserve face orientation 10 | //face order according to README of https://github.com/fprotais/polycube_withHexEx/blob/main/README.md 11 | const Eigen::MatrixXi TETRAHEDRON_TO_FACES = (Eigen::MatrixXi(4,3) << 1, 2, 3, 12 | 0, 3, 2, 13 | 0, 1, 3, 14 | 0, 2, 1).finished(); 15 | 16 | inline Eigen::RowVector3d geom_swap(const HexEx::Vec3d& v) { 17 | return Eigen::RowVector3d(v[0], v[1], v[2]); 18 | } 19 | 20 | inline HexEx::Vec3d geom_swap(const Eigen::RowVector3d& v) { 21 | return { v[0], v[1], v[2] }; 22 | } 23 | 24 | inline void HexEx2Eigen(const HexEx::HexahedralMesh& in, Eigen::MatrixXi& hexes_out, Eigen::MatrixXd& V_hexes_out) { 25 | 26 | //fill V_hexes_out (vertices) 27 | V_hexes_out.resize(in.n_vertices(),3); 28 | int ct = 0; 29 | for (auto it = in.vertices_begin(); it != in.vertices_end(); it++) { 30 | V_hexes_out.row(ct++) = geom_swap(in.vertex(*it)); 31 | } 32 | 33 | //fill hexes_out (cells) 34 | hexes_out.resize(in.n_cells(),8); 35 | // /!\ WARNING : MEDIT convention here (.mesh). Different from UM, OVM conventions 36 | // 5-------6 37 | // /| /| 38 | // / | / | 39 | // 1-------2 | 40 | // | 4----|--7 41 | // | / | / 42 | // |/ |/ 43 | // 0-------3 44 | constexpr int transition[8] = { 0, 3, 2, 1, 4, 5, 6, 7 }; 45 | for (auto c_it = in.cells_begin(); c_it != in.cells_end(); ++c_it) { 46 | ct = 0; 47 | for (auto cv_it = OpenVolumeMesh::HexVertexIter(*c_it, &in); cv_it.valid(); ++cv_it) { 48 | hexes_out(c_it->idx(), transition[ct++]) = cv_it->idx(); 49 | } 50 | } 51 | } 52 | 53 | bool run_HexEx(const Eigen::MatrixXi& tets, const Eigen::MatrixXd& V_tets, const Eigen::MatrixXd& corner_param, Eigen::MatrixXi& hexes, Eigen::MatrixXd& V_hexes) { 54 | 55 | //fill the tetrahedral mesh in the HexEx format 56 | HexEx::TetrahedralMesh tetMesh; 57 | std::vector corners; 58 | for (int i = 0; i < V_tets.rows(); i++) { //for each vertex of the tetrahedral mesh 59 | corners.push_back(tetMesh.add_vertex(geom_swap(V_tets.row(i)))); 60 | } 61 | 62 | //fill the parametrization in the HexEx format 63 | auto parametrization = tetMesh.request_cell_property >("Parametrization"); 64 | tetMesh.set_persistent(parametrization); 65 | for(int c = 0; c < tets.rows(); c++) { //for each tet cell 66 | //get the 4 vertices of c 67 | int T[4]; 68 | for (int cv = 0; cv < 4; cv++) { 69 | T[cv] = tets(c,cv); 70 | } 71 | auto chexex = tetMesh.add_cell(corners[T[0]], corners[T[1]], corners[T[2]], corners[T[3]]); 72 | for (int cv = 0; cv < 4; cv++) { 73 | parametrization[chexex][corners[T[cv]]] = geom_swap(corner_param.row(4 * c + cv)); 74 | } 75 | } 76 | 77 | //fill the hexahedral mesh in the HexEx format 78 | HexEx::HexahedralMesh hexMesh; 79 | std::cerr << "Running Hexex... "; 80 | HexEx::extractHexMesh(tetMesh, parametrization, hexMesh); 81 | std::cerr << "Done!" << std::endl; 82 | std::cout << "The extracted hex mesh has " << hexMesh.n_cells() << " cells, " 83 | << hexMesh.n_faces() << " faces, " << hexMesh.n_edges() << " edges, and " 84 | << hexMesh.n_vertices() << " vertices." << std::endl; 85 | 86 | HexEx2Eigen(hexMesh, hexes, V_hexes); 87 | return true; 88 | } -------------------------------------------------------------------------------- /src/chart.cpp: -------------------------------------------------------------------------------- 1 | #include "chart.h" 2 | #include 3 | #include 4 | 5 | #include "disjointset.h" 6 | #include "logging.h" 7 | 8 | int updateCharts(const Eigen::MatrixXi& TT, const Eigen::VectorXi& labeling, Eigen::VectorXi& charts){ 9 | int n_fs = TT.rows(); 10 | DisjointSet ds(n_fs); 11 | 12 | for (int f = 0; f < n_fs; f ++) { 13 | int dim = labeling(f) / 2; 14 | for (int side = 0; side < 3; side ++) { 15 | int neighbor = TT(f, side); 16 | if (labeling(neighbor) == labeling(f)) 17 | ds.merge(f, neighbor); 18 | } 19 | } 20 | 21 | std::vector idmap; 22 | int nb_charts = ds.get_sets_id(idmap); 23 | if (idmap.size() != TT.rows()) coloredPrint("Unexpected sizes in updateChart", "red"); 24 | 25 | charts.resize(idmap.size()); 26 | for (int i = 0; i < idmap.size(); i++){ // TODO optimize ? 27 | charts(i) = idmap[i]; 28 | } 29 | return nb_charts; 30 | } 31 | 32 | Eigen::VectorXi perChartLabels(const Eigen::VectorXi& charts, const Eigen::VectorXi& labeling){ 33 | Eigen::VectorXi res(charts.maxCoeff() + 1); 34 | for (int i=0; i perBorderAxis(const std::vector>& patches_per_border, 41 | const Eigen::VectorXi& per_chart_labels){ 42 | std::vector per_border_axis; 43 | for (int i=0; i> chartAdjacency(const Eigen::MatrixXi& TT, const Eigen::VectorXi& charts){ 64 | if (TT.rows() != charts.rows()) coloredPrint("Error, wrong sizes in chartAdjacency", "red"); 65 | 66 | std::vector> adj; 67 | for (int c=0; c<=charts.maxCoeff(); c++){ 68 | adj.push_back({}); 69 | } 70 | for (int i=0; i>& ordered_borders, 90 | std::vector>& patches_per_border, 91 | std::vector& border_triangles){ 92 | std::vector> edges; 93 | std::vector> patches_per_edge; 94 | 95 | border_triangles.clear(); 96 | for (int i=0; i p1, std::pair p2){ // Can be replaced with something from std? Maybe std::set? 111 | return (p1 == p2 || (p1.first == p2.second && p1.second == p2.first)); 112 | }; 113 | 114 | DisjointSet ds(edges.size()); 115 | 116 | for (int i=0; i idmap; 128 | int n_borders = ds.get_sets_id(idmap); 129 | 130 | std::vector>> boundaries; 131 | for (int border=0; border occ; 148 | for (std::pair p: boundaries[border]){ 149 | if (occ.count(p.first) == 0) occ[p.first] = 1; 150 | else occ[p.first] = occ[p.first] + 1; 151 | if (occ.count(p.second) == 0) occ[p.second] = 1; 152 | else occ[p.second] = occ[p.second] + 1; 153 | 154 | } 155 | 156 | // find starting vertex 157 | int start = -1; 158 | for (auto it = occ.begin(); it != occ.end(); it++){ 159 | if (it->second == 1){ 160 | start = it->first; 161 | break; 162 | } 163 | } 164 | if (start == -1){ // it's a loop, start anywhere 165 | start = boundaries[border][0].first; 166 | } 167 | }*/ 168 | 169 | // --- From flagging.cpp in hexercise: --- 170 | ordered_borders.clear(); 171 | for (int border=0; border> vec = boundaries[border]; 173 | // find endings: they're only on one edge of this boundary 174 | std::set singles; 175 | for (int i=0; i v_ids = {}; 187 | int last_elem; 188 | if (singles.size() == 2){ 189 | for (int i: singles){ 190 | if (v_ids.empty()) v_ids.push_back(i); 191 | else last_elem = i; 192 | } 193 | } 194 | else if (singles.size() == 0){ 195 | // it's a loop: start and end with same id 196 | v_ids.push_back(vec[0].first); 197 | v_ids.push_back(vec[0].second); 198 | last_elem = vec[0].first; 199 | vec.erase(vec.begin()); 200 | } else { 201 | std::cout << "singles.size() = " << singles.size() << std::endl; 202 | for (int elem: singles) std::cout << elem << " "; 203 | std::cout << std::endl; 204 | std::cout << "Error identifying boundaries: Bad mesh/flagging ?" << std::endl; 205 | 206 | // TODO find out why following snippet is needed... 207 | for (int i: singles){ 208 | if (v_ids.empty()) v_ids.push_back(i); 209 | else last_elem = i; 210 | } 211 | } 212 | 213 | // Go through consecutive edges until end is reached 214 | int last_added = v_ids[v_ids.size()-1]; 215 | while (last_added != last_elem){ 216 | bool added = false; 217 | for (int i=0; i pair = vec[i]; 219 | if (pair.first == last_added){ 220 | v_ids.push_back(pair.second); 221 | last_added = pair.second; 222 | vec.erase(vec.begin()+i); 223 | added = true; 224 | break; 225 | } 226 | if (pair.second == last_added){ 227 | v_ids.push_back(pair.first); 228 | last_added = pair.first; 229 | vec.erase(vec.begin()+i); 230 | added = true; 231 | break; 232 | } 233 | } 234 | if (!added || v_ids.size() > 3 * F.rows()) { 235 | coloredPrint("Failed to identify border. Non-manifold?", "red"); 236 | break; 237 | } 238 | } 239 | 240 | ordered_borders.push_back(v_ids); 241 | } 242 | } -------------------------------------------------------------------------------- /src/distortion.cpp: -------------------------------------------------------------------------------- 1 | #include "distortion.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "logging.h" 7 | 8 | Eigen::RowVector3d crossProd(const Eigen::RowVector3d& v1, 9 | const Eigen::RowVector3d& v2){ 10 | return v1.transpose().cross(v2.transpose()).transpose(); 11 | } 12 | 13 | void localTransfo(const Eigen::MatrixXd& V, 14 | const Eigen::MatrixXi& F, 15 | const Eigen::MatrixXd& N, 16 | std::vector& vecA){ 17 | 18 | vecA.resize(F.rows()); 19 | 20 | #pragma omp parallel for 21 | for (int f_id = 0; f_id < F.rows(); f_id ++){ 22 | Eigen::RowVector3d local_axis1 = V.row(F(f_id, 1)) - V.row(F(f_id, 0)); 23 | local_axis1.normalize(); 24 | Eigen::RowVector3d local_axis2 = crossProd(N.row(f_id), local_axis1); //N.row(f_id).cross(local_axis1); 25 | 26 | Eigen::RowVector2d local_coords1 = Eigen::RowVector2d((V.row(F(f_id, 1)) - V.row(F(f_id, 0))).norm(), 0); 27 | Eigen::RowVector2d local_coords2; 28 | local_coords2(0) = (V.row(F(f_id, 2)) - V.row(F(f_id, 0))).dot(local_axis1); 29 | local_coords2(1) = (V.row(F(f_id, 2)) - V.row(F(f_id, 0))).dot(local_axis2); 30 | 31 | Eigen::Matrix2d A; 32 | A.col(0) = local_coords1; 33 | A.col(1) = local_coords2; 34 | vecA[f_id] = A; 35 | } 36 | } 37 | 38 | void computeJacobians(const Eigen::MatrixXd& V1, 39 | const Eigen::MatrixXd& V2, 40 | const Eigen::MatrixXi& F, 41 | const Eigen::MatrixXd& N, 42 | const Eigen::MatrixXd& N_def, 43 | std::vector& jacobians){ 44 | 45 | std::vector vecA1, vecA2; 46 | localTransfo(V1, F, N, vecA1); 47 | localTransfo(V2, F, N_def, vecA2); 48 | 49 | jacobians.resize(F.rows()); 50 | 51 | #pragma omp parallel for 52 | for (int f_id = 0; f_id < F.rows(); f_id++){ 53 | jacobians[f_id] = vecA2[f_id] * vecA1[f_id].inverse(); 54 | } 55 | } 56 | 57 | void computeDisto(const Eigen::MatrixXd& V1, 58 | const Eigen::MatrixXd& V2, 59 | const Eigen::MatrixXi& F, 60 | const Eigen::MatrixXd& N, 61 | const Eigen::MatrixXd& N_def, 62 | Eigen::VectorXd& disto){ 63 | disto.resize(F.rows()); 64 | 65 | std::vector jacobians; 66 | computeJacobians(V1, V2, F, N, N_def, jacobians); 67 | 68 | #pragma omp parallel for 69 | for (int f_id = 0; f_id < F.rows(); f_id++){ 70 | Eigen::JacobiSVD svd(jacobians[f_id]); 71 | double s1 = svd.singularValues()(0); 72 | double s2 = svd.singularValues()(1); 73 | disto(f_id) = s1 * s2 + 1.0 / (s1 * s2) - 2.0; // area 74 | disto(f_id) += s1 / s2 + s2 / s1 - 2.0; // angle 75 | } 76 | } 77 | 78 | double integrateDistortion(const Eigen::VectorXd& A, 79 | const Eigen::VectorXd& disto){ 80 | 81 | if (disto.minCoeff() < 0) coloredPrint("ERROR: distortion is < 0", "red"); 82 | 83 | /*std::cout << "disto range" << std::endl; 84 | std::cout << disto.minCoeff() << std::endl; 85 | std::cout << disto.maxCoeff() << std::endl;*/ 86 | 87 | int max_disto = 10e8; 88 | 89 | Eigen::VectorXd distop = disto.array().min(static_cast(max_disto)); 90 | 91 | return (A.array() * distop.array()).sum() / A.sum(); //+ static_cast(n_inf * max_disto); 92 | 93 | } 94 | 95 | // Spherical Parametrization and Remeshing 96 | // Praun & Hoppe 97 | double computeStretch(const Eigen::VectorXd& A, double A_m, double A_d, 98 | std::vector> per_tri_singular_values){ 99 | 100 | double sum = 0; 101 | for (int f_id = 0; f_id < per_tri_singular_values.size(); f_id++){ 102 | double s1 = per_tri_singular_values[f_id].first; 103 | double s2 = per_tri_singular_values[f_id].second; 104 | sum += A(f_id) * (s1 * s1 + s2 * s2) / 2.0; 105 | } 106 | sum /= A.sum(); 107 | 108 | return (A_m / A_d) * (1.0 / std::pow(sum, 2)); 109 | } 110 | 111 | // PolyCube-Maps 112 | // Tarini & Hormann & Cignoni & Montani 113 | double computeAreaDisto(const Eigen::VectorXd& A, std::vector> per_tri_singular_values){ 114 | double sum = 0; 115 | for (int f_id = 0; f_id < per_tri_singular_values.size(); f_id++){ 116 | double s1 = per_tri_singular_values[f_id].first; 117 | double s2 = per_tri_singular_values[f_id].second; 118 | sum += A(f_id) * 0.5 * (s1 * s2 + 1.0 / (s1 * s2)); 119 | } 120 | 121 | return sum / A.sum(); 122 | } 123 | 124 | double computeAngleDisto(const Eigen::VectorXd& A, std::vector> per_tri_singular_values){ 125 | double sum = 0; 126 | for (int f_id = 0; f_id < per_tri_singular_values.size(); f_id++){ 127 | double s1 = per_tri_singular_values[f_id].first; 128 | double s2 = per_tri_singular_values[f_id].second; 129 | sum += A(f_id) * 0.5 * (s1 / s2 + s2 / s1); 130 | } 131 | 132 | return sum / A.sum(); 133 | } 134 | 135 | // Computing Surface PolyCube-Maps by Constrained Voxelization 136 | // Yang, Fu, Liu 137 | double computeIsometricDisto(const Eigen::VectorXd& A, std::vector> per_tri_singular_values){ 138 | double sum = 0; 139 | for (int f_id = 0; f_id < per_tri_singular_values.size(); f_id++){ 140 | double s1 = per_tri_singular_values[f_id].first; 141 | double s2 = per_tri_singular_values[f_id].second; 142 | sum += std::max(s1, 1.0 / s2) * A(f_id); 143 | } 144 | return sum / A.sum(); 145 | } -------------------------------------------------------------------------------- /src/flagging_utils.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "flagging_utils.h" 3 | #include 4 | 5 | Eigen::MatrixXd axesMatrix(){ 6 | Eigen::MatrixXd axes = Eigen::MatrixXd::Zero(6,3); 7 | for (int axis=0; axis<3; axis++){ 8 | for (int dir=0; dir<2; dir++){ 9 | axes(2*axis + dir, axis) = 1 - 2*dir; 10 | } 11 | } 12 | return axes; 13 | } 14 | 15 | int oppositeLabel(int label){ 16 | if (label % 2) return label -1; 17 | return label +1; 18 | } 19 | 20 | Eigen::VectorXi openFlagging(std::string file_name, int expected_size){ 21 | Eigen::VectorXi assignment(expected_size); 22 | if (expected_size == 0) return assignment; 23 | std::ifstream input_file(file_name); 24 | if (!input_file.is_open()) { 25 | coloredPrint("Could not open assignment file: " + file_name, "red"); 26 | assignment = Eigen::VectorXi::Zero(expected_size); 27 | } 28 | else { 29 | int number; 30 | int n=0; 31 | while (input_file >> number) { 32 | assignment(n) = number; 33 | n++; 34 | } 35 | if (n != expected_size){ 36 | std::cout << "Error reading assignment" << std::endl; 37 | assignment = Eigen::VectorXi::Zero(expected_size); 38 | return assignment; 39 | } 40 | } 41 | 42 | return assignment; 43 | } 44 | 45 | Eigen::MatrixXd colorsFromFlagging(const Eigen::VectorXi& flagging){ 46 | if (flagging.rows() == 0){ 47 | std::cout << "WARNING: computing colors from empty flagging" << std::endl; 48 | } 49 | Eigen::MatrixXd flagging_colors(flagging.rows(), 3); 50 | 51 | Eigen::MatrixXd color_map = Eigen::MatrixXd::Zero(7,3); 52 | for (int axis=0; axis<3; axis++){ 53 | for (int dir=0; dir<2; dir++){ 54 | color_map(2*axis + dir, axis) = 1 - 0.4*dir; 55 | } 56 | } 57 | 58 | // replace green with white 59 | color_map.row(2) = Eigen::RowVector3d(1.0, 1.0, 1.0); 60 | color_map.row(3) = Eigen::RowVector3d(0.7, 0.7, 0.7); 61 | 62 | 63 | double diff = 40.0; 64 | color_map.row(0) = Eigen::RowVector3d(196.0/255, 32.0/255, 33.0/255); 65 | //color_map.row(1) = Eigen::RowVector3d(166.0/255, 15.0/255, 16.0/255); 66 | color_map.row(2) = Eigen::RowVector3d(235.0/255, 245.0/255, 238.0/255); 67 | color_map.row(2) = Eigen::RowVector3d(235.0/255, 235.0/255, 235.0/255); 68 | //color_map.row(3) = Eigen::RowVector3d(185.0/255, 195.0/255, 198.0/255); 69 | color_map.row(4) = Eigen::RowVector3d(5.0/255, 74.0/255, 145.0/255); 70 | color_map.row(4) = Eigen::RowVector3d(5.0/255, 90.0/255, 195.0/255); 71 | //color_map.row(5) = Eigen::RowVector3d(0.0/255, 44.0/255, 115.0/255); 72 | 73 | // German theme 74 | //color_map.row(0) = Eigen::RowVector3d(0.0/255, 0.0/255, 0.0/255); 75 | //color_map.row(2) = Eigen::RowVector3d(221.0/255, 0.0/255, 0.0/255); 76 | //color_map.row(4) = Eigen::RowVector3d(255.0/255, 206.0/255, 0.0/255); 77 | 78 | 79 | // updated theme 80 | /* 81 | color_map.row(0) = Eigen::RowVector3d(255.0/255, 15.0/255, 60.0/255); 82 | color_map.row(2) = Eigen::RowVector3d(245.0/255, 245.0/255, 245.0/255); 83 | color_map.row(4) = Eigen::RowVector3d(0.0/255, 130.0/255, 255.0/255); 84 | //*/ 85 | 86 | color_map.row(1) = color_map.row(0).array() - 0.9 * diff/255.0; 87 | color_map.row(3) = color_map.row(2).array() - 0.6 * diff/255.0; 88 | color_map.row(5) = color_map.row(4).array() - 0.7 * diff/255.0; 89 | 90 | color_map = color_map.array() * 1.1; 91 | 92 | for (int i=0; i mini){ 148 | best = ax; 149 | mini = cross_p.dot(axes.row(ax)); 150 | } 151 | } 152 | flagging(i) = best; 153 | } 154 | return flagging; 155 | } 156 | 157 | double flaggingSimilarity(const Eigen::VectorXi& labeling1, const Eigen::VectorXi& labeling2) { 158 | if(labeling1.rows()!=labeling2.rows()) { 159 | return -1.0; 160 | } 161 | int same_label_counter = 0; 162 | for(int face_number = 0; face_number < labeling1.rows(); face_number++) { 163 | if(labeling1(face_number) == labeling2(face_number)) { 164 | same_label_counter++; 165 | } 166 | } 167 | return ((double) same_label_counter) / labeling1.rows(); 168 | } -------------------------------------------------------------------------------- /src/graphcut_labeling.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "graphcut_labeling.h" 3 | #include 4 | #include 5 | 6 | #include "logging.h" 7 | #include "flagging_utils.h" 8 | 9 | Eigen::VectorXi graphcutFlagging(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 10 | const Eigen::MatrixXd& N, const Eigen::MatrixXi& TT, 11 | const Eigen::VectorXi& locked_flags, const Eigen::VectorXi& forbidden_flags, 12 | int compact_coeff, int fidelity_coeff){ 13 | 14 | // See example.cpp in gco-v3.0 15 | // function called "GridGraph_DArraySArraySpatVarying" 16 | 17 | // Note: it would probably be worth looking for a more recent library 18 | 19 | std::chrono::steady_clock::time_point before_precomp = std::chrono::steady_clock::now(); 20 | 21 | Eigen::MatrixXd axes = axesMatrix(); 22 | 23 | Eigen::VectorXi result(F.rows()); 24 | int num_elem = F.rows(); 25 | int num_labels = 6; 26 | // first set up the array for data costs 27 | int *data = new int[num_elem*num_labels]; 28 | for ( int i = 0; i < num_elem; i++ ){ 29 | Eigen::RowVector3d nt = N.row(i); 30 | for (int l = 0; l < num_labels; l++ ){ 31 | double dot = (nt.dot(axes.row(l)) - 1.0)/0.2; 32 | double cost = 1 - std::exp(-(1./2.)*std::pow(dot,2)); 33 | data[i*num_labels+l] = (int) (fidelity_coeff*100*cost); 34 | } 35 | } 36 | 37 | //Use data costs to enforce locked flags 38 | if (locked_flags.rows() > 0 && locked_flags.maxCoeff() > -1){ 39 | if (locked_flags.minCoeff() == locked_flags.maxCoeff()){ 40 | coloredPrint("Warning in Graph-cut flagging: locked on a single color.", "yellow"); 41 | } 42 | //std::cout << "Locked percentage: " << 100*((locked_flags.array() != -1).count())/F.rows() << std::endl; 43 | for (int i = 0; i < num_elem; i++){ 44 | if (locked_flags(i) == -1) continue; 45 | for (int l = 0; l < num_labels; l++ ){ 46 | if (l!=locked_flags(i)){ 47 | data[i*num_labels+l] = 10e4; 48 | } 49 | else { 50 | data[i*num_labels+l] = 0; 51 | } 52 | } 53 | } 54 | } 55 | 56 | //Use data costs to enforce forbidden flags 57 | if (forbidden_flags.rows() > 0 && forbidden_flags.maxCoeff() > -1){ 58 | if (forbidden_flags.minCoeff() == forbidden_flags.maxCoeff()){ 59 | coloredPrint("Warning in Graph-cut flagging: single color forbidden.", "yellow"); 60 | } 61 | 62 | for (int i = 0; i < num_elem; i++){ 63 | if (forbidden_flags(i) == -1) continue; 64 | for (int l = 0; l < num_labels; l++ ){ 65 | if (l==forbidden_flags(i)){ 66 | data[i*num_labels+l] = 10e4; 67 | } 68 | } 69 | } 70 | } 71 | 72 | // next set up the array for smooth costs 73 | // 1 if neighbors, 0 otherwise. Will be 74 | // multiplied by neighbor coefficient provided in 75 | // setNeighbors call. 76 | 77 | bool prevent_opposite_neighbors = false; 78 | 79 | int *smooth = new int[num_labels*num_labels]; 80 | for ( int l1 = 0; l1 < num_labels; l1++ ){ 81 | for (int l2 = 0; l2 < num_labels; l2++ ){ 82 | if (l1==l2) smooth[l1+l2*num_labels] = 0; 83 | else if (axes.row(l1) == - axes.row(l2)) { 84 | if (prevent_opposite_neighbors) 85 | smooth[l1+l2*num_labels] = 10000; 86 | else smooth[l1+l2*num_labels] = 1; 87 | } 88 | else smooth[l1+l2*num_labels] = 1; 89 | } 90 | } 91 | 92 | 93 | try{ 94 | GCoptimizationGeneralGraph *gc = new GCoptimizationGeneralGraph(num_elem,num_labels); 95 | gc->setDataCost(data); 96 | gc->setSmoothCost(smooth); 97 | 98 | for (int i=0; isetNeighbors(i, TT(i,j), (int) (compact_coeff*100*cost)); 106 | } 107 | } 108 | 109 | 110 | std::chrono::steady_clock::time_point after_precomp = std::chrono::steady_clock::now(); 111 | //precompute_time = std::chrono::duration_cast(after_precomp - before_precomp).count(); 112 | 113 | //printf("\nBefore optimization energy is %d",gc->compute_energy()); 114 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 115 | //printf("\nAfter optimization energy is %d\n",gc->compute_energy()); 116 | 117 | for ( int i = 0; i < num_elem; i++ ) 118 | result[i] = gc->whatLabel(i); 119 | 120 | delete gc; 121 | } 122 | catch (GCException e){ 123 | e.Report(); 124 | } 125 | 126 | delete [] smooth; 127 | delete [] data; 128 | 129 | return result; 130 | } 131 | 132 | Eigen::VectorXi graphcutFlagging(const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 133 | const Eigen::MatrixXd& N, const Eigen::MatrixXi& TT, 134 | int compact_coeff, int fidelity_coeff){ 135 | //int precompute_time; 136 | Eigen::VectorXi locked_flags, forbidden_flags; 137 | 138 | return graphcutFlagging(V, F, N, TT, locked_flags, forbidden_flags, 139 | compact_coeff, fidelity_coeff); 140 | } 141 | 142 | std::vector graphcutTurningPoints(const std::vector& bnd, const Eigen::MatrixXd& V, 143 | const Eigen::RowVector3d& desired_dir){ 144 | Eigen::VectorXi result(bnd.size() - 1); //first vertex can't be a turning point 145 | int num_elem = bnd.size() - 1; 146 | int num_labels = 2; 147 | 148 | if(num_elem==1) { 149 | //boundary of 1 edge only. no turning-point. 150 | std::vector empty; 151 | return empty; 152 | } 153 | 154 | // unary costs 155 | int *data = new int[num_elem*num_labels]; 156 | Eigen::RowVector3d dir = desired_dir; 157 | 158 | for (int i = 0; i < num_elem; i++ ){ 159 | Eigen::RowVector3d edge = (V.row(bnd[i+1]) - V.row(bnd[i])).normalized(); 160 | 161 | data[i*num_labels+0] = 0; 162 | data[i*num_labels+1] = 0; 163 | double dot = (dir.dot(edge))/0.9; 164 | double cost = 1.0 - std::exp(-(1./2.)*std::pow(dot,2)); 165 | if (dir.dot(edge) > 0){ 166 | data[i*num_labels + 0] = (int) (100.0 * cost); 167 | } 168 | else { 169 | data[i*num_labels + 1] = (int) (100.0 * cost); 170 | } 171 | 172 | } 173 | 174 | // binary cost coefficients 175 | int *smooth = new int[num_labels*num_labels]; 176 | for ( int l1 = 0; l1 < num_labels; l1++ ){ 177 | for (int l2 = 0; l2 < num_labels; l2++ ){ 178 | if (l1==l2) smooth[l1+l2*num_labels] = 0; 179 | else smooth[l1+l2*num_labels] = 1; 180 | } 181 | } 182 | 183 | double falloff_binary = 1.0; // smaller value -> more turning points 184 | 185 | try{ 186 | GCoptimizationGeneralGraph *gc = new GCoptimizationGeneralGraph(num_elem,num_labels); 187 | gc->setDataCost(data); 188 | gc->setSmoothCost(smooth); 189 | 190 | // binary costs 191 | for (int i=0; isetNeighbors(i, i+1, (int) (100.0*cost)); 199 | } 200 | 201 | gc->expansion(2);// run expansion for 2 iterations. For swap use gc->swap(num_iterations); 202 | 203 | for ( int i = 0; i < num_elem; i++ ) 204 | result[i] = gc->whatLabel(i); 205 | 206 | delete gc; 207 | } 208 | catch (GCException e){ 209 | e.Report(); 210 | } 211 | 212 | delete [] smooth; 213 | delete [] data; 214 | 215 | std::vector turning_points; 216 | for (int i=1; i 4 | #include 5 | #include 6 | #include 7 | 8 | void coloredPrint(std::string text, std::string color){ 9 | std::cout << "\033[1;"; 10 | if (color == "black") 11 | std::cout << "30m"; 12 | else if (color == "red") 13 | std::cout << "31m"; 14 | else if (color == "green") 15 | std::cout << "32m"; 16 | else if (color == "yellow") 17 | std::cout << "33m"; 18 | else if (color == "blue") 19 | std::cout << "34m"; 20 | else if (color == "magenta") 21 | std::cout << "35m"; 22 | else if (color == "cyan") 23 | std::cout << "36m"; 24 | else std::cout << "37m"; 25 | 26 | std::cout << text << "\033[0m" << std::endl; 27 | } 28 | 29 | nlohmann::json readJSON(std::string filepath){ 30 | std::ifstream i(filepath); 31 | nlohmann::json j; 32 | if (i.good()) 33 | i >> j; 34 | i.close(); 35 | return j; 36 | } 37 | 38 | void writeJSON(nlohmann::json j, std::string filepath){ 39 | std::ofstream o(filepath); 40 | o << std::setw(4) << j << std::endl; 41 | o.close(); 42 | } 43 | 44 | void resetLogFile(std::string filepath){ 45 | writeJSON(nlohmann::json(), filepath); 46 | } 47 | 48 | std::string readLogsValue(std::string tag1, std::string tag2, std::string filepath){ 49 | nlohmann::json j = readJSON(filepath); 50 | if (j.contains(tag1) && j[tag1].contains(tag2)) 51 | return j[tag1][tag2]; 52 | return "null"; 53 | } 54 | 55 | void fillLogInfo(std::string tag, std::string filepath, std::string value){ 56 | nlohmann::json j = readJSON(filepath); 57 | j[tag] = value; 58 | writeJSON(j, filepath); 59 | } 60 | 61 | void fillLogInfo(std::string tag1, std::string tag2, std::string filepath, std::string value){ 62 | nlohmann::json j = readJSON(filepath); 63 | j[tag1][tag2] = value; 64 | writeJSON(j, filepath); 65 | } 66 | 67 | void fillLogInfo(std::string tag1, std::string tag2, std::string filepath, double value){ 68 | fillLogInfo(tag1, tag2, filepath, std::to_string(value)); 69 | } 70 | 71 | void fillLogInfo(std::string tag1, std::string tag2, std::string filepath, int value){ 72 | fillLogInfo(tag1, tag2, filepath, std::to_string(value)); 73 | } 74 | 75 | void removeLogInfo(std::string tag, std::string filepath){ 76 | nlohmann::json j = readJSON(filepath); 77 | j.erase(tag); 78 | writeJSON(j, filepath); 79 | } 80 | 81 | std::string readLogInfo(std::string tag, std::string filepath){ 82 | nlohmann::json j = readJSON(filepath); 83 | return j[tag]; 84 | } 85 | 86 | std::map readAllLogInfo(std::string filepath){ 87 | std::map result; 88 | nlohmann::json j = readJSON(filepath); 89 | for(auto it: j.items()){ 90 | result[it.key()] = it.value(); 91 | } 92 | return result; 93 | } 94 | 95 | std::vector getLogTags(std::string filepath){ 96 | std::vector result; 97 | nlohmann::json j = readJSON(filepath); 98 | for(auto it: j.items()){ 99 | result.push_back(it.key()); 100 | } 101 | return result; 102 | } 103 | 104 | void printLog(std::string filepath){ 105 | nlohmann::json j = readJSON(filepath); 106 | for(auto it: j.items()){ 107 | std::cout << it.key() << "\t| " << it.value() << '\n'; 108 | } 109 | } 110 | 111 | 112 | double measureTime(std::chrono::time_point t1, 113 | std::chrono::time_point t2){ 114 | return static_cast(std::chrono::duration_cast 115 | (t2 - t1).count()) / 1000000.0; 116 | } -------------------------------------------------------------------------------- /src/quick_label_ev.cpp: -------------------------------------------------------------------------------- 1 | #include "quick_label_ev.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "disjointset.h" 12 | #include "distortion.h" 13 | #include "flagging_utils.h" 14 | 15 | //#define DEBUG_QUICK_LABEL_EV 16 | #ifdef DEBUG_QUICK_LABEL_EV 17 | #include 18 | #endif 19 | 20 | #define FOR(i, n) for(int i = 0; i < n; i++) 21 | 22 | Eigen::MatrixXd QuickLabelEv::LDLTDeformation(const Eigen::VectorXi& labeling) const { 23 | int N_vs = V_.rows(); 24 | int N_fs = labeling.rows(); 25 | auto begin = std::chrono::steady_clock::now(); 26 | 27 | DisjointSet ds(3 * N_vs); 28 | FOR(f, N_fs) { 29 | int dim = labeling(f) / 2; 30 | FOR(fv, 3) ds.merge(dim * N_vs + F_(f, fv), dim * N_vs + F_(f, (fv + 1) % 3)); 31 | } 32 | 33 | std::vector idmap; 34 | int nb_variables = ds.get_sets_id(idmap); 35 | 36 | Eigen::SparseMatrix A; 37 | Eigen::VectorXd b; 38 | Eigen::VectorXd x; 39 | 40 | { // makeSparseMatrix 41 | int expected_eqs = 3 * 2 * N_fs; 42 | int n_coords = nb_variables; // TODO 43 | A.resize(expected_eqs, n_coords); 44 | x.resize(n_coords); 45 | b.resize(expected_eqs); 46 | 47 | std::vector> triplet_list; 48 | triplet_list.resize(2 * expected_eqs); 49 | int next_equation_id = 0; 50 | 51 | FOR(f, N_fs) { 52 | int dim = labeling(f) / 2; 53 | FOR(d, 3) { 54 | if (dim == d) continue; 55 | FOR(fv, 3) { 56 | triplet_list[2 * next_equation_id] = Eigen::Triplet(next_equation_id, idmap[d * N_vs + F_(f, fv)], 1.0); 57 | triplet_list[2 * next_equation_id + 1] = Eigen::Triplet(next_equation_id, idmap[d * N_vs + F_(f, (fv + 1) % 3)], -1.0); 58 | b(next_equation_id) = V_(F_(f, fv), d) - V_(F_(f, (fv + 1) % 3), d); 59 | next_equation_id ++; 60 | } 61 | } 62 | } 63 | A.setFromTriplets(triplet_list.begin(), triplet_list.end()); 64 | } 65 | 66 | auto buildend = std::chrono::steady_clock::now(); 67 | 68 | Eigen::SimplicialLDLT> solver; 69 | //Eigen::ConjugateGradient, Eigen::Lower|Eigen::Upper> solver; 70 | /*Eigen::BiCGSTAB> solver; 71 | solver.setMaxIterations(20); 72 | solver.setTolerance(0.001);*/ 73 | solver.compute(A.transpose() * A); 74 | if(solver.info() != Eigen::Success) { 75 | std::cout << "ERROR: decomposition failed" << std::endl; 76 | } 77 | 78 | x = solver.solve(A.transpose() * b); 79 | if(solver.info() != Eigen::Success) { 80 | std::cout << "ERROR, solving failed: "; 81 | if(solver.info() == Eigen::NumericalIssue) 82 | std::cout << "NumericalIssue" << std::endl; 83 | if(solver.info() == Eigen::NoConvergence) 84 | std::cout << "NoConvergence" << std::endl; 85 | if(solver.info() == Eigen::InvalidInput) 86 | std::cout << "InvalidInput" << std::endl; 87 | //x = Eigen::VectorXd::Constant(x.rows(), 10e6); 88 | } 89 | 90 | Eigen::MatrixXd def_V(N_vs, 3); 91 | FOR(v, N_vs) FOR(d, 3) def_V(v, d) = x(idmap[d * N_vs + v]); 92 | 93 | auto solveend = std::chrono::steady_clock::now(); 94 | 95 | double totaltime = double(std::chrono::duration_cast (solveend - begin).count()) / 1000; 96 | double buildtime = double(std::chrono::duration_cast (buildend - begin).count()) / 1000; 97 | double solvetime = double(std::chrono::duration_cast (solveend - buildend).count()) / 1000; 98 | //std::cerr << "LDLT build + solve: " << buildtime << " + " << solvetime << " = " << totaltime << "sec." << std::endl; 99 | 100 | #ifdef DEBUG_QUICK_LABEL_EV 101 | //igl::writeOBJ("../debug_quick_label_ev.obj", def_V, F_); 102 | std::cout << "hardDeformation done" << std::endl; 103 | #endif 104 | 105 | return def_V; 106 | } 107 | 108 | Eigen::MatrixXd QuickLabelEv::hardDeformation(const Eigen::VectorXi& labeling) const { 109 | int N_vs = V_.rows(); 110 | int N_fs = labeling.rows(); 111 | auto begin = std::chrono::steady_clock::now(); 112 | auto context = nlNewContext(); 113 | 114 | DisjointSet ds(3 * N_vs); 115 | FOR(f, N_fs) { 116 | int dim = labeling(f) / 2; 117 | FOR(fv, 3) ds.merge(dim * N_vs + F_(f, fv), dim * N_vs + F_(f, (fv + 1) % 3)); 118 | } 119 | 120 | std::vector idmap; 121 | int nb_variables = ds.get_sets_id(idmap); 122 | 123 | nlSolverParameteri(NL_LEAST_SQUARES, NL_TRUE); 124 | nlSolverParameteri(NL_NB_VARIABLES, NLint(nb_variables)); 125 | //nlEnable(NL_VERBOSE); 126 | nlBegin(NL_SYSTEM); 127 | 128 | nlBegin(NL_MATRIX); 129 | FOR(f, N_fs) { 130 | int dim = labeling(f) / 2; 131 | FOR(d, 3) { 132 | if (dim == d) continue; 133 | FOR(fv, 3) { 134 | nlRowScaling(A_(f)); 135 | nlBegin(NL_ROW); 136 | nlCoefficient(idmap[d * N_vs + F_(f, fv)], 1); 137 | nlCoefficient(idmap[d * N_vs + F_(f, (fv + 1) % 3)], -1); 138 | nlRightHandSide(V_(F_(f, fv), d) - V_(F_(f, (fv + 1) % 3), d)); 139 | nlEnd(NL_ROW); 140 | } 141 | } 142 | } 143 | nlEnd(NL_MATRIX); 144 | nlEnd(NL_SYSTEM); 145 | 146 | auto buildend = std::chrono::steady_clock::now(); 147 | 148 | nlSolve(); 149 | Eigen::MatrixXd def_V(N_vs, 3); 150 | FOR(v, N_vs) FOR(d, 3) def_V(v, d) = nlGetVariable(idmap[d * N_vs + v]); 151 | nlDeleteContext(context); 152 | 153 | auto solveend = std::chrono::steady_clock::now(); 154 | 155 | double totaltime = double(std::chrono::duration_cast (solveend - begin).count()) / 1000; 156 | double buildtime = double(std::chrono::duration_cast (buildend - begin).count()) / 1000; 157 | double solvetime = double(std::chrono::duration_cast (solveend - buildend).count()) / 1000; 158 | std::cerr << "Fast bnd poly build + solve: " << buildtime << " + " << solvetime << " = " << totaltime << "sec." << std::endl; 159 | 160 | #ifdef DEBUG_QUICK_LABEL_EV 161 | //igl::writeOBJ("../debug_quick_label_ev.obj", def_V, F_); 162 | std::cout << "hardDeformation done" << std::endl; 163 | #endif 164 | 165 | return def_V; 166 | } 167 | 168 | Eigen::MatrixXd QuickLabelEv::computeDeformedV(const Eigen::VectorXi& labeling) const { 169 | return LDLTDeformation(labeling); 170 | } 171 | 172 | double QuickLabelEv::evaluate(const Eigen::VectorXi& labeling) const { 173 | int n_fail_invert; 174 | return evaluate(labeling, n_fail_invert); 175 | } 176 | 177 | double QuickLabelEv::evaluate(const Eigen::VectorXi& labeling, int& n_fail_invert) const { 178 | 179 | Eigen::MatrixXd def_V = LDLTDeformation(labeling); 180 | 181 | Eigen::MatrixXd N_def; 182 | igl::per_face_normals(def_V, F_, N_def); 183 | 184 | Eigen::VectorXd disto; 185 | computeDisto(V_, def_V, F_, N_, N_def, disto); 186 | 187 | disto = disto.array() * disto.array(); 188 | 189 | double final_disto = integrateDistortion(A_, disto); 190 | 191 | n_fail_invert = 0; 192 | Eigen::MatrixXd axes_matrix = axesMatrix(); 193 | for (int i=0; i(n_fail_invert); 202 | 203 | /*std::cout << "n_fail_invert: " << n_fail_invert << std::endl; 204 | std::cout << "final_disto: " << final_disto << std::endl; 205 | std::cout << "disto.maxCoeff(): " << disto.maxCoeff() << std::endl; 206 | std::cout << "A_.minCoeff() : " << A_.minCoeff() << std::endl;*/ 207 | return final_disto; 208 | } 209 | 210 | Eigen::MatrixXd QuickLabelEv::distoAboveThreshold(const Eigen::VectorXi& labeling, double threshold) const { 211 | Eigen::MatrixXd def_V = LDLTDeformation(labeling); 212 | 213 | Eigen::MatrixXd N_def; 214 | igl::per_face_normals(def_V, F_, N_def); 215 | 216 | Eigen::VectorXd disto; 217 | computeDisto(V_, def_V, F_, N_, N_def, disto); 218 | 219 | Eigen::MatrixXd colors = Eigen::MatrixXd::Constant(labeling.rows(), 3, 1.0); 220 | 221 | int n_fail_threshold = 0; 222 | for (int i=0; i threshold){ 224 | n_fail_threshold ++; 225 | colors.row(i) = Eigen::RowVector3d(1.0, 0, 0); 226 | } 227 | } 228 | 229 | coloredPrint("Elements above threshold: " + std::to_string(n_fail_threshold), "cyan"); 230 | return colors; 231 | } -------------------------------------------------------------------------------- /src/tet_boundary.cpp: -------------------------------------------------------------------------------- 1 | #include "tet_boundary.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "logging.h" 10 | 11 | BndToTetConverter::BndToTetConverter(std::string input_file){ 12 | int line; 13 | std::ifstream file(input_file); 14 | 15 | if (file.is_open()){ 16 | file >> n_tris_; 17 | file >> n_tets_; 18 | table_.resize(n_tris_); 19 | for (int i=0; i < n_tris_; i++){ 20 | file >> table_(i); 21 | } 22 | file.close(); 23 | } 24 | 25 | else std::cout << "BndToTetConverter: Unable to open file"; 26 | } 27 | 28 | BndToTetConverter::BndToTetConverter(const Eigen::VectorXi table, int n_tets) : table_(table) { 29 | n_tris_ = table_.rows(); 30 | n_tets_ = n_tets; 31 | } 32 | 33 | void BndToTetConverter::writeTo(std::string write_path){ 34 | std::ofstream file(write_path); 35 | if (file.is_open()){ 36 | file << n_tris_; 37 | file << "\n"; 38 | file << n_tets_; 39 | file << "\n"; 40 | for (int i=0; i> corres = { 102 | {1, 3, 2}, 103 | {0, 2, 3}, 104 | {0, 3, 1}, 105 | {0, 1, 2} 106 | }; 107 | 108 | if (Fb.rows() != conv.table_.rows()) coloredPrint("Correspondence mismatch", "red"); 109 | 110 | for (int i=0; i