├── .clang-format
├── .github
└── workflows
│ ├── macos.yml
│ ├── ubuntu.yml
│ └── windows.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── CMakeLists.txt
├── LICENSE
├── README.md
├── build_exts.py
├── clang-format.sh
├── cmake
├── FindGoogleTest.cmake
└── Macros.cmake
├── data
├── .gitignore
├── get_large_meshes.py
└── models
│ ├── box.obj
│ ├── bunny.ply
│ ├── bunny_mini.ply
│ ├── fandisk.ply
│ ├── plane.obj
│ ├── sphere.obj
│ ├── torus.obj
│ └── torus.ply
├── examples
├── CMakeLists.txt
├── cpp
│ ├── CMakeLists.txt
│ ├── example_denoise.cpp
│ ├── example_hole_fill.cpp
│ ├── example_read_write.cpp
│ ├── example_remesh.cpp
│ ├── example_simplify.cpp
│ └── example_smooth.cpp
└── python
│ ├── construct.ipynb
│ ├── curvatures.ipynb
│ ├── denoise.ipynb
│ ├── feature_line_fields.ipynb
│ ├── heat_kernel_signatures.ipynb
│ ├── hole_filling.ipynb
│ ├── laplace_beltrami.ipynb
│ └── viewer.py
├── figures
├── bunny_before.png
├── bunny_hole_fill_adv_front.png
├── bunny_hole_fill_min_dihedral.png
├── bunny_holes.png
├── bunny_remesh_1.png
├── bunny_remesh_2.png
├── dragon_before.png
├── dragon_simplify_10000.png
├── dragon_simplify_50000.png
├── fandisk_before.png
├── fandisk_denoise_l0.png
└── fandisk_noise.png
├── pyproject.toml
├── src
├── CMakeLists.txt
├── pybind11.cpp
└── tinymesh
│ ├── CMakeLists.txt
│ ├── core
│ ├── api.h
│ ├── bounds.h
│ ├── bvh.cpp
│ ├── bvh.h
│ ├── completion.cpp
│ ├── debug.h
│ ├── eigen.cpp
│ ├── eigen.h
│ ├── external.cpp
│ ├── face.cpp
│ ├── face.h
│ ├── filesystem.h
│ ├── halfedge.cpp
│ ├── halfedge.h
│ ├── mesh.cpp
│ ├── mesh.h
│ ├── openmp.h
│ ├── progress.h
│ ├── triangle.cpp
│ ├── triangle.h
│ ├── utils.h
│ ├── vec.h
│ ├── vertex.cpp
│ └── vertex.h
│ ├── filters
│ ├── denoise.cpp
│ ├── filters.h
│ └── smooth.cpp
│ ├── ops
│ ├── features.cpp
│ ├── metrics.cpp
│ ├── operators.cpp
│ └── ops.h
│ ├── remesh
│ ├── remesh.cpp
│ ├── remesh.h
│ └── simplify.cpp
│ ├── restore
│ ├── completion.cpp
│ └── restore.h
│ └── tinymesh.h
└── tests
├── CMakeLists.txt
├── gtest.cpp
├── python
├── test_construct.py
├── test_filters.py
├── test_ops.py
└── test_remesh.py
├── test_config.h.in
├── test_filters.cpp
├── test_mesh.cpp
├── test_ops.cpp
├── test_remesh.cpp
├── test_utils.h
└── test_vec.cpp
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: Google
2 |
3 | ColumnLimit: 120
4 | IndentWidth: 4
5 | Standard: Cpp11
6 | AccessModifierOffset: -4
7 | IndentCaseLabels: false
8 |
9 | AlignAfterOpenBracket: Align
10 | AlignConsecutiveAssignments: false
11 | AlignTrailingComments: true
12 | AlignOperands: true
13 | AllowAllParametersOfDeclarationOnNextLine: false
14 | AllowAllConstructorInitializersOnNextLine: false
15 | AllowShortBlocksOnASingleLine: false
16 | AllowShortFunctionsOnASingleLine: false
17 | BinPackArguments: true
18 | BreakConstructorInitializers: BeforeColon
19 | BreakConstructorInitializersBeforeComma: true
20 | Cpp11BracedListStyle: false
21 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
22 | DerivePointerAlignment: false
23 | MaxEmptyLinesToKeep: 1
24 | PointerAlignment: Right
25 | ReflowComments: false
26 | SortIncludes: false
27 |
28 | UseTab: Never
29 |
--------------------------------------------------------------------------------
/.github/workflows/macos.yml:
--------------------------------------------------------------------------------
1 | name: MacOS CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | - pr
9 | tags:
10 | - v*
11 |
12 | jobs:
13 | macos:
14 | runs-on: macos-latest
15 |
16 | strategy:
17 | matrix:
18 | python-version-major: [3]
19 | python-version-minor: [8, 9, 10, 11, 12]
20 |
21 | env:
22 | BUILD_TYPE: Release
23 | python-version: ${{ format('{0}.{1}', matrix.python-version-major, matrix.python-version-minor) }}
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 | with:
28 | submodules: true
29 |
30 | - name: Set up Python version
31 | uses: actions/setup-python@v4
32 | with:
33 | python-version: ${{ env.python-version }}
34 |
35 | - name: Install Poetry
36 | run: |
37 | python -m pip install --upgrade pip
38 | python -m pip install poetry
39 | poetry config virtualenvs.path ~/.local/share/virtualenvs
40 |
41 | - name: Cache Poetry
42 | uses: actions/cache@v3
43 | id: poetry-cache
44 | with:
45 | path: ~/.local/share/virtualenvs
46 | key: ${{ runner.os }}-poetry-${{ hashFiles('**/pyproject.toml') }}
47 | restore-keys: |
48 | ${{ runner.os }}-poetry-${{ hashFiles('**/pyproject.toml') }}
49 |
50 | - name: Install Google Test
51 | env:
52 | C_COMPILER: ${{ matrix.compiler }}
53 | run: |
54 | git clone https://github.com/google/googletest.git --depth 1
55 | cd googletest
56 | mkdir build
57 | cd build
58 |
59 | cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
60 | cmake --build . --config $BUILD_TYPE --parallel 2
61 | cmake --build . --target install
62 |
63 | - name: CMake build
64 | env:
65 | C_COMPILER: ${{ matrix.compiler }}
66 | run: |
67 | cmake -E make_directory ${{runner.workspace}}/build
68 | cd ${{runner.workspace}}/build
69 |
70 | cmake $GITHUB_WORKSPACE \
71 | -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
72 | -DBUILD_EXAMPLES=ON \
73 | -DBUILD_TESTS=ON
74 |
75 | cmake --build . --config $BUILD_TYPE --parallel 2
76 |
77 | - name: Unit test
78 | working-directory: ${{runner.workspace}}/build
79 | shell: bash
80 | run: ctest -C $BUILD_TYPE
81 |
82 | - name: Install Python modules
83 | if: steps.pipenv-cache.outputs.cache-hit != 'true'
84 | run: |
85 | poetry build -f wheel
86 | poetry install
87 |
88 | - name: Python module test
89 | run: |
90 | poetry run pytest -s tests
91 |
92 | - name: Create release
93 | if: startsWith(github.ref, 'refs/tags/')
94 | uses: softprops/action-gh-release@v1
95 | with:
96 | files: ./dist/*.whl
97 | draft: false
98 | prerelease: false
99 |
--------------------------------------------------------------------------------
/.github/workflows/ubuntu.yml:
--------------------------------------------------------------------------------
1 | name: Ubuntu CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | - pr
9 | tags:
10 | - v*
11 |
12 | jobs:
13 | ubuntu:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | python-version-major: [3]
19 | python-version-minor: [8, 9, 10, 11, 12]
20 |
21 | env:
22 | BUILD_TYPE: Release
23 | python-version: ${{ format('{0}.{1}', matrix.python-version-major, matrix.python-version-minor) }}
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 | with:
28 | submodules: true
29 |
30 | - name: Set up Python version
31 | uses: actions/setup-python@v4
32 | with:
33 | python-version: ${{ env.python-version }}
34 |
35 | - name: Install Poetry
36 | run: |
37 | python -m pip install --upgrade pip
38 | python -m pip install poetry
39 | poetry config virtualenvs.path ~/.local/share/virtualenvs
40 |
41 | - name: Cache Poetry
42 | uses: actions/cache@v3
43 | id: poetry-cache
44 | with:
45 | path: ~/.local/share/virtualenvs
46 | key: ${{ runner.os }}-poetry-${{ hashFiles('**/pyproject.toml') }}
47 | restore-keys: |
48 | ${{ runner.os }}-poetry-${{ hashFiles('**/pyproject.toml') }}
49 |
50 | - name: Install GNU C/C++ compiler
51 | run: |
52 | sudo apt-get install -y gcc-9 g++-9
53 | export CC=gcc-9
54 | export CXX=g++-9
55 |
56 | - name: Install Google Test
57 | env:
58 | C_COMPILER: ${{ matrix.compiler }}
59 | run: |
60 | git clone https://github.com/google/googletest.git
61 | cd googletest
62 | mkdir build
63 | cd build
64 |
65 | cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
66 | cmake --build . --config $BUILD_TYPE --parallel 2
67 | sudo cmake --build . --target install
68 |
69 | - name: CMake build
70 | env:
71 | C_COMPILER: ${{ matrix.compiler }}
72 | run: |
73 | cmake -E make_directory ${{runner.workspace}}/build
74 | cd ${{runner.workspace}}/build
75 |
76 | cmake $GITHUB_WORKSPACE \
77 | -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
78 | -DBUILD_EXAMPLES=ON \
79 | -DBUILD_TESTS=ON
80 |
81 | cmake --build . --config $BUILD_TYPE --parallel 2
82 |
83 | - name: Unit test
84 | working-directory: ${{ runner.workspace }}/build
85 | shell: bash
86 | run: ctest -C $BUILD_TYPE
87 |
88 | - name: Install Python modules
89 | if: steps.pipenv-cache.outputs.cache-hit != 'true'
90 | run: |
91 | poetry build -f wheel
92 | poetry install
93 |
94 | - name: Python module test
95 | run: |
96 | poetry run pytest -s tests
97 |
98 | - name: Create release
99 | if: startsWith(github.ref, 'refs/tags/')
100 | uses: softprops/action-gh-release@v1
101 | with:
102 | files: ./dist/*.whl
103 | draft: false
104 | prerelease: false
105 |
--------------------------------------------------------------------------------
/.github/workflows/windows.yml:
--------------------------------------------------------------------------------
1 | name: Windows CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | - pr
9 | tags:
10 | - v*
11 |
12 | jobs:
13 | windows:
14 | runs-on: windows-latest
15 |
16 | strategy:
17 | matrix:
18 | python-version-major: [3]
19 | python-version-minor: [8, 9, 10, 11, 12]
20 |
21 | env:
22 | BUILD_TYPE: Release
23 | python-version: ${{ format('{0}.{1}', matrix.python-version-major, matrix.python-version-minor) }}
24 |
25 | steps:
26 | - uses: actions/checkout@v4
27 | with:
28 | submodules: true
29 |
30 | - name: Set up Python version
31 | uses: actions/setup-python@v4
32 | with:
33 | python-version: ${{ env.python-version }}
34 |
35 | - name: Install Poetry
36 | run: |
37 | python -m pip install --upgrade pip
38 | python -m pip install poetry
39 | poetry config virtualenvs.path ~/.local/share/virtualenvs
40 |
41 | - name: Cache Poetry
42 | uses: actions/cache@v3
43 | id: poetry-cache
44 | with:
45 | path: ~/.local/share/virtualenvs
46 | key: ${{ runner.os }}-poetry-${{ hashFiles('**/pyproject.toml') }}
47 | restore-keys: |
48 | ${{ runner.os }}-poetry-${{ hashFiles('**/pyproject.toml') }}
49 |
50 | - name: Install Google Test
51 | run: |
52 | git clone https://github.com/google/googletest.git --depth 1
53 | cd googletest
54 | mkdir build
55 | cd build
56 | cmake .. `
57 | -G "Visual Studio 17 2022" -A x64 `
58 | -Dgtest_force_shared_crt=ON `
59 | -DCMAKE_INSTALL_PREFIX="C:/Program Files/GoogleTest/"
60 | cmake --build . --config "$env:BUILD_TYPE"
61 | cmake --build . --config "$env:BUILD_TYPE" --target INSTALL
62 |
63 | - name: CMake build
64 | run: |
65 | cmake -E make_directory ${{ runner.workspace }}/build
66 | cd ${{ runner.workspace }}/build
67 | cmake "$env:GITHUB_WORKSPACE" `
68 | -G "Visual Studio 17 2022" -A x64 `
69 | -DGoogleTest_DIR="C:/Program Files/GoogleTest" `
70 | -DBUILD_TESTS=ON `
71 | -DBUILD_EXAMPLES=ON
72 | cmake --build . --config "$env:BUILD_TYPE"
73 |
74 | - name: Unit test
75 | working-directory: ${{ runner.workspace }}/build
76 | shell: bash
77 | run: ctest -C $BUILD_TYPE
78 |
79 | - name: Install Python modules
80 | if: steps.pipenv-cache.outputs.cache-hit != 'true'
81 | run: |
82 | poetry build -f wheel
83 | poetry install
84 |
85 | - name: Python module test
86 | run: |
87 | poetry run pytest -s tests
88 |
89 | - name: Create release
90 | if: startsWith(github.ref, 'refs/tags/')
91 | uses: softprops/action-gh-release@v1
92 | with:
93 | files: ./dist/*.whl
94 | draft: false
95 | prerelease: false
96 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .idea/*
3 | .vscode/*
4 | build/*
5 | Debug/*
6 | Release/*
7 | cmake-build-debug/*
8 | cmake-build-release/*
9 | Testing/*
10 |
11 | *.TMP
12 | .DS_Store
13 |
14 | test_config.h
15 |
16 | # C/C++ libraries
17 | *.a
18 | *.so
19 | *.lib
20 | *.exp
21 | *.pdb
22 | *.dll
23 | *.dylib
24 |
25 | # Python cache files
26 | setup.py
27 | Pipfile.lock
28 | poetry.lock
29 | *.pyc
30 | *.pyd
31 | dist/*
32 | __pycache__/
33 | *.egg-info
34 | .ipynb_checkpoints/
35 |
36 | # Mesh file format
37 | *.ply
38 | *.obj
39 | *.off
40 | *.stl
41 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "src/tinymesh/ext/tinyobjloader"]
2 | path = src/tinymesh/ext/tinyobjloader
3 | url = https://github.com/syoyo/tinyobjloader
4 | [submodule "src/tinymesh/ext/tinyply"]
5 | path = src/tinymesh/ext/tinyply
6 | url = https://github.com/ddiakopoulos/tinyply
7 | [submodule "src/tinymesh/ext/eigen"]
8 | path = src/tinymesh/ext/eigen
9 | url = https://gitlab.com/libeigen/eigen.git
10 | [submodule "src/tinymesh/ext/spectra"]
11 | path = src/tinymesh/ext/spectra
12 | url = https://github.com/yixuan/spectra
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | v0.2.4
2 | ---
3 | * Update CI scripts to build wheels for 5 latest Python versions.
4 |
5 |
6 | v0.2.3
7 | ---
8 | * Update some method names (e.g., `isStatic` -> `isLocked`)
9 | * Delete `InHalfedgeIterator', and rename `OutHalfedgeIterator` as `HalfedgeIterator`.
10 | * Add const version of iterators.
11 | * Advancing front method [Zhao et al. 2007] is implemented.
12 | * Change dev-env manager to Poetry.
13 | * Add a method to compute principal curvatures (and thier directions) [Rusinkiewicz 2004].
14 | * Add a method of context-based coherent surface completion [Harary et al. 2016].
15 | * Add own print function to support both C++/Python standard outputs.
16 |
17 | v0.2.2
18 | ---
19 | * Change Python env manager to Pipenv.
20 | * Add methods computing approximated Laplacian-Beltrami operators (adjacent, cotangent, Belkin+2008).
21 | * Add IPython notebooks as Pythons examples rather than simple scripts.
22 | * Change traversal order of elements around a vertex to counter-clockwise order.
23 | * Add heat kernel signature [Sun et al. 2009].
24 |
25 | v0.2.1
26 | ---
27 | * Minor bug fix for GCC v8.
28 |
29 | v0.2.0
30 | ---
31 | * Add unit test for each functions (just to check run or not).
32 | * Add feature preservation in remeshing.
33 | * Include Eigen to repo as a submodule.
34 | * Update Python setup to use Poetry.
35 | * Add mesh denoising via bilateral filter [Zheng et al. 2011] and L0 smoothing [He and Schaefer 2013].
36 | * Update Python module build to use Python-side `pybind11` installation.
37 | * Add mesh denoising via normal Gaussian filter [Ohtake et al. 2001].
38 | * Implicit fairing `implicit_fair` is updated to its normalized version.
39 | * Hole filling `hole_fill` is updated to take the upper bound of allowed dihedral angle.
40 | * Update `setup.py` to support the operation `install`.
41 |
42 | v0.1.0
43 | ---
44 | * First release
45 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.18.0 FATAL_ERROR)
2 | project(TinyMesh)
3 |
4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
5 | include(Macros)
6 |
7 | # ----------
8 | # Options
9 | # ----------
10 | option(WITH_OPENMP "Use OpenMP or not" ON)
11 | option(BUILD_EXAMPLES "Build examples." OFF)
12 | option(BUILD_TESTS "Build unit tests." OFF)
13 |
14 | set(CMAKE_CXX_STANDARD 17)
15 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
16 | if (UNIX)
17 | set(CMAKE_CXX_FLAGS "-pthread -fPIC -Wall")
18 | set(CMAKE_CXX_FLAGS_RELEASE "-g -O2")
19 | set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
20 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
21 | set(CXX_FS_LIBRARY "stdc++fs")
22 | endif()
23 | endif()
24 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
25 |
26 | # ----------
27 | # Common target names
28 | # ----------
29 | set(TINYMESH_LIBRARY "tinymesh")
30 | set(TINYMESH_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/src")
31 |
32 | # ----------
33 | # OpenMP
34 | # ----------
35 | if (WITH_OPENMP)
36 | find_package(OpenMP)
37 | if (OPENMP_FOUND)
38 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
39 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
40 | set(OpenMP_INCLUDE_DIRS "${OpenMP_C_INCLUDE_DIRS}")
41 | set(OpenMP_LIBRARIES "${OpenMP_C_LIBRARIES}")
42 | endif()
43 | endif()
44 |
45 | # ----------
46 | # Output paths
47 | # ----------
48 |
49 | if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
50 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
51 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
52 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
53 | endif()
54 |
55 | if (NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
56 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
57 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin)
58 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin)
59 | endif()
60 |
61 | if (NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
62 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
63 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/lib)
64 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/lib)
65 | endif()
66 |
67 | set(CMAKE_DEBUG_POSTFIX "-debug")
68 |
69 | # ----------
70 | # OS specific settings
71 | # ----------
72 | set_property(GLOBAL PROPERTY USE_FOLDERS ON)
73 |
74 | if (MSVC)
75 | add_compile_options("/wd4251" "/wd4819")
76 | endif()
77 |
78 | if (APPLE)
79 | set(CMAKE_MACOSX_RPATH 1)
80 | endif()
81 |
82 | # ----------
83 | # Traverse subdirectories
84 | # ----------
85 | add_subdirectory(src)
86 |
87 | if (BUILD_EXAMPLES)
88 | add_subdirectory(examples)
89 | endif()
90 |
91 | if (BUILD_TESTS)
92 | find_package(GoogleTest REQUIRED)
93 | enable_testing()
94 | add_subdirectory(tests)
95 | endif()
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | TinyMesh
2 | ===
3 |
4 | [](https://www.codacy.com/manual/tatsy/tinymesh?utm_source=github.com&utm_medium=referral&utm_content=tatsy/tinymesh&utm_campaign=Badge_Grade)
5 | 
6 | 
7 | 
8 | [](https://opensource.org/licenses/MPL-2.0)
9 |
10 | > TinyMesh is a light-weight mesh processing library in C/C++.
11 |
12 | [](https://www.buymeacoffee.com/tatsy425)
13 |
14 |
15 | Modules
16 | ---
17 |
18 | Here is the list of modules and reference papers for that.
19 |
20 | * **Geometric properties**
21 | * Principal curvatures [[Rusinkiewicz 2004]](https://ieeexplore.ieee.org/document/1335277)
22 | * Heat kernel signatures [[Sun et al. 2009]](https://onlinelibrary.wiley.com/doi/10.1111/j.1467-8659.2009.01515.x)
23 | * **Smoothing**
24 | * Laplacian smoothing
25 | * Taubin smoothing [[Taubin 1995]](https://dl.acm.org/doi/10.1145/218380.218473)
26 | * Implicit fairing [[Desbrun 1999]](https://dl.acm.org/doi/10.1145/311535.311576)
27 | * **Denoising**
28 | * Normal Gaussian filter [[Ohtake et al. 2001]](https://www.semanticscholar.org/paper/Mesh-Smoothing-by-Adaptive-and-Anisotropic-Gaussian-Ohtake-Belyaev/19b431c843f4b37d2218e7efcd8f64b6ff589c1f)
29 | * Normal bilateral filter [[Zheng et al. 2011]](https://ieeexplore.ieee.org/document/5674028)
30 | * L0 mesh smoothing [[He and Schaefer 2013]](https://dl.acm.org/doi/10.1145/2461912.2461965)
31 | * **Remeshing**
32 | * Uniform triangulation [[Hoppe 1996]](https://dl.acm.org/doi/10.1145/237170.237216)
33 | * **Simplification**
34 | * Quadric error metrics (QEM) [[Garland and Heckbert 1997]](https://dl.acm.org/doi/10.1145/258734.258849)
35 | * **Hole filling**
36 | * Min-area hole filling [[Barequet and Sharir 1995]](https://www.sciencedirect.com/science/article/pii/016783969400011G?via%3Dihub)
37 | * Min-dihedral angle [[Liepa 2003]](http://diglib.eg.org/handle/10.2312/SGP.SGP03.200-206)
38 | * Advancing front [[Zhao et al. 2007]](https://link.springer.com/article/10.1007/s00371-007-0167-y)
39 |
40 | Install
41 | ---
42 |
43 | The module is tested its compilation using the following compilers.
44 |
45 | * **Windows** - Visual Studio 2022 (Microsoft Windows Server 2022)
46 | * **MacOS** - Apple Clang 11.0 (MacOS 11.7)
47 | * **Linux** - LLVM Clang 11.0, GNU C Compiler 9.4.0 (Ubuntu 22.04 LTS)
48 |
49 | ### Library and examples (C++)
50 |
51 | You can build a shared library and all the examples by `CMake` with the following commands.
52 |
53 | ```shell
54 | git clone https://github.com/tatsy/tinymesh.git --depth 1
55 | cd tinymesh
56 | git submodule update --init --recursive
57 | mkdir build && cd build
58 | cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON ..
59 | cmake --build . --config Release --parallel 2
60 | ```
61 |
62 | ### Python module
63 |
64 | You can install the Python module using `Pip`.
65 |
66 | ```shell
67 | pip install git+https://github.com/tatsy/tinymesh.git
68 | ```
69 |
70 | You can also use `poetry` to install from source codes.
71 |
72 | ```shell
73 | git clone https://github.com/tatsy/tinymesh
74 | cd tinymesh
75 | poetry install
76 | ```
77 |
78 | Run examples
79 | ---
80 |
81 | #### C++
82 |
83 | ```shell
84 | ./build/bin/example_simplify data/models/bunny.ply
85 | ```
86 |
87 | #### Python
88 |
89 | ```shell
90 | # Run "poetry shell" to enable virtualenv if you test with poetry.
91 | python examples/python/fill_and_fair.py data/models/bunny.ply
92 | ```
93 |
94 | Gallery
95 | ---
96 |
97 | #### Remeshing
98 |
99 |
100 |
101 | Input |
102 | Remesh |
103 | Remesh (bottom part) |
104 |
105 |
106 |  |
107 |  |
108 |  |
109 |
110 |
111 |
112 |
113 | #### Simplification
114 |
115 |
116 |
117 | Input |
118 | Simplify (50000 faces) |
119 | Simplify (10000 faces) |
120 |
121 |
122 |  |
123 |  |
124 |  |
125 |
126 |
127 |
128 | #### Denoising (L0 mesh smoothing)
129 |
130 |
131 |
132 | Original |
133 | Noisy |
134 | Denoise |
135 |
136 |
137 |  |
138 |  |
139 |  |
140 |
141 |
142 |
143 | #### Hole filling
144 |
145 |
146 |
147 | Original |
148 | Hole filled (minimum dihedral angles) |
149 | Hole filled (advancing front) |
150 |
151 |
152 |  |
153 |  |
154 |  |
155 |
156 |
157 |
158 |
159 | Notice
160 | ---
161 |
162 | The functions provided by this repo are not perfect and their process will fail for problematic meshes, e.g., with non-manifold faces. In such cases, you can fix the problem by repairing the mesh using [MeshFix](https://github.com/MarcoAttene/MeshFix-V2.1).
163 |
164 | License
165 | ---
166 |
167 | Mozilla Public License v2 (c) Tatsuya Yatagawa 2020-2021
168 |
--------------------------------------------------------------------------------
/build_exts.py:
--------------------------------------------------------------------------------
1 | import re
2 | import pathlib
3 | import platform
4 |
5 | from setuptools.errors import CCompilerError, PackageDiscoveryError
6 | from setuptools.command.build_ext import build_ext
7 |
8 | try:
9 | from pybind11.setup_helpers import Pybind11Extension
10 | except ImportError:
11 | from setuptools import Extension as Pybind11Extension
12 |
13 |
14 | class TinyMeshBuildExt(build_ext):
15 | def run(self):
16 | try:
17 | super(TinyMeshBuildExt, self).run()
18 | except FileNotFoundError:
19 | raise Exception("File not found. Could not compile C extension")
20 |
21 | def build_extension(self, ext):
22 | # common settings
23 | for e in self.extensions:
24 | e.define_macros.extend(
25 | [
26 | ("TINYMESH_PYTHON_MODULE", 1),
27 | ]
28 | )
29 |
30 | # OS specific settings
31 | if platform.system() == "Darwin":
32 | for e in self.extensions:
33 | e.extra_compile_args.extend(
34 | [
35 | "-mmacosx-version-min=10.15",
36 | ]
37 | )
38 |
39 | elif platform.system() == "Linux":
40 | for e in self.extensions:
41 | e.extra_compile_args.extend(
42 | [
43 | "-fopenmp",
44 | ]
45 | )
46 | e.extra_link_args.extend(
47 | [
48 | "-fopenmp",
49 | "-lstdc++fs",
50 | ]
51 | )
52 |
53 | # compiler specific settings
54 | if self.compiler.compiler_type == "unix":
55 | for e in self.extensions:
56 | e.extra_compile_args.extend(
57 | [
58 | "-std=c++17",
59 | ]
60 | )
61 |
62 | elif self.compiler.compiler_type == "msvc":
63 | for e in self.extensions:
64 | e.extra_compile_args.extend(["/utf-8", "/openmp"])
65 | e.define_macros.extend(
66 | [
67 | ("_CRT_SECURE_NO_WARNINGS", 1),
68 | ("_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING", 1),
69 | ]
70 | )
71 |
72 | # building
73 | try:
74 | super(TinyMeshBuildExt, self).build_extension(ext)
75 | except (CCompilerError, PackageDiscoveryError, ValueError):
76 | raise Exception("Could not compile C extension")
77 |
78 |
79 | exclude = ["src/tinymesh/ext"]
80 | sources = pathlib.Path().glob("src/**/*.cpp")
81 | sources = [str(path).replace("\\", "/") for path in sources]
82 | sources = [path for path in sources if all([not re.search(e, path) for e in exclude])]
83 |
84 | include = [
85 | "src/tinymesh",
86 | "src/tinymesh/ext/tinyobjloader",
87 | "src/tinymesh/ext/tinyply/source",
88 | "src/tinymesh/ext/eigen",
89 | "src/tinymesh/ext/spectra/include",
90 | ]
91 |
92 | ext_modules = [
93 | Pybind11Extension(
94 | "tinymesh",
95 | sources,
96 | language="c++",
97 | include_dirs=include,
98 | )
99 | ]
100 |
101 |
102 | def build(setup_kwargs):
103 | setup_kwargs.update(
104 | {
105 | "ext_modules": ext_modules,
106 | "cmdclass": {"build_ext": TinyMeshBuildExt},
107 | }
108 | )
109 |
--------------------------------------------------------------------------------
/clang-format.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | EXTS=".cpp .hpp .c .h"
4 | FILES=""
5 | for e in $EXTS; do
6 | NEW=`git ls-files | grep $e\$`
7 | FILES="${FILES} ${NEW}"
8 | done
9 |
10 | for f in $FILES; do
11 | echo $f
12 | clang-format -i $f
13 | done
14 |
15 |
--------------------------------------------------------------------------------
/cmake/FindGoogleTest.cmake:
--------------------------------------------------------------------------------
1 | include(FindPackageHandleStandardArgs)
2 |
3 | set(GoogleTest_DIR "GoogleTest_DIR" CACHE PATH "")
4 |
5 | if (WIN32)
6 | find_path(GTEST_INCLUDE_DIR
7 | NAMES gtest/gtest.h
8 | PATHS
9 | ${GoogleTest_DIR}
10 | ${GoogleTest_DIR}/include)
11 |
12 | find_library(GTEST_LIBRARY
13 | NAMES gtest
14 | PATHS
15 | ${GoogleTest_DIR}
16 | ${GoogleTest_DIR}/lib
17 | ${GoogleTest_DIR}/lib/Release)
18 |
19 | find_library(GTEST_MAIN_LIBRARY
20 | NAMES gtest_main
21 | PATHS
22 | ${GoogleTest_DIR}
23 | ${GoogleTest_DIR}/lib
24 | ${GoogleTest_DIR}/lib/Release)
25 | else()
26 | find_path(GTEST_INCLUDE_DIR
27 | NAMES gtest/gtest.h
28 | PATHS
29 | /usr/include
30 | /usr/local/include
31 | ${GoogleTest_DIR}
32 | ${GoogleTest_DIR}/include)
33 |
34 | find_library(GTEST_LIBRARY
35 | NAMES gtest
36 | PATHS
37 | /usr/lib
38 | /usr/local/lib
39 | /usr/lib/x86_64-linux-gnu
40 | ${GoogleTest_DIR}
41 | ${GoogleTest_DIR}/lib)
42 |
43 | find_library(GTEST_MAIN_LIBRARY
44 | NAMES gtest_main
45 | PATHS
46 | /usr/lib
47 | /usr/local/lib
48 | /usr/lib/x86_64-linux-gnu
49 | ${GoogleTest_DIR}
50 | ${GoogleTest_DIR}/lib
51 | ${GoogleTest_DIR}/lib/Release)
52 | endif()
53 |
54 | find_package_handle_standard_args(
55 | GoogleTest DEFAULT_MSG
56 | GTEST_INCLUDE_DIR
57 | GTEST_LIBRARY
58 | GTEST_MAIN_LIBRARY
59 | )
60 |
61 | if (GoogleTest_FOUND)
62 | message(STATUS "GoogleTest include: ${GTEST_INCLUDE_DIR}")
63 | message(STATUS "GoogleTest library: ${GTEST_LIBRARY}")
64 | message(STATUS "GoogleTest main library: ${GTEST_MAIN_LIBRARY}")
65 | set(GTEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIR})
66 | set(GTEST_LIBRARIES ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
67 | endif()
68 |
69 | mark_as_advanced(GTEST_INCLUDE_DIR GTEST_LIBRARY GTEST_MAIN_LIBRARY)
70 |
--------------------------------------------------------------------------------
/cmake/Macros.cmake:
--------------------------------------------------------------------------------
1 | # ----------
2 | # Add source codes in the folder
3 | # ----------
4 | function(add_folder SOURCES FOLDER_NAME)
5 | file(GLOB LOCAL_FILES
6 | "${FOLDER_NAME}/*.cpp" "${FOLDER_NAME}/*.hpp"
7 | "${FOLDER_NAME}/*.c" "${FOLDER_NAME}/*.h")
8 | set(${SOURCES} ${${SOURCES}} ${LOCAL_FILES} PARENT_SCOPE)
9 |
10 | source_group(${FOLDER_NAME} FILES ${LOCAL_FILES})
11 | endfunction()
12 |
13 | # ----------
14 | # Add example program
15 | # ----------
16 | function(ADD_EXAMPLE)
17 | set(options)
18 | set(oneValueArgs NAME)
19 | set(multiValueArgs SOURCES)
20 | cmake_parse_arguments(ADD_EXAMPLE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
21 |
22 | message(STATUS "Example: ${ADD_EXAMPLE_NAME}")
23 | set(EXP_EXE_NAME "example_${ADD_EXAMPLE_NAME}")
24 | add_executable(${EXP_EXE_NAME} ${ADD_EXAMPLE_SOURCES})
25 | add_dependencies(${EXP_EXE_NAME} ${TINYMESH_LIBRARY})
26 |
27 | target_include_directories(${EXP_EXE_NAME} PRIVATE ${TINYMESH_INCLUDE_DIR})
28 | target_link_libraries(${EXP_EXE_NAME} PRIVATE ${TINYMESH_LIBRARY})
29 |
30 | set_target_properties(${EXP_EXE_NAME} PROPERTIES FOLDER "Examples")
31 | set_target_properties(${EXP_EXE_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
32 | source_group("Source Files" FILES ${SOURCE_FILES})
33 |
34 | if (MSVC)
35 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi")
36 | set_property(TARGET ${EXP_EXE_NAME} APPEND PROPERTY LINK_FLAGS "/DEBUG /PROFILE")
37 | endif()
38 | endfunction(ADD_EXAMPLE)
39 |
40 | # ----------
41 | # Add test program
42 | # ----------
43 | function(ADD_UNIT_TEST)
44 | set(options)
45 | set(oneValueArgs NAME DEPS)
46 | set(multiValueArgs SOURCES)
47 | cmake_parse_arguments(ADD_UNIT_TEST "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
48 |
49 | message(STATUS "Test: ${ADD_UNIT_TEST_NAME}")
50 | add_executable(${ADD_UNIT_TEST_NAME})
51 | add_dependencies(${ADD_UNIT_TEST_NAME} ${TINYMESH_LIBRARY})
52 |
53 | target_sources(${ADD_UNIT_TEST_NAME} PRIVATE ${ADD_UNIT_TEST_SOURCES})
54 | target_include_directories(${ADD_UNIT_TEST_NAME} PRIVATE ${TINYMESH_INCLUDE_DIR} ${GTEST_INCLUDE_DIRS})
55 | target_link_libraries(${ADD_UNIT_TEST_NAME} PRIVATE ${TINYMESH_LIBRARY} ${GTEST_LIBRARIES})
56 |
57 | set_target_properties(${ADD_UNIT_TEST_NAME} PROPERTIES FOLDER "Tests")
58 | set_target_properties(${ADD_UNIT_TEST_NAME} PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})
59 | source_group("Source Files" FILES ${SOURCE_FILES})
60 |
61 | set(${ADD_UNIT_TEST_DEPS} ${${ADD_UNIT_TEST_DEPS}} ${ADD_UNIT_TEST_NAME} PARENT_SCOPE)
62 |
63 | add_test(NAME ${ADD_UNIT_TEST_NAME} COMMAND ${ADD_UNIT_TEST_NAME})
64 | set_tests_properties(${ADD_UNIT_TEST_NAME} PROPERTIES TIMEOUT 120)
65 | endfunction(ADD_UNIT_TEST)
66 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | !models/bunny.ply
2 | !models/bunny_mini.ply
3 | !models/fandisk.ply
4 | !models/box.obj
5 | !models/plane.obj
6 | !models/sphere.obj
7 | !models/torus.ply
8 |
--------------------------------------------------------------------------------
/data/get_large_meshes.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function
2 |
3 | import os
4 | import requests
5 | import zipfile
6 | import tarfile
7 | from collections import OrderedDict
8 |
9 | class Item:
10 | def __init__(self, url, name):
11 | self.url = url
12 | self.name = name
13 |
14 | def __repr__(self):
15 | return self.url
16 |
17 | target_folder = 'large_meshes'
18 |
19 | url_list = OrderedDict([
20 | ('happy_budda', Item('http://graphics.stanford.edu/pub/3Dscanrep/happy/happy_recon.tar.gz', 'happy_vrip.ply')),
21 | ('dragon', Item('http://graphics.stanford.edu/pub/3Dscanrep/dragon/dragon_recon.tar.gz', 'dragon_vrip.ply')),
22 | ('lucy', Item('http://graphics.stanford.edu/data/3Dscanrep/lucy.tar.gz', 'lucy.ply'))
23 | ])
24 |
25 | def get_file_from_url(url):
26 | temp = url.split('/')[-1]
27 | length = temp.rfind('?')
28 | if length == -1:
29 | return temp
30 | else:
31 | return temp[:length]
32 |
33 | def bar_string(rate):
34 | length = 40
35 | if rate == 100.0:
36 | return '=' * length
37 | else:
38 | num = int(rate / (100.0 / length))
39 | return ('=' * num) + '>' + (' ' * (length - num - 1))
40 |
41 | def download(url):
42 | print('Download: {:s}'.format(url))
43 |
44 | filename = get_file_from_url(url)
45 | r = requests.get(url, stream=True)
46 |
47 | size_total = int(r.headers['content-length'])
48 | size_get = 0
49 |
50 | chunk_size = 1024
51 | with open(filename, 'wb') as f:
52 | for chunk in r.iter_content(chunk_size=chunk_size):
53 | if chunk:
54 | f.write(chunk)
55 | f.flush()
56 | size_get += chunk_size
57 |
58 | rate = min(100.0, 100.0 * size_get / size_total)
59 | print('[ %6.2f %% ] [ %s ]' % (rate, bar_string(rate)), end='\r')
60 | print('\nDownload finished!!')
61 | return filename
62 |
63 | return None
64 |
65 | def unarchive(filename, target, extract_to):
66 | _, ext = os.path.splitext(filename)
67 | try:
68 | if ext == '.gz' or ext == '.tar':
69 | tar = tarfile.open(filename)
70 | members = list(filter(lambda mem: target in mem.name, tar.getmembers()))
71 | if len(members) != 1:
72 | raise Exception('Multiple files with name "{:s}" are detected!'.format(target))
73 |
74 | with open(extract_to, 'wb') as f:
75 | f.write(tar.extractfile(members[0]).read())
76 | tar.close()
77 |
78 | elif ext == '.zip':
79 | zip_file = zipfile.ZipFile(filename)
80 | members = list(filter(lambda mem: target in mem.filename, zip_file.infolist()))
81 | if len(members) != 1:
82 | raise Exception('Multiple files with name "{:s}" are detected!'.format(target))
83 |
84 | with open(extract_to, 'wb') as f:
85 | f.write(zip_file.open(members[0].filename).read())
86 | zip_file.close()
87 |
88 | else:
89 | raise Exception('Unknown extension: %s' % ext)
90 |
91 | except Exception as e:
92 | raise e
93 |
94 | print('File is unarchived: {:s}'.format(extract_to))
95 |
96 |
97 | def process(name, item):
98 | # Download
99 | filename = download(item.url)
100 | # Extract target file
101 | _, ext = os.path.splitext(item.name)
102 | extract_to = os.path.join(target_folder, name) + ext
103 | unarchive(filename, item.name, os.path.join(target_folder, name) + ext)
104 | # Remove urchive file
105 | os.remove(filename)
106 |
107 | def main():
108 | if not os.path.exists(target_folder):
109 | os.makedirs(target_folder)
110 |
111 | for name, item in url_list.items():
112 | process(name, item)
113 |
114 | if __name__ == '__main__':
115 | main()
--------------------------------------------------------------------------------
/data/models/box.obj:
--------------------------------------------------------------------------------
1 | ####
2 | #
3 | # OBJ File Generated by Meshlab
4 | #
5 | ####
6 | # Object box.obj
7 | #
8 | # Vertices: 8
9 | # Faces: 6
10 | #
11 | ####
12 | vn -0.577350 -0.577350 -0.577350
13 | v -0.500000 -0.500000 -0.500000
14 | vn 0.333333 -0.666667 -0.666667
15 | v 0.500000 -0.500000 -0.500000
16 | vn -0.666667 0.333333 -0.666667
17 | v -0.500000 0.500000 -0.500000
18 | vn 0.666667 0.666667 -0.333333
19 | v 0.500000 0.500000 -0.500000
20 | vn -0.666667 -0.666667 0.333333
21 | v -0.500000 -0.500000 0.500000
22 | vn 0.666667 -0.333333 0.666667
23 | v 0.500000 -0.500000 0.500000
24 | vn -0.333333 0.666667 0.666667
25 | v -0.500000 0.500000 0.500000
26 | vn 0.577350 0.577350 0.577350
27 | v 0.500000 0.500000 0.500000
28 | # 8 vertices, 0 vertices normals
29 |
30 | f 1//1 3//3 4//4 2//2
31 | f 1//1 5//5 7//7 3//3
32 | f 1//1 2//2 6//6 5//5
33 | f 8//8 7//7 5//5 6//6
34 | f 8//8 4//4 3//3 7//7
35 | f 8//8 6//6 2//2 4//4
36 | # 6 faces, 0 coords texture
37 |
38 | # End of File
39 |
--------------------------------------------------------------------------------
/data/models/bunny.ply:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/data/models/bunny.ply
--------------------------------------------------------------------------------
/data/models/bunny_mini.ply:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/data/models/bunny_mini.ply
--------------------------------------------------------------------------------
/data/models/fandisk.ply:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/data/models/fandisk.ply
--------------------------------------------------------------------------------
/data/models/torus.ply:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/data/models/torus.ply
--------------------------------------------------------------------------------
/examples/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_subdirectory(cpp)
2 |
--------------------------------------------------------------------------------
/examples/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # ----------
2 | # Add example directories
3 | # ----------
4 | add_example(NAME read_write SOURCES example_read_write.cpp)
5 | add_example(NAME remesh SOURCES example_remesh.cpp)
6 | add_example(NAME simplify SOURCES example_simplify.cpp)
7 | add_example(NAME smooth SOURCES example_smooth.cpp)
8 | add_example(NAME denoise SOURCES example_denoise.cpp)
9 | add_example(NAME hole_fill SOURCES example_hole_fill.cpp)
10 |
--------------------------------------------------------------------------------
/examples/cpp/example_denoise.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "tinymesh/tinymesh.h"
5 |
6 | namespace fs = std::filesystem;
7 | namespace tms = tinymesh;
8 |
9 | int main(int argc, char **argv) {
10 | try {
11 | if (argc <= 1) {
12 | printf("usage: %s [input mesh] [sigma]\n", argv[0]);
13 | return 1;
14 | }
15 |
16 | const double sigma = argc <= 2 ? 0.2 : atof(argv[2]);
17 |
18 | // Basename of input file
19 | const fs::path filepath = fs::canonical(fs::path(argv[1]));
20 | const fs::path dirpath = filepath.parent_path();
21 | const std::string extension = filepath.extension().string();
22 | const std::string basename = filepath.stem().string();
23 |
24 | // Random number generator
25 | std::random_device randev;
26 | std::mt19937 mt(randev());
27 | std::uniform_real_distribution dist(-0.5, 0.5);
28 |
29 | // Save noise mesh
30 | std::string noiseMeshFile;
31 | {
32 | tms::Mesh mesh(argv[1]);
33 | mesh.fillHoles();
34 | const double Lavg = mesh.getMeanEdgeLength();
35 |
36 | // Add noise
37 | for (int i = 0; i < (int)mesh.numVertices(); i++) {
38 | const Vec3 pos = mesh.vertex(i)->pos();
39 | const Vec3 noise = Vec3(dist(mt), dist(mt), dist(mt));
40 | mesh.vertex(i)->setPos(pos + Lavg * noise);
41 | }
42 | noiseMeshFile = (dirpath / fs::path((basename + "_noise" + extension).c_str())).string();
43 | mesh.save(noiseMeshFile);
44 | }
45 |
46 | // Denoise (Normal Gaussian filter)
47 | {
48 | tms::Mesh mesh(noiseMeshFile);
49 | mesh.fillHoles();
50 | tms::denoiseNormalGaussian(mesh, sigma, 10);
51 |
52 | const std::string outfile =
53 | (dirpath / fs::path((basename + "_denoise_Gaussian" + extension).c_str())).string();
54 | mesh.save(outfile);
55 | printf("Save: %s\n", outfile.c_str());
56 | }
57 |
58 | // Denoise (Normal bilateral filter)
59 | {
60 | tms::Mesh mesh(noiseMeshFile);
61 | mesh.fillHoles();
62 | tms::denoiseNormalBilateral(mesh, sigma, 0.1, 10);
63 |
64 | const std::string outfile =
65 | (dirpath / fs::path((basename + "_denoise_bilateral" + extension).c_str())).string();
66 | mesh.save(outfile);
67 | printf("Save: %s\n", outfile.c_str());
68 | }
69 |
70 | // Denoise (L0 smoothing)
71 | {
72 | tms::Mesh mesh(noiseMeshFile);
73 | mesh.fillHoles();
74 | tms::denoiseL0Smooth(mesh);
75 |
76 | const std::string outfile = (dirpath / fs::path((basename + "_denoise_l0" + extension).c_str())).string();
77 | mesh.save(outfile);
78 | printf("Save: %s\n", outfile.c_str());
79 | }
80 | } catch (std::runtime_error &e) {
81 | std::cerr << e.what() << std::endl;
82 | return 1;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/examples/cpp/example_hole_fill.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "tinymesh/tinymesh.h"
4 |
5 | namespace fs = std::filesystem;
6 | namespace tms = tinymesh;
7 |
8 | int main(int argc, char **argv) {
9 | if (argc <= 1) {
10 | printf("usage: %s [input mesh]\n", argv[0]);
11 | return 1;
12 | }
13 |
14 | // Basename of input file
15 | const fs::path filepath = fs::canonical(fs::path(argv[1]));
16 | const fs::path dirpath = filepath.parent_path();
17 | const std::string extension = filepath.extension().string();
18 | const std::string basename = filepath.stem().string();
19 |
20 | // Hole filling with minimum dihedral angle [Liepa 2003]
21 | {
22 | tms::Mesh mesh(argv[1]);
23 | for (int i = 0; i < mesh.numFaces(); i++) {
24 | tms::Face *f = mesh.face(i);
25 | if (f->isHole()) {
26 | tms::holeFillMinDihedral(mesh, f, Pi / 6.0);
27 | }
28 | }
29 | const std::string outfile = (dirpath / fs::path((basename + "_fill_minDA" + extension).c_str())).string();
30 | mesh.save(outfile);
31 | printf("Save: %s\n", outfile.c_str());
32 | }
33 |
34 | // Hole filling with advancing front algorithm [Zhao et al. 2007]
35 | {
36 | tms::Mesh mesh(argv[1]);
37 | std::vector holeFaces;
38 | for (int i = 0; i < mesh.numFaces(); i++) {
39 | tms::Face *f = mesh.face(i);
40 | if (f->isHole()) {
41 | tms::holeFillAdvancingFront(mesh, f);
42 | }
43 | }
44 | const std::string outfile = (dirpath / fs::path((basename + "_fill_adv_front" + extension).c_str())).string();
45 | mesh.save(outfile);
46 | printf("Save: %s\n", outfile.c_str());
47 | }
48 |
49 | // Context-based coherent surface completion [Harary and Tal 2016]
50 | {
51 | tms::Mesh mesh(argv[1]);
52 | tms::holeFillContextCoherent(mesh, 300);
53 |
54 | const std::string outfile =
55 | (dirpath / fs::path((basename + "_fill_context_coherent" + extension).c_str())).string();
56 | mesh.save(outfile);
57 | printf("Save: %s\n", outfile.c_str());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/examples/cpp/example_read_write.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "tinymesh/tinymesh.h"
4 |
5 | namespace fs = std::filesystem;
6 | namespace tms = tinymesh;
7 |
8 | int main(int argc, char **argv) {
9 | if (argc <= 1) {
10 | printf("usage: %s [input mesh]\n", argv[0]);
11 | return 1;
12 | }
13 |
14 | // Load
15 | tms::Mesh mesh(argv[1]);
16 |
17 | // Save
18 | const fs::path filepath = fs::canonical(fs::path(argv[1]));
19 | const fs::path dirpath = filepath.parent_path();
20 | const std::string extension = filepath.extension().string();
21 | const std::string basename = filepath.stem().string();
22 | const std::string outfile = (dirpath / fs::path((basename + "_copy" + extension).c_str())).string();
23 | mesh.save(outfile);
24 | printf("Save: %s\n", outfile.c_str());
25 | }
26 |
--------------------------------------------------------------------------------
/examples/cpp/example_remesh.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "tinymesh/tinymesh.h"
4 |
5 | namespace fs = std::filesystem;
6 | namespace tms = tinymesh;
7 |
8 | int main(int argc, char **argv) {
9 | if (argc <= 1) {
10 | printf("usage: %s [input mesh] [keep angle]\n", argv[0]);
11 | return 1;
12 | }
13 |
14 | // Load
15 | tms::Mesh mesh(argv[1]);
16 |
17 | // Fill holes & remesh
18 | const double keepAngle = argc > 2 ? std::atof(argv[2]) * Pi / 180.0 : 0.0;
19 | mesh.fillHoles();
20 | tms::remeshTriangular(mesh, 0.8, 1.333, keepAngle);
21 |
22 | // Save
23 | const fs::path filepath = fs::canonical(fs::path(argv[1]));
24 | const fs::path dirpath = filepath.parent_path();
25 | const std::string extension = filepath.extension().string();
26 | const std::string basename = filepath.stem().string();
27 | const std::string outfile = (dirpath / fs::path((basename + "_remesh" + extension).c_str())).string();
28 | mesh.save(outfile);
29 | printf("Save: %s\n", outfile.c_str());
30 | }
31 |
--------------------------------------------------------------------------------
/examples/cpp/example_simplify.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "tinymesh/tinymesh.h"
4 |
5 | namespace fs = std::filesystem;
6 | namespace tms = tinymesh;
7 |
8 | int main(int argc, char **argv) {
9 | if (argc <= 1) {
10 | printf("usage: %s [input mesh] [ratio]\n", argv[0]);
11 | return 1;
12 | }
13 |
14 | printf("Hello\n");
15 |
16 | const double ratio = argc <= 2 ? 0.1 : atof(argv[2]);
17 |
18 | // Load
19 | tms::Mesh mesh(argv[1]);
20 |
21 | // Simplify
22 | const int target = (int)(ratio * mesh.numFaces());
23 | mesh.fillHoles();
24 | tms::simplifyQEM(mesh, target, 10, true);
25 | tms::remeshTriangular(mesh, 0.0, 1e6); // remesh by keeping #faces
26 |
27 | // Save
28 | const fs::path filepath = fs::canonical(fs::path(argv[1]));
29 | const fs::path dirpath = filepath.parent_path();
30 | const std::string extension = filepath.extension().string();
31 | const std::string basename = filepath.stem().string();
32 | const std::string outfile = (dirpath / fs::path((basename + "_simplify" + extension).c_str())).string();
33 | mesh.save(outfile);
34 | printf("Save: %s\n", outfile.c_str());
35 | }
36 |
--------------------------------------------------------------------------------
/examples/cpp/example_smooth.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "tinymesh/tinymesh.h"
4 |
5 | namespace fs = std::filesystem;
6 | namespace tms = tinymesh;
7 |
8 | int main(int argc, char **argv) {
9 | if (argc <= 1) {
10 | printf("usage: %s [input mesh]\n", argv[0]);
11 | return 1;
12 | }
13 |
14 | // Basename of input file
15 | const fs::path filepath = fs::canonical(fs::path(argv[1]));
16 | const fs::path dirpath = filepath.parent_path();
17 | const std::string extension = filepath.extension().string();
18 | const std::string basename = filepath.stem().string();
19 |
20 | // Smooth (Laplacian)
21 | {
22 | tms::Mesh mesh(argv[1]);
23 | mesh.fillHoles();
24 | tms::smoothLaplacian(mesh, 0.5, true, 100);
25 | const std::string outfile = (dirpath / fs::path((basename + "_laplace_smooth" + extension).c_str())).string();
26 | mesh.save(outfile);
27 | printf("Save: %s\n", outfile.c_str());
28 | }
29 |
30 | // Smooth (Taubin)
31 | {
32 | tms::Mesh mesh(argv[1]);
33 | mesh.fillHoles();
34 | tms::smoothTaubin(mesh, 0.5, 0.53, 100);
35 | const std::string outfile = (dirpath / fs::path((basename + "_taubin_smooth" + extension).c_str())).string();
36 | mesh.save(outfile);
37 | printf("Save: %s\n", outfile.c_str());
38 | }
39 |
40 | // Smooth (implicit fairing)
41 | {
42 | tms::Mesh mesh(argv[1]);
43 | mesh.fillHoles();
44 | tms::implicitFairing(mesh, 1.0e-2, 1);
45 | const std::string outfile = (dirpath / fs::path((basename + "_implicit_fair" + extension).c_str())).string();
46 | mesh.save(outfile);
47 | printf("Save: %s\n", outfile.c_str());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/examples/python/viewer.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib.pyplot as plt
3 | from matplotlib.collections import PolyCollection
4 |
5 |
6 | def normalize(x):
7 | return x / np.linalg.norm(x)
8 |
9 |
10 | def frustum(left, right, bottom, top, znear, zfar):
11 | M = np.zeros((4, 4))
12 | M[0, 0] = +2.0 * znear / (right - left)
13 | M[1, 1] = +2.0 * znear / (top - bottom)
14 | M[2, 2] = -(zfar + znear) / (zfar - znear)
15 | M[0, 2] = (right + left) / (right - left)
16 | M[2, 1] = (top + bottom) / (top - bottom)
17 | M[2, 3] = -2.0 * znear * zfar / (zfar - znear)
18 | M[3, 2] = -1.0
19 | return M
20 |
21 |
22 | class Viewer(object):
23 | def __init__(self, width=500, height=500, dpi=100):
24 | self.width = width
25 | self.height = height
26 | self.aspect = width / height
27 | self.dpi = 100
28 | self.set_identity()
29 |
30 | def set_identity(self):
31 | self.M = np.eye(4)
32 | self.V = np.eye(4)
33 | self.P = np.eye(4)
34 |
35 | def perspective(self, fovy, aspect, znear, zfar):
36 | h = np.tan(0.5 * np.radians(fovy)) * znear
37 | w = h * aspect
38 | P = frustum(-w, w, -h, h, znear, zfar)
39 | self.P = self.P @ P
40 |
41 | def lookat(self, ex, ey, ez, cx, cy, cz, ux, uy, uz):
42 | e = np.array((ex, ey, ez))
43 | c = np.array((cx, cy, cz))
44 | u = np.array((ux, uy, uz))
45 | z = normalize(e - c)
46 | x = normalize(np.cross(u, z))
47 | y = normalize(np.cross(z, x))
48 | rot = np.eye(4)
49 | rot[0, :3] = x
50 | rot[1, :3] = y
51 | rot[2, :3] = z
52 | tran = np.eye(4)
53 | tran[:3, 3] = -e
54 | V = rot @ tran
55 | self.V = self.V @ V
56 |
57 | def translate(self, x, y, z):
58 | M = np.array([
59 | [1.0, 0.0, 0.0, x],
60 | [0.0, 1.0, 0.0, y],
61 | [0.0, 0.0, 1.0, z],
62 | [0.0, 0.0, 0.0, 1.0],
63 | ])
64 | self.M = self.M @ M
65 |
66 | def rotate(self, theta, wx, wy, wz):
67 | rad = np.radians(theta)
68 | st = np.sin(rad)
69 | ct = np.cos(rad)
70 | K = np.array([[0.0, -wz, wy], [wz, 0.0, -wx], [-wy, wx, 0.0]])
71 | R = np.eye(3) + st * K + (1.0 - ct) * (K @ K)
72 | M = np.eye(4)
73 | M[:3, :3] = R
74 | self.M = self.M @ M
75 |
76 | def scale(self, sx, sy, sz):
77 | M = np.eye(4)
78 | M[0, 0] = sx
79 | M[1, 1] = sy
80 | M[2, 2] = sz
81 | self.M = self.M @ M
82 |
83 | def visualization(self,
84 | verts,
85 | faces,
86 | colors=None,
87 | wireframe=False,
88 | shade=True,
89 | title="",
90 | save=False,
91 | filename="output.png"):
92 | V = verts.copy()
93 | F = faces.copy()
94 | if colors is not None:
95 | C = colors[:, :3].copy()
96 | else:
97 | C = 0.7 * np.ones_like(verts)
98 |
99 | ## coordinate transformation
100 | MV = self.V @ self.M
101 | MVP = self.P @ MV
102 | V = V[F]
103 | C = C[F]
104 |
105 | N = np.cross(V[:, 1, :] - V[:, 0, :], V[:, 2, :] - V[:, 0, :])
106 | N /= np.linalg.norm(N, axis=-1, keepdims=True)
107 | N = N @ MV[:3, :3].T
108 | N /= np.linalg.norm(N, axis=-1, keepdims=True)
109 | L = np.c_[np.zeros(N.shape), np.ones((*N.shape[:-1], 1))] @ MV.T
110 | L = -L[:, :3] / L[:, 3:4]
111 | L /= np.linalg.norm(L, axis=-1, keepdims=True)
112 | NdotL = np.maximum(0.0, np.sum(N * L, axis=-1, keepdims=True))
113 |
114 | V = np.c_[V, np.ones((*V.shape[:-1], 1))] @ MVP.T
115 | V /= V[:, :, 3:4]
116 | T = V[:, :, :2]
117 |
118 | ## z-sort
119 | Z = -V[:, :, 2].mean(axis=1)
120 | zmin, zmax = Z.min(), Z.max()
121 | Z = (Z - zmin) / (zmax - zmin)
122 | l = np.argsort(Z)
123 |
124 | C = C.mean(axis=1)
125 | if shade:
126 | C *= NdotL
127 | T, C = T[l, :], C[l, :]
128 |
129 | if wireframe:
130 | linewidth = 0.1
131 | edgecolor = "black"
132 | else:
133 | linewidth = 1.0
134 | edgecolor = C
135 |
136 | xsiz = int(self.width / self.dpi)
137 | ysiz = int(self.height / self.dpi)
138 | fig = plt.figure(figsize=(xsiz, ysiz), dpi=self.dpi)
139 | ax = fig.add_axes([0, 0, 1, 1], xlim=[-1, 1], ylim=[-1, 1], aspect=1.0 / self.aspect, frameon=False)
140 | ax.axis("off")
141 | collection = PolyCollection(T,
142 | closed=True,
143 | linewidth=linewidth,
144 | facecolor=C,
145 | edgecolor=edgecolor,
146 | antialiaseds=True)
147 | ax.add_collection(collection)
148 |
149 | if len(title) != 0:
150 | plt.title(title)
151 |
152 | if save:
153 | plt.savefig(filename, bbox_inches="tight")
154 |
155 | plt.show()
156 |
157 | def mesh_visualization(self, mesh, *args, **kwargs):
158 | verts = mesh.get_vertices()
159 | faces = mesh.get_vertex_indices()
160 | verts = np.asarray(verts, dtype="float32")
161 | faces = np.asarray(faces, dtype="uint32").reshape((-1, 3))
162 | self.visualization(verts, faces, *args, **kwargs)
163 |
--------------------------------------------------------------------------------
/figures/bunny_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/bunny_before.png
--------------------------------------------------------------------------------
/figures/bunny_hole_fill_adv_front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/bunny_hole_fill_adv_front.png
--------------------------------------------------------------------------------
/figures/bunny_hole_fill_min_dihedral.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/bunny_hole_fill_min_dihedral.png
--------------------------------------------------------------------------------
/figures/bunny_holes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/bunny_holes.png
--------------------------------------------------------------------------------
/figures/bunny_remesh_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/bunny_remesh_1.png
--------------------------------------------------------------------------------
/figures/bunny_remesh_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/bunny_remesh_2.png
--------------------------------------------------------------------------------
/figures/dragon_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/dragon_before.png
--------------------------------------------------------------------------------
/figures/dragon_simplify_10000.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/dragon_simplify_10000.png
--------------------------------------------------------------------------------
/figures/dragon_simplify_50000.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/dragon_simplify_50000.png
--------------------------------------------------------------------------------
/figures/fandisk_before.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/fandisk_before.png
--------------------------------------------------------------------------------
/figures/fandisk_denoise_l0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/fandisk_denoise_l0.png
--------------------------------------------------------------------------------
/figures/fandisk_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tatsy/tinymesh/c3f46d036a3a04b84baf236be7b08358ac6124bf/figures/fandisk_noise.png
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "poetry_core>=1.5.0", "pybind11>=2.0.0"]
3 | build-backend = "poetry.core.masonry.api"
4 |
5 | [tool.poetry]
6 | name = "tinymesh"
7 | version = "0.2.4"
8 | description = "TinyMesh: a light-weight mesh processing library"
9 |
10 | license = "MPL-v2"
11 |
12 | authors = ["Tatsuya Yatagawa "]
13 | readme = 'README.md'
14 | repository = "https://github.com/tatsy/tinymesh"
15 | homepage = "https://github.com/tatsy/tinymesh"
16 |
17 | keywords = ['graphics', 'geometry']
18 | exclude = ["src/*", "tests/*", "cmake/*", "data/*", "figures/*"]
19 |
20 | [tool.poetry.build]
21 | script = "build_exts.py"
22 | generate-setup-file = true
23 |
24 | [tool.poetry.dependencies]
25 | python = ">=3.8,<=3.12"
26 | setuptools = "^57"
27 | pybind11 = "^2.0"
28 |
29 | [tool.poetry.group.dev.dependencies]
30 | black = "*"
31 | flake8 = "*"
32 | mypy = "*"
33 | isort = "*"
34 | pytest = "*"
35 | jupyterlab = "*"
36 | matplotlib = "*"
37 |
38 | numpy = [
39 | { version = "~1.26.0", python = "^3.12" },
40 | { version = "*", python = ">=3.8,<3.12" },
41 | ]
42 |
43 | scipy = [
44 | { version = "~1.11.2", python = "^3.12" },
45 | { version = "*", python = ">=3.8,<3.12" },
46 | ]
47 |
48 | [tool.isort]
49 | profile = "black"
50 | length_sort = "True"
51 | line_length = 120
52 |
53 | [tool.mypy]
54 | python_version = 3.9
55 | follow_imports = "silent"
56 | disallow_any_generics = "True"
57 | strict_optional = "True"
58 | check_untyped_defs = "True"
59 | allow_redefinition = "True"
60 |
61 | [tool.flake8]
62 | max-line-length = 120
63 | ignore = "Q000,E127,E203,E402,W503,W504"
64 |
65 | [tool.black]
66 | target-version = ["py39", "py310", "py311"]
67 | line-length = 120
68 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_subdirectory(tinymesh)
2 |
--------------------------------------------------------------------------------
/src/pybind11.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | namespace py = pybind11;
6 |
7 | #include "tinymesh/tinymesh.h"
8 | using namespace tinymesh;
9 |
10 | // type caster: Matrix <-> NumPy-array
11 | namespace pybind11 {
12 | namespace detail {
13 |
14 | template
15 | struct type_caster> {
16 | public:
17 | using Type = Vec;
18 | PYBIND11_TYPE_CASTER(Type, _("Vec"));
19 |
20 | // Conversion part 1 (Python -> C++)
21 | bool load(py::handle src, bool convert) {
22 | if (!convert && !py::array_t::check_(src)) return false;
23 |
24 | auto buf = py::array_t::ensure(src);
25 | if (!buf) return false;
26 |
27 | auto dims = buf.ndim();
28 | if (dims != 1) return false;
29 | auto elems = buf.shape()[0];
30 | if (elems != Dims) return false;
31 |
32 | value = Vec();
33 | const Float *ptr = buf.data();
34 | for (int d = 0; d < Dims; d++) {
35 | value[d] = ptr[d];
36 | }
37 |
38 | return true;
39 | }
40 |
41 | // Conversion part 2 (C++ -> Python)
42 | static py::handle cast(const Vec &src, py::return_value_policy policy, py::handle parent) {
43 | std::vector shape(1, Dims);
44 | std::vector strides(1, sizeof(Float));
45 | py::array a(std::move(shape), std::move(strides), (Float *)&src);
46 | return a.release();
47 | }
48 | };
49 |
50 | } // namespace detail
51 | } // namespace pybind11
52 |
53 | PYBIND11_MODULE(tinymesh, m) {
54 | py::class_(m, "Mesh")
55 | .def(py::init<>())
56 | .def(py::init())
57 | .def(py::init &, const std::vector &>())
58 | .def("clone", &Mesh::clone, py::return_value_policy::move)
59 | .def("load", &Mesh::load)
60 | .def("save", &Mesh::save)
61 | .def("vertex", &Mesh::vertex, py::return_value_policy::reference)
62 | .def("face", &Mesh::face, py::return_value_policy::reference)
63 | .def("get_vertices", &Mesh::getVertices)
64 | .def("get_vertex_indices", &Mesh::getVertexIndices)
65 | .def("num_vertices", &Mesh::numVertices)
66 | .def("num_edges", &Mesh::numEdges)
67 | .def("num_halfedges", &Mesh::numHalfedges)
68 | .def("num_faces", &Mesh::numFaces)
69 | .def("fill_holes", &Mesh::fillHoles, "Fill all holes")
70 | .def("verify", &Mesh::verify);
71 |
72 | py::class_>(m, "Vertex")
73 | .def(py::init<>())
74 | .def("pos", &Vertex::pos)
75 | .def("set_pos", &Vertex::setPos)
76 | .def("normal", &Vertex::normal)
77 | .def("K", &Vertex::K)
78 | .def("H", &Vertex::H)
79 | .def("is_boundary", &Vertex::isBoundary)
80 | .def("is_locked", &Vertex::isLocked)
81 | .def("lock", &Vertex::lock)
82 | .def("unlock", &Vertex::unlock);
83 |
84 | py::class_>(m, "Face")
85 | .def(py::init<>())
86 | .def("area", &Face::area)
87 | .def("is_boundary", &Face::isBoundary)
88 | .def("is_hole", &Face::isHole)
89 | .def("is_locked", &Face::isLocked);
90 |
91 | /*** Metrics ***/
92 | m.def("get_hausdorff_distance", &getHausdorffDistance, "Hausdorff distance", py::arg("mesh0"), py::arg("mesh1"));
93 | m.def("get_per_vertex_shortest_distances", &getPerVertexShortestDistances, "Per-vertex shortest distances",
94 | py::arg("src"), py::arg("dst"));
95 |
96 | /*** Utilities ***/
97 | py::enum_(m, "MeshLaplace")
98 | .value("ADJACENT", MeshLaplace::Adjacent)
99 | .value("COTANGENT", MeshLaplace::Cotangent)
100 | .value("BELKIN08", MeshLaplace::Belkin08);
101 |
102 | m.def("get_mesh_laplacian", &getMeshLaplacian, "Laplacian-Beltrami opeartor", py::arg("mesh"), py::arg("type"));
103 | m.def("get_heat_kernel_signatures", &getHeatKernelSignatures, "Heat kernel signatures", py::arg("mesh"),
104 | py::arg("K") = 300, py::arg("n_times") = 100);
105 | m.def("get_principal_curvatures", &getPrincipalCurvatures, "Principal curvatures", py::arg("mesh"),
106 | py::arg("smooth_tensors") = false);
107 | m.def("get_principal_curvatures_with_derivatives", &getPrincipalCurvaturesWithDerivatives,
108 | "Principal curvatures with derivatives", py::arg("mesh"), py::arg("smooth_tensors") = false);
109 | m.def("get_feature_line_field", &getFeatureLineField, "Feature line field", py::arg("mesh"),
110 | py::arg("smooth_tensors") = false);
111 | m.def("get_feature_line_field_with_flags", getFeatureLineFieldWithFlags,
112 | "Feature line field with ridge/valley flags", py::arg("mesh"), py::arg("smooth_tensors") = false);
113 |
114 | /*** Smoothing ***/
115 | m.def("smooth_laplacian", &smoothLaplacian, "Laplacian smoothing", py::arg("mesh"), py::arg("epsilon") = 1.0,
116 | py::arg("cotangent_weight") = false, py::arg("iterations") = 3);
117 | m.def("smooth_taubin", &smoothTaubin, "Taubin smoothing", py::arg("mesh"), py::arg("shrink") = 0.5,
118 | py::arg("inflate") = 0.53, py::arg("iterations") = 3);
119 | m.def("implicit_fairing", &implicitFairing, "Implicit fairing", py::arg("mesh"), py::arg("epsilon") = 1.0,
120 | py::arg("iterations") = 1);
121 |
122 | /*** Denoising ***/
123 | m.def("denoise_normal_gaussian", &denoiseNormalGaussian, "Denoising by normal Gaussian filter", py::arg("mesh"),
124 | py::arg("sigma") = 0.2, py::arg("iterations") = 3);
125 | m.def("denoise_normal_bilateral", &denoiseNormalBilateral, "Denoising by normal bilateral filter", py::arg("mesh"),
126 | py::arg("sigma_c") = 0.2, py::arg("sigma_s") = 0.1, py::arg("iterations") = 3);
127 | m.def("denoise_l0_smooth", &denoiseL0Smooth, "Denoising by L0 smoothing", py::arg("mesh"), py::arg("alpha") = 0.1,
128 | py::arg("beta") = 0.001);
129 |
130 | /*** Remesh ***/
131 | m.def("remesh_triangular", &remeshTriangular, "Triangle remeshing", py::arg("mesh"), py::arg("short_length") = 0.8,
132 | py::arg("long_length") = 1.333, py::arg("angle_keep_less_than") = 0.0, py::arg("iterations") = 5,
133 | py::arg("verbose") = false);
134 |
135 | /*** Simplification ***/
136 | m.def("simplify_qem", &simplifyQEM, "QEM-based simplification", py::arg("mesh"), py::arg("n_triangles"),
137 | py::arg("n_trials") = 10, py::arg("verbose") = false);
138 |
139 | /*** Hole filling ***/
140 | m.def("hole_fill_min_dihedral", &holeFillMinDihedral, "Max-area hole filling", py::arg("mesh"), py::arg("face"),
141 | py::arg("dihedral_bound") = Pi);
142 | m.def("hole_fill_advancing_front", &holeFillAdvancingFront, "Advancing front hole filling", py::arg("mesh"),
143 | py::arg("face"));
144 | m.def("hole_fill_context_coherent", &holeFillContextCoherent, "Context-based coherent surface completion",
145 | py::arg("mesh"), py::arg("maxiters") = 200);
146 | }
147 |
--------------------------------------------------------------------------------
/src/tinymesh/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # ----------
2 | # Common target names
3 | # ----------
4 | set(EIGEN_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/ext/eigen)
5 | set(SPECTRA_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/ext/spectra/include)
6 | set(TINYOBJ_LOADER_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/ext/tinyobjloader)
7 | set(SOURCE_FILES ${SOURCE_FILES};${TINYOBJ_LOADER_INCLUDE_DIR}/tiny_obj_loader.h)
8 | set(TINYPLY_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR}/ext/tinyply/source)
9 | set(SOURCE_FILES ${SOURCE_FILES};${TINYPLY_INCLUDE_DIR}/tinyply.h)
10 |
11 | # ----------
12 | # Components
13 | # ----------
14 | set(SOURCE_FILES ${SOURCE_FILES} "tinymesh.h")
15 | add_folder(SOURCE_FILES "core")
16 | add_folder(SOURCE_FILES "filters")
17 | add_folder(SOURCE_FILES "ops")
18 | add_folder(SOURCE_FILES "remesh")
19 | add_folder(SOURCE_FILES "restore")
20 |
21 | add_library(${TINYMESH_LIBRARY} STATIC ${SOURCE_FILES})
22 |
23 | source_group("Source Files" FILES ${SOURCE_FILES})
24 |
25 | target_include_directories(
26 | ${TINYMESH_LIBRARY} PUBLIC
27 | ${CMAKE_CURRENT_LIST_DIR}
28 | ${OpenMP_INCLUDE_DIRS}
29 | ${EIGEN_INCLUDE_DIR}
30 | ${SPECTRA_INCLUDE_DIR}
31 | ${TINYOBJ_LOADER_INCLUDE_DIR}
32 | ${TINYPLY_INCLUDE_DIR})
33 |
34 | target_link_libraries(
35 | ${TINYMESH_LIBRARY} PUBLIC
36 | ${CXX_FS_LIBRARY}
37 | ${OpenMP_LIBRARIES})
38 |
39 | # Disable warning for external files
40 | set(WARNING_DISABLED_CXX_FLAGS)
41 | if (MSVC)
42 | set(WARNING_DISABLED_CXX_FLAGS "${CMAKE_CXX_FLAGS} /w")
43 | else()
44 | set(WARNING_DISABLED_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w")
45 | endif()
46 |
47 | set_source_files_properties(
48 | ${CMAKE_CURRENT_LIST_DIR}/core/external.cpp
49 | PROPERTIES
50 | COMPILE_FLAGS ${WARNING_DISABLED_CXX_FLAGS})
51 |
52 | # Enable profiler options for MSVC
53 | if (MSVC)
54 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi")
55 | set_property(TARGET ${TINYMESH_LIBRARY} APPEND PROPERTY LINK_FLAGS "/DEBUG /PROFILE")
56 | endif()
57 |
--------------------------------------------------------------------------------
/src/tinymesh/core/api.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_API_H
6 | #define TINYMESH_API_H
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | static const double Pi = 4.0 * std::atan(1.0);
13 |
14 | // -----------------------------------------------------------------------------
15 | // Forward declaration
16 | // -----------------------------------------------------------------------------
17 | namespace tinymesh {
18 |
19 | class Vertex;
20 | class Halfedge;
21 | class Face;
22 | class Mesh;
23 |
24 | } // namespace tinymesh
25 |
26 | // -----------------------------------------------------------------------------
27 | // API export macro
28 | // -----------------------------------------------------------------------------
29 |
30 | #if (defined(WIN32) || defined(_WIN32) || defined(WINCE) || defined(__CYGWIN__))
31 | #if defined(TINYMESH_API_EXPORT)
32 | #define TINYMESH_API __declspec(dllexport)
33 | #define TINYMESH_IMPORTS
34 | #else
35 | #define TINYMESH_API
36 | #define TINYMESH_IMPORTS __declspec(dllimport)
37 | #endif
38 | #elif defined(__GNUC__) && __GNUC__ >= 4
39 | #define TINYMESH_API __attribute__((visibility("default")))
40 | #define TINYMESH_IMPORTS
41 | #else
42 | #define TINYMESH_API
43 | #define TINYMESH_IMPORTS
44 | #endif
45 |
46 | #endif // TINYMESH_API_H
47 |
--------------------------------------------------------------------------------
/src/tinymesh/core/bounds.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_BOUNDS_H
6 | #define TINYMESH_BOUNDS_H
7 |
8 | #include "debug.h"
9 | #include "vec.h"
10 |
11 | namespace tinymesh {
12 |
13 | template
14 | class Bounds {
15 | typedef Vec VecD;
16 |
17 | public:
18 | Bounds() = default;
19 |
20 | Bounds(const VecD &posMin, const VecD &posMax)
21 | : posMin_(posMin)
22 | , posMax_(posMax) {
23 | }
24 |
25 | void merge(const VecD &p) {
26 | posMin_ = std::min(posMin_, p);
27 | posMax_ = std::max(posMax_, p);
28 | }
29 |
30 | double area() const {
31 | static_assert(Dims == 3, "Bounds::area is defined only for 3D bounds!");
32 | const VecD d = std::abs(posMax_ - posMin_);
33 | return (2.0 * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0]));
34 | }
35 |
36 | bool inside(const VecD &p) const {
37 | for (int d = 0; d < Dims; d++) {
38 | if (p[d] < posMin_[d] || posMax_[d] < p[d]) return false;
39 | }
40 | return true;
41 | }
42 |
43 | int maxExtent() const {
44 | const VecD v = std::abs(posMax_ - posMin_);
45 | int dim = -1;
46 | Float vmax = -1.0;
47 | for (int d = 0; d < Dims; d++) {
48 | if (vmax < v[d]) {
49 | vmax = v[d];
50 | dim = d;
51 | }
52 | }
53 |
54 | Assertion(dim >= 0, "Something is wrong!");
55 | return dim;
56 | }
57 |
58 | double distance(const VecD &p) {
59 | const Vec3 vmin = std::abs(posMin_ - p);
60 | const Vec3 vmax = std::abs(posMax_ - p);
61 | double dist = 0.0;
62 | for (int d = 0; d < Dims; d++) {
63 | if (p[d] < posMin_[d] || posMax_[d] < p[d]) {
64 | const double gap = std::min(vmin[d], vmax[d]);
65 | dist += gap * gap;
66 | }
67 | }
68 | return std::sqrt(dist);
69 | }
70 |
71 | static Bounds merge(const Bounds &b0, const Bounds &b1) {
72 | Bounds ret;
73 | ret.posMin_ = std::min(b0.posMin_, b1.posMin_);
74 | ret.posMax_ = std::max(b0.posMax_, b1.posMax_);
75 | return ret;
76 | }
77 |
78 | VecD posMin() const {
79 | return posMin_;
80 | }
81 | VecD posMax() const {
82 | return posMax_;
83 | }
84 |
85 | private:
86 | VecD posMin_ = VecD(1.0e20);
87 | VecD posMax_ = VecD(-1.0e20);
88 | };
89 |
90 | using Bounds2 = Bounds;
91 | using Bounds3 = Bounds;
92 |
93 | } // namespace tinymesh
94 |
95 | #endif // TINYMESH_BOUNDS_H
96 |
--------------------------------------------------------------------------------
/src/tinymesh/core/bvh.cpp:
--------------------------------------------------------------------------------
1 | #define TINYMESH_API_EXPORT
2 | #include "bvh.h"
3 |
4 | #include
5 |
6 | #include "mesh.h"
7 |
8 | namespace tinymesh {
9 |
10 | struct BucketInfo {
11 | int count;
12 | Bounds3 bounds;
13 | BucketInfo()
14 | : count(0)
15 | , bounds() {
16 | }
17 | };
18 |
19 | struct ComparePoint {
20 | int dim;
21 | explicit ComparePoint(int d)
22 | : dim(d) {
23 | }
24 | bool operator()(const BVHPrimitiveInfo &a, const BVHPrimitiveInfo &b) const {
25 | return a.centroid[dim] < b.centroid[dim];
26 | }
27 | };
28 |
29 | struct CompareToBucket {
30 | int splitBucket, nBuckets, dim;
31 | const Bounds3 ¢roidBounds;
32 |
33 | CompareToBucket(int split, int num, int d, const Bounds3 &b)
34 | : splitBucket(split)
35 | , nBuckets(num)
36 | , dim(d)
37 | , centroidBounds(b) {
38 | }
39 |
40 | bool operator()(const BVHPrimitiveInfo &p) const {
41 | const double cmin = centroidBounds.posMin()[dim];
42 | const double cmax = centroidBounds.posMax()[dim];
43 | const double inv = (1.0) / (std::abs(cmax - cmin) + 1.0e-12);
44 | const double diff = std::abs(p.centroid[dim] - cmin);
45 | int b = static_cast(nBuckets * diff * inv);
46 | if (b >= nBuckets) {
47 | b = nBuckets - 1;
48 | }
49 | return b <= splitBucket;
50 | }
51 | };
52 |
53 | BVH::BVH(const Mesh &mesh)
54 | : BVH{ mesh.getVertices(), mesh.getVertexIndices() } {
55 | }
56 |
57 | BVH::BVH(const std::vector &vertices, const std::vector &indices) {
58 | construct(vertices, indices);
59 | }
60 |
61 | void BVH::construct(const std::vector &vertices, const std::vector &indices) {
62 | tris.clear();
63 | std::vector buildData;
64 | for (size_t i = 0; i < indices.size(); i += 3) {
65 | const uint32_t i0 = indices[i + 0];
66 | const uint32_t i1 = indices[i + 1];
67 | const uint32_t i2 = indices[i + 2];
68 | const size_t idx = tris.size();
69 | tris.emplace_back(vertices[i0], vertices[i1], vertices[i2]);
70 | buildData.emplace_back(idx, tris[idx].bounds());
71 | }
72 |
73 | root = constructRec(buildData, 0, tris.size());
74 | }
75 |
76 | double BVH::distance(const Vec3 &p) const {
77 | const Vec3 pc = closestPoint(p);
78 | return length(pc - p);
79 | }
80 |
81 | Vec3 BVH::closestPoint(const Vec3 &query) const {
82 | std::stack stk;
83 | stk.push(root);
84 |
85 | double dist = 1.0e20;
86 | Vec3 ret;
87 | while (!stk.empty()) {
88 | BVHNode *node = stk.top();
89 | stk.pop();
90 |
91 | if (node->isLeaf()) {
92 | const Vec3 pc = tris[node->primIdx].closestPoint(query);
93 | const double dc = length(query - pc);
94 | if (dc < dist) {
95 | dist = dc;
96 | ret = pc;
97 | }
98 | } else {
99 | // distance to furthest corner
100 | const bool isInside = node->bounds.inside(query);
101 | const double dc = node->bounds.distance(query);
102 | if (isInside || dc <= dist) {
103 | if (node->left) stk.push(node->left);
104 | if (node->right) stk.push(node->right);
105 | }
106 | }
107 | }
108 | return ret;
109 | }
110 |
111 | BVHNode *BVH::constructRec(std::vector &buildData, int start, int end) {
112 | if (start == end) return nullptr;
113 |
114 | BVHNode *node = new BVHNode();
115 | nodes.emplace_back(node);
116 |
117 | Bounds3 bounds;
118 | for (int i = start; i < end; i++) {
119 | bounds = Bounds3::merge(bounds, buildData[i].bounds);
120 | }
121 |
122 | int nprims = end - start;
123 | if (nprims == 1) {
124 | // Leaf node
125 | node->initLeaf(bounds, buildData[start].primIdx);
126 | } else {
127 | // Fork node
128 | Bounds3 centroidBounds;
129 | for (int i = start; i < end; i++) {
130 | centroidBounds.merge(buildData[i].centroid);
131 | }
132 |
133 | int splitAxis = centroidBounds.maxExtent();
134 | int mid = (start + end) / 2;
135 | if (nprims <= 8) {
136 | std::nth_element(buildData.begin() + start, buildData.begin() + mid, buildData.begin() + end,
137 | ComparePoint(splitAxis));
138 | } else {
139 | // Seperate with SAH (surface area heuristics)
140 | const int nBuckets = 16;
141 | BucketInfo buckets[nBuckets];
142 |
143 | const double cmin = centroidBounds.posMin()[splitAxis];
144 | const double cmax = centroidBounds.posMax()[splitAxis];
145 | const double idenom = 1.0 / (std::abs(cmax - cmin) + 1.0e-12);
146 | for (int i = start; i < end; i++) {
147 | const double numer = buildData[i].centroid[splitAxis] - centroidBounds.posMin()[splitAxis];
148 | int b = static_cast(nBuckets * std::abs(numer) * idenom);
149 | if (b == nBuckets) {
150 | b = nBuckets - 1;
151 | }
152 |
153 | buckets[b].count++;
154 | buckets[b].bounds = Bounds3::merge(buckets[b].bounds, buildData[i].bounds);
155 | }
156 |
157 | double bucketCost[nBuckets - 1] = { 0 };
158 | for (int i = 0; i < nBuckets - 1; i++) {
159 | Bounds3 b0, b1;
160 | int cnt0 = 0, cnt1 = 0;
161 | for (int j = 0; j <= i; j++) {
162 | b0 = Bounds3::merge(b0, buckets[j].bounds);
163 | cnt0 += buckets[j].count;
164 | }
165 | for (int j = i + 1; j < nBuckets; j++) {
166 | b1 = Bounds3::merge(b1, buckets[j].bounds);
167 | cnt1 += buckets[j].count;
168 | }
169 | bucketCost[i] += 0.125 + (cnt0 * b0.area() + cnt1 * b1.area()) / bounds.area();
170 | }
171 |
172 | double minCost = bucketCost[0];
173 | int minCostSplit = 0;
174 | for (int i = 1; i < nBuckets - 1; i++) {
175 | if (minCost > bucketCost[i]) {
176 | minCost = bucketCost[i];
177 | minCostSplit = i;
178 | }
179 | }
180 |
181 | if (minCost < nprims) {
182 | auto it = std::partition(buildData.begin() + start, buildData.begin() + end,
183 | CompareToBucket(minCostSplit, nBuckets, splitAxis, centroidBounds));
184 | mid = it - buildData.begin();
185 | }
186 | }
187 |
188 | BVHNode *left = constructRec(buildData, start, mid);
189 | BVHNode *right = constructRec(buildData, mid, end);
190 | node->initFork(bounds, left, right, splitAxis);
191 | }
192 | return node;
193 | }
194 |
195 | } // namespace tinymesh
196 |
--------------------------------------------------------------------------------
/src/tinymesh/core/bvh.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif // _MSC_VER
4 |
5 | #ifndef TINYMESH_BVH_H
6 | #define TINYMESH_BVH_H
7 |
8 | #include
9 | #include
10 |
11 | #include "api.h"
12 | #include "vec.h"
13 | #include "bounds.h"
14 | #include "triangle.h"
15 |
16 | namespace tinymesh {
17 |
18 | struct BVHPrimitiveInfo {
19 | BVHPrimitiveInfo() {
20 | }
21 | BVHPrimitiveInfo(int pid, const Bounds3 &b)
22 | : primIdx(pid)
23 | , centroid()
24 | , bounds(b) {
25 | centroid = (b.posMax() + b.posMin()) * 0.5;
26 | }
27 |
28 | int primIdx;
29 | Vec3 centroid;
30 | Bounds3 bounds;
31 | };
32 |
33 | struct BVHNode {
34 | void initLeaf(const Bounds3 &b, int pid) {
35 | this->bounds = b;
36 | this->splitAxis = 0;
37 | this->primIdx = pid;
38 | }
39 |
40 | void initFork(const Bounds3 &b, BVHNode *l, BVHNode *r, int axis) {
41 | this->bounds = b;
42 | this->left = l;
43 | this->right = r;
44 | this->splitAxis = axis;
45 | this->primIdx = -1;
46 | }
47 |
48 | bool isLeaf() const {
49 | return primIdx >= 0;
50 | }
51 |
52 | Bounds3 bounds;
53 | BVHNode *left = nullptr;
54 | BVHNode *right = nullptr;
55 | int splitAxis;
56 | int primIdx;
57 | };
58 |
59 | class TINYMESH_API BVH {
60 | public:
61 | BVH() = default;
62 | BVH(const Mesh &mesh);
63 | BVH(const std::vector &vertices, const std::vector &indices);
64 | void construct(const std::vector &vertices, const std::vector &indices);
65 |
66 | double distance(const Vec3 &p) const;
67 | Vec3 closestPoint(const Vec3 &p) const;
68 |
69 | private:
70 | BVHNode *constructRec(std::vector &buildData, int start, int end);
71 |
72 | BVHNode *root = nullptr;
73 | std::vector tris;
74 | std::vector> nodes;
75 | };
76 |
77 | } // namespace tinymesh
78 |
79 | #endif // TINYMESH_BVH_H
80 |
--------------------------------------------------------------------------------
/src/tinymesh/core/debug.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_DEBUG_H
6 | #define TINYMESH_DEBUG_H
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #if defined(TINYMESH_PYTHON_MODULE)
14 | #include
15 | namespace py = pybind11;
16 | #endif // TINYMESH_PYTHON_MODULE
17 |
18 | // -----------------------------------------------------------------------------
19 | // ANSI color
20 | // -----------------------------------------------------------------------------
21 | #define FG_RED "\033[31m"
22 | #define FG_GRN "\033[32m"
23 | #define FG_YEL "\033[33m"
24 | #define FG_BLU "\033[34m"
25 | #define FG_NC "\033[0m"
26 |
27 | // -----------------------------------------------------------------------------
28 | // Debug output stream
29 | // -----------------------------------------------------------------------------
30 | class PrintStream {
31 | public:
32 | PrintStream() {
33 | }
34 |
35 | virtual ~PrintStream() {
36 | flush();
37 | }
38 |
39 | template
40 | PrintStream &operator<<(const Type &s) {
41 | ss << s;
42 | if (ss.str().size() >= bufSize) {
43 | flush();
44 | }
45 | return *this;
46 | }
47 |
48 | PrintStream &operator<<(std::ostream &(*f)(std::ostream &)) {
49 | ss << f;
50 | flush();
51 | return *this;
52 | }
53 |
54 | void flush() {
55 | #if !defined(TINYMESH_PYTHON_MODULE)
56 | std::cout << ss.str();
57 | std::flush(std::cout);
58 | #else
59 | py::print(ss.str(), py::arg("end") = "", py::arg("flush") = true);
60 | #endif
61 | ss.str("");
62 | }
63 |
64 | private:
65 | std::ostringstream ss;
66 | static const int bufSize = 128;
67 | };
68 |
69 | // -----------------------------------------------------------------------------
70 | // Message handlers
71 | // -----------------------------------------------------------------------------
72 |
73 | template
74 | std::string STR_FMT(const char *format, Args... args) {
75 | #ifdef __clang__
76 | #pragma clang diagnostic push
77 | #pragma clang diagnostic ignored "-Wformat"
78 | #endif // __clang__
79 | int size_s = std::snprintf(nullptr, 0, format, args...) + 1;
80 | #ifdef __clang__
81 | #pragma clang diagnostic pop
82 | #endif // __clang__
83 | if (size_s <= 0) {
84 | throw std::runtime_error("Error during formatting.");
85 | }
86 | auto size = static_cast(size_s);
87 | auto buf = std::make_unique(size);
88 | #ifdef __clang__
89 | #pragma clang diagnostic push
90 | #pragma clang diagnostic ignored "-Wformat"
91 | #endif // __clang__
92 | std::snprintf(buf.get(), size, format, args...);
93 | #ifdef __clang__
94 | #pragma clang diagnostic pop
95 | #endif // __clang__
96 | return std::string(buf.get(), buf.get() + size - 1);
97 | }
98 |
99 | template
100 | void Print(const char *format, Args... args) {
101 | PrintStream() << STR_FMT(format, args...);
102 | }
103 |
104 | template
105 | void Info(const char *format, Args... args) {
106 | PrintStream() << FG_BLU << "[INFO] " << FG_NC << STR_FMT(format, args...) << std::endl;
107 | }
108 |
109 | template
110 | void Debug(const char *format, Args... args) {
111 | PrintStream() << FG_GRN << "[DEBUG] " << FG_NC << STR_FMT(format, args...) << std::endl;
112 | }
113 |
114 | template
115 | void Warn(const char *format, Args... args) {
116 | PrintStream() << FG_YEL << "[WARNING] " << FG_NC << STR_FMT(format, args...) << std::endl;
117 | }
118 |
119 | #if !defined(TINYMESH_PYTHON_MODULE)
120 | template
121 | void Error(const char *format, Args... args) {
122 | PrintStream() << FG_RED << "[ERROR] " << FG_NC << STR_FMT(format, args...) << std::endl;
123 | std::terminate();
124 | }
125 | #else // TINYMESH_PYTHON_MODULE
126 | template
127 | void Error(const char *format, Args... args) {
128 | throw std::runtime_error(STR_FMT(format, args...));
129 | }
130 | #endif // TINYMESH_PYTHON_MODULE
131 |
132 | // -----------------------------------------------------------------------------
133 | // Assertion with message
134 | // -----------------------------------------------------------------------------
135 |
136 | #ifndef __FUNCTION_NAME__
137 | #if defined(_WIN32) || defined(__WIN32__)
138 | #define __FUNCTION_NAME__ __FUNCTION__
139 | #else
140 | #define __FUNCTION_NAME__ __func__
141 | #endif
142 | #endif
143 |
144 | inline std::string ASRT_LABEL(const char *predicate, const char *file, int line, const char *funcname) {
145 | return STR_FMT("Assertion \"%s\" failed in %s L-%d (%s)", predicate, file, line, funcname);
146 | }
147 |
148 | #if defined(TINYMESH_PYTHON_MODULE)
149 | #define Assertion(PREDICATE, ...) \
150 | do { \
151 | if (!(PREDICATE)) { \
152 | throw std::runtime_error(ASRT_LABEL(#PREDICATE, __FILE__, __LINE__, __FUNCTION_NAME__)); \
153 | } \
154 | } while (false)
155 | #elif !defined(TINYMESH_DISABLE_ASSERT)
156 | #define Assertion(PREDICATE, ...) \
157 | do { \
158 | if (!(PREDICATE)) { \
159 | std::cerr << ASRT_LABEL(#PREDICATE, __FILE__, __LINE__, __FUNCTION_NAME__) << std::endl; \
160 | std::abort(); \
161 | } \
162 | } while (false)
163 | #else // TINYMESH_DISABLE_ASSERT
164 | #define Assertion(PREDICATE, ...) \
165 | do { \
166 | } while (false)
167 | #endif // TINYMESH_DISABLE_ASSERT
168 |
169 | #endif // TINYMESH_DEBUG_H
170 |
--------------------------------------------------------------------------------
/src/tinymesh/core/eigen.cpp:
--------------------------------------------------------------------------------
1 | #include "eigen.h"
2 |
3 | #include
4 |
5 | void eigenSVD(const Eigen::MatrixXd &A, Eigen::MatrixXd &U, Eigen::VectorXd &sigma, Eigen::MatrixXd &V) {
6 | Eigen::JacobiSVD svd(A, Eigen::ComputeFullU | Eigen::ComputeFullV);
7 | U = svd.matrixU();
8 | sigma = svd.singularValues();
9 | V = svd.matrixV();
10 | }
11 |
--------------------------------------------------------------------------------
/src/tinymesh/core/eigen.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_EIGEN_H
6 | #define TINYMESH_EIGEN_H
7 |
8 | #include
9 |
10 | using FloatType = double;
11 | using IndexType = int64_t;
12 |
13 | #include
14 | using EigenVector2 = Eigen::Matrix;
15 | using EigenVector3 = Eigen::Matrix;
16 | using EigenVector4 = Eigen::Matrix;
17 | using EigenVector = Eigen::Matrix;
18 | using EigenMatrix2 = Eigen::Matrix;
19 | using EigenMatrix3 = Eigen::Matrix;
20 | using EigenMatrix4 = Eigen::Matrix;
21 | using EigenMatrix = Eigen::Matrix;
22 |
23 | //! SVD using Eigen
24 | void eigenSVD(const EigenMatrix &A, EigenMatrix &U, EigenVector &Sigma, EigenMatrix &V);
25 |
26 | // #ifdef EIGEN_ENABLE_SPARSE
27 | #include
28 | using EigenTriplet = Eigen::Triplet;
29 | using EigenSparseVector = Eigen::SparseVector;
30 | using EigenSparseMatrix = Eigen::SparseMatrix;
31 | // #endif // EIGEN_ENABLE_SPARSE
32 |
33 | #endif // TINYMESH_EIGEN_H
34 |
--------------------------------------------------------------------------------
/src/tinymesh/core/external.cpp:
--------------------------------------------------------------------------------
1 | #define TINYOBJLOADER_IMPLEMENTATION
2 | #include "tiny_obj_loader.h"
3 | #define TINYPLY_IMPLEMENTATION
4 | #include "tinyply.h"
5 |
--------------------------------------------------------------------------------
/src/tinymesh/core/face.cpp:
--------------------------------------------------------------------------------
1 | #define TINYMESH_API_EXPORT
2 | #include "face.h"
3 |
4 | #include
5 |
6 | #include "debug.h"
7 | #include "vertex.h"
8 | #include "halfedge.h"
9 | #include "triangle.h"
10 |
11 | namespace tinymesh {
12 |
13 | Face::Face() {
14 | }
15 |
16 | bool Face::operator==(const Face &other) const {
17 | bool ret = true;
18 | ret &= (halfedge_ == other.halfedge_);
19 | ret &= (index_ == other.index_);
20 | return ret;
21 | }
22 |
23 | Triangle Face::toTriangle() const {
24 | std::vector vs;
25 | for (auto it = v_begin(); it != v_end(); ++it) {
26 | vs.push_back(it->pos());
27 | }
28 |
29 | Assertion(vs.size() == 3, "Non-triangle face cannot be converted to Triangle!");
30 |
31 | return { vs[0], vs[1], vs[2] };
32 | }
33 |
34 | Vec3 Face::normal() const {
35 | std::vector vs;
36 | for (auto it = v_begin(); it != v_end(); ++it) {
37 | vs.push_back(it->pos());
38 | }
39 |
40 | const int N = static_cast(vs.size());
41 | Vec3 norm(0.0);
42 | for (int i = 0; i < N; i++) {
43 | const int prev = (i - 1 + N) % N;
44 | const int next = (i + 1) % N;
45 | const Vec3 &p0 = vs[prev];
46 | const Vec3 &p1 = vs[i];
47 | const Vec3 &p2 = vs[next];
48 | norm += cross(p2 - p1, p0 - p1);
49 | }
50 | return normalize(norm);
51 | }
52 |
53 | double Face::area() const {
54 | std::vector vs;
55 | for (auto it = v_begin(); it != v_end(); ++it) {
56 | vs.push_back(it->pos());
57 | }
58 |
59 | const int N = static_cast(vs.size());
60 | double area = 0.0;
61 | for (int i = 1; i < N - 1; i++) {
62 | const Vec3 &p0 = vs[i];
63 | const Vec3 &p1 = vs[i - 1];
64 | const Vec3 &p2 = vs[i + 1];
65 | area += 0.5 * length(cross(p1 - p0, p2 - p0));
66 | }
67 | return area;
68 | }
69 |
70 | int Face::numCorners() const {
71 | int count = 0;
72 | for (auto it = v_begin(); it != v_end(); ++it) {
73 | count++;
74 | }
75 | return count;
76 | }
77 |
78 | bool Face::isHole() const {
79 | // Face is hole if all the vertices are at the boundary.
80 | for (auto it = this->he_begin(); it != this->he_end(); ++it) {
81 | if (!it->isBoundary()) {
82 | return false;
83 | }
84 | }
85 | return true;
86 | }
87 |
88 | bool Face::isBoundary() const {
89 | for (auto it = this->he_begin(); it != this->he_end(); ++it) {
90 | if (it->isBoundary()) {
91 | return true;
92 | }
93 | }
94 | return false;
95 | }
96 |
97 | bool Face::isLocked() const {
98 | for (auto it = this->v_begin(); it != this->v_end(); ++it) {
99 | if (it->isLocked()) {
100 | return true;
101 | }
102 | }
103 | return false;
104 | }
105 |
106 | Face::VertexIterator Face::v_begin() {
107 | return Face::VertexIterator(halfedge_);
108 | }
109 |
110 | Face::VertexIterator Face::v_end() {
111 | return Face::VertexIterator(nullptr);
112 | }
113 |
114 | Face::ConstVertexIterator Face::v_begin() const {
115 | return Face::ConstVertexIterator(halfedge_);
116 | }
117 |
118 | Face::ConstVertexIterator Face::v_end() const {
119 | return Face::ConstVertexIterator(nullptr);
120 | }
121 |
122 | Face::HalfedgeIterator Face::he_begin() {
123 | return Face::HalfedgeIterator(halfedge_);
124 | }
125 |
126 | Face::HalfedgeIterator Face::he_end() {
127 | return Face::HalfedgeIterator(nullptr);
128 | }
129 |
130 | Face::ConstHalfedgeIterator Face::he_begin() const {
131 | return Face::ConstHalfedgeIterator(halfedge_);
132 | }
133 |
134 | Face::ConstHalfedgeIterator Face::he_end() const {
135 | return Face::ConstHalfedgeIterator(nullptr);
136 | }
137 |
138 | Face::FaceIterator Face::f_begin() {
139 | return Face::FaceIterator(halfedge_);
140 | }
141 |
142 | Face::FaceIterator Face::f_end() {
143 | return Face::FaceIterator(nullptr);
144 | }
145 |
146 | Face::ConstFaceIterator Face::f_begin() const {
147 | return Face::ConstFaceIterator(halfedge_);
148 | }
149 |
150 | Face::ConstFaceIterator Face::f_end() const {
151 | return Face::ConstFaceIterator(nullptr);
152 | }
153 |
154 | // ----------
155 | // VertexIterator
156 | // ----------
157 |
158 | Face::VertexIterator::VertexIterator(Halfedge *he)
159 | : halfedge_{ he }
160 | , iter_{ he } {
161 | }
162 |
163 | bool Face::VertexIterator::operator!=(const Face::VertexIterator &it) const {
164 | return iter_ != it.iter_;
165 | }
166 |
167 | Vertex &Face::VertexIterator::operator*() {
168 | return *iter_->src();
169 | }
170 |
171 | Vertex *Face::VertexIterator::ptr() const {
172 | return iter_->src();
173 | }
174 |
175 | Vertex *Face::VertexIterator::operator->() const {
176 | return iter_->src();
177 | }
178 |
179 | Face::VertexIterator &Face::VertexIterator::operator++() {
180 | iter_ = iter_->next();
181 | if (iter_ == halfedge_) {
182 | iter_ = nullptr;
183 | }
184 | return *this;
185 | }
186 |
187 | Face::VertexIterator Face::VertexIterator::operator++(int) {
188 | Halfedge *tmp = iter_;
189 | iter_ = iter_->next();
190 | if (iter_ == halfedge_) {
191 | iter_ = nullptr;
192 | }
193 | return Face::VertexIterator(tmp);
194 | }
195 |
196 | // ----------
197 | // ConstVertexIterator
198 | // ----------
199 |
200 | Face::ConstVertexIterator::ConstVertexIterator(Halfedge *he)
201 | : halfedge_{ he }
202 | , iter_{ he } {
203 | }
204 |
205 | bool Face::ConstVertexIterator::operator!=(const Face::ConstVertexIterator &it) const {
206 | return iter_ != it.iter_;
207 | }
208 |
209 | const Vertex &Face::ConstVertexIterator::operator*() const {
210 | return *iter_->src();
211 | }
212 |
213 | const Vertex *Face::ConstVertexIterator::ptr() const {
214 | return iter_->src();
215 | }
216 |
217 | const Vertex *Face::ConstVertexIterator::operator->() const {
218 | return iter_->src();
219 | }
220 |
221 | Face::ConstVertexIterator &Face::ConstVertexIterator::operator++() {
222 | iter_ = iter_->next();
223 | if (iter_ == halfedge_) {
224 | iter_ = nullptr;
225 | }
226 | return *this;
227 | }
228 |
229 | Face::ConstVertexIterator Face::ConstVertexIterator::operator++(int) {
230 | Halfedge *tmp = iter_;
231 | iter_ = iter_->next();
232 | if (iter_ == halfedge_) {
233 | iter_ = nullptr;
234 | }
235 | return Face::ConstVertexIterator(tmp);
236 | }
237 |
238 | // ----------
239 | // HalfedgeIterator
240 | // ----------
241 |
242 | Face::HalfedgeIterator::HalfedgeIterator(Halfedge *he)
243 | : halfedge_{ he }
244 | , iter_{ he } {
245 | }
246 |
247 | bool Face::HalfedgeIterator::operator!=(const Face::HalfedgeIterator &it) const {
248 | return iter_ != it.iter_;
249 | }
250 |
251 | Halfedge &Face::HalfedgeIterator::operator*() {
252 | return *iter_;
253 | }
254 |
255 | Halfedge *Face::HalfedgeIterator::ptr() const {
256 | return iter_;
257 | }
258 |
259 | Halfedge *Face::HalfedgeIterator::operator->() const {
260 | return iter_;
261 | }
262 |
263 | Face::HalfedgeIterator &Face::HalfedgeIterator::operator++() {
264 | iter_ = iter_->next();
265 | if (iter_ == halfedge_) {
266 | iter_ = nullptr;
267 | }
268 | return *this;
269 | }
270 |
271 | Face::HalfedgeIterator Face::HalfedgeIterator::operator++(int) {
272 | Halfedge *tmp = iter_;
273 | iter_ = iter_->next();
274 | if (iter_ == halfedge_) {
275 | iter_ = nullptr;
276 | }
277 | return Face::HalfedgeIterator(tmp);
278 | }
279 |
280 | // ----------
281 | // ConstHalfedgeIterator
282 | // ----------
283 |
284 | Face::ConstHalfedgeIterator::ConstHalfedgeIterator(Halfedge *he)
285 | : halfedge_{ he }
286 | , iter_{ he } {
287 | }
288 |
289 | bool Face::ConstHalfedgeIterator::operator!=(const Face::ConstHalfedgeIterator &it) const {
290 | return iter_ != it.iter_;
291 | }
292 |
293 | const Halfedge &Face::ConstHalfedgeIterator::operator*() const {
294 | return *iter_;
295 | }
296 |
297 | const Halfedge *Face::ConstHalfedgeIterator::ptr() const {
298 | return iter_;
299 | }
300 |
301 | const Halfedge *Face::ConstHalfedgeIterator::operator->() const {
302 | return iter_;
303 | }
304 |
305 | Face::ConstHalfedgeIterator &Face::ConstHalfedgeIterator::operator++() {
306 | iter_ = iter_->next();
307 | if (iter_ == halfedge_) {
308 | iter_ = nullptr;
309 | }
310 | return *this;
311 | }
312 |
313 | Face::ConstHalfedgeIterator Face::ConstHalfedgeIterator::operator++(int) {
314 | Halfedge *tmp = iter_;
315 | iter_ = iter_->next();
316 | if (iter_ == halfedge_) {
317 | iter_ = nullptr;
318 | }
319 | return Face::ConstHalfedgeIterator(tmp);
320 | }
321 |
322 | // ----------
323 | // FaceIterator
324 | // ----------
325 |
326 | Face::FaceIterator::FaceIterator(Halfedge *he)
327 | : halfedge_{ he }
328 | , iter_{ he } {
329 | }
330 |
331 | bool Face::FaceIterator::operator!=(const Face::FaceIterator &it) const {
332 | return iter_ != it.iter_;
333 | }
334 |
335 | Face &Face::FaceIterator::operator*() {
336 | return *iter_->rev()->face();
337 | }
338 |
339 | Face *Face::FaceIterator::ptr() const {
340 | return iter_->rev()->face();
341 | }
342 |
343 | Face *Face::FaceIterator::operator->() const {
344 | return iter_->rev()->face();
345 | }
346 |
347 | Face::FaceIterator &Face::FaceIterator::operator++() {
348 | iter_ = iter_->next();
349 | if (iter_ == halfedge_) {
350 | iter_ = nullptr;
351 | }
352 | return *this;
353 | }
354 |
355 | Face::FaceIterator Face::FaceIterator::operator++(int) {
356 | Halfedge *tmp = iter_;
357 | iter_ = iter_->next();
358 | if (iter_ == halfedge_) {
359 | iter_ = nullptr;
360 | }
361 | return Face::FaceIterator(tmp);
362 | }
363 |
364 | // ----------
365 | // ConstFaceIterator
366 | // ----------
367 |
368 | Face::ConstFaceIterator::ConstFaceIterator(Halfedge *he)
369 | : halfedge_{ he }
370 | , iter_{ he } {
371 | }
372 |
373 | bool Face::ConstFaceIterator::operator!=(const Face::ConstFaceIterator &it) const {
374 | return iter_ != it.iter_;
375 | }
376 |
377 | const Face &Face::ConstFaceIterator::operator*() const {
378 | return *iter_->rev()->face();
379 | }
380 |
381 | const Face *Face::ConstFaceIterator::ptr() const {
382 | return iter_->rev()->face();
383 | }
384 |
385 | const Face *Face::ConstFaceIterator::operator->() const {
386 | return iter_->rev()->face();
387 | }
388 |
389 | Face::ConstFaceIterator &Face::ConstFaceIterator::operator++() {
390 | iter_ = iter_->next();
391 | if (iter_ == halfedge_) {
392 | iter_ = nullptr;
393 | }
394 | return *this;
395 | }
396 |
397 | Face::ConstFaceIterator Face::ConstFaceIterator::operator++(int) {
398 | Halfedge *tmp = iter_;
399 | iter_ = iter_->next();
400 | if (iter_ == halfedge_) {
401 | iter_ = nullptr;
402 | }
403 | return Face::ConstFaceIterator(tmp);
404 | }
405 |
406 | } // namespace tinymesh
407 |
--------------------------------------------------------------------------------
/src/tinymesh/core/face.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_FACE_H
6 | #define TINYMESH_FACE_H
7 |
8 | #include
9 |
10 | #include "core/api.h"
11 | #include "core/vec.h"
12 |
13 | namespace tinymesh {
14 |
15 | class Triangle;
16 |
17 | class TINYMESH_API Face {
18 | public:
19 | // Forward declaration
20 | class VertexIterator;
21 | class ConstVertexIterator;
22 | class HalfedgeIterator;
23 | class ConstHalfedgeIterator;
24 | class FaceIterator;
25 | class ConstFaceIterator;
26 |
27 | public:
28 | Face();
29 | Face(const Face &face) = default;
30 | Face(Face &&face) noexcept = default;
31 | virtual ~Face() = default;
32 |
33 | Face &operator=(const Face &face) = default;
34 | Face &operator=(Face &&face) noexcept = default;
35 |
36 | bool operator==(const Face &other) const;
37 |
38 | Triangle toTriangle() const;
39 |
40 | VertexIterator v_begin();
41 | VertexIterator v_end();
42 | ConstVertexIterator v_begin() const;
43 | ConstVertexIterator v_end() const;
44 |
45 | HalfedgeIterator he_begin();
46 | HalfedgeIterator he_end();
47 | ConstHalfedgeIterator he_begin() const;
48 | ConstHalfedgeIterator he_end() const;
49 |
50 | FaceIterator f_begin();
51 | FaceIterator f_end();
52 | ConstFaceIterator f_begin() const;
53 | ConstFaceIterator f_end() const;
54 |
55 | int index() const {
56 | return index_;
57 | }
58 |
59 | Vec3 normal() const;
60 | double area() const;
61 | int numCorners() const;
62 |
63 | bool isHole() const;
64 | bool isBoundary() const;
65 | bool isLocked() const;
66 |
67 | private:
68 | Halfedge *halfedge_ = nullptr;
69 | int index_ = -1;
70 |
71 | friend class Mesh;
72 | };
73 |
74 | class TINYMESH_API Face::VertexIterator {
75 | public:
76 | explicit VertexIterator(Halfedge *he);
77 | bool operator!=(const VertexIterator &it) const;
78 | Vertex &operator*();
79 | Vertex *operator->() const;
80 | Vertex *ptr() const;
81 | VertexIterator &operator++();
82 | VertexIterator operator++(int);
83 |
84 | private:
85 | Halfedge *halfedge_, *iter_;
86 | };
87 |
88 | class TINYMESH_API Face::ConstVertexIterator {
89 | public:
90 | explicit ConstVertexIterator(Halfedge *he);
91 | bool operator!=(const ConstVertexIterator &it) const;
92 | const Vertex &operator*() const;
93 | const Vertex *operator->() const;
94 | const Vertex *ptr() const;
95 | ConstVertexIterator &operator++();
96 | ConstVertexIterator operator++(int);
97 |
98 | private:
99 | Halfedge *halfedge_, *iter_;
100 | };
101 |
102 | class TINYMESH_API Face::HalfedgeIterator {
103 | public:
104 | explicit HalfedgeIterator(Halfedge *he);
105 | bool operator!=(const HalfedgeIterator &it) const;
106 | Halfedge &operator*();
107 | Halfedge *operator->() const;
108 | Halfedge *ptr() const;
109 | HalfedgeIterator &operator++();
110 | HalfedgeIterator operator++(int);
111 |
112 | private:
113 | Halfedge *halfedge_, *iter_;
114 | };
115 |
116 | class TINYMESH_API Face::ConstHalfedgeIterator {
117 | public:
118 | explicit ConstHalfedgeIterator(Halfedge *he);
119 | bool operator!=(const ConstHalfedgeIterator &it) const;
120 | const Halfedge &operator*() const;
121 | const Halfedge *operator->() const;
122 | const Halfedge *ptr() const;
123 | ConstHalfedgeIterator &operator++();
124 | ConstHalfedgeIterator operator++(int);
125 |
126 | private:
127 | Halfedge *halfedge_, *iter_;
128 | };
129 |
130 | class TINYMESH_API Face::FaceIterator {
131 | public:
132 | explicit FaceIterator(Halfedge *he);
133 | bool operator!=(const FaceIterator &it) const;
134 | Face &operator*();
135 | Face *operator->() const;
136 | Face *ptr() const;
137 | FaceIterator &operator++();
138 | FaceIterator operator++(int);
139 |
140 | private:
141 | Halfedge *halfedge_, *iter_;
142 | };
143 |
144 | class TINYMESH_API Face::ConstFaceIterator {
145 | public:
146 | explicit ConstFaceIterator(Halfedge *he);
147 | bool operator!=(const ConstFaceIterator &it) const;
148 | const Face &operator*() const;
149 | const Face *operator->() const;
150 | const Face *ptr() const;
151 | ConstFaceIterator &operator++();
152 | ConstFaceIterator operator++(int);
153 |
154 | private:
155 | Halfedge *halfedge_, *iter_;
156 | };
157 |
158 | } // namespace tinymesh
159 |
160 | #endif // TINYMESH_FACE_H
161 |
--------------------------------------------------------------------------------
/src/tinymesh/core/filesystem.h:
--------------------------------------------------------------------------------
1 | // We haven't checked which filesystem to include yet
2 | #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
3 |
4 | // Check for feature test macro for
5 | #if defined(__cpp_lib_filesystem)
6 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
7 |
8 | // Check for feature test macro for
9 | #elif defined(__cpp_lib_experimental_filesystem)
10 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
11 |
12 | // We can't check if headers exist...
13 | // Let's assume experimental to be safe
14 | #elif !defined(__has_include)
15 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
16 |
17 | // Check if the header "" exists
18 | #elif __has_include()
19 |
20 | // If we're compiling on Visual Studio and are not compiling with C++17, we need to use experimental
21 | #ifdef _MSC_VER
22 |
23 | // Check and include header that defines "_HAS_CXX17"
24 | #if __has_include()
25 | #include
26 |
27 | // Check for enabled C++17 support
28 | #if defined(_HAS_CXX17) && _HAS_CXX17
29 | // We're using C++17, so let's use the normal version
30 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
31 | #endif
32 | #endif
33 |
34 | // If the marco isn't defined yet, that means any of the other VS specific checks failed, so we need to use experimental
35 | #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
36 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
37 | #endif
38 |
39 | // Not on Visual Studio. Let's use the normal version
40 | #else // #ifdef _MSC_VER
41 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 0
42 | #endif
43 |
44 | // Check if the header "" exists
45 | #elif __has_include()
46 | #define INCLUDE_STD_FILESYSTEM_EXPERIMENTAL 1
47 |
48 | // Fail if neither header is available with a nice error message
49 | #else
50 | #error Could not find system header "" or ""
51 | #endif
52 |
53 | // We priously determined that we need the exprimental version
54 | #if INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
55 | // Include it
56 | #include
57 |
58 | // We need the alias from std::experimental::filesystem to std::filesystem
59 | namespace std {
60 | namespace filesystem = experimental::filesystem;
61 | }
62 |
63 | // We have a decent compiler and can use the normal version
64 | #else
65 | // Include it
66 | #include
67 | #endif
68 |
69 | #endif // #ifndef INCLUDE_STD_FILESYSTEM_EXPERIMENTAL
--------------------------------------------------------------------------------
/src/tinymesh/core/halfedge.cpp:
--------------------------------------------------------------------------------
1 | #define TINYMESH_API_EXPORT
2 | #include "halfedge.h"
3 |
4 | #include "core/vertex.h"
5 | #include "core/face.h"
6 |
7 | namespace tinymesh {
8 |
9 | Halfedge::Halfedge() {
10 | }
11 |
12 | bool Halfedge::operator==(const Halfedge &other) const {
13 | bool ret = true;
14 | ret &= (src_ == other.src_);
15 | ret &= (next_ == other.next_);
16 | ret &= (rev_ == other.rev_);
17 | ret &= (face_ == other.face_);
18 | ret &= (index_ == other.index_);
19 | return ret;
20 | }
21 |
22 | double Halfedge::length() const {
23 | return ::length(src()->pos() - dst()->pos());
24 | }
25 |
26 | double Halfedge::cotWeight() const {
27 | Vertex *v0 = this->src();
28 | Vertex *v1 = this->dst();
29 | Vertex *v2 = this->next()->dst();
30 | Vertex *v3 = this->rev()->next()->dst();
31 | const Vec3 &p0 = v0->pos();
32 | const Vec3 &p1 = v1->pos();
33 | const Vec3 &p2 = v2->pos();
34 | const Vec3 &p3 = v3->pos();
35 | const double sin_a = ::length(cross(p0 - p2, p1 - p2));
36 | const double cos_a = dot(p0 - p2, p1 - p2);
37 | const double cot_a = cos_a / std::max(sin_a, 1.0e-6);
38 | const double sin_b = ::length(cross(p0 - p3, p1 - p3));
39 | const double cos_b = dot(p0 - p3, p1 - p3);
40 | const double cot_b = cos_b / std::max(sin_b, 1.0e-6);
41 | return 0.5 * (cot_a + cot_b);
42 | }
43 |
44 | bool Halfedge::isLocked() const {
45 | return this->src()->isLocked() && this->dst()->isLocked();
46 | }
47 |
48 | } // namespace tinymesh
49 |
--------------------------------------------------------------------------------
/src/tinymesh/core/halfedge.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_HALFEDGE_H
6 | #define TINYMESH_HALFEDGE_H
7 |
8 | #include
9 |
10 | #include "core/api.h"
11 |
12 | namespace tinymesh {
13 |
14 | class TINYMESH_API Halfedge {
15 | public:
16 | Halfedge();
17 | Halfedge(const Halfedge &he) = default;
18 | Halfedge(Halfedge &&he) noexcept = default;
19 | virtual ~Halfedge() = default;
20 |
21 | Halfedge &operator=(const Halfedge &he) = default;
22 | Halfedge &operator=(Halfedge &&he) noexcept = default;
23 |
24 | bool operator==(const Halfedge &other) const;
25 |
26 | double length() const;
27 | double cotWeight() const;
28 |
29 | Vertex *src() const {
30 | return src_;
31 | }
32 | Vertex *dst() const {
33 | return next_->src_;
34 | }
35 | Halfedge *next() const {
36 | return next_;
37 | }
38 | Halfedge *prev() const {
39 | Halfedge *iter = next_;
40 | while (iter->next_ != this) {
41 | iter = iter->next_;
42 | }
43 | return iter;
44 | }
45 |
46 | Halfedge *rev() const {
47 | return rev_;
48 | }
49 | Face *face() const {
50 | return face_;
51 | }
52 | int index() const {
53 | return index_;
54 | }
55 | bool isLocked() const;
56 |
57 | bool isBoundary() const {
58 | return isBoundary_;
59 | }
60 |
61 | void setIsBoundary(bool flag) {
62 | isBoundary_ = flag;
63 | }
64 |
65 | private:
66 | Vertex *src_ = nullptr;
67 | Halfedge *next_ = nullptr;
68 | Halfedge *rev_ = nullptr;
69 | Face *face_ = nullptr;
70 | int index_ = -1;
71 | bool isBoundary_ = false;
72 |
73 | friend class Mesh;
74 | };
75 |
76 | } // namespace tinymesh
77 |
78 | #endif // TINYMESH_HALFEDGE_H
79 |
--------------------------------------------------------------------------------
/src/tinymesh/core/mesh.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_MESH_H
6 | #define TINYMESH_MESH_H
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "core/api.h"
16 | #include "core/vec.h"
17 | #include "core/utils.h"
18 | #include "core/debug.h"
19 |
20 | namespace tinymesh {
21 |
22 | /**
23 | * "Mesh" is a class for halfedge data structure for polygonal mesh.
24 | */
25 | class TINYMESH_API Mesh {
26 | public:
27 | // Public methods
28 | Mesh();
29 | explicit Mesh(const std::string &filename);
30 | Mesh(const std::vector &vertices, const std::vector &indices);
31 |
32 | void load(const std::string &filename);
33 | void save(const std::string &filename) const;
34 | void construct(const std::vector &vertices, const std::vector &indices);
35 | Mesh clone();
36 |
37 | std::vector getVertices() const;
38 | std::vector getVertexIndices() const;
39 |
40 | bool flipHE(Halfedge *he);
41 | bool splitHE(Halfedge *he);
42 | bool collapseHE(Halfedge *he);
43 | bool collapseFace(Face *f);
44 | void fillHoles();
45 |
46 | double getMeanEdgeLength() const;
47 | double getMeanDihedralAngle() const;
48 | double getMeanFaceArea() const;
49 |
50 | bool verify() const;
51 |
52 | Vertex *vertex(int index) const {
53 | Assertion(index >= 0 && index < (int)vertices_.size(), "Vertex index out of bounds!");
54 | return vertices_[index].get();
55 | }
56 |
57 | Halfedge *halfedge(int index) const {
58 | Assertion(index >= 0 && index < (int)halfedges_.size(), "Halfedge index out of bounds!");
59 | return halfedges_[index].get();
60 | }
61 |
62 | Face *face(int index) const {
63 | Assertion(index >= 0 && index < (int)faces_.size(), "Face index out of bounds!");
64 | return faces_[index].get();
65 | }
66 |
67 | size_t numVertices() const;
68 | size_t numEdges() const;
69 | size_t numHalfedges() const;
70 | size_t numFaces() const;
71 |
72 | private:
73 | // Private methods
74 | void loadOBJ(const std::string &filename);
75 | void loadPLY(const std::string &filename);
76 | void saveOBJ(const std::string &filename) const;
77 | void savePLY(const std::string &filename) const;
78 |
79 | void construct();
80 |
81 | void addVertex(Vertex *v);
82 | void addHalfedge(Halfedge *he);
83 | void addFace(Face *f);
84 | void removeVertex(Vertex *v);
85 | void removeHalfedge(Halfedge *he);
86 | void removeFace(Face *f);
87 |
88 | void triangulate(Face *f);
89 | bool verifyVertex(Vertex *v) const;
90 |
91 | // ----- Mesh completion methods (in completion.cpp) -----
92 |
93 | //! This method adds a new triangle using vertices specified with "boundary" and indices in the list.
94 | Face *addNewTriangle(const std::vector &boundary, const std::tuple &tri,
95 | std::unordered_map &pair2he,
96 | std::unordered_map &he2pair);
97 | void holeFillMinDihedral_(Face *face, double dihedralBound = Pi);
98 | void holeFillAdvancingFront_(Face *face);
99 |
100 | // Private parameters
101 | std::vector> vertices_;
102 | //std::vector> edges_;
103 | std::vector> halfedges_;
104 | std::vector> faces_;
105 | std::vector indices_;
106 |
107 | // Friend methods
108 | friend TINYMESH_API void holeFillMinDihedral(Mesh &mesh, Face *face, double dihedralBound);
109 | friend TINYMESH_API void holeFillAdvancingFront(Mesh &mesh, Face *face);
110 | };
111 |
112 | } // namespace tinymesh
113 |
114 | #endif // TINYMESH_MESH_H
115 |
--------------------------------------------------------------------------------
/src/tinymesh/core/openmp.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef COMMON_OPENMP_H
6 | #define COMMON_OPENMP_H
7 |
8 | #if defined(_OPENMP)
9 | #include
10 | #if defined(_MSC_VER)
11 | #define omp_pragma __pragma(omp parallel for)
12 | #define omp_critical __pragma(omp critical)
13 | #else
14 | #define omp_pragma _Pragma("omp parallel for")
15 | #define omp_critical _Pragma("omp critical")
16 | #define omp_atomic _Pragma("omp atomic")
17 | #endif
18 | #define omp_parallel_for omp_pragma for
19 | #define omp_lock_t omp_lock_t
20 | #define omp_init_lock(lock) omp_init_lock(lock)
21 | #define omp_destroy_lock(lock) omp_destroy_lock(lock)
22 | #define omp_lock(lock) omp_set_lock(lock)
23 | #define omp_unlock(lock) omp_unset_lock(lock)
24 | #else
25 | #define omp_set_num_threads(n)
26 | #define omp_get_thread_num() 0
27 | #define omp_get_max_threads() 1
28 | #define omp_get_num_threads() 1
29 | #define omp_parallel_for for
30 | #define omp_critical
31 | #define omp_lock_t int
32 | #define omp_init_lock(lock)
33 | #define omp_destroy_lock(lock)
34 | #define omp_lock(lock)
35 | #define omp_unlock(lock)
36 | #endif
37 |
38 | #endif // COMMON_OPENMP_H
39 |
--------------------------------------------------------------------------------
/src/tinymesh/core/progress.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef PROGRESS_H
6 | #define PROGRESS_H
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | #include "debug.h"
15 |
16 | class ProgressBar {
17 | public:
18 | ProgressBar() {
19 | }
20 |
21 | ProgressBar(int total) {
22 | m_step = 0;
23 | m_total = total;
24 | m_description = "";
25 | start = std::chrono::system_clock::now();
26 | }
27 |
28 | virtual ~ProgressBar() {
29 | }
30 |
31 | template
32 | void set_description(const char *format, Args... args) {
33 | m_description = STR_FMT(format, args...);
34 | }
35 |
36 | void step(int n = 1) {
37 | m_step += n;
38 | auto now = std::chrono::system_clock::now();
39 |
40 | const double percent = 100.0 * m_step / m_total;
41 | const int64_t elapsed = std::chrono::duration_cast(now - start).count();
42 | const double time_msec_per_step = (double)elapsed / (double)m_step;
43 | const double rest_time = time_msec_per_step * (m_total - m_step);
44 |
45 | const int n_min = (int)(elapsed / 1000.0) / 60;
46 | const int n_sec = (int)(elapsed / 1000.0) % 60;
47 | const int r_min = (int)(rest_time / 1000.0) / 60;
48 | const int r_sec = (int)(rest_time / 1000.0) % 60;
49 |
50 | std::string it_text = "";
51 | const int steps_per_sec = (int)(1000.0 / time_msec_per_step);
52 | if (steps_per_sec > 0) {
53 | std::ostringstream oss;
54 | if (steps_per_sec < 1000) {
55 | oss << steps_per_sec;
56 | } else {
57 | oss << "1000+";
58 | }
59 | oss << "it/s";
60 | it_text = oss.str();
61 | } else {
62 | std::ostringstream oss;
63 | oss << std::setprecision(2);
64 | oss << time_msec_per_step / 1000.0 << "s/it";
65 | it_text = oss.str();
66 | }
67 |
68 | const int tick = (int)(m_width * m_step / m_total);
69 | std::string pbar = std::string(tick, '=');
70 | if (tick != m_width) {
71 | pbar += ">";
72 | pbar += std::string(m_width - tick - 1, ' ');
73 | }
74 |
75 | if (m_step == m_total || m_step % std::max(1, (m_total / 1000)) == 0) {
76 | Print("\r");
77 | if (!m_description.empty()) {
78 | Print("%s ", m_description.c_str());
79 | }
80 | Print("[%3d%%]|%s| %d/%d [%02d:%02d<%02d:%02d, %s]", (int)percent, pbar.c_str(), m_step, m_total, n_min,
81 | n_sec, r_min, r_sec, it_text.c_str());
82 | }
83 |
84 | if (m_step == m_total) {
85 | Print("\n");
86 | }
87 | }
88 |
89 | void finish() {
90 | if (m_step != m_total) {
91 | step(m_total - m_step);
92 | }
93 | }
94 |
95 | private:
96 | const int m_width = 40;
97 | int m_step, m_total;
98 | std::string m_description = "";
99 | std::chrono::system_clock::time_point start;
100 | };
101 |
102 | #endif // PROGRESS_H
103 |
--------------------------------------------------------------------------------
/src/tinymesh/core/triangle.cpp:
--------------------------------------------------------------------------------
1 | #define TINYMESH_API_EXPORT
2 | #include "triangle.h"
3 |
4 | namespace tinymesh {
5 |
6 | double Triangle::distance(const Vec3 &p) const {
7 | return length(p - closestPoint(p));
8 | }
9 |
10 | Vec3 Triangle::closestPoint(const Vec3 &p) const {
11 | const Vec3 ab = p1 - p0;
12 | const Vec3 ac = p2 - p0;
13 | const Vec3 ap = p - p0;
14 |
15 | const double d1 = dot(ab, ap);
16 | const double d2 = dot(ac, ap);
17 | if (d1 <= 0.0 && d2 <= 0.0) return p0; //#1
18 |
19 | const Vec3 bp = p - p2;
20 | const double d3 = dot(ab, bp);
21 | const double d4 = dot(ac, bp);
22 | if (d3 >= 0.0 && d4 <= d3) return p1; //#2
23 |
24 | const Vec3 cp = p - p2;
25 | const double d5 = dot(ab, cp);
26 | const double d6 = dot(ac, cp);
27 | if (d6 >= 0.0 && d5 <= d6) return p2; //#3
28 |
29 | const double vc = d1 * d4 - d3 * d2;
30 | if (vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0) {
31 | const double v = d1 / (d1 - d3);
32 | return p0 + v * ab; //#4
33 | }
34 |
35 | const double vb = d5 * d2 - d1 * d6;
36 | if (vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0) {
37 | const double v = d2 / (d2 - d6);
38 | return p0 + v * ac; //#5
39 | }
40 |
41 | const double va = d3 * d6 - d5 * d4;
42 | if (va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0) {
43 | const double v = (d4 - d3) / ((d4 - d3) + (d5 - d6));
44 | return p1 + v * (p2 - p1); //#6
45 | }
46 |
47 | const double denom = 1.0 / (va + vb + vc);
48 | const double v = vb * denom;
49 | const double w = vc * denom;
50 | return p0 + v * ab + w * ac; //#0
51 | }
52 |
53 | Bounds3 Triangle::bounds() const {
54 | Bounds3 ret;
55 | ret.merge(p0);
56 | ret.merge(p1);
57 | ret.merge(p2);
58 | return ret;
59 | }
60 |
61 | } // namespace tinymesh
62 |
--------------------------------------------------------------------------------
/src/tinymesh/core/triangle.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_TRIANGLE_H
6 | #define TINYMESH_TRIANGLE_H
7 |
8 | #include "api.h"
9 | #include "vec.h"
10 | #include "bounds.h"
11 |
12 | namespace tinymesh {
13 |
14 | class TINYMESH_API Triangle {
15 | public:
16 | Triangle() = default;
17 | Triangle(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2)
18 | : p0(p0)
19 | , p1(p1)
20 | , p2(p2) {
21 | }
22 |
23 | double distance(const Vec3 &p) const;
24 | Vec3 closestPoint(const Vec3 &p) const;
25 | Bounds3 bounds() const;
26 |
27 | private:
28 | Vec3 p0, p1, p2;
29 | };
30 |
31 | } // namespace tinymesh
32 |
33 | #endif // TINYMESH_TRIANGLE_H
34 |
--------------------------------------------------------------------------------
/src/tinymesh/core/utils.h:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | #pragma once
3 | #endif
4 |
5 | #ifndef TINYMESH_UTILS_H
6 | #define TINYMESH_UTILS_H
7 |
8 | #include