├── python ├── curve │ ├── __init__.py │ ├── data │ │ ├── tri_o1_lv3.h5 │ │ ├── tri_o2_lv3.h5 │ │ ├── tri_o3_lv3.h5 │ │ ├── tri_o4_lv3.h5 │ │ ├── tetra_o3_l2b.h5 │ │ ├── tetra_o4_l2b.h5 │ │ ├── tetra_o5_l2b.h5 │ │ ├── tetra_o6_l2b.h5 │ │ ├── tetra_o7_l2b.h5 │ │ ├── tetra_o8_l2b.h5 │ │ ├── tetra_o9_l2b.h5 │ │ ├── p3_quniform5_dxyz.h5 │ │ ├── p4_quniform5_dxyz.h5 │ │ ├── p5_quniform5_dxyz.h5 │ │ └── p6_quniform5_dxyz.h5 │ └── gmsh_helper.py ├── interpolate │ └── __init__.py ├── cadconvert │ ├── fem_tabulator.py │ ├── bezier.py │ ├── occ_step.py │ └── tensor-product-fit.py ├── cumin_datagen.py ├── format_utils.py └── vtk_node_ordering.py ├── src ├── prism │ ├── osqp │ │ ├── osqp_normal.hpp │ │ └── osqp_normal.cpp │ ├── extraction.hpp │ ├── phong │ │ ├── tangent_orientation.hpp │ │ ├── tangent_orientation.cpp │ │ ├── query_correspondence.hpp │ │ ├── projection.hpp │ │ └── projection.cpp │ ├── cgal │ │ ├── QP.hpp │ │ ├── tetrahedron_tetrahedron_intersection.hpp │ │ ├── boolean.hpp │ │ ├── triangle_tetrahedron_intersection.hpp │ │ ├── triangle_triangle_intersection.hpp │ │ ├── polyhedron_self_intersect.hpp │ │ ├── boolean.cpp │ │ ├── tetrahedron_tetrahedron_intersection.cpp │ │ ├── QP.cpp │ │ └── triangle_triangle_intersection.cpp │ ├── predicates │ │ ├── tetrahedron_overlap.hpp │ │ ├── triangle_triangle_intersection.hpp │ │ ├── positive_prism_volume_12.hpp │ │ ├── inside_prism_tetra.hpp │ │ ├── tetrahedron_overlap.cpp │ │ ├── inside_prism_tetra.cpp │ │ ├── inside_octahedron.hpp │ │ └── positive_prism_volume_12.cpp │ ├── geogram │ │ ├── geogram_utils.hpp │ │ ├── AABB.hpp │ │ └── geogram_utils.cpp │ ├── local_operations │ │ ├── mesh_coloring.hpp │ │ ├── remesh_with_feature.hpp │ │ ├── retain_triangle_adjacency.hpp │ │ ├── mesh_coloring.cpp │ │ ├── section_remesh.hpp │ │ ├── local_mesh_edit.hpp │ │ └── remesh_pass.hpp │ ├── intersections.hpp │ ├── polyshell_utils.hpp │ ├── energy │ │ ├── map_distortion.hpp │ │ ├── map_distortion.cpp │ │ ├── prism_quality.hpp │ │ └── smoother_pillar.hpp │ ├── cage_check.hpp │ ├── spatial-hash │ │ ├── self_intersection.hpp │ │ ├── AABB_hash.hpp │ │ └── self_intersection.cpp │ ├── bevel_utils.hpp │ ├── intersections.cpp │ ├── PrismCage.hpp │ ├── feature_utils.hpp │ └── cage_utils.hpp ├── cumin │ ├── stitch_surface_to_volume.hpp │ ├── bernstein_eval.hpp │ ├── inversion_check.hpp │ ├── curve_validity.hpp │ ├── high_order_optimization.hpp │ ├── curve_pass.cpp │ ├── curve_common.hpp │ ├── bernstein_eval.cpp │ └── inversion_check.cpp ├── python │ ├── module.cpp │ ├── prism.cpp │ ├── CMakeLists.txt │ ├── curve.cpp │ └── spatial.cpp ├── CMakeLists.txt ├── getRSS.c └── curve_in_shell.cpp ├── tests ├── test_common.hpp ├── test_main.cpp ├── doo_sabin.cpp ├── numerical_self_intersection.cpp ├── surface_stitch.cpp ├── bevel_init.cpp ├── cgal_qp.cpp ├── CMakeLists.txt ├── remesh_shell_bin.cpp ├── phong.cpp ├── cgal_AABB.cpp ├── curve_fitting.cpp ├── feature_parse_bin.cpp └── curved_tetra_mips.cpp ├── cmake ├── CMakeColors.cmake ├── DownloadProject.CMakeLists.cmake.in ├── FindLIBIGL.cmake ├── FindMPFR.cmake ├── geogram.cmake ├── PrismDependencies.cmake └── Warnings.cmake ├── .gitignore ├── CMakeLists.txt └── LICENSE.txt /python/curve/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/interpolate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python/curve/data/tri_o1_lv3.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tri_o1_lv3.h5 -------------------------------------------------------------------------------- /python/curve/data/tri_o2_lv3.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tri_o2_lv3.h5 -------------------------------------------------------------------------------- /python/curve/data/tri_o3_lv3.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tri_o3_lv3.h5 -------------------------------------------------------------------------------- /python/curve/data/tri_o4_lv3.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tri_o4_lv3.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o3_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o3_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o4_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o4_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o5_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o5_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o6_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o6_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o7_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o7_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o8_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o8_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/tetra_o9_l2b.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/tetra_o9_l2b.h5 -------------------------------------------------------------------------------- /python/curve/data/p3_quniform5_dxyz.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/p3_quniform5_dxyz.h5 -------------------------------------------------------------------------------- /python/curve/data/p4_quniform5_dxyz.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/p4_quniform5_dxyz.h5 -------------------------------------------------------------------------------- /python/curve/data/p5_quniform5_dxyz.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/p5_quniform5_dxyz.h5 -------------------------------------------------------------------------------- /python/curve/data/p6_quniform5_dxyz.h5: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangzhongshi/bichon/HEAD/python/curve/data/p6_quniform5_dxyz.h5 -------------------------------------------------------------------------------- /src/prism/osqp/osqp_normal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common.hpp" 4 | namespace prism { 5 | Vec3d osqp_normal(const RowMatd &N, const std::vector&nb); 6 | } -------------------------------------------------------------------------------- /src/prism/extraction.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_EXTRACTION_HPP 2 | #define PRISM_EXTRACTION_HPP 3 | 4 | struct PrismCage; 5 | namespace prism { 6 | [[deprecated]] bool mid_surface_extraction(PrismCage&); 7 | bool shell_extraction(PrismCage& pc, bool base); 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /src/prism/phong/tangent_orientation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_PHONG_TANGENT_ORIENTATION_HPP 2 | #define PRISM_PHONG_TANGENT_ORIENTATION_HPP 3 | 4 | #include "../common.hpp" 5 | namespace prism::phong { 6 | bool tangent_orientation(const std::array& stacked_V, 7 | const std::array& triangle); 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /src/prism/cgal/QP.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CGAL_QP_HPP 2 | #define PRISM_CGAL_QP_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace prism::cgal { 8 | Vec3d qp_normal(const RowMatd& FN, const std::vector& nb); 9 | } 10 | namespace prism{ 11 | Vec3d qp_normal_igl(const RowMatd& FN, const std::vector& nb); 12 | } 13 | 14 | #endif -------------------------------------------------------------------------------- /tests/test_common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_TEST_COMMON 2 | #define PRISM_TEST_COMMON 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #endif -------------------------------------------------------------------------------- /tests/test_main.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Keep this file empty, and implement unit tests in separate compilation units! 3 | //////////////////////////////////////////////////////////////////////////////// 4 | 5 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 6 | #define DOCTEST_CONFIG_SUPER_FAST_ASSERTS 7 | #include 8 | -------------------------------------------------------------------------------- /src/prism/predicates/tetrahedron_overlap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_PREDICATE_TETRAHEDRON_TETRAHEDRON_INTERSECTION_HPP 2 | #define PRISM_PREDICATE_TETRAHEDRON_TETRAHEDRON_INTERSECTION_HPP 3 | 4 | #include 5 | #include "../common.hpp" 6 | 7 | namespace prism::predicates { 8 | bool tetrahedron_tetrahedron_overlap(const std::array&, 9 | const std::array&); 10 | } // namespace prism 11 | 12 | #endif -------------------------------------------------------------------------------- /src/prism/cgal/tetrahedron_tetrahedron_intersection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CGAL_TETRAHEDRON_TETRAHEDRON_INTERSECTION_HPP 2 | #define PRISM_CGAL_TETRAHEDRON_TETRAHEDRON_INTERSECTION_HPP 3 | 4 | #include 5 | #include "../common.hpp" 6 | 7 | namespace prism::cgal { 8 | [[deprecated]] 9 | bool tetrahedron_tetrahedron_intersection(const std::array&, 10 | const std::array&); 11 | } // namespace prism 12 | 13 | #endif -------------------------------------------------------------------------------- /src/prism/geogram/geogram_utils.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace prism::geo { 10 | void init_geogram(); 11 | 12 | // switch based on F.cols() 13 | void to_geogram_mesh(const RowMatd& V, const RowMati& F, GEO::Mesh& M); 14 | void from_geogram_mesh(const GEO::Mesh& M, RowMatd& V, RowMati& T); 15 | } -------------------------------------------------------------------------------- /src/prism/local_operations/mesh_coloring.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_COLORING_MESH_HPP 2 | #define PRISM_COLORING_MESH_HPP 3 | 4 | #include 5 | 6 | namespace prism::local { 7 | 8 | // greedy graph coloring algorithm 9 | // [https://www.geeksforgeeks.org/graph-coloring-set-2-greedy-algorithm/] 10 | void vertex_coloring(const RowMati& F, std::vector>& group); 11 | 12 | void red_green_coloring(const RowMati& F, const RowMati& FF, 13 | Eigen::VectorXi& colors); 14 | 15 | } // namespace prism::local 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /src/prism/cgal/boolean.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CGAL_BOOLEAN_HPP 2 | #define PRISM_CGAL_BOOLEAN_HPP 3 | 4 | #include 5 | #include 6 | namespace prism { 7 | void boolean_resolve(const RowMatd& V1, const RowMati& F1, const RowMatd& V2, 8 | const RowMati& F2, RowMatd& Vc, RowMati& Fc, Eigen::VectorXi& J); 9 | void mesh_boolean(const RowMatd& VA, const RowMati& FA, const RowMatd& VB, 10 | const RowMati& FB, igl::MeshBooleanType type, RowMatd& V_ABi, 11 | RowMati& F_ABi, Eigen::VectorXi& Birth); 12 | } 13 | #endif -------------------------------------------------------------------------------- /src/prism/predicates/triangle_triangle_intersection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_PREDICATES_TRIANGLE_TRIANGLE_INTERSECTION_HPP 2 | #define PRISM_PREDICATES_TRIANGLE_TRIANGLE_INTERSECTION_HPP 3 | 4 | #include 5 | #include "../common.hpp" 6 | 7 | namespace prism::predicates { 8 | bool triangle_triangle_overlap(const std::array& tri0, 9 | const std::array& tri1); 10 | bool segment_triangle_overlap(const std::array& seg, 11 | const std::array& tri1); 12 | } // namespace prism 13 | 14 | #endif -------------------------------------------------------------------------------- /cmake/CMakeColors.cmake: -------------------------------------------------------------------------------- 1 | if (NOT WIN32) 2 | string(ASCII 27 Esc) 3 | set(ColorReset "${Esc}[m") 4 | set(ColorBold "${Esc}[1m") 5 | set(Red "${Esc}[31m") 6 | set(Green "${Esc}[32m") 7 | set(Yellow "${Esc}[33m") 8 | set(Blue "${Esc}[34m") 9 | set(Magenta "${Esc}[35m") 10 | set(Cyan "${Esc}[36m") 11 | set(White "${Esc}[37m") 12 | set(BoldRed "${Esc}[1;31m") 13 | set(BoldGreen "${Esc}[1;32m") 14 | set(BoldYellow "${Esc}[1;33m") 15 | set(BoldBlue "${Esc}[1;34m") 16 | set(BoldMagenta "${Esc}[1;35m") 17 | set(BoldCyan "${Esc}[1;36m") 18 | set(BoldWhite "${Esc}[1;37m") 19 | endif() 20 | -------------------------------------------------------------------------------- /src/prism/cgal/triangle_tetrahedron_intersection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CGAL_TRIANGLE_TETRAHEDRON_INTERSECTION_HPP 2 | #define PRISM_CGAL_TRIANGLE_TETRAHEDRON_INTERSECTION_HPP 3 | 4 | #include 5 | #include "../common.hpp" 6 | 7 | namespace prism::cgal { 8 | [[deprecated]] 9 | bool triangle_tetrahedron_intersection(const std::array&, 10 | const std::array&, 11 | bool wireless=false); 12 | bool triangle_tetrahedron_intersection_wireless( 13 | const std::array& triangle, 14 | const std::array& tetrahedron); 15 | } // namespace prism 16 | 17 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.h5 2 | *.ipynb 3 | *.log 4 | *.npy 5 | *.npz 6 | *.obj 7 | *.pickle 8 | *.pkl 9 | *.png 10 | *.ser 11 | *.so 12 | *.svg 13 | */.ipynb_checkpoints/ 14 | .idea/ 15 | .vscode/ 16 | CMakeLists\.txt\.user 17 | Untitled-1.nb 18 | \.ipynb_checkpoints/ 19 | __pycache__/ 20 | debug/ 21 | build/ 22 | bin*/ 23 | build_*/ 24 | buildd/ 25 | buildp 26 | buildr/ 27 | external/ 28 | log/ 29 | python/*.html 30 | python/*.msh 31 | python/Untitled.ipynb 32 | python/data/ 33 | temp-plot.html 34 | tests/data/ 35 | visualize/*.mesh 36 | visualize/*.msh 37 | visualize/*.ply 38 | visualize/data 39 | visualize/ff/ 40 | python/curve/spherewl/ 41 | .cache/ 42 | *build* 43 | *.code-workspace -------------------------------------------------------------------------------- /src/prism/local_operations/remesh_with_feature.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_LOCAL_REMESH_WITH_FEATURE_HPP 2 | #define PRISM_LOCAL_REMESH_WITH_FEATURE_HPP 3 | #include "remesh_pass.hpp" 4 | 5 | namespace prism::local { 6 | int feature_collapse_pass(PrismCage &, RemeshOptions &); 7 | int feature_slide_pass(PrismCage &, RemeshOptions &); 8 | int feature_split_pass(PrismCage &, RemeshOptions &); 9 | int zig_collapse_pass(PrismCage &pc, RemeshOptions &option); 10 | int zig_slide_pass(PrismCage &pc, RemeshOptions &option); 11 | int zig_split_pass(PrismCage &pc, RemeshOptions &option); 12 | int zig_comb_pass(PrismCage &pc, RemeshOptions &option); 13 | } // namespace prism::local 14 | 15 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | project(prism) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 8 | set(PRISM_EXTERNAL ${CMAKE_CURRENT_SOURCE_DIR}/external/) 9 | 10 | include(PrismDependencies) 11 | 12 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 13 | cmake_policy(SET CMP0069 NEW) 14 | # set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 15 | 16 | set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 17 | 18 | add_subdirectory(src) 19 | 20 | option(PRISM_TESTS "Unit Tests" ON) 21 | if(PRISM_TESTS) 22 | include(CTest) 23 | add_subdirectory(tests) 24 | endif() -------------------------------------------------------------------------------- /src/prism/predicates/positive_prism_volume_12.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_POSITIVE_PRISM_VOLUME_12_HPP 2 | #define PRISM_POSITIVE_PRISM_VOLUME_12_HPP 3 | 4 | #include "../common.hpp" 5 | namespace prism::predicates { 6 | bool positive_prism_volume(const std::array& verts, 7 | const std::array& constrained, 8 | bool numerical = false); 9 | bool positive_prism_volume(const std::array& verts); 10 | 11 | // this is not yet an exact predicate. 12 | bool positive_nonlinear_prism(const std::array& verts, 13 | const std::array& constrained); 14 | } // namespace prism::predicates 15 | #endif -------------------------------------------------------------------------------- /src/prism/phong/tangent_orientation.cpp: -------------------------------------------------------------------------------- 1 | #include "tangent_orientation.hpp" 2 | 3 | #include "../predicates/inside_prism_tetra.hpp" 4 | 5 | namespace prism::phong { 6 | bool tangent_orientation(const std::array& stacked_V, 7 | const std::array& triangle) { 8 | double EPS = 1e-8; 9 | Vec3d tri_normal = (triangle[1] - triangle[0]) 10 | .cross(triangle[2] - triangle[0]) 11 | .stableNormalized(); 12 | for (int i = 0; i < 3; i++) { 13 | Vec3d fiber = stacked_V[i + 3] - stacked_V[i]; 14 | if (fiber.dot(tri_normal) < EPS) return false; 15 | } 16 | return true; 17 | } 18 | 19 | } // namespace prism::phong 20 | -------------------------------------------------------------------------------- /src/cumin/stitch_surface_to_volume.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CUMIN_STITCH_SURFACE_HPP 2 | #define CUMIN_STITCH_SURFACE_HPP 3 | 4 | #include 5 | 6 | //////// 7 | // Prerequisite: Vmsh is ordered as Vbase, Vin 8 | // 1. Process Vmsh, to become [Vbase, _, Vin], leaving empty index margin. 9 | // 2. Assemble tuples for shell, and msh. 10 | // 11 | // the final vertex indices are aranged as V_base, V_top, V_in 12 | 13 | namespace prism::curve { 14 | bool stitch_surface_to_volume( 15 | const RowMatd &base, const RowMatd &top, const RowMati &F_sh, 16 | const std::vector &complete_cp, 17 | const Eigen::MatrixXd &Vmsh, const Eigen::MatrixXi &Tmsh, 18 | RowMatd& output_nodes, RowMati& p4T); 19 | } 20 | 21 | #endif -------------------------------------------------------------------------------- /cmake/DownloadProject.CMakeLists.cmake.in: -------------------------------------------------------------------------------- 1 | # Distributed under the OSI-approved MIT License. See accompanying 2 | # file LICENSE or https://github.com/Crascit/DownloadProject for details. 3 | 4 | cmake_minimum_required(VERSION 2.8.2) 5 | 6 | project(${DL_ARGS_PROJ}-download NONE) 7 | 8 | include(ExternalProject) 9 | ExternalProject_Add(${DL_ARGS_PROJ}-download 10 | ${DL_ARGS_UNPARSED_ARGUMENTS} 11 | SOURCE_DIR "${DL_ARGS_SOURCE_DIR}" 12 | BINARY_DIR "${DL_ARGS_BINARY_DIR}" 13 | CONFIGURE_COMMAND "" 14 | BUILD_COMMAND "" 15 | INSTALL_COMMAND "" 16 | TEST_COMMAND "" 17 | ) 18 | -------------------------------------------------------------------------------- /src/prism/local_operations/retain_triangle_adjacency.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_LOCAL_OPERATIONS_RETAIN_TRIANGLE_ADJACENCY_HPP 2 | #define PRISM_LOCAL_OPERATIONS_RETAIN_TRIANGLE_ADJACENCY_HPP 3 | 4 | #include 5 | #include "../common.hpp" 6 | namespace prism::local { 7 | std::pair, std::vector> triangle_triangle_adjacency( 8 | const std::vector& F); 9 | // lazy person's book-keeping: Burn the books and buy again. 10 | void retain_triangle_adjacency(const std::set& delete_id, 11 | const std::vector& new_F, 12 | std::vector& F, std::vector& TT, 13 | std::vector& TTi); 14 | } // namespace prism::local 15 | 16 | #endif -------------------------------------------------------------------------------- /src/prism/intersections.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "common.hpp" 5 | namespace igl { 6 | struct Hit; 7 | } 8 | namespace prism{ 9 | struct Hit{ 10 | int gid, id; 11 | double u,v,t; 12 | }; 13 | } 14 | namespace prism::intersections { 15 | std::optional segment_triangle_intersection_inexact( 16 | const std::array &seg, const std::array &tri); 17 | 18 | bool segment_triangle_hit(const std::array &seg, 19 | const std::array &tri, prism::Hit &hit); 20 | bool segment_triangle_hit_cgal(const std::array &seg, 21 | const std::array &tri, prism::Hit &hit); 22 | 23 | } // namespace prism::intersections 24 | -------------------------------------------------------------------------------- /src/prism/polyshell_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_POLYSHELL_UTILS_HPP 2 | #define PRISM_POLYSHELL_UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "common.hpp" 10 | struct PrismCage; 11 | namespace prism::local_validity { 12 | auto identify_zig(const std::map, std::pair>> &meta_edges, 13 | const Vec3i &f) -> std::tuple> ; 14 | 15 | auto zig_constructor(const PrismCage &pc, int v0, int v1, int v2, 16 | const std::vector &segs, bool lets_comb_the_pillars) 17 | -> std::tuple, std::vector, std::vector, 18 | std::vector, std::vector> ; 19 | } 20 | #endif -------------------------------------------------------------------------------- /src/cumin/bernstein_eval.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CUMIN_BERNSTEIN_EVAL_HPP 2 | #define CUMIN_BERNSTEIN_EVAL_HPP 3 | #include 4 | namespace prism::curve { 5 | // 6 | // 7 | // (number of basis) by (number of samples) 8 | Eigen::ArrayXXd evaluate_bernstein(const Eigen::VectorXd& X, 9 | const Eigen::VectorXd& Y, 10 | const Eigen::VectorXd& Z, 11 | const RowMati& short_codecs); 12 | 13 | // 14 | // 15 | // vector of 3: (number of basis) by (number of samples) 16 | std::array evaluate_bernstein_derivative( 17 | const Eigen::VectorXd& X, const Eigen::VectorXd& Y, 18 | const Eigen::VectorXd& Z, const RowMati& short_codecs); 19 | } // namespace prism::curve 20 | 21 | #endif -------------------------------------------------------------------------------- /src/cumin/inversion_check.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CUMIN_INVERSION_CHECK_HPP 2 | #define PRISM_CUMIN_INVERSION_CHECK_HPP 3 | 4 | #include 5 | namespace prism::curve { 6 | bool tetrahedron_recursive_positive_check(const Eigen::VectorXd& controlpts, 7 | const RowMatd& bern_from_lag, 8 | const Eigen::MatrixX4i& short_codecs); 9 | bool tetrahedron_inversion_check(const RowMatd& cp, const Eigen::MatrixX4i& codecs_o4, 10 | const Eigen::MatrixX4i& codec_o9, 11 | const RowMatd& bern_from_lagr_o4, 12 | const RowMatd& bern_from_lagr_o9); 13 | bool tetrahedron_inversion_check(const RowMatd& cp); 14 | } // namespace prism::curve 15 | 16 | #endif -------------------------------------------------------------------------------- /src/prism/cgal/triangle_triangle_intersection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CGAL_TRIANGLE_TRIANGLE_INTERSECTION_HPP 2 | #define PRISM_CGAL_TRIANGLE_TRIANGLE_INTERSECTION_HPP 3 | 4 | #include 5 | #include 6 | #include "../common.hpp" 7 | 8 | namespace prism{struct Hit;} 9 | namespace prism::cgal { 10 | [[deprecated]] bool triangle_triangle_overlap(const std::array& tri0, 11 | const std::array& tri1); 12 | [[deprecated]] bool segment_triangle_overlap(const std::array& seg, 13 | const std::array& tri1); 14 | 15 | std::optional segment_triangle_intersection(const std::array& seg, 16 | const std::array& tri1); 17 | } // namespace prism 18 | 19 | #endif -------------------------------------------------------------------------------- /src/prism/energy/map_distortion.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_ENERGY_MAP_DISTORTION_HPP 2 | #define PRISM_ENERGY_MAP_DISTORTION_HPP 3 | 4 | #include "../common.hpp" 5 | namespace prism::energy { 6 | enum class DistortionType { SYMMETRIC_DIRICHLET, ARAP }; 7 | 8 | double map_max_distortion( 9 | const Vec3d& pillar, const std::array& source_tri, 10 | const std::array& target_tri, 11 | DistortionType qt = DistortionType::SYMMETRIC_DIRICHLET); 12 | 13 | 14 | bool map_max_angle_bound(const Vec3d& ez, // pillar 15 | const std::array& target_tri, 16 | double angle_bound = 0.1); 17 | 18 | double map_max_cos_angle(const Vec3d& ez, // pillar 19 | const std::array& target_tri); 20 | } // namespace prism::energy 21 | #endif -------------------------------------------------------------------------------- /src/prism/predicates/inside_prism_tetra.hpp: -------------------------------------------------------------------------------- 1 | // Inside tests for triangulated prism 2 | 3 | #ifndef PRISM_PREDICATES_INSIDE_PRISM_TETRA_HPP 4 | #define PRISM_PREDICATES_INSIDE_PRISM_TETRA_HPP 5 | 6 | #include "../common.hpp" 7 | namespace prism::predicates { 8 | bool point_in_tetrahedron(const Vec3d& point, const Vec3d& T0, const Vec3d& T1, 9 | const Vec3d& T2, const Vec3d& T3); 10 | 11 | bool point_in_prism(const Vec3d& point, bool tetra_split_AB, 12 | const std::array& verts); 13 | 14 | 15 | // [[deprecated("Not used now, tri-tet intersect is more informative")]] 16 | bool triangle_intersects_prism(const std::array& tri_v, bool tetra_split_AB, 17 | const std::array& pri_v); 18 | 19 | } // namespace prism::predicates 20 | 21 | #endif -------------------------------------------------------------------------------- /src/cumin/curve_validity.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CUMIN_CURVE_VALIDITY_HPP 2 | #define CUMIN_CURVE_VALIDITY_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include "curve_utils.hpp" 8 | 9 | struct PrismCage; 10 | namespace prism::local { 11 | struct RemeshOptions; 12 | } 13 | namespace prism::curve { 14 | 15 | // curve checker function handles 16 | // input pc is for experimental with features 17 | std::pair curve_func_handles( 18 | std::vector &complete_cp, const PrismCage &pc, const prism::local::RemeshOptions &option, 19 | int tri_order); 20 | 21 | // local smoother to optimize each curve 22 | // looks constant but their is a function in option that owns global 23 | // controlpoints value 24 | void localcurve_pass(const PrismCage &pc, 25 | const prism::local::RemeshOptions &option); 26 | } // namespace prism::curve 27 | #endif -------------------------------------------------------------------------------- /src/prism/cage_check.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CAGE_CHECK_HPP 2 | #define PRISM_CAGE_CHECK_HPP 3 | #include 4 | #include 5 | 6 | #include "common.hpp" 7 | struct PrismCage; 8 | namespace prism::local { 9 | struct RemeshOptions; 10 | } 11 | namespace prism::cage_check { 12 | bool cage_is_positive(const PrismCage& pc); 13 | bool cage_is_away_from_ref(const PrismCage& pc); 14 | bool verify_bijection(const PrismCage& pc, const std::vector& V, 15 | const std::vector& F, 16 | const std::vector>& track_to_prism); 17 | bool verify_edge_based_track(const PrismCage& pc, 18 | const prism::local::RemeshOptions& option, 19 | std::vector>& track_to_prism); 20 | 21 | void initial_trackee_reconcile(PrismCage& pc, 22 | double); 23 | } // namespace prism::cage_check 24 | #endif -------------------------------------------------------------------------------- /src/prism/spatial-hash/self_intersection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_SPATIAL_HASH_SELF_INTERSECTION_HPP 2 | #define PRISM_SPATIAL_HASH_SELF_INTERSECTION_HPP 3 | 4 | #include "../common.hpp" 5 | namespace prism::spatial_hash { 6 | std::vector> self_intersections( 7 | const std::vector &V, const std::vector &F); 8 | 9 | // raw routine for single-layer tetrashell candidates test. To be split into 10 | // multiple stages for integration. 11 | std::set> tetrashell_self_intersections( 12 | const std::vector &base, const std::vector &top, 13 | const std::vector &F); 14 | 15 | std::function &)> find_offending_pairs( 16 | const std::vector &F, const std::vector &tetV, 17 | const std::vector &tetT, 18 | std::set> &offend_pairs); 19 | } // namespace prism::spatial_hash 20 | 21 | #endif -------------------------------------------------------------------------------- /python/cadconvert/fem_tabulator.py: -------------------------------------------------------------------------------- 1 | import sympy 2 | import math 3 | import numpy as np 4 | 5 | 6 | def tuple_gen(order, var_n): 7 | if var_n == 0: 8 | return [[order]] 9 | l = [] 10 | for i in range(order + 1): 11 | r = tuple_gen(order-i, var_n - 1) 12 | l += [[i]+t for t in r] 13 | l = sorted(l, key=lambda x: (-sum(i**2 for i in x), x[::-1])) 14 | return l 15 | 16 | def bernstein_evaluator(x, y, z, codecs): 17 | m = len(codecs[0]) # dim + 1 18 | n = codecs[0][0] # order 19 | mc_dict = sympy.multinomial_coefficients(m, n) 20 | mc = np.array([mc_dict[tuple(c)] for c in codecs]) 21 | 22 | w = 1-x-y-z 23 | computed_powers = np.array([(w**i, x**i, y**i, z**i) 24 | for i in range(n + 1)]) # make use of 0**0 == 1 25 | return mc[:,None]*np.array( 26 | [np.prod([computed_powers[c, i] for i, c in enumerate(cod)], axis=0) for cod in codecs]) 27 | 28 | -------------------------------------------------------------------------------- /tests/doo_sabin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include "prism/PrismCage.hpp" 8 | #include "prism/geogram/AABB.hpp" 9 | #include "prism/local_operations/remesh_pass.hpp" 10 | 11 | #include "test_common.hpp" 12 | 13 | #include 14 | constexpr auto total_energy = [](auto& V, auto& F) { 15 | std::set low_quality_vertices; 16 | double total_quality = 0; 17 | double max_quality = 0; 18 | for (auto [v0, v1, v2] : F) { 19 | auto q = prism::energy::triangle_quality({V[v0], V[v1], V[v2]}); 20 | total_quality += q; 21 | max_quality = std::max(max_quality, q); 22 | } 23 | 24 | spdlog::info("Total Q {} number {}, divide {}, max {}", total_quality, F.size(), 25 | total_quality / F.size(), max_quality); 26 | if (max_quality < 10){spdlog::info("SUCCESS"); exit(0);} 27 | return max_quality; 28 | }; 29 | -------------------------------------------------------------------------------- /src/prism/phong/query_correspondence.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../common.hpp" 4 | 5 | struct PrismCage; 6 | namespace prism::geogram { 7 | struct AABB; 8 | }; 9 | namespace prism { 10 | struct Hit; 11 | }; 12 | namespace prism { 13 | // project query positions onto proxy (pxV, pxF) for fid and bc. 14 | void correspond_bc(const PrismCage &pc, const RowMatd &pxV, const RowMati &pxF, 15 | const RowMatd &queryPos, Eigen::VectorXi &resF, 16 | RowMatd &resUV); 17 | 18 | // internal 19 | bool project_to_proxy_mesh(const std::array &stack, 20 | const prism::geogram::AABB &pxtree, 21 | bool prism_type, const Vec3d &spatial, prism::Hit &hit); 22 | 23 | // used in sectional remeshing, with track enabled 24 | bool project_to_ref_mesh(const PrismCage &pc, 25 | const std::vector> &track_to_prism, 26 | const std::vector &tris, const Vec3d &point_value, 27 | Vec3d &point_on_ref); 28 | } // namespace prism -------------------------------------------------------------------------------- /tests/numerical_self_intersection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | TEST_CASE("numerical self intersection") { 9 | std::vector inters = { "104563.stl"}; 10 | spdlog::set_level(spdlog::level::trace); 11 | for (auto filename : inters) { 12 | RowMatd V; 13 | RowMati F; 14 | Eigen::VectorXi SVI, SVJ; 15 | igl::read_triangle_mesh( 16 | "/home/zhongshi/data/Thingi10K/raw_meshes/" + filename, V, F); 17 | RowMatd temp_V = V; // for STL file 18 | igl::remove_duplicate_vertices(temp_V, 0, V, SVI, SVJ); 19 | for (int i = 0; i < F.rows(); i++) 20 | for (int j : {0, 1, 2}) F(i, j) = SVJ[F(i, j)]; 21 | put_in_unit_box(V); 22 | prism::geogram::AABB tree(V, F); 23 | double tol = 1e-16; 24 | 25 | while (!tree.numerical_self_intersection(tol)) tol *= 10; 26 | spdlog::info("{}: self {}", filename, tol); 27 | REQUIRE_GT(tol, 1e-5); 28 | } 29 | } -------------------------------------------------------------------------------- /src/cumin/high_order_optimization.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CUMIN_HIGH_ORDER_OPTIMIZATION_HPP 2 | #define CUMIN_HIGH_ORDER_OPTIMIZATION_HPP 3 | 4 | #include "curve_common.hpp" 5 | 6 | namespace prism::curve { 7 | bool InversionCheck(const RowMatd &lagr, const RowMati &p4T, 8 | const RowMati &codec_fixed, const RowMati &codec9_fixed, 9 | const RowMatd &bern_from_lagr_o4, 10 | const RowMatd &bern_from_lagr_o9); 11 | 12 | Eigen::VectorXd energy_evaluation(RowMatd &lagr, RowMati &p4T, 13 | const std::vector &vec_dxyz); 14 | 15 | // lagr is unique here per nodes. not the duplicated version. 16 | void vertex_star_smooth(RowMatd &lagr, RowMati &p4T, int, int); 17 | 18 | int edge_collapsing(RowMatd &lagr, RowMati &p4T, double stop_energy); 19 | 20 | int edge_swapping(RowMatd &lagr, RowMati &p4T, double stop_energy); 21 | 22 | int cutet_collapse(RowMatd &lagr, RowMati &p4T, double stop_energy); 23 | int cutet_swap(RowMatd &lagr, RowMati &p4T, double stop_energy); 24 | } // namespace prism::curve 25 | 26 | #endif -------------------------------------------------------------------------------- /tests/surface_stitch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | TEST_CASE("stitch-surface") { 14 | RowMatd V; 15 | RowMati T(1, 4); 16 | T << 0, 1, 2, 3; 17 | V = RowMatd::Identity(4, 4); 18 | V = V.rightCols(3).eval(); 19 | spdlog::info(V); 20 | 21 | std::vector cp(1); // surface cp 22 | RowMatd mB = V.topRows(3); 23 | RowMatd mT = mB.array() + 1; 24 | RowMati mF(1, 3); 25 | mF << 0, 1, 2; 26 | int order = 3; 27 | cp[0].setRandom((order + 1) * (order + 2) / 2, 3); // cubic 28 | 29 | auto helper = prism::curve::magic_matrices(3,3); 30 | const auto elevlag_from_bern =helper.elev_lag_from_bern; 31 | const auto vec_dxyz =helper.volume_data.vec_dxyz; 32 | 33 | RowMatd nodes; 34 | RowMati p4T; 35 | prism::curve::stitch_surface_to_volume(mB, mT, mF, cp, V, T, nodes, p4T); 36 | } -------------------------------------------------------------------------------- /src/prism/bevel_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRIISM_BEVEL_UTILS_HPP 2 | #define PRIISM_BEVEL_UTILS_HPP 3 | #include 4 | namespace prism::bevel_utils { 5 | void adaptive_doo_sabin(const RowMatd& V, const RowMati& F, const RowMatd& VN, 6 | double eps, RowMatd& dsV, RowMati& dsF, RowMatd& dsVN, 7 | std::vector& face_parent); 8 | 9 | // additional splits after the normal bevel, around singularities to make 10 | // sections 11 | void singularity_special_bevel(RowMatd& V, RowMati& F, int num_cons, 12 | RowMatd& VN, std::vector& face_parent); 13 | 14 | // simplified bevel that use edge based overlap check, and allows edge markers 15 | void edge_based_bevel(const RowMatd& V, const RowMati& F, const RowMatd& VN, 16 | const RowMati& feat_vv, RowMatd& dsV, RowMati& dsF, 17 | RowMatd& dsVN, std::vector& face_parent); 18 | 19 | bool verify_edge_bevel(const RowMatd& V, const RowMati& F, const RowMatd& VN, 20 | const RowMati& feat_vv); 21 | } // namespace prism::bevel_utils 22 | 23 | #endif -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2021 Zhongshi Jiang and others 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /cmake/FindLIBIGL.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find the LIBIGL library 2 | # Once done this will define 3 | # 4 | # LIBIGL_FOUND - system has LIBIGL 5 | # LIBIGL_INCLUDE_DIR - **the** LIBIGL include directory 6 | if(LIBIGL_FOUND) 7 | return() 8 | endif() 9 | 10 | find_path(LIBIGL_INCLUDE_DIR igl/readOBJ.h 11 | HINTS 12 | ENV LIBIGL 13 | ENV LIBIGLROOT 14 | ENV LIBIGL_ROOT 15 | ENV LIBIGL_DIR 16 | PATHS 17 | ${CMAKE_SOURCE_DIR}/../.. 18 | ${CMAKE_SOURCE_DIR}/.. 19 | ${CMAKE_SOURCE_DIR} 20 | ${CMAKE_SOURCE_DIR}/libigl 21 | ${CMAKE_SOURCE_DIR}/../libigl 22 | ${CMAKE_SOURCE_DIR}/../../libigl 23 | /usr 24 | /usr/local 25 | /usr/local/igl/libigl 26 | PATH_SUFFIXES include 27 | ) 28 | 29 | include(FindPackageHandleStandardArgs) 30 | find_package_handle_standard_args(LIBIGL 31 | "\nlibigl not found --- You can download it using:\n\tgit clone --recursive https://github.com/libigl/libigl.git ${CMAKE_SOURCE_DIR}/../libigl" 32 | LIBIGL_INCLUDE_DIR) 33 | mark_as_advanced(LIBIGL_INCLUDE_DIR) 34 | 35 | list(APPEND CMAKE_MODULE_PATH "${LIBIGL_INCLUDE_DIR}/../cmake") 36 | include(libigl) 37 | -------------------------------------------------------------------------------- /src/prism/cgal/polyhedron_self_intersect.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_POLYHEDRON_SELF_INTERSECT_HPP 2 | #define PRISM_POLYHEDRON_SELF_INTERSECT_HPP 3 | 4 | #include 5 | 6 | namespace prism { 7 | namespace cgal { 8 | // wrapper for CGAL self intersect detection 9 | bool polyhedron_self_intersect(const Eigen::MatrixXd& V, 10 | const Eigen::MatrixXi& F); 11 | 12 | // get all the fe that is responsible. For middle surface extraction since that 13 | // is the only case. 14 | bool polyhedron_self_intersect_edges( 15 | const Eigen::MatrixXd& V, const Eigen::MatrixXi& F, 16 | std::vector>& fe_pairs); 17 | 18 | void polyhedron_all_self_intersections(const Eigen::MatrixXd& V, 19 | const Eigen::MatrixXi& F); 20 | bool tetrashell_self_intersect(const Eigen::MatrixXd& base, 21 | const Eigen::MatrixXd& top, 22 | const Eigen::MatrixXi& F, 23 | const std::vector& mask, 24 | std::vector>& pairs); 25 | } // namespace cgal 26 | } // namespace prism 27 | 28 | #endif -------------------------------------------------------------------------------- /src/prism/phong/projection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_PHONG_PROJECTION_HPP 2 | #define PRISM_PHONG_PROJECTION_HPP 3 | 4 | #include "../common.hpp" 5 | namespace prism::phong { 6 | // for one prism, tuple record a,b,t 7 | bool phong_projection(const std::array& stacked_V, const Vec3d& point, 8 | bool tetra_split_way, std::array& tuple); 9 | 10 | bool phong_projection_uv(const std::array& base_v, 11 | const std::array& mid_v, 12 | const std::array& top_v, const Vec3d& point, 13 | bool tetra_split_way, std::array& uv); 14 | // get the endpoints of a single fiber (prism slab, so 4 points) 15 | void fiber_endpoints(const std::array& stacked_V, 16 | bool tetra_split_way, double u, double v, 17 | std::array& endpoints); 18 | 19 | // aggreagated function 20 | void fiber_endpoints(const std::array& stacked_V, 21 | bool tetra_split_way, double u, double v, 22 | std::array& endpoints); 23 | } // namespace prism::phong 24 | 25 | namespace prism::nonlinear { 26 | bool phong_projection(const Eigen::Matrix3d& V, const Eigen::Matrix3d& N, 27 | const Eigen::RowVector3d& point, 28 | std::array& tuple); 29 | } 30 | #endif -------------------------------------------------------------------------------- /src/python/module.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | //////////////////////////////////////////////////////////////////////////////// 10 | 11 | namespace py = pybind11; 12 | 13 | using namespace pybind11::literals; 14 | // using namespace prism; 15 | 16 | extern void python_export_curve(py::module&); 17 | extern void python_export_spatial(py::module&); 18 | extern void python_export_prism(py::module&); 19 | 20 | PYBIND11_MODULE(prism, m) { 21 | m.doc() = R"docstring( 22 | ================ 23 | pyPrism: Python Binding for Bijective Projection Shell and Curved Meshing 24 | Techniques used in "Bijective Projection in a Shell" and "Bijective and Coarse High Order Tetrahedral Meshes" 25 | Zhongshi Jiang, 2021 26 | ================ 27 | )docstring"; 28 | 29 | m.def("foo", 30 | [](const Eigen::MatrixXd& V, const Eigen::MatrixXi& F) { return V; }, 31 | "foo", "vertices"_a, "facets"_a); 32 | m.def("loglevel", [](int level) { 33 | spdlog::set_level(static_cast(level)); 34 | }, "set log level", "level"_a); 35 | 36 | python_export_spatial(m); 37 | python_export_prism(m); 38 | python_export_curve(m); 39 | } 40 | -------------------------------------------------------------------------------- /src/prism/energy/map_distortion.cpp: -------------------------------------------------------------------------------- 1 | #include "map_distortion.hpp" 2 | 3 | #include 4 | double prism::energy::map_max_distortion(const Vec3d& ez, // pillar 5 | const std::array& source_tri, 6 | const std::array& target_tri, 7 | DistortionType qt) { 8 | Vec3d r01 = source_tri[1] - source_tri[0]; 9 | Vec3d source_N = (r01.cross(source_tri[2] - source_tri[0])).stableNormalized(); 10 | Vec3d r02 = source_N.cross(r01); 11 | r01.normalize(); 12 | r02.normalize(); 13 | 14 | Vec3d tN = 15 | (target_tri[1] - target_tri[0]).cross(target_tri[2] - target_tri[0]); 16 | tN.normalize(); 17 | 18 | if (ez.dot(tN) <= 0) 19 | return std::numeric_limits::infinity(); 20 | Vec3d d01 = r01 - r01.dot(tN) / (ez.dot(tN)) * ez; 21 | Vec3d d02 = r02 - r02.dot(tN) / (ez.dot(tN)) * ez; 22 | 23 | double det = tN.dot(d01.cross(d02)); 24 | if (det <= 0) return std::numeric_limits::infinity(); 25 | assert(det > 0); 26 | double frobsq = d01.cwiseAbs2().sum() + d02.cwiseAbs2().sum(); 27 | if (qt == DistortionType::SYMMETRIC_DIRICHLET) 28 | return frobsq * (1 + 1 / (det * det)); 29 | else 30 | return -1; 31 | } 32 | 33 | double prism::energy::map_max_cos_angle(const Vec3d& ez, // pillar 34 | const std::array& target_tri) { 35 | Vec3d tN = 36 | (target_tri[1] - target_tri[0]).cross(target_tri[2] - target_tri[0]); 37 | return ez.dot(tN) / (ez.norm()*tN.norm()); 38 | } -------------------------------------------------------------------------------- /src/prism/cgal/boolean.cpp: -------------------------------------------------------------------------------- 1 | #include "boolean.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // void prism::boolean_resolve(const RowMatd& V1, const RowMati& F1, 8 | // const RowMatd& V2, const RowMati& F2, RowMatd& 9 | // Vc, RowMati& Fc, Eigen::VectorXi& J) { 10 | // igl::copyleft::cgal::mesh_boolean(V1, F1, V2, F2, "resolve", Vc, Fc, J); 11 | // } 12 | namespace prism { 13 | void mesh_boolean(const RowMatd& VA, const RowMati& FA, const RowMatd& VB, 14 | const RowMati& FB, igl::MeshBooleanType type, RowMatd& V_ABi, 15 | RowMati& F_ABi, Eigen::VectorXi& Birth) { 16 | igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, type, V_ABi, F_ABi, Birth); 17 | } 18 | void remesh_intersect(const RowMatd& V1, const RowMati& F1, const RowMatd& V2, 19 | const RowMati& F2, RowMatd& Vr, RowMati& Fr, 20 | Eigen::VectorXi& CJ, Eigen::VectorXi& IM) { 21 | Eigen::MatrixXd VV; 22 | Eigen::MatrixXi FF; 23 | igl::combine(std::vector{V1, V2}, std::vector{F1, F2}, VV, 24 | FF); 25 | igl::copyleft::cgal::RemeshSelfIntersectionsParam params; 26 | params.stitch_all = true; 27 | Eigen::MatrixXi IF, I; 28 | Eigen::MatrixXd matVr; 29 | Eigen::MatrixXi matFr; 30 | igl::copyleft::cgal::remesh_self_intersections(VV, FF, params, matVr, matFr, 31 | IF, CJ, IM); 32 | Vr = matVr; 33 | Fr = matFr; 34 | } 35 | } // namespace prism -------------------------------------------------------------------------------- /tests/bevel_init.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "cumin/curve_validity.hpp" 18 | #include "prism/PrismCage.hpp" 19 | #include "prism/common.hpp" 20 | #include "prism/local_operations/remesh_with_feature.hpp" 21 | #include "test_common.hpp" 22 | 23 | TEST_CASE("edge based bevel") { 24 | RowMatd V; 25 | RowMati F; 26 | igl::read_triangle_mesh( 27 | "/home/zhongshi/Workspace/libigl/tutorial/data/cube.off", V, F); 28 | prism::geo::init_geogram(); 29 | RowMatd VN; 30 | std::set omni_singu; 31 | 32 | bool good_normals = 33 | prism::cage_utils::most_visible_normals(V, F, VN, omni_singu); 34 | 35 | std::vector face_parent; 36 | RowMatd dsV, dsVN; 37 | RowMati dsF; 38 | prism::bevel_utils::edge_based_bevel(V, F, VN, RowMati(), dsV, dsF, dsVN, 39 | face_parent); 40 | prism::bevel_utils::verify_edge_bevel(dsV, dsF, dsVN, RowMati()); 41 | { 42 | auto file = H5Easy::File("temp.h5", H5Easy::File::Overwrite); 43 | H5Easy::dump(file, "V", dsV); 44 | H5Easy::dump(file, "F", dsF); 45 | H5Easy::dump(file, "VN", dsVN); 46 | H5Easy::dump(file, "fp", face_parent); 47 | } 48 | } -------------------------------------------------------------------------------- /python/cadconvert/bezier.py: -------------------------------------------------------------------------------- 1 | import fem_tabulator as feta 2 | import numpy as np 3 | import quad_curve_utils as qr 4 | import tqdm 5 | 6 | def eval_bc(verts, faces, bc_i, denom:int): 7 | vf = (verts[faces[bc_i[:,0]]]) 8 | return np.einsum('sed,se->sd', vf, bc_i[:,1:])/denom 9 | 10 | def bezier_fit_matrix(order : int, level : int) -> np.ndarray: 11 | std_x, std_y = qr.quad_tuple_gen(level).T 12 | bsv = qr.tp_sample_value(std_x/level, std_y/level, order=order) 13 | bsv = bsv.reshape(len(bsv),-1) 14 | return bsv 15 | 16 | def bezier_check_validity(mB,mT,F, quads, q2t, trim_types, quad_cp, order, valid_check, progress_bar = True): 17 | all_b, all_t = qr.top_and_bottom_sample(mB,mT, F, quads, q2t, trim_types, level=1) 18 | v4, f4 = qr.split_square(1) 19 | 20 | tup = feta.tuple_gen(order = order + 1, var_n=2) # elevated one order for quartic tetrahedra 21 | grids = np.einsum('fed,Ee->fEd', v4[f4], np.asarray(tup)) 22 | 23 | grid_ids = np.ravel_multi_index(grids.reshape(-1,2).T, dims = (order + 2, 24 | order + 2)).reshape(len(f4), -1) 25 | valid_quad = np.ones(len(quad_cp), dtype=bool) 26 | 27 | A13 = bezier_fit_matrix(order, order+1) 28 | if progress_bar: 29 | pbar = tqdm.tqdm(quad_cp, desc='Bezier Quads Checking validity') 30 | else: 31 | pbar = quad_cp 32 | for q,qcp in enumerate(pbar): 33 | lagr = A13@qcp 34 | for t,g in zip(f4, grid_ids): 35 | if not (valid_check(all_b[q][t], all_t[q][t], 36 | lagr[g], True)): 37 | valid_quad[q] = False 38 | break 39 | return valid_quad -------------------------------------------------------------------------------- /src/prism/predicates/tetrahedron_overlap.cpp: -------------------------------------------------------------------------------- 1 | #include "tetrahedron_overlap.hpp" 2 | 3 | #include 4 | 5 | #include "inside_octahedron.hpp" 6 | #include "inside_prism_tetra.hpp" 7 | #include "triangle_triangle_intersection.hpp" 8 | 9 | bool prism::predicates::tetrahedron_tetrahedron_overlap( 10 | const std::array& Atet, const std::array& Btet) { 11 | if (GEO::PCK::orient_3d(Atet[0].data(), Atet[1].data(), Atet[2].data(), 12 | Atet[3].data()) == 0) 13 | return false; 14 | if (GEO::PCK::orient_3d(Btet[0].data(), Btet[1].data(), Btet[2].data(), 15 | Btet[3].data()) == 0) 16 | return false; 17 | for (int i = 0; i < 4; i++) { 18 | if (prism::predicates::point_in_tetrahedron(Atet[i], Btet[0], Btet[1], 19 | Btet[2], Btet[3])) 20 | return true; 21 | 22 | if (prism::predicates::point_in_tetrahedron(Btet[i], Atet[0], Atet[1], 23 | Atet[2], Atet[3])) 24 | return true; 25 | } 26 | 27 | constexpr std::array, 6> edges{ 28 | {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}}; 29 | constexpr std::array, 4> faces{ 30 | {{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}}}; 31 | 32 | for (auto e : edges) { 33 | for (auto f : faces) { 34 | if (prism::predicates::segment_triangle_overlap( 35 | {Atet[e[0]], Atet[e[1]]}, {Btet[f[0]], Btet[f[1]], Btet[f[2]]})) 36 | return true; 37 | if (prism::predicates::segment_triangle_overlap( 38 | {Btet[e[0]], Btet[e[1]]}, {Atet[f[0]], Atet[f[1]], Atet[f[2]]})) 39 | return true; 40 | } 41 | } 42 | 43 | return false; 44 | } -------------------------------------------------------------------------------- /tests/cgal_qp.cpp: -------------------------------------------------------------------------------- 1 | // example: construct a quadratic program from data 2 | // the QP below is the first quadratic program example in the user manual 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE("first qp") { 10 | RowMatd N(12, 3); 11 | N << -0.9486833415780438, 5.0279891799382285e-06, 0.3162276353942456, 12 | -0.999991169948381, 6.681749257755522e-08, 0.004202383283774774, 13 | -0.9486750614923991, -8.799672176083576e-06, 0.31625247449618255, 14 | -0.9486750614923991, -8.799672176075144e-06, 0.31625247449618255, 15 | -0.9486934595912638, -9.103923427002401e-05, 0.3161972666558282, 16 | 2.4836718473564433e-05, 0.9999999994251878, -2.3081635029728415e-05, 17 | -0.9999804455872972, -0.00625352337133846, -4.3456585218043606e-05, -0.0, 18 | 1.0, 0.0, 0.0, 1.0, 0.0, -0.0, 1.0, 0.0, 1.0, 0.0, -0.0, 1.0, 0.0, 0.0; 19 | std::vector nb(12); 20 | for (int i = 0; i < 12; i++) nb[i] = i; 21 | auto x = prism::cgal::qp_normal(N, nb); 22 | CAPTURE(x); 23 | REQUIRE(x.norm() == 0); 24 | } 25 | 26 | TEST_CASE("compare qp") { 27 | RowMatd N(6, 3); 28 | std::vector nb; 29 | for (int i = 0; i < N.rows(); i++) nb.push_back(i); 30 | for (int i = 0; i < 10; i++) { 31 | N.setRandom(); 32 | 33 | auto x = prism::cgal::qp_normal(N, nb); 34 | auto x2 = prism::osqp_normal(N, nb); 35 | // auto x2 = prism::qp_normal_igl(N, nb); 36 | CAPTURE(x); 37 | CAPTURE(x2); 38 | REQUIRE_EQ((x).norm(), doctest::Approx(x2.norm())); 39 | if (x.norm() == 0) continue; 40 | REQUIRE(x.norm() == doctest::Approx(1.)); 41 | for (int i = 0; i < N.rows(); i++) { 42 | REQUIRE_GT(N.row(i).dot(x), 0); 43 | } 44 | } 45 | } 46 | 47 | TEST_CASE("osqp") {} -------------------------------------------------------------------------------- /src/python/prism.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | //////////////////////////////////////////////////////////////////////////////// 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace py = pybind11; 16 | 17 | using namespace pybind11::literals; 18 | 19 | 20 | void python_export_prism(py::module &m) 21 | { 22 | py::class_ cage(m, "PrismCage"); 23 | cage.def(py::init()) 24 | .def_property_readonly("base", [](const PrismCage &cage) { 25 | RowMatd mb; 26 | vec2eigen(cage.base, mb); 27 | return mb; 28 | }) 29 | .def_property_readonly("mid", [](const PrismCage &cage) { 30 | RowMatd mb; 31 | vec2eigen(cage.mid, mb); 32 | return mb; 33 | }) 34 | .def_property_readonly("top", [](const PrismCage &cage) { 35 | RowMatd mb; 36 | vec2eigen(cage.top, mb); 37 | return mb; 38 | }) 39 | .def_property_readonly("F", [](const PrismCage &cage) { 40 | RowMati mb; 41 | vec2eigen(cage.F, mb); 42 | return mb; }) 43 | .def_property_readonly("refV", [](const PrismCage &cage) { return cage.ref.V; }) 44 | .def_property_readonly("refF", [](const PrismCage &cage) { return cage.ref.F; }) 45 | .def("transfer", [](const PrismCage &cage, const RowMatd &pxV, const RowMati &pxF, const RowMatd &queryP) { 46 | Eigen::VectorXi queryF; 47 | RowMatd queryUV; 48 | prism::correspond_bc(cage, pxV, pxF, queryP, queryF, queryUV); 49 | return std::tuple(queryF, queryUV); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /src/prism/geogram/AABB.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_CGAL_AABB_HPP 2 | #define PRISM_CGAL_AABB_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace GEO { 9 | class MeshFacetsAABB; 10 | class Mesh; 11 | } // namespace GEO 12 | namespace prism{ 13 | struct Hit; 14 | } 15 | namespace prism::geogram { 16 | 17 | struct AABB { 18 | // if `enabled = False`, non of the tests are active, 19 | // this allows for a non-intrusive implementation for disabling AABB 20 | AABB(const RowMatd &V, const RowMati &F, bool enabled = true); 21 | bool intersects_triangle(const std::array &P, 22 | bool use_freeze = false) const; 23 | // if there are multiple intersection, the function will return 24 | std::optional segment_query(const Vec3d &start, 25 | const Vec3d &end) const; 26 | bool segment_query(const Vec3d &start, const Vec3d &end, int &face_id, 27 | Vec3d &finalpoint) const; 28 | bool segment_hit(const Vec3d &start, const Vec3d &end, prism::Hit &hit) const; 29 | double ray_length(const Vec3d &start, const Vec3d &dir, double max_step, 30 | int ignore_v) const; 31 | 32 | // test the numerical separation of a mesh. 33 | // For each point, construct a bounding box with edge=2*tol. 34 | // Then for each intersected primitive (with the box), check if it is 35 | // neighbor. 36 | bool numerical_self_intersection(double tol) const; 37 | bool self_intersections(std::vector>& pairs); 38 | 39 | std::shared_ptr geo_tree_ptr_; 40 | std::shared_ptr geo_polyhedron_ptr_; 41 | std::vector geo_vertex_ind; 42 | std::vector geo_face_ind; 43 | int num_freeze = 0; 44 | const bool enabled = true; 45 | }; 46 | 47 | } // namespace prism::geogram 48 | 49 | #endif -------------------------------------------------------------------------------- /src/prism/intersections.cpp: -------------------------------------------------------------------------------- 1 | #include "intersections.hpp" 2 | 3 | #include 4 | extern "C" { 5 | #include "igl/raytri.c" 6 | } 7 | #include "cgal/triangle_triangle_intersection.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | std::optional 13 | prism::intersections::segment_triangle_intersection_inexact( 14 | const std::array &seg, const std::array &tri) { 15 | assert(false && "This function is not correct. Temporary disabled;"); 16 | 17 | return {}; 18 | double t, u, v; 19 | auto [v0, v1, v2] = tri; 20 | auto s_d = seg[0]; 21 | Vec3d dir = seg[1] - seg[0]; 22 | intersect_triangle1(s_d.data(), dir.data(), v0.data(), v1.data(), v2.data(), 23 | &t, &u, &v); 24 | if (t > 1 || t < 0) 25 | return {}; 26 | else 27 | return s_d * (1 - t) + seg[1] * t; 28 | } 29 | 30 | bool prism::intersections::segment_triangle_hit(const std::array &seg, 31 | const std::array &tri, 32 | prism::Hit &hit) { 33 | hit.u = -1; 34 | hit.v = -1; 35 | hit.t = -1; 36 | auto [v0, v1, v2] = tri; 37 | auto s_d = seg[0]; 38 | Vec3d dir = seg[1] - seg[0]; 39 | auto flag = intersect_triangle1(s_d.data(), dir.data(), v0.data(), v1.data(), 40 | v2.data(), &hit.t, &hit.u, &hit.v); 41 | return (flag == 1 && hit.t >= 0); 42 | } 43 | 44 | bool prism::intersections::segment_triangle_hit_cgal( 45 | const std::array &seg, const std::array &tri, 46 | prism::Hit &hit) { 47 | auto inter = prism::cgal::segment_triangle_intersection(seg, tri); 48 | if (!inter) return false; 49 | Vec3d bc; 50 | igl::barycentric_coordinates(inter.value(), tri[0], tri[1], tri[2], bc); 51 | hit.u = bc[1]; 52 | hit.v = bc[2]; 53 | return true; 54 | } -------------------------------------------------------------------------------- /src/prism/local_operations/mesh_coloring.cpp: -------------------------------------------------------------------------------- 1 | #include "mesh_coloring.hpp" 2 | #include 3 | #include 4 | void prism::local::vertex_coloring(const RowMati& F, 5 | std::vector>& groups) { 6 | // from github hankstag/progressive_embedding 7 | int n = F.maxCoeff() + 1; 8 | // meanless to color an empty mesh 9 | assert(n > 0); 10 | 11 | std::vector> N; // Adjacency list 12 | igl::adjacency_list(F, N); 13 | // initialize color for each vertex 14 | Eigen::VectorXd C; 15 | C.setConstant(n, -1); 16 | 17 | size_t vm = 0; // max valence 18 | for (auto l : N) vm = std::max(l.size(), vm); 19 | 20 | // assign color 0 for vertex 0 21 | C[0] = 0; 22 | // for every 1 ring, mark the color that has been used 23 | // in theory, it need (vm+1) colors at most 24 | Eigen::VectorXi G; 25 | G.setZero(vm + 1); 26 | int nc = 1; // # of color used 27 | for (int i = 1; i < n; i++) { 28 | for (int k = 0; k < N[i].size(); k++) { 29 | if (C[N[i][k]] != -1) G(C[N[i][k]]) = 1; 30 | } 31 | for (int j = 0; j < G.rows(); j++) { 32 | if (G(j) == 0) { 33 | C[i] = j; 34 | break; 35 | } 36 | } 37 | G.setZero(); 38 | } 39 | 40 | groups.resize(C.maxCoeff() + 1); 41 | for (auto id = 0; id < C.rows(); id++) { 42 | groups[C[id]].push_back(id); 43 | } 44 | } 45 | 46 | void prism::local::red_green_coloring(const RowMati& F, const RowMati& FF, 47 | Eigen::VectorXi& colors) { 48 | int sum = 0; 49 | while (sum != colors.sum()) { 50 | sum = colors.sum(); 51 | for (int i = 0; i < F.rows(); i++) { 52 | if (colors[i] == 2) continue; // red 53 | int count = 0; 54 | for (int j = 0; j < 3; j++) { 55 | if (FF(i, j) != -1 && colors[FF(i, j)] == 2) count++; 56 | } 57 | if (count > 1) // red 58 | colors[i] = 2; 59 | if (count == 1) colors[i] = 1; // green 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/prism/cgal/tetrahedron_tetrahedron_intersection.cpp: -------------------------------------------------------------------------------- 1 | #include "tetrahedron_tetrahedron_intersection.hpp" 2 | #include 3 | 4 | namespace prism::cgal { 5 | bool tetrahedron_tetrahedron_intersection( 6 | const CGAL::Epick::Tetrahedron_3& Atet, 7 | const CGAL::Epick::Tetrahedron_3& Btet) { 8 | using K = CGAL::Epick; 9 | if (Atet.is_degenerate() || Btet.is_degenerate()) return false; 10 | if (!CGAL::do_overlap(Atet.bbox(), Btet.bbox())) return false; 11 | 12 | for (int i = 0; i < 4; i++) { 13 | if (Btet.bounded_side(Atet.vertex(i)) != 14 | CGAL::Bounded_side::ON_UNBOUNDED_SIDE) { // on or in 15 | return true; 16 | } 17 | if (Atet.bounded_side(Btet.vertex(i)) != 18 | CGAL::Bounded_side::ON_UNBOUNDED_SIDE) { // on or in 19 | return true; 20 | } 21 | } 22 | 23 | if (CGAL::do_intersect(K::Triangle_3(Atet.vertex(0), Atet.vertex(1), Atet.vertex(2)), Btet)) 24 | return true; 25 | if (CGAL::do_intersect(K::Triangle_3(Atet.vertex(0), Atet.vertex(1), Atet.vertex(3)), Btet)) 26 | return true; 27 | if (CGAL::do_intersect(K::Triangle_3(Atet.vertex(0), Atet.vertex(2), Atet.vertex(3)), Btet)) 28 | return true; 29 | if (CGAL::do_intersect(K::Triangle_3(Atet.vertex(1), Atet.vertex(2), Atet.vertex(3)), Btet)) 30 | return true; 31 | 32 | return false; 33 | } 34 | 35 | bool tetrahedron_tetrahedron_intersection(const std::array& tetA, 36 | const std::array& tetB) { 37 | typedef ::CGAL::Exact_predicates_inexact_constructions_kernel K; 38 | std::array A_points; 39 | std::array B_points; 40 | 41 | for (int i = 0; i < 4; i++) { 42 | A_points[i] = K::Point_3(tetA[i][0], tetA[i][1], tetA[i][2]); 43 | B_points[i] = K::Point_3(tetB[i][0], tetB[i][1], tetB[i][2]); 44 | } 45 | 46 | K::Tetrahedron_3 Atet(A_points[0], A_points[1], A_points[2], A_points[3]); 47 | K::Tetrahedron_3 Btet(B_points[0], B_points[1], B_points[2], B_points[3]); 48 | return tetrahedron_tetrahedron_intersection(Atet, Btet); 49 | 50 | // Now, all three vertices are outside. 51 | } 52 | } // namespace prism::cgal 53 | -------------------------------------------------------------------------------- /python/curve/gmsh_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import torch as th 3 | import numpy as np 4 | import gmsh 5 | import sys 6 | import meshio 7 | import pdb 8 | import os 9 | folder = os.path.dirname(os.path.abspath(__file__)) 10 | 11 | 12 | def mips_p4(filename, rule): 13 | ''' 14 | nodes35: (num_tets, 35, 3) 15 | ''' 16 | m = meshio.read(filename) 17 | V, T = m.points, m.cells[0].data 18 | with np.load(os.path.join(folder, f'data/p4_q{rule}_dxyz.npz')) as npl: 19 | dxyz, weights, pts = map( 20 | lambda x: th.from_numpy(npl[x]), ['dxyz', 'weights', 'points']) 21 | print('Total Shape', T.shape) 22 | quadpoints = dxyz.shape[1] 23 | split_num = len(T)//5000 + 1 24 | for T0 in np.array_split(T, split_num): 25 | print('>> current shape', T0.shape) 26 | nodes35 = th.from_numpy(V[T0]) 27 | jacs = (dxyz@(nodes35.unsqueeze(1)) 28 | ).transpose(1, 2).reshape(-1, 3, 3) 29 | dets = th.sum(jacs[:, :, 0] * 30 | th.cross(jacs[:, :, 1], jacs[:, :, 2]), dim=-1) 31 | frob2 = th.sum(jacs.reshape(-1, 9)**2, dim=-1) 32 | mipses = (frob2/dets**(2/3)).reshape(len(nodes35), -1) 33 | print(f'dets {dets.min()}, {dets.max()}') 34 | if (dets.min() < 0): 35 | dets = dets.reshape(len(nodes35), quadpoints) 36 | print(f'flip at', np.unravel_index(dets.argmin(), dets.shape)) 37 | continue 38 | print(f'mipses {mipses.min()}, {mipses.max()}') 39 | print((mipses@weights).mean()) 40 | 41 | 42 | def gmsh_check(filename: str): 43 | gmsh.initialize() 44 | gmsh.option.setNumber("General.Terminal", 1) 45 | gmsh.open(filename) 46 | gmsh.plugin.setNumber('AnalyseMeshQuality', 'Recompute', 1) 47 | gmsh.plugin.setNumber('AnalyseMeshQuality', 'JacobianDeterminant', 1) 48 | gmsh.plugin.run('AnalyseMeshQuality') 49 | gmsh.finalize() 50 | 51 | 52 | def gmsh_optimize(filename: str): 53 | gmsh.initialize() 54 | gmsh.option.setNumber("General.Terminal", 1) 55 | gmsh.open(filename) 56 | m = gmsh.model.mesh 57 | m.optimize("HighOrder") 58 | 59 | 60 | if __name__ == '__main__': 61 | import fire 62 | fire.Fire() 63 | -------------------------------------------------------------------------------- /python/cumin_datagen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | 5 | 6 | import h5py 7 | import sys 8 | from curve import fem_generator 9 | 10 | import igl 11 | import numpy as np 12 | import itertools 13 | from pathlib import Path 14 | 15 | Path('../python/curve/data').mkdir(parents=True, exist_ok=True) 16 | 17 | codec_to_n = lambda co: [k for i,j in enumerate(co) for k in [i]*j] 18 | 19 | level=3 20 | usV, usF = igl.upsample( 21 | np.array([[0, 0.], [1, 0], [0, 1]]), np.array([[0, 1, 2]]), level) 22 | u, v = usV[:, :1], usV[:, 1:] 23 | 24 | 25 | 26 | for order in range(1,5): 27 | tri_o = fem_generator.basis_info(order=order, nsd=2, derivative=True) 28 | basis = tri_o['basis'] 29 | tri_o1 = fem_generator.basis_info(order=order+1, nsd=2, derivative=False) 30 | cod_o1 = tri_o1['codec'] 31 | 32 | values_at_nodes = np.hstack(basis(u, v)).T 33 | sample_deri = np.array([[f(*p) for p in usV] for f in tri_o['basis_d']]) 34 | _, cod_o4_u, cod_o4_v = np.array(tri_o['codec']).T/(order) 35 | _, cod_o5_u, cod_o5_v = np.array(cod_o1).T/(order+1) 36 | assert(cod_o5_u.max() == 1) 37 | with h5py.File(f'../python/curve/data/tri_o{order}_lv{level}.h5','w') as f: 38 | f['bern'] = values_at_nodes 39 | f['bern2elevlag'] = np.vstack(basis(cod_o5_u,cod_o5_v)) 40 | f['bern2lag'] = np.vstack(basis(cod_o4_u,cod_o4_v)) 41 | f['deri_u'] = sample_deri[0] 42 | f['deri_v'] = sample_deri[1] 43 | 44 | 45 | N=5 46 | sample_pts = np.asarray(list(filter(lambda x: sum(x) == N, itertools.product(range(N+1), repeat=4))))[:,1:]/N 47 | 48 | 49 | for order in range(3,10): 50 | tet_o = fem_generator.basis_info(order=order, nsd=3, derivative=False) 51 | bern_from_lag = tet_o['l2b'] 52 | with h5py.File(f'../python/curve/data/tetra_o{order}_l2b.h5','w') as f: 53 | f['l2b'] = bern_from_lag 54 | 55 | for order in range(2,6): 56 | tet_o = fem_generator.basis_info(order=order+1, nsd=3, derivative=True) 57 | bern_from_lag = tet_o['l2b'] 58 | with h5py.File(f'../python/curve/data/p{order+1}_quniform{N}_dxyz.h5','w') as f: 59 | f['dxyz'] = np.array([[f(*p) for p in sample_pts] for f in tet_o['basis_d']])@bern_from_lag 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Find Python 3 | ################################################################################ 4 | 5 | if(NOT ${PYTHONINTERP_FOUND}) 6 | execute_process( 7 | COMMAND 8 | python -c "import sys; sys.stdout.write(sys.version)" 9 | OUTPUT_VARIABLE PYTHON_VERSION) 10 | 11 | if(NOT PYTHON_VERSION) 12 | message(FATAL_ERROR "Unable to run python") 13 | endif() 14 | 15 | string(REGEX MATCH "^3\.*" IS_PYTHON3 ${PYTHON_VERSION}) 16 | 17 | if(NOT IS_PYTHON3) 18 | message(FATAL_ERROR "Unable to find python 3") 19 | else() 20 | set(PYTHON_EXECUTABLE "python") 21 | endif() 22 | endif() 23 | 24 | ################################################################################ 25 | # Pybind11 26 | ################################################################################ 27 | 28 | if(UNIX) 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") 30 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") 31 | if(NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG) 32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -flto") 33 | endif() 34 | endif() 35 | 36 | 37 | pybind11_add_module(prism_python 38 | module.cpp 39 | prism.cpp 40 | spatial.cpp 41 | curve.cpp 42 | ) 43 | 44 | add_library(prism::python ALIAS prism_python) 45 | 46 | target_link_libraries(prism_python 47 | PUBLIC igl::core prism_library 48 | ) 49 | target_link_libraries(prism_python 50 | PUBLIC cumin_library 51 | ) 52 | 53 | # Generate position independent code 54 | set_target_properties(prism_python PROPERTIES POSITION_INDEPENDENT_CODE ON) 55 | 56 | # Output location 57 | string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE) 58 | if(${U_CMAKE_BUILD_TYPE} MATCHES RELEASE) 59 | set_target_properties(prism_python PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python) 60 | set_target_properties(prism_python PROPERTIES OUTPUT_NAME "prism") 61 | else() 62 | set_target_properties(prism_python PROPERTIES OUTPUT_NAME "prism") 63 | set_target_properties(prism_python PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/python/debug) 64 | endif() 65 | set_target_properties(prism_python PROPERTIES CXX_VISIBILITY_PRESET hidden) 66 | -------------------------------------------------------------------------------- /src/prism/predicates/inside_prism_tetra.cpp: -------------------------------------------------------------------------------- 1 | #include "inside_prism_tetra.hpp" 2 | 3 | #include 4 | 5 | #include "prism/predicates/triangle_triangle_intersection.hpp" 6 | namespace prism::predicates { 7 | 8 | bool point_in_tetrahedron(const Vec3d& point, const Vec3d& T0, const Vec3d& T1, 9 | const Vec3d& T2, const Vec3d& T3) { 10 | auto orient3D = [](const auto& a, const auto& b, const auto& c, 11 | const auto& d) { return GEO::PCK::orient_3d(a, b, c, d); }; 12 | return orient3D(T0.data(), T3.data(), T1.data(), point.data()) >= 0 && 13 | orient3D(T1.data(), T3.data(), T2.data(), point.data()) >= 0 && 14 | orient3D(T0.data(), T1.data(), T2.data(), point.data()) >= 0 && 15 | orient3D(T0.data(), T2.data(), T3.data(), point.data()) >= 0; 16 | } 17 | 18 | bool point_in_prism(const Vec3d& point, bool tetra_split_AB, 19 | const std::array& verts) { 20 | auto tets = tetra_split_AB ? TETRA_SPLIT_A : TETRA_SPLIT_B; 21 | auto sing = verts[0] == verts[3]; 22 | for (int i = sing?1:0; i < 3; i++) 23 | if (point_in_tetrahedron(point, verts[tets[i][0]], verts[tets[i][1]], 24 | verts[tets[i][2]], verts[tets[i][3]])) 25 | return true; 26 | return false; 27 | } 28 | 29 | bool triangle_intersects_prism(const std::array& tri_pts, 30 | bool tetra_split_AB, 31 | const std::array& verts) { 32 | auto bnd = tetra_split_AB ? PRISM_BOUNDARY_A : PRISM_BOUNDARY_B; 33 | 34 | for (auto& t : tri_pts) 35 | if (point_in_prism(t, tetra_split_AB, verts)) return true; 36 | auto sing = verts[0] == verts[3]; 37 | if (sing) { // the following is very specific to the pre-defined PRISM_BOUNDARY, careful when reset. 38 | bnd[1] = {0, 4, 5}; 39 | bnd[2][0] = -1; 40 | bnd[5][0] = -1; 41 | } 42 | for (auto& t : bnd) { 43 | if (t[0] == -1)continue; 44 | std::array boundary_triangle{verts[t[0]], verts[t[1]], 45 | verts[t[2]]}; 46 | if (prism::predicates::triangle_triangle_overlap(boundary_triangle, 47 | tri_pts)) 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | } // namespace prism::predicates -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(prism_tests) 3 | 4 | list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake) 5 | 6 | 7 | ### Download unit test framework 8 | if (NOT TARGET doctest) 9 | prism_download_doctest() 10 | add_library(doctest INTERFACE) 11 | target_include_directories(doctest SYSTEM INTERFACE ${PRISM_EXTERNAL}/doctest/doctest/) 12 | endif() 13 | 14 | set(CMAKE_MODULE_PATH "${PRISM_EXTERNAL}/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH}) 15 | 16 | 17 | 18 | find_package(MPFR) 19 | IF(NOT ${MPFR_FOUND}) 20 | MESSAGE(FATAL_ERROR "Cannot find MPFR") 21 | ENDIF() 22 | 23 | MESSAGE(STATUS "Found MPFR ${MPFR_INCLUDES}") 24 | 25 | # Create test executable 26 | add_executable(prism_tests test_main.cpp) 27 | target_sources(prism_tests PRIVATE 28 | cgal_AABB.cpp 29 | cgal_qp.cpp 30 | spatial_hash.cpp 31 | predicates.cpp 32 | phong.cpp 33 | numerical_self_intersection.cpp) 34 | target_sources(prism_tests PRIVATE 35 | bevel_init.cpp 36 | curved_tetra_mips.cpp 37 | surface_stitch.cpp 38 | zig_shell_collapse.cpp 39 | curve_fitting.cpp 40 | tangential_smooth_bin.cpp 41 | remesh_shell_bin.cpp) 42 | 43 | target_link_libraries(prism_tests PUBLIC doctest cumin_library prism::prism json) 44 | #spdlog::spdlog igl::core highfive geogram mitsuba_autodiff igl::cgal) 45 | 46 | target_compile_features(prism_tests PUBLIC cxx_std_17) 47 | target_include_directories(prism_tests PUBLIC ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/../src/) 48 | target_include_directories(prism_tests PUBLIC ${PRISM_EXTERNAL}/rational/) 49 | # target_include_directories(prism_tests PUBLIC ${MPFR_INCLUDES}) 50 | # target_include_directories(prism_tests PUBLIC ${PRISM_EXTERNAL}/libigl/external/eigen/unsupported/test/mpreal) 51 | # target_link_libraries(prism_tests PUBLIC ${MPFR_LIBRARIES}) 52 | 53 | target_compile_options(prism_tests PUBLIC "-Wno-deprecated") 54 | 55 | if (ENABLE_ASAN) 56 | message(STATUS "[prism] enabled asan ubsan for test") 57 | target_compile_options(prism_tests PUBLIC "-fsanitize=undefined") 58 | target_compile_options(prism_tests PUBLIC "-fsanitize=address") 59 | target_link_options(prism_tests PUBLIC "-fsanitize=address") 60 | endif() 61 | -------------------------------------------------------------------------------- /src/prism/local_operations/section_remesh.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_LOCAL_SECTION_REMESH_HPP 2 | #define PRISM_LOCAL_SECTION_REMESH_HPP 3 | 4 | #include "../common.hpp" 5 | 6 | struct PrismCage; 7 | namespace prism::geogram { 8 | struct AABB; 9 | } 10 | namespace prism::section { 11 | 12 | struct RemeshOptions { 13 | std::function sizing_field; 14 | int iteration_number = 3; 15 | int smooth_per_iteration = 5; 16 | double distortion_bound = 0.1; 17 | bool parallel = true; 18 | double collapse_quality_threshold = 30; 19 | bool collapse_improve_quality = true; 20 | bool split_improve_quality = true; 21 | RemeshOptions() = default; 22 | RemeshOptions(int v_num, double edge_len) { 23 | sizing_field = [edge_len](const Vec3d&) { return edge_len; }; 24 | } 25 | }; 26 | int wildcollapse_pass(const PrismCage& pc, const prism::geogram::AABB& base_tree, 27 | const prism::geogram::AABB& top_tree, RemeshOptions& option, 28 | std::vector& V, std::vector& F, 29 | std::vector>& track_to_prism, 30 | std::vector& target_adjustment); 31 | 32 | bool project_to_ref_mesh(const PrismCage& pc, 33 | const std::vector>& track_ref, 34 | const std::vector& tris, 35 | const Vec3d& point_value, Vec3d& point_on_ref); 36 | void wildsplit_pass(const PrismCage& pc, const prism::geogram::AABB& base_tree, 37 | const prism::geogram::AABB& top_tree, RemeshOptions& option, 38 | std::vector& V, std::vector& F, 39 | std::vector>& track_ref, 40 | std::vector& target_adjustment); 41 | 42 | void wildflip_pass(const PrismCage& pc, const prism::geogram::AABB& base_tree, 43 | const prism::geogram::AABB& top_tree, RemeshOptions& option, 44 | std::vector& V, std::vector& F, 45 | std::vector>& track_ref); 46 | void localsmooth_pass(const PrismCage& pc, const prism::geogram::AABB& base_tree, 47 | const prism::geogram::AABB& top_tree, RemeshOptions& option, 48 | std::vector& V, std::vector& F, 49 | std::vector>& track_ref); 50 | } // namespace prism::section 51 | 52 | #endif -------------------------------------------------------------------------------- /src/prism/predicates/inside_octahedron.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_PREDICATES_INSIDE_OCTAHEDRON_HPP 2 | #define PRISM_PREDICATES_INSIDE_OCTAHEDRON_HPP 3 | 4 | #include 5 | 6 | #include "../common.hpp" 7 | namespace prism { 8 | bool inside_convex_octahedron(const std::array& base, 9 | const std::array& top, const Vec3d&); 10 | 11 | bool octa_convexity(const std::array& base, 12 | const std::array& top, 13 | const std::array& oct_type); 14 | 15 | void determine_convex_octahedron(const std::array& base, 16 | const std::array& top, 17 | std::array& oct_type, 18 | bool degenerate = false); 19 | 20 | // a (conservative test) of whether a triangle intersects a prism. 21 | // (1) any tri-vert is contained inside 22 | // otherwise, all vertices are outside 23 | // (2) find out if triangle intersects with prism-boundary faces. 24 | // (2') prismatic pillar intersects the triangle, or triangle edge intersects 25 | // prismatic face. 26 | bool triangle_intersect_octahedron(const std::array& base, 27 | const std::array& top, 28 | const std::array& oct_type, 29 | const std::array& tri, 30 | bool degenerate); 31 | 32 | // similar to the previous, but ignore singularity. 33 | // Note: this is actually triangle vs. pyramid 34 | bool singularless_triangle_intersect_octahedron( 35 | const std::array& base, const std::array& top, 36 | const std::array& oct_type, const std::array& tri); 37 | 38 | // intersection predicate, ignoring the first point 39 | bool pointless_triangle_intersect_octahedron( 40 | const std::array& base, const std::array& top, 41 | const std::array& oct_type, const std::array& tri); 42 | 43 | bool pointless_triangle_intersect_tripletetra( 44 | const std::array& base, const std::array& top, 45 | bool oct_type, const std::array& tri); 46 | 47 | bool triangle_intersect_tripletetra( 48 | const std::array& base, const std::array& top, 49 | bool oct_type, const std::array& tri, bool degenerate); 50 | } // namespace prism 51 | #endif -------------------------------------------------------------------------------- /src/prism/local_operations/local_mesh_edit.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_LOCAL_OPERATIONS_HPP 2 | #define PRISM_LOCAL_OPERATIONS_HPP 3 | 4 | #include 5 | #include "../common.hpp" 6 | #include "triangle_tuple.h" 7 | 8 | namespace prism { 9 | bool edge_flip(std::vector& F, std::vector& FF, 10 | std::vector& FFi, int f0, int e0); 11 | bool edge_split(int ux, std::vector& F, std::vector& FF_vec, 12 | std::vector& FFi_vec, int f0, int e0); 13 | 14 | // one ring of emanating edges from the vertex F[f0][e0] 15 | // by default, this gives *clockwise* tuples, with F[f][e] == v0 16 | // if switch, this will gives cc tuples, with F[f][e+1] == v0 17 | template 18 | inline bool get_star_edges(const DerivedF& F, const DerivedF& FF, 19 | const DerivedF& FFi, int f0, int e0, 20 | std::vector>& neighbor, 21 | bool clockwise = true) { 22 | auto v_center = igl::triangle_tuple_get_vert(f0, e0, true, F, FF, FFi); 23 | 24 | if (!clockwise) { 25 | e0 = (e0 + 2) % 3; 26 | } 27 | auto v_start = igl::triangle_tuple_get_vert(f0, e0, !clockwise, F, FF, FFi); 28 | int cur_v = -1; 29 | bool interior = true; 30 | while (cur_v != v_start) { 31 | if (!igl::triangle_tuple_next_in_one_ring(f0, e0, clockwise, F, FF, FFi)) 32 | interior = false; 33 | cur_v = igl::triangle_tuple_get_vert(f0, e0, !clockwise, F, FF, FFi); 34 | neighbor.emplace_back(f0, e0); 35 | } 36 | return interior; 37 | } 38 | 39 | bool edge_collapse(std::vector& F, std::vector& FF, 40 | std::vector& FFi, int f0, int e0); 41 | } // namespace prism 42 | 43 | namespace prism { 44 | bool edge_split(std::vector& V0, std::vector& F0, 45 | std::vector& FF, std::vector& FFi, 46 | std::vector& E, // not v0, v1 but f,e 47 | std::vector& EMAP, // Fx[0,1,2] -> E 48 | int ue); 49 | 50 | bool edge_collapse(std::vector& V, std::vector& F, 51 | std::vector& FF, std::vector& FFi, 52 | std::vector& E, std::vector& EMAP, 53 | int ue); 54 | 55 | bool edge_flip(std::vector& V, std::vector& F, 56 | std::vector& FF, std::vector& FFi, 57 | std::vector& E, std::vector& EMAP, 58 | int ue); 59 | } // namespace prism 60 | #endif -------------------------------------------------------------------------------- /src/prism/osqp/osqp_normal.cpp: -------------------------------------------------------------------------------- 1 | #include "osqp_normal.hpp" 2 | namespace osqp { 3 | #include 4 | }; 5 | #include 6 | 7 | Vec3d prism::osqp_normal(const RowMatd &N, const std::vector &nb){ 8 | // Load problem data 9 | using osqp::c_float, osqp::c_int, osqp::OSQPWorkspace, osqp::OSQPSettings; 10 | using osqp::OSQPSolution, osqp::OSQPTimer, osqp::OSQPData; 11 | c_float P_x[3] = {1.0, 1.0, 1.0}; 12 | c_int P_nnz = 3; 13 | c_int P_i[3] = { 14 | 0, 15 | 1, 16 | 2, 17 | }; 18 | c_int P_p[4] = { 19 | 0, 20 | 1, 21 | 2, 22 | 3, 23 | }; 24 | 25 | c_float q[3] = {0.0, 0.0, 0.}; 26 | 27 | c_int n = 3; 28 | c_int m = nb.size(); 29 | c_int A_nnz = m * n; 30 | std::vector A_x(m * n); 31 | for (int i = 0; i < m; i++) 32 | for (int j = 0; j < n; j++) A_x[j * m + i] = N(nb[i], j); 33 | std::vector l(m, 1.); 34 | std::vector u(m, std::numeric_limits::infinity()); 35 | std::vector A_i(m * n), A_p(n + 1); 36 | for (int i = 0; i < 3; i++) 37 | for (int j = 0; j < m; j++) { 38 | A_i[i * m + j] = j; 39 | } 40 | for (int i = 0; i <= n; i++) A_p[i] = i * m; 41 | 42 | // Exitflag 43 | c_int exitflag = 0; 44 | Vec3d res(0, 0, 0); 45 | { 46 | // Workspace structures 47 | OSQPWorkspace *work; 48 | OSQPSettings *settings = (OSQPSettings *)c_malloc(sizeof(OSQPSettings)); 49 | OSQPData *data = (OSQPData *)c_malloc(sizeof(OSQPData)); 50 | 51 | // Populate data 52 | if (data) { 53 | data->n = n; 54 | data->m = m; 55 | data->P = ::osqp::csc_matrix(data->n, data->n, P_nnz, P_x, P_i, P_p); 56 | data->q = q; 57 | data->A = ::osqp::csc_matrix(data->m, data->n, A_nnz, A_x.data(), A_i.data(), 58 | A_p.data()); 59 | data->l = l.data(); 60 | data->u = u.data(); 61 | } 62 | 63 | // Define solver settings as default 64 | if (settings) ::osqp::osqp_set_default_settings(settings); 65 | settings->verbose = false; 66 | 67 | // Setup workspace 68 | exitflag = ::osqp::osqp_setup(&work, data, settings); 69 | 70 | // Solve Problem 71 | ::osqp::osqp_solve(work); 72 | if (work->info->status_val == 1) { 73 | auto sol = work->solution->x; 74 | res = Vec3d(sol[0], sol[1], sol[2]); 75 | } 76 | // Clean workspace 77 | ::osqp::osqp_cleanup(work); 78 | if (data) { 79 | if (data->A) c_free(data->A); 80 | if (data->P) c_free(data->P); 81 | c_free(data); 82 | } 83 | if (settings) c_free(settings); 84 | } 85 | return res.normalized(); 86 | }; -------------------------------------------------------------------------------- /src/prism/spatial-hash/AABB_hash.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_SPATIAL_HASH_AABB_HASH 2 | #define PRISM_SPATIAL_HASH_AABB_HASH 3 | #include 4 | #include 5 | #include 6 | /// @brief An entry into the hash grid as a (key, value) pair. 7 | 8 | #include "../common.hpp" 9 | namespace GEO { 10 | class Box; 11 | }; 12 | namespace prism { 13 | struct HashItem { 14 | int key; /// @brief The key of the item. 15 | int id; /// @brief The value of the item. 16 | std::shared_ptr 17 | aabb; /// @brief The axis-aligned bounding box of the element 18 | HashItem(int key, int id) : key(key), id(id) {} 19 | 20 | /// @brief Compare HashItems by their keys for sorting. 21 | bool operator<(const HashItem &other) const; 22 | }; 23 | 24 | using HashMap = 25 | std::map, std::shared_ptr>>; 26 | using HashPtr = 27 | std::pair>, std::list::iterator>; 28 | 29 | 30 | // The following does not store AABB or any geometry at all, only indices. 31 | struct HashGrid { 32 | HashGrid(const Vec3d &lower, const Vec3d &upper, double cell) 33 | : m_domain_min(lower), m_domain_max(upper), m_cell_size(cell) { 34 | m_grid_size = int(std::ceil((upper - lower).maxCoeff() / m_cell_size)); 35 | }; 36 | HashGrid(const std::vector &V, const std::vector &F, 37 | bool filled = true); 38 | HashGrid(const RowMatd &V, const RowMati &F, bool filled = true); 39 | void insert_triangles(const RowMatd &V, const RowMati &F, 40 | const std::vector &fid); 41 | void insert_triangles(const std::vector &V, 42 | const std::vector &F, 43 | const std::vector &fid); 44 | std::vector> self_candidates() const; 45 | void query(const Vec3d &lower, const Vec3d &upper, std::set &) const; 46 | void add_element(const Vec3d &lower, const Vec3d &upper, const int index); 47 | void remove_element(const int index); 48 | void bound_convert(const Vec3d &, Eigen::Array &) const; 49 | bool clear() {m_face_items.clear(); face_stores.clear();return true;} 50 | 51 | // reorder and update after edge collapse 52 | void update_after_collapse(); 53 | // spatial partition parameters 54 | Vec3d m_domain_min; 55 | Vec3d m_domain_max; 56 | double m_cell_size; 57 | size_t m_grid_size; 58 | 59 | // key is (integral) spatial coordinate 60 | // val points to list of faces (abstract, w/o geometry). 61 | HashMap m_face_items; 62 | std::vector> face_stores; // facilitate element removal 63 | }; 64 | } // namespace prism 65 | #endif -------------------------------------------------------------------------------- /src/prism/energy/prism_quality.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_ENERGY_PRISM_QUALIY_HPP 2 | #define PRISM_ENERGY_PRISM_QUALIY_HPP 3 | 4 | #include "../common.hpp" 5 | #include 6 | #include 7 | 8 | namespace prism::energy { 9 | enum class QualityType { SYMMETRIC_DIRICHLET, MIPS_3D }; 10 | using DScalar = DScalar2; 11 | 12 | // the quality measure for the well shapeness of prisms, the larger, the worse! 13 | double prism_full_quality(const std::array& corners, 14 | const Eigen::RowVector3d& dimscale = Vec3d(1, 1, 1), 15 | QualityType qt = QualityType::MIPS_3D); 16 | 17 | DScalar prism_full_quality(const std::array& corners, 18 | const Eigen::RowVector3d& dimscale, QualityType qt, 19 | int id_with_grad); 20 | 21 | double prism_one_ring_quality(const std::vector& base, 22 | const std::vector& top, 23 | const std::vector& F, 24 | const std::vector& nb, 25 | const std::vector& nbi, 26 | double target_height, 27 | const std::vector& areas, 28 | const std::pair& modification); 29 | 30 | double prism_one_ring_quality(const std::vector& base, 31 | const std::vector& top, 32 | const std::vector& F, 33 | const std::vector& nb, 34 | const std::vector& nbi, 35 | double target_height, 36 | const std::vector& areas, 37 | const std::pair& modification); 38 | 39 | // default last parameter means not taking gradient 40 | std::tuple prism_one_ring_quality( 41 | const std::vector& base, const std::vector& top, 42 | const std::vector& F, const std::vector& nb, 43 | const std::vector& nbi, double target_height, 44 | const std::vector& areas, bool on_base, int v_with_grad = -1); 45 | 46 | DScalar triangle_quality(const std::array& vertices, int v_with_grad); 47 | double triangle_quality(const std::array& vertices); 48 | 49 | std::tuple triangle_one_ring_quality( 50 | const std::vector& mid, const std::vector& F, 51 | const std::vector& nb, const std::vector& nbi, 52 | bool with_grad, Vec3d modification = Vec3d(0., 0, 0)); 53 | } // namespace prism::energy 54 | 55 | #endif -------------------------------------------------------------------------------- /src/prism/cgal/QP.cpp: -------------------------------------------------------------------------------- 1 | #include "QP.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | // choose exact integral type 8 | #ifdef CGAL_USE_GMP 9 | #include 10 | typedef CGAL::Gmpzf ET; 11 | #else 12 | #include 13 | typedef CGAL::MP_Float ET; 14 | #endif 15 | // program and solution types 16 | 17 | namespace prism::cgal { 18 | typedef CGAL::Quadratic_program Program; 19 | typedef CGAL::Quadratic_program_solution Solution; 20 | Vec3d qp_normal(const RowMatd &FN, const std::vector &nb) { 21 | Program qp(CGAL::LARGER, false, 0, false, 0); 22 | 23 | for (int i = 0; i < nb.size(); i++) { 24 | for (int j = 0; j < 3; j++) 25 | qp.set_a(j, i, FN(nb[i], j)); 26 | qp.set_b(i, 1); // n*x >= 1 27 | qp.set_d(i, i, 1); 28 | } 29 | // solve the program, using ET as the exact type 30 | Solution s = CGAL::solve_quadratic_program(qp, ET()); 31 | if (!s.solves_quadratic_program(qp)) { 32 | spdlog::error("CGAL QP failing."); 33 | return Vec3d(0, 0, 0); 34 | } 35 | 36 | if (s.is_infeasible()) { 37 | spdlog::trace("Infeasible"); 38 | return Vec3d(0, 0, 0); 39 | } 40 | // collect and normalize 41 | auto it = s.variable_values_begin(); 42 | auto s0 = (*it); 43 | auto s1 = *next(it); 44 | auto s2 = *next(next(it)); 45 | // normalize 46 | auto r_sq = s0 * s0 + s1 * s1 + s2 * s2; 47 | auto sqrt_sign = [&r_sq](auto &s) { 48 | spdlog::trace("s{}", s); 49 | if (s * s < 1e-20) 50 | return 0.; 51 | double sign = s > 0 ? 1 : -1; 52 | auto squared = CGAL::to_double((s * s) / r_sq); 53 | return sqrt(squared) * sign; 54 | }; 55 | Vec3d normal; 56 | normal[0] = sqrt_sign(s0); 57 | normal[1] = sqrt_sign(s1); 58 | normal[2] = sqrt_sign(s2); 59 | spdlog::trace("CGAL normal {}", normal); 60 | for (auto f : nb) { 61 | auto dot = normal.dot(FN.row(f)); 62 | if (dot < 0) { 63 | spdlog::warn("Wrong Dot", dot); 64 | return Vec3d(0, 0, 0); 65 | } 66 | } 67 | return normal.stableNormalized(); 68 | } 69 | 70 | } // namespace prism::cgal 71 | #include 72 | Vec3d prism::qp_normal_igl(const RowMatd &FN, const std::vector &nb) { 73 | Eigen::MatrixXd G = Eigen::MatrixXd::Identity(3, 3); 74 | Eigen::VectorXd g0 = Eigen::VectorXd::Zero(3); 75 | Eigen::MatrixXd CE, CI(3, nb.size()); 76 | Eigen::VectorXd ce0, ci0 = -Eigen::VectorXd::Ones(nb.size()); 77 | for (int i = 0; i < nb.size(); i++) { 78 | for (int j = 0; j < 3; j++) 79 | CI(j, i)=FN(nb[i], j); 80 | } 81 | Eigen::VectorXd x; 82 | // CI'x >= - ci0 83 | bool flag = igl::copyleft::quadprog(G,g0,CE,ce0,CI,ci0, x); 84 | if (!flag) return Vec3d(0,0,0); 85 | return x.stableNormalized(); 86 | } -------------------------------------------------------------------------------- /python/format_utils.py: -------------------------------------------------------------------------------- 1 | import meshio 2 | import h5py 3 | import vtk 4 | import numpy as np 5 | import pyvista as pv 6 | import numpy_indexed as npi 7 | from vtk_node_ordering import vtk_node_ordering 8 | from curve.fem_generator import tuple_gen 9 | 10 | def convert_cutet(file1, file2): 11 | # a handy conversion for the default output mesh (p4). 12 | with h5py.File(file1, 'r') as fp: 13 | lagr, p4T = fp['lagr'][()], fp['cells'][()] 14 | if p4T.shape[1] == 35: 15 | reorder = np.array([0, 1, 2, 3, 4, 16, 5, 7, 18, 9, 8, 17, 6, 13, 19, 10, 15, 16 | 21, 12, 14, 20, 11, 22, 24, 23, 25, 26, 31, 27, 32, 29, 33, 28, 30, 34]) 17 | elif p4T.shape[1] == 20: 18 | reorder = np.array([ 0, 1, 2, 3, 4, 5, 7, 9, 8, 6, 13, 10, 15, 12, 14, 11, 16, 19 | 17, 18, 19]) 20 | else: 21 | assert False, "only hard-coded P3 or P4 for now." 22 | num = p4T.shape[1] 23 | tag = f'tetra{num}' 24 | meshio.gmsh.write(file2, 25 | meshio.Mesh(points=lagr, cells=[(tag, p4T[:, reorder])])) 26 | 27 | def codec_to_points(codec): 28 | 29 | codec_op = np.eye(4)[codec].mean(axis=1) 30 | points = codec_op@np.vstack([np.zeros(3),np.eye(3)]) 31 | return points 32 | 33 | def codec_to_n(co): return [k for i, j in enumerate(co) for k in [i]*j] 34 | 35 | def vtk_reordering(order, precision=5): 36 | vtk_points = vtk_node_ordering(order) 37 | codec = np.array(tuple_gen(order=order, var_n=3)) 38 | 39 | auto_cod_n = np.array([codec_to_n(c) for c in codec]) 40 | auto_points = codec_to_points(auto_cod_n) 41 | reorder = npi.indices(np.round(auto_points, precision), 42 | np.round(vtk_points, precision), axis=0) 43 | return reorder 44 | 45 | def convert_vtk(file1, file2): 46 | with h5py.File(file1, 'r') as fp: 47 | lagr, p4T = fp['lagr'][()], fp['cells'][()] 48 | 49 | n_points_per_cell = p4T.shape[1] 50 | n_cells = p4T.shape[0] 51 | 52 | shape_order = {10:2, 20:3, 35:4, 56:5} 53 | order = shape_order[n_points_per_cell] 54 | reorder = vtk_reordering(order) 55 | 56 | p4T = p4T[:,reorder] 57 | 58 | cell_type = np.array([vtk.VTK_LAGRANGE_TETRAHEDRON]*n_cells) 59 | cells = np.hstack( [np.ones((n_cells,1), dtype=np.int32)*n_points_per_cell, p4T]) 60 | grid = pv.UnstructuredGrid(cells, cell_type, lagr) 61 | grid.save(file2) 62 | 63 | 64 | if __name__ == '__main__': 65 | import sys, os 66 | file_extension = os.path.splitext(sys.argv[2])[-1] 67 | if file_extension==".msh": 68 | convert_cutet(sys.argv[1],sys.argv[2]) 69 | elif file_extension==".vtu": 70 | convert_vtk(sys.argv[1],sys.argv[2]) 71 | else: 72 | print(f"file type {file_extension} not supported. Use gmsh (.msh) for P4 or .vtu for arbitrary order") 73 | 74 | -------------------------------------------------------------------------------- /src/cumin/curve_pass.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "curve_validity.hpp" 11 | #include "prism/PrismCage.hpp" 12 | #include "prism/cage_utils.hpp" 13 | #include "prism/geogram/AABB.hpp" 14 | #include "prism/intersections.hpp" 15 | #include "prism/local_operations/mesh_coloring.hpp" 16 | #include "prism/local_operations/remesh_pass.hpp" 17 | #include "prism/spatial-hash/AABB_hash.hpp" 18 | namespace prism::curve { 19 | auto smooth_prism(const PrismCage &pc, int vid, 20 | const std::vector> &VF, 21 | const std::vector> &VFi, 22 | const prism::local::RemeshOptions &option, 23 | const std::vector &skip) { 24 | if (vid < pc.ref.aabb->num_freeze) 25 | return false; // only skip singularity, not boundary or feature 26 | 27 | std::vector> checker; 28 | auto flag = 1; 29 | double alpha = 1.; 30 | std::vector local_cp; 31 | std::vector moved_tris; 32 | for (auto f : VF[vid]) moved_tris.push_back(pc.F[f]); 33 | if (option.curve_checker.first.has_value() && 34 | !(std::any_cast &, 36 | const decltype(moved_tris) &, decltype(local_cp) &)>>( 37 | option.curve_checker.first))(pc, VF[vid], moved_tris, local_cp)) { 38 | spdlog::debug("Curving checker failed."); 39 | return false; 40 | } 41 | 42 | if (option.curve_checker.second.has_value()) 43 | (std::any_cast< 44 | std::function &, const std::vector &, 45 | const std::vector &)>>( 46 | option.curve_checker.second))(VF[vid], VF[vid], local_cp); 47 | 48 | spdlog::trace("Curving SUCCEED, move to next."); 49 | return true; 50 | } 51 | } // namespace prism::curve 52 | 53 | void prism::curve::localcurve_pass(const PrismCage &pc, 54 | const prism::local::RemeshOptions &option) { 55 | std::vector> VF, VFi, groups; 56 | std::vector skip_flag(pc.mid.size(), false); 57 | 58 | for (int i = 0; i < pc.ref.aabb->num_freeze; i++) skip_flag[i] = true; 59 | { 60 | RowMati mF, mE; 61 | vec2eigen(pc.F, mF); 62 | igl::vertex_triangle_adjacency(pc.mid.size(), mF, VF, VFi); 63 | } 64 | auto succ = 0; 65 | for (auto vid = 0; vid < pc.mid.size(); vid++) { 66 | auto flag = smooth_prism(pc, vid, VF, VFi, option, skip_flag); 67 | if (flag) succ ++; 68 | } 69 | 70 | spdlog::info("Finished Curve Smoothing {}/{}", succ, pc.mid.size()); 71 | return; 72 | } 73 | -------------------------------------------------------------------------------- /src/prism/cgal/triangle_triangle_intersection.cpp: -------------------------------------------------------------------------------- 1 | #include "triangle_triangle_intersection.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace prism::cgal { 8 | 9 | bool triangle_triangle_overlap(const std::array &tri0, 10 | const std::array &tri1) { 11 | typedef ::CGAL::Exact_predicates_inexact_constructions_kernel K; 12 | std::array cgal_points; 13 | for (int i = 0; i < tri0.size(); i++) { 14 | cgal_points[i] = K::Point_3(tri0[i][0], tri0[i][1], tri0[i][2]); 15 | cgal_points[i + 3] = K::Point_3(tri1[i][0], tri1[i][1], tri1[i][2]); 16 | } 17 | K::Triangle_3 t0(cgal_points[0], cgal_points[1], cgal_points[2]); 18 | K::Triangle_3 t1(cgal_points[3], cgal_points[4], cgal_points[5]); 19 | return CGAL::do_intersect(t0, t1); 20 | } 21 | 22 | bool segment_triangle_overlap(const std::array &seg, 23 | const std::array &tri) { 24 | typedef ::CGAL::Exact_predicates_inexact_constructions_kernel K; 25 | std::array cgal_points; 26 | for (int i = 0; i < 3; i++) 27 | cgal_points[i] = K::Point_3(tri[i][0], tri[i][1], tri[i][2]); 28 | for (int i = 0; i < 2; i++) 29 | cgal_points[i + 3] = K::Point_3(seg[i][0], seg[i][1], seg[i][2]); 30 | K::Triangle_3 t0(cgal_points[0], cgal_points[1], cgal_points[2]); 31 | K::Segment_3 s0(cgal_points[3], cgal_points[4]); 32 | if (t0.is_degenerate()) { 33 | spdlog::debug("degenerate triangle"); 34 | spdlog::dump_backtrace(); 35 | exit(1); 36 | return false; 37 | } 38 | return CGAL::do_intersect(t0, s0); 39 | } 40 | 41 | std::optional segment_triangle_intersection( 42 | const std::array &seg, const std::array &tri) { 43 | typedef ::CGAL::Exact_predicates_inexact_constructions_kernel K; 44 | std::array cgal_points; 45 | for (int i = 0; i < 3; i++) 46 | cgal_points[i] = K::Point_3(tri[i][0], tri[i][1], tri[i][2]); 47 | for (int i = 0; i < 2; i++) 48 | cgal_points[i + 3] = K::Point_3(seg[i][0], seg[i][1], seg[i][2]); 49 | K::Triangle_3 t0(cgal_points[0], cgal_points[1], cgal_points[2]); 50 | K::Segment_3 s0(cgal_points[3], cgal_points[4]); 51 | auto inter = CGAL::intersection(t0, s0); 52 | if (inter) { 53 | if (inter.value().which() != 0) { 54 | return {}; 55 | } 56 | const K::Point_3 *point = &boost::get((inter).value()); 57 | return Vec3d(CGAL::to_double(point->x()), CGAL::to_double(point->y()), 58 | CGAL::to_double(point->z())); 59 | } 60 | return {}; 61 | } 62 | 63 | std::optional gmp_segment_triangle_intersection( 64 | const std::array &seg, const std::array &tri) { 65 | return {}; 66 | } 67 | } // namespace prism::cgal -------------------------------------------------------------------------------- /src/prism/PrismCage.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_PRISMCAGE_HPP 2 | #define PRISM_PRISMCAGE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "common.hpp" 13 | 14 | namespace prism::geogram { 15 | struct AABB; 16 | }; 17 | namespace prism { 18 | struct HashGrid; 19 | }; 20 | 21 | struct PrismCage { 22 | enum class SeparateType { kShell, kSurface, kNone }; 23 | /////////////////////////////////////// 24 | // The input reference surface, with fixed structure throughout the 25 | // optimization. contains V, F and acceleration AABB. 26 | /////////////////////////////////////// 27 | struct RefSurf { 28 | RowMatd inpV; 29 | RowMatd V; 30 | RowMati F; 31 | std::vector> VF, VFi; 32 | std::unique_ptr aabb; 33 | }; 34 | RefSurf ref; 35 | 36 | void serialize(std::string filename, std::any additional = {}) const; 37 | // data for zig 38 | RowMatd zig_top; 39 | RowMatd zig_base; 40 | /////////////////////////////////////// 41 | // Data for the Cage 42 | // Base Vertex, Top Vertex, F, TT, TTi 43 | // All with std::vector to enable dynamic change. 44 | /////////////////////////////////////// 45 | std::vector base; 46 | std::vector top; 47 | std::vector mid; 48 | std::vector F; 49 | 50 | std::vector> track_ref; 51 | std::shared_ptr base_grid = nullptr; 52 | std::shared_ptr top_grid = nullptr; 53 | std::mutex grid_mutex; 54 | 55 | 56 | // marked feature edges. 57 | // a map from endpoints to chain id and list of vertices. As a feature representation. 58 | using meta_type_t = std::map, std::pair>>; 59 | meta_type_t meta_edges; 60 | 61 | // specified constraint points (for distance bound) on each face. 62 | std::vector> constraints_per_face; 63 | RowMatd constraints_points_bc; 64 | /////////////////////////////////////// 65 | // Constructor and Initialize cage 66 | /////////////////////////////////////// 67 | PrismCage() = default; 68 | PrismCage(std::string); 69 | PrismCage(const RowMatd &vert, const RowMati &face, double dooseps = 0.2, 70 | double initial_step = 1e-4, SeparateType st=SeparateType::kSurface); 71 | PrismCage(const RowMatd &vert, const RowMati &face, 72 | RowMati&& feature_edges, 73 | Eigen::VectorXi&& feature_corners, 74 | Eigen::VectorXi && cons_points_fid, RowMatd&& cons_points_bc, 75 | double initial_step = 1e-4, SeparateType st=SeparateType::kSurface); 76 | void load_from_hdf5(std::string); 77 | void construct_cage(const RowMatd &); 78 | void init_track(); 79 | void cleanup_empty_faces(Eigen::VectorXi &NI, Eigen::VectorXi &NJ); 80 | }; 81 | 82 | #endif -------------------------------------------------------------------------------- /src/prism/predicates/positive_prism_volume_12.cpp: -------------------------------------------------------------------------------- 1 | #include "positive_prism_volume_12.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | bool prism::predicates::positive_prism_volume( 9 | const std::array &verts) { 10 | using GEO::PCK::orient_3d; 11 | for (int i = 0; i < 12; i++) { 12 | if (orient_3d(verts[TWELVE_TETRAS[i][0]].data(), 13 | verts[TWELVE_TETRAS[i][1]].data(), 14 | verts[TWELVE_TETRAS[i][2]].data(), 15 | verts[TWELVE_TETRAS[i][3]].data()) <= 0) 16 | return false; 17 | } 18 | return true; 19 | } 20 | 21 | bool prism::predicates::positive_prism_volume( 22 | const std::array &verts, const std::array &constrained, 23 | bool numerical) { 24 | using GEO::PCK::orient_3d; 25 | for (int i = 0; i < 3; i++) { 26 | if (constrained[i]) continue; 27 | for (int j = 0; j < 4; j++) { 28 | auto &tet = TWELVE_TETRAS[i * 4 + j]; 29 | if (numerical) { // also check numerical validity 30 | RowMat3d local_verts; 31 | for (int k = 1; k < 4; k++) 32 | local_verts.row(k - 1) = verts[tet[k]] - verts[tet[0]]; 33 | if (local_verts.determinant() <= 0) { 34 | spdlog::trace("vol: failing numerical"); 35 | return false; 36 | } 37 | } 38 | 39 | if (orient_3d(verts[tet[0]].data(), verts[tet[1]].data(), 40 | verts[tet[2]].data(), verts[tet[3]].data()) <= 0) { 41 | spdlog::trace("vol: failing predicate"); 42 | return false; 43 | } 44 | } 45 | } 46 | return true; 47 | } 48 | #include 49 | #include 50 | #include 51 | bool prism::predicates::positive_nonlinear_prism( 52 | const std::array &verts, const std::array &constrained) { 53 | using Scalar = CGAL::MP_Float; 54 | constexpr std::array, 3> reorder = { 55 | {{1, 3, 0, 2}, {5, 7, 4, 6}, {10, 11, 8, 9}}}; // a,c, b1,b2 56 | for (int i = 0; i < 3; i++) { 57 | if (constrained[i]) continue; 58 | std::array vols; 59 | for (int j = 0; j < 4; j++) { 60 | auto ti = reorder[i][j]; 61 | auto &tet = TWELVE_TETRAS[ti]; 62 | Eigen::Matrix local_verts; 63 | for (int k = 1; k < 4; k++) 64 | for (int l = 0; l < 3; l++) 65 | local_verts(k - 1, l) = 66 | Scalar(verts[tet[k]][l]) - Scalar(verts[tet[0]][l]); 67 | vols[j] = local_verts.determinant(); 68 | } 69 | // spdlog::trace("vols {}", vols); 70 | auto [a, c, b1, b2] = vols; 71 | auto b = b1 + b2; 72 | if (a <= 0 || c <= 0) return false; 73 | // spdlog::trace("delta {}", b * b - 4 * a * c); 74 | if (b <= 0 && b * b > 4 * a * c) return false; 75 | } 76 | return true; 77 | } -------------------------------------------------------------------------------- /tests/remesh_shell_bin.cpp: -------------------------------------------------------------------------------- 1 | #include "test_common.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "cumin/curve_validity.hpp" 9 | #include "prism/PrismCage.hpp" 10 | #include "prism/geogram/AABB.hpp" 11 | #include "prism/geogram/geogram_utils.hpp" 12 | #include "prism/local_operations/remesh_pass.hpp" 13 | #include "prism/predicates/positive_prism_volume_12.hpp" 14 | #include "prism/spatial-hash/AABB_hash.hpp" 15 | #include 16 | #include 17 | #include 18 | #define DATA_PATH "../build_clang/" 19 | 20 | TEST_CASE("cumin collapse") { 21 | prism::geo::init_geogram(); 22 | spdlog::set_level(spdlog::level::debug); 23 | #ifdef NDEBUG 24 | spdlog::set_level(spdlog::level::info); 25 | if (true) { 26 | RowMatd V; 27 | RowMati F; 28 | igl::read_triangle_mesh( 29 | // "../tests/data/sphere.obj",V,F); 30 | DATA_PATH "spot.obj", V, F); 31 | put_in_unit_box(V); 32 | PrismCage pc(V, F, 0.2, 0.1); 33 | pc.serialize(DATA_PATH "sphere_temp.h5"); 34 | } 35 | #endif 36 | PrismCage pc( DATA_PATH "sphere_temp.h5"); 37 | prism::local::RemeshOptions option(pc.mid.size(), 0.5); 38 | option.distortion_bound = 1e-5; 39 | option.target_adjustment.resize(pc.mid.size(), 0.03); 40 | // option.target_thickness = 0.01; 41 | 42 | option.collapse_quality_threshold = 30; 43 | option.parallel = false; 44 | 45 | int order = 3; 46 | double normal_th = 0.99; 47 | double dist_th = 1e-2; 48 | spdlog::info("order {}", order); 49 | auto complete_cp = 50 | prism::curve::initialize_cp(pc.mid, pc.F, codecs_gen_id(order, 2)); 51 | 52 | for (int i = 0; i < 10; i++) { 53 | prism::local::wildcollapse_pass(pc, option); 54 | complete_cp.erase(std::remove_if(complete_cp.begin(), complete_cp.end(), 55 | [](auto &c) { return c(0, 0) == -1; }), 56 | complete_cp.end()); 57 | prism::local::localsmooth_pass(pc, option); 58 | prism::local::wildflip_pass(pc, option); 59 | REQUIRE(prism::cage_check::cage_is_away_from_ref(pc)); 60 | pc.serialize(fmt::format("spot_noelev_n{}_o{}.h5",normal_th, order), 61 | std::function( 62 | [complete_cp](HighFive::File &file) { 63 | std::vector>> vec_cp( 64 | complete_cp.size()); 65 | for (int i = 0; i < complete_cp.size(); i++) 66 | igl::matrix_to_list(complete_cp[i], vec_cp[i]); 67 | 68 | H5Easy::dump(file, "complete_cp", vec_cp); 69 | })); 70 | // if (pc.F.size() < 1000) 71 | // return; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cmake/FindMPFR.cmake: -------------------------------------------------------------------------------- 1 | # Try to find the MPFR library 2 | # See http://www.mpfr.org/ 3 | # 4 | # This module supports requiring a minimum version, e.g. you can do 5 | # find_package(MPFR 2.3.0) 6 | # to require version 2.3.0 to newer of MPFR. 7 | # 8 | # Once done this will define 9 | # 10 | # MPFR_FOUND - system has MPFR lib with correct version 11 | # MPFR_INCLUDES - the MPFR include directory 12 | # MPFR_LIBRARIES - the MPFR library 13 | # MPFR_VERSION - MPFR version 14 | # Copyright (c) 2006, 2007 Montel Laurent, 15 | # Copyright (c) 2008, 2009 Gael Guennebaud, 16 | # Copyright (c) 2010 Jitse Niesen, 17 | # Copyright (c) 2015 Jack Poulson, 18 | # Redistribution and use is allowed according to the terms of the BSD license. 19 | 20 | find_path(MPFR_INCLUDES NAMES mpfr.h PATHS $ENV{GMPDIR} $ENV{MPFRDIR} 21 | ${INCLUDE_INSTALL_DIR}) 22 | 23 | # Set MPFR_FIND_VERSION to 1.0.0 if no minimum version is specified 24 | if(NOT MPFR_FIND_VERSION) 25 | if(NOT MPFR_FIND_VERSION_MAJOR) 26 | set(MPFR_FIND_VERSION_MAJOR 1) 27 | endif() 28 | if(NOT MPFR_FIND_VERSION_MINOR) 29 | set(MPFR_FIND_VERSION_MINOR 0) 30 | endif() 31 | if(NOT MPFR_FIND_VERSION_PATCH) 32 | set(MPFR_FIND_VERSION_PATCH 0) 33 | endif() 34 | set(MPFR_FIND_VERSION 35 | "${MPFR_FIND_VERSION_MAJOR}.${MPFR_FIND_VERSION_MINOR}.${MPFR_FIND_VERSION_PATCH}") 36 | endif() 37 | 38 | if(MPFR_INCLUDES) 39 | # Query MPFR_VERSION 40 | file(READ "${MPFR_INCLUDES}/mpfr.h" _mpfr_version_header) 41 | 42 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_MAJOR[ \t]+([0-9]+)" 43 | _mpfr_major_version_match "${_mpfr_version_header}") 44 | set(MPFR_MAJOR_VERSION "${CMAKE_MATCH_1}") 45 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_MINOR[ \t]+([0-9]+)" 46 | _mpfr_minor_version_match "${_mpfr_version_header}") 47 | set(MPFR_MINOR_VERSION "${CMAKE_MATCH_1}") 48 | string(REGEX MATCH "define[ \t]+MPFR_VERSION_PATCHLEVEL[ \t]+([0-9]+)" 49 | _mpfr_patchlevel_version_match "${_mpfr_version_header}") 50 | set(MPFR_PATCHLEVEL_VERSION "${CMAKE_MATCH_1}") 51 | 52 | set(MPFR_VERSION 53 | ${MPFR_MAJOR_VERSION}.${MPFR_MINOR_VERSION}.${MPFR_PATCHLEVEL_VERSION}) 54 | 55 | # Check whether found version exceeds minimum required 56 | if(${MPFR_VERSION} VERSION_LESS ${MPFR_FIND_VERSION}) 57 | set(MPFR_VERSION_OK FALSE) 58 | message(STATUS "MPFR version ${MPFR_VERSION} found in ${MPFR_INCLUDES}, " 59 | "but at least version ${MPFR_FIND_VERSION} is required") 60 | else() 61 | set(MPFR_VERSION_OK TRUE) 62 | endif() 63 | endif() 64 | 65 | find_library(MPFR_LIBRARIES mpfr 66 | PATHS $ENV{GMPDIR} $ENV{MPFRDIR} ${LIB_INSTALL_DIR}) 67 | 68 | include(FindPackageHandleStandardArgs) 69 | find_package_handle_standard_args(MPFR DEFAULT_MSG 70 | MPFR_INCLUDES MPFR_LIBRARIES MPFR_VERSION_OK) 71 | mark_as_advanced(MPFR_INCLUDES MPFR_LIBRARIES) -------------------------------------------------------------------------------- /src/prism/local_operations/remesh_pass.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_LOCAL_OPERATIONS_REMESH_PASS_HPP 2 | #define PRISM_LOCAL_OPERATIONS_REMESH_PASS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../common.hpp" 11 | 12 | struct PrismCage; 13 | namespace prism::geogram { 14 | struct AABB; 15 | }; 16 | namespace prism::local { 17 | struct RemeshOptions { 18 | double distortion_bound = 0.1; 19 | double target_thickness = 0.01; 20 | double zig_thick = 1e-4; 21 | bool parallel = true; 22 | bool use_polyshell = false; // zig remesh or snapper remesh 23 | double relax_quality_threshold = 20; 24 | double collapse_quality_threshold = 30; 25 | double collapse_valence_threshold = -1; // enabled if positive. 26 | bool split_improve_quality = true; 27 | bool volume_centric = false; // volume quality etc. 28 | bool dynamic_hashgrid = 29 | false; // use a dynamic spatial hashgrid instead of static AABB 30 | 31 | std::function sizing_field; 32 | std::vector target_adjustment; 33 | RemeshOptions() = default; 34 | RemeshOptions(int v_num, double edge_len) { 35 | sizing_field = [edge_len](const Vec3d &) { return edge_len; }; 36 | target_adjustment.resize(v_num, 1); 37 | } 38 | bool linear_curve = true; // postpone curving to later stages, to save time at the beginning. 39 | // additional 40 | double curve_dist_bound = 1e-2; 41 | double curve_normal_bound = 1e-2; 42 | bool curve_recurse_check = true; 43 | // pre and post 44 | std::pair curve_checker; 45 | std::vector> chain_reject_trackee; 46 | }; 47 | } // namespace prism::local 48 | 49 | namespace prism::local { 50 | constexpr auto shift_left = [](const auto &new_fid, const auto &new_shifts, 51 | auto &F, auto &FF, auto &FFi) { 52 | constexpr auto roll_shift_left = [](auto &vec, int s) -> Vec3i { 53 | auto [a, b, c] = vec; 54 | if (s == 0) return {a, b, c}; 55 | if (s == 1) 56 | return {b, c, a}; 57 | else 58 | return {c, a, b}; 59 | }; 60 | for (int i = 0; i < new_shifts.size(); i++) { 61 | auto f = new_fid[i]; 62 | auto s = new_shifts[i]; 63 | // shift F,FF,FFi 64 | F[f] = roll_shift_left(F[f], s); 65 | FF[f] = roll_shift_left(FF[f], s); 66 | FFi[f] = roll_shift_left(FFi[f], s); 67 | // take care of the FFi for neighbors 68 | for (int j : {0, 1, 2}) { 69 | int f1 = FF[f][j]; 70 | if (f1 == -1) continue; 71 | int e1 = FFi[f][j]; 72 | FFi[f1][e1] = j; 73 | } 74 | } 75 | }; 76 | 77 | int wildcollapse_pass(PrismCage &pc, RemeshOptions &); 78 | void wildflip_pass(PrismCage &pc, const RemeshOptions &); 79 | int wildsplit_pass(PrismCage &pc, RemeshOptions &); 80 | void localsmooth_pass(PrismCage &pc, const RemeshOptions &); 81 | void shellsmooth_pass(PrismCage &pc, const RemeshOptions &option); 82 | } // namespace prism::local 83 | #endif -------------------------------------------------------------------------------- /tests/phong.cpp: -------------------------------------------------------------------------------- 1 | #include "prism/common.hpp" 2 | #include "prism/phong/projection.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE("sanity-phong") { 10 | // sanity check of the projection within the standard one. 11 | using namespace Eigen; 12 | Vec3d t0 = STANDARD_PRISM[0]; 13 | Vec3d t1 = STANDARD_PRISM[1]; 14 | Vec3d t2 = STANDARD_PRISM[2]; 15 | Vec3d t3 = STANDARD_PRISM[3]; 16 | Vec3d t4 = STANDARD_PRISM[4]; 17 | Vec3d t5 = STANDARD_PRISM[5]; 18 | std::array base_top{t0, t1, t2, t3, t4, t5}; 19 | 20 | Vec3d point = (t0 + 2 * t1 + 3 * t2 + t3 + 2 * t4 + 3 * t5) / 12; 21 | 22 | SUBCASE("Not Symmetric") { 23 | using doctest::Approx; 24 | for (auto boolcase : {true, false}) { 25 | std::array tuple; 26 | REQUIRE(prism::phong::phong_projection(base_top, point, boolcase, tuple)); 27 | 28 | CAPTURE(tuple); 29 | auto [a, b, t] = tuple; 30 | REQUIRE(a == Approx(1 / 3.)); 31 | REQUIRE(b == Approx(1 / 2.)); 32 | REQUIRE(t == Approx(1 / 2.)); 33 | } 34 | } 35 | } 36 | 37 | TEST_CASE("phong-numeric") { 38 | // some numeric example for phong 39 | using namespace Eigen; 40 | Eigen::MatrixXd N(3, 3), P(3, 3); 41 | Eigen::RowVector3d point(0.1604241749682691, 0.555453476270909, 42 | 0.04448819585023656); 43 | P << 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 2.0, 0.0; 44 | N << 0.5554048211476403, -0.5249175599301754, 0.8242785326613685, 45 | 0.9314983960859995, 0.9452022278097867, 0.4534492474173122, 46 | 0.21808492552255587, 0.5510530292096933, 0.6416133447590692; 47 | N = N + P; 48 | 49 | std::array base_top{P.row(0), P.row(1), P.row(2), 50 | N.row(0), N.row(1), N.row(2)}; 51 | std::array tuple; 52 | REQUIRE(prism::phong::phong_projection(base_top, point, true, tuple)); 53 | 54 | auto [a, b, t] = tuple; 55 | Eigen::RowVector3d bary(a, b, 1 - a - b); 56 | INFO("Note the following is not carefully verified, just try to keep the " 57 | "same result as Oct 24"); 58 | CHECK(a == doctest::Approx(0.0304147639)); 59 | CHECK(b == doctest::Approx(0.2501706989)); 60 | CHECK(t == doctest::Approx(0.0782576654)); 61 | } 62 | 63 | // #include 64 | // TEST_CASE("multiprec") { 65 | // using namespace mpfr; 66 | // using namespace Eigen; 67 | // // set precision to 256 bits (double has only 53 bits) 68 | // mpreal::set_default_prec(256); 69 | // // Declare matrix and vector types with multi-precision scalar type 70 | // typedef Matrix MatrixXmp; 71 | // typedef Matrix VectorXmp; 72 | 73 | // MatrixXmp A = MatrixXmp::Random(100,100); 74 | // VectorXmp b = VectorXmp::Random(100); 75 | 76 | // // Solve Ax=b using LU 77 | // VectorXmp x = A.lu().solve(b); 78 | // std::cout << "relative error: " << (A*x - b).norm() / b.norm() << std::endl; 79 | 80 | // } -------------------------------------------------------------------------------- /src/prism/energy/smoother_pillar.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_ENERGY_SMOOTHER_PILLAR_HPP 2 | #define PRISM_ENERGY_SMOOTHER_PILLAR_HPP 3 | #include 4 | #include 5 | 6 | namespace prism { 7 | RowMatd one_ring_volumes(const std::vector& base, 8 | const std::vector& mid, 9 | const std::vector& top, 10 | const std::vector& F, 11 | const std::vector& nb, 12 | const std::vector& nbi, 13 | const std::array& modify = { 14 | Vec3d(0, 0, 0), Vec3d(0, 0, 0), Vec3d(0, 0, 0)}); 15 | 16 | double get_min_step_to_singularity(const std::vector& base, 17 | const std::vector& mid, 18 | const std::vector& top, 19 | const std::vector& F, 20 | const std::vector& nb, 21 | const std::vector& nbi, 22 | std::array /*base,mid,top*/ change, 23 | const Vec3d& direction, int num_freeze = 0); 24 | 25 | // Pan: parallel move pillar for a better triangle quality, considering deprecate it. 26 | std::optional smoother_direction( 27 | const std::vector& base, const std::vector& mid, 28 | const std::vector& top, const std::vector& F, int num_freeze, 29 | const std::vector>& VF, 30 | const std::vector>& VFi, int vid); 31 | 32 | // Legacy version of zoom and rotate, for prism full quality 33 | std::optional> zoom_and_rotate( 34 | const std::vector& base, const std::vector& mid, 35 | const std::vector& top, const std::vector& F, int num_freeze, 36 | const std::vector>& VF, 37 | const std::vector>& VFi, int vid, double target_height); 38 | 39 | std::optional smoother_location_legacy( 40 | const std::vector& base, const std::vector& mid, 41 | const std::vector& top, const std::vector& F, int freeze, 42 | const std::vector>& VF, 43 | const std::vector> VFi, int vid, bool on_base); 44 | 45 | std::optional> zoom( 46 | const std::vector& base, const std::vector& mid, 47 | const std::vector& top, const std::vector& F, 48 | const std::vector>& VF, 49 | const std::vector>& VFi, int vid, 50 | double target_thickness); 51 | 52 | 53 | std::optional> rotate( 54 | const std::vector& base, const std::vector& mid, 55 | const std::vector& top, const std::vector& F, 56 | const std::vector>& VF, 57 | const std::vector>& VFi, int vid, double); 58 | } // namespace prism 59 | 60 | #endif -------------------------------------------------------------------------------- /src/prism/feature_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRISM_FEATUE_UTILS_HPP 2 | #define PRISM_FEATUE_UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "common.hpp" 9 | namespace prism { 10 | 11 | constexpr auto vv2fe = [](auto &v0, auto &v1, const auto &F, const auto &VF) { 12 | std::vector common; 13 | std::set_intersection(VF[v0].begin(), VF[v0].end(), VF[v1].begin(), 14 | VF[v1].end(), std::back_inserter(common)); 15 | for (auto f : common) { 16 | for (int j = 0; j < 3; j++) { 17 | if (F(f, j) == v0 && F(f, (j + 1) % 3) == v1) { 18 | return std::pair(f, j); 19 | } 20 | } 21 | } 22 | return std::pair(-1, -1); 23 | }; 24 | 25 | // reader from [Gao et al] 26 | bool read_feature_graph(std::string path, Eigen::VectorXi &feat_nodes, 27 | RowMati &feat_edges); 28 | 29 | bool read_feature_h5(std::string path, Eigen::VectorXi &feat_nodes, 30 | RowMati &feat_edges, Eigen::VectorXi &points_fid, 31 | RowMatd &points_bc); 32 | // compute the dot product of adjacent face normals (TT and FN) 33 | // and mark v0-v1 if below threshold. 34 | void mark_feature_edges(const RowMatd &V, const RowMati &F, double thre, 35 | RowMati &feat); 36 | // connect list of feature edges, and split into disjoint chains. 37 | // feat_nodes is optional and high valence connectors are automatically 38 | // detected. Note: Cyclic loops is not specially marked, since front == end; 39 | bool feature_chains_from_edges(const Eigen::VectorXi &feat_nodes, 40 | const RowMati &feat_edges, int vnum, 41 | std::vector> &all_chain); 42 | 43 | // mark the region around the chains 44 | // for each chain, mark the reference that should be reject by left/right. 45 | void feature_chain_region_segments( 46 | const RowMati &F, int vnum, const std::vector> chains, 47 | std::vector> &feature_ignore, 48 | std::vector> ®ion_around_chain); 49 | 50 | std::vector> glue_meta_together( 51 | const std::map, std::pair>> 52 | &meta); 53 | std::vector> recover_chains_from_meta_edges( 54 | const std::map, std::pair>> 55 | &meta); 56 | 57 | // split triangles according to slice_vv 58 | // while maintaining feature_edges correctly tagged. 59 | std::tuple subdivide_feature_triangles( 60 | const RowMatd &mV, const RowMati &mF, const RowMati &feature_edges, 61 | const std::vector> &slicer, 62 | std::vector &face_parent); 63 | 64 | // split the edges connecting two feature verts but not feature edge. 65 | void split_feature_ears(RowMatd &mV, RowMati &mF, const RowMati &mE, 66 | std::vector &face_parent); 67 | 68 | bool feature_sanity(const RowMatd &mV, RowMati &mE); 69 | 70 | // this function splits extrmely long feature edges above a threshold 71 | // to promote the grouping of feature edges, without the need of fractional. 72 | bool feature_pre_split(RowMatd &V, RowMati &F, RowMati &feature_edges, 73 | double threshold, std::vector &face_parent); 74 | } // namespace prism 75 | 76 | #endif -------------------------------------------------------------------------------- /tests/cgal_AABB.cpp: -------------------------------------------------------------------------------- 1 | #include "prism/geogram/AABB.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | TEST_CASE("segment test") { 12 | using namespace Eigen; 13 | auto file = 14 | "/home/zhongshi/Workspace/InterSurfaceMapping/python/data/fertility.off"; 15 | 16 | RowMatd V; 17 | RowMati F; 18 | igl::read_triangle_mesh(file, V, F); 19 | put_in_unit_box(V); 20 | for (int i = 0; i < F.rows(); i++) { 21 | auto [s, mt, shift] = 22 | tetra_split_AorB({F(i, 0), F(i, 1), F(i, 2)}); 23 | for (int j = 0; j < 3; j++) 24 | F(i, j) = mt[j]; 25 | } 26 | constexpr int exp_count = 1000; 27 | SUBCASE("CGAL") { 28 | prism::geogram::AABB tree(V, F); 29 | RowMatd P(3, 3); 30 | int intersect_count = 0; 31 | RowMatd segment_hits(exp_count, 3); 32 | RowMatd all_P(exp_count, 9); 33 | int seg_count = 0; 34 | segment_hits.setZero(); 35 | srand((0)); 36 | for (int i = 0; i < exp_count; i++) { 37 | P.setRandom(); 38 | all_P.row(i) = Eigen::Map(P.data(), 9); 39 | if (tree.intersects_triangle({P.row(0), P.row(1), P.row(2)})) { 40 | intersect_count++; 41 | } 42 | auto seg = tree.segment_query(P.row(0), P.row(1)); 43 | if (seg) { 44 | segment_hits.row(i) = seg.value(); 45 | seg_count++; 46 | // spdlog::info("seg {}", seg.value()); 47 | } 48 | } 49 | spdlog::info("Finish {}/{}/{}", seg_count, intersect_count, exp_count); 50 | CHECK(exp_count == 1000); 51 | CHECK((seg_count == 132 || seg_count == 112)); 52 | CHECK((intersect_count == 284 || intersect_count == 290)); 53 | } 54 | } 55 | 56 | TEST_CASE("touching case") { 57 | using namespace Eigen; 58 | MatrixXd V(4, 3); 59 | V << 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1; 60 | MatrixXi F(4, 3); 61 | F << 0, 1, 3, 1, 2, 3, 0, 3, 2, 0, 2, 1; 62 | prism::geogram::AABB tree(V, F); 63 | 64 | // touch one edge 65 | Eigen::MatrixXd P(3, 3); 66 | P << 0, 0, 0, 1, 0, 0, 0, -1, 0; 67 | CHECK(tree.intersects_triangle({P.row(0), P.row(1), P.row(2)})); 68 | 69 | // lift a bit 70 | P(2, 2) = 0.1; 71 | CHECK(tree.intersects_triangle({P.row(0), P.row(1), P.row(2)})); 72 | 73 | // Moving whole edge away 74 | P(0, 1) = -1; 75 | P(1, 1) = -1; 76 | CHECK_FALSE(tree.intersects_triangle({P.row(0), P.row(1), P.row(2)})); 77 | 78 | Eigen::MatrixXd touch_vertex(3, 3); 79 | touch_vertex << 0, 0, 0, -1, 0, 0, 0, -1, 0; 80 | CHECK(tree.intersects_triangle( 81 | {touch_vertex.row(0), touch_vertex.row(1), touch_vertex.row(2)})); 82 | } 83 | 84 | TEST_CASE("aabb singularity") { 85 | RowMatd V; 86 | RowMati F; 87 | igl::read_triangle_mesh("../tests/data/saddle/original.obj", V, F); 88 | put_in_unit_box(V); 89 | PrismCage pc(V, F); 90 | V = pc.ref.V; 91 | F = pc.ref.F; 92 | 93 | RowMatd P(2, 3); 94 | spdlog::set_level(spdlog::level::info); 95 | int num_inter = 0; 96 | for (int i = 0; i < 100; i++) { 97 | P.setRandom(); 98 | auto inter = (pc.ref.aabb->intersects_triangle( 99 | {V.row(0), P.row(0), P.row(1)}, true)); 100 | if (inter) 101 | num_inter++; 102 | } 103 | CHECK(num_inter == 62); 104 | } 105 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(prism_library) 2 | target_sources(prism_library 3 | PRIVATE 4 | prism/PrismCage.cpp 5 | prism/extraction.cpp 6 | prism/feature_utils.cpp 7 | prism/cage_utils.cpp 8 | prism/polyshell_utils.cpp 9 | prism/bevel_utils.cpp 10 | prism/geogram/geogram_utils.cpp 11 | prism/cgal/polyhedron_self_intersect.cpp 12 | prism/cgal/triangle_triangle_intersection.cpp 13 | # prism/cgal/tetrahedron_tetrahedron_intersection.cpp 14 | prism/cgal/QP.cpp 15 | prism/geogram/AABB.cpp 16 | prism/phong/projection.cpp 17 | prism/phong/trilinear_projection.cpp 18 | prism/phong/tangent_orientation.cpp 19 | prism/phong/query_correspondence.cpp 20 | prism/predicates/inside_prism_tetra.cpp 21 | prism/predicates/inside_octahedron.cpp 22 | prism/predicates/positive_prism_volume_12.cpp 23 | prism/predicates/triangle_triangle_intersection.cpp 24 | prism/predicates/tetrahedron_overlap.cpp 25 | prism/energy/prism_quality.cpp 26 | prism/energy/map_distortion.cpp 27 | prism/energy/smoother_pillar.cpp 28 | prism/local_operations/remesh_pass.cpp 29 | prism/local_operations/smooth_pass.cpp 30 | prism/local_operations/remesh_pass_collapse.cpp 31 | prism/local_operations/remesh_pass_polyshell.cpp 32 | prism/local_operations/remesh_pass_feature.cpp 33 | prism/local_operations/section_remesh.cpp 34 | prism/local_operations/mesh_coloring.cpp 35 | prism/local_operations/local_mesh_edit.cpp 36 | prism/local_operations/validity_checks.cpp 37 | prism/local_operations/retain_triangle_adjacency.cpp 38 | prism/spatial-hash/AABB_hash.cpp 39 | prism/spatial-hash/self_intersection.cpp 40 | prism/osqp/osqp_normal.cpp 41 | prism/cage_check.cpp 42 | prism/intersections.cpp 43 | ) 44 | 45 | target_compile_definitions(prism_library PUBLIC CGAL_QP) 46 | target_compile_features(prism_library PUBLIC cxx_std_17) 47 | target_link_libraries(prism_library PUBLIC spdlog::spdlog igl::core osqpstatic highfive geogram mitsuba_autodiff igl::cgal) 48 | target_include_directories(prism_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/) 49 | set_target_properties(prism_library PROPERTIES POSITION_INDEPENDENT_CODE ON) 50 | add_library(prism::prism ALIAS prism_library) 51 | 52 | 53 | add_library(cumin_library 54 | cumin/curve_utils.cpp 55 | cumin/curve_pass.cpp 56 | cumin/inversion_check.cpp 57 | cumin/bernstein_eval.cpp 58 | cumin/high_order_optimization.cpp 59 | cumin/stitch_surface_to_volume.cpp 60 | cumin/curve_validity.cpp) 61 | target_compile_features(cumin_library PUBLIC cxx_std_17) 62 | target_link_libraries(cumin_library PUBLIC prism_library) 63 | target_include_directories(cumin_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/) 64 | target_compile_definitions(cumin_library PUBLIC CUMIN_MAGIC_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/../python/curve/data/") 65 | 66 | add_executable(cumin_bin) 67 | 68 | target_sources(cumin_bin PRIVATE pipeline_schedules.cpp curve_in_shell.cpp) 69 | target_link_libraries(cumin_bin prism_library cumin_library CLI11::CLI11 json libTetShell) 70 | 71 | if (ENABLE_ASAN) 72 | target_compile_options(cumin_bin PUBLIC "-fsanitize=address") 73 | target_link_options(cumin_bin PUBLIC "-fsanitize=address") 74 | endif() 75 | 76 | option(PYBICHON "Enable Python Binding" OFF) 77 | if (PYBICHON) 78 | find_package(PythonInterp QUIET) 79 | prism_download_pybind11() 80 | add_subdirectory(${PRISM_EXTERNAL}/pybind11/ pybind11) 81 | add_subdirectory(python) 82 | endif() -------------------------------------------------------------------------------- /src/prism/phong/projection.cpp: -------------------------------------------------------------------------------- 1 | #include "projection.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "../common.hpp" 12 | #include "../predicates/inside_prism_tetra.hpp" 13 | 14 | const auto canonical_prism = 15 | std::array{Vec3d(0, 0, 0), Vec3d(1, 0, 0), Vec3d(0, 1, 0), 16 | Vec3d(0, 0, 1), Vec3d(1, 0, 1), Vec3d(0, 1, 1)}; 17 | // for one prism, tuple record a,b,t 18 | bool prism::phong::phong_projection(const std::array &stacked_V, 19 | const Vec3d &point, bool tetra_split_way, 20 | std::array &tuple) { 21 | auto &tetconfig = tetra_split_way ? TETRA_SPLIT_A : TETRA_SPLIT_B; 22 | 23 | // find where is the point 24 | for (auto i = 2; i > (stacked_V[0] == stacked_V[3] ? 0 : -1); 25 | i--) // a very un-readable to skip singularity 26 | if (predicates::point_in_tetrahedron( 27 | point, stacked_V[tetconfig[i][0]], stacked_V[tetconfig[i][1]], 28 | stacked_V[tetconfig[i][2]], stacked_V[tetconfig[i][3]])) { 29 | Eigen::RowVector4d bary; 30 | igl::barycentric_coordinates( 31 | point, stacked_V[tetconfig[i][0]], stacked_V[tetconfig[i][1]], 32 | stacked_V[tetconfig[i][2]], stacked_V[tetconfig[i][3]], bary); 33 | if (bary.hasNaN()) { 34 | spdlog::warn("Nan Bary"); 35 | bary.setConstant(0.25); 36 | }; 37 | Vec3d sol = Vec3d(0, 0, 0); 38 | for (int j = 0; j < 4; j++) { 39 | sol += bary[j] * canonical_prism[tetconfig[i][j]]; 40 | } 41 | tuple[0] = sol[0]; 42 | tuple[1] = sol[1]; 43 | tuple[2] = sol[2]; 44 | if (std::isnan(tuple[0]) || std::isnan(tuple[1])) { 45 | spdlog::error("tup Bary"); 46 | exit(1); 47 | }; 48 | return true; 49 | } 50 | return false; 51 | } 52 | 53 | // the convention is (1-u-v, u, v) 54 | void prism::phong::fiber_endpoints(const std::array &stacked_V, 55 | bool tetra_split_way, double u, double v, 56 | std::array &endpoints) { 57 | auto &rfconfig = 58 | tetra_split_way ? PRISM_RISING_FACETS_A : PRISM_RISING_FACETS_B; 59 | for (int i = 0; i < 4; i++) { 60 | endpoints[i] = stacked_V[rfconfig[i][0]] * (1 - u - v) + 61 | stacked_V[rfconfig[i][1]] * u + 62 | stacked_V[rfconfig[i][2]] * v; 63 | } 64 | } 65 | 66 | void prism::phong::fiber_endpoints(const std::array &stacked_V, 67 | bool tetra_split_way, double u, double v, 68 | std::array &endpoints) { 69 | auto &rfconfig = 70 | tetra_split_way ? PRISM_RISING_FACETS_A : PRISM_RISING_FACETS_B; 71 | for (int i = 0; i < 3; i++) { 72 | endpoints[i] = stacked_V[rfconfig[i][0]] * (1 - u - v) + 73 | stacked_V[rfconfig[i][1]] * u + 74 | stacked_V[rfconfig[i][2]] * v; 75 | } 76 | // notice that it is always 0,1,2 xxx xxx 3,4,5 so the first and last match. 77 | for (int i = 0; i < 4; i++) { 78 | endpoints[i + 3] = stacked_V[rfconfig[i][0] + 3] * (1 - u - v) + 79 | stacked_V[rfconfig[i][1] + 3] * u + 80 | stacked_V[rfconfig[i][2] + 3] * v; 81 | } 82 | } -------------------------------------------------------------------------------- /cmake/geogram.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # Find Geogram and build it as part of the current build 3 | ################################################################################ 4 | 5 | if(TARGET geogram) 6 | return() 7 | endif() 8 | 9 | ################################################################################ 10 | 11 | if(PRISM_EXTERNAL) 12 | set(GEOGRAM_SEARCH_PATHS ${PRISM_EXTERNAL}) 13 | else() 14 | set(GEOGRAM_SEARCH_PATHS 15 | ${GEOGRAM_INSTALL_PREFIX} 16 | "$ENV{GEOGRAM_INSTALL_PREFIX}" 17 | "/usr/local/" 18 | "$ENV{PROGRAMFILES}/Geogram" 19 | "$ENV{PROGRAMW6432}/Geogram" 20 | "$ENV{HOME}/.local/") 21 | endif() 22 | 23 | find_path(GEOGRAM_SOURCE_INCLUDE_DIR 24 | geogram/basic/common.h 25 | PATHS ${GEOGRAM_SEARCH_PATHS} 26 | PATH_SUFFIXES geogram/src/lib 27 | ) 28 | 29 | set(GEOGRAM_ROOT ${GEOGRAM_SOURCE_INCLUDE_DIR}/../..) 30 | 31 | ################################################################################ 32 | 33 | if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 34 | set(VORPALINE_ARCH_64 TRUE CACHE BOOL "" FORCE) 35 | set(VORPALINE_PLATFORM Win-vs-generic CACHE STRING "" FORCE) 36 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 37 | set(VORPALINE_PLATFORM Linux64-gcc-dynamic CACHE STRING "" FORCE) 38 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 39 | set(VORPALINE_PLATFORM Darwin-clang-dynamic CACHE STRING "" FORCE) 40 | endif() 41 | 42 | option(GEOGRAM_WITH_GRAPHICS "Viewers and geogram_gfx library" OFF) 43 | option(GEOGRAM_WITH_LEGACY_NUMERICS "Legacy numerical libraries" OFF) 44 | option(GEOGRAM_WITH_HLBFGS "Non-linear solver (Yang Liu's HLBFGS)" OFF) 45 | option(GEOGRAM_WITH_TETGEN "Tetrahedral mesher (Hang Si's TetGen)" OFF) 46 | option(GEOGRAM_WITH_TRIANGLE "Triangle mesher (Jonathan Shewchuk's triangle)" ON) 47 | option(GEOGRAM_WITH_EXPLORAGRAM "Experimental code (hexahedral meshing vpipeline and optimal transport)" OFF) 48 | option(GEOGRAM_WITH_LUA "Built-in LUA interpreter" OFF) 49 | option(GEOGRAM_LIB_ONLY "Libraries only (no example programs/no viewer)" ON) 50 | option(GEOGRAM_WITH_FPG "Predicate generator (Sylvain Pion's FPG)" OFF) 51 | option(GEOGRAM_USE_SYSTEM_GLFW3 "Use the version of GLFW3 installed in the system if found" OFF) 52 | 53 | ################################################################################ 54 | 55 | add_subdirectory(${GEOGRAM_ROOT} geogram) 56 | target_include_directories(geogram SYSTEM PUBLIC ${GEOGRAM_SOURCE_INCLUDE_DIR}) 57 | 58 | ################################################################################ 59 | 60 | if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 61 | # remove warning for multiply defined symbols (caused by multiple 62 | # instanciations of STL templates) 63 | target_compile_options(geogram INTERFACE /wd4251) 64 | 65 | # remove all unused stuff from windows.h 66 | target_compile_definitions(geogram INTERFACE -DWIN32_LEAN_AND_MEAN) 67 | target_compile_definitions(geogram INTERFACE -DVC_EXTRALEAN) 68 | 69 | # do not define a min() and a max() macro, breaks 70 | # std::min() and std::max() !! 71 | target_compile_definitions(geogram INTERFACE -DNOMINMAX) 72 | 73 | # we want M_PI etc... 74 | target_compile_definitions(geogram INTERFACE -D_USE_MATH_DEFINES) 75 | 76 | if(NOT VORPALINE_BUILD_DYNAMIC) 77 | # If we use static library, we link with the static C++ runtime. 78 | foreach(config ${CMAKE_CONFIGURATION_TYPES}) 79 | string(TOUPPER ${config} config) 80 | string(REPLACE /MD /MT CMAKE_C_FLAGS_${config} "${CMAKE_C_FLAGS_${config}}") 81 | string(REPLACE /MD /MT CMAKE_CXX_FLAGS_${config} "${CMAKE_CXX_FLAGS_${config}}") 82 | endforeach() 83 | endif() 84 | endif() 85 | -------------------------------------------------------------------------------- /src/python/curve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | //////////////////////////////////////////////////////////////////////////////// 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | namespace py = pybind11; 19 | 20 | using namespace pybind11::literals; 21 | auto find_order_tet = [](int rows) { 22 | int order = 0; 23 | while ((order + 1) * (order + 2) * (order + 3) < 6 * rows) { 24 | order++; 25 | } 26 | if ((order + 1) * (order + 2) * (order + 3) != 6 * rows) { 27 | throw std::runtime_error( 28 | fmt::format("cp is not a tetrahedron control points, order={}, cp={}", 29 | order, rows)); 30 | } 31 | return order; 32 | }; 33 | auto find_order_tri = [](int rows) { 34 | int order = 0; 35 | while ((order + 1) * (order + 2) < 2 * rows) { 36 | order++; 37 | } 38 | if ((order + 1) * (order + 2) != 2 * rows) { 39 | throw std::runtime_error( 40 | fmt::format("cp is not a tetrahedron control points, order={}, cp={}", 41 | order, rows)); 42 | } 43 | return order; 44 | }; 45 | void python_export_curve(py::module &m) { 46 | m.def( 47 | "tetrahedron_inversion_check", 48 | [](const RowMatd &cp) { 49 | prism::curve::magic_matrices(find_order_tet(cp.rows()) - 1); 50 | return prism::curve::tetrahedron_inversion_check(cp); 51 | }, 52 | "", "cp"_a); 53 | 54 | m.def("clear_curve_cache", []() { 55 | spdlog::info("clear curve cache. For repeated experiment of different orders."); 56 | prism::curve::HelperTensors::tensors_.reset(); 57 | }); 58 | m.def( 59 | "elevated_positive_check", 60 | [](const RowMatd &f_base, const RowMatd &f_top, const RowMatd &lagcp, 61 | bool recurse_check) -> bool { 62 | if (f_base.rows() != 3 || f_top.rows() != 3) { 63 | throw std::runtime_error( 64 | "elevated_positive_check: f_base and f_top must be 3x3 matrices"); 65 | } 66 | auto order = find_order_tri(lagcp.rows()) - 1; 67 | spdlog::trace("triangle order {}", order); 68 | auto helper = 69 | prism::curve::magic_matrices(order, 3); 70 | auto &tri15lag_from_tri10bern = helper.elev_lag_from_bern; 71 | auto &dxyz = helper.volume_data.vec_dxyz; 72 | auto tri4_cod = codecs_gen_id(helper.tri_order + 1, 2); 73 | auto tet4_cod = codecs_gen_id(helper.tri_order + 1, 3); 74 | // 6T {35 x 3D} 75 | auto tens = prism::curve::surface_to_decomposed_tetra( 76 | f_base, lagcp, f_top, f_base.row(0) == f_top.row(0), true, tri4_cod, 77 | tet4_cod); 78 | for (auto &d : dxyz) { 79 | for (auto &t : tens) { 80 | Eigen::Matrix3d j = d * t; 81 | auto det = j.determinant(); 82 | if (det <= 0) { 83 | spdlog::debug("negative {}", det); 84 | return false; 85 | } 86 | } 87 | } 88 | if (recurse_check) { 89 | for (auto &t : tens) { 90 | // this is codec_bc. 91 | if (!prism::curve::tetrahedron_inversion_check(t)) { 92 | spdlog::debug("blocked by recursive"); 93 | return false; 94 | } 95 | } 96 | } 97 | return true; 98 | }, 99 | "f_base"_a, "f_top"_a, "lagrcp"_a, "recurse_check"_a = false, "comment"); 100 | } 101 | -------------------------------------------------------------------------------- /cmake/PrismDependencies.cmake: -------------------------------------------------------------------------------- 1 | 2 | include(PrismDownloadExternal) 3 | 4 | if (NOT TARGET igl::core) 5 | prism_download_libigl() 6 | set(LIBIGL_EIGEN_VERSION 3.3.7) 7 | set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE TRUE) 8 | option(LIBIGL_USE_STATIC_LIBRARY "" OFF) 9 | option(LIBIGL_WITH_CGAL "Use CGAL" ON) 10 | option(LIBIGL_WITH_EMBREE "Use embree" OFF) 11 | set(LIBIGL_INCLUDE_DIR ${PRISM_EXTERNAL}/libigl/include) 12 | find_package(LIBIGL REQUIRED) 13 | endif() 14 | 15 | # fmt 16 | if(NOT TARGET fmt::fmt) 17 | prism_download_fmt() 18 | add_subdirectory(${PRISM_EXTERNAL}/fmt) 19 | endif() 20 | 21 | if(NOT TARGET spdlog::spdlog) 22 | prism_download_spdlog() 23 | add_library(spdlog INTERFACE) 24 | add_library(spdlog::spdlog ALIAS spdlog) 25 | target_include_directories(spdlog INTERFACE ${PRISM_EXTERNAL}/spdlog/include) 26 | target_compile_definitions(spdlog INTERFACE -DSPDLOG_FMT_EXTERNAL) 27 | target_link_libraries(spdlog INTERFACE fmt::fmt) 28 | endif() 29 | 30 | if(NOT TARGET spdlog::spdlog) 31 | prism_download_spdlog() 32 | option(SPDLOG_BUILD_SHARED ON) 33 | option(SPDLOG_FMT_EXTERNAL ON) 34 | add_subdirectory(${PRISM_EXTERNAL}/spdlog) 35 | endif() 36 | 37 | if (NOT TARGET json) 38 | prism_download_json() 39 | add_library(json INTERFACE) 40 | target_include_directories(json SYSTEM INTERFACE ${PRISM_EXTERNAL}/json/include) 41 | endif() 42 | 43 | if(NOT TARGET CLI11::CLI11) 44 | prism_download_cli11() 45 | add_subdirectory(${PRISM_EXTERNAL}/cli11) 46 | endif() 47 | 48 | if(NOT TARGET highfive) 49 | prism_download_HighFive() 50 | option(HIGHFIVE_USE_EIGEN ON) 51 | 52 | find_package(HDF5 REQUIRED) 53 | add_library(highfive INTERFACE) 54 | target_include_directories(highfive SYSTEM INTERFACE ${PRISM_EXTERNAL}/HighFive/include/ ${HDF5_INCLUDE_DIRS}) 55 | target_link_libraries(highfive INTERFACE ${HDF5_LIBRARIES}) 56 | target_compile_definitions(highfive INTERFACE H5_BUILT_AS_DYNAMIC_LIB) 57 | endif() 58 | 59 | if(NOT TARGET geogram::geogram) 60 | prism_download_geogram() 61 | include(geogram) 62 | endif() 63 | 64 | # if (NOT TARGET tbb::tbb) 65 | # prism_download_tbb() 66 | 67 | # set(TBB_BUILD_STATIC ON CACHE BOOL " " FORCE) 68 | # set(TBB_BUILD_SHARED OFF CACHE BOOL " " FORCE) 69 | # set(TBB_BUILD_TBBMALLOC OFF CACHE BOOL " " FORCE) 70 | # set(TBB_BUILD_TBBMALLOC_PROXY OFF CACHE BOOL " " FORCE) 71 | # set(TBB_BUILD_TESTS OFF CACHE BOOL " " FORCE) 72 | 73 | # add_subdirectory(${PRISM_EXTERNAL}/tbb) 74 | 75 | # add_library(tbb::tbb ALIAS tbb_static) 76 | # endif() 77 | 78 | if (NOT TARGET cvc3_rational) 79 | file(DOWNLOAD https://raw.githubusercontent.com/wildmeshing/fTetWild/master/src/external/Rational.h 80 | ${PRISM_EXTERNAL}/rational/Rational.h) 81 | add_library(cvc3_rational INTERFACE) 82 | target_include_directories(cvc3_rational INTERFACE ${PRISM_EXTERNAL}/rational/) 83 | target_link_libraries(cvc3_rational INTERFACE ${GMP_LIBRARIES}) 84 | endif() 85 | 86 | if (NOT TARGET mitsuba_autodiff) 87 | file(DOWNLOAD https://raw.githubusercontent.com/polyfem/polyfem/master/src/polyfem/utils/autodiff.h 88 | ${PRISM_EXTERNAL}/autodiff/autodiff_mitsuba.h) 89 | add_library(mitsuba_autodiff INTERFACE) 90 | target_include_directories(mitsuba_autodiff INTERFACE ${PRISM_EXTERNAL}/autodiff/) 91 | endif() 92 | 93 | if (NOT TARGET osqp) 94 | prism_download_project(osqp 95 | GIT_REPOSITORY https://github.com/oxfordcontrol/osqp.git 96 | GIT_TAG da403d4b41e86b7dc00237047ea4f00354d902ed 97 | # GIT_SHALLOW true 98 | # GIT_SUBMODULES qdldl 99 | ) 100 | add_subdirectory(${PRISM_EXTERNAL}/osqp) 101 | endif() 102 | 103 | if (NOT TARGET libTetShell) 104 | prism_download_tetshell() 105 | option(TETSHELL_LIBONLY ON) 106 | add_subdirectory(${PRISM_EXTERNAL}/tetshell) 107 | endif() 108 | -------------------------------------------------------------------------------- /python/cadconvert/occ_step.py: -------------------------------------------------------------------------------- 1 | from OCC.Core.TColgp import TColgp_HArray1OfPnt2d, TColgp_Array1OfPnt2d,TColgp_Array2OfPnt 2 | from OCC.Core.gp import gp_Pnt2d,gp_Vec, gp_Pnt 3 | from OCC.Core.Geom import Geom_BezierSurface, Geom_BSplineSurface 4 | from OCC.Core.TColGeom import TColGeom_Array2OfBezierSurface 5 | from OCC.Core.GeomConvert import GeomConvert_CompBezierSurfacesToBSplineSurface 6 | from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace 7 | from OCC.Display.SimpleGui import init_display 8 | from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs 9 | from OCC.Core.Interface import Interface_Static_SetCVal 10 | import numpy as np 11 | 12 | def example_vis(surf): 13 | display, start_display, add_menu, add_function_to_menu = init_display() 14 | display.EraseAll() 15 | display.DisplayShape(surf, update=True) 16 | start_display() 17 | 18 | def cp_to_bz(cp): 19 | """Tensor Product (Bezier) Control Points to OCC-BezierSurface object. 20 | 21 | Args: 22 | cp (np.array): input control point, 4x4x3 23 | 24 | Returns: 25 | OCC-Geom_BezierSurface: OCC internal type for a Bezier Patch 26 | """ 27 | array1 = TColgp_Array2OfPnt(1, len(cp), 1, len(cp)) 28 | for i in range(1,len(cp)+1): 29 | for j in range(1,len(cp)+1): 30 | array1.SetValue(i,j, gp_Pnt(*cp[i-1,j-1])) 31 | BZ1 = Geom_BezierSurface(array1) 32 | return BZ1 33 | 34 | def cp_write_to_step(output_file, quad_cp): 35 | step_writer = STEPControl_Writer() 36 | Interface_Static_SetCVal("write.step.schema", "AP203") 37 | 38 | for cp in quad_cp: 39 | assert len(cp) == 16 40 | b1 = cp_to_bz(cp.reshape(4,4,3)) 41 | build = BRepBuilderAPI_MakeFace(b1, 1e-6) 42 | step_writer.Transfer(build.Shape(),STEPControl_AsIs) 43 | status = step_writer.Write(output_file) 44 | 45 | 46 | def compose_bezier(bz_list): 47 | bezierarray = TColGeom_Array2OfBezierSurface(1, len(bz_list), 1,1) 48 | for i,b in enumerate(bz_list): 49 | bezierarray.SetValue(i+1, 1, b) 50 | BB = GeomConvert_CompBezierSurfacesToBSplineSurface(bezierarray) 51 | if BB.IsDone(): 52 | poles = BB.Poles().Array2() 53 | uknots = BB.UKnots().Array1() 54 | vknots = BB.VKnots().Array1() 55 | umult = BB.UMultiplicities().Array1() 56 | vmult = BB.VMultiplicities().Array1() 57 | udeg = BB.UDegree() 58 | vdeg = BB.VDegree() 59 | BSPLSURF = Geom_BSplineSurface( poles, uknots, vknots, umult, vmult, udeg, vdeg, False, False) 60 | 61 | return BSPLSURF 62 | else: 63 | return None 64 | 65 | def test_compose(): 66 | cp = np.zeros((4,4,3)) 67 | cp2 = np.zeros((4,4,3)) 68 | for i in range(4): 69 | for j in range(4): 70 | cp[i,j] = (i-3, j,(i-3)**2) 71 | cp2[i,j] = (i,j,i**2) 72 | b1 = cp_to_bz(cp) 73 | b2 = cp_to_bz(cp2) 74 | bsp = compose_bezier([b1,b2]) 75 | 76 | step_writer = STEPControl_Writer() 77 | Interface_Static_SetCVal("write.step.schema", "AP203") 78 | 79 | build = BRepBuilderAPI_MakeFace(bsp, 1e-6) 80 | step_writer.Transfer(build.Shape(),STEPControl_AsIs) 81 | status = step_writer.Write('test.stp') 82 | 83 | def stripe_writer(out_file, all_stripes, quad_cp): 84 | def rotate(cp, e): 85 | return np.rot90(cp.reshape(4,4,3), k=-e) 86 | step_writer = STEPControl_Writer() 87 | Interface_Static_SetCVal("write.step.schema", "AP203") 88 | 89 | for stripe in all_stripes: 90 | s0cp = np.array([rotate(quad_cp[f],e) for f,e in stripe]) 91 | bzlist = [cp_to_bz(s) for s in s0cp] 92 | if len(stripe) > 1: 93 | bb = compose_bezier(bzlist) 94 | else: 95 | bb = bzlist[0] 96 | step_writer.Transfer(BRepBuilderAPI_MakeFace(bb, 1e-6).Shape(), 97 | STEPControl_AsIs) 98 | status = step_writer.Write(out_file) 99 | return status -------------------------------------------------------------------------------- /src/cumin/curve_common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CUMIN_CURVE_COMMON_HPP 2 | #define CUMIN_CURVE_COMMON_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "prism/common.hpp" 9 | 10 | // template using ArrXX = std::array, 11 | // r>; template using Arri = std::array; 12 | 13 | template 14 | struct MatLexComp { 15 | bool operator()(const T &a, const T &b) const { 16 | return std::lexicographical_compare(a.data(), a.data() + a.size(), b.data(), 17 | b.data() + b.size()); 18 | } 19 | }; 20 | 21 | // Generic Map with a lex comparator that works for Eigen array. 22 | template 23 | using MatLexMap = std::map>; 24 | using MatLexCompi = MatLexComp; 25 | // Map Codec to some indices 26 | using CodecMap = std::map; 27 | 28 | // sorted(face[cod]) with triangle case. 29 | inline Eigen::VectorXi sort_slice(const Vec3i &face, 30 | const Eigen::VectorXi &cod) { 31 | Eigen::VectorXi face_cod = cod; 32 | for (int i = 0; i < cod.size(); i++) { 33 | face_cod[i] = face[cod[i]]; 34 | } 35 | std::sort(face_cod.data(), face_cod.data() + face_cod.size()); 36 | 37 | return face_cod; 38 | }; 39 | 40 | // a sorting based global dof ordering 41 | inline auto global_entry_map(const std::vector &F, 42 | const RowMati &codec) { 43 | auto n = codec.rows(), e = codec.cols(); 44 | CodecMap entries; // map codec to index of cp. 45 | std::vector> 46 | index_entries; // which face, and whose node is in the list. 47 | for (auto i = 0; i < F.size(); i++) 48 | for (int c = 0; c < n; c++) { 49 | auto cnt = index_entries.size(); 50 | auto it = entries.try_emplace(sort_slice(F[i], codec.row(c)), cnt); 51 | if (it.second) // insertion happened 52 | index_entries.emplace_back(i, c); 53 | } 54 | return std::tuple(index_entries, entries); 55 | }; 56 | 57 | template 58 | constexpr auto expand_codec(const std::array &cod) { 59 | std::array r{}; 60 | int k=0; 61 | for (auto i = 0; i < cod.size(); i++) { 62 | for (auto j = 0; j < cod[i]; j++) { 63 | r[k++] = i; // repeat each i by cod[i] times. 64 | } 65 | } 66 | return r; 67 | } 68 | 69 | inline auto codecs_gen(int order, int num_var) { 70 | // num_var = 2 for triangle 3 for tetra 71 | if (num_var == 0) return std::vector>({{order}}); 72 | std::vector> l; 73 | for (int i = 0; i < order + 1; i++) { 74 | auto r = codecs_gen(order - i, num_var - 1); 75 | for (auto t : r) { 76 | t.push_back(i); 77 | l.emplace_back(t); 78 | } 79 | } 80 | auto to_lex = [](auto &a) { 81 | int sqnorm = 0; 82 | for (auto i : a) sqnorm += i * i; 83 | std::vector l(a.rbegin(), a.rend()); 84 | l.insert(l.begin(), -sqnorm); 85 | return l; 86 | }; 87 | std::sort(l.begin(), l.end(), [&to_lex](auto &c, auto &d) { 88 | auto cl = to_lex(c); 89 | auto dl = to_lex(d); 90 | return std::lexicographical_compare(cl.begin(), cl.end(), dl.begin(), 91 | dl.end()); 92 | }); 93 | return l; 94 | }; 95 | 96 | inline auto codec_bc2id(const RowMati& codec_bc){ 97 | auto order = codec_bc(0,0); 98 | assert (order == codec_bc.row(0).sum()); 99 | auto codec_i = RowMati(codec_bc.rows(), order); 100 | for (auto i=0; i 4 | #include 5 | 6 | #include "curve_common.hpp" 7 | 8 | auto factorial = [](int n) { 9 | auto res = 1UL; 10 | for (int i = 1; i <= n; i++) { 11 | res *= i; 12 | } 13 | assert(res > 0); 14 | return res; 15 | }; 16 | 17 | std::vector multinomial_gen(int order, const RowMati& short_codecs) { 18 | std::vector multinomial; 19 | auto faco = factorial(order); 20 | for (int i = 0; i < short_codecs.rows(); i++) { 21 | auto r = faco; 22 | for (int j = 0; j < short_codecs.cols(); j++) { 23 | r /= factorial(short_codecs(i, j)); 24 | } 25 | multinomial.emplace_back(r); 26 | } 27 | return std::move(multinomial); 28 | }; 29 | 30 | std::vector precompute_powers(int order, const Eigen::VectorXd& X, const Eigen::VectorXd& Y, const Eigen::VectorXd& Z) { 31 | std::vector stored_powers(order + 1); 32 | stored_powers[0] = Eigen::ArrayX4d::Ones(X.size(), 4); 33 | stored_powers[1] = Eigen::ArrayX4d::Ones(X.size(), 4); 34 | stored_powers[1].col(0) -= (X + Y + Z).array(); 35 | stored_powers[1].col(1) = X; 36 | stored_powers[1].col(2) = Y; 37 | stored_powers[1].col(3) = Z; 38 | for (int i = 2; i < order + 1; i++) { 39 | stored_powers[i] = stored_powers[i - 1] * stored_powers[1]; 40 | } 41 | return std::move(stored_powers); 42 | } 43 | 44 | Eigen::ArrayXXd prism::curve::evaluate_bernstein(const Eigen::VectorXd& X, 45 | const Eigen::VectorXd& Y, 46 | const Eigen::VectorXd& Z, 47 | const RowMati& short_codecs) { 48 | assert((short_codecs.cols() == 4 || short_codecs.cols() == 3) && 49 | "use the fixed length version of codec"); 50 | int order = short_codecs(0, 0); 51 | assert(order == short_codecs.maxCoeff() && "Short Codecs convention."); 52 | auto multinomial = multinomial_gen(order, short_codecs); 53 | 54 | auto stored_powers = precompute_powers(order, X, Y, Z); 55 | 56 | Eigen::ArrayXXd res(X.size(), multinomial.size()); 57 | for (auto ci = 0; ci < multinomial.size(); ci++) { 58 | auto cod = short_codecs.row(ci); 59 | res.col(ci) = Eigen::ArrayXd::Constant(X.size(), multinomial[ci]); 60 | for (auto i = 0; i < cod.size(); i++) { 61 | auto c = cod[i]; 62 | res.col(ci) *= stored_powers[c].col(i); 63 | } 64 | } 65 | return res; 66 | } 67 | 68 | std::array prism::curve::evaluate_bernstein_derivative( 69 | const Eigen::VectorXd& X, const Eigen::VectorXd& Y, 70 | const Eigen::VectorXd& Z, const RowMati& short_codecs) { 71 | assert(short_codecs.cols() == 4 && "use the fixed length bc version of codec"); 72 | int order = short_codecs.maxCoeff(); 73 | auto multinomial = multinomial_gen(order, short_codecs); 74 | 75 | auto stored_powers = precompute_powers(order, X, Y, Z); 76 | 77 | std::array dxdydz; 78 | double debug_sum = 0; 79 | for (auto d = 0; d < 3; d++) { 80 | dxdydz[d].resize(multinomial.size(), X.size()); 81 | for (auto ci = 0; ci < multinomial.size(); ci++) { 82 | auto mn = multinomial[ci]; 83 | auto cod = short_codecs.row(ci); 84 | Eigen::ArrayXd part1 = Eigen::ArrayXd::Ones(X.size()) * cod[d + 1]; 85 | for (auto i = 0; i < short_codecs.cols(); i++) { 86 | auto e = cod[i]; 87 | if (i != d + 1) { // normal powers 88 | part1 *= stored_powers[e].col(i); 89 | } else if (e == 0) { 90 | part1 *= 0; 91 | } else { // when d+1 == i and e != 0 92 | part1 *= stored_powers[e - 1].col(i); 93 | } 94 | } // dx part 95 | if (cod[0] != 0) { 96 | auto e0 = cod[0]; 97 | Eigen::ArrayXd part2 = e0 * stored_powers[e0 - 1].col(0); 98 | for (int i = 1; i < cod.size(); i++) 99 | part2 *= stored_powers[cod[i]].col(i); 100 | 101 | part1 -= part2; 102 | } // dw part 103 | debug_sum += part1.square().sum(); 104 | dxdydz[d].row(ci) = multinomial[ci] * part1; 105 | } // over all basis 106 | } // over all variables (dx,dy,dz) 107 | return std::move(dxdydz); 108 | } -------------------------------------------------------------------------------- /src/getRSS.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: David Robert Nadeau 3 | * Site: http://NadeauSoftware.com/ 4 | * License: Creative Commons Attribution 3.0 Unported License 5 | * http://creativecommons.org/licenses/by/3.0/deed.en_US 6 | */ 7 | 8 | #if defined(_WIN32) 9 | #include 10 | #include 11 | 12 | #elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) 13 | #include 14 | #include 15 | 16 | #if defined(__APPLE__) && defined(__MACH__) 17 | #include 18 | 19 | #elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__))) 20 | #include 21 | #include 22 | 23 | #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) 24 | #include 25 | 26 | #endif 27 | 28 | #else 29 | #error "Cannot define getPeakRSS( ) or getCurrentRSS( ) for an unknown OS." 30 | #endif 31 | 32 | 33 | 34 | 35 | 36 | /** 37 | * Returns the peak (maximum so far) resident set size (physical 38 | * memory use) measured in bytes, or zero if the value cannot be 39 | * determined on this OS. 40 | */ 41 | size_t getPeakRSS( ) 42 | { 43 | #if defined(_WIN32) 44 | /* Windows -------------------------------------------------- */ 45 | PROCESS_MEMORY_COUNTERS info; 46 | GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) ); 47 | return (size_t)info.PeakWorkingSetSize; 48 | 49 | #elif (defined(_AIX) || defined(__TOS__AIX__)) || (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__))) 50 | /* AIX and Solaris ------------------------------------------ */ 51 | struct psinfo psinfo; 52 | int fd = -1; 53 | if ( (fd = open( "/proc/self/psinfo", O_RDONLY )) == -1 ) 54 | return (size_t)0L; /* Can't open? */ 55 | if ( read( fd, &psinfo, sizeof(psinfo) ) != sizeof(psinfo) ) 56 | { 57 | close( fd ); 58 | return (size_t)0L; /* Can't read? */ 59 | } 60 | close( fd ); 61 | return (size_t)(psinfo.pr_rssize * 1024L); 62 | 63 | #elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__)) 64 | /* BSD, Linux, and OSX -------------------------------------- */ 65 | struct rusage rusage; 66 | getrusage( RUSAGE_SELF, &rusage ); 67 | #if defined(__APPLE__) && defined(__MACH__) 68 | return (size_t)rusage.ru_maxrss; 69 | #else 70 | return (size_t)(rusage.ru_maxrss * 1024L); 71 | #endif 72 | 73 | #else 74 | /* Unknown OS ----------------------------------------------- */ 75 | return (size_t)0L; /* Unsupported. */ 76 | #endif 77 | } 78 | 79 | 80 | 81 | 82 | 83 | /** 84 | * Returns the current resident set size (physical memory use) measured 85 | * in bytes, or zero if the value cannot be determined on this OS. 86 | */ 87 | size_t getCurrentRSS( ) 88 | { 89 | #if defined(_WIN32) 90 | /* Windows -------------------------------------------------- */ 91 | PROCESS_MEMORY_COUNTERS info; 92 | GetProcessMemoryInfo( GetCurrentProcess( ), &info, sizeof(info) ); 93 | return (size_t)info.WorkingSetSize; 94 | 95 | #elif defined(__APPLE__) && defined(__MACH__) 96 | /* OSX ------------------------------------------------------ */ 97 | struct mach_task_basic_info info; 98 | mach_msg_type_number_t infoCount = MACH_TASK_BASIC_INFO_COUNT; 99 | if ( task_info( mach_task_self( ), MACH_TASK_BASIC_INFO, 100 | (task_info_t)&info, &infoCount ) != KERN_SUCCESS ) 101 | return (size_t)0L; /* Can't access? */ 102 | return (size_t)info.resident_size; 103 | 104 | #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__) 105 | /* Linux ---------------------------------------------------- */ 106 | long rss = 0L; 107 | FILE* fp = NULL; 108 | if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL ) 109 | return (size_t)0L; /* Can't open? */ 110 | if ( fscanf( fp, "%*s%ld", &rss ) != 1 ) 111 | { 112 | fclose( fp ); 113 | return (size_t)0L; /* Can't read? */ 114 | } 115 | fclose( fp ); 116 | return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE); 117 | 118 | #else 119 | /* AIX, BSD, Solaris, and Unknown OS ------------------------ */ 120 | return (size_t)0L; /* Unsupported. */ 121 | #endif 122 | } 123 | -------------------------------------------------------------------------------- /src/prism/geogram/geogram_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "geogram_utils.hpp" 2 | 3 | namespace { 4 | 5 | #ifdef WIN32 6 | int setenv(const char* name, const char* value, int overwrite) 7 | { 8 | int errcode = 0; 9 | if (!overwrite) { 10 | size_t envsize = 0; 11 | errcode = getenv_s(&envsize, NULL, 0, name); 12 | if (errcode || envsize) return errcode; 13 | } 14 | return _putenv_s(name, value); 15 | } 16 | #endif 17 | 18 | } // namespace 19 | 20 | void prism::geo::init_geogram() { 21 | static bool first_time = true; 22 | 23 | if (first_time) { 24 | first_time = false; 25 | } else { 26 | return; 27 | } 28 | 29 | // Do not install custom signal handlers 30 | setenv("GEO_NO_SIGNAL_HANDLER", "1", 1); 31 | 32 | // Init logger first so we can hide geogram output from init 33 | GEO::Logger::initialize(); 34 | 35 | // Do not show geogram output 36 | GEO::Logger::instance()->unregister_all_clients(); 37 | GEO::Logger::instance()->set_quiet(true); 38 | 39 | #if 0 40 | // Use the following code to disable multi-threading in geogram (for debugging purposes). 41 | GEO::Process::enable_multithreading(false); 42 | GEO::Process::set_max_threads(1); 43 | #endif 44 | 45 | // Initialize global instances (e.g., logger), register MeshIOHandler, etc. 46 | GEO::initialize(GEO::GEOGRAM_NO_HANDLER); 47 | 48 | // Import standard command line arguments, and custom ones 49 | GEO::CmdLine::import_arg_group("standard"); 50 | GEO::CmdLine::import_arg_group("pre"); 51 | GEO::CmdLine::import_arg_group("algo"); 52 | // GEO::CmdLine::import_arg_group("sys"); 53 | GEO::CmdLine::set_arg("sys:assert", "throw"); 54 | } 55 | 56 | void prism::geo::to_geogram_mesh(const RowMatd &V, const RowMati &F, 57 | GEO::Mesh &M) { 58 | init_geogram(); 59 | M.clear(); 60 | // Setup vertices 61 | M.vertices.create_vertices((int)V.rows()); 62 | for (int i = 0; i < (int)M.vertices.nb(); ++i) { 63 | GEO::vec3 &p = M.vertices.point(i); 64 | p[0] = V(i, 0); 65 | p[1] = V(i, 1); 66 | p[2] = V(i, 2); 67 | } 68 | { 69 | GEO::Attribute indices(M.vertices.attributes(), "vertex_id"); 70 | for (int i = 0; i < V.rows(); i++) 71 | indices[i] = i; 72 | } 73 | 74 | if (F.cols() == 2) { 75 | M.edges.create_edges((int)F.rows()); 76 | for (int i = 0; i < F.rows(); i++) 77 | for (int j = 0; j < F.cols(); j++) { 78 | M.edges.set_vertex(i,j,F(i,j)); 79 | } 80 | GEO::Attribute indices(M.edges.attributes(), "edge_id"); 81 | for (int i = 0; i < F.rows(); i++) 82 | indices[i] = i; 83 | } else if (F.cols() == 3) { 84 | M.facets.create_triangles((int)F.rows()); 85 | // Setup faces 86 | for (int i = 0; i < F.rows(); i++) 87 | for (int j = 0; j < F.cols(); j++) 88 | M.facets.set_vertex(i, j, F(i, j)); 89 | M.facets.connect(); 90 | GEO::Attribute indices(M.facets.attributes(), "facet_id"); 91 | for (int i = 0; i < F.rows(); i++) 92 | indices[i] = i; 93 | 94 | } else { 95 | M.cells.create_tets((int)F.rows()); 96 | // Setup faces 97 | for (int i = 0; i < F.rows(); i++) 98 | for (int j = 0; j < 4; j++) 99 | M.cells.set_vertex(i, j, F(i, j)); 100 | M.cells.connect(); 101 | GEO::Attribute indices(M.cells.attributes(), "cell_id"); 102 | for (int i = 0; i < F.rows(); i++) 103 | indices[i] = i; 104 | } 105 | } 106 | 107 | void prism::geo::from_geogram_mesh(const GEO::Mesh &M, RowMatd &V, RowMati &T) { 108 | init_geogram(); 109 | V.resize(M.vertices.nb(), 3); 110 | for (int i = 0; i < (int)M.vertices.nb(); ++i) { 111 | GEO::vec3 p = M.vertices.point(i); 112 | V.row(i) << p[0], p[1], p[2]; 113 | } 114 | if (M.cells.nb() > 0) { 115 | assert(M.cells.are_simplices()); 116 | T.resize(M.cells.nb(), 4); 117 | for (int c = 0; c < (int)M.cells.nb(); ++c) { 118 | for (int lv = 0; lv < 4; ++lv) { 119 | T(c, lv) = M.cells.vertex(c, lv); 120 | } 121 | } 122 | } else if (M.facets.nb() > 0) { 123 | assert(M.facets.are_simplices()); 124 | T.resize(M.facets.nb(), 3); 125 | for (int c = 0; c < (int)M.facets.nb(); ++c) { 126 | for (int lv = 0; lv < 4; ++lv) { 127 | T(c, lv) = M.facets.vertex(c, lv); 128 | } 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/prism/cage_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PRIISM_CAGE_UTILS_HPP 2 | #define PRIISM_CAGE_UTILS_HPP 3 | #include 4 | // #include 5 | namespace prism::geogram{struct AABB;}; 6 | namespace prism {struct HashGrid;}; 7 | namespace prism::cage_utils { 8 | 9 | // terminology from On the ‘most normal’ normal—Part 2, Aubry et al. 10 | bool most_visible_normals(const RowMatd& V, const RowMati& F, RowMatd& VN, 11 | std::set& omni_saddle, double tolerance_singularity = 1e-2); 12 | 13 | // hint with ray cast. Volume shrinking step. 14 | std::vector extrude_along_normals(const RowMatd& V, const RowMati& F, 15 | const prism::geogram::AABB& tree, 16 | const RowMatd& N, bool outward, 17 | int num_cons, double initial_step); 18 | // no hint version 19 | std::vector volume_extrude_steps(const RowMatd& V, const RowMati& F, 20 | const RowMatd& N, bool outward, 21 | int num_cons, 22 | const std::vector& ray_step); 23 | 24 | // iteratively retract with respect to AABB collision. 25 | void iterative_retract_normal(const RowMatd& V, const RowMati& F, 26 | const prism::geogram::AABB& tree, 27 | const RowMatd& N_in, bool outward, int num_cons, 28 | std::vector& alpha); 29 | 30 | // call above twice and retract for intersections. 31 | void extrude_for_base_and_top(const RowMatd& V, const RowMati& F, 32 | const prism::geogram::AABB& tree, 33 | const RowMatd& N, int num_cons, RowMatd& inner, 34 | RowMatd& outer, double initial_step); 35 | 36 | // modifies top in place. 37 | void hashgrid_shrink(const std::vector &mid, std::vector &top, 38 | const std::vector &vecF, const std::vector>& VF); 39 | 40 | bool safe_shrink(const std::vector &mid, std::vector &top, 41 | const std::vector &vecF, const std::vector>& VF); 42 | 43 | void reorder_singularity_to_front(RowMatd& V, RowMati& F, RowMatd& VN, 44 | const std::set& omni_singu, 45 | Eigen::VectorXi& idx_map); 46 | 47 | // mark out the "singularity-like" vertices on the border 48 | void mark_singular_on_border(const RowMatd& V, const RowMati& F, RowMatd& VN, 49 | std::set& omni_sing); 50 | 51 | // prism_id = tet_id / 3, intra: base-top 0,1,2 52 | // V = stack([base, top]) 53 | void tetmesh_from_prismcage(const std::vector& base, 54 | const std::vector& top, 55 | const std::vector& F, std::vector& V, 56 | std::vector& T); 57 | 58 | // prism_id = tet_id / 6, intra: base-mid 0,1,2 then mid-top 3,4,5 59 | // V = stack([base, mid, top]) 60 | // auto prism_id = tet_id / 6; 61 | // auto pillar_id = tet_id % 3; 62 | // bool bottom = (tet_id%6) < 3; 63 | void tetmesh_from_prismcage(const std::vector& base, 64 | const std::vector& mid, 65 | const std::vector& top, 66 | const std::vector& F, int num_singularity, 67 | std::vector& V, std::vector& T); 68 | 69 | // verification function 70 | bool all_volumes_are_positive(const std::vector& base, 71 | const std::vector& mid, 72 | const std::vector& top, 73 | const std::vector& F, int num_cons); 74 | 75 | std::map, int> split_singular_edges(RowMatd& V, RowMati& F, RowMatd& VN, 76 | const std::set& omni_singu); 77 | 78 | void recover_positive_volumes(std::vector& mid, std::vector& top, 79 | const std::vector& F, const RowMatd& VN, 80 | const std::vector>& VF, 81 | int num_cons, 82 | bool outward); 83 | } // namespace prism::cage_utils 84 | 85 | #endif -------------------------------------------------------------------------------- /src/curve_in_shell.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void feature_and_curve(std::string filename, std::string fgname, 12 | std::string ser_file, nlohmann::json config); 13 | 14 | auto dict_to_option(nlohmann::json &config, CLI::App &program) -> void { 15 | for (auto &[cmd, subopt] : config.items()) { 16 | for (auto &[key, val] : subopt.items()) { 17 | if (val.is_boolean()) { 18 | program.add_flag( 19 | fmt::format("--{}-{}, !--{}-no-{}", cmd, key, cmd, key), 20 | val.get_ref()); 21 | } else if (val.is_number_integer()) { 22 | program.add_option(fmt::format("--{}-{}", cmd, key), 23 | val.get_ref(), 24 | "int"); 25 | } else if (val.is_number_float()) { 26 | program.add_option(fmt::format("--{}-{}", cmd, key), 27 | val.get_ref(), 28 | "double"); 29 | } else { 30 | throw std::runtime_error("Option Type not implemented."); 31 | } 32 | } 33 | } 34 | } 35 | 36 | int main(int argc, char **argv) { 37 | CLI::App program{"Bijective and Coarse High-Order Tetrahedral Meshes."}; 38 | 39 | std::string filename, output_dir = "./", input_file, feature_graph_file, 40 | log_dir = ""; 41 | program.add_option("-i,--input", input_file, "input mesh name") 42 | ->required() 43 | ->check(CLI::ExistingFile) 44 | ->each([&filename](const std::string &s) { 45 | filename = std::filesystem::path(s).filename().string(); 46 | }); 47 | program.add_option("-g,--graph", feature_graph_file, "feature graph .fgraph"); 48 | program.add_option("-o,--output", output_dir, "output dir") 49 | ->default_str("./"); 50 | program.add_option("-l,--logdir", log_dir, "log dir"); 51 | program.add_option_function( 52 | "--loglevel", 53 | [](const int &l) { 54 | spdlog::set_level(static_cast(l)); 55 | }, 56 | "log level"); 57 | 58 | std::string suffix = ""; 59 | program.add_option("--suffix", suffix, "suffix identifier"); 60 | 61 | auto config = nlohmann::json(); 62 | config["curve"] = {{"order", 3}, 63 | {"distance_threshold", 1e-2}, 64 | {"normal_threshold", -1.0}, 65 | {"recursive_check", true}}; 66 | config["shell"] = {{"initial_thickness", 1e-2}, 67 | {"target_edge_length", 1e-1}, 68 | {"distortion_bound", 0.01}, 69 | {"target_thickness", 5e-2}}; 70 | config["feature"] = { 71 | {"enable_polyshell", false}, 72 | {"initial_split_edge", 2e-1}, 73 | {"dihedral_threshold", 0.5} // 120 degree. 74 | }; 75 | config["control"] = { 76 | {"enable_curve", true}, 77 | {"reset_cp", false}, // this is a experiment switch: reset linear cp so 78 | // that we can load a un-curved intermediate model. 79 | {"serialize_level", 2}, 80 | {"freeze_feature", false}, 81 | {"only_initial", false}, 82 | {"skip_collapse", false}, 83 | {"skip_split", true}, 84 | {"skip_volume", false}, 85 | {"danger_relax_precondition", false}, // this is a experiment switch: bypass thresholds in precondition, the result may or may not encounter floating point failures. 86 | }; 87 | config["tetfill"] = {{"tetwild", true}}; 88 | config["cutet"] = { 89 | {"debug", false}, 90 | {"passes", 6}, 91 | {"smooth_iter", 4}, 92 | {"energy_threshold", 100}, 93 | }; 94 | dict_to_option(config, program); 95 | 96 | program.callback([&]() { 97 | filename = std::filesystem::path(input_file).filename().string(); 98 | if (log_dir != "") { 99 | auto file_logger = spdlog::basic_logger_mt( 100 | "cumin", log_dir + "/" + filename + suffix + ".log"); 101 | spdlog::set_default_logger(file_logger); 102 | } 103 | spdlog::flush_on(spdlog::level::info); 104 | spdlog::info("{}", config.dump()); 105 | feature_and_curve(input_file, feature_graph_file, 106 | output_dir + "/" + filename + suffix + ".h5", config); 107 | }); 108 | 109 | CLI11_PARSE(program, argc, argv); 110 | } 111 | -------------------------------------------------------------------------------- /src/python/spatial.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | //////////////////////////////////////////////////////////////////////////////// 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace py = pybind11; 15 | 16 | using namespace pybind11::literals; 17 | 18 | void python_export_spatial(py::module& m) { 19 | // AABB 20 | py::class_ > AABB2(m, "AABB2", "using igl::AABB for 2D case, not very robust."); 21 | AABB2.def(py::init<>()) 22 | .def(py::init&>()) 23 | .def("init", 24 | [](igl::AABB& tree, const Eigen::MatrixXd& V, 25 | const Eigen::MatrixXi& Ele) { 26 | return tree.init(V, Ele, 27 | Eigen::Matrix(), 28 | Eigen::Matrix(), 29 | Eigen::VectorXi(), 0); 30 | }) 31 | .def("squared_distance", 32 | [](const igl::AABB& tree, 33 | const Eigen::MatrixXd& V, const Eigen::MatrixXi& Ele, 34 | const Eigen::MatrixXd& P, Eigen::MatrixXd& sqrD, 35 | Eigen::MatrixXi& I, Eigen::MatrixXd& C) { 36 | return tree.squared_distance(V, Ele, P, sqrD, I, C); 37 | }) 38 | .def("find", 39 | [](const igl::AABB& tree, 40 | const Eigen::MatrixXd& V, const Eigen::MatrixXi& Ele, 41 | const Eigen::RowVectorXd& q, bool first = true) { 42 | auto f = tree.find(V, Ele, q, first); 43 | if (f.size() == 0) 44 | return -1; 45 | else 46 | return f[0]; 47 | }) 48 | .def("find_all", [](const igl::AABB& tree, 49 | const Eigen::MatrixXd& V, const Eigen::MatrixXi& Ele, 50 | const Eigen::MatrixXd& q) { 51 | Eigen::VectorXi Fid(q.rows()); 52 | Fid.setConstant(-1); 53 | Eigen::MatrixXd BC = Eigen::MatrixXd::Zero(q.rows(), 3); 54 | for (int i = 0; i < q.rows(); i++) { 55 | auto f = tree.find(V, Ele, q.row(i), true); 56 | if (f.size() != 0) { 57 | Fid(i) = f[0]; 58 | } 59 | } 60 | int n = q.rows(); 61 | Eigen::MatrixXd A(n, 2), B(n, 2), C(n, 2); 62 | auto F = Ele; 63 | for (int i = 0; i < n; i++) { 64 | auto f = Fid(i); 65 | if (f != -1) { 66 | A.row(i) = V.row(F(f, 0)); 67 | B.row(i) = V.row(F(f, 1)); 68 | C.row(i) = V.row(F(f, 2)); 69 | } else { 70 | A.row(i) = V.row(0); 71 | B.row(i) = V.row(0); 72 | C.row(i) = V.row(0); 73 | } 74 | } 75 | igl::barycentric_coordinates(q, A, B, C, BC); 76 | return std::make_tuple(Fid, BC); 77 | }); 78 | 79 | py::class_ AABB(m, "AABB"); 80 | AABB.def(py::init()) 81 | .def("intersects_triangle", 82 | [](const prism::geogram::AABB& self, const Vec3d& P0, const Vec3d& P1, 83 | const Vec3d& P2) { 84 | return self.intersects_triangle({P0, P1, P2}); 85 | }) 86 | .def("segment_query", 87 | [](const prism::geogram::AABB& self, const Vec3d& P0, const Vec3d& P1) { 88 | return self.segment_query(P0, P1); 89 | }) 90 | .def("segment_hit", [](const prism::geogram::AABB& self, const Vec3d& P0, 91 | const Vec3d& P1, bool ray = false) { 92 | prism::Hit hit; 93 | bool result = self.segment_hit(P0, P1, hit); 94 | if (result) 95 | return std::tuple(hit.id, hit.u, hit.v, hit.t); 96 | else 97 | return std::tuple(-1, 0., 0., -1.); 98 | }); 99 | 100 | m.def("self_intersect", [] 101 | ( const Eigen::MatrixXd & V, 102 | const Eigen::MatrixXi& F)->bool { 103 | std::vector vecV; 104 | std::vector vecF; 105 | eigen2vec(V, vecV); 106 | eigen2vec(V, vecV); 107 | auto pairs = prism::spatial_hash::self_intersections(vecV,vecF); 108 | return pairs.size() > 0; 109 | }, "use spatial hash for self intersection check", "V"_a, "F"_a); 110 | } 111 | -------------------------------------------------------------------------------- /tests/curve_fitting.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | #include "cumin/bernstein_eval.hpp" 23 | #include "cumin/curve_common.hpp" 24 | #include "cumin/curve_utils.hpp" 25 | #include "prism/PrismCage.hpp" 26 | #include "prism/geogram/AABB.hpp" 27 | #include "prism/local_operations/remesh_pass.hpp" 28 | 29 | TEST_CASE("Bernstein Evaluators") { 30 | RowMati short_codecs(35, 4); 31 | short_codecs << 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 3, 1, 0, 0, 1, 32 | 3, 0, 0, 3, 0, 1, 0, 0, 3, 1, 0, 1, 0, 3, 0, 0, 1, 3, 0, 3, 0, 0, 1, 0, 3, 33 | 0, 1, 0, 0, 3, 1, 1, 0, 0, 3, 0, 1, 0, 3, 0, 0, 1, 3, 2, 2, 0, 0, 2, 0, 2, 34 | 0, 0, 2, 2, 0, 2, 0, 0, 2, 0, 2, 0, 2, 0, 0, 2, 2, 2, 1, 1, 0, 1, 2, 1, 0, 35 | 1, 1, 2, 0, 2, 1, 0, 1, 1, 2, 0, 1, 2, 0, 1, 1, 0, 2, 1, 1, 1, 0, 2, 1, 0, 36 | 1, 2, 1, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1, 2, 1, 1, 1, 1; 37 | 38 | Eigen::ArrayXd X(35), Y(35), Z(35); 39 | for (int i = 0; i < 35; i++) { 40 | X[i] = i; 41 | Y[i] = i + 35; 42 | Z[i] = i + 70; 43 | } 44 | X = X.sin().array() + 1; 45 | Y = Y.sin().array() + 1; 46 | Z = Z.sin().array() + 1; 47 | auto r = prism::curve::evaluate_bernstein(X, Y, Z, short_codecs); 48 | CHECK(r.square().sum() == doctest::Approx(3404647.5544482986)); 49 | 50 | auto r1 = prism::curve::evaluate_bernstein_derivative(X, Y, Z, short_codecs); 51 | CHECK(r1[0].square().sum() == doctest::Approx(6337101.645255285)); 52 | CHECK(r1[1].square().sum() == doctest::Approx(4524720.611048738)); 53 | CHECK(r1[2].square().sum() == doctest::Approx(6333786.654051635)); 54 | } 55 | 56 | #include "cumin/inversion_check.hpp" 57 | TEST_CASE("recursive-inversion") { 58 | H5Easy::File file("../python/curve/data/tetra_o9_l2b.h5"); 59 | RowMatd bern_from_lagr_o9 = H5Easy::load(file, "l2b"); 60 | RowMati codecs_o4(35, 4), codecs_o9(220, 4); 61 | vec2eigen(codecs_gen(4, 3), codecs_o4); 62 | vec2eigen(codecs_gen(9, 3), codecs_o9); 63 | 64 | spdlog::set_level(spdlog::level::info); 65 | std::map tests = {{1e-1, true}, {1, false}}; 66 | for (auto [case_num, answer] : tests) { 67 | RowMatd cp(35, 3); 68 | for (int i = 0; i < 35; i++) { 69 | for (int j = 0; j < 3; j++) { 70 | cp(i, j) = i * 3 + j; 71 | } 72 | } 73 | cp = codecs_o4.rightCols(3).cast() - 74 | case_num * (cp.array().sin().matrix()).eval(); 75 | 76 | // CHECK(prism::curve::tetrahedron_inversion_check( 77 | // cp, codecs_o4, codecs_o9, bern_from_lagr_o9) == answer); 78 | } 79 | } 80 | #include "cumin/curve_utils.hpp" 81 | 82 | TEST_CASE("singularity in curve") { 83 | std::string filename = "../buildr/1582416.stl.h5"; 84 | auto pc = PrismCage(filename); 85 | auto complete_cp = prism::curve::load_cp(filename); 86 | 87 | spdlog::set_level(spdlog::level::debug); 88 | for (auto i : {5}) { 89 | // for (auto i = 0; i vec_dxyz; 105 | auto tet4_dxyz = H5Easy::load>>>(file, "dxyz"); 106 | vec_dxyz.resize(tet4_dxyz[0].size()); 107 | for (auto i1 = 0; i1 < tet4_dxyz[0].size(); i1++) { 108 | vec_dxyz[i1].resize(tet4_dxyz.size(), tet4_dxyz[0][0].size()); 109 | for (auto i0 = 0; i0 < tet4_dxyz.size(); i0++) 110 | for (auto i2 = 0; i2 < tet4_dxyz[0][0].size(); i2++) 111 | vec_dxyz[i1](i0, i2) = tet4_dxyz[i0][i1][i2]; 112 | } 113 | spdlog::critical("vecdxyz 0, 20 {}", vec_dxyz[10].row(2)); 114 | spdlog::critical("vecdxyz {} x{}x{}", vec_dxyz.size(), vec_dxyz[0].rows(), vec_dxyz[0].cols()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /python/cadconvert/tensor-product-fit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | import fem_tabulator as feta 4 | import h5py 5 | import quad_curve_utils as qr 6 | import quad_utils 7 | import numpy as np 8 | import igl 9 | import sys 10 | sys.path.append('/home/zhongshi/Workspace/bichon/python/debug') 11 | import prism 12 | import os 13 | import scipy 14 | import bezier as qb 15 | import tqdm 16 | from occ_step import cp_write_to_step 17 | import occ_step 18 | 19 | def valid_pairing(faces, score, sharp_markers, valid_combine_func): 20 | tt, tti = igl.triangle_triangle_adjacency(faces) 21 | occupied = -np.ones(len(faces),dtype=int) 22 | qid = 0 23 | pairs = [] 24 | queue = [] 25 | for fi in range(len(faces)): 26 | for e in range(3): 27 | if sharp_markers[fi,e]: 28 | continue 29 | queue.append((score(fi,e), fi,e)) 30 | queue = sorted(queue) 31 | for _, fi,e in queue: 32 | if occupied[fi] >= 0: 33 | continue 34 | fo = tt[fi,e] 35 | 36 | if fo < 0: 37 | continue 38 | if occupied[fo] >= 0: 39 | continue 40 | 41 | if not valid_combine_func(fi, fo): # combine fi with fo. 42 | continue 43 | 44 | occupied[fi] = fo 45 | occupied[fo] = fi 46 | # q = list(faces[fi]) 47 | # q.insert(e+1, faces[fo][tti[fi,e]-1]) 48 | # pairs.append(q) 49 | qid += 1 50 | return occupied, pairs 51 | 52 | 53 | def main(input_file, output_file = None, order =3, level=6, post_check=False): 54 | with h5py.File(input_file, 'r') as f: 55 | V,F,refV,refF,inpV,mB,mT = map(lambda x:f[x][()], ('mV','mF','ref.V','ref.F','inpV','mbase','mtop')) 56 | 57 | ## Bezier fitting 58 | A = scipy.sparse.coo_matrix(qb.bezier_fit_matrix(order, level)).tocsr() 59 | query = qr.query 60 | query.aabb, query.F, query.mB, query.mT, query.inpV, query.refF = prism.AABB(refV, refF), F, mB, mT, inpV, refF 61 | 62 | def valid_combine_func(fi, fo) -> bool: 63 | # First fit 64 | quad, trims = qr.combine_tris(F[fi], F[fo]) 65 | tbc0 = np.array(qr.sample_for_quad_trim(trims[0], trims[1], level), 66 | dtype=int) 67 | tbc0[:, 0] = np.asarray([fi, fo])[tbc0[:, 0]] 68 | sample_vals = query(tbc0, denom=level) 69 | local_cp = qr.quadratic_minimize(A, sample_vals) 70 | # Second, check 71 | v = qb.bezier_check_validity(mB, mT, F[[fi,fo]], quad.reshape(-1,4), np.array([[0,1]]), 72 | trims, np.array([local_cp]), order, 73 | valid_check = prism.elevated_positive_check, 74 | progress_bar=False) 75 | 76 | return v 77 | siblings, _ = valid_pairing(F, score = lambda f,e: -np.linalg.norm(V[F[f,e]] - V[F[f,(e+1)%3]]), 78 | sharp_markers=quad_utils.edge_dots(V,F) < np.cos(np.pi/4), 79 | valid_combine_func=valid_combine_func) 80 | print('empty siblings', np.count_nonzero(siblings == -1), '/', len(siblings)) 81 | t2q, q2t,trim_types, quads = qr.quad_trim_assign(siblings, F) 82 | 83 | 84 | quad_cp, samples = qr.quad_fit(V, F, quads, q2t, trim_types, level, order, A, query, None) 85 | 86 | if post_check: 87 | valids = bezier_check_validity(mB, mT, F,quads, q2t, trim_types, quad_cp, 3) 88 | 89 | print('valids', np.count_nonzero(valids), '/', len(valids)) 90 | # adjust quads, quads_cp, q2t, t2q based on validity 91 | siblings[q2t[valids==False].flatten()] = -1 92 | quads = quads[valids] 93 | quad_cp = quad_cp[valids] 94 | q2t = q2t[valids] 95 | t2q = np.ones_like(t2q) * -1 96 | for q, (t0,t1) in enumerate(q2t): 97 | t2q[t0] = q 98 | t2q[t1] = q 99 | 100 | new_v, known_cp, newquads = qr.solo_cc_split(V, F, siblings, t2q, quads, quad_cp, order, subd=None) 101 | cc_cp = qr.constrained_cc_fit(V, F, siblings, newquads, known_cp, level, order, A, query) 102 | if output_file is None: 103 | output_file = f'/home/zhongshi/ntopo/ntopmodels/fit/{os.path.basename(input_file)}' 104 | _, stripe0 = quad_utils.group_quads(quads) 105 | _, stripe1 = quad_utils.group_quads(np.array(newquads)) 106 | print(f'Quad Counts: {len(quads)} + {len(newquads)}') 107 | print(f'Stripe Counts: {len(stripe0)} + {len(stripe1)}') 108 | offset = lambda x: (x[0] + len(quads), x[1]) 109 | occ_step.stripe_writer(output_file + '.stp', 110 | stripe0 + [list(map(offset, stripe)) for stripe in stripe1], 111 | np.vstack([quad_cp,cc_cp])) 112 | 113 | np.savez(output_file + '.npz', quad_cp = quad_cp, cc_cp = cc_cp, quads=quads, newquads=newquads, 114 | stripe0=stripe0, stripe1=stripe1) 115 | 116 | def test_stripe(): 117 | with np.load('temp.npz') as npl: 118 | quads, quad_cp = npl['quads'], npl['quad_cp'] 119 | 120 | # quads -- quad_cp 121 | stripe_paint, all_stripes = quad_utils.group_quads(quads) 122 | print('num of stripes', len(all_stripes)) 123 | stripe_writer(all_stripes, 'quad_cp, stripe.stp') 124 | 125 | if __name__ == '__main__': 126 | import fire 127 | fire.Fire(main) 128 | -------------------------------------------------------------------------------- /tests/feature_parse_bin.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "prism/PrismCage.hpp" 20 | #include "prism/cage_check.hpp" 21 | #include "prism/geogram/AABB.hpp" 22 | #include "prism/local_operations/remesh_pass.hpp" 23 | #include "prism/local_operations/retain_triangle_adjacency.hpp" 24 | #include "prism/spatial-hash/AABB_hash.hpp" 25 | #include "prism/spatial-hash/self_intersection.hpp" 26 | #include "test_common.hpp" 27 | 28 | #define DATA_PATH "../build_clang/" 29 | 30 | #include "prism/local_operations/remesh_with_feature.hpp" 31 | namespace prism::curve { 32 | void localcurve_pass(PrismCage &pc, const prism::local::RemeshOptions &option); 33 | } 34 | auto post_collapse = [](auto &complete_cp) { 35 | complete_cp.erase(std::remove_if(complete_cp.begin(), complete_cp.end(), 36 | [](auto &c) { return c(0, 0) == -1; }), 37 | complete_cp.end()); 38 | }; 39 | 40 | auto collision_checker = [](auto &pc) { 41 | if (!prism::spatial_hash::self_intersections(pc.top, pc.F).empty()) { 42 | spdlog::error("top inter"); 43 | return false; 44 | } 45 | if (!prism::spatial_hash::self_intersections(pc.base, pc.F).empty()) { 46 | spdlog::error("base inter"); 47 | return false; 48 | } 49 | if (!prism::cage_check::cage_is_away_from_ref(pc)) { 50 | spdlog::error("X inter"); 51 | return false; 52 | } 53 | return true; 54 | }; 55 | 56 | auto reverse_feature_order = [](PrismCage &pc, 57 | prism::local::RemeshOptions &option) { 58 | decltype(pc.meta_edges) meta; 59 | for (auto [a, b] : pc.meta_edges) { 60 | auto a1 = std::pair{a.second, a.first}; 61 | auto b1 = b; 62 | b1.second = std::vector(b.second.rbegin(), b.second.rend()); 63 | meta.emplace(a1, b1); 64 | } 65 | pc.meta_edges = std::move(meta); 66 | auto &crt = option.chain_reject_trackee; 67 | for (int i = 0; i < crt.size(); i += 2) { 68 | std::swap(crt[i], crt[i + 1]); 69 | } 70 | }; 71 | 72 | #include "prism/energy/prism_quality.hpp" 73 | double total_energy(const std::vector &V, const std::vector &F) { 74 | std::set low_quality_vertices; 75 | double total_quality = 0; 76 | double max_quality = 0; 77 | for (auto [v0, v1, v2] : F) { 78 | auto q = prism::energy::triangle_quality({V[v0], V[v1], V[v2]}); 79 | total_quality += q; 80 | max_quality = std::max(max_quality, q); 81 | } 82 | 83 | spdlog::info("Total Q {} fnum {}, avg {}, max {}", total_quality, F.size(), 84 | total_quality / F.size(), max_quality); 85 | return max_quality; 86 | }; 87 | 88 | //std::tuple, std::array, 89 | // std::tuple, 3UL>, std::vector>> 91 | //magic_matrices(int tri_order, int level); 92 | std::tuple, std::array, std::tuple, 3UL>, std::vector>> magic_matrices(int tri_order, int level); 93 | 94 | #include 95 | auto max_distance_error = [](const PrismCage &pc, 96 | const std::vector &cp) { 97 | prism::geogram::AABB tree(pc.ref.V, pc.ref.F); 98 | auto [tri10_lv5, elevlag_from_bern, tet4_dxyz, duv_lv5, upsample_helper] = 99 | magic_matrices(3, 3); 100 | 101 | int sample_size = tri10_lv5.cols(); 102 | RowMatd high_order_pos = RowMatd::Zero(pc.F.size() * sample_size, 3); 103 | for (int f = 0; f < pc.F.size(); f++) { 104 | high_order_pos.middleRows(sample_size * f, sample_size) = 105 | tri10_lv5.transpose() * cp[f]; 106 | } 107 | auto ulevel = 3ul; 108 | std::vector sp_fid; 109 | std::vector sp_uv; 110 | auto [unitV, unitF, vert_id, edge_id, face_id] = upsample_helper; 111 | for (int f = 0; f < pc.F.size(); f++) { 112 | for (int s = 0; s < sample_size; s++) { 113 | sp_fid.push_back(f); 114 | auto &u = unitV(s, 0), &v = unitV(s, 1); 115 | sp_uv.emplace_back(1 - u - v, u, v); 116 | } 117 | } 118 | 119 | std::vector ray_hits; 120 | std::set combined_track; 121 | prism::curve::sample_hit_discrete(pc.base, pc.mid, pc.top, pc.F, sp_fid, 122 | sp_uv, pc.ref.V, pc.ref.F, tree, 123 | combined_track, ray_hits); 124 | RowMatd ray_hit_pos(ray_hits.size(), 3); 125 | auto &inpF = pc.ref.F; 126 | auto &inpV = pc.ref.inpV; 127 | for (int i = 0; i < ray_hits.size(); i++) { 128 | auto hit = ray_hits[i]; 129 | auto v0 = inpF(hit.id, 0), v1 = inpF(hit.id, 1), v2 = inpF(hit.id, 2); 130 | auto u = hit.u, v = hit.v; 131 | ray_hit_pos.row(i) = 132 | inpV.row(v0) * (1 - u - v) + inpV.row(v1) * u + inpV.row(v2) * v; 133 | } 134 | spdlog::info((high_order_pos - ray_hit_pos).rowwise().norm().maxCoeff()); 135 | }; -------------------------------------------------------------------------------- /tests/curved_tetra_mips.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "cumin/bernstein_eval.hpp" 11 | #include "cumin/curve_common.hpp" 12 | #include "cumin/curve_utils.hpp" 13 | 14 | using RowMatX3d = Eigen::Matrix; 15 | std::tuple mips_energy(const RowMatX3d& nodes, 16 | const std::vector& dxyz, 17 | bool with_grad = false); 18 | 19 | TEST_CASE("cute-mips") { 20 | auto& helper = prism::curve::magic_matrices(3,3); 21 | auto &vec_dxyz = helper.volume_data.vec_dxyz; 22 | auto &elevlag_from_bern = helper.elev_lag_from_bern; 23 | auto tri4_cod = codecs_gen_id(helper.tri_order + 1, 2); 24 | auto tet4_cod = codecs_gen_id(helper.tri_order + 1, 3); 25 | 26 | RowMati codecs_o4(35, 4), codecs_o9(220, 4); 27 | vec2eigen(codecs_gen(4, 3), codecs_o4); 28 | vec2eigen(codecs_gen(9, 3), codecs_o9); 29 | RowMatX3d nodes35 = codecs_o4.rightCols(3).cast() / 3; 30 | { 31 | auto [v, _] = mips_energy(nodes35, vec_dxyz, false); 32 | CHECK_EQ(v, doctest::Approx(504)); 33 | } 34 | nodes35(0, 0) += 0.1; 35 | auto [val, grad] = mips_energy(nodes35, vec_dxyz, true); 36 | auto [val1, _] = mips_energy(nodes35 - 1e-6 * grad, vec_dxyz, false); 37 | CHECK_LT(val1, val); 38 | } 39 | 40 | auto get_l2b = [](std::string filename) { 41 | H5Easy::File file1("../python/curve/data/" + filename); 42 | return H5Easy::load(file1, "l2b"); 43 | }; 44 | 45 | #include "cumin/inversion_check.hpp" 46 | TEST_CASE("recursive-check") { 47 | auto helper = prism::curve::magic_matrices(3, 3); 48 | auto file = H5Easy::File("../buildr/after.h5", H5Easy::File::ReadOnly); 49 | auto lagr = H5Easy::load(file, "lagr"); 50 | auto p4T = H5Easy::load(file, "cells"); 51 | for (auto i : {1902}) { 52 | auto t = i; 53 | RowMatX3d nodes35(35, 3); 54 | for (auto j = 0; j < 35; j++) { 55 | nodes35.row(j) = lagr.row(p4T(t, j)); 56 | } 57 | spdlog::enable_backtrace(100); 58 | // spdlog::set_level(spdlog::level::trace); 59 | if (!prism::curve::tetrahedron_inversion_check( 60 | nodes35, helper.volume_data.vol_codec, helper.volume_data.vol_jac_codec, helper.volume_data.vol_bern_from_lagr, 61 | helper.volume_data.vol_jac_bern_from_lagr)) { 62 | spdlog::warn("negative"); 63 | } 64 | } 65 | } 66 | 67 | auto save_cute = [](std::string name, auto& V, auto& T) { 68 | RowMatd mV; 69 | RowMati mT; 70 | vec2eigen(V, mV); 71 | vec2eigen(T, mT); 72 | auto file = H5Easy::File(name, H5Easy::File::Overwrite); 73 | H5Easy::dump(file, "V", mV); 74 | H5Easy::dump(file, "T", mT); 75 | }; 76 | 77 | #include 78 | namespace prism::curve{ 79 | 80 | } 81 | TEST_CASE("cute-collapse") { 82 | spdlog::set_pattern("[%l] %v"); 83 | std::string in_file = "../buildr/before_opt.h5"; 84 | auto file = H5Easy::File(in_file, H5Easy::File::ReadOnly); 85 | auto lagr = H5Easy::load(file, "lagr"); //*3 86 | auto p4T = H5Easy::load(file, "cells"); //*35 87 | 88 | auto helper = prism::curve::magic_matrices(3, 3); 89 | REQUIRE_EQ(helper.volume_data.vol_codec.rows(), p4T.cols()); 90 | spdlog::info("Codec {}x{}", helper.volume_data.vol_codec.rows(), 91 | helper.volume_data.vol_codec.cols()); 92 | spdlog::info("codec row {}", helper.volume_data.vol_codec.row(0)); 93 | // prism::curve::edge_collapsing( lagr, p4T, 1e2); 94 | // prism::curve::edge_swapping( lagr, p4T, 1e2); 95 | for (int pass = 1; pass <= 1; pass++) { 96 | spdlog::info("======== Optimization Pass {}/{} ========", pass, 6); 97 | auto col = prism::curve::edge_collapsing(lagr, p4T, 100); 98 | CHECK_EQ(col, 281); 99 | 100 | spdlog::set_level(spdlog::level::debug); 101 | auto swa = prism::curve::edge_swapping(lagr, p4T, 100); 102 | CHECK_EQ(swa, 46); 103 | 104 | prism::curve::vertex_star_smooth(lagr, p4T, 0, 1); 105 | } 106 | // auto ofile = H5Easy::File(in_file + "_out.h5", H5Easy::File::Overwrite); 107 | // H5Easy::dump(ofile, "lagr", lagr); 108 | // H5Easy::dump(ofile, "cells", p4T); 109 | } 110 | 111 | TEST_CASE("cute-collapse-edit") { 112 | spdlog::set_pattern("[%l] %v"); 113 | std::string in_file = "../buildr/before_opt.h5"; 114 | auto file = H5Easy::File(in_file, H5Easy::File::ReadOnly); 115 | auto lagr = H5Easy::load(file, "lagr"); //*3 116 | auto p4T = H5Easy::load(file, "cells"); //*35 117 | 118 | auto helper = prism::curve::magic_matrices(2, 3); 119 | REQUIRE_EQ(helper.volume_data.vol_codec.rows(), p4T.cols()); 120 | spdlog::info("Codec {}x{}", helper.volume_data.vol_codec.rows(), 121 | helper.volume_data.vol_codec.cols()); 122 | // prism::curve::edge_collapsing( lagr, p4T, 1e2); 123 | // prism::curve::edge_swapping( lagr, p4T, 1e2); 124 | for (int pass = 1; pass <= 6; pass++) { 125 | spdlog::info("======== Optimization Pass {}/{} ========", pass, 6); 126 | auto col = prism::curve::cutet_collapse(lagr, p4T, 100); 127 | // CHECK_EQ(col, 281); 128 | 129 | spdlog::set_level(spdlog::level::info); 130 | auto swa = prism::curve::cutet_swap(lagr, p4T, 100); 131 | // CHECK_EQ(swa, 46); 132 | 133 | prism::curve::vertex_star_smooth(lagr, p4T, 3, 1); 134 | } 135 | } -------------------------------------------------------------------------------- /src/prism/spatial-hash/self_intersection.cpp: -------------------------------------------------------------------------------- 1 | #include "self_intersection.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "AABB_hash.hpp" 9 | #include "prism/cage_utils.hpp" 10 | #include "prism/predicates/tetrahedron_overlap.hpp" 11 | #include "prism/local_operations/retain_triangle_adjacency.hpp" 12 | #include "prism/predicates/triangle_triangle_intersection.hpp" 13 | #include 14 | 15 | 16 | constexpr auto share_vertex = [](const auto &f0, auto &f1, int &s0, int &s1) { 17 | auto cnt = 0; 18 | for (int i = 0; i < 3; i++) 19 | for (int j = 0; j < 3; j++) 20 | if (f0[i] == f1[j]) { 21 | cnt++; 22 | s0 = i; 23 | s1 = j; 24 | }; 25 | assert(cnt < 3); // unless all the same 26 | return cnt; 27 | }; 28 | constexpr auto intersection = [](const auto &V, const auto &f0, auto &f1) { 29 | auto &[p0, p1, p2] = f0; 30 | auto &[q0, q1, q2] = f1; 31 | return prism::predicates::triangle_triangle_overlap({V[p0], V[p1], V[p2]}, 32 | {V[q0], V[q1], V[q2]}); 33 | }; 34 | constexpr auto reduced_intersection = [](const auto &V, const auto &f0, 35 | auto &f1) { 36 | auto &[p0, p1, p2] = f0; 37 | auto &[q0, q1, q2] = f1; 38 | assert(V[p0] == V[q0] && "first vertex coincide"); 39 | bool o1 = prism::predicates::segment_triangle_overlap({V[p1], V[p2]}, 40 | {V[q0], V[q1], V[q2]}); 41 | bool o2 = prism::predicates::segment_triangle_overlap({V[q1], V[q2]}, 42 | {V[p0], V[p1], V[p2]}); 43 | return o1 || o2; 44 | }; 45 | 46 | auto prism::spatial_hash::self_intersections(const std::vector &vecV, 47 | const std::vector &vecF) 48 | -> std::vector> { 49 | prism::HashGrid hg(vecV, vecF); 50 | auto cand = hg.self_candidates(); 51 | std::vector> offending_cand; 52 | std::for_each( 53 | cand.begin(), cand.end(), 54 | [&offending_cand, &vecV, &vecF](const auto &f01) { 55 | auto [f0, f1] = f01; 56 | auto verts0 = Vec3i({vecF[f0][0], vecF[f0][1], vecF[f0][2]}); 57 | auto verts1 = Vec3i({vecF[f1][0], vecF[f1][1], vecF[f1][2]}); 58 | int s0 = -1, s1 = -1; 59 | auto cnt = share_vertex(verts0, verts1, s0, s1); 60 | auto flag = false; 61 | if (cnt == 0) { 62 | flag = intersection(vecV, verts0, verts1); 63 | } else if (cnt == 1) { 64 | int df0 = f0, df1 = f1; 65 | assert(s0 != -1 && s1 != -1); 66 | std::swap(verts0[0], verts0[s0]); 67 | std::swap(verts1[0], verts1[s1]); 68 | flag = reduced_intersection(vecV, verts0, verts1); 69 | if (flag == true) { 70 | spdlog::warn( 71 | "hypothesis: this would not happen in shell settings."); 72 | } 73 | } else { 74 | ; 75 | }; 76 | if (flag) { 77 | offending_cand.emplace_back(f0, f1); 78 | } 79 | }); 80 | return offending_cand; 81 | } 82 | 83 | auto prism::spatial_hash::tetrashell_self_intersections( 84 | const std::vector &base, const std::vector &top, 85 | const std::vector &F) -> std::set> { 86 | // this is not dealing with singularity explicitly, but the degenerate tetra 87 | // should not interfere. 88 | 89 | std::vector tetV; 90 | std::vector tetT; 91 | prism::cage_utils::tetmesh_from_prismcage(base, top, F, tetV, tetT); 92 | 93 | prism::HashGrid hg(top, F, /*start empty*/ false); 94 | for (int i = 0; i < tetT.size(); i++) { 95 | Eigen::Matrix local; 96 | for (auto k : {0, 1, 2, 3}) local.row(k) = tetV[tetT[i][k]]; 97 | auto aabb_min = local.colwise().minCoeff(); 98 | auto aabb_max = local.colwise().maxCoeff(); 99 | hg.add_element(aabb_min, aabb_max, i); 100 | } 101 | 102 | auto cand = hg.self_candidates(); 103 | std::set> offend_pairs; 104 | auto offend_handle = 105 | prism::spatial_hash::find_offending_pairs(F, tetV, tetT, offend_pairs); 106 | std::for_each(cand.begin(), cand.end(), offend_handle); 107 | return offend_pairs; 108 | } 109 | 110 | std::function &)> 111 | prism::spatial_hash::find_offending_pairs( 112 | const std::vector &F, const std::vector &tetV, 113 | const std::vector &tetT, 114 | std::set> &offend_pairs) { 115 | return [&F, &tetV, &tetT, 116 | &offend_pairs](const std::pair &t01) -> void { 117 | auto [t0, t1] = t01; 118 | auto f0 = t0 / 3, f1 = t1 / 3; 119 | if (f0 == f1) return; 120 | if (f0 > f1) std::swap(f0,f1); 121 | if (offend_pairs.find({f0,f1}) != offend_pairs.end()) return; 122 | { 123 | auto verts0 = Vec3i({F[f0][0], F[f0][1], F[f0][2]}); 124 | auto verts1 = Vec3i({F[f1][0], F[f1][1], F[f1][2]}); 125 | int s0 = -1, s1 = -1; 126 | auto cnt = share_vertex(verts0, verts1, s0, s1); 127 | if (cnt > 0) return; // skip vertex touching case. TODO: prove this 128 | } 129 | 130 | std::array local0, local1; 131 | for (auto k : {0, 1, 2, 3}) local0[k] = tetV[tetT[t0][k]]; 132 | for (auto k : {0, 1, 2, 3}) local1[k] = tetV[tetT[t1][k]]; 133 | if (prism::predicates::tetrahedron_tetrahedron_overlap(local0, local1)) { 134 | offend_pairs.insert({f0, f1}); 135 | } 136 | 137 | }; 138 | } -------------------------------------------------------------------------------- /src/cumin/inversion_check.cpp: -------------------------------------------------------------------------------- 1 | #include "inversion_check.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "bernstein_eval.hpp" 10 | #include "curve_utils.hpp" 11 | inline const RowMati sub_tetras = 12 | (RowMati(8, 4) << 3, 6, 9, 8, 0, 4, 5, 6, 1, 4, 8, 7, 2, 5, 7, 9, 4, 5, 6, 13 | 8, 5, 6, 8, 9, 4, 5, 8, 7, 5, 7, 9, 8) 14 | .finished(); 15 | 16 | inline const RowMati sub_verts = (RowMati(10, 2) << 0, 0, 1, 1, 2, 2, 3, 3, 0, 17 | 1, 0, 2, 0, 3, 1, 2, 1, 3, 2, 3) 18 | .finished(); 19 | 20 | bool prism::curve::tetrahedron_recursive_positive_check( 21 | const Eigen::VectorXd& input_cp, const RowMatd& bern_from_lag, 22 | const Eigen::MatrixX4i& short_codecs) { 23 | const int max_level = 3; 24 | int num_cp = input_cp.size(); 25 | auto order = short_codecs(0, 0); 26 | 27 | std::queue> q; 28 | int level_count = 0; 29 | q.push({(RowMatd(4, 3) << 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1).finished(), 30 | Eigen::VectorXd(input_cp)}); 31 | 32 | RowMatd inp_bern = bern_from_lag * input_cp; 33 | 34 | q.push({RowMatd(), Eigen::VectorXd()}); 35 | while (!q.empty()) { 36 | spdlog::trace("level {}. size {}", level_count, q.size()); 37 | auto [std_vert_pos, lag_cp] = q.front(); 38 | q.pop(); 39 | if (std_vert_pos.size() == 0) { 40 | spdlog::trace("level indicator {}", level_count); 41 | ++level_count; 42 | if (level_count >= max_level) { 43 | spdlog::trace("{} pass max level {}", level_count, max_level); 44 | return false; // empty indicator 45 | } 46 | if (q.empty()) return true; 47 | q.push({RowMatd(), Eigen::VectorXd()}); 48 | continue; 49 | } 50 | if (lag_cp.minCoeff() <= 0) { 51 | spdlog::trace("lag negative {}: {}", level_count, lag_cp.minCoeff()); 52 | return false; // Lagrange Negative -> must negative 53 | } 54 | Eigen::VectorXd bern_cp = bern_from_lag * lag_cp; 55 | if (bern_cp.minCoeff() > 0) { 56 | spdlog::trace("Bern Positive {}", bern_cp.minCoeff()); 57 | continue; // Bern Positive -> all positive 58 | } 59 | 60 | // subdivide 61 | RowMatd sub_verts_pos(10, 3); 62 | for (int i = 0; i < sub_verts.rows(); i++) { 63 | sub_verts_pos.row(i) = (std_vert_pos.row(sub_verts(i, 0)) + 64 | std_vert_pos.row(sub_verts(i, 1))) / 65 | 2; 66 | } 67 | spdlog::trace("splitting {}", level_count); 68 | for (auto ti = 0; ti < 8; ti++) { 69 | auto t = sub_tetras.row(ti); 70 | auto sub_tet_verts = RowMatd(4, 3); 71 | for (int j = 0; j < 4; j++) 72 | sub_tet_verts.row(j) = sub_verts_pos.row(t[j]); 73 | RowMatd sub_params = short_codecs.cast() * sub_tet_verts / order; 74 | auto basis_val = 75 | prism::curve::evaluate_bernstein(sub_params.col(0), sub_params.col(1), 76 | sub_params.col(2), short_codecs); 77 | Eigen::VectorXd sub_lagr = basis_val.matrix() * inp_bern; 78 | if (sub_lagr.minCoeff() <= 0) { 79 | spdlog::trace("sub min {}", sub_lagr.minCoeff()); 80 | return false; 81 | } 82 | q.emplace(sub_tet_verts, sub_lagr); 83 | spdlog::trace("push {} {}", ti, sub_lagr.minCoeff()); 84 | } 85 | } 86 | return true; 87 | } 88 | 89 | bool prism::curve::tetrahedron_inversion_check(const RowMatd& cp) { 90 | auto& helper = prism::curve::magic_matrices(); 91 | return tetrahedron_inversion_check(cp, helper.volume_data.vol_codec, 92 | helper.volume_data.vol_jac_codec, 93 | helper.volume_data.vol_bern_from_lagr, 94 | helper.volume_data.vol_jac_bern_from_lagr); 95 | } 96 | 97 | bool prism::curve::tetrahedron_inversion_check( 98 | const RowMatd& cp, const Eigen::Matrix& codecs_o4, 99 | const Eigen::MatrixX4i & codecs_o9, 100 | const RowMatd& bern_from_lagr_o4, const RowMatd& bern_from_lagr_o9) { 101 | // ANCHOR: this function is 50% of the profile bottleneck. 102 | int order = codecs_o4(0, 0); 103 | int high_order = codecs_o9(0, 0); 104 | assert(high_order == (order - 1) * 3); 105 | auto& helper = prism::curve::magic_matrices(); 106 | if (helper.inversion_helper.cache != 107 | high_order) { // TODO: potentially not thread-safe 108 | // 3v x 35b x 220s 109 | auto r1 = prism::curve::evaluate_bernstein_derivative( 110 | codecs_o9.col(1).cast() / high_order, 111 | codecs_o9.col(2).cast() / high_order, 112 | codecs_o9.col(3).cast() / high_order, codecs_o4); 113 | for (auto k = 0; k < 3; k++) { 114 | helper.inversion_helper.bernstein_derivatives_checker[k] = 115 | r1[k].matrix().transpose(); 116 | } 117 | helper.inversion_helper.cache = high_order; 118 | } 119 | auto& r1 = helper.inversion_helper.bernstein_derivatives_checker; 120 | 121 | RowMatd b_cp = bern_from_lagr_o4 * cp; 122 | // 3v x 220s x 3d 123 | std::vector var_dim_sample; 124 | for (auto& r : r1) { 125 | var_dim_sample.emplace_back(r * b_cp); 126 | } 127 | Eigen::VectorXd lagr(codecs_o9.rows()); 128 | for (int i = 0; i < codecs_o9.rows(); i++) { 129 | Eigen::Matrix3d temp; 130 | for (int j = 0; j < 3; j++) temp.row(j) = var_dim_sample[j].row(i); 131 | lagr[i] = temp.determinant(); 132 | } 133 | return prism::curve::tetrahedron_recursive_positive_check( 134 | lagr, bern_from_lagr_o9, codecs_o9); 135 | } --------------------------------------------------------------------------------