Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f42bc7406 | ||
|
|
804e659e6d | ||
|
|
fbe4cc6a86 | ||
|
|
f25d9601cb | ||
|
|
07513bd951 | ||
|
|
9a795162f4 | ||
|
|
cdce9e7618 | ||
|
|
628edc8e26 | ||
|
|
d209ead1c2 | ||
|
|
112f9ba501 | ||
|
|
2a63971983 | ||
|
|
4962ebf879 | ||
|
|
d713986f62 | ||
|
|
3b72852b0f | ||
|
|
e17eb038bf | ||
|
|
7b0758eddc | ||
|
|
20178cb209 | ||
|
|
61d87c503c | ||
|
|
7c2007771e | ||
|
|
d82cf061f8 | ||
|
|
dca9cce529 | ||
|
|
754a4811a5 | ||
|
|
83131c672e | ||
|
|
bd8da6ce1e | ||
|
|
3c736f9030 | ||
|
|
bb24367220 | ||
|
|
a97c666358 | ||
|
|
7c8ce3c935 | ||
|
|
4d434e4f8c | ||
|
|
9e9a22d66b | ||
|
|
893a4f8ac3 | ||
|
|
78c03f2611 | ||
|
|
2915f7aa90 | ||
|
|
95daa54591 | ||
|
|
2c597d1eef | ||
|
|
e2a8e94906 | ||
|
|
eae9653745 | ||
|
|
feb7fb6edd | ||
|
|
32f887d321 | ||
|
|
3a962cfa53 | ||
|
|
36ca7acf6c | ||
|
|
0704f62eaa | ||
|
|
5041c38424 | ||
|
|
c4bf392f66 | ||
|
|
3f42e5e6d7 | ||
|
|
376a7c676f | ||
|
|
e6bb87b2bb | ||
|
|
9477deaa9c | ||
|
|
02affd5247 | ||
|
|
171fe5466e | ||
|
|
5331b68485 | ||
|
|
20d7ef96fb | ||
|
|
3560bbf679 | ||
|
|
4415cfce46 | ||
|
|
bbb1fabcac | ||
|
|
f43dded234 | ||
|
|
05f9ceb999 | ||
|
|
422c7f26d9 | ||
|
|
a701d2be8e | ||
|
|
5b5f56674a | ||
|
|
1b7b365c3a | ||
|
|
812b056179 | ||
|
|
bd46bfd1cf | ||
|
|
a7b457db74 | ||
|
|
87059508e7 |
@@ -26,6 +26,7 @@ jobs:
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
name: wasm
|
||||
path: ./public
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
@@ -12,30 +12,30 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
|
||||
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
pip install trimesh
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
run: |
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
@@ -67,8 +67,36 @@ jobs:
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
|
||||
|
||||
build_cbind:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build C bindings with OMP and CUDA
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=OMP -DMANIFOLD_USE_CUDA=ON .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test --gtest_filter=CBIND.*
|
||||
|
||||
build_wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
@@ -77,7 +105,7 @@ jobs:
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- name: Setup WASM
|
||||
run: |
|
||||
# setup emscripten
|
||||
@@ -89,7 +117,6 @@ jobs:
|
||||
- name: Build WASM
|
||||
run: |
|
||||
source ./emsdk/emsdk_env.sh
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
|
||||
@@ -108,9 +135,12 @@ jobs:
|
||||
build/bindings/wasm/manifold.js
|
||||
build/bindings/wasm/manifold.wasm
|
||||
build/bindings/wasm/manifold.d.ts
|
||||
retention-days: 7
|
||||
build/bindings/wasm/manifold-global-types.d.ts
|
||||
build/bindings/wasm/manifold-encapsulated-types.d.ts
|
||||
retention-days: 90
|
||||
|
||||
build_windows:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
@@ -121,7 +151,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Install CUDA
|
||||
@@ -144,7 +174,7 @@ jobs:
|
||||
- name: Build ${{matrix.backend}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DPYBIND11_FINDPYTHON=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
@@ -154,12 +184,17 @@ jobs:
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mac:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install pkg-config
|
||||
pip install trimesh
|
||||
- name: Install OpenMP
|
||||
if: matrix.parallel_backend == 'OMP'
|
||||
run: brew install libomp
|
||||
@@ -168,11 +203,10 @@ jobs:
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
@@ -182,6 +216,7 @@ jobs:
|
||||
./manifold_test
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
|
||||
@@ -189,6 +224,8 @@ jobs:
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
- run: |
|
||||
nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
@@ -11,3 +11,9 @@
|
||||
path = bindings/python/third_party/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
[submodule "src/third_party/clipper2"]
|
||||
path = src/third_party/clipper2
|
||||
url = https://github.com/AngusJohnson/clipper2
|
||||
[submodule "meshIO/third_party/assimp"]
|
||||
path = meshIO/third_party/assimp
|
||||
url = https://github.com/assimp/assimp.git
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DBUILD_SHARED_LIBS=ON",
|
||||
"-DMANIFOLD_EXPORT=OFF",
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=NONE",
|
||||
@@ -118,4 +118,14 @@
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
}
|
||||
@@ -6,4 +6,5 @@
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Google LLC
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
|
||||
@@ -18,26 +18,40 @@ project(manifold LANGUAGES CXX)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
option(MANIFOLD_EXPORT off)
|
||||
option(MANIFOLD_DEBUG off)
|
||||
option(MANIFOLD_USE_CUDA off)
|
||||
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(MANIFOLD_USE_CUDA "Enable GPU support via CUDA" OFF)
|
||||
option(MANIFOLD_PYBIND "Build python bindings" ON)
|
||||
option(MANIFOLD_CBIND "Build C (FFI) bindings" OFF)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS -O3)
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
message("Building for Emscripten")
|
||||
set(MANIFOLD_FLAGS -fexceptions)
|
||||
set(MANIFOLD_FLAGS -fexceptions -D_LIBCUDACXX_HAS_THREAD_API_EXTERNAL -D_LIBCUDACXX_HAS_THREAD_API_CUDA)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
endif()
|
||||
|
||||
option(PYBIND11_FINDPYTHON on)
|
||||
option(PYBIND11_FINDPYTHON "Enable PyBind to perform FindPython for you" ON)
|
||||
|
||||
option(BUILD_TEST_CGAL off)
|
||||
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
|
||||
|
||||
option(BUILD_SHARED_LIBS off)
|
||||
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
|
||||
option(BUILD_SHARED_LIBS "Build dynamic/shared libraries" OFF)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
set(MANIFOLD_EXPORT ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
|
||||
# Allow shared libraries to be relocatable (add Place Independent Code flag).
|
||||
# Also include when statically linking C bindings to avoid issues with bundling
|
||||
# artefacts in host languages using them for FFI.
|
||||
add_compile_options(-fPIC)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
enable_language(CUDA)
|
||||
find_package(CUDA REQUIRED)
|
||||
@@ -69,7 +83,14 @@ if(CODE_COVERAGE AND NOT MSVC)
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
add_subdirectory(bindings)
|
||||
add_subdirectory(bindings)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_subdirectory(meshIO)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_TEST)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
endif()
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
## Users
|
||||
|
||||
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
|
||||
|
||||
For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:
|
||||
|
||||
<img src="https://elalish.github.io/manifold/samples/models/perfSpheres.png" width="350px"/>
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app. The operations are not GPU-accelerated, but it's still pretty fast and a good way to test out our Manifold library.
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not GPU-accelerated, but it's still quite fast and a good way to test out our Manifold library.
|
||||
|
||||

|
||||
|
||||
# Manifold
|
||||
|
||||
[**High-level Documentation**](https://elalish.blogspot.com/search/label/Manifold) | [**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
|
||||
[**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
|
||||
|
||||
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm) and Python to make this library more portable and easy to use. We have only three dependencies (all built-in header-only libraries): graphlite provides a connected components algorithm, GLM is a compact vector library, and Thrust is Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms). We make use of submodules to ensure compatibility.
|
||||
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
|
||||
|
||||
We have four core dependencies, making use of submodules to ensure compatibility:
|
||||
- `graphlite`: connected components algorithm
|
||||
- `Clipper2`: provides our 2D subsystem
|
||||
- `GLM`: a compact vector library
|
||||
- `Thrust`: Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
|
||||
|
||||
## What's here
|
||||
|
||||
@@ -21,7 +35,7 @@ This library is fast with guaranteed manifold output. As such you need manifold
|
||||
|
||||
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
|
||||
|
||||
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
|
||||
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP, TBB, and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
|
||||
|
||||
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
|
||||
|
||||
@@ -33,7 +47,6 @@ Build and test (Ubuntu or similar):
|
||||
```
|
||||
git clone --recurse-submodules https://github.com/elalish/manifold.git
|
||||
cd manifold
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
|
||||
@@ -43,7 +56,7 @@ test/manifold_test
|
||||
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
|
||||
- `MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
|
||||
- `MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, uses `assimp`.
|
||||
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
|
||||
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
|
||||
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
project(manifoldc)
|
||||
|
||||
|
||||
add_library(manifoldc manifoldc.cpp conv.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET manifoldc PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
manifoldc
|
||||
PRIVATE manifold sdf meshIO graphlite cross_section
|
||||
)
|
||||
|
||||
target_include_directories(manifoldc PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(manifoldc PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(manifoldc PRIVATE cxx_std_17)
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2) {
|
||||
auto p1 = glm::vec3(x1, y1, z1);
|
||||
auto p2 = glm::vec3(x2, y2, z2);
|
||||
auto box = new (mem) Box(p1, p2);
|
||||
return to_c(box);
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
|
||||
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
|
||||
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
|
||||
auto v = from_c(b)->Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
auto v = from_c(b)->Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
|
||||
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return from_c(b)->Contains(p);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
auto box = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(b)->Transform(mat);
|
||||
return to_c(new (mem) Box(transformed));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto scaled = (*from_c(b)) * p;
|
||||
return to_c(new (mem) Box(scaled));
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return from_c(b)->DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return from_c(a)->DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
|
||||
@@ -0,0 +1,262 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "include/types.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
return reinterpret_cast<ManifoldManifold *>(m);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms) {
|
||||
return reinterpret_cast<ManifoldManifoldVec *>(ms);
|
||||
}
|
||||
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs) {
|
||||
return reinterpret_cast<ManifoldCrossSection *>(cs);
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv) {
|
||||
return reinterpret_cast<ManifoldCrossSectionVec *>(csv);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon *>(m);
|
||||
}
|
||||
|
||||
ManifoldPolygons *to_c(manifold::Polygons *m) {
|
||||
return reinterpret_cast<ManifoldPolygons *>(m);
|
||||
}
|
||||
|
||||
ManifoldMesh *to_c(manifold::Mesh *m) {
|
||||
return reinterpret_cast<ManifoldMesh *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m) {
|
||||
return reinterpret_cast<ManifoldCurvature *>(m);
|
||||
}
|
||||
|
||||
ManifoldOpType to_c(manifold::OpType optype) {
|
||||
ManifoldOpType op = MANIFOLD_ADD;
|
||||
switch (optype) {
|
||||
case manifold::OpType::Add:
|
||||
break;
|
||||
case manifold::OpType::Subtract:
|
||||
op = MANIFOLD_SUBTRACT;
|
||||
break;
|
||||
case manifold::OpType::Intersect:
|
||||
op = MANIFOLD_INTERSECT;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
ManifoldError e = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
break;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
break;
|
||||
case Manifold::Error::NotManifold:
|
||||
e = MANIFOLD_NOT_MANIFOLD;
|
||||
break;
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
|
||||
break;
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
break;
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
e = MANIFOLD_INVALID_CONSTRUCTION;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
|
||||
ManifoldBox *to_c(manifold::Box *m) {
|
||||
return reinterpret_cast<ManifoldBox *>(m);
|
||||
}
|
||||
|
||||
ManifoldRect *to_c(manifold::Rect *m) {
|
||||
return reinterpret_cast<ManifoldRect *>(m);
|
||||
}
|
||||
|
||||
ManifoldMaterial *to_c(manifold::Material *m) {
|
||||
return reinterpret_cast<ManifoldMaterial *>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
|
||||
return reinterpret_cast<ManifoldExportOptions *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
|
||||
|
||||
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldProperties to_c(manifold::Properties p) {
|
||||
return {p.surfaceArea, p.volume};
|
||||
}
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m) {
|
||||
return reinterpret_cast<manifold::Manifold const *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms) {
|
||||
return reinterpret_cast<ManifoldVec *>(ms);
|
||||
}
|
||||
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs) {
|
||||
return reinterpret_cast<manifold::CrossSection *>(cs);
|
||||
}
|
||||
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv) {
|
||||
return reinterpret_cast<CrossSectionVec *>(csv);
|
||||
}
|
||||
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m) {
|
||||
return reinterpret_cast<manifold::Polygons const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m) {
|
||||
return reinterpret_cast<manifold::Mesh const *>(m);
|
||||
}
|
||||
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
|
||||
return reinterpret_cast<manifold::MeshGL const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *c) {
|
||||
return reinterpret_cast<manifold::Curvature const *>(c);
|
||||
}
|
||||
|
||||
OpType from_c(ManifoldOpType optype) {
|
||||
auto op = OpType::Add;
|
||||
switch (optype) {
|
||||
case MANIFOLD_ADD:
|
||||
break;
|
||||
case MANIFOLD_SUBTRACT:
|
||||
op = OpType::Subtract;
|
||||
break;
|
||||
case MANIFOLD_INTERSECT:
|
||||
op = OpType::Intersect;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
|
||||
auto fr = CrossSection::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case MANIFOLD_FILL_RULE_EVEN_ODD:
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NON_ZERO:
|
||||
fr = CrossSection::FillRule::NonZero;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_POSITIVE:
|
||||
fr = CrossSection::FillRule::Positive;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NEGATIVE:
|
||||
fr = CrossSection::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
|
||||
auto jt = CrossSection::JoinType::Square;
|
||||
switch (join_type) {
|
||||
case MANIFOLD_JOIN_TYPE_SQUARE:
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_ROUND:
|
||||
jt = CrossSection::JoinType::Round;
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_MITER:
|
||||
jt = CrossSection::JoinType::Miter;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
const manifold::Box *from_c(ManifoldBox *m) {
|
||||
return reinterpret_cast<manifold::Box const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Rect *from_c(ManifoldRect *m) {
|
||||
return reinterpret_cast<manifold::Rect const *>(m);
|
||||
}
|
||||
|
||||
manifold::Material *from_c(ManifoldMaterial *mat) {
|
||||
return reinterpret_cast<manifold::Material *>(mat);
|
||||
}
|
||||
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options) {
|
||||
return reinterpret_cast<manifold::ExportOptions *>(options);
|
||||
}
|
||||
|
||||
glm::vec2 from_c(ManifoldVec2 v) { return glm::vec2(v.x, v.y); }
|
||||
|
||||
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::ivec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec4>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem) {
|
||||
return to_c(new (mem) CrossSection());
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto cross = *from_c(cs);
|
||||
return to_c(new (mem) CrossSection(cross));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
|
||||
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
|
||||
ManifoldPolygons *p,
|
||||
ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem) {
|
||||
return to_c(new (mem) CrossSectionVec());
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz) {
|
||||
return to_c(new (mem) CrossSectionVec(sz));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
|
||||
size_t sz) {
|
||||
from_c(csv)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv) {
|
||||
return from_c(csv)->size();
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_vec_get(
|
||||
void *mem, ManifoldCrossSectionVec *csv, int idx) {
|
||||
auto cs = (*from_c(csv))[idx];
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
|
||||
ManifoldCrossSection *cs) {
|
||||
(*from_c(csv))[idx] = *from_c(cs);
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
|
||||
ManifoldCrossSection *cs) {
|
||||
return from_c(csv)->push_back(*from_c(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
|
||||
int center) {
|
||||
auto cs = CrossSection::Square(glm::vec2(x, y), center);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
|
||||
int circular_segments) {
|
||||
auto cs = CrossSection::Circle(radius, circular_segments);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b,
|
||||
ManifoldOpType op) {
|
||||
auto cs = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_batch_boolean(
|
||||
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op) {
|
||||
auto cs = CrossSection::BatchBoolean(*from_c(csv), from_c(op));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_union(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b) {
|
||||
auto cs = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_difference(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
|
||||
auto cs = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_intersection(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
|
||||
auto cs = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_rect_clip(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
ManifoldRect *r) {
|
||||
auto clipped = from_c(cs)->RectClip(*from_c(r));
|
||||
return to_c(new (mem) CrossSection(clipped));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y) {
|
||||
auto translated = from_c(cs)->Translate(glm::vec2(x, y));
|
||||
return to_c(new (mem) CrossSection(translated));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float deg) {
|
||||
auto rotated = from_c(cs)->Rotate(deg);
|
||||
return to_c(new (mem) CrossSection(rotated));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y) {
|
||||
auto scaled = from_c(cs)->Scale(glm::vec2(x, y));
|
||||
return to_c(new (mem) CrossSection(scaled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float ax_x, float ax_y) {
|
||||
auto mirrored = from_c(cs)->Mirror(glm::vec2(ax_x, ax_y));
|
||||
return to_c(new (mem) CrossSection(mirrored));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3) {
|
||||
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
|
||||
auto transformed = from_c(cs)->Transform(mat);
|
||||
return to_c(new (mem) CrossSection(transformed));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_warp(
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float)) {
|
||||
std::function<void(glm::vec2 & v)> warp = [fun](glm::vec2 &v) {
|
||||
v = from_c(fun(v.x, v.y));
|
||||
};
|
||||
auto warped = from_c(cs)->Warp(warp);
|
||||
return to_c(new (mem) CrossSection(warped));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
double epsilon) {
|
||||
auto simplified = from_c(cs)->Simplify(epsilon);
|
||||
return to_c(new (mem) CrossSection(simplified));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_offset(
|
||||
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, double arc_tolerance) {
|
||||
auto offset =
|
||||
from_c(cs)->Offset(delta, from_c(jt), miter_limit, arc_tolerance);
|
||||
return to_c(new (mem) CrossSection(offset));
|
||||
}
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->Area();
|
||||
}
|
||||
|
||||
int manifold_cross_section_num_vert(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->NumVert();
|
||||
}
|
||||
|
||||
int manifold_cross_section_num_contour(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->NumContour();
|
||||
}
|
||||
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->IsEmpty();
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_cross_section_bounds(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto rect = from_c(cs)->Bounds();
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto ps = from_c(cs)->ToPolygons();
|
||||
return to_c(new (mem) Polygons(ps));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_compose(
|
||||
void *mem, ManifoldCrossSectionVec *csv) {
|
||||
auto cs = CrossSection::Compose(*from_c(csv));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_decompose(
|
||||
void *mem, ManifoldCrossSection *cs) {
|
||||
auto comps = from_c(cs)->Decompose();
|
||||
return to_c(new (mem) CrossSectionVec(comps));
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include <types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m);
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms);
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs);
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldRect *to_c(manifold::Rect *m);
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldMaterial *to_c(manifold::Material *m);
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m);
|
||||
ManifoldVec2 to_c(glm::vec2 v);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms);
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
OpType from_c(ManifoldOpType op);
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
|
||||
CrossSection::JoinType from_c(ManifoldJoinType jt);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Rect *from_c(ManifoldRect *r);
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *m);
|
||||
manifold::Material *from_c(ManifoldMaterial *mat);
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options);
|
||||
glm::vec2 from_c(ManifoldVec2 v);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector_of_array(T *ts, size_t length) {
|
||||
auto vec = std::vector<T>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *copy_data(void *mem, std::vector<T> v) {
|
||||
T *ts = reinterpret_cast<T *>(mem);
|
||||
memcpy(ts, v.data(), sizeof(T) * v.size());
|
||||
return ts;
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Polygons
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length);
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length);
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p);
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps);
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx);
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx);
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx);
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx);
|
||||
|
||||
// Mesh Construction
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris);
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent);
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// SDF
|
||||
// By default, the execution policy (sequential or parallel) of
|
||||
// manifold_level_set will be chosen automatically depending on the size of the
|
||||
// job and whether Manifold has been compiled with a PAR backend. If you are
|
||||
// using these bindings from a language that has a runtime lock preventing the
|
||||
// parallel execution of closures, then you should use manifold_level_set_seq to
|
||||
// force sequential execution.
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
|
||||
// Manifold Vectors
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem);
|
||||
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz);
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz);
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms);
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx);
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
ManifoldManifold *m);
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
|
||||
ManifoldManifold *m);
|
||||
|
||||
// Manifold Booleans
|
||||
|
||||
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b, ManifoldOpType op);
|
||||
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
|
||||
ManifoldOpType op);
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
|
||||
// Manifold Transformations
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz);
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float));
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
|
||||
|
||||
// Manifold Shapes / Constructors
|
||||
ManifoldManifold *manifold_empty(void *mem);
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem);
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center);
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center);
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_idxs);
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Manifold Info
|
||||
int manifold_is_empty(ManifoldManifold *m);
|
||||
ManifoldError manifold_status(ManifoldManifold *m);
|
||||
int manifold_num_vert(ManifoldManifold *m);
|
||||
int manifold_num_edge(ManifoldManifold *m);
|
||||
int manifold_num_tri(ManifoldManifold *m);
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
|
||||
float manifold_precision(ManifoldManifold *m);
|
||||
int manifold_genus(ManifoldManifold *m);
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
|
||||
ManifoldCurvature *manifold_get_curvature(void *mem, ManifoldManifold *m);
|
||||
ManifoldCurvatureBounds manifold_curvature_bounds(ManifoldCurvature *curv);
|
||||
size_t manifold_curvature_vert_length(ManifoldCurvature *curv);
|
||||
float *manifold_curvature_vert_mean(void *mem, ManifoldCurvature *curv);
|
||||
float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv);
|
||||
int manifold_get_circular_segments(float radius);
|
||||
int manifold_original_id(ManifoldManifold *m);
|
||||
|
||||
// CrossSection Shapes/Constructors
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem);
|
||||
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
|
||||
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr);
|
||||
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
|
||||
ManifoldPolygons *p,
|
||||
ManifoldFillRule fr);
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
|
||||
int center);
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
|
||||
int circular_segments);
|
||||
ManifoldCrossSection *manifold_cross_section_compose(
|
||||
void *mem, ManifoldCrossSectionVec *csv);
|
||||
ManifoldCrossSectionVec *manifold_cross_section_decompose(
|
||||
void *mem, ManifoldCrossSection *cs);
|
||||
|
||||
// CrossSection Vectors
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem);
|
||||
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz);
|
||||
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
|
||||
size_t sz);
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv);
|
||||
ManifoldCrossSection *manifold_cross_section_vec_get(
|
||||
void *mem, ManifoldCrossSectionVec *csv, int idx);
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
|
||||
ManifoldCrossSection *cs);
|
||||
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
|
||||
ManifoldCrossSection *cs);
|
||||
|
||||
// CrossSection Booleans
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b,
|
||||
ManifoldOpType op);
|
||||
ManifoldCrossSection *manifold_cross_section_batch_boolean(
|
||||
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op);
|
||||
ManifoldCrossSection *manifold_cross_section_union(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b);
|
||||
ManifoldCrossSection *manifold_cross_section_difference(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
|
||||
ManifoldCrossSection *manifold_cross_section_intersection(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
|
||||
ManifoldCrossSection *manifold_cross_section_rect_clip(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
ManifoldRect *r);
|
||||
|
||||
// CrossSection Transformation
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y);
|
||||
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float deg);
|
||||
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y);
|
||||
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float ax_x, float ax_y);
|
||||
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3);
|
||||
ManifoldCrossSection *manifold_cross_section_warp(
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float));
|
||||
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
double epsilon);
|
||||
ManifoldCrossSection *manifold_cross_section_offset(
|
||||
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, double arc_tolerance);
|
||||
|
||||
// CrossSection Info
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_num_vert(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_num_contour(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection *cs);
|
||||
ManifoldRect *manifold_cross_section_bounds(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
|
||||
// Rectangle
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2);
|
||||
ManifoldVec2 manifold_rect_min(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_max(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_center(ManifoldRect *r);
|
||||
float manifold_rect_scale(ManifoldRect *r);
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y);
|
||||
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b);
|
||||
void manifold_rect_include_pt(ManifoldRect *r, float x, float y);
|
||||
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b);
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
|
||||
float y1, float x2, float y2, float x3,
|
||||
float y3);
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
|
||||
float y);
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y);
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r);
|
||||
int manifold_rect_is_empty(ManifoldRect *r);
|
||||
int manifold_rect_is_finite(ManifoldRect *r);
|
||||
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
|
||||
ManifoldRect *r);
|
||||
|
||||
// Bounding Box
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2);
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b);
|
||||
float manifold_box_scale(ManifoldBox *b);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
|
||||
int manifold_box_is_finite(ManifoldBox *b);
|
||||
|
||||
// Static Quality Globals
|
||||
void manifold_set_min_circular_angle(float degrees);
|
||||
void manifold_set_min_circular_edge_length(float length);
|
||||
void manifold_set_circular_segments(int number);
|
||||
|
||||
// Manifold Mesh Extraction
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// MeshIO / Export
|
||||
ManifoldMaterial *manifold_material(void *mem);
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert);
|
||||
ManifoldExportOptions *manifold_export_options(void *mem);
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted);
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat);
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options);
|
||||
|
||||
// memory size
|
||||
size_t manifold_manifold_size();
|
||||
size_t manifold_manifold_vec_size();
|
||||
size_t manifold_cross_section_size();
|
||||
size_t manifold_cross_section_vec_size();
|
||||
size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
size_t manifold_meshgl_size();
|
||||
size_t manifold_box_size();
|
||||
size_t manifold_rect_size();
|
||||
size_t manifold_curvature_size();
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_manifold(ManifoldManifold *m);
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms);
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *m);
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_destruct_box(ManifoldBox *b);
|
||||
void manifold_destruct_rect(ManifoldRect *b);
|
||||
void manifold_destruct_curvature(ManifoldCurvature *c);
|
||||
void manifold_destruct_material(ManifoldMaterial *m);
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *options);
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_manifold(ManifoldManifold *m);
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms);
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *cs);
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_delete_polygons(ManifoldPolygons *p);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_delete_box(ManifoldBox *b);
|
||||
void manifold_delete_rect(ManifoldRect *b);
|
||||
void manifold_delete_curvature(ManifoldCurvature *c);
|
||||
void manifold_delete_material(ManifoldMaterial *m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions *options);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct ManifoldManifold ManifoldManifold;
|
||||
typedef struct ManifoldManifoldVec ManifoldManifoldVec;
|
||||
typedef struct ManifoldCrossSection ManifoldCrossSection;
|
||||
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
|
||||
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
|
||||
typedef struct ManifoldPolygons ManifoldPolygons;
|
||||
typedef struct ManifoldMesh ManifoldMesh;
|
||||
typedef struct ManifoldMeshGL ManifoldMeshGL;
|
||||
typedef struct ManifoldCurvature ManifoldCurvature;
|
||||
typedef struct ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldRect ManifoldRect;
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
|
||||
typedef struct ManifoldManifoldPair {
|
||||
ManifoldManifold* first;
|
||||
ManifoldManifold* second;
|
||||
} ManifoldManifoldPair;
|
||||
|
||||
typedef struct ManifoldVec2 {
|
||||
float x;
|
||||
float y;
|
||||
} ManifoldVec2;
|
||||
|
||||
typedef struct ManifoldVec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} ManifoldVec3;
|
||||
|
||||
typedef struct ManifoldIVec3 {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} ManifoldIVec3;
|
||||
|
||||
typedef struct ManifoldVec4 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
} ManifoldVec4;
|
||||
|
||||
typedef struct ManifoldProperties {
|
||||
float surface_area;
|
||||
float volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
typedef struct ManifoldCurvatureBounds {
|
||||
float max_mean_curvature;
|
||||
float min_mean_curvature;
|
||||
float max_gaussian_curvature;
|
||||
float min_gaussian_curvature;
|
||||
} ManifoldCurvatureBounds;
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
MANIFOLD_ADD,
|
||||
MANIFOLD_SUBTRACT,
|
||||
MANIFOLD_INTERSECT
|
||||
} ManifoldOpType;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
MANIFOLD_NO_ERROR,
|
||||
MANIFOLD_NON_FINITE_VERTEX,
|
||||
MANIFOLD_NOT_MANIFOLD,
|
||||
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_PROPERTIES_WRONG_LENGTH,
|
||||
MANIFOLD_MISSING_POSITION_PROPERTIES,
|
||||
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_TRANSFORM_WRONG_LENGTH,
|
||||
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
|
||||
MANIFOLD_FACE_ID_WRONG_LENGTH,
|
||||
MANIFOLD_INVALID_CONSTRUCTION,
|
||||
} ManifoldError;
|
||||
|
||||
typedef enum ManifoldFillRule {
|
||||
MANIFOLD_FILL_RULE_EVEN_ODD,
|
||||
MANIFOLD_FILL_RULE_NON_ZERO,
|
||||
MANIFOLD_FILL_RULE_POSITIVE,
|
||||
MANIFOLD_FILL_RULE_NEGATIVE
|
||||
} ManifoldFillRule;
|
||||
|
||||
typedef enum ManifoldJoinType {
|
||||
MANIFOLD_JOIN_TYPE_SQUARE,
|
||||
MANIFOLD_JOIN_TYPE_ROUND,
|
||||
MANIFOLD_JOIN_TYPE_MITER,
|
||||
} ManifoldJoinType;
|
||||
@@ -0,0 +1,549 @@
|
||||
#include <conv.h>
|
||||
#include <cross_section.h>
|
||||
#include <manifold.h>
|
||||
#include <manifoldc.h>
|
||||
#include <meshIO.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "box.cpp"
|
||||
#include "cross.cpp"
|
||||
#include "include/conv.h"
|
||||
#include "include/types.h"
|
||||
#include "meshio.cpp"
|
||||
#include "rect.cpp"
|
||||
#include "types.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length, float level,
|
||||
bool seq) {
|
||||
// typing with std::function rather than auto compiles when CUDA is on,
|
||||
// passing it into GPU (and crashing) is avoided dynamically in `sdf.h`
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<glm::vec2>;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back({ps[i].x, ps[i].y});
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<SimplePolygon>;
|
||||
auto polys = reinterpret_cast<SimplePolygon **>(ps);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back(*polys[i]);
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p) {
|
||||
return from_c(p)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps) {
|
||||
return from_c(ps)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx) {
|
||||
return (*from_c(ps))[idx].size();
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx) {
|
||||
return to_c((*from_c(p))[idx]);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx) {
|
||||
auto sp = (*from_c(ps))[idx];
|
||||
return to_c(new (mem) SimplePolygon(sp));
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx) {
|
||||
return to_c((*from_c(ps))[simple_idx][pt_idx]);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem) {
|
||||
return to_c(new (mem) ManifoldVec());
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz) {
|
||||
return to_c(new (mem) ManifoldVec(sz));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz) {
|
||||
from_c(ms)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms) {
|
||||
return from_c(ms)->size();
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx) {
|
||||
auto m = (*from_c(ms))[idx];
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
ManifoldManifold *m) {
|
||||
(*from_c(ms))[idx] = *from_c(m);
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
|
||||
ManifoldManifold *m) {
|
||||
return from_c(ms)->push_back(*from_c(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b, ManifoldOpType op) {
|
||||
auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
|
||||
ManifoldOpType op) {
|
||||
auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b) {
|
||||
auto pair = from_c(a)->Split(*from_c(b));
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto pair = from_c(m)->SplitByPlane(normal, offset);
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
|
||||
return to_c(new (mem) Manifold(trimmed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto v = glm::vec3(x, y, z);
|
||||
auto translated = from_c(m)->Translate(v);
|
||||
return to_c(new (mem) Manifold(translated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto rotated = from_c(m)->Rotate(x, y, z);
|
||||
return to_c(new (mem) Manifold(rotated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto s = glm::vec3(x, y, z);
|
||||
auto scaled = from_c(m)->Scale(s);
|
||||
return to_c(new (mem) Manifold(scaled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(m)->Transform(mat);
|
||||
return to_c(new (mem) Manifold(transformed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz) {
|
||||
auto mirrored = from_c(m)->Mirror({nx, ny, nz});
|
||||
return to_c(new (mem) Manifold(mirrored));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float)) {
|
||||
std::function<void(glm::vec3 & v)> warp = [fun](glm::vec3 &v) {
|
||||
v = from_c(fun(v.x, v.y, v.z));
|
||||
};
|
||||
auto warped = from_c(m)->Warp(warp);
|
||||
return to_c(new (mem) Manifold(warped));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, false);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, true);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
|
||||
auto refined = from_c(m)->Refine(refine);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem) {
|
||||
return to_c(new (mem) Manifold());
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m) {
|
||||
return to_c(new (mem) Manifold(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem) {
|
||||
auto m = Manifold::Tetrahedron();
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center) {
|
||||
auto size = glm::vec3(x, y, z);
|
||||
auto m = Manifold::Cube(size, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center) {
|
||||
auto m = Manifold::Cylinder(height, radius_low, radius_high,
|
||||
circular_segments, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Sphere(radius, circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (int i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
|
||||
auto m = Manifold(*from_c(mesh));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y) {
|
||||
auto scale = glm::vec2(scale_x, scale_y);
|
||||
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms) {
|
||||
auto composed = Manifold::Compose(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m) {
|
||||
auto comps = from_c(m)->Decompose();
|
||||
return to_c(new (mem) std::vector<Manifold>(comps));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m) {
|
||||
auto mesh = from_c(m)->GetMeshGL();
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
|
||||
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->triVerts.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->mergeFromVert.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runOriginalID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runTransform.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->faceID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->halfedgeTangent.size();
|
||||
}
|
||||
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->vertProperties);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->triVerts);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeFromVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeToVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runOriginalID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runTransform);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->faceID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
|
||||
auto orig = from_c(m)->AsOriginal();
|
||||
return to_c(new (mem) Manifold(orig));
|
||||
}
|
||||
|
||||
int manifold_original_id(ManifoldManifold *m) {
|
||||
return from_c(m)->OriginalID();
|
||||
}
|
||||
|
||||
int manifold_is_empty(ManifoldManifold *m) { return from_c(m)->IsEmpty(); }
|
||||
|
||||
ManifoldError manifold_status(ManifoldManifold *m) {
|
||||
auto error = from_c(m)->Status();
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
|
||||
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
|
||||
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
|
||||
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
|
||||
return to_c(from_c(m)->GetProperties());
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
|
||||
auto box = from_c(m)->BoundingBox();
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
|
||||
ManifoldCurvature *manifold_get_curvature(void *mem, ManifoldManifold *m) {
|
||||
auto curv = from_c(m)->GetCurvature();
|
||||
return to_c(new (mem) Curvature(curv));
|
||||
}
|
||||
|
||||
ManifoldCurvatureBounds manifold_curvature_bounds(ManifoldCurvature *curv) {
|
||||
auto c = *from_c(curv);
|
||||
return {c.maxMeanCurvature, c.minMeanCurvature, c.maxGaussianCurvature,
|
||||
c.minGaussianCurvature};
|
||||
}
|
||||
|
||||
size_t manifold_curvature_vert_length(ManifoldCurvature *curv) {
|
||||
return from_c(curv)->vertMeanCurvature.size();
|
||||
}
|
||||
|
||||
float *manifold_curvature_vert_mean(void *mem, ManifoldCurvature *curv) {
|
||||
return copy_data(mem, from_c(curv)->vertMeanCurvature);
|
||||
}
|
||||
|
||||
float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv) {
|
||||
return copy_data(mem, from_c(curv)->vertGaussianCurvature);
|
||||
}
|
||||
|
||||
// Static Quality Globals
|
||||
|
||||
void manifold_set_min_circular_angle(float degrees) {
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
return Quality::GetCircularSegments(radius);
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
|
||||
size_t manifold_cross_section_vec_size() {
|
||||
return sizeof(std::vector<CrossSection>);
|
||||
}
|
||||
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
|
||||
size_t manifold_polygons_size() { return sizeof(Polygons); }
|
||||
size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
|
||||
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
|
||||
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
|
||||
size_t manifold_box_size() { return sizeof(Box); }
|
||||
size_t manifold_rect_size() { return sizeof(Rect); }
|
||||
size_t manifold_curvature_size() { return sizeof(Curvature); }
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *c) {
|
||||
delete from_c(c);
|
||||
}
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv) {
|
||||
delete from_c(csv);
|
||||
}
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
delete from_c(p);
|
||||
}
|
||||
void manifold_delete_polygons(ManifoldPolygons *p) { delete from_c(p); }
|
||||
void manifold_delete_manifold(ManifoldManifold *m) { delete from_c(m); }
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
delete from_c(ms);
|
||||
}
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
|
||||
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
|
||||
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
|
||||
void manifold_delete_curvature(ManifoldCurvature *c) { delete from_c(c); }
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *cs) {
|
||||
from_c(cs)->~CrossSection();
|
||||
}
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv) {
|
||||
from_c(csv)->~CrossSectionVec();
|
||||
}
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
from_c(p)->~SimplePolygon();
|
||||
}
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p) { from_c(p)->~Polygons(); }
|
||||
void manifold_destruct_manifold(ManifoldManifold *m) { from_c(m)->~Manifold(); }
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
from_c(ms)->~ManifoldVec();
|
||||
}
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
|
||||
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
|
||||
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
|
||||
void manifold_destruct_curvature(ManifoldCurvature *c) {
|
||||
from_c(c)->~Curvature();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "meshIO.h"
|
||||
|
||||
#include <conv.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldMaterial *manifold_material(void *mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert) {
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions *m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2) {
|
||||
auto p1 = glm::vec2(x1, y1);
|
||||
auto p2 = glm::vec2(x2, y2);
|
||||
auto rect = new (mem) Rect(p1, p2);
|
||||
return to_c(rect);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_min(ManifoldRect *r) {
|
||||
return to_c((*from_c(r)).min);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_max(ManifoldRect *r) {
|
||||
return to_c((*from_c(r)).max);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r) {
|
||||
auto v = from_c(r)->Size();
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_center(ManifoldRect *r) {
|
||||
auto v = from_c(r)->Center();
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
float manifold_rect_scale(ManifoldRect *r) { return from_c(r)->Scale(); }
|
||||
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
return rect.Contains(p);
|
||||
}
|
||||
|
||||
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_rect_include_pt(ManifoldRect *r, float x, float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
rect.Union(p);
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b) {
|
||||
auto rect = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
|
||||
float y1, float x2, float y2, float x3,
|
||||
float y3) {
|
||||
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
|
||||
auto transformed = from_c(r)->Transform(mat);
|
||||
return to_c(new (mem) Rect(transformed));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
|
||||
float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto translated = (*from_c(r)) + p;
|
||||
return to_c(new (mem) Rect(translated));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto scaled = (*from_c(r)) * p;
|
||||
return to_c(new (mem) Rect(scaled));
|
||||
}
|
||||
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r) {
|
||||
return from_c(a)->DoesOverlap(*from_c(r));
|
||||
}
|
||||
|
||||
int manifold_rect_is_empty(ManifoldRect *r) { return from_c(r)->IsEmpty(); }
|
||||
|
||||
int manifold_rect_is_finite(ManifoldRect *r) { return from_c(r)->IsFinite(); }
|
||||
|
||||
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
|
||||
ManifoldRect *r) {
|
||||
auto cs = from_c(r)->AsCrossSection();
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
@@ -18,7 +18,7 @@ add_subdirectory(third_party)
|
||||
pybind11_add_module(pymanifold pymanifold.cpp)
|
||||
target_link_libraries(pymanifold PRIVATE manifold)
|
||||
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_14)
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_17)
|
||||
target_include_directories(pymanifold
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from pymanifold import Manifold
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
|
||||
return obj
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "pybind11/functional.h"
|
||||
#include "pybind11/numpy.h"
|
||||
@@ -36,12 +37,57 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
"documentation for APIs.\n"
|
||||
"This binding will perform copying to make the API more familiar to "
|
||||
"OpenSCAD users.";
|
||||
|
||||
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
|
||||
"Sets an angle constraint the default number of circular segments for "
|
||||
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
|
||||
"and Manifold::Revolve() constructors. The number of segments will be "
|
||||
"rounded up to the nearest factor of four."
|
||||
"\n\n"
|
||||
"@param angle The minimum angle in degrees between consecutive "
|
||||
"segments. The angle will increase if the the segments hit the minimum "
|
||||
"edge length.\n"
|
||||
"Default is 10 degrees.");
|
||||
|
||||
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
|
||||
"Sets a length constraint the default number of circular segments for "
|
||||
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
|
||||
"and Manifold::Revolve() constructors. The number of segments will be "
|
||||
"rounded up to the nearest factor of four."
|
||||
"\n\n"
|
||||
"@param length The minimum length of segments. The length will "
|
||||
"increase if the the segments hit the minimum angle. Default is 1.0.");
|
||||
|
||||
m.def("set_circular_segments", Quality::SetCircularSegments,
|
||||
"Sets the default number of circular segments for the "
|
||||
"CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and "
|
||||
"Manifold::Revolve() constructors. Overrides the edge length and angle "
|
||||
"constraints and sets the number of segments to exactly this value."
|
||||
"\n\n"
|
||||
"@param number Number of circular segments. Default is 0, meaning no "
|
||||
"constraint is applied.");
|
||||
|
||||
m.def(
|
||||
"get_circular_segments", Quality::GetCircularSegments,
|
||||
"Determine the result of the SetMinCircularAngle(), "
|
||||
"SetMinCircularEdgeLength(), and SetCircularSegments() defaults."
|
||||
"\n\n"
|
||||
"@param radius For a given radius of circle, determine how many default");
|
||||
|
||||
py::class_<Manifold>(m, "Manifold")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
for (Manifold &manifold : manifolds) result += manifold;
|
||||
return result;
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
return first;
|
||||
} else {
|
||||
return Manifold();
|
||||
}
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
@@ -63,11 +109,10 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("m"),
|
||||
"Transform this Manifold in space. The first three columns form a "
|
||||
"3x3 matrix\n"
|
||||
"transform and the last is a translation vector. This operation can "
|
||||
"be\n"
|
||||
"chained. Transforms are combined and applied lazily.\n"
|
||||
"\n"
|
||||
"3x3 matrix transform and the last is a translation vector. This "
|
||||
"operation can be chained. Transforms are combined and applied "
|
||||
"lazily.\n"
|
||||
"\n\n"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.def(
|
||||
"translate",
|
||||
@@ -76,9 +121,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param x: X axis translation. (default 0.0).\n"
|
||||
":param y: Y axis translation. (default 0.0).\n"
|
||||
":param z: Z axis translation. (default 0.0).")
|
||||
@@ -92,9 +136,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def("scale",
|
||||
static_cast<Manifold (*)(Manifold, float)>(
|
||||
@@ -103,9 +146,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
}),
|
||||
py::arg("scale"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param scale: The scalar multiplier for each component of every "
|
||||
"vertices.")
|
||||
.def(
|
||||
@@ -119,9 +161,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def(
|
||||
"rotate",
|
||||
@@ -133,17 +174,13 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("v"),
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then\n"
|
||||
"Y, then Z, in degrees. We use degrees so that we can minimize "
|
||||
"rounding error,\n"
|
||||
"and eliminate it completely for any multiples of 90 degrees. "
|
||||
"Additionally, more\n"
|
||||
"efficient code paths are used to update the manifold when the "
|
||||
"transforms only\n"
|
||||
"rotate by multiples of 90 degrees. This operation can be chained. "
|
||||
"Transforms\n"
|
||||
"are combined and applied lazily.\n"
|
||||
"\n"
|
||||
"axis, then Y, then Z, in degrees. We use degrees so that we can "
|
||||
"minimize rounding error, and eliminate it completely for any "
|
||||
"multiples of 90 degrees. Additionally, more efficient code paths "
|
||||
"are used to update the manifold when the transforms only rotate by "
|
||||
"multiples of 90 degrees. This operation can be chained. Transforms "
|
||||
"are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: [X, Y, Z] rotation in degrees.")
|
||||
.def(
|
||||
"rotate",
|
||||
@@ -154,17 +191,13 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
|
||||
py::arg("z_degrees") = 0.0f,
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then\n"
|
||||
"Y, then Z, in degrees. We use degrees so that we can minimize "
|
||||
"rounding error,\n"
|
||||
"and eliminate it completely for any multiples of 90 degrees. "
|
||||
"Additionally, more\n"
|
||||
"efficient code paths are used to update the manifold when the "
|
||||
"transforms only\n"
|
||||
"rotate by multiples of 90 degrees. This operation can be chained. "
|
||||
"Transforms\n"
|
||||
"are combined and applied lazily.\n"
|
||||
"\n"
|
||||
"axis, then Y, then Z, in degrees. We use degrees so that we can "
|
||||
"minimize rounding error, and eliminate it completely for any "
|
||||
"multiples of 90 degrees. Additionally, more efficient code paths "
|
||||
"are used to update the manifold when the transforms only rotate by "
|
||||
"multiples of 90 degrees. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param x: X rotation in degrees. (default 0.0).\n"
|
||||
":param y: Y rotation in degrees. (default 0.0).\n"
|
||||
":param z: Z rotation in degrees. (default 0.0).")
|
||||
@@ -183,74 +216,143 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
"refine", [](Manifold self, int n) { return self.Refine(n); },
|
||||
py::arg("n"),
|
||||
"Increase the density of the mesh by splitting every edge into n "
|
||||
"pieces. For\n"
|
||||
"instance, with n = 2, each triangle will be split into 4 triangles. "
|
||||
"These\n"
|
||||
"will all be coplanar (and will not be immediately collapsed) unless "
|
||||
"the\n"
|
||||
"Mesh/Manifold has halfedgeTangents specified (e.g. from the "
|
||||
"Smooth()\n"
|
||||
"constructor), in which case the new vertices will be moved to the\n"
|
||||
"interpolated surface according to their barycentric coordinates.\n"
|
||||
"pieces. For instance, with n = 2, each triangle will be split into "
|
||||
"4 triangles. These will all be coplanar (and will not be "
|
||||
"immediately collapsed) unless the Mesh/Manifold has "
|
||||
"halfedgeTangents specified (e.g. from the Smooth() constructor), "
|
||||
"in which case the new vertices will be moved to the interpolated "
|
||||
"surface according to their barycentric coordinates.\n"
|
||||
"\n"
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def("to_mesh", &Manifold::GetMesh)
|
||||
.def_static("smooth", Manifold::Smooth,
|
||||
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def("num_vert", &Manifold::NumVert,
|
||||
"The number of vertices in the Manifold.")
|
||||
.def("num_edge", &Manifold::NumEdge,
|
||||
"The number of edges in the Manifold.")
|
||||
.def("num_tri", &Manifold::NumTri,
|
||||
"The number of triangles in the Manifold.")
|
||||
.def("num_prop", &Manifold::NumProp,
|
||||
"The number of properties per vertex in the Manifold")
|
||||
.def("num_prop_vert", &Manifold::NumPropVert,
|
||||
"The number of property vertices in the Manifold. This will always "
|
||||
"be >= NumVert, as some physical vertices may be duplicated to "
|
||||
"account for different properties on different neighboring "
|
||||
"triangles.")
|
||||
.def("precision", &Manifold::Precision,
|
||||
"Returns the precision of this Manifold's vertices, which tracks "
|
||||
"the approximate rounding error over all the transforms and "
|
||||
"operations that have led to this state. Any triangles that are "
|
||||
"colinear within this precision are considered degenerate and "
|
||||
"removed. This is the value of ε defining "
|
||||
"[ε-valid](https://github.com/elalish/manifold/wiki/"
|
||||
"Manifold-Library#definition-of-%CE%B5-valid).")
|
||||
.def("genus", &Manifold::Genus,
|
||||
"The genus is a topological property of the manifold, representing "
|
||||
"the number of \"handles\". A sphere is 0, torus 1, etc. It is only "
|
||||
"meaningful for a single mesh, so it is best to call Decompose() "
|
||||
"first.")
|
||||
.def(
|
||||
"get_volume",
|
||||
[](Manifold self) { return self.GetProperties().volume; },
|
||||
"Get the volume of the manifold\n This is clamped to zero for a "
|
||||
"given face if they are within the Precision().")
|
||||
.def(
|
||||
"get_surface_area",
|
||||
[](Manifold self) { return self.GetProperties().surfaceArea; },
|
||||
"Get the surface area of the manifold\n This is clamped to zero for "
|
||||
"a given face if they are within the Precision().")
|
||||
.def("original_id", &Manifold::OriginalID,
|
||||
"If this mesh is an original, this returns its meshID that can be "
|
||||
"referenced by product manifolds' MeshRelation. If this manifold is "
|
||||
"a product, this returns -1.")
|
||||
.def("as_original", &Manifold::AsOriginal,
|
||||
"This function condenses all coplanar faces in the relation, and "
|
||||
"collapses those edges. In the process the relation to ancestor "
|
||||
"meshes is lost and this new Manifold is marked an original. "
|
||||
"Properties are preserved, so if they do not match across an edge, "
|
||||
"that edge will be kept.")
|
||||
.def("is_empty", &Manifold::IsEmpty,
|
||||
"Does the Manifold have any triangles?")
|
||||
.def(
|
||||
"decompose", [](Manifold self) { return self.Decompose(); },
|
||||
" This operation returns a vector of Manifolds that are "
|
||||
"topologically disconnected. If everything is connected, the vector "
|
||||
"is length one, containing a copy of the original. It is the inverse "
|
||||
"operation of Compose().")
|
||||
.def("split", &Manifold::Split,
|
||||
"Split cuts this manifold in two using the cutter manifold. The "
|
||||
"first result is the intersection, second is the difference. This "
|
||||
"is more efficient than doing them separately.")
|
||||
.def(
|
||||
"split_by_plane",
|
||||
[](Manifold self, Float3 normal, float originOffset) {
|
||||
return self.SplitByPlane(
|
||||
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
|
||||
originOffset);
|
||||
},
|
||||
py::arg("normal"), py::arg("origin_offset"),
|
||||
"Convenient version of Split() for a half-space."
|
||||
"\n\n"
|
||||
"@param normal This vector is normal to the cutting plane and its "
|
||||
"length does not matter. The first result is in the direction of "
|
||||
"this vector, the second result is on the opposite side.\n"
|
||||
"@param originOffset The distance of the plane from the origin in "
|
||||
"the direction of the normal vector.")
|
||||
.def(
|
||||
"trim_by_plane",
|
||||
[](Manifold self, Float3 normal, float originOffset) {
|
||||
return self.TrimByPlane(
|
||||
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
|
||||
originOffset);
|
||||
},
|
||||
py::arg("normal"), py::arg("origin_offset"),
|
||||
"Identical to SplitByPlane(), but calculating and returning only the "
|
||||
"first result."
|
||||
"\n\n"
|
||||
"@param normal This vector is normal to the cutting plane and its "
|
||||
"length does not matter. The result is in the direction of this "
|
||||
"vector from the plane.\n"
|
||||
"@param originOffset The distance of the plane from the origin in "
|
||||
"the direction of the normal vector.")
|
||||
.def_static(
|
||||
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
|
||||
"Constructs a smooth version of the input mesh by creating tangents; "
|
||||
"this method will throw if you have supplied tangents with your "
|
||||
"mesh already. The actual triangle resolution is unchanged; use the "
|
||||
"Refine() method to interpolate to a higher-resolution curve."
|
||||
"\n\n"
|
||||
"By default, every edge is calculated for maximum smoothness (very "
|
||||
"much approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude. No higher-order derivatives are considered, "
|
||||
"as the interpolation is independent per triangle, only sharing "
|
||||
"constraints on their boundaries."
|
||||
"\n\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector of "
|
||||
"sharpened halfedges, which should in general be a small subset of "
|
||||
"all halfedges. Order of entries doesn't matter, as each one "
|
||||
"specifies the desired smoothness (between zero and one, with one "
|
||||
"the default for all unspecified halfedges) and the halfedge index "
|
||||
"(3 * triangle index + [0,1,2] where 0 is the edge between triVert 0 "
|
||||
"and 1, etc)."
|
||||
"\n\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is interpolated along each edge, so the specified value "
|
||||
"should be thought of as an average. Where exactly two sharpened "
|
||||
"edges meet at a vertex, their tangents are rotated to be colinear "
|
||||
"so that the sharpened edge can be continuous. Vertices with only "
|
||||
"one sharpened edge are completely smooth, allowing sharpened edges "
|
||||
"to smoothly vanish at termination. A single vertex can be sharpened "
|
||||
"by sharping all edges that are incident on it, allowing cones to be "
|
||||
"formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
|
||||
py::arg("mesh"))
|
||||
.def_static(
|
||||
"tetrahedron", []() { return Manifold::Tetrahedron(); },
|
||||
"Constructs a tetrahedron centered at the origin with one vertex at "
|
||||
"(1,1,1)\n"
|
||||
"and the rest at similarly symmetric points.")
|
||||
"(1,1,1) and the rest at similarly symmetric points.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](Float3 size, bool center = false) {
|
||||
@@ -262,9 +364,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
|
||||
py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first\n"
|
||||
"octant, touching the origin.\n"
|
||||
"\n"
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
@@ -278,9 +379,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("size"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first\n"
|
||||
"octant, touching the origin.\n"
|
||||
"\n"
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
@@ -290,9 +390,8 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first\n"
|
||||
"octant, touching the origin.\n"
|
||||
"\n"
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param x: The X dimensions of the box.\n"
|
||||
":param y: The Y dimensions of the box.\n"
|
||||
":param z: The Z dimensions of the box.\n"
|
||||
@@ -300,27 +399,24 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
.def_static(
|
||||
"cylinder",
|
||||
[](float height, float radiusLow, float radiusHigh = -1.0f,
|
||||
int circularSegments = 0) {
|
||||
int circularSegments = 0, bool center = false) {
|
||||
return Manifold::Cylinder(height, radiusLow, radiusHigh,
|
||||
circularSegments);
|
||||
circularSegments, center);
|
||||
},
|
||||
py::arg("height"), py::arg("radius_low"),
|
||||
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
|
||||
py::arg("center") = false,
|
||||
"A convenience constructor for the common case of extruding a "
|
||||
"circle. Can also\n"
|
||||
"form cones if both radii are specified.\n"
|
||||
"\n"
|
||||
"circle. Can also form cones if both radii are specified."
|
||||
"\n\n"
|
||||
":param height: Z-extent\n"
|
||||
":param radiusLow: Radius of bottom circle. Must be positive.\n"
|
||||
":param radiusHigh: Radius of top circle. Can equal zero. Default "
|
||||
"(-1) is equal to\n"
|
||||
"radiusLow.\n"
|
||||
"(-1) is equal to radiusLow.\n"
|
||||
":param circularSegments: How many line segments to use around the "
|
||||
"circle.\n"
|
||||
"Default (-1) is calculated by the static Defaults.\n"
|
||||
"circle. Default (-1) is calculated by the static Defaults.\n"
|
||||
":param center: Set to true to shift the center to the origin. "
|
||||
"Default is\n"
|
||||
"origin at the bottom.")
|
||||
"Default is origin at the bottom.")
|
||||
.def_static(
|
||||
"sphere",
|
||||
[](float radius, int circularSegments = 0) {
|
||||
@@ -330,24 +426,24 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
"Constructs a geodesic sphere of a given radius.\n"
|
||||
"\n"
|
||||
":param radius: Radius of the sphere. Must be positive.\n"
|
||||
":param circularSegments: Number of segments along its\n"
|
||||
"diameter. This number will always be rounded up to the nearest "
|
||||
"factor of\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"This number will always be rounded up to the nearest factor of "
|
||||
"four, as this sphere is constructed by refining an octahedron. This "
|
||||
"means\n"
|
||||
"there are a circle of vertices on all three of the axis planes. "
|
||||
"Default is\n"
|
||||
"calculated by the static Defaults.");
|
||||
"means there are a circle of vertices on all three of the axis "
|
||||
"planes. Default is calculated by the static Defaults.")
|
||||
.def_static("reserve_ids", Manifold::ReserveIDs, py::arg("n"),
|
||||
"Returns the first of n sequential new unique mesh IDs for "
|
||||
"marking sets of triangles that can be looked up after "
|
||||
"further operations. Assign to MeshGL.runOriginalID vector");
|
||||
|
||||
py::class_<PolygonsWrapper>(m, "Polygons")
|
||||
.def(py::init([](std::vector<std::vector<Float2>> &polygons) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
std::vector<PolyVert> vertices(polygons[i].size());
|
||||
std::vector<glm::vec2> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {
|
||||
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
|
||||
j};
|
||||
vertices[j] = {std::get<0>(polygons[i][j]),
|
||||
std::get<1>(polygons[i][j])};
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
@@ -369,23 +465,18 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
py::arg("twist_degrees") = 0.0f,
|
||||
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
"Constructs a manifold from the set of polygons by extruding them "
|
||||
"along the\n"
|
||||
"Z-axis.\n"
|
||||
"along the Z-axis.\n"
|
||||
"\n"
|
||||
":param height: Z-extent of extrusion.\n"
|
||||
":param nDivisions: Number of extra copies of the crossSection to "
|
||||
"insert into\n"
|
||||
"the shape vertically; especially useful in combination with "
|
||||
"twistDegrees to\n"
|
||||
"avoid interpolation artifacts. Default is none.\n"
|
||||
"insert into the shape vertically; especially useful in combination "
|
||||
"with twistDegrees to avoid interpolation artifacts. Default is "
|
||||
"none.\n"
|
||||
":param twistDegrees: Amount to twist the top crossSection relative "
|
||||
"to the\n"
|
||||
"bottom, interpolated linearly for the divisions in between.\n"
|
||||
"to the bottom, interpolated linearly for the divisions in between.\n"
|
||||
":param scaleTop: Amount to scale the top (independently in X and "
|
||||
"Y). If the\n"
|
||||
"scale is (0, 0), a pure cone is formed with only a single vertex at "
|
||||
"the top.\n"
|
||||
"Default (1, 1).")
|
||||
"Y). If the scale is (0, 0), a pure cone is formed with only a "
|
||||
"single vertex at the top. Default (1, 1).")
|
||||
.def(
|
||||
"revolve",
|
||||
[](PolygonsWrapper &self, int circularSegments = 0) {
|
||||
@@ -393,18 +484,13 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
},
|
||||
py::arg("circular_segments") = 0,
|
||||
"Constructs a manifold from the set of polygons by revolving this "
|
||||
"cross-section\n"
|
||||
"around its Y-axis and then setting this as the Z-axis of the "
|
||||
"resulting\n"
|
||||
"manifold. If the polygons cross the Y-axis, only the part on the "
|
||||
"positive X\n"
|
||||
"side is used. Geometrically valid input will result in "
|
||||
"geometrically valid\n"
|
||||
"output.\n"
|
||||
"cross-section around its Y-axis and then setting this as the Z-axis "
|
||||
"of the resulting manifold. If the polygons cross the Y-axis, only "
|
||||
"the part on the positive X side is used. Geometrically valid input "
|
||||
"will result in geometrically valid output.\n"
|
||||
"\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is\n"
|
||||
"calculated by the static Defaults.");
|
||||
"Default is calculated by the static Defaults.");
|
||||
|
||||
py::class_<Mesh>(m, "Mesh")
|
||||
.def(py::init([](py::array_t<float> &vertPos, py::array_t<int> &triVerts,
|
||||
@@ -501,4 +587,152 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
halfedge_tangent_view(i, j) = self.halfedgeTangent[i][j];
|
||||
return halfedge_tangent;
|
||||
});
|
||||
|
||||
py::enum_<CrossSection::FillRule>(m, "FillRule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
|
||||
"Only odd numbered sub-regions are filled.")
|
||||
.value("NonZero", CrossSection::FillRule::NonZero,
|
||||
"Only non-zero sub-regions are filled.")
|
||||
.value("Positive", CrossSection::FillRule::Positive,
|
||||
"Only sub-regions with winding counts > 0 are filled.")
|
||||
.value("Negative", CrossSection::FillRule::Negative,
|
||||
"Only sub-regions with winding counts < 0 are filled.");
|
||||
|
||||
py::enum_<CrossSection::JoinType>(m, "JoinType")
|
||||
.value("Square", CrossSection::JoinType::Square,
|
||||
"Squaring is applied uniformly at all joins where the internal "
|
||||
"join angle is less that 90 degrees. The squared edge will be at "
|
||||
"exactly the offset distance from the join vertex.")
|
||||
.value(
|
||||
"Round", CrossSection::JoinType::Square,
|
||||
"Rounding is applied to all joins that have convex external angles, "
|
||||
"and it maintains the exact offset distance from the join vertex.")
|
||||
.value(
|
||||
"Miter", CrossSection::JoinType::Miter,
|
||||
"There's a necessary limit to mitered joins (to avoid narrow angled "
|
||||
"joins producing excessively long and narrow "
|
||||
"[spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/"
|
||||
"Classes/ClipperOffset/Properties/MiterLimit.htm)). So where mitered "
|
||||
"joins would exceed a given maximum miter distance (relative to the "
|
||||
"offset distance), these are 'squared' instead.");
|
||||
|
||||
py::class_<CrossSection>(
|
||||
m, "CrossSection",
|
||||
"Two-dimensional cross sections guaranteed to be without "
|
||||
"self-intersections, or overlaps between polygons (from construction "
|
||||
"onwards). This class makes use of the "
|
||||
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
|
||||
"for polygon clipping (boolean) and offsetting operations.")
|
||||
.def(py::init<>())
|
||||
.def(py::init(
|
||||
[](PolygonsWrapper &contours, CrossSection::FillRule fillrule) {
|
||||
return CrossSection(*contours.polygons, fillrule);
|
||||
}),
|
||||
py::arg("contours"),
|
||||
py::arg("fillrule") = CrossSection::FillRule::Positive)
|
||||
.def("area", &CrossSection::Area)
|
||||
.def("num_vert", &CrossSection::NumVert)
|
||||
.def("num_contour", &CrossSection::NumContour)
|
||||
.def("is_empty", &CrossSection::IsEmpty)
|
||||
.def("translate",
|
||||
[](CrossSection self, Float2 v) {
|
||||
return self.Translate({std::get<0>(v), std::get<1>(v)});
|
||||
})
|
||||
.def("rotate", &CrossSection::Rotate)
|
||||
.def("scale",
|
||||
[](CrossSection self, Float2 s) {
|
||||
return self.Scale({std::get<0>(s), std::get<1>(s)});
|
||||
})
|
||||
.def("mirror",
|
||||
[](CrossSection self, Float2 ax) {
|
||||
return self.Mirror({std::get<0>(ax), std::get<1>(ax)});
|
||||
})
|
||||
.def("transform",
|
||||
[](CrossSection self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat_view.shape(0) != 2 || mat_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid matrix shape");
|
||||
glm::mat3x2 mat_glm;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
})
|
||||
.def(
|
||||
"warp",
|
||||
[](CrossSection self, const std::function<Float2(Float2)> &f) {
|
||||
return self.Warp([&f](glm::vec2 &v) {
|
||||
Float2 fv = f(std::make_tuple(v.x, v.y));
|
||||
v.x = std::get<0>(fv);
|
||||
v.y = std::get<1>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"))
|
||||
.def("simplify", &CrossSection::Simplify)
|
||||
.def("offset", &CrossSection::Offset, py::arg("delta"),
|
||||
py::arg("join_type"), py::arg("miter_limit") = 2.0,
|
||||
py::arg("arc_tolerance") = 0.0)
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def("decompose", &CrossSection::Decompose)
|
||||
.def("to_polygons",
|
||||
[](CrossSection self) {
|
||||
return PolygonsWrapper{
|
||||
std::make_unique<Polygons>(self.ToPolygons())};
|
||||
})
|
||||
.def(
|
||||
"extrude",
|
||||
[](CrossSection self, float height, int nDivisions = 0,
|
||||
float twistDegrees = 0.0f,
|
||||
Float2 scaleTop = std::make_tuple(1.0f, 1.0f)) {
|
||||
glm::vec2 scaleTopVec(std::get<0>(scaleTop), std::get<1>(scaleTop));
|
||||
return Manifold::Extrude(self, height, nDivisions, twistDegrees,
|
||||
scaleTopVec);
|
||||
},
|
||||
py::arg("height"), py::arg("n_divisions") = 0,
|
||||
py::arg("twist_degrees") = 0.0f,
|
||||
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
"Constructs a manifold from the set of polygons by extruding them "
|
||||
"along the Z-axis.\n"
|
||||
"\n"
|
||||
":param height: Z-extent of extrusion.\n"
|
||||
":param nDivisions: Number of extra copies of the crossSection to "
|
||||
"insert into the shape vertically; especially useful in combination "
|
||||
"with twistDegrees to avoid interpolation artifacts. Default is "
|
||||
"none.\n"
|
||||
":param twistDegrees: Amount to twist the top crossSection relative "
|
||||
"to the bottom, interpolated linearly for the divisions in between.\n"
|
||||
":param scaleTop: Amount to scale the top (independently in X and "
|
||||
"Y). If the scale is (0, 0), a pure cone is formed with only a "
|
||||
"single vertex at the top. Default (1, 1).")
|
||||
.def(
|
||||
"revolve",
|
||||
[](CrossSection self, int circularSegments = 0) {
|
||||
return Manifold::Revolve(self, circularSegments);
|
||||
},
|
||||
py::arg("circular_segments") = 0,
|
||||
"Constructs a manifold from the set of polygons by revolving this "
|
||||
"cross-section around its Y-axis and then setting this as the Z-axis "
|
||||
"of the resulting manifold. If the polygons cross the Y-axis, only "
|
||||
"the part on the positive X side is used. Geometrically valid input "
|
||||
"will result in geometrically valid output.\n"
|
||||
"\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is calculated by the static Defaults.")
|
||||
.def_static(
|
||||
"square",
|
||||
[](Float2 dims, bool center) {
|
||||
return CrossSection::Square({std::get<0>(dims), std::get<1>(dims)},
|
||||
center);
|
||||
},
|
||||
py::arg("dims"), py::arg("center") = false)
|
||||
.def_static(
|
||||
"circle",
|
||||
[](float radius, int circularSegments) {
|
||||
return CrossSection::Circle(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circularSegments") = 0);
|
||||
}
|
||||
|
||||
@@ -19,12 +19,12 @@ add_executable(manifoldjs bindings.cpp)
|
||||
# make sure that we recompile the wasm when bindings.js is being modified
|
||||
set_source_files_properties(bindings.cpp OBJECT_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
|
||||
target_link_libraries(manifoldjs manifold sdf)
|
||||
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
|
||||
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1)
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_14)
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -34,5 +34,5 @@ add_custom_command(
|
||||
TARGET manifoldjs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold-*.d.ts
|
||||
${CMAKE_CURRENT_BINARY_DIR}/examples/)
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
using namespace manifold;
|
||||
@@ -29,65 +32,100 @@ Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
|
||||
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
|
||||
|
||||
Manifold UnionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::ADD);
|
||||
;
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Add);
|
||||
}
|
||||
|
||||
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::SUBTRACT);
|
||||
;
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Subtract);
|
||||
}
|
||||
|
||||
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::INTERSECT);
|
||||
;
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Intersect);
|
||||
}
|
||||
|
||||
std::vector<SimplePolygon> ToPolygon(
|
||||
std::vector<std::vector<glm::vec2>>& polygons) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
std::vector<PolyVert> vertices(polygons[i].size());
|
||||
std::vector<glm::vec2> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {polygons[i][j], j};
|
||||
vertices[j] = polygons[i][j];
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return simplePolygons;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold) {
|
||||
MeshGL mesh = manifold.GetMeshGL();
|
||||
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
|
||||
MeshGL mesh = manifold.GetMeshGL(normalIdx);
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("numProp", mesh.numProp);
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertPos",
|
||||
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
|
||||
meshJS.set("vertProperties",
|
||||
val(typed_memory_view(mesh.vertProperties.size(),
|
||||
mesh.vertProperties.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
|
||||
mesh.mergeFromVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
|
||||
mesh.mergeToVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runIndex",
|
||||
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
|
||||
mesh.runOriginalID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("faceID",
|
||||
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
|
||||
mesh.vertNormal.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
|
||||
mesh.runTransform.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.numProp = mesh["numProp"].as<int>();
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
|
||||
if (mesh["vertNormal"] != val::undefined()) {
|
||||
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
|
||||
out.vertProperties =
|
||||
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
|
||||
if (mesh["mergeFromVert"] != val::undefined()) {
|
||||
out.mergeFromVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
|
||||
}
|
||||
if (mesh["mergeToVert"] != val::undefined()) {
|
||||
out.mergeToVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
|
||||
}
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["runOriginalID"] != val::undefined()) {
|
||||
out.runOriginalID =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
if (mesh["runTransform"] != val::undefined()) {
|
||||
out.runTransform =
|
||||
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -109,10 +147,11 @@ Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
|
||||
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, std::vector<float>& mat) {
|
||||
Manifold Transform(Manifold& manifold, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat4x3 matrix;
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
|
||||
for (const int col : {0, 1, 2, 3})
|
||||
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
|
||||
return manifold.Transform(matrix);
|
||||
}
|
||||
|
||||
@@ -151,17 +190,20 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("w", &glm::vec4::w);
|
||||
|
||||
enum_<Manifold::Error>("status")
|
||||
.value("NO_ERROR", Manifold::Error::NO_ERROR)
|
||||
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
|
||||
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
|
||||
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
|
||||
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
|
||||
.value("PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
|
||||
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
@@ -173,16 +215,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
value_object<BaryRef>("baryRef")
|
||||
.field("meshID", &BaryRef::meshID)
|
||||
.field("originalID", &BaryRef::originalID)
|
||||
.field("tri", &BaryRef::tri)
|
||||
.field("vertBary", &BaryRef::vertBary);
|
||||
|
||||
value_object<MeshRelation>("meshRelation")
|
||||
.field("barycentric", &MeshRelation::barycentric)
|
||||
.field("triBary", &MeshRelation::triBary);
|
||||
|
||||
value_object<Curvature>("curvature")
|
||||
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
|
||||
.field("minMeanCurvature", &Curvature::minMeanCurvature)
|
||||
@@ -198,7 +230,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<BaryRef>("Vector_baryRef");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
@@ -206,14 +237,17 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("add", &Union)
|
||||
.function("subtract", &Difference)
|
||||
.function("intersect", &Intersection)
|
||||
.function("_TrimByPlane", &Manifold::TrimByPlane)
|
||||
.function("_GetMeshJS", &GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("_Warp", &Warp)
|
||||
.function("_Transform", &Transform)
|
||||
.function("transform", &Transform)
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
.function("_Rotate", &Manifold::Rotate)
|
||||
.function("_Scale", &Manifold::Scale)
|
||||
.function("_Decompose", &Manifold::Decompose)
|
||||
.function("_Mirror", &Manifold::Mirror)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
@@ -225,8 +259,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("_getCurvature", &Manifold::GetCurvature)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal)
|
||||
.function("_getMeshRelation", &Manifold::GetMeshRelation);
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
function("_Cube", &Manifold::Cube);
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
@@ -234,6 +267,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &Smooth);
|
||||
function("_Extrude", &Extrude);
|
||||
function("_Triangulate", &Triangulate);
|
||||
function("_Revolve", &Revolve);
|
||||
function("_LevelSet", &LevelSetJs);
|
||||
|
||||
@@ -242,8 +276,9 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("_intersectionN", &IntersectionN);
|
||||
function("_Compose", &Manifold::Compose);
|
||||
|
||||
function("setMinCircularAngle", &Manifold::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Manifold::SetCircularSegments);
|
||||
function("getCircularSegments", &Manifold::GetCircularSegments);
|
||||
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Quality::SetCircularSegments);
|
||||
function("getCircularSegments", &Quality::GetCircularSegments);
|
||||
function("reserveIDs", &Manifold::ReserveIDs);
|
||||
}
|
||||
|
||||
@@ -75,19 +75,6 @@ Module.setup = function() {
|
||||
return out;
|
||||
};
|
||||
|
||||
// note that the matrix is using column major (same as glm)
|
||||
Module.Manifold.prototype.transform = function(mat) {
|
||||
console.assert(mat.length == 4, 'expects a 3x4 matrix');
|
||||
const vec = new Module.Vector_f32();
|
||||
for (let col of mat) {
|
||||
console.assert(col.length == 3, 'expects a 3x4 matrix');
|
||||
for (let x of col) mat.push_back(x);
|
||||
}
|
||||
const result = this._Transform(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec(vec));
|
||||
};
|
||||
@@ -103,6 +90,14 @@ Module.setup = function() {
|
||||
return this._Scale(vararg2vec([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.trimByPlane = function(normal, offset) {
|
||||
return this._TrimByPlane(vararg2vec([normal]), offset);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
@@ -123,15 +118,27 @@ Module.setup = function() {
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
triVerts = new Uint32Array(),
|
||||
vertPos = new Float32Array(),
|
||||
vertNormal,
|
||||
halfedgeTangent
|
||||
vertProperties = new Float32Array(),
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent,
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
this.vertPos = vertPos;
|
||||
this.vertNormal = vertNormal;
|
||||
this.vertProperties = vertProperties;
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.runOriginalID = runOriginalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
@@ -139,7 +146,11 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertPos.length / 3;
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
get numRun() {
|
||||
return this.runOriginalID.length;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
@@ -147,43 +158,36 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
|
||||
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
|
||||
}
|
||||
|
||||
normal(vert) {
|
||||
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
numProp * vert + 3, numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
|
||||
transform(run) {
|
||||
const mat4 = new Array(16);
|
||||
for (const col of [0, 1, 2, 3]) {
|
||||
for (const row of [0, 1, 2]) {
|
||||
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
|
||||
}
|
||||
}
|
||||
mat4[15] = 1;
|
||||
return mat4;
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function() {
|
||||
return new Mesh(this._GetMeshJS());
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getMeshRelation = function() {
|
||||
const result = this._getMeshRelation();
|
||||
const oldBarycentric = result.barycentric;
|
||||
const oldTriBary = result.triBary;
|
||||
const conversion1 = v => [v.x, v.y, v.z];
|
||||
const conversion2 = v => [v[0], v[1], v[2]];
|
||||
const conversion3 = v => {
|
||||
return {
|
||||
meshID: v.meshID,
|
||||
originalID: v.originalID,
|
||||
tri: v.tri,
|
||||
vertBary: conversion2(v.vertBary)
|
||||
};
|
||||
};
|
||||
result.barycentric = fromVec(oldBarycentric, conversion1);
|
||||
result.triBary = fromVec(oldTriBary, conversion3);
|
||||
oldBarycentric.delete();
|
||||
oldTriBary.delete();
|
||||
return result;
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
|
||||
if (normalIdx instanceof Array)
|
||||
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
@@ -197,23 +201,37 @@ Module.setup = function() {
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
case Module.status.NON_FINITE_VERTEX.value:
|
||||
case Module.status.NonFiniteVertex.value:
|
||||
message = 'Non-finite vertex';
|
||||
break;
|
||||
case Module.status.NOT_MANIFOLD.value:
|
||||
case Module.status.NotManifold.value:
|
||||
message = 'Not manifold';
|
||||
break;
|
||||
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
|
||||
case Module.status.VertexOutOfBounds.value:
|
||||
message = 'Vertex index out of bounds';
|
||||
break;
|
||||
case Module.status.PROPERTIES_WRONG_LENGTH.value:
|
||||
case Module.status.PropertiesWrongLength.value:
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Tri properties have wrong length';
|
||||
case Module.status.MissingPositionProperties.value:
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
|
||||
message = 'Tri properties out of bounds';
|
||||
case Module.status.MergeVectorsDifferentLengths.value:
|
||||
message = 'Merge vectors have different lengths';
|
||||
break;
|
||||
case Module.status.MergeIndexOutOfBounds.value:
|
||||
message = 'Merge index out of bounds';
|
||||
break;
|
||||
case Module.status.TransformWrongLength.value:
|
||||
message = 'Transform vector has wrong length';
|
||||
break;
|
||||
case Module.status.RunIndexWrongLength.value:
|
||||
message = 'Run index vector has wrong length';
|
||||
break;
|
||||
case Module.status.FaceIDWrongLength.value:
|
||||
message = 'Face ID vector has wrong length';
|
||||
case Module.status.InvalidConstruction.value:
|
||||
message = 'Manifold constructed with invalid parameters';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -284,6 +302,13 @@ Module.setup = function() {
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.triangulate = function(polygons, precision = -1) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = fromVec(Module._Triangulate(polygonsVec, precision), (x) => [x[0], x[1], x[2]]);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
}
|
||||
|
||||
Module.revolve = function(polygons, circularSegments = 0) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Revolve(polygonsVec, circularSegments);
|
||||
@@ -324,7 +349,7 @@ Module.setup = function() {
|
||||
const result = Module['_' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Module.union = batchbool('union');
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
manifold*
|
||||
@@ -128,7 +128,7 @@ model-viewer {
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.margin {
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
@@ -186,6 +186,16 @@ model-viewer {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.edit {
|
||||
background-image: url(icons/pencil.png);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
background-image: url(icons/trash.png);
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {examples} from './examples.js';
|
||||
const exampleFunctions = examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register('service-worker.js');
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
@@ -115,8 +120,7 @@ function uniqueName(name) {
|
||||
function addEdit(button) {
|
||||
const label = button.firstChild;
|
||||
const edit = addIcon(button);
|
||||
edit.style.backgroundImage = 'url(icons/pencil.png)';
|
||||
edit.style.right = '30px';
|
||||
edit.classList.add('edit');
|
||||
|
||||
edit.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
@@ -156,8 +160,7 @@ function addEdit(button) {
|
||||
};
|
||||
|
||||
const trash = addIcon(button);
|
||||
trash.style.backgroundImage = 'url(icons/trash.png)';
|
||||
trash.style.right = '0px';
|
||||
trash.classList.add('trash');
|
||||
let lastClick = 0;
|
||||
|
||||
trash.onclick = function(event) {
|
||||
@@ -210,14 +213,52 @@ function initializeRun() {
|
||||
|
||||
// Editor ------------------------------------------------------------
|
||||
let tsWorker = undefined;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('manifold-encapsulated-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
return `
|
||||
${global.replaceAll('export', '')}
|
||||
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
|
||||
declare interface ManifoldStatic {
|
||||
cube: typeof cube;
|
||||
cylinder: typeof cylinder;
|
||||
sphere: typeof sphere;
|
||||
smooth: typeof smooth;
|
||||
tetrahedron: typeof tetrahedron;
|
||||
extrude: typeof extrude;
|
||||
revolve: typeof revolve;
|
||||
union: typeof union;
|
||||
difference: typeof difference;
|
||||
intersection: typeof intersection;
|
||||
compose: typeof compose;
|
||||
levelSet: typeof levelSet;
|
||||
setMinCircularAngle: typeof setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof setCircularSegments;
|
||||
getCircularSegments: typeof getCircularSegments;
|
||||
reserveIDs: typeof reserveIDs;
|
||||
}
|
||||
declare const module: ManifoldStatic;
|
||||
`;
|
||||
}
|
||||
|
||||
require.config({
|
||||
paths:
|
||||
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
|
||||
});
|
||||
require(['vs/editor/editor.main'], async function() {
|
||||
const content =
|
||||
await fetch('manifold.d.ts').then(response => response.text());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
|
||||
async function addTypes(uri) {
|
||||
const content = await fetch(uri).then(response => response.text());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
|
||||
}
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getManifoldDTS());
|
||||
await addTypes('editor.d.ts');
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
@@ -309,7 +350,7 @@ let objectURL = null;
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new Worker('worker.js');
|
||||
manifoldWorker = new Worker('worker.js', {type: 'module'});
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
|
||||
@@ -12,429 +12,423 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
(function(exports) {
|
||||
export const examples = {
|
||||
functions: {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the
|
||||
// API docs. Type e.g. "box." to see the Manifold API. Type "module." to
|
||||
// see the static API - these functions can also be used bare. Use
|
||||
// console.log() to print output (lower-right). This editor defines Z as
|
||||
// up and units of mm.
|
||||
|
||||
exports.functions = {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the API
|
||||
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
|
||||
// static API - these functions can also be used bare. Use console.log() to
|
||||
// print output (lower-right). This editor defines Z as up and units of mm.
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
// For visual debug, wrap any shape with show() and it and all of its
|
||||
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
|
||||
// only() to ghost out everything else, akin to * in OpenSCAD.
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This app is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// To share your code with another browser/device, simply copy the text to a
|
||||
// file.
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This PWA is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// Consider installing it (icon in the search bar) for easy access.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples.
|
||||
return result;
|
||||
},
|
||||
// See the script drop-down above ("Intro") for usage examples. The
|
||||
// gl-matrix package from npm is automatically imported for convenience -
|
||||
// its API is available in the top-level glMatrix object.
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as a
|
||||
// puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as
|
||||
// a puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions =
|
||||
50; // Number of divisions (both ways) in the screw surface.
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions = 50; // Divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = tetrahedron().scale(scale);
|
||||
const tet = tetrahedron().scale(scale);
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
let edge1 = union(corner, edge);
|
||||
edge1 = edge1.rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
let edge2 = edge1.rotate([0, 0, 180]);
|
||||
edge2 = union(edge2, edge1);
|
||||
edge2 =
|
||||
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
let edge4 = edge2.rotate([0, 0, 90]);
|
||||
edge4 = union(edge4, edge2);
|
||||
|
||||
let frame = edge4.translate([0, 0, -edgeLength / 2]);
|
||||
frame = union(frame, frame.rotate([180, 0, 0]));
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function. This
|
||||
// example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with smooth()
|
||||
// and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
vertPos.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const scallop = new Mesh();
|
||||
scallop.triVerts = Uint32Array.from(triVerts);
|
||||
scallop.vertPos = Float32Array.from(vertPos);
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common factor
|
||||
// then you will get multiple separate, interwoven knots. This is an example
|
||||
// of using the warp() method, thus avoiding any direct handling of
|
||||
// triangles.
|
||||
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
const offset = 2;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
const point = new THREE.Vector3();
|
||||
const axis = new THREE.Vector3();
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
const func =
|
||||
(v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
point.set(Math.cos(phi), 0, Math.sin(phi))
|
||||
.multiplyScalar(threadRadius);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
point.applyAxisAngle(
|
||||
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
|
||||
point.x += minorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 1, 0), theta);
|
||||
point.x += majorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 0, 1), psi);
|
||||
v[0] = point.x;
|
||||
v[1] = point.y;
|
||||
v[2] = point.z;
|
||||
}
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df =
|
||||
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
knot = compose(knots);
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with
|
||||
// smooth() and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
positions.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
const triVerts = Uint32Array.from(triangles);
|
||||
const vertProperties = Float32Array.from(positions);
|
||||
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common
|
||||
// factor then you will get multiple separate, interwoven knots. This is
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
|
||||
[0.0, -w]
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
|
||||
}
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
const offset = 2;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
}
|
||||
|
||||
const hole = compose(holes);
|
||||
const func = (v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(p * minorRadius, q * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
|
||||
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
|
||||
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
|
||||
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).trimByPlane([1, 1, 1], 0).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const o = vec2.fromValues(0, 0);
|
||||
const p0 = vec2.fromValues(outerRadius, 0);
|
||||
const p1 = vec2.fromValues(innerRadius, -cut);
|
||||
const p2 = vec2.fromValues(innerRadius, cut);
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
}
|
||||
b = intersection(extrude(stretch, width), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
|
||||
// use of a Signed Distance Function (SDF) to create smooth, complex
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
const size = 20;
|
||||
const n = 20;
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4));
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result =
|
||||
result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
function rotate(v, theta) {
|
||||
return [
|
||||
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
|
||||
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
|
||||
];
|
||||
}
|
||||
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor =
|
||||
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
|
||||
0, radius, 0
|
||||
]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const p0 = [outerRadius, 0];
|
||||
const p1 = [innerRadius, -cut];
|
||||
const p2 = [innerRadius, cut];
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
stretch.push(rotate(p1, dPhiRad * i));
|
||||
stretch.push(rotate(p2, dPhiRad * i));
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
}
|
||||
b = intersection(extrude(stretch, width), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
|
||||
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
|
||||
|
||||
const size = 20;
|
||||
const n = 20;
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: [-period, -period, -period],
|
||||
max: [period, period, period]
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
exports.functionBodies = new Map();
|
||||
|
||||
for (const [func, code] of Object.entries(exports.functions)) {
|
||||
for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first four leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
exports.functionBodies.set(name, body);
|
||||
};
|
||||
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
|
||||
examples.functionBodies.set(name, body);
|
||||
};
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 271 KiB |
@@ -5,15 +5,36 @@
|
||||
<title>ManifoldCAD</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
<link type="text/css" href="editor.css" rel="stylesheet" />
|
||||
<meta name="description"
|
||||
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
|
||||
<meta name="theme-color" content="#A288B3">
|
||||
<meta property="og:title" content="ManifoldCAD">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://manifoldcad.org/" />
|
||||
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
|
||||
<meta property="og:image:width" content="850">
|
||||
<meta property="og:image:height" content="850">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link type="text/css" href="editor.css" rel="stylesheet">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "ManifoldCAD",
|
||||
"applicationCategory": "DesignApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="background-color: rgb(162 136 179);">
|
||||
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
</p>
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
@@ -30,9 +51,7 @@
|
||||
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;">
|
||||
Loading...
|
||||
</p>
|
||||
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="console"></div>
|
||||
@@ -40,17 +59,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
|
||||
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script src="examples.js"></script>
|
||||
<script src="editor.js"></script>
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ManifoldCAD",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/mengerSponge192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/icons/mengerSponge512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"background_color": "#FFFFFF",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"scope": "/",
|
||||
"theme_color": "#A288B3",
|
||||
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
|
||||
"categories": [
|
||||
"design",
|
||||
"developer",
|
||||
"education",
|
||||
"graphics",
|
||||
"productivity"
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/icons/manifoldCAD.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
},
|
||||
{
|
||||
"src": "/icons/manifoldCADonly.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -37,9 +36,10 @@
|
||||
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
href="https://modelviewer.dev/"><model-viewer></a>
|
||||
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
|
||||
</p>
|
||||
href="https://modelviewer.dev/"><model-viewer></a> via <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
|
||||
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
|
||||
three.js example is <a href="three.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
@@ -47,80 +47,220 @@
|
||||
</select>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { Accessor, Document, WebIO } from 'https://cdn.skypack.dev/pin/@gltf-transform/core@v3.0.0-SfbIFhNPTRdr1UE2VSan/mode=imports,min/optimized/@gltf-transform/core.js';
|
||||
import Module from '../manifold.js';
|
||||
|
||||
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
|
||||
<script src="https://unpkg.com/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://unpkg.com/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="https://unpkg.com/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
|
||||
color: 'yellow',
|
||||
metalness: 1,
|
||||
roughness: 0.2
|
||||
}));
|
||||
const io = new WebIO();
|
||||
const doc = new Document();
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const buffer = doc.createBuffer();
|
||||
const position = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const uv = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC2);
|
||||
const moonIndices = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const spaceIndices = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const moonTexture = doc.createTexture('moon');
|
||||
const spaceTexture = doc.createTexture('space');
|
||||
const moonMaterial = doc.createMaterial('moon').setBaseColorTexture(moonTexture);
|
||||
const spaceMaterial = doc.createMaterial('moon').setBaseColorTexture(spaceTexture);
|
||||
const moonPrimitive = doc.createPrimitive().setMaterial(moonMaterial)
|
||||
.setIndices(moonIndices).setAttribute('POSITION', position).setAttribute('TEXCOORD_0', uv);
|
||||
const spacePrimitive = doc.createPrimitive().setMaterial(spaceMaterial)
|
||||
.setIndices(spaceIndices).setAttribute('POSITION', position).setAttribute('TEXCOORD_0', uv);
|
||||
const mesh = doc.createMesh('result').addPrimitive(moonPrimitive).addPrimitive(spacePrimitive);
|
||||
const node = doc.createNode().setMesh(mesh);
|
||||
const scene = doc.createScene().addChild(node);
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
push2MV(mesh);
|
||||
};
|
||||
async function loadTexture(texture, uri) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
async function loadTextures() {
|
||||
const moonLoads = loadTexture(moonTexture, './moonAltitude.jpg');
|
||||
const spaceLoads = loadTexture(spaceTexture, './hubbleDeepField.jpg');
|
||||
await Promise.all([moonLoads, spaceLoads]);
|
||||
}
|
||||
|
||||
csg('difference');
|
||||
});
|
||||
const texturesLoad = loadTextures();
|
||||
const materialMap = new Map();
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const firstID = wasm.reserveIDs(2);
|
||||
const space = cubeUV(firstID).scale(50);
|
||||
const moon = sphereUV(firstID + 1).scale(60);
|
||||
materialMap.set(firstID, spaceIndices);
|
||||
materialMap.set(firstID + 1, moonIndices);
|
||||
|
||||
const csg = function (operation) {
|
||||
push2MV(wasm[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
|
||||
function push2MV(mesh) {
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
},
|
||||
() => console.log('GLTF export failed!'),
|
||||
{ binary: true }
|
||||
);
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
for (const [i, id] of mesh.runOriginalID.entries()) {
|
||||
const indices = materialMap.get(id);
|
||||
indices.setArray(mesh.triVerts.subarray(mesh.runIndex[i], mesh.runIndex[i + 1]));
|
||||
}
|
||||
|
||||
const numVert = mesh.numVert;
|
||||
const numProp = mesh.numProp;
|
||||
const posArray = new Float32Array(3 * numVert);
|
||||
const uvArray = new Float32Array(2 * numVert);
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
posArray[3 * i] = mesh.vertProperties[numProp * i];
|
||||
posArray[3 * i + 1] = mesh.vertProperties[numProp * i + 1];
|
||||
posArray[3 * i + 2] = mesh.vertProperties[numProp * i + 2];
|
||||
uvArray[2 * i] = mesh.vertProperties[numProp * i + 3];
|
||||
uvArray[2 * i + 1] = mesh.vertProperties[numProp * i + 4];
|
||||
}
|
||||
position.setArray(posArray);
|
||||
uv.setArray(uvArray);
|
||||
|
||||
await texturesLoad;
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
}
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
function cubeUV(id) {
|
||||
const numProp = 5;
|
||||
const numTri = 12;
|
||||
const numVert = 8;
|
||||
const numPropVert = 24;
|
||||
const vertPos =
|
||||
[-1, -1, -1,
|
||||
1, -1, -1,
|
||||
1, 1, -1,
|
||||
-1, 1, -1,
|
||||
-1, -1, 1,
|
||||
1, -1, 1,
|
||||
1, 1, 1,
|
||||
-1, 1, 1];
|
||||
const uvs = [0, 0, 0, 1, 1, 1, 1, 0];
|
||||
const triVerts = new Uint32Array(
|
||||
[0, 3, 2, 0, 2, 1,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
3, 0, 4, 3, 4, 7]);
|
||||
const v4 = [0, 1, 2, 5];
|
||||
const v6 = [0, 1, 2, 0, 2, 3];
|
||||
const vertProperties = new Float32Array(numProp * numPropVert);
|
||||
const mergeFromVert = new Uint32Array(numPropVert - numVert);
|
||||
const mergeToVert = new Uint32Array(numPropVert - numVert);
|
||||
const runOriginalID = new Uint32Array(1);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
|
||||
runOriginalID[0] = id;
|
||||
const vert2vert = [-1, -1, -1, -1, -1, -1, -1, -1];
|
||||
let idx = 0;
|
||||
for (let face = 0; face < 6; ++face) {
|
||||
for (let v = 0; v < 4; ++v) {
|
||||
const vIdx = 4 * face + v;
|
||||
const vOld = triVerts[6 * face + v4[v]];
|
||||
if (vert2vert[vOld] === -1) {
|
||||
vert2vert[vOld] = vIdx;
|
||||
} else {
|
||||
mergeFromVert[idx] = vIdx;
|
||||
mergeToVert[idx] = vert2vert[vOld];
|
||||
++idx;
|
||||
}
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
vertProperties[numProp * vIdx + i] = vertPos[3 * vOld + i];
|
||||
}
|
||||
vertProperties[numProp * vIdx + 3] = uvs[2 * v];
|
||||
vertProperties[numProp * vIdx + 4] = uvs[2 * v + 1];
|
||||
}
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
triVerts[6 * face + i] = 4 * face + v6[i];
|
||||
}
|
||||
}
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
function sphereUV(id) {
|
||||
const n = 100;
|
||||
const tol = 1 / (2 * n);
|
||||
const mesh = wasm.sphere(1, n).getMesh();
|
||||
const nMerge = n / 2 - 1;
|
||||
const numVert = mesh.numVert;
|
||||
const numTri = mesh.numTri;
|
||||
const numPropIn = mesh.numProp;
|
||||
const numProp = 5;
|
||||
const triVerts = new Uint32Array(3 * numTri);
|
||||
const vertProperties = new Float32Array(numProp * (numVert + nMerge));
|
||||
const mergeFromVert = new Uint32Array(nMerge);
|
||||
const mergeToVert = new Uint32Array(nMerge);
|
||||
const runOriginalID = new Uint32Array(1);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
|
||||
runOriginalID[0] = id;
|
||||
const vert2vert = new Uint32Array(numVert);
|
||||
let idx = 0;
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
const x = mesh.vertProperties[numPropIn * i];
|
||||
const y = mesh.vertProperties[numPropIn * i + 1];
|
||||
const z = mesh.vertProperties[numPropIn * i + 2];
|
||||
vertProperties[numProp * i] = x;
|
||||
vertProperties[numProp * i + 1] = y;
|
||||
vertProperties[numProp * i + 2] = z;
|
||||
const u = Math.atan2(y, x) / (2 * Math.PI) + 0.5;
|
||||
const v = Math.acos(z) / Math.PI;
|
||||
vertProperties[numProp * i + 4] = v;
|
||||
if (Math.abs(y) < tol && x < -tol) {// Seam
|
||||
const iNew = numVert + idx;
|
||||
mergeFromVert[idx] = iNew;
|
||||
mergeToVert[idx] = i;
|
||||
vert2vert[i] = iNew;
|
||||
vertProperties[numProp * iNew] = x;
|
||||
vertProperties[numProp * iNew + 1] = y;
|
||||
vertProperties[numProp * iNew + 2] = z;
|
||||
vertProperties[numProp * iNew + 3] = 1;
|
||||
vertProperties[numProp * iNew + 4] = v;
|
||||
vertProperties[numProp * i + 3] = 0;
|
||||
++idx;
|
||||
} else {
|
||||
vert2vert[i] = i;
|
||||
vertProperties[numProp * i + 3] = u;
|
||||
}
|
||||
}
|
||||
for (let tri = 0; tri < numTri; ++tri) {
|
||||
const v0 = mesh.triVerts[3 * tri];
|
||||
const v1 = mesh.triVerts[3 * tri + 1];
|
||||
const v2 = mesh.triVerts[3 * tri + 2];
|
||||
if ((vert2vert[v0] !== v0 || vert2vert[v1] !== v1 || vert2vert[v2] !== v2) &&
|
||||
(vertProperties[numProp * v0 + 1] > tol || vertProperties[numProp * v1 + 1] > tol ||
|
||||
vertProperties[numProp * v2 + 1] > tol)) {
|
||||
triVerts[3 * tri] = vert2vert[v0];
|
||||
triVerts[3 * tri + 1] = vert2vert[v1];
|
||||
triVerts[3 * tri + 2] = vert2vert[v2];
|
||||
} else {
|
||||
triVerts[3 * tri] = v0;
|
||||
triVerts[3 * tri + 1] = v1;
|
||||
triVerts[3 * tri + 2] = v2;
|
||||
}
|
||||
}
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Increment version when updating CDN URLs to clean up cache.
|
||||
const cacheName = 'manifoldCAD-cache-v1';
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(caches.keys().then((keyList) => {
|
||||
return Promise.all(keyList.map((key) => {
|
||||
if (key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
// Serves from cache, then updates the cache in the background from the network,
|
||||
// if available. Update available on refresh.
|
||||
self.addEventListener(
|
||||
'fetch',
|
||||
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
|
||||
const networkFetch = fetch(e.request).then(response => {
|
||||
const clone = response.clone();
|
||||
caches.open(cacheName).then(cache => {
|
||||
cache.put(e.request, clone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
return cachedResponse || networkFetch;
|
||||
}))});
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -37,8 +36,7 @@
|
||||
|
||||
<body>
|
||||
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>.
|
||||
</p>
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
@@ -46,64 +44,67 @@
|
||||
</select><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js';
|
||||
import { mergeVertices } from 'https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/utils/BufferGeometryUtils.js';
|
||||
import Module from '../manifold.js';
|
||||
|
||||
<script src="https://unpkg.com/three@0.142.0/build/three.js"></script>
|
||||
<script src="https://unpkg.com/three@0.142.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
wasm.setup();
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
csg('difference');
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
return new wasm.Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
@@ -112,7 +113,7 @@
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
const simplified = mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
|
||||
@@ -12,20 +12,107 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
|
||||
importScripts(
|
||||
'manifold.js', threePath + 'build/three.js',
|
||||
threePath + 'examples/js/exporters/GLTFExporter.js');
|
||||
import * as glMatrix from 'https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/+esm';
|
||||
import {Accessor, Document, Material, WebIO} from 'https://cdn.skypack.dev/pin/@gltf-transform/core@v3.0.0-SfbIFhNPTRdr1UE2VSan/mode=imports,min/optimized/@gltf-transform/core.js';
|
||||
|
||||
import Module from '../manifold.js';
|
||||
|
||||
const module = await Module();
|
||||
module.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
glMatrix.glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
// Scene setup
|
||||
const io = new WebIO();
|
||||
const doc = new Document();
|
||||
|
||||
const buffer = doc.createBuffer();
|
||||
const position =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const indices =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const resultMaterial = doc.createMaterial()
|
||||
.setBaseColorFactor([1, 1, 0, 1])
|
||||
.setMetallicFactor(1)
|
||||
.setRoughnessFactor(0.2);
|
||||
const resultPrimitive = doc.createPrimitive()
|
||||
.setMaterial(resultMaterial)
|
||||
.setIndices(indices)
|
||||
.setAttribute('POSITION', position);
|
||||
const mesh = doc.createMesh().addPrimitive(resultPrimitive);
|
||||
const node = doc.createNode('result').setMesh(mesh);
|
||||
const scene = doc.createScene().addChild(node);
|
||||
|
||||
// Debug setup to show source meshes
|
||||
const shown = new Map();
|
||||
let ghost = false;
|
||||
const debugMaterial = doc.createMaterial()
|
||||
.setBaseColorFactor([1, 0, 0, 0.25])
|
||||
.setAlphaMode(Material.AlphaMode.BLEND)
|
||||
.setDoubleSided(true)
|
||||
.setMetallicFactor(0);
|
||||
const ghostMaterial = doc.createMaterial()
|
||||
.setBaseColorFactor([0.5, 0.5, 0.5, 0.25])
|
||||
.setAlphaMode(Material.AlphaMode.BLEND)
|
||||
.setDoubleSided(true)
|
||||
.setMetallicFactor(0);
|
||||
|
||||
function debug(manifold, material) {
|
||||
const position =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const indices =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
|
||||
const primitive = doc.createPrimitive()
|
||||
.setMaterial(material)
|
||||
.setIndices(indices)
|
||||
.setAttribute('POSITION', position);
|
||||
const mesh = doc.createMesh('debug').addPrimitive(primitive);
|
||||
|
||||
const result = manifold.asOriginal();
|
||||
outputMesh(indices, position, result.getMesh());
|
||||
|
||||
shown.set(result.originalID(), mesh);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.show = (manifold) => {
|
||||
return debug(manifold, debugMaterial);
|
||||
};
|
||||
|
||||
module.only = (manifold) => {
|
||||
ghost = true;
|
||||
return debug(manifold, resultMaterial);
|
||||
};
|
||||
|
||||
function debugCleanup() {
|
||||
for (const [id, mesh] of shown) {
|
||||
const primitive = mesh.listPrimitives()[0];
|
||||
primitive.getAttribute('POSITION').dispose();
|
||||
primitive.getIndices().dispose();
|
||||
primitive.dispose();
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
scene.traverse((node) => {
|
||||
if (node.getName() == 'debug') {
|
||||
node.dispose();
|
||||
}
|
||||
});
|
||||
shown.clear();
|
||||
ghost = false;
|
||||
}
|
||||
|
||||
// manifold member functions that returns a new manifold
|
||||
const memberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
|
||||
'scale', 'asOriginal', 'decompose'
|
||||
'add', 'subtract', 'intersect', 'trimByPlane', 'refine', 'transform',
|
||||
'translate', 'rotate', 'scale', 'mirror', 'asOriginal', 'decompose'
|
||||
];
|
||||
// top level functions that constructs a new manifold
|
||||
const constructors = [
|
||||
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
|
||||
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
|
||||
'difference', 'intersection', 'compose', 'levelSet', 'smooth', 'show', 'only'
|
||||
];
|
||||
const utils = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
@@ -33,49 +120,44 @@ const utils = [
|
||||
];
|
||||
const exposedFunctions = constructors.concat(utils);
|
||||
|
||||
let wasm;
|
||||
Module().then(function(tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = wasm.Manifold.prototype[name];
|
||||
wasm.Manifold.prototype['_' + name] = originalFn;
|
||||
wasm.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
const manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = module.Manifold.prototype[name];
|
||||
module.Manifold.prototype['_' + name] = originalFn;
|
||||
module.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = module[name];
|
||||
module[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
module.cleanup = function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry.length = 0;
|
||||
};
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = wasm[name];
|
||||
wasm[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
wasm.cleanup =
|
||||
function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry = [];
|
||||
}
|
||||
|
||||
postMessage(null);
|
||||
});
|
||||
// Setup complete
|
||||
postMessage(null);
|
||||
|
||||
const oldLog = console.log;
|
||||
console.log = function(...args) {
|
||||
@@ -91,34 +173,38 @@ console.log = function(...args) {
|
||||
oldLog(...args);
|
||||
};
|
||||
|
||||
onmessage =
|
||||
(e) => {
|
||||
const content = e.data + '\nexportGLB(result);\n';
|
||||
try {
|
||||
const f = new Function(...exposedFunctions, content);
|
||||
f(...exposedFunctions.map(name => wasm[name]));
|
||||
} catch (error) {
|
||||
console.log(error.toString());
|
||||
postMessage({objectURL: null});
|
||||
} finally {
|
||||
wasm.cleanup();
|
||||
}
|
||||
}
|
||||
onmessage = async (e) => {
|
||||
const content = e.data + '\nreturn exportGLB(result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportGLB, glMatrix, module,
|
||||
...exposedFunctions.map(name => module[name]));
|
||||
} catch (error) {
|
||||
console.log(error.toString());
|
||||
postMessage({objectURL: null});
|
||||
} finally {
|
||||
module.cleanup();
|
||||
debugCleanup();
|
||||
}
|
||||
};
|
||||
|
||||
// Export & Rendering
|
||||
// ------------------------------------------------------------
|
||||
const mesh = new THREE.Mesh(
|
||||
undefined,
|
||||
new THREE.MeshStandardMaterial(
|
||||
{color: 'yellow', metalness: 1, roughness: 0.2}));
|
||||
const rotation = new THREE.Matrix4();
|
||||
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
|
||||
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
|
||||
mesh.scale.setScalar(0.001); // mm -> m
|
||||
function outputMesh(indices, position, mesh) {
|
||||
indices.setArray(mesh.triVerts);
|
||||
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
const numVert = mesh.numVert;
|
||||
const numProp = mesh.numProp;
|
||||
const posArray = new Float32Array(3 * numVert);
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
posArray[3 * i] = mesh.vertProperties[numProp * i];
|
||||
posArray[3 * i + 1] = mesh.vertProperties[numProp * i + 1];
|
||||
posArray[3 * i + 2] = mesh.vertProperties[numProp * i + 2];
|
||||
}
|
||||
position.setArray(posArray);
|
||||
}
|
||||
|
||||
function exportGLB(manifold) {
|
||||
async function exportGLB(manifold) {
|
||||
console.log(`Triangles: ${manifold.numTri().toLocaleString()}`);
|
||||
const box = manifold.boundingBox();
|
||||
const size = [0, 0, 0];
|
||||
@@ -131,24 +217,25 @@ function exportGLB(manifold) {
|
||||
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
|
||||
(volume / 100).toLocaleString()} cm^3`);
|
||||
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(manifold.getMesh());
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], {type: 'application/octet-stream'});
|
||||
postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
},
|
||||
() => {
|
||||
console.log('glTF export failed!');
|
||||
postMessage({objectURL: null});
|
||||
},
|
||||
{binary: true});
|
||||
}
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
outputMesh(indices, position, mesh);
|
||||
|
||||
for (const [run, id] of mesh.runOriginalID.entries()) {
|
||||
const outMesh = shown.get(id);
|
||||
if (outMesh == null) {
|
||||
continue;
|
||||
}
|
||||
const node =
|
||||
doc.createNode('debug').setMesh(outMesh).setMatrix(mesh.transform(run));
|
||||
scene.addChild(node);
|
||||
}
|
||||
|
||||
resultPrimitive.setMaterial(ghost ? ghostMaterial : resultMaterial);
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Box, Curvature, Mat4, Polygons, Properties, SealedFloat32Array, SealedUint32Array, Smoothness, Vec2, Vec3} from './manifold-global-types';
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
export function cube(size?: Vec3|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
export function cylinder(
|
||||
height: number, radiusLow: number, radiusHigh?: number,
|
||||
circularSegments?: number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
export function sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param mesh input Mesh.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
export function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
export function tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
export function extrude(
|
||||
crossSection: Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2): Manifold;
|
||||
|
||||
/**
|
||||
* Triangulates a set of /epsilon-valid polygons.
|
||||
*
|
||||
* @param polygons The set of polygons, wound CCW and representing multiple
|
||||
* polygons and/or holes.
|
||||
* @param precision The value of epsilon, bounding the uncertainty of the input
|
||||
* @return The triangles, referencing the original polygon points in order.
|
||||
*/
|
||||
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by revolving this cross-section
|
||||
* around its Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
export function revolve(
|
||||
crossSection: Polygons, circularSegments?: number): Manifold;
|
||||
|
||||
export function union(a: Manifold, b: Manifold): Manifold;
|
||||
export function difference(a: Manifold, b: Manifold): Manifold;
|
||||
export function intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
export function union(manifolds: Manifold[]): Manifold;
|
||||
export function difference(manifolds: Manifold[]): Manifold;
|
||||
export function intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a list of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A list of Manifolds to lazy-union together.
|
||||
*/
|
||||
export function compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance function which returns the signed distance of
|
||||
* a given point in R^3. Positive values are inside, negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
*/
|
||||
export function levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
export function setMinCircularAngle(angle: number): void;
|
||||
export function setMinCircularEdgeLength(length: number): void;
|
||||
export function setCircularSegments(segments: number): void;
|
||||
export function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
/**
|
||||
* Returns the first of n sequential new unique mesh IDs for marking sets of
|
||||
* triangles that can be looked up after further operations. Assign to
|
||||
* Mesh.runOriginalID vector.
|
||||
*/
|
||||
export function reserveIDs(count: number): number;
|
||||
|
||||
export class Manifold {
|
||||
/**
|
||||
* Convert a Mesh into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will throw an error if the
|
||||
* result is not an oriented 2-manifold. Will collapse degenerate triangles
|
||||
* and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless
|
||||
* round-trip of data from getMesh(). For multi-material input, use
|
||||
* reserveIDs() to set a unique originalID for each material, and sort the
|
||||
* materials into triangle runs.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
/**
|
||||
* Transform this Manifold in space. Stored in column-major order. This
|
||||
* operation can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transformation matrix to apply to all the vertices. The
|
||||
* last row is ignored.
|
||||
*/
|
||||
transform(m: Mat4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Mirror this Manifold over the plane described by the unit form of the given
|
||||
* normal vector. If the length of the normal is zero, an empty Manifold is
|
||||
* returned. This operation can be chained. Transforms are combined and
|
||||
* applied lazily.
|
||||
*
|
||||
* @param normal The normal vector of the plane to be mirrored over
|
||||
*/
|
||||
mirror(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Removes everything behind the given half-space plane.
|
||||
*
|
||||
* @param normal This vector is normal to the cutting plane and its length
|
||||
* does not matter. The result is in the direction of this vector from the
|
||||
* plane.
|
||||
* @param originOffset The distance of the plane from the origin in the
|
||||
* direction of the normal vector.
|
||||
*/
|
||||
trimByPlane(normal: vec3, originOffset: number): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
* will all be coplanar (and will not be immediately collapsed) unless the
|
||||
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
|
||||
* constructor), in which case the new vertices will be moved to the
|
||||
* interpolated surface according to their barycentric coordinates.
|
||||
*
|
||||
* @param n The number of pieces to split every edge into. Must be > 1.
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
* function that warps a geometrically valid object into one which overlaps,
|
||||
* but that is not checked here, so it is up to the user to choose their
|
||||
* function with discretion.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. This approximates them for every vertex (returned
|
||||
* as vectors in the structure) and also returns their minimum and maximum
|
||||
* values.
|
||||
*/
|
||||
getCurvature(): Curvature;
|
||||
|
||||
/**
|
||||
* Returns a Mesh that is designed to easily push into a renderer, including
|
||||
* all interleaved vertex properties that may have been input. It also
|
||||
* includes relations to all the input meshes that form a part of this result
|
||||
* and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original Mesh inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output Mesh to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* Meshes must use the same channels for their normals.
|
||||
*/
|
||||
getMesh(normalIdx?: Vec3): Mesh;
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its IDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its ID that can be referenced
|
||||
* by product manifolds. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
constructor(options: {
|
||||
numProp: number,
|
||||
vertProperties: Float32Array,
|
||||
triVerts: Uint32Array,
|
||||
mergeFromVert?: Uint32Array,
|
||||
mergeToVert?: Uint32Array,
|
||||
runIndex?: Uint32Array,
|
||||
runOriginalID?: Uint32Array,
|
||||
runTransform?: Float32Array,
|
||||
faceID?: Uint32Array,
|
||||
halfedgeTangent?: Float32Array
|
||||
});
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
mergeFromVert?: Uint32Array;
|
||||
mergeToVert?: Uint32Array;
|
||||
runIndex?: Uint32Array;
|
||||
runOriginalID?: Uint32Array;
|
||||
runTransform?: Float32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
get numRun(): number;
|
||||
verts(tri: number): SealedUint32Array<3>;
|
||||
position(vert: number): SealedFloat32Array<3>;
|
||||
extras(vert: number): Float32Array;
|
||||
tangent(halfedge: number): SealedFloat32Array<4>;
|
||||
transform(run: number): Mat4;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export interface SealedUint32Array<N extends number> extends Uint32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export interface SealedFloat32Array<N extends number> extends Float32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export type Vec2 = [number, number];
|
||||
export type Vec3 = [number, number, number];
|
||||
// 4x4 matrix stored in column-major order
|
||||
export type Mat4 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
export type SimplePolygon = Vec2[];
|
||||
export type Polygons = SimplePolygon|SimplePolygon[];
|
||||
export type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
export type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
export type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
export type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
@@ -12,441 +12,34 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
type Vec2 = [number, number];
|
||||
type Vec3 = [number, number, number];
|
||||
type Vec4 = [number, number, number, number];
|
||||
type Matrix3x4 = [Vec3, Vec3, Vec3, Vec3];
|
||||
type SimplePolygon = Vec2[];
|
||||
type Polygons = SimplePolygon|SimplePolygon[];
|
||||
type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
type BaryRef = {
|
||||
meshID: number,
|
||||
originalID: number,
|
||||
tri: number,
|
||||
vertBary: Vec3
|
||||
};
|
||||
type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
type MeshRelation = {
|
||||
barycentric: Vec3[],
|
||||
triBary: BaryRef[],
|
||||
};
|
||||
import * as T from './manifold-encapsulated-types';
|
||||
export * from './manifold-global-types';
|
||||
|
||||
declare class Mesh {
|
||||
vertPos: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
vertNormal?: Float32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
verts(tri: number): Uint32Array<3>;
|
||||
position(vert: number): Float32Array<3>;
|
||||
normal(vert: number): Float32Array<3>;
|
||||
tangent(halfedge: number): Float32Array<4>;
|
||||
}
|
||||
export type Manifold = T.Manifold;
|
||||
export type Mesh = T.Mesh;
|
||||
|
||||
declare class Manifold {
|
||||
/**
|
||||
* Create a Manifold from a Mesh object.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
/**
|
||||
* Transform this Manifold in space. The first three columns form a 3x3 matrix
|
||||
* transform and the last is a translation vector. This operation can be
|
||||
* chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transform matrix to apply to all the vertices.
|
||||
*/
|
||||
transform(m: Matrix3x4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
* will all be coplanar (and will not be immediately collapsed) unless the
|
||||
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
|
||||
* constructor), in which case the new vertices will be moved to the
|
||||
* interpolated surface according to their barycentric coordinates.
|
||||
*
|
||||
* @param n The number of pieces to split every edge into. Must be > 1.
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
* function that warps a geometrically valid object into one which overlaps,
|
||||
* but that is not checked here, so it is up to the user to choose their
|
||||
* function with discretion.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. This approximates them for every vertex (returned
|
||||
* as vectors in the structure) and also returns their minimum and maximum
|
||||
* values.
|
||||
*/
|
||||
getCurvature(): Curvature;
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable
|
||||
* for saving or other operations outside of the context of this library.
|
||||
*/
|
||||
getMesh(): Mesh;
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous meshes, for the purpose of assigning
|
||||
* properties like texture coordinates. The triBary vector is the same length
|
||||
* as Mesh.triVerts: BaryRef.originalID indicates the source mesh and
|
||||
* BaryRef.tri is that mesh's triangle index to which these barycentric
|
||||
* coordinates refer. BaryRef.vertBary gives an index for each vertex into the
|
||||
* barycentric vector if that index is >= 0, indicating it is a new vertex. If
|
||||
* the index is < 0, this indicates it is an original vertex, the index + 3
|
||||
* vert of the referenced triangle.
|
||||
*
|
||||
* BaryRef.meshID is a unique ID to the particular instance of a given mesh.
|
||||
* For instance, if you want to convert the triangle mesh to a polygon mesh,
|
||||
* all the triangles from a given face will have the same .meshID and .tri
|
||||
* values.
|
||||
*/
|
||||
getMeshRelation(): MeshRelation;
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
declare function cylinder(
|
||||
height: number, radiusLow: number, radiusHigh?: number,
|
||||
circularSegments?: number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
declare function sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param mesh input Mesh.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
declare function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
declare function tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
declare function extrude(
|
||||
crossSection: Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by revolving this cross-section
|
||||
* around its Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
declare function revolve(
|
||||
crossSection: Polygons, circularSegments?: number): Manifold;
|
||||
|
||||
declare function union(a: Manifold, b: Manifold): Manifold;
|
||||
declare function difference(a: Manifold, b: Manifold): Manifold;
|
||||
declare function intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
declare function union(manifolds: Manifold[]): Manifold;
|
||||
declare function difference(manifolds: Manifold[]): Manifold;
|
||||
declare function intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a list of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A list of Manifolds to lazy-union together.
|
||||
*/
|
||||
declare function compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance function which returns the signed distance of
|
||||
* a given point in R^3. Positive values are inside, negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
*/
|
||||
declare function levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
declare function setMinCircularAngle(angle: number): void;
|
||||
declare function setMinCircularEdgeLength(length: number): void;
|
||||
declare function setCircularSegments(segments: number): void;
|
||||
declare function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
/**
|
||||
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
|
||||
* constructor, this method does not dispose the Mesh after using it.
|
||||
*
|
||||
* @param meshVec The serialized Mesh object to convert into a Manifold.
|
||||
*/
|
||||
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
|
||||
|
||||
declare interface ManifoldStatic {
|
||||
cube: typeof cube;
|
||||
cylinder: typeof cylinder;
|
||||
sphere: typeof sphere;
|
||||
smooth: typeof smooth;
|
||||
tetrahedron: typeof tetrahedron;
|
||||
extrude: typeof extrude;
|
||||
revolve: typeof revolve;
|
||||
union: typeof union;
|
||||
difference: typeof difference;
|
||||
intersection: typeof intersection;
|
||||
compose: typeof compose;
|
||||
levelSet: typeof levelSet;
|
||||
setMinCircularAngle: typeof setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof setCircularSegments;
|
||||
getCircularSegments: typeof getCircularSegments;
|
||||
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
|
||||
Manifold: typeof Manifold;
|
||||
export interface ManifoldStatic {
|
||||
cube: typeof T.cube;
|
||||
cylinder: typeof T.cylinder;
|
||||
sphere: typeof T.sphere;
|
||||
smooth: typeof T.smooth;
|
||||
tetrahedron: typeof T.tetrahedron;
|
||||
extrude: typeof T.extrude;
|
||||
triangulate: typeof T.triangulate;
|
||||
revolve: typeof T.revolve;
|
||||
union: typeof T.union;
|
||||
difference: typeof T.difference;
|
||||
intersection: typeof T.intersection;
|
||||
compose: typeof T.compose;
|
||||
levelSet: typeof T.levelSet;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
reserveIDs: typeof T.reserveIDs;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
setup: () => void;
|
||||
}
|
||||
|
||||
declare function Module(): Promise<ManifoldStatic>;
|
||||
export default function Module(config: {locateFile: () => string}): Promise<ManifoldStatic>;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"chai": "*",
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"files": [
|
||||
"manifold.js",
|
||||
"manifold.wasm",
|
||||
"manifold.d.ts",
|
||||
"manifold-encapsulated-types.d.ts",
|
||||
"manifold-global-types.d.ts",
|
||||
"examples/examples.js",
|
||||
"test/test.js"
|
||||
],
|
||||
"typings": "manifold.d.ts",
|
||||
"types": "manifold.d.ts",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/elalish/manifold.git"
|
||||
@@ -41,6 +44,6 @@
|
||||
"devDependencies": {
|
||||
"chai": "*",
|
||||
"mocha": "*",
|
||||
"three": "^0.146.0"
|
||||
"gl-matrix": "3.4.3"
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,22 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const wasm = require('../manifold.js');
|
||||
const examples = require('../examples/examples');
|
||||
const THREE = require('three');
|
||||
import {expect} from 'chai';
|
||||
import glMatrix from 'gl-matrix';
|
||||
|
||||
import {examples} from '../examples/examples.js';
|
||||
import Module from '../manifold.js';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
glMatrix.glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
// manifold member functions that returns a new manifold
|
||||
const memberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
|
||||
'scale', 'asOriginal', 'decompose'
|
||||
'add', 'subtract', 'intersect', 'trimByPlane', 'refine', 'transform',
|
||||
'translate', 'rotate', 'scale', 'mirror', 'asOriginal', 'decompose'
|
||||
];
|
||||
// top level functions that constructs a new manifold
|
||||
const constructors = [
|
||||
@@ -33,121 +40,122 @@ const utils = [
|
||||
];
|
||||
const exposedFunctions = constructors.concat(utils);
|
||||
|
||||
wasm().then(function(Module) {
|
||||
Module.setup();
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = Module.Manifold.prototype[name];
|
||||
Module.Manifold.prototype['_' + name] = originalFn;
|
||||
Module.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = wasm.Manifold.prototype[name];
|
||||
wasm.Manifold.prototype['_' + name] = originalFn;
|
||||
wasm.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = wasm[name];
|
||||
wasm[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
wasm.cleanup = function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry = [];
|
||||
};
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = Module[name];
|
||||
Module[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
function runExample(name) {
|
||||
try {
|
||||
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
|
||||
const f = new Function(...exposedFunctions, 'glMatrix', content);
|
||||
const manifold = f(...exposedFunctions.map(name => wasm[name]), glMatrix);
|
||||
|
||||
const mesh = manifold.getMesh();
|
||||
expect(mesh.mergeFromVert.length).to.equal(mesh.mergeToVert.length);
|
||||
expect(mesh.mergeFromVert.length)
|
||||
.to.equal(mesh.numVert - manifold.numVert());
|
||||
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
return {...prop, genus};
|
||||
} finally {
|
||||
wasm.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
Module.cleanup =
|
||||
function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry = [];
|
||||
}
|
||||
suite('Examples', () => {
|
||||
test('Intro', () => {
|
||||
const result = runExample('Intro');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
function runExample(name) {
|
||||
try {
|
||||
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
|
||||
;
|
||||
const f = new Function(...exposedFunctions, 'THREE', content);
|
||||
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
return {...prop, genus};
|
||||
} finally {
|
||||
Module.cleanup();
|
||||
}
|
||||
}
|
||||
test('Tetrahedron Puzzle', () => {
|
||||
const result = runExample('Tetrahedron Puzzle');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
suite('Examples', () => {
|
||||
test('Intro', () => {
|
||||
const result = runExample('Intro');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
test('Rounded Frame', () => {
|
||||
const result = runExample('Rounded Frame');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Tetrahedron Puzzle', () => {
|
||||
const result = runExample('Tetrahedron Puzzle');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
|
||||
});
|
||||
test('Heart', () => {
|
||||
const result = runExample('Heart');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Rounded Frame', () => {
|
||||
const result = runExample('Rounded Frame');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(353706, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
|
||||
});
|
||||
test('Scallop', () => {
|
||||
const result = runExample('Scallop');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Heart', () => {
|
||||
const result = runExample('Heart');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(282743, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
|
||||
});
|
||||
test('Torus Knot', () => {
|
||||
const result = runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Scallop', () => {
|
||||
const result = runExample('Scallop');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
|
||||
});
|
||||
test('Menger Sponge', () => {
|
||||
const result = runExample('Menger Sponge');
|
||||
expect(result.genus).to.equal(729, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203222, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(130475, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Torus Knot', () => {
|
||||
const result = runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
|
||||
});
|
||||
test('Stretchy Bracelet', () => {
|
||||
const result = runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Menger Sponge', () => {
|
||||
const result = runExample('Menger Sponge');
|
||||
expect(result.genus).to.equal(1409, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(406457, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', () => {
|
||||
const result = runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Gyroid Module', () => {
|
||||
const result = runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
test('Gyroid Module', () => {
|
||||
const result = runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,18 +16,20 @@ project(extras)
|
||||
|
||||
add_executable(perfTest perf_test.cpp)
|
||||
target_link_libraries(perfTest manifold)
|
||||
|
||||
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTest PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTest PUBLIC cxx_std_17)
|
||||
|
||||
add_executable(largeSceneTest large_scene_test.cpp)
|
||||
target_link_libraries(largeSceneTest manifold)
|
||||
target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(largeSceneTest PUBLIC cxx_std_17)
|
||||
|
||||
if(BUILD_TEST_CGAL)
|
||||
add_executable(perfTestCGAL perf_test_cgal.cpp)
|
||||
find_package(CGAL REQUIRED COMPONENTS Core)
|
||||
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
|
||||
|
||||
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
|
||||
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
|
||||
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "manifold.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
/*
|
||||
Build & execute with the following command:
|
||||
|
||||
( mkdir -p build && cd build && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_PAR=TBB .. && \
|
||||
make -j && \
|
||||
time ./extras/largeSceneTest 50 )
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
int n = 20;
|
||||
if (argc == 2) n = atoi(argv[1]);
|
||||
|
||||
std::cout << "n = " << n << std::endl;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Manifold scene;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int j = 0; j < n; ++j) {
|
||||
for (int k = 0; k < n; ++k) {
|
||||
if (i == 0 && j == 0 && k == 0) continue;
|
||||
|
||||
Manifold sphere = Manifold::Sphere(1).Translate(glm::vec3(i, j, k));
|
||||
scene = scene.Boolean(sphere, OpType::Add);
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.NumTri();
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> elapsed = end - start;
|
||||
std::cout << "nTri = " << scene.NumTri() << ", time = " << elapsed.count()
|
||||
<< " sec" << std::endl;
|
||||
}
|
||||
@@ -25,10 +25,10 @@
|
||||
"manifold-${parallel-backend}";
|
||||
version = "beta";
|
||||
src = self;
|
||||
patches = [ ./thrust.diff ];
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages(ps: with ps; [trimesh])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PYBIND=ON"
|
||||
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
|
||||
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
|
||||
];
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
add_subdirectory(third_party)
|
||||
|
||||
project(meshIO)
|
||||
|
||||
add_library(${PROJECT_NAME} src/meshIO.cpp)
|
||||
@@ -26,4 +28,4 @@ target_link_libraries(${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
@@ -41,8 +41,14 @@ struct Material {
|
||||
glm::vec4 color = glm::vec4(1.0f);
|
||||
/// Optional: If non-empty, must match Mesh.vertPos. Provides an RGBA color
|
||||
/// for each vertex, linearly interpolated across triangles. Colors are
|
||||
/// linear, not sRGB.
|
||||
/// linear, not sRGB. Only used with Mesh export, not MeshGL.
|
||||
std::vector<glm::vec4> vertColor;
|
||||
/// For MeshGL export, gives the property indicies where the normal channels
|
||||
/// can be found. Must be >= 3, since the first three are position.
|
||||
glm::ivec3 normalChannels = glm::ivec3(-1);
|
||||
/// For MeshGL export, gives the property indicies where the color channels
|
||||
/// can be found. Any index < 0 will output all 1.0 for that channel.
|
||||
glm::ivec4 colorChannels = glm::ivec4(-1);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,6 +66,9 @@ Mesh ImportMesh(const std::string& filename, bool forceCleanup = false);
|
||||
|
||||
void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
const ExportOptions& options);
|
||||
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options);
|
||||
/** @} */
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -19,11 +19,75 @@
|
||||
|
||||
#include "assimp/Exporter.hpp"
|
||||
#include "assimp/Importer.hpp"
|
||||
#include "assimp/pbrmaterial.h"
|
||||
#include "assimp/material.h"
|
||||
#include "assimp/postprocess.h"
|
||||
#include "assimp/scene.h"
|
||||
#include "optional_assert.h"
|
||||
|
||||
#ifndef AI_MATKEY_ROUGHNESS_FACTOR
|
||||
#include "assimp/pbrmaterial.h"
|
||||
#define AI_MATKEY_METALLIC_FACTOR \
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
|
||||
#define AI_MATKEY_ROUGHNESS_FACTOR \
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
aiScene* CreateScene(const ExportOptions& options) {
|
||||
aiScene* scene = new aiScene();
|
||||
|
||||
scene->mNumMaterials = 1;
|
||||
scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
|
||||
scene->mMaterials[0] = new aiMaterial();
|
||||
|
||||
aiMaterial* material = scene->mMaterials[0];
|
||||
material->AddProperty(&options.mat.roughness, 1, AI_MATKEY_ROUGHNESS_FACTOR);
|
||||
material->AddProperty(&options.mat.metalness, 1, AI_MATKEY_METALLIC_FACTOR);
|
||||
const glm::vec4& color = options.mat.color;
|
||||
aiColor4D baseColor(color.r, color.g, color.b, color.a);
|
||||
material->AddProperty(&baseColor, 1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
scene->mNumMeshes = 1;
|
||||
scene->mMeshes = new aiMesh*[scene->mNumMeshes];
|
||||
scene->mMeshes[0] = new aiMesh();
|
||||
scene->mMeshes[0]->mMaterialIndex = 0;
|
||||
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->mNumMeshes = 1;
|
||||
scene->mRootNode->mMeshes = new glm::uint[scene->mRootNode->mNumMeshes];
|
||||
scene->mRootNode->mMeshes[0] = 0;
|
||||
|
||||
scene->mMeshes[0]->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
return scene;
|
||||
}
|
||||
|
||||
std::string GetType(const std::string& filename) {
|
||||
std::string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
if (ext == "glb") ext = "glb2";
|
||||
if (ext == "gltf") ext = "gltf2";
|
||||
return ext;
|
||||
}
|
||||
|
||||
void ExportScene(aiScene* scene, const std::string& filename) {
|
||||
Assimp::Exporter exporter;
|
||||
|
||||
// int n = exporter.GetExportFormatCount();
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// auto desc = exporter.GetExportFormatDescription(i);
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
auto result = exporter.Export(scene, GetType(filename), filename);
|
||||
|
||||
delete scene;
|
||||
|
||||
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
@@ -91,6 +155,95 @@ Mesh ImportMesh(const std::string& filename, bool forceCleanup) {
|
||||
return mesh_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Mesh to the desired file type, determined from the extension
|
||||
* specified. In the case of .glb/.gltf, this will save in version 2.0.
|
||||
*
|
||||
* This is a very simple export function and is intended primarily as a
|
||||
* demonstration. Generally users of this library will need to modify this to
|
||||
* write all the important properties for their application and read any custom
|
||||
* data structures.
|
||||
*
|
||||
* @param filename The file extension must be one that Assimp supports for
|
||||
* export. GLB & 3MF are recommended.
|
||||
* @param mesh The mesh to export, likely from Manifold.GetMeshGL().
|
||||
* @param options The options currently only affect an exported GLB's material.
|
||||
* Pass {} for defaults.
|
||||
*/
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options) {
|
||||
if (mesh.triVerts.size() == 0) {
|
||||
std::cout << filename << " was not saved because the input mesh was empty."
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string type = GetType(filename);
|
||||
const bool isYup = type == "glb2" || type == "gltf2";
|
||||
|
||||
aiScene* scene = CreateScene(options);
|
||||
aiMesh* mesh_out = scene->mMeshes[0];
|
||||
|
||||
mesh_out->mNumVertices = mesh.NumVert();
|
||||
mesh_out->mVertices = new aiVector3D[mesh_out->mNumVertices];
|
||||
if (!options.faceted) {
|
||||
bool validChannels = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
int c = options.mat.normalChannels[i];
|
||||
validChannels &= c >= 3 && c < (int)mesh.numProp;
|
||||
}
|
||||
ASSERT(validChannels, userErr,
|
||||
"When faceted is false, valid normalChannels must be supplied.");
|
||||
mesh_out->mNormals = new aiVector3D[mesh_out->mNumVertices];
|
||||
}
|
||||
|
||||
bool validChannels = true;
|
||||
bool hasColor = false;
|
||||
for (int i : {0, 1, 2, 3}) {
|
||||
int c = options.mat.colorChannels[i];
|
||||
validChannels &= c < (int)mesh.numProp;
|
||||
hasColor |= c >= 0;
|
||||
}
|
||||
ASSERT(validChannels, userErr, "Invalid colorChannels.");
|
||||
if (hasColor) mesh_out->mColors[0] = new aiColor4D[mesh_out->mNumVertices];
|
||||
|
||||
for (int i = 0; i < mesh_out->mNumVertices; ++i) {
|
||||
glm::vec3 v;
|
||||
for (int j : {0, 1, 2}) v[j] = mesh.vertProperties[i * mesh.numProp + j];
|
||||
mesh_out->mVertices[i] =
|
||||
isYup ? aiVector3D(v.y, v.z, v.x) : aiVector3D(v.x, v.y, v.z);
|
||||
if (!options.faceted) {
|
||||
glm::vec3 n;
|
||||
for (int j : {0, 1, 2})
|
||||
n[j] = mesh.vertProperties[i * mesh.numProp +
|
||||
options.mat.normalChannels[j]];
|
||||
mesh_out->mNormals[i] =
|
||||
isYup ? aiVector3D(n.y, n.z, n.x) : aiVector3D(n.x, n.y, n.z);
|
||||
}
|
||||
if (hasColor) {
|
||||
glm::vec4 c;
|
||||
for (int j : {0, 1, 2, 3})
|
||||
c[j] = options.mat.colorChannels[j] < 0
|
||||
? 1
|
||||
: mesh.vertProperties[i * mesh.numProp +
|
||||
options.mat.colorChannels[j]];
|
||||
mesh_out->mColors[0][i] = aiColor4D(c.r, c.g, c.b, c.a);
|
||||
}
|
||||
}
|
||||
|
||||
mesh_out->mNumFaces = mesh.NumTri();
|
||||
mesh_out->mFaces = new aiFace[mesh_out->mNumFaces];
|
||||
|
||||
for (int i = 0; i < mesh_out->mNumFaces; ++i) {
|
||||
aiFace& face = mesh_out->mFaces[i];
|
||||
face.mNumIndices = 3;
|
||||
face.mIndices = new glm::uint[face.mNumIndices];
|
||||
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[3 * i + j];
|
||||
}
|
||||
|
||||
ExportScene(scene, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Mesh to the desired file type, determined from the extension
|
||||
* specified. In the case of .glb/.gltf, this will save in version 2.0.
|
||||
@@ -114,38 +267,11 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
return;
|
||||
}
|
||||
|
||||
std::string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
const bool isYup = ext == "glb" || ext == "gltf";
|
||||
if (ext == "glb") ext = "glb2";
|
||||
if (ext == "gltf") ext = "gltf2";
|
||||
|
||||
aiScene* scene = new aiScene();
|
||||
|
||||
scene->mNumMaterials = 1;
|
||||
scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
|
||||
scene->mMaterials[0] = new aiMaterial();
|
||||
|
||||
aiMaterial* material = scene->mMaterials[0];
|
||||
material->AddProperty(&options.mat.roughness, 1,
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR);
|
||||
material->AddProperty(&options.mat.metalness, 1,
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR);
|
||||
const glm::vec4& color = options.mat.color;
|
||||
aiColor4D baseColor(color.r, color.g, color.b, color.a);
|
||||
material->AddProperty(&baseColor, 1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
scene->mNumMeshes = 1;
|
||||
scene->mMeshes = new aiMesh*[scene->mNumMeshes];
|
||||
scene->mMeshes[0] = new aiMesh();
|
||||
scene->mMeshes[0]->mMaterialIndex = 0;
|
||||
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->mNumMeshes = 1;
|
||||
scene->mRootNode->mMeshes = new glm::uint[scene->mRootNode->mNumMeshes];
|
||||
scene->mRootNode->mMeshes[0] = 0;
|
||||
std::string type = GetType(filename);
|
||||
const bool isYup = type == "glb2" || type == "gltf2";
|
||||
|
||||
aiScene* scene = CreateScene(options);
|
||||
aiMesh* mesh_out = scene->mMeshes[0];
|
||||
mesh_out->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
|
||||
mesh_out->mNumVertices = mesh.vertPos.size();
|
||||
mesh_out->mVertices = new aiVector3D[mesh_out->mNumVertices];
|
||||
@@ -186,20 +312,7 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[i][j];
|
||||
}
|
||||
|
||||
Assimp::Exporter exporter;
|
||||
|
||||
// int n = exporter.GetExportFormatCount();
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// auto desc = exporter.GetExportFormatDescription(i);
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
auto result = exporter.Export(scene, ext, filename);
|
||||
|
||||
delete scene;
|
||||
|
||||
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
|
||||
ExportScene(scene, filename);
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright 2023 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# I feel like there has to be a better way - I hate using the CMake cache.
|
||||
set(ASSIMP_BUILD_TESTS OFF CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_INSTALL OFF CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_BUILD_ASSIMP_VIEW OFF CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT OFF CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT OFF CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_BUILD_GLTF_EXPORTER ON CACHE INTERNAL "" FORCE)
|
||||
set(ASSIMP_BUILD_GLTF_IMPORTER ON CACHE INTERNAL "" FORCE)
|
||||
|
||||
add_subdirectory(assimp)
|
||||
|
||||
target_compile_options(assimp PRIVATE -Wno-error -Wno-deprecated-declarations)
|
||||
@@ -25,7 +25,7 @@ target_link_libraries(samples
|
||||
)
|
||||
|
||||
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samples PUBLIC cxx_std_14)
|
||||
target_compile_features(samples PUBLIC cxx_std_17)
|
||||
|
||||
project(samplesGPU)
|
||||
|
||||
@@ -46,6 +46,5 @@ target_link_libraries(samplesGPU
|
||||
|
||||
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samplesGPU
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
@@ -52,4 +52,4 @@ Manifold Scallop();
|
||||
|
||||
Manifold GyroidModule(float size = 20, int n = 20);
|
||||
/** @} */ // end of Samples
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
|
After Width: | Height: | Size: 428 KiB |
|
After Width: | Height: | Size: 106 KiB |
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "samples.h"
|
||||
|
||||
namespace {
|
||||
@@ -23,14 +24,8 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
int nCut, int nDivision) {
|
||||
Manifold base = Manifold::Cylinder(width, radius + twistRadius / 2);
|
||||
|
||||
Polygons circle(1);
|
||||
float dPhiDeg = 180.0f / nDivision;
|
||||
for (int i = 0; i < 2 * nDivision; ++i) {
|
||||
circle[0].push_back(
|
||||
{glm::vec2(decorRadius * cosd(dPhiDeg * i) + twistRadius,
|
||||
decorRadius * sind(dPhiDeg * i)),
|
||||
0});
|
||||
}
|
||||
CrossSection circle =
|
||||
CrossSection::Circle(decorRadius, nDivision).Translate({twistRadius, 0});
|
||||
Manifold decor = Manifold::Extrude(circle, width, nDivision, 180)
|
||||
.Scale({1.0f, 0.5f, 1.0f})
|
||||
.Translate({0.0f, radius, 0.0f});
|
||||
@@ -45,13 +40,14 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
glm::vec2 p1(innerRadius, -cut);
|
||||
glm::vec2 p2(innerRadius, cut);
|
||||
for (int i = 0; i < nCut; ++i) {
|
||||
stretch[0].push_back({glm::rotate(p0, dPhiRad * i), 0});
|
||||
stretch[0].push_back({glm::rotate(p1, dPhiRad * i), 0});
|
||||
stretch[0].push_back({glm::rotate(p2, dPhiRad * i), 0});
|
||||
stretch[0].push_back({glm::rotate(p0, dPhiRad * i), 0});
|
||||
stretch[0].push_back(glm::rotate(p0, dPhiRad * i));
|
||||
stretch[0].push_back(glm::rotate(p1, dPhiRad * i));
|
||||
stretch[0].push_back(glm::rotate(p2, dPhiRad * i));
|
||||
stretch[0].push_back(glm::rotate(p0, dPhiRad * i));
|
||||
}
|
||||
|
||||
base = Manifold::Extrude(stretch, width) ^ base;
|
||||
// Remove extra edges in coplanar faces
|
||||
base = base.AsOriginal();
|
||||
|
||||
return base;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "samples.h"
|
||||
|
||||
namespace {
|
||||
@@ -33,7 +34,7 @@ namespace manifold {
|
||||
* @param minorRadius Radius of the small cross-section of the imaginary donut.
|
||||
* @param threadRadius Radius of the small cross-section of the actual object.
|
||||
* @param circularSegments Number of linear segments making up the threadRadius
|
||||
* circle. Default is Manifold.GetCircularSegments().
|
||||
* circle. Default is Quality.GetCircularSegments().
|
||||
* @param linearSegments Number of segments along the length of the knot.
|
||||
* Default makes roughly square facets.
|
||||
*/
|
||||
@@ -44,16 +45,11 @@ Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Manifold::GetCircularSegments(threadRadius);
|
||||
: Quality::GetCircularSegments(threadRadius);
|
||||
int m =
|
||||
linearSegments > 2 ? linearSegments : n * q * majorRadius / threadRadius;
|
||||
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back({glm::vec2(cosd(dPhi * i) + 2.0f, sind(dPhi * i)), 0});
|
||||
}
|
||||
|
||||
CrossSection circle = CrossSection::Circle(1., n).Translate({2, 0});
|
||||
Manifold knot = Manifold::Revolve(circle, m);
|
||||
|
||||
knot =
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace manifold {
|
||||
* @param edgeLength Distance between the corners.
|
||||
* @param radius Radius of the frame members.
|
||||
* @param circularSegments Number of segments in the cylinders and spheres.
|
||||
* Defaults to Manifold.GetCircularSegments().
|
||||
* Defaults to Quality.GetCircularSegments().
|
||||
*/
|
||||
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments) {
|
||||
Manifold edge = Manifold::Cylinder(edgeLength, radius, -1, circularSegments);
|
||||
|
||||
@@ -31,10 +31,10 @@ Manifold TetPuzzle(float edgeLength, float gap, int nDivisions) {
|
||||
Manifold tet = Manifold::Tetrahedron().Scale(scale);
|
||||
|
||||
Polygons box;
|
||||
box.push_back({{glm::vec2(2, -2), 0}, {glm::vec2(2, 2), 1}});
|
||||
box.push_back({{2, -2}, {2, 2}});
|
||||
|
||||
for (int i = 0; i <= nDivisions; ++i) {
|
||||
box[0].push_back({glm::vec2(gap / 2, 2 - i * 4.0f / nDivisions), 2 + i});
|
||||
box[0].push_back({gap / 2, 2 - i * 4.0f / nDivisions});
|
||||
}
|
||||
|
||||
Manifold screw = Manifold::Extrude(box, 2, nDivisions, 270)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(utilities)
|
||||
add_subdirectory(collider)
|
||||
add_subdirectory(cross_section)
|
||||
add_subdirectory(polygon)
|
||||
add_subdirectory(manifold)
|
||||
add_subdirectory(sdf)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (collider)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
@@ -25,8 +25,6 @@ endif()
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Copyright 2023 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project (cross_section)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
PUBLIC utilities Clipper2
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
@@ -0,0 +1,228 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <clipper2/clipper.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "glm/ext/matrix_float3x2.hpp"
|
||||
#include "glm/ext/vector_float2.hpp"
|
||||
#include "public.h"
|
||||
|
||||
namespace C2 = Clipper2Lib;
|
||||
|
||||
namespace manifold {
|
||||
|
||||
class Rect;
|
||||
|
||||
/** @addtogroup Core
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Two-dimensional cross sections guaranteed to be without self-intersections,
|
||||
* or overlaps between polygons (from construction onwards). This class makes
|
||||
* use of the [Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm)
|
||||
* library for polygon clipping (boolean) and offsetting operations.
|
||||
*/
|
||||
class CrossSection {
|
||||
public:
|
||||
/** @name Creation
|
||||
* Constructors
|
||||
*/
|
||||
///@{
|
||||
|
||||
CrossSection();
|
||||
~CrossSection();
|
||||
|
||||
CrossSection(const CrossSection& other);
|
||||
CrossSection& operator=(const CrossSection& other);
|
||||
CrossSection(CrossSection&&) noexcept;
|
||||
CrossSection& operator=(CrossSection&&) noexcept;
|
||||
|
||||
// Adapted from Clipper2 docs:
|
||||
// http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/FillRule.htm
|
||||
// (Copyright © 2010-2023 Angus Johnson)
|
||||
/**
|
||||
* Filling rules defining which polygon sub-regions are considered to be
|
||||
* inside a given polygon, and which sub-regions will not (based on winding
|
||||
* numbers). See the [Clipper2
|
||||
* docs](http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/FillRule.htm)
|
||||
* for a detailed explaination with illusrations.
|
||||
*/
|
||||
enum class FillRule {
|
||||
EvenOdd, ///< Only odd numbered sub-regions are filled.
|
||||
NonZero, ///< Only non-zero sub-regions are filled.
|
||||
Positive, ///< Only sub-regions with winding counts > 0 are filled.
|
||||
Negative ///< Only sub-regions with winding counts < 0 are filled.
|
||||
};
|
||||
|
||||
CrossSection(const SimplePolygon& contour,
|
||||
FillRule fillrule = FillRule::Positive);
|
||||
CrossSection(const Polygons& contours,
|
||||
FillRule fillrule = FillRule::Positive);
|
||||
static CrossSection Square(const glm::vec2 dims, bool center = false);
|
||||
static CrossSection Circle(float radius, int circularSegments = 0);
|
||||
///@}
|
||||
|
||||
/** @name Information
|
||||
* Details of the cross-section
|
||||
*/
|
||||
///@{
|
||||
double Area() const;
|
||||
int NumVert() const;
|
||||
int NumContour() const;
|
||||
bool IsEmpty() const;
|
||||
Rect Bounds() const;
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
*/
|
||||
///@{
|
||||
CrossSection Translate(const glm::vec2 v) const;
|
||||
CrossSection Rotate(float degrees) const;
|
||||
CrossSection Scale(const glm::vec2 s) const;
|
||||
CrossSection Mirror(const glm::vec2 ax) const;
|
||||
CrossSection Transform(const glm::mat3x2& m) const;
|
||||
CrossSection Warp(std::function<void(glm::vec2&)> warpFunc) const;
|
||||
CrossSection Simplify(double epsilon = 1e-6) const;
|
||||
|
||||
// Adapted from Clipper2 docs:
|
||||
// http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/JoinType.htm
|
||||
// (Copyright © 2010-2023 Angus Johnson)
|
||||
/**
|
||||
* Specifies the treatment of path/contour joins (corners) when offseting
|
||||
* CrossSections. See the [Clipper2
|
||||
* doc](http://www.angusj.com/clipper2/Docs/Units/Clipper/Types/JoinType.htm)
|
||||
* for illustrations.
|
||||
*/
|
||||
enum class JoinType {
|
||||
Square, /*!< Squaring is applied uniformly at all joins where the internal
|
||||
join angle is less that 90 degrees. The squared edge will be at
|
||||
exactly the offset distance from the join vertex. */
|
||||
Round, /*!< Rounding is applied to all joins that have convex external
|
||||
angles, and it maintains the exact offset distance from the join
|
||||
vertex. */
|
||||
Miter /*!< There's a necessary limit to mitered joins (to avoid narrow
|
||||
angled joins producing excessively long and narrow
|
||||
[spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)).
|
||||
So where mitered joins would exceed a given maximum miter distance
|
||||
(relative to the offset distance), these are 'squared' instead. */
|
||||
};
|
||||
|
||||
CrossSection Offset(double delta, JoinType jt, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0) const;
|
||||
///@}
|
||||
|
||||
/** @name Boolean
|
||||
* Combine two manifolds
|
||||
*/
|
||||
///@{
|
||||
CrossSection Boolean(const CrossSection& second, OpType op) const;
|
||||
static CrossSection BatchBoolean(
|
||||
const std::vector<CrossSection>& crossSections, OpType op);
|
||||
CrossSection operator+(const CrossSection&) const;
|
||||
CrossSection& operator+=(const CrossSection&);
|
||||
CrossSection operator-(const CrossSection&) const;
|
||||
CrossSection& operator-=(const CrossSection&);
|
||||
CrossSection operator^(const CrossSection&) const;
|
||||
CrossSection& operator^=(const CrossSection&);
|
||||
CrossSection RectClip(const Rect& rect) const;
|
||||
///@}
|
||||
|
||||
/** @name Topological
|
||||
*/
|
||||
///@{
|
||||
static CrossSection Compose(std::vector<CrossSection>&);
|
||||
std::vector<CrossSection> Decompose() const;
|
||||
///@}
|
||||
|
||||
/** @name Conversion
|
||||
*/
|
||||
///@{
|
||||
Polygons ToPolygons() const;
|
||||
///@}
|
||||
|
||||
private:
|
||||
mutable std::shared_ptr<const C2::PathsD> paths_;
|
||||
mutable glm::mat3x2 transform_ = glm::mat3x2(1.0f);
|
||||
CrossSection(C2::PathsD paths);
|
||||
C2::PathsD GetPaths() const;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Connections
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Axis-aligned rectangular bounds.
|
||||
*/
|
||||
class Rect {
|
||||
public:
|
||||
glm::vec2 min = glm::vec2(0);
|
||||
glm::vec2 max = glm::vec2(0);
|
||||
|
||||
/** @name Creation
|
||||
* Constructors
|
||||
*/
|
||||
///@{
|
||||
Rect();
|
||||
~Rect();
|
||||
Rect(const Rect& other);
|
||||
Rect& operator=(const Rect& other);
|
||||
Rect(Rect&&) noexcept;
|
||||
Rect& operator=(Rect&&) noexcept;
|
||||
Rect(const glm::vec2 a, const glm::vec2 b);
|
||||
///@}
|
||||
|
||||
/** @name Information
|
||||
* Details of the rectangle
|
||||
*/
|
||||
///@{
|
||||
glm::vec2 Size() const;
|
||||
float Scale() const;
|
||||
glm::vec2 Center() const;
|
||||
bool Contains(const glm::vec2& pt) const;
|
||||
bool Contains(const Rect& other) const;
|
||||
bool DoesOverlap(const Rect& other) const;
|
||||
bool IsEmpty() const;
|
||||
bool IsFinite() const;
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
*/
|
||||
///@{
|
||||
void Union(const glm::vec2 p);
|
||||
Rect Union(const Rect& other) const;
|
||||
Rect operator+(const glm::vec2 shift) const;
|
||||
Rect& operator+=(const glm::vec2 shift);
|
||||
Rect operator*(const glm::vec2 scale) const;
|
||||
Rect& operator*=(const glm::vec2 scale);
|
||||
Rect Transform(const glm::mat3x2& m) const;
|
||||
///@}
|
||||
|
||||
/** @name Conversion
|
||||
*/
|
||||
///@{
|
||||
CrossSection AsCrossSection() const;
|
||||
///@}
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -0,0 +1,767 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
const int precision_ = 8;
|
||||
|
||||
C2::ClipType cliptype_of_op(OpType op) {
|
||||
C2::ClipType ct = C2::ClipType::Union;
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
break;
|
||||
case OpType::Subtract:
|
||||
ct = C2::ClipType::Difference;
|
||||
break;
|
||||
case OpType::Intersect:
|
||||
ct = C2::ClipType::Intersection;
|
||||
break;
|
||||
};
|
||||
return ct;
|
||||
}
|
||||
|
||||
C2::FillRule fr(CrossSection::FillRule fillrule) {
|
||||
C2::FillRule fr = C2::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case CrossSection::FillRule::EvenOdd:
|
||||
break;
|
||||
case CrossSection::FillRule::NonZero:
|
||||
fr = C2::FillRule::NonZero;
|
||||
break;
|
||||
case CrossSection::FillRule::Positive:
|
||||
fr = C2::FillRule::Positive;
|
||||
break;
|
||||
case CrossSection::FillRule::Negative:
|
||||
fr = C2::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
C2::JoinType jt(CrossSection::JoinType jointype) {
|
||||
C2::JoinType jt = C2::JoinType::Square;
|
||||
switch (jointype) {
|
||||
case CrossSection::JoinType::Square:
|
||||
break;
|
||||
case CrossSection::JoinType::Round:
|
||||
jt = C2::JoinType::Round;
|
||||
break;
|
||||
case CrossSection::JoinType::Miter:
|
||||
jt = C2::JoinType::Miter;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
glm::vec2 v2_of_pd(const C2::PointD p) { return {p.x, p.y}; }
|
||||
|
||||
C2::PointD v2_to_pd(const glm::vec2 v) { return C2::PointD(v.x, v.y); }
|
||||
|
||||
C2::PathD pathd_of_contour(const SimplePolygon& ctr) {
|
||||
auto p = C2::PathD();
|
||||
p.reserve(ctr.size());
|
||||
for (auto v : ctr) {
|
||||
p.push_back(v2_to_pd(v));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
C2::PathsD transform(const C2::PathsD ps, const glm::mat3x2 m) {
|
||||
const bool invert = glm::determinant(glm::mat2(m)) < 0;
|
||||
auto transformed = C2::PathsD();
|
||||
transformed.reserve(ps.size());
|
||||
for (auto path : ps) {
|
||||
auto sz = path.size();
|
||||
auto s = C2::PathD(sz);
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
auto idx = invert ? sz - 1 - i : i;
|
||||
s[idx] = v2_to_pd(m * glm::vec3(path[i].x, path[i].y, 1));
|
||||
}
|
||||
transformed.push_back(s);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
std::shared_ptr<const C2::PathsD> shared_paths(const C2::PathsD& ps) {
|
||||
return std::make_shared<const C2::PathsD>(ps);
|
||||
}
|
||||
|
||||
// forward declaration for mutual recursion
|
||||
void decompose_hole(const C2::PolyTreeD* outline,
|
||||
std::vector<C2::PathsD>& polys, C2::PathsD& poly,
|
||||
int n_holes, int j);
|
||||
|
||||
void decompose_outline(const C2::PolyTreeD* tree,
|
||||
std::vector<C2::PathsD>& polys, int i) {
|
||||
auto n_outlines = tree->Count();
|
||||
if (i < n_outlines) {
|
||||
auto outline = tree->Child(i);
|
||||
auto n_holes = outline->Count();
|
||||
auto poly = C2::PathsD(n_holes + 1);
|
||||
poly[0] = outline->Polygon();
|
||||
decompose_hole(outline, polys, poly, n_holes, 0);
|
||||
polys.push_back(poly);
|
||||
if (i < n_outlines - 1) {
|
||||
decompose_outline(tree, polys, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompose_hole(const C2::PolyTreeD* outline,
|
||||
std::vector<C2::PathsD>& polys, C2::PathsD& poly,
|
||||
int n_holes, int j) {
|
||||
if (j < n_holes) {
|
||||
auto child = outline->Child(j);
|
||||
decompose_outline(child, polys, 0);
|
||||
poly[j + 1] = child->Polygon();
|
||||
decompose_hole(outline, polys, poly, n_holes, j + 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* The default constructor is an empty cross-section (containing no contours).
|
||||
*/
|
||||
CrossSection::CrossSection() { paths_ = shared_paths(C2::PathsD()); }
|
||||
|
||||
CrossSection::~CrossSection() = default;
|
||||
CrossSection::CrossSection(CrossSection&&) noexcept = default;
|
||||
CrossSection& CrossSection::operator=(CrossSection&&) noexcept = default;
|
||||
|
||||
/**
|
||||
* The copy constructor avoids copying the underlying paths vector (sharing
|
||||
* with its parent via shared_ptr), however subsequent transformations, and
|
||||
* their application will not be shared. It is generally recommended to avoid
|
||||
* this, opting instead to simply create CrossSections with the available
|
||||
* const methods.
|
||||
*/
|
||||
CrossSection::CrossSection(const CrossSection& other) {
|
||||
paths_ = other.paths_;
|
||||
transform_ = other.transform_;
|
||||
}
|
||||
|
||||
CrossSection& CrossSection::operator=(const CrossSection& other) {
|
||||
if (this != &other) {
|
||||
paths_ = other.paths_;
|
||||
transform_ = other.transform_;
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
// Private, skips unioning.
|
||||
CrossSection::CrossSection(C2::PathsD ps) { paths_ = shared_paths(ps); }
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from a single contour. A boolean union operation
|
||||
* (with Positive filling rule by default) is performed to ensure the
|
||||
* resulting CrossSection is free of self-intersections.
|
||||
*
|
||||
* @param contour A closed path outlining the desired cross-section.
|
||||
* @param fillrule The filling rule used to interpret polygon sub-regions
|
||||
* created by self-intersections in contour.
|
||||
*/
|
||||
CrossSection::CrossSection(const SimplePolygon& contour, FillRule fillrule) {
|
||||
auto ps = C2::PathsD{(pathd_of_contour(contour))};
|
||||
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from a set of contours (complex polygons). A
|
||||
* boolean union operation (with Positive filling rule by default) is
|
||||
* performed to combine overlapping polygons and ensure the resulting
|
||||
* CrossSection is free of intersections.
|
||||
*
|
||||
* @param contours A set of closed paths describing zero or more complex
|
||||
* polygons.
|
||||
* @param fillrule The filling rule used to interpret polygon sub-regions in
|
||||
* contours.
|
||||
*/
|
||||
CrossSection::CrossSection(const Polygons& contours, FillRule fillrule) {
|
||||
auto ps = C2::PathsD();
|
||||
ps.reserve(contours.size());
|
||||
for (auto ctr : contours) {
|
||||
ps.push_back(pathd_of_contour(ctr));
|
||||
}
|
||||
paths_ = shared_paths(C2::Union(ps, fr(fillrule), precision_));
|
||||
}
|
||||
|
||||
// Private
|
||||
// All access to paths_ should be done through the GetPaths() method, which
|
||||
// applies the accumulated transform_
|
||||
C2::PathsD CrossSection::GetPaths() const {
|
||||
if (transform_ == glm::mat3x2(1.0f)) {
|
||||
return *paths_;
|
||||
}
|
||||
paths_ = shared_paths(transform(*paths_, transform_));
|
||||
transform_ = glm::mat3x2(1.0f);
|
||||
return *paths_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a square with the given XY dimensions. By default it is
|
||||
* positioned in the first quadrant, touching the origin. If any dimensions in
|
||||
* size are negative, or if all are zero, an empty Manifold will be returned.
|
||||
*
|
||||
* @param size The X, and Y dimensions of the square.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
CrossSection CrossSection::Square(const glm::vec2 size, bool center) {
|
||||
if (size.x < 0.0f || size.y < 0.0f || glm::length(size) == 0.0f) {
|
||||
return CrossSection();
|
||||
}
|
||||
|
||||
auto p = C2::PathD(4);
|
||||
if (center) {
|
||||
const auto w = size.x / 2;
|
||||
const auto h = size.y / 2;
|
||||
p[0] = C2::PointD(w, h);
|
||||
p[1] = C2::PointD(-w, h);
|
||||
p[2] = C2::PointD(-w, -h);
|
||||
p[3] = C2::PointD(w, -h);
|
||||
} else {
|
||||
const double x = size.x;
|
||||
const double y = size.y;
|
||||
p[0] = C2::PointD(0.0, 0.0);
|
||||
p[1] = C2::PointD(x, 0.0);
|
||||
p[2] = C2::PointD(x, y);
|
||||
p[3] = C2::PointD(0.0, y);
|
||||
}
|
||||
return CrossSection(C2::PathsD{p});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a circle of a given radius.
|
||||
*
|
||||
* @param radius Radius of the circle. Must be positive.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Quality defaults according to the radius.
|
||||
*/
|
||||
CrossSection CrossSection::Circle(float radius, int circularSegments) {
|
||||
if (radius <= 0.0f) {
|
||||
return CrossSection();
|
||||
}
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
float dPhi = 360.0f / n;
|
||||
auto circle = C2::PathD(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[i] = C2::PointD(radius * cosd(dPhi * i), radius * sind(dPhi * i));
|
||||
}
|
||||
return CrossSection(C2::PathsD{circle});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given boolean operation between this and another CrossSection.
|
||||
*/
|
||||
CrossSection CrossSection::Boolean(const CrossSection& second,
|
||||
OpType op) const {
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths(),
|
||||
second.GetPaths(), precision_);
|
||||
return CrossSection(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the given boolean operation on a list of CrossSections. In case of
|
||||
* Subtract, all CrossSections in the tail are differenced from the head.
|
||||
*/
|
||||
CrossSection CrossSection::BatchBoolean(
|
||||
const std::vector<CrossSection>& crossSections, OpType op) {
|
||||
if (crossSections.size() == 0)
|
||||
return CrossSection();
|
||||
else if (crossSections.size() == 1)
|
||||
return crossSections[0];
|
||||
|
||||
auto subjs = crossSections[0].GetPaths();
|
||||
int n_clips = 0;
|
||||
for (int i = 1; i < crossSections.size(); ++i) {
|
||||
n_clips += crossSections[i].GetPaths().size();
|
||||
}
|
||||
auto clips = C2::PathsD();
|
||||
clips.reserve(n_clips);
|
||||
for (int i = 1; i < crossSections.size(); ++i) {
|
||||
auto ps = crossSections[i].GetPaths();
|
||||
clips.insert(clips.end(), ps.begin(), ps.end());
|
||||
}
|
||||
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res =
|
||||
C2::BooleanOp(ct, C2::FillRule::Positive, subjs, clips, precision_);
|
||||
return CrossSection(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean union between two cross-sections.
|
||||
*/
|
||||
CrossSection CrossSection::operator+(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean union between two cross-sections, assigning the result
|
||||
* to the first.
|
||||
*/
|
||||
CrossSection& CrossSection::operator+=(const CrossSection& Q) {
|
||||
*this = *this + Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean difference of a (clip) cross-section from another
|
||||
* (subject).
|
||||
*/
|
||||
CrossSection CrossSection::operator-(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Subtract);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean difference of a (clip) cross-section from a another
|
||||
* (subject), assigning the result to the subject.
|
||||
*/
|
||||
CrossSection& CrossSection::operator-=(const CrossSection& Q) {
|
||||
*this = *this - Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean intersection between two cross-sections.
|
||||
*/
|
||||
CrossSection CrossSection::operator^(const CrossSection& Q) const {
|
||||
return Boolean(Q, OpType::Intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the boolean intersection between two cross-sections, assigning the
|
||||
* result to the first.
|
||||
*/
|
||||
CrossSection& CrossSection::operator^=(const CrossSection& Q) {
|
||||
*this = *this ^ Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a CrossSection from a vector of other CrossSections (batch
|
||||
* boolean union).
|
||||
*/
|
||||
CrossSection CrossSection::Compose(std::vector<CrossSection>& crossSections) {
|
||||
return BatchBoolean(crossSections, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of CrossSections that are topologically
|
||||
* disconnected, each containing one outline contour with zero or more
|
||||
* holes.
|
||||
*/
|
||||
std::vector<CrossSection> CrossSection::Decompose() const {
|
||||
if (NumContour() < 2) {
|
||||
return std::vector<CrossSection>{CrossSection(*this)};
|
||||
}
|
||||
|
||||
C2::PolyTreeD tree;
|
||||
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths(),
|
||||
C2::PathsD(), tree, precision_);
|
||||
|
||||
auto polys = std::vector<C2::PathsD>();
|
||||
decompose_outline(&tree, polys, 0);
|
||||
|
||||
auto n_polys = polys.size();
|
||||
auto comps = std::vector<CrossSection>(n_polys);
|
||||
// reverse the stack while wrapping
|
||||
for (int i = 0; i < n_polys; ++i) {
|
||||
comps[n_polys - i - 1] = CrossSection(polys[i]);
|
||||
}
|
||||
|
||||
return comps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the intersection between a cross-section and an axis-aligned
|
||||
* rectangle. This operation has much higher performance (<B>O(n)</B> vs
|
||||
* <B>O(n<SUP>3</SUP>)</B>) than the general purpose intersection algorithm
|
||||
* used for sets of cross-sections.
|
||||
*/
|
||||
CrossSection CrossSection::RectClip(const Rect& rect) const {
|
||||
auto r = C2::RectD(rect.min.x, rect.min.y, rect.max.x, rect.max.y);
|
||||
auto ps = C2::ExecuteRectClip(r, GetPaths(), false, precision_);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this CrossSection in space. This operation can be chained. Transforms
|
||||
* are combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
CrossSection CrossSection::Translate(const glm::vec2 v) const {
|
||||
glm::mat3x2 m(1.0f, 0.0f, //
|
||||
0.0f, 1.0f, //
|
||||
v.x, v.y);
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param degrees degrees about the Z-axis to rotate.
|
||||
*/
|
||||
CrossSection CrossSection::Rotate(float degrees) const {
|
||||
auto s = sind(degrees);
|
||||
auto c = cosd(degrees);
|
||||
glm::mat3x2 m(c, s, //
|
||||
-s, c, //
|
||||
0.0f, 0.0f);
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this CrossSection in space. This operation can be chained. Transforms
|
||||
* are combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
CrossSection CrossSection::Scale(const glm::vec2 scale) const {
|
||||
glm::mat3x2 m(scale.x, 0.0f, //
|
||||
0.0f, scale.y, //
|
||||
0.0f, 0.0f);
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror this CrossSection over the arbitrary axis described by the unit form
|
||||
* of the given vector. If the length of the vector is zero, an empty
|
||||
* CrossSection is returned. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param ax the axis to be mirrored over
|
||||
*/
|
||||
CrossSection CrossSection::Mirror(const glm::vec2 ax) const {
|
||||
if (glm::length(ax) == 0.) {
|
||||
return CrossSection();
|
||||
}
|
||||
auto n = glm::normalize(glm::abs(ax));
|
||||
auto m = glm::mat3x2(glm::mat2(1.0f) - 2.0f * glm::outerProduct(n, n));
|
||||
return Transform(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform this CrossSection in space. The first two columns form a 2x2
|
||||
* matrix transform and the last is a translation vector. This operation can
|
||||
* be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transform matrix to apply to all the vertices.
|
||||
*/
|
||||
CrossSection CrossSection::Transform(const glm::mat3x2& m) const {
|
||||
auto transformed = CrossSection();
|
||||
transformed.transform_ = m * glm::mat3(transform_);
|
||||
transformed.paths_ = paths_;
|
||||
return transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the vertices of this CrossSection (creating a new one) according to
|
||||
* any arbitrary input function, followed by a union operation (with a
|
||||
* Positive fill rule) that ensures any introduced intersections are not
|
||||
* included in the result.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
CrossSection CrossSection::Warp(
|
||||
std::function<void(glm::vec2&)> warpFunc) const {
|
||||
auto paths = GetPaths();
|
||||
auto warped = C2::PathsD();
|
||||
warped.reserve(paths.size());
|
||||
for (auto path : paths) {
|
||||
auto sz = path.size();
|
||||
auto s = C2::PathD(sz);
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
auto v = v2_of_pd(path[i]);
|
||||
warpFunc(v);
|
||||
s[i] = v2_to_pd(v);
|
||||
}
|
||||
warped.push_back(s);
|
||||
}
|
||||
return CrossSection(C2::Union(warped, C2::FillRule::Positive, precision_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove vertices from the contours in this CrossSection that are less than
|
||||
* the specified distance epsilon from an imaginary line that passes through
|
||||
* its two adjacent vertices. Near duplicate vertices and collinear points
|
||||
* will be removed at lower epsilons, with elimination of line segments
|
||||
* becoming increasingly aggressive with larger epsilons.
|
||||
*
|
||||
* It is recommended to apply this function following Offset, in order to
|
||||
* clean up any spurious tiny line segments introduced that do not improve
|
||||
* quality in any meaningful way. This is particularly important if further
|
||||
* offseting operations are to be performed, which would compound the issue.
|
||||
*/
|
||||
CrossSection CrossSection::Simplify(double epsilon) const {
|
||||
auto ps = SimplifyPaths(GetPaths(), epsilon, false);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the contours in CrossSection by the specified delta, handling
|
||||
* corners according to the given JoinType.
|
||||
*
|
||||
* @param delta Positive deltas will cause the expansion of outlining contours
|
||||
* to expand, and retraction of inner (hole) contours. Negative deltas will
|
||||
* have the opposite effect.
|
||||
* @param jt The join type specifying the treatment of contour joins
|
||||
* (corners).
|
||||
* @param miter_limit The maximum distance in multiples of delta that vertices
|
||||
* can be offset from their original positions with before squaring is
|
||||
* applied, <B>when the join type is Miter</B> (default is 2, which is the
|
||||
* minimum allowed). See the [Clipper2
|
||||
* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)
|
||||
* page for a visual example.
|
||||
* @param arc_tolerance The maximum acceptable imperfection for curves drawn
|
||||
* (approximated with line segments) for Round joins (not relevant for other
|
||||
* JoinTypes). By default (when undefined or =0), the allowable imprecision is
|
||||
* scaled in inverse proportion to the offset delta.
|
||||
*/
|
||||
CrossSection CrossSection::Offset(double delta, JoinType jointype,
|
||||
double miter_limit,
|
||||
double arc_tolerance) const {
|
||||
auto ps =
|
||||
C2::InflatePaths(GetPaths(), delta, jt(jointype), C2::EndType::Polygon,
|
||||
miter_limit, precision_, arc_tolerance);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total area covered by complex polygons making up the
|
||||
* CrossSection.
|
||||
*/
|
||||
double CrossSection::Area() const { return C2::Area(GetPaths()); }
|
||||
|
||||
/**
|
||||
* Return the number of vertices in the CrossSection.
|
||||
*/
|
||||
int CrossSection::NumVert() const {
|
||||
int n = 0;
|
||||
auto paths = GetPaths();
|
||||
for (auto p : paths) {
|
||||
n += p.size();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of contours (both outer and inner paths) in the
|
||||
* CrossSection.
|
||||
*/
|
||||
int CrossSection::NumContour() const { return GetPaths().size(); }
|
||||
|
||||
/**
|
||||
* Does the CrossSection contain any contours?
|
||||
*/
|
||||
bool CrossSection::IsEmpty() const { return GetPaths().empty(); }
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding rectangle of all the CrossSections'
|
||||
* vertices.
|
||||
*/
|
||||
Rect CrossSection::Bounds() const {
|
||||
auto r = C2::GetBounds(GetPaths());
|
||||
return Rect({r.left, r.bottom}, {r.right, r.top});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the contours of this CrossSection as a Polygons.
|
||||
*/
|
||||
Polygons CrossSection::ToPolygons() const {
|
||||
auto polys = Polygons();
|
||||
auto paths = GetPaths();
|
||||
polys.reserve(paths.size());
|
||||
for (auto p : paths) {
|
||||
auto sp = SimplePolygon();
|
||||
sp.reserve(p.size());
|
||||
for (auto v : p) {
|
||||
sp.push_back({v.x, v.y});
|
||||
}
|
||||
polys.push_back(sp);
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
|
||||
// Rect
|
||||
|
||||
/**
|
||||
* Default constructor is an empty rectangle..
|
||||
*/
|
||||
Rect::Rect() {}
|
||||
|
||||
Rect::~Rect() = default;
|
||||
Rect::Rect(Rect&&) noexcept = default;
|
||||
Rect& Rect::operator=(Rect&&) noexcept = default;
|
||||
Rect::Rect(const Rect& other) {
|
||||
min = glm::vec2(other.min);
|
||||
max = glm::vec2(other.max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a rectangle that contains the two given points.
|
||||
*/
|
||||
Rect::Rect(const glm::vec2 a, const glm::vec2 b) {
|
||||
min = glm::min(a, b);
|
||||
max = glm::max(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dimensions of the rectangle.
|
||||
*/
|
||||
glm::vec2 Rect::Size() const { return max - min; }
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
float Rect::Scale() const {
|
||||
glm::vec2 absMax = glm::max(glm::abs(min), glm::abs(max));
|
||||
return glm::max(absMax.x, absMax.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center point of the rectangle.
|
||||
*/
|
||||
glm::vec2 Rect::Center() const { return 0.5f * (max + min); }
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes equal) the given point?
|
||||
*/
|
||||
bool Rect::Contains(const glm::vec2& p) const {
|
||||
return glm::all(glm::greaterThanEqual(p, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes equal) the given rectangle?
|
||||
*/
|
||||
bool Rect::Contains(const Rect& rect) const {
|
||||
return glm::all(glm::greaterThanEqual(rect.min, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, rect.max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle overlap the one given (including equality)?
|
||||
*/
|
||||
bool Rect::DoesOverlap(const Rect& rect) const {
|
||||
return min.x <= rect.max.x && min.y <= rect.max.y && max.x >= rect.min.x &&
|
||||
max.y >= rect.min.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the rectangle empty (containing no space)?
|
||||
*/
|
||||
bool Rect::IsEmpty() const { return max.y <= min.y || max.x <= min.x; };
|
||||
|
||||
/**
|
||||
* Does this recangle have finite bounds?
|
||||
*/
|
||||
bool Rect::IsFinite() const {
|
||||
return glm::all(glm::isfinite(min)) && glm::all(glm::isfinite(max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this rectangle (in place) to include the given point.
|
||||
*/
|
||||
void Rect::Union(const glm::vec2 p) {
|
||||
min = glm::min(min, p);
|
||||
max = glm::max(max, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this rectangle to include the given Rect.
|
||||
*/
|
||||
Rect Rect::Union(const Rect& rect) const {
|
||||
Rect out;
|
||||
out.min = glm::min(min, rect.min);
|
||||
out.max = glm::max(max, rect.max);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle by the given vector.
|
||||
*/
|
||||
Rect Rect::operator+(const glm::vec2 shift) const {
|
||||
Rect out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& Rect::operator+=(const glm::vec2 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle by the given vector.
|
||||
*/
|
||||
Rect Rect::operator*(const glm::vec2 scale) const {
|
||||
Rect out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& Rect::operator*=(const glm::vec2 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the rectangle by the given axis-aligned affine transform.
|
||||
*
|
||||
* Ensure the transform passed in is axis-aligned (rotations are all
|
||||
* multiples of 90 degrees), or else the resulting rectangle will no longer
|
||||
* bound properly.
|
||||
*/
|
||||
Rect Rect::Transform(const glm::mat3x2& m) const {
|
||||
Rect rect;
|
||||
rect.min = m * glm::vec3(min, 1);
|
||||
rect.max = m * glm::vec3(max, 1);
|
||||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a CrossSection with an outline defined by this axis-aligned
|
||||
* rectangle.
|
||||
*/
|
||||
CrossSection Rect::AsCrossSection() const {
|
||||
SimplePolygon p(4);
|
||||
p[0] = glm::vec2(min.x, max.y);
|
||||
p[1] = glm::vec2(max.x, max.y);
|
||||
p[2] = glm::vec2(max.x, min.y);
|
||||
p[3] = glm::vec2(min.x, min.y);
|
||||
return CrossSection(p);
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
@@ -18,18 +18,16 @@ file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties( ${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite
|
||||
PUBLIC utilities cross_section
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite Clipper2
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
@@ -49,12 +50,11 @@ class Manifold {
|
||||
Manifold(Manifold&&) noexcept;
|
||||
Manifold& operator=(Manifold&&) noexcept;
|
||||
|
||||
Manifold(
|
||||
const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
Manifold(const MeshGL&, const std::vector<float>& propertyTolerance = {});
|
||||
Manifold(const Mesh&);
|
||||
|
||||
static Manifold Smooth(const MeshGL&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Smooth(const Mesh&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Tetrahedron();
|
||||
@@ -63,10 +63,10 @@ class Manifold {
|
||||
float radiusHigh = -1.0f, int circularSegments = 0,
|
||||
bool center = false);
|
||||
static Manifold Sphere(float radius, int circularSegments = 0);
|
||||
static Manifold Extrude(Polygons crossSection, float height,
|
||||
static Manifold Extrude(const CrossSection& crossSection, float height,
|
||||
int nDivisions = 0, float twistDegrees = 0.0f,
|
||||
glm::vec2 scaleTop = glm::vec2(1.0f));
|
||||
static Manifold Revolve(const Polygons& crossSection,
|
||||
static Manifold Revolve(const CrossSection& crossSection,
|
||||
int circularSegments = 0);
|
||||
///@}
|
||||
|
||||
@@ -78,41 +78,33 @@ class Manifold {
|
||||
std::vector<Manifold> Decompose() const;
|
||||
///@}
|
||||
|
||||
/** @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
static void SetMinCircularAngle(float degrees);
|
||||
static void SetMinCircularEdgeLength(float length);
|
||||
static void SetCircularSegments(int number);
|
||||
static int GetCircularSegments(float radius);
|
||||
///@}
|
||||
|
||||
/** @name Information
|
||||
* Details of the manifold
|
||||
*/
|
||||
///@{
|
||||
Mesh GetMesh() const;
|
||||
MeshGL GetMeshGL() const;
|
||||
MeshGL GetMeshGL(glm::ivec3 normalIdx = glm::ivec3(0)) const;
|
||||
bool IsEmpty() const;
|
||||
enum class Error {
|
||||
NO_ERROR,
|
||||
NON_FINITE_VERTEX,
|
||||
NOT_MANIFOLD,
|
||||
VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_OUT_OF_BOUNDS,
|
||||
NoError,
|
||||
NonFiniteVertex,
|
||||
NotManifold,
|
||||
VertexOutOfBounds,
|
||||
PropertiesWrongLength,
|
||||
MissingPositionProperties,
|
||||
MergeVectorsDifferentLengths,
|
||||
MergeIndexOutOfBounds,
|
||||
TransformWrongLength,
|
||||
RunIndexWrongLength,
|
||||
FaceIDWrongLength,
|
||||
InvalidConstruction,
|
||||
};
|
||||
Error Status() const;
|
||||
int NumVert() const;
|
||||
int NumEdge() const;
|
||||
int NumTri() const;
|
||||
int NumProp() const;
|
||||
int NumPropVert() const;
|
||||
Box BoundingBox() const;
|
||||
float Precision() const;
|
||||
int Genus() const;
|
||||
@@ -120,14 +112,14 @@ class Manifold {
|
||||
Curvature GetCurvature() const;
|
||||
///@}
|
||||
|
||||
/** @name Relation
|
||||
/** @name Mesh ID
|
||||
* Details of the manifold's relation to its input meshes, for the purposes
|
||||
* of reapplying mesh properties.
|
||||
*/
|
||||
///@{
|
||||
MeshRelation GetMeshRelation() const;
|
||||
int OriginalID() const;
|
||||
Manifold AsOriginal() const;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
@@ -138,6 +130,7 @@ class Manifold {
|
||||
Manifold Rotate(float xDegrees, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f) const;
|
||||
Manifold Transform(const glm::mat4x3&) const;
|
||||
Manifold Mirror(glm::vec3) const;
|
||||
Manifold Warp(std::function<void(glm::vec3&)>) const;
|
||||
Manifold Refine(int) const;
|
||||
// Manifold RefineToLength(float);
|
||||
@@ -148,19 +141,15 @@ class Manifold {
|
||||
* Combine two manifolds
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* Boolean operation type: ADD (Union), SUBTRACT (Difference), and INTERSECT.
|
||||
*/
|
||||
enum class OpType { ADD, SUBTRACT, INTERSECT };
|
||||
Manifold Boolean(const Manifold& second, OpType op) const;
|
||||
static Manifold BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
OpType op);
|
||||
// Boolean operation shorthand
|
||||
Manifold operator+(const Manifold&) const; // ADD (Union)
|
||||
Manifold operator+(const Manifold&) const; // Add (Union)
|
||||
Manifold& operator+=(const Manifold&);
|
||||
Manifold operator-(const Manifold&) const; // SUBTRACT (Difference)
|
||||
Manifold operator-(const Manifold&) const; // Subtract (Difference)
|
||||
Manifold& operator-=(const Manifold&);
|
||||
Manifold operator^(const Manifold&) const; // INTERSECT
|
||||
Manifold operator^(const Manifold&) const; // Intersect
|
||||
Manifold& operator^=(const Manifold&);
|
||||
std::pair<Manifold, Manifold> Split(const Manifold&) const;
|
||||
std::pair<Manifold, Manifold> SplitByPlane(glm::vec3 normal,
|
||||
@@ -183,14 +172,11 @@ class Manifold {
|
||||
private:
|
||||
Manifold(std::shared_ptr<CsgNode> pNode_);
|
||||
Manifold(std::shared_ptr<Impl> pImpl_);
|
||||
static Manifold Invalid();
|
||||
|
||||
mutable std::shared_ptr<CsgNode> pNode_;
|
||||
|
||||
CsgLeafNode& GetCsgLeafNode() const;
|
||||
|
||||
static int circularSegments_;
|
||||
static float circularAngle_;
|
||||
static float circularEdgeLength_;
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
|
||||
@@ -512,10 +512,10 @@ VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
|
||||
|
||||
namespace manifold {
|
||||
Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
Manifold::OpType op)
|
||||
OpType op)
|
||||
: inP_(inP),
|
||||
inQ_(inQ),
|
||||
expandP_(op == Manifold::OpType::ADD ? 1.0 : -1.0),
|
||||
expandP_(op == OpType::Add ? 1.0 : -1.0),
|
||||
policy_(autoPolicy(glm::max(inP.NumEdge(), inQ.NumEdge()))) {
|
||||
// Symbolic perturbation:
|
||||
// Union -> expand inP
|
||||
|
||||
@@ -47,9 +47,8 @@ namespace manifold {
|
||||
/** @ingroup Private */
|
||||
class Boolean3 {
|
||||
public:
|
||||
Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
|
||||
Manifold::OpType op);
|
||||
Manifold::Impl Result(Manifold::OpType op) const;
|
||||
Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ, OpType op);
|
||||
Manifold::Impl Result(OpType op) const;
|
||||
|
||||
private:
|
||||
const Manifold::Impl &inP_, &inQ_;
|
||||
|
||||
@@ -197,17 +197,10 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
|
||||
return edges;
|
||||
}
|
||||
|
||||
// A Ref carries the reference of a halfedge's startVert back to the input
|
||||
// manifolds. PQ is 0 if the halfedge comes from triangle tri of P, and 1 for Q.
|
||||
// vert is 0, 1, or 2 to denote which vertex of tri it is, and -1 if it is new.
|
||||
struct Ref {
|
||||
int PQ, tri, vert;
|
||||
};
|
||||
|
||||
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
VecDH<int> &facePtrR,
|
||||
std::map<int, std::vector<EdgePos>> &edgesP,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<int> &i03, const VecDH<int> &vP2R,
|
||||
const VecDH<int>::IterC faceP2R, bool forward) {
|
||||
// Each edge in the map is partially retained; for each of these, look up
|
||||
@@ -266,11 +259,8 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW. This is only valid if this is a retained vert; it
|
||||
// will be ignored later if the vert is new.
|
||||
const Ref forwardRef = {forward ? 0 : 1, faceLeftP,
|
||||
(edgeP + (reversed ? 1 : 0)) % 3};
|
||||
const Ref backwardRef = {
|
||||
forward ? 0 : 1, faceRightP,
|
||||
(halfedge.pairedHalfedge + (reversed ? 1 : 0)) % 3};
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
@@ -293,7 +283,8 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
void AppendNewEdges(
|
||||
Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
VecDH<Ref> &halfedgeRef, const VecDH<int> &facePQ2R, const int numFaceP) {
|
||||
VecDH<TriRef> &halfedgeRef, const VecDH<int> &facePQ2R,
|
||||
const int numFaceP) {
|
||||
// Pair up each edge's verts and distribute to faces based on indices in key.
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
|
||||
@@ -322,8 +313,8 @@ void AppendNewEdges(
|
||||
// add halfedges to result
|
||||
const int faceLeft = facePQ2R[faceP];
|
||||
const int faceRight = facePQ2R[numFaceP + faceQ];
|
||||
const Ref forwardRef = {0, faceP, -4};
|
||||
const Ref backwardRef = {1, faceQ, -4};
|
||||
const TriRef forwardRef = {0, -1, faceP};
|
||||
const TriRef backwardRef = {1, -1, faceQ};
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
const int backwardEdge = facePtrR[faceRight]++;
|
||||
@@ -344,7 +335,7 @@ void AppendNewEdges(
|
||||
|
||||
struct DuplicateHalfedges {
|
||||
Halfedge *halfedgesR;
|
||||
Ref *halfedgeRef;
|
||||
TriRef *halfedgeRef;
|
||||
int *facePtr;
|
||||
const Halfedge *halfedgesP;
|
||||
const int *i03;
|
||||
@@ -374,11 +365,8 @@ struct DuplicateHalfedges {
|
||||
// Negative inclusion means the halfedges are reversed, which means our
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW.
|
||||
const Ref forwardRef = {forward ? 0 : 1, faceLeftP,
|
||||
(edgeP + (inclusion < 0 ? 1 : 0)) % 3};
|
||||
const Ref backwardRef = {
|
||||
forward ? 0 : 1, faceRightP,
|
||||
(halfedge.pairedHalfedge + (inclusion < 0 ? 1 : 0)) % 3};
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (int i = 0; i < glm::abs(inclusion); ++i) {
|
||||
int forwardEdge = AtomicAdd(facePtr[halfedge.face], 1);
|
||||
@@ -398,7 +386,7 @@ struct DuplicateHalfedges {
|
||||
};
|
||||
|
||||
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<char> wholeHalfedgeP, const VecDH<int> &i03,
|
||||
const VecDH<int> &vP2R, const int *faceP2R, bool forward,
|
||||
ExecutionPolicy policy) {
|
||||
@@ -410,127 +398,172 @@ void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
|
||||
}
|
||||
|
||||
struct CreateBarycentric {
|
||||
glm::vec3 *barycentricR;
|
||||
BaryRef *faceRef;
|
||||
int *idx;
|
||||
|
||||
struct MapTriRef {
|
||||
const TriRef *triRefP;
|
||||
const TriRef *triRefQ;
|
||||
const int offsetQ;
|
||||
const int firstNewVert;
|
||||
const glm::vec3 *vertPosR;
|
||||
|
||||
__host__ __device__ void operator()(TriRef &triRef) {
|
||||
const int tri = triRef.tri;
|
||||
const bool PQ = triRef.meshID == 0;
|
||||
triRef = PQ ? triRefP[tri] : triRefQ[tri];
|
||||
if (!PQ) triRef.meshID += offsetQ;
|
||||
}
|
||||
};
|
||||
|
||||
VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ, bool invertQ,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<TriRef> refPQ = outR.meshRelation_.triRef;
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
for_each_n(policy, outR.meshRelation_.triRef.begin(), outR.NumTri(),
|
||||
MapTriRef({inP.meshRelation_.triRef.cptrD(),
|
||||
inQ.meshRelation_.triRef.cptrD(), offsetQ}));
|
||||
|
||||
for (const auto &pair : inP.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
for (const auto &pair : inQ.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ] = pair.second;
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
|
||||
invertQ;
|
||||
}
|
||||
return refPQ;
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
glm::vec3 *uvw;
|
||||
const glm::vec3 *vertPosP;
|
||||
const glm::vec3 *vertPosQ;
|
||||
const glm::vec3 *vertPosR;
|
||||
const Halfedge *halfedgeP;
|
||||
const Halfedge *halfedgeQ;
|
||||
const BaryRef *triBaryP;
|
||||
const BaryRef *triBaryQ;
|
||||
const glm::vec3 *barycentricP;
|
||||
const glm::vec3 *barycentricQ;
|
||||
const bool invertQ;
|
||||
const Halfedge *halfedgeR;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<int &, Ref, Halfedge> inOut) {
|
||||
int &halfedgeBary = thrust::get<0>(inOut);
|
||||
const Ref halfedgeRef = thrust::get<1>(inOut);
|
||||
const Halfedge halfedgeR = thrust::get<2>(inOut);
|
||||
__host__ __device__ void operator()(thrust::tuple<int, TriRef> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const TriRef refPQ = thrust::get<1>(in);
|
||||
if (halfedgeR[3 * tri].startVert < 0) return;
|
||||
|
||||
const glm::vec3 *barycentric =
|
||||
halfedgeRef.PQ == 0 ? barycentricP : barycentricQ;
|
||||
const int tri = halfedgeRef.tri;
|
||||
const BaryRef oldRef = halfedgeRef.PQ == 0 ? triBaryP[tri] : triBaryQ[tri];
|
||||
const int triPQ = refPQ.tri;
|
||||
const bool PQ = refPQ.meshID == 0;
|
||||
const auto &vertPos = PQ ? vertPosP : vertPosQ;
|
||||
const auto &halfedge = PQ ? halfedgeP : halfedgeQ;
|
||||
|
||||
faceRef[halfedgeR.face] = oldRef;
|
||||
glm::mat3 triPos;
|
||||
for (const int j : {0, 1, 2})
|
||||
triPos[j] = vertPos[halfedge[3 * triPQ + j].startVert];
|
||||
|
||||
if (halfedgeRef.PQ == 1) {
|
||||
// Mark the meshID as coming from Q
|
||||
faceRef[halfedgeR.face].meshID += offsetQ;
|
||||
}
|
||||
|
||||
if (halfedgeR.startVert < firstNewVert) { // retained vert
|
||||
int i = halfedgeRef.vert;
|
||||
const int bary = oldRef.vertBary[i];
|
||||
if (bary < 0) {
|
||||
halfedgeBary = bary;
|
||||
} else {
|
||||
halfedgeBary = AtomicAdd(*idx, 1);
|
||||
barycentricR[halfedgeBary] = barycentric[bary];
|
||||
}
|
||||
} else { // new vert
|
||||
halfedgeBary = AtomicAdd(*idx, 1);
|
||||
|
||||
const glm::vec3 *vertPos = halfedgeRef.PQ == 0 ? vertPosP : vertPosQ;
|
||||
const Halfedge *halfedge = halfedgeRef.PQ == 0 ? halfedgeP : halfedgeQ;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2})
|
||||
triPos[i] = vertPos[halfedge[3 * tri + i].startVert];
|
||||
|
||||
glm::mat3 uvwOldTri;
|
||||
for (int i : {0, 1, 2})
|
||||
uvwOldTri[i] = UVW(oldRef.vertBary[i], barycentric);
|
||||
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPosR[halfedgeR.startVert], triPos, precision);
|
||||
barycentricR[halfedgeBary] = uvwOldTri * uvw;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = halfedgeR[3 * tri + i].startVert;
|
||||
uvw[3 * tri + i] = GetBarycentric(vertPosR[vert], triPos, precision);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
|
||||
Manifold::Impl &outR, const VecDH<Ref> &halfedgeRef,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, int firstNewVert,
|
||||
int numFaceR, bool invertQ, ExecutionPolicy policy) {
|
||||
outR.meshRelation_.barycentric.resize(outR.halfedge_.size());
|
||||
VecDH<BaryRef> faceRef(numFaceR);
|
||||
VecDH<int> halfedgeBary(halfedgeRef.size());
|
||||
void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
ExecutionPolicy policy) {
|
||||
const int numPropP = inP.NumProp();
|
||||
const int numPropQ = inQ.NumProp();
|
||||
const int numProp = glm::max(numPropP, numPropQ);
|
||||
outR.meshRelation_.numProp = numProp;
|
||||
if (numProp == 0) return;
|
||||
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
VecDH<int> idx(1, 0);
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(halfedgeBary.begin(), halfedgeRef.begin(), outR.halfedge_.cbegin()),
|
||||
halfedgeRef.size(),
|
||||
CreateBarycentric(
|
||||
{outR.meshRelation_.barycentric.ptrD(), faceRef.ptrD(), idx.ptrD(),
|
||||
offsetQ, firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
inP.meshRelation_.triBary.cptrD(), inQ.meshRelation_.triBary.cptrD(),
|
||||
inP.meshRelation_.barycentric.cptrD(),
|
||||
inQ.meshRelation_.barycentric.cptrD(), invertQ, outR.precision_}));
|
||||
outR.meshRelation_.barycentric.resize(idx[0]);
|
||||
const int numTri = outR.NumTri();
|
||||
outR.meshRelation_.triProperties.resize(numTri);
|
||||
|
||||
return std::make_pair(faceRef, halfedgeBary);
|
||||
VecDH<glm::vec3> bary(outR.halfedge_.size());
|
||||
for_each_n(policy, zip(countAt(0), refPQ.cbegin()), numTri,
|
||||
Barycentric({bary.ptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), outR.vertPos_.cptrD(),
|
||||
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
outR.halfedge_.cptrD(), outR.precision_}));
|
||||
|
||||
std::map<std::tuple<int, int, int, int>, int> propIdx;
|
||||
int idx = 0;
|
||||
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
// Skip collapsed triangles
|
||||
if (outR.halfedge_[3 * tri].startVert < 0) continue;
|
||||
|
||||
const int triPQ = refPQ[tri].tri;
|
||||
const bool PQ = refPQ[tri].meshID == 0;
|
||||
const auto &properties =
|
||||
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties;
|
||||
const auto &triProp = PQ ? inP.meshRelation_.triProperties[triPQ]
|
||||
: inQ.meshRelation_.triProperties[triPQ];
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = outR.halfedge_[3 * tri + i].startVert;
|
||||
const glm::vec3 uvw = bary[3 * tri + i];
|
||||
|
||||
auto key = std::make_tuple(PQ, vert, -1, -1);
|
||||
int edge = -1;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (uvw[j] == 1) {
|
||||
// On a retained vert, the propVert must also match
|
||||
std::get<2>(key) = triProp[j];
|
||||
edge = -1;
|
||||
break;
|
||||
}
|
||||
if (uvw[j] == 0) edge = j;
|
||||
}
|
||||
if (edge >= 0) {
|
||||
// On an edge, both propVerts must match
|
||||
const int p0 = triProp[Next3(edge)];
|
||||
const int p1 = triProp[Prev3(edge)];
|
||||
std::get<2>(key) = glm::min(p0, p1);
|
||||
std::get<3>(key) = glm::max(p0, p1);
|
||||
}
|
||||
|
||||
const auto it = propIdx.find(key);
|
||||
if (it != propIdx.end()) {
|
||||
outR.meshRelation_.triProperties[tri][i] = it->second;
|
||||
continue;
|
||||
}
|
||||
outR.meshRelation_.triProperties[tri][i] = idx;
|
||||
propIdx.insert({key, idx++});
|
||||
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
glm::vec3 oldProps;
|
||||
for (const int j : {0, 1, 2})
|
||||
oldProps[j] = properties[numProp * triProp[j] + p];
|
||||
outR.meshRelation_.properties.push_back(glm::dot(uvw, oldProps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
Timer assemble;
|
||||
assemble.Start();
|
||||
#endif
|
||||
|
||||
ASSERT((expandP_ > 0) == (op == Manifold::OpType::ADD), logicErr,
|
||||
ASSERT((expandP_ > 0) == (op == OpType::Add), logicErr,
|
||||
"Result op type not compatible with constructor op type.");
|
||||
const int c1 = op == Manifold::OpType::INTERSECT ? 0 : 1;
|
||||
const int c2 = op == Manifold::OpType::ADD ? 1 : 0;
|
||||
const int c3 = op == Manifold::OpType::INTERSECT ? 1 : -1;
|
||||
const int c1 = op == OpType::Intersect ? 0 : 1;
|
||||
const int c2 = op == OpType::Add ? 1 : 0;
|
||||
const int c3 = op == OpType::Intersect ? 1 : -1;
|
||||
|
||||
if (w03_.size() == 0) {
|
||||
if (w30_.size() != 0 && op == Manifold::OpType::ADD) {
|
||||
if (w30_.size() != 0 && op == OpType::Add) {
|
||||
return inQ_;
|
||||
}
|
||||
return Manifold::Impl();
|
||||
} else if (w30_.size() == 0) {
|
||||
if (op == Manifold::OpType::INTERSECT) {
|
||||
if (op == OpType::Intersect) {
|
||||
return Manifold::Impl();
|
||||
}
|
||||
return inP_;
|
||||
}
|
||||
|
||||
const bool invertQ = op == Manifold::OpType::SUBTRACT;
|
||||
const bool invertQ = op == OpType::Subtract;
|
||||
|
||||
// Convert winding numbers to inclusion values based on operation type.
|
||||
VecDH<int> i12(x12_.size());
|
||||
@@ -622,9 +655,9 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
// Intersected halfedges are marked false.
|
||||
VecDH<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
VecDH<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
// The halfedgeRef contains the data that will become triBary once the faces
|
||||
// The halfedgeRef contains the data that will become triRef once the faces
|
||||
// are triangulated.
|
||||
VecDH<Ref> halfedgeRef(2 * outR.NumEdge());
|
||||
VecDH<TriRef> halfedgeRef(2 * outR.NumEdge());
|
||||
|
||||
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
|
||||
i03, vP2R, facePQ2R.begin(), true);
|
||||
@@ -639,11 +672,6 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
|
||||
facePQ2R.cptrD() + inP_.NumTri(), false, policy_);
|
||||
|
||||
VecDH<BaryRef> faceRef;
|
||||
VecDH<int> halfedgeBary;
|
||||
std::tie(faceRef, halfedgeBary) = CalculateMeshRelation(
|
||||
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ, policy_);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
assemble.Stop();
|
||||
Timer triangulate;
|
||||
@@ -655,7 +683,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
|
||||
|
||||
outR.Face2Tri(faceEdge, faceRef, halfedgeBary);
|
||||
outR.Face2Tri(faceEdge, halfedgeRef);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
triangulate.Stop();
|
||||
@@ -666,13 +694,15 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
|
||||
|
||||
VecDH<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ, policy_);
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
CreateProperties(outR, refPQ, inP_, inQ_, policy_);
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
|
||||
|
||||
outR.IncrementMeshIDs(0, outR.NumTri());
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
simplify.Stop();
|
||||
Timer sort;
|
||||
@@ -680,6 +710,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
#endif
|
||||
|
||||
outR.Finish();
|
||||
outR.IncrementMeshIDs();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
sort.Stop();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <thrust/sequence.h>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "csg_tree.h"
|
||||
#include "graph.h"
|
||||
#include "impl.h"
|
||||
@@ -50,6 +51,48 @@ struct RemoveFace {
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param meshGL input MeshGL.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
Manifold Manifold::Smooth(const MeshGL& meshGL,
|
||||
const std::vector<Smoothness>& sharpenedEdges) {
|
||||
ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
// Don't allow any triangle merging.
|
||||
std::vector<float> propertyTolerance(meshGL.numProp - 3, -1);
|
||||
std::shared_ptr<Impl> impl =
|
||||
std::make_shared<Impl>(meshGL, propertyTolerance);
|
||||
impl->CreateTangents(sharpenedEdges);
|
||||
return Manifold(impl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
@@ -84,7 +127,8 @@ Manifold Manifold::Smooth(const Mesh& mesh,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh);
|
||||
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh, relation);
|
||||
impl->CreateTangents(sharpenedEdges);
|
||||
return Manifold(impl);
|
||||
}
|
||||
@@ -94,21 +138,26 @@ Manifold Manifold::Smooth(const Mesh& mesh,
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
Manifold Manifold::Tetrahedron() {
|
||||
return Manifold(std::make_shared<Impl>(Impl::Shape::TETRAHEDRON));
|
||||
return Manifold(std::make_shared<Impl>(Impl::Shape::Tetrahedron));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
* octant, touching the origin. If any dimensions in size are negative, or if
|
||||
* all are zero, an empty Manifold will be returned.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
Manifold Manifold::Cube(glm::vec3 size, bool center) {
|
||||
auto cube = Manifold(std::make_shared<Impl>(Impl::Shape::CUBE));
|
||||
if (size.x < 0.0f || size.y < 0.0f || size.z < 0.0f ||
|
||||
glm::length(size) == 0.) {
|
||||
return Invalid();
|
||||
}
|
||||
auto cube = Manifold(std::make_shared<Impl>(Impl::Shape::Cube));
|
||||
cube = cube.Scale(size);
|
||||
if (center) cube = cube.Translate(-size / 2.0f);
|
||||
return cube;
|
||||
return cube.AsOriginal();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,19 +175,20 @@ Manifold Manifold::Cube(glm::vec3 size, bool center) {
|
||||
*/
|
||||
Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
int circularSegments, bool center) {
|
||||
if (height <= 0.0f || radiusLow <= 0.0f) {
|
||||
return Invalid();
|
||||
}
|
||||
float scale = radiusHigh >= 0.0f ? radiusHigh / radiusLow : 1.0f;
|
||||
float radius = fmax(radiusLow, radiusHigh);
|
||||
int n = circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back(
|
||||
{radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)), 0});
|
||||
}
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
|
||||
CrossSection circle = CrossSection::Circle(radiusLow, n);
|
||||
Manifold cylinder =
|
||||
Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale));
|
||||
if (center)
|
||||
cylinder = cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f));
|
||||
cylinder =
|
||||
cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f)).AsOriginal();
|
||||
return cylinder;
|
||||
}
|
||||
|
||||
@@ -153,15 +203,18 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
if (radius <= 0.0f) {
|
||||
return Invalid();
|
||||
}
|
||||
int n = circularSegments > 0 ? (circularSegments + 3) / 4
|
||||
: GetCircularSegments(radius) / 4;
|
||||
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::OCTAHEDRON);
|
||||
: Quality::GetCircularSegments(radius) / 4;
|
||||
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);
|
||||
pImpl_->Subdivide(n);
|
||||
for_each_n(autoPolicy(pImpl_->NumVert()), pImpl_->vertPos_.begin(),
|
||||
pImpl_->NumVert(), ToSphere({radius}));
|
||||
pImpl_->Finish();
|
||||
// Ignore preceding octahedron.
|
||||
pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1));
|
||||
pImpl_->InitializeOriginal();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -180,8 +233,14 @@ Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
float twistDegrees, glm::vec2 scaleTop) {
|
||||
Manifold Manifold::Extrude(const CrossSection& crossSection, float height,
|
||||
int nDivisions, float twistDegrees,
|
||||
glm::vec2 scaleTop) {
|
||||
auto polygons = crossSection.ToPolygons();
|
||||
if (polygons.size() == 0 || height <= 0.0f) {
|
||||
return Invalid();
|
||||
}
|
||||
|
||||
scaleTop.x = glm::max(scaleTop.x, 0.0f);
|
||||
scaleTop.y = glm::max(scaleTop.y, 0.0f);
|
||||
|
||||
@@ -193,12 +252,15 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
int nCrossSection = 0;
|
||||
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
|
||||
int idx = 0;
|
||||
for (auto& poly : crossSection) {
|
||||
PolygonsIdx polygonsIndexed;
|
||||
for (auto& poly : polygons) {
|
||||
nCrossSection += poly.size();
|
||||
for (PolyVert& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.pos.x, polyVert.pos.y, 0.0f});
|
||||
polyVert.idx = idx++;
|
||||
SimplePolygonIdx simpleIndexed;
|
||||
for (const glm::vec2& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.x, polyVert.y, 0.0f});
|
||||
simpleIndexed.push_back({polyVert, idx++});
|
||||
}
|
||||
polygonsIndexed.push_back(simpleIndexed);
|
||||
}
|
||||
for (int i = 1; i < nDivisions + 1; ++i) {
|
||||
float alpha = i / float(nDivisions);
|
||||
@@ -208,7 +270,7 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
transform = transform * glm::mat2(scale.x, 0.0f, 0.0f, scale.y);
|
||||
int j = 0;
|
||||
int idx = 0;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
for (int vert = 0; vert < poly.size(); ++vert) {
|
||||
int offset = idx + nCrossSection * i;
|
||||
int thisVert = vert + offset;
|
||||
@@ -217,7 +279,7 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
triVerts.push_back({nCrossSection * i + j, lastVert - nCrossSection,
|
||||
thisVert - nCrossSection});
|
||||
} else {
|
||||
glm::vec2 pos = transform * poly[vert].pos;
|
||||
glm::vec2 pos = transform * poly[vert];
|
||||
vertPos.push_back({pos.x, pos.y, height * alpha});
|
||||
triVerts.push_back({thisVert, lastVert, thisVert - nCrossSection});
|
||||
triVerts.push_back(
|
||||
@@ -229,9 +291,9 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
}
|
||||
}
|
||||
if (isCone)
|
||||
for (int j = 0; j < crossSection.size(); ++j) // Duplicate vertex for Genus
|
||||
for (int j = 0; j < polygons.size(); ++j) // Duplicate vertex for Genus
|
||||
vertPos.push_back({0.0f, 0.0f, height});
|
||||
std::vector<glm::ivec3> top = Triangulate(crossSection);
|
||||
std::vector<glm::ivec3> top = TriangulateIdx(polygonsIndexed);
|
||||
for (const glm::ivec3& tri : top) {
|
||||
triVerts.push_back({tri[0], tri[2], tri[1]});
|
||||
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
|
||||
@@ -239,7 +301,9 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeNewReference();
|
||||
pImpl_->meshRelation_.originalID = ReserveIDs(1);
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -254,24 +318,30 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
Manifold Manifold::Revolve(const CrossSection& crossSection,
|
||||
int circularSegments) {
|
||||
auto polygons = crossSection.ToPolygons();
|
||||
if (polygons.size() == 0) {
|
||||
return Invalid();
|
||||
}
|
||||
|
||||
float radius = 0.0f;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
for (const auto& vert : poly) {
|
||||
radius = fmax(radius, vert.pos.x);
|
||||
radius = fmax(radius, vert.x);
|
||||
}
|
||||
}
|
||||
int nDivisions =
|
||||
circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
int nDivisions = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(radius);
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
VecDH<glm::ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH;
|
||||
float dPhi = 360.0f / nDivisions;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
int start = -1;
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
if (poly[polyVert].pos.x <= 0) {
|
||||
if (poly[polyVert].x <= 0) {
|
||||
start = polyVert;
|
||||
break;
|
||||
}
|
||||
@@ -285,7 +355,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
|
||||
triVerts.push_back({startVert + slice, startVert + lastSlice,
|
||||
lastStart + lastSlice});
|
||||
@@ -295,11 +365,11 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
}
|
||||
} else { // poly crosses zero
|
||||
int polyVert = start;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
do {
|
||||
glm::vec2 lastPos = pos;
|
||||
polyVert = (polyVert + 1) % poly.size();
|
||||
pos = poly[polyVert].pos;
|
||||
pos = poly[polyVert];
|
||||
if (pos.x > 0) {
|
||||
if (lastPos.x <= 0) {
|
||||
float a = pos.x / (pos.x - lastPos.x);
|
||||
@@ -309,7 +379,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
|
||||
if (lastPos.x > 0) {
|
||||
triVerts.push_back({startVert + slice, startVert + lastSlice,
|
||||
@@ -338,7 +408,9 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeNewReference();
|
||||
pImpl_->meshRelation_.originalID = ReserveIDs(1);
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -372,18 +444,18 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
if (halfedge.IsForward())
|
||||
graph.add_edge(halfedge.startVert, halfedge.endVert);
|
||||
}
|
||||
std::vector<int> components;
|
||||
const int numLabel = ConnectedComponents(components, graph);
|
||||
std::vector<int> componentIndices;
|
||||
const int numComponents = ConnectedComponents(componentIndices, graph);
|
||||
|
||||
if (numLabel == 1) {
|
||||
if (numComponents == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
VecDH<int> vertLabel(components);
|
||||
VecDH<int> vertLabel(componentIndices);
|
||||
|
||||
std::vector<Manifold> meshes;
|
||||
for (int i = 0; i < numLabel; ++i) {
|
||||
for (int i = 0; i < numComponents; ++i) {
|
||||
auto impl = std::make_shared<Impl>();
|
||||
// inherit original object's precision
|
||||
impl->precision_ = pImpl_->precision_;
|
||||
|
||||
@@ -40,16 +40,6 @@ struct TransformNormals {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateTriBary {
|
||||
const int nextBary;
|
||||
|
||||
__host__ __device__ BaryRef operator()(BaryRef ref) {
|
||||
for (int i : {0, 1, 2})
|
||||
if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateHalfedge {
|
||||
const int nextVert;
|
||||
const int nextEdge;
|
||||
@@ -64,6 +54,15 @@ struct UpdateHalfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshIDs {
|
||||
const int offset;
|
||||
|
||||
__host__ __device__ TriRef operator()(TriRef ref) {
|
||||
ref.meshID += offset;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckOverlap {
|
||||
const Box *boxes;
|
||||
const size_t i;
|
||||
@@ -74,6 +73,19 @@ struct CheckOverlap {
|
||||
} // namespace
|
||||
namespace manifold {
|
||||
|
||||
std::shared_ptr<CsgNode> CsgNode::Boolean(
|
||||
const std::shared_ptr<CsgNode> &second, OpType op) {
|
||||
if (auto opNode = std::dynamic_pointer_cast<CsgOpNode>(second)) {
|
||||
// "this" is not a CsgOpNode (which overrides Boolean), but if "second" is
|
||||
// and the operation is commutative, we let it built the tree.
|
||||
if ((op == OpType::Add || op == OpType::Intersect)) {
|
||||
return opNode->Boolean(shared_from_this(), op);
|
||||
}
|
||||
}
|
||||
std::vector<std::shared_ptr<CsgNode>> children({shared_from_this(), second});
|
||||
return std::make_shared<CsgOpNode>(children, op);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgNode::Translate(const glm::vec3 &t) const {
|
||||
glm::mat4x3 transform(1.0f);
|
||||
transform[3] += t;
|
||||
@@ -128,7 +140,7 @@ std::shared_ptr<CsgNode> CsgLeafNode::Transform(const glm::mat4x3 &m) const {
|
||||
return std::make_shared<CsgLeafNode>(pImpl_, m * glm::mat4(transform_));
|
||||
}
|
||||
|
||||
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::LEAF; }
|
||||
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; }
|
||||
|
||||
/**
|
||||
* Efficient union of a set of pairwise disjoint meshes.
|
||||
@@ -139,7 +151,9 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 0;
|
||||
int numBary = 0;
|
||||
std::vector<int> vertIndices;
|
||||
std::vector<int> edgeIndices;
|
||||
std::vector<int> triIndices;
|
||||
for (auto &node : nodes) {
|
||||
float nodeOldScale = node->pImpl_->bBox_.Scale();
|
||||
float nodeNewScale =
|
||||
@@ -150,10 +164,12 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
if (!glm::isfinite(nodePrecision)) nodePrecision = -1;
|
||||
precision = glm::max(precision, nodePrecision);
|
||||
|
||||
vertIndices.push_back(numVert);
|
||||
edgeIndices.push_back(numEdge * 2);
|
||||
triIndices.push_back(numTri);
|
||||
numVert += node->pImpl_->NumVert();
|
||||
numEdge += node->pImpl_->NumEdge();
|
||||
numTri += node->pImpl_->NumTri();
|
||||
numBary += node->pImpl_->meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
Manifold::Impl combined;
|
||||
@@ -162,117 +178,171 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
combined.halfedge_.resize(2 * numEdge);
|
||||
combined.faceNormal_.resize(numTri);
|
||||
combined.halfedgeTangent_.resize(2 * numEdge);
|
||||
combined.meshRelation_.barycentric.resize(numBary);
|
||||
combined.meshRelation_.triBary.resize(numTri);
|
||||
combined.meshRelation_.triRef.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
int nextVert = 0;
|
||||
int nextEdge = 0;
|
||||
int nextTri = 0;
|
||||
int nextBary = 0;
|
||||
for (auto &node : nodes) {
|
||||
if (node->transform_ == glm::mat4x3(1.0f)) {
|
||||
copy(policy, node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),
|
||||
combined.vertPos_.begin() + nextVert);
|
||||
copy(policy, node->pImpl_->faceNormal_.begin(),
|
||||
node->pImpl_->faceNormal_.end(),
|
||||
combined.faceNormal_.begin() + nextTri);
|
||||
} else {
|
||||
// no need to apply the transform to the node, just copy the vertices and
|
||||
// face normals and apply transform on the fly
|
||||
auto vertPosBegin = thrust::make_transform_iterator(
|
||||
node->pImpl_->vertPos_.begin(), Transform4x3({node->transform_}));
|
||||
glm::mat3 normalTransform =
|
||||
glm::inverse(glm::transpose(glm::mat3(node->transform_)));
|
||||
auto faceNormalBegin =
|
||||
thrust::make_transform_iterator(node->pImpl_->faceNormal_.begin(),
|
||||
TransformNormals({normalTransform}));
|
||||
copy_n(policy, vertPosBegin, node->pImpl_->vertPos_.size(),
|
||||
combined.vertPos_.begin() + nextVert);
|
||||
copy_n(policy, faceNormalBegin, node->pImpl_->faceNormal_.size(),
|
||||
combined.faceNormal_.begin() + nextTri);
|
||||
// if we are already parallelizing for each node, do not perform multithreaded
|
||||
// copying as it will slightly hurt performance
|
||||
if (nodes.size() > 1 &&
|
||||
(policy == ExecutionPolicy::Par ||
|
||||
(policy == ExecutionPolicy::ParUnseq && !CudaEnabled())))
|
||||
policy = ExecutionPolicy::Seq;
|
||||
|
||||
for_each_n_host(
|
||||
nodes.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
|
||||
countAt(0), nodes.size(),
|
||||
[&nodes, &vertIndices, &edgeIndices, &triIndices, &combined,
|
||||
policy](int i) {
|
||||
auto &node = nodes[i];
|
||||
if (node->transform_ == glm::mat4x3(1.0f)) {
|
||||
copy(policy, node->pImpl_->vertPos_.begin(),
|
||||
node->pImpl_->vertPos_.end(),
|
||||
combined.vertPos_.begin() + vertIndices[i]);
|
||||
copy(policy, node->pImpl_->faceNormal_.begin(),
|
||||
node->pImpl_->faceNormal_.end(),
|
||||
combined.faceNormal_.begin() + triIndices[i]);
|
||||
} else {
|
||||
// no need to apply the transform to the node, just copy the vertices
|
||||
// and face normals and apply transform on the fly
|
||||
auto vertPosBegin = thrust::make_transform_iterator(
|
||||
node->pImpl_->vertPos_.begin(), Transform4x3({node->transform_}));
|
||||
glm::mat3 normalTransform =
|
||||
glm::inverse(glm::transpose(glm::mat3(node->transform_)));
|
||||
auto faceNormalBegin = thrust::make_transform_iterator(
|
||||
node->pImpl_->faceNormal_.begin(),
|
||||
TransformNormals({normalTransform}));
|
||||
copy_n(policy, vertPosBegin, node->pImpl_->vertPos_.size(),
|
||||
combined.vertPos_.begin() + vertIndices[i]);
|
||||
copy_n(policy, faceNormalBegin, node->pImpl_->faceNormal_.size(),
|
||||
combined.faceNormal_.begin() + triIndices[i]);
|
||||
}
|
||||
copy(policy, node->pImpl_->halfedgeTangent_.begin(),
|
||||
node->pImpl_->halfedgeTangent_.end(),
|
||||
combined.halfedgeTangent_.begin() + edgeIndices[i]);
|
||||
transform(
|
||||
policy, node->pImpl_->halfedge_.begin(),
|
||||
node->pImpl_->halfedge_.end(),
|
||||
combined.halfedge_.begin() + edgeIndices[i],
|
||||
UpdateHalfedge({vertIndices[i], edgeIndices[i], triIndices[i]}));
|
||||
// Since the nodes may be copies containing the same meshIDs, it is
|
||||
// important to add an offset so that each node instance gets
|
||||
// unique meshIDs.
|
||||
const int offset = i * Manifold::Impl::meshIDCounter_;
|
||||
transform(policy, node->pImpl_->meshRelation_.triRef.begin(),
|
||||
node->pImpl_->meshRelation_.triRef.end(),
|
||||
combined.meshRelation_.triRef.begin() + triIndices[i],
|
||||
UpdateMeshIDs({offset}));
|
||||
});
|
||||
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
auto &node = nodes[i];
|
||||
const int offset = i * Manifold::Impl::meshIDCounter_;
|
||||
|
||||
for (const auto pair : node->pImpl_->meshRelation_.meshIDtransform) {
|
||||
combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second;
|
||||
}
|
||||
copy(policy, node->pImpl_->halfedgeTangent_.begin(),
|
||||
node->pImpl_->halfedgeTangent_.end(),
|
||||
combined.halfedgeTangent_.begin() + nextEdge);
|
||||
copy(policy, node->pImpl_->meshRelation_.barycentric.begin(),
|
||||
node->pImpl_->meshRelation_.barycentric.end(),
|
||||
combined.meshRelation_.barycentric.begin() + nextBary);
|
||||
transform(policy, node->pImpl_->meshRelation_.triBary.begin(),
|
||||
node->pImpl_->meshRelation_.triBary.end(),
|
||||
combined.meshRelation_.triBary.begin() + nextTri,
|
||||
UpdateTriBary({nextBary}));
|
||||
transform(policy, node->pImpl_->halfedge_.begin(),
|
||||
node->pImpl_->halfedge_.end(),
|
||||
combined.halfedge_.begin() + nextEdge,
|
||||
UpdateHalfedge({nextVert, nextEdge, nextTri}));
|
||||
|
||||
// Since the nodes may be copies containing the same meshIDs, it is
|
||||
// important to increment them separately so that each node instance gets
|
||||
// unique meshIDs.
|
||||
combined.IncrementMeshIDs(nextTri, node->pImpl_->NumTri());
|
||||
|
||||
nextVert += node->pImpl_->NumVert();
|
||||
nextEdge += 2 * node->pImpl_->NumEdge();
|
||||
nextTri += node->pImpl_->NumTri();
|
||||
nextBary += node->pImpl_->meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
// required to remove parts that are smaller than the precision
|
||||
combined.SimplifyTopology();
|
||||
combined.Finish();
|
||||
combined.IncrementMeshIDs();
|
||||
return combined;
|
||||
}
|
||||
|
||||
CsgOpNode::CsgOpNode() {}
|
||||
|
||||
CsgOpNode::CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children,
|
||||
Manifold::OpType op)
|
||||
: impl_(std::make_shared<Impl>()) {
|
||||
impl_->children_ = children;
|
||||
OpType op)
|
||||
: impl_(Impl{}) {
|
||||
auto impl = impl_.GetGuard();
|
||||
impl->children_ = children;
|
||||
SetOp(op);
|
||||
// opportunistically flatten the tree without costly evaluation
|
||||
GetChildren(false);
|
||||
}
|
||||
|
||||
CsgOpNode::CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children,
|
||||
Manifold::OpType op)
|
||||
: impl_(std::make_shared<Impl>()) {
|
||||
impl_->children_ = children;
|
||||
OpType op)
|
||||
: impl_(Impl{}) {
|
||||
auto impl = impl_.GetGuard();
|
||||
impl->children_ = children;
|
||||
SetOp(op);
|
||||
// opportunistically flatten the tree without costly evaluation
|
||||
GetChildren(false);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgOpNode::Boolean(
|
||||
const std::shared_ptr<CsgNode> &second, OpType op) {
|
||||
std::vector<std::shared_ptr<CsgNode>> children;
|
||||
|
||||
auto isReused = [](const auto &node) { return node->impl_.UseCount() > 1; };
|
||||
|
||||
auto copyChildren = [&](const auto &list, const glm::mat4x3 &transform) {
|
||||
for (const auto &child : list) {
|
||||
children.push_back(child->Transform(transform));
|
||||
}
|
||||
};
|
||||
|
||||
auto self = std::dynamic_pointer_cast<CsgOpNode>(shared_from_this());
|
||||
assert(self);
|
||||
if (IsOp(op) && !isReused(self)) {
|
||||
auto impl = impl_.GetGuard();
|
||||
copyChildren(impl->children_, transform_);
|
||||
} else {
|
||||
children.push_back(self);
|
||||
}
|
||||
|
||||
auto secondOp = std::dynamic_pointer_cast<CsgOpNode>(second);
|
||||
auto canInlineSecondOp = [&]() {
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
case OpType::Intersect:
|
||||
return secondOp->IsOp(op);
|
||||
case OpType::Subtract:
|
||||
return secondOp->IsOp(OpType::Add);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (secondOp && canInlineSecondOp() && !isReused(secondOp)) {
|
||||
auto secondImpl = secondOp->impl_.GetGuard();
|
||||
copyChildren(secondImpl->children_, secondOp->transform_);
|
||||
} else {
|
||||
children.push_back(second);
|
||||
}
|
||||
|
||||
return std::make_shared<CsgOpNode>(children, op);
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgNode> CsgOpNode::Transform(const glm::mat4x3 &m) const {
|
||||
auto node = std::make_shared<CsgOpNode>();
|
||||
node->impl_ = impl_;
|
||||
node->transform_ = m * glm::mat4(transform_);
|
||||
node->op_ = op_;
|
||||
return node;
|
||||
}
|
||||
|
||||
std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
if (cache_ != nullptr) return cache_;
|
||||
if (impl_->children_.empty()) return nullptr;
|
||||
// turn the children into leaf nodes
|
||||
GetChildren();
|
||||
auto &children_ = impl_->children_;
|
||||
auto impl = impl_.GetGuard();
|
||||
auto &children_ = impl->children_;
|
||||
if (children_.size() > 1) {
|
||||
switch (impl_->op_) {
|
||||
case CsgNodeType::UNION:
|
||||
switch (op_) {
|
||||
case CsgNodeType::Union:
|
||||
BatchUnion();
|
||||
break;
|
||||
case CsgNodeType::INTERSECTION: {
|
||||
case CsgNodeType::Intersection: {
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls;
|
||||
for (auto &child : children_) {
|
||||
impls.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(child)->GetImpl());
|
||||
}
|
||||
BatchBoolean(Manifold::OpType::INTERSECT, impls);
|
||||
children_.clear();
|
||||
children_.push_back(std::make_shared<CsgLeafNode>(impls.front()));
|
||||
children_.push_back(std::make_shared<CsgLeafNode>(
|
||||
BatchBoolean(OpType::Intersect, impls)));
|
||||
break;
|
||||
};
|
||||
case CsgNodeType::DIFFERENCE: {
|
||||
case CsgNodeType::Difference: {
|
||||
// take the lhs out and treat the remaining nodes as the rhs, perform
|
||||
// union optimization for them
|
||||
auto lhs = std::dynamic_pointer_cast<CsgLeafNode>(children_.front());
|
||||
@@ -280,16 +350,17 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
BatchUnion();
|
||||
auto rhs = std::dynamic_pointer_cast<CsgLeafNode>(children_.front());
|
||||
children_.clear();
|
||||
Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(),
|
||||
Manifold::OpType::SUBTRACT);
|
||||
Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(), OpType::Subtract);
|
||||
children_.push_back(
|
||||
std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(
|
||||
boolean.Result(Manifold::OpType::SUBTRACT))));
|
||||
boolean.Result(OpType::Subtract))));
|
||||
};
|
||||
case CsgNodeType::LEAF:
|
||||
case CsgNodeType::Leaf:
|
||||
// unreachable
|
||||
break;
|
||||
}
|
||||
} else if (children_.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
// children_ must contain only one CsgLeafNode now, and its Transform will
|
||||
// give CsgLeafNode as well
|
||||
@@ -302,10 +373,10 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
* Efficient boolean operation on a set of nodes utilizing commutativity of the
|
||||
* operation. Only supports union and intersection.
|
||||
*/
|
||||
void CsgOpNode::BatchBoolean(
|
||||
Manifold::OpType operation,
|
||||
std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
|
||||
OpType operation,
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> &results) {
|
||||
ASSERT(operation != Manifold::OpType::SUBTRACT, logicErr,
|
||||
ASSERT(operation != OpType::Subtract, logicErr,
|
||||
"BatchBoolean doesn't support Difference.");
|
||||
auto cmpFn = [](std::shared_ptr<const Manifold::Impl> a,
|
||||
std::shared_ptr<const Manifold::Impl> b) {
|
||||
@@ -326,10 +397,17 @@ void CsgOpNode::BatchBoolean(
|
||||
results.pop_back();
|
||||
// boolean operation
|
||||
Boolean3 boolean(*a, *b, operation);
|
||||
if (results.size() == 0) {
|
||||
return std::make_shared<Manifold::Impl>(boolean.Result(operation));
|
||||
}
|
||||
results.push_back(
|
||||
std::make_shared<const Manifold::Impl>(boolean.Result(operation)));
|
||||
std::push_heap(results.begin(), results.end(), cmpFn);
|
||||
}
|
||||
if (results.size() == 1) {
|
||||
return std::make_shared<Manifold::Impl>(*results.front());
|
||||
}
|
||||
return std::make_shared<Manifold::Impl>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +423,8 @@ void CsgOpNode::BatchUnion() const {
|
||||
// If the number of children exceeded this limit, we will operate on chunks
|
||||
// with size kMaxUnionSize.
|
||||
constexpr int kMaxUnionSize = 1000;
|
||||
auto &children_ = impl_->children_;
|
||||
auto impl = impl_.GetGuard();
|
||||
auto &children_ = impl->children_;
|
||||
while (children_.size() > 1) {
|
||||
const int start = (children_.size() > kMaxUnionSize)
|
||||
? (children_.size() - kMaxUnionSize)
|
||||
@@ -375,25 +454,31 @@ void CsgOpNode::BatchUnion() const {
|
||||
}
|
||||
}
|
||||
// compose each set of disjoint children
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls;
|
||||
for (const auto &set : disjointSets) {
|
||||
if (set.size() == 1) {
|
||||
impls.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + set[0]])
|
||||
->GetImpl());
|
||||
} else {
|
||||
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
|
||||
for (size_t j : set) {
|
||||
tmp.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + j]));
|
||||
}
|
||||
impls.push_back(
|
||||
std::make_shared<const Manifold::Impl>(CsgLeafNode::Compose(tmp)));
|
||||
}
|
||||
}
|
||||
BatchBoolean(Manifold::OpType::ADD, impls);
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls(
|
||||
disjointSets.size());
|
||||
for_each_n_host(
|
||||
disjointSets.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
|
||||
countAt(0), disjointSets.size(),
|
||||
[children_, &impls, &disjointSets, start](int i) {
|
||||
auto set = disjointSets[i];
|
||||
if (set.size() == 1) {
|
||||
impls[i] = std::dynamic_pointer_cast<CsgLeafNode>(
|
||||
children_[start + set[0]])
|
||||
->GetImpl();
|
||||
} else {
|
||||
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
|
||||
for (size_t j : set) {
|
||||
tmp.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + j]));
|
||||
}
|
||||
impls[i] = std::make_shared<const Manifold::Impl>(
|
||||
CsgLeafNode::Compose(tmp));
|
||||
}
|
||||
});
|
||||
|
||||
children_.erase(children_.begin() + start, children_.end());
|
||||
children_.push_back(std::make_shared<CsgLeafNode>(impls.back()));
|
||||
children_.push_back(
|
||||
std::make_shared<CsgLeafNode>(BatchBoolean(OpType::Add, impls)));
|
||||
// move it to the front as we process from the back, and the newly added
|
||||
// child should be quite complicated
|
||||
std::swap(children_.front(), children_.back());
|
||||
@@ -402,60 +487,56 @@ void CsgOpNode::BatchUnion() const {
|
||||
|
||||
/**
|
||||
* Flatten the children to a list of leaf nodes and return them.
|
||||
* If finalize is true, the list will be guaranteed to be a list of leaf nodes
|
||||
* (i.e. no ops). Otherwise, the list may contain ops.
|
||||
* Note that this function will not apply the transform to children, as they may
|
||||
* be shared with other nodes.
|
||||
* If forceToLeafNodes is true, the list will be guaranteed to be a list of leaf
|
||||
* nodes (i.e. no ops). Otherwise, the list may contain ops. Note that this
|
||||
* function will not apply the transform to children, as they may be shared with
|
||||
* other nodes.
|
||||
*/
|
||||
std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
|
||||
bool finalize) const {
|
||||
auto &children_ = impl_->children_;
|
||||
if (children_.empty() || (impl_->simplified_ && !finalize) ||
|
||||
impl_->flattened_)
|
||||
return children_;
|
||||
impl_->simplified_ = true;
|
||||
impl_->flattened_ = finalize;
|
||||
std::vector<std::shared_ptr<CsgNode>> newChildren;
|
||||
bool forceToLeafNodes) const {
|
||||
auto impl = impl_.GetGuard();
|
||||
|
||||
CsgNodeType op = impl_->op_;
|
||||
for (auto &child : children_) {
|
||||
if (child->GetNodeType() == op && child.use_count() == 1 &&
|
||||
std::dynamic_pointer_cast<CsgOpNode>(child)->impl_.use_count() == 1) {
|
||||
auto grandchildren =
|
||||
std::dynamic_pointer_cast<CsgOpNode>(child)->GetChildren(finalize);
|
||||
int start = children_.size();
|
||||
for (auto &grandchild : grandchildren) {
|
||||
newChildren.push_back(grandchild->Transform(child->GetTransform()));
|
||||
}
|
||||
} else {
|
||||
if (!finalize || child->GetNodeType() == CsgNodeType::LEAF) {
|
||||
newChildren.push_back(child);
|
||||
} else {
|
||||
newChildren.push_back(child->ToLeafNode());
|
||||
}
|
||||
}
|
||||
// special handling for difference: we treat it as first - (second + third +
|
||||
// ...) so op = UNION after the first node
|
||||
if (op == CsgNodeType::DIFFERENCE) op = CsgNodeType::UNION;
|
||||
if (forceToLeafNodes && !impl->forcedToLeafNodes_) {
|
||||
impl->forcedToLeafNodes_ = true;
|
||||
for_each_host(impl->children_.size() > 1 ? ExecutionPolicy::Par
|
||||
: ExecutionPolicy::Seq,
|
||||
impl->children_.begin(), impl->children_.end(),
|
||||
[](auto &child) {
|
||||
if (child->GetNodeType() != CsgNodeType::Leaf) {
|
||||
child = child->ToLeafNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
children_ = newChildren;
|
||||
return children_;
|
||||
return impl->children_;
|
||||
}
|
||||
|
||||
void CsgOpNode::SetOp(Manifold::OpType op) {
|
||||
void CsgOpNode::SetOp(OpType op) {
|
||||
switch (op) {
|
||||
case Manifold::OpType::ADD:
|
||||
impl_->op_ = CsgNodeType::UNION;
|
||||
case OpType::Add:
|
||||
op_ = CsgNodeType::Union;
|
||||
break;
|
||||
case Manifold::OpType::SUBTRACT:
|
||||
impl_->op_ = CsgNodeType::DIFFERENCE;
|
||||
case OpType::Subtract:
|
||||
op_ = CsgNodeType::Difference;
|
||||
break;
|
||||
case Manifold::OpType::INTERSECT:
|
||||
impl_->op_ = CsgNodeType::INTERSECTION;
|
||||
case OpType::Intersect:
|
||||
op_ = CsgNodeType::Intersection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CsgOpNode::IsOp(OpType op) {
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
return op_ == CsgNodeType::Union;
|
||||
case OpType::Subtract:
|
||||
return op_ == CsgNodeType::Difference;
|
||||
case OpType::Intersect:
|
||||
return op_ == CsgNodeType::Intersection;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4x3 CsgOpNode::GetTransform() const { return transform_; }
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -14,20 +14,24 @@
|
||||
|
||||
#pragma once
|
||||
#include "manifold.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
enum class CsgNodeType { UNION, INTERSECTION, DIFFERENCE, LEAF };
|
||||
enum class CsgNodeType { Union, Intersection, Difference, Leaf };
|
||||
|
||||
class CsgLeafNode;
|
||||
|
||||
class CsgNode {
|
||||
class CsgNode : public std::enable_shared_from_this<CsgNode> {
|
||||
public:
|
||||
virtual std::shared_ptr<CsgLeafNode> ToLeafNode() const = 0;
|
||||
virtual std::shared_ptr<CsgNode> Transform(const glm::mat4x3 &m) const = 0;
|
||||
virtual CsgNodeType GetNodeType() const = 0;
|
||||
virtual glm::mat4x3 GetTransform() const = 0;
|
||||
|
||||
virtual std::shared_ptr<CsgNode> Boolean(
|
||||
const std::shared_ptr<CsgNode> &second, OpType op);
|
||||
|
||||
std::shared_ptr<CsgNode> Translate(const glm::vec3 &t) const;
|
||||
std::shared_ptr<CsgNode> Scale(const glm::vec3 &s) const;
|
||||
std::shared_ptr<CsgNode> Rotate(float xDegrees = 0, float yDegrees = 0,
|
||||
@@ -63,42 +67,43 @@ class CsgOpNode final : public CsgNode {
|
||||
public:
|
||||
CsgOpNode();
|
||||
|
||||
CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children,
|
||||
Manifold::OpType op);
|
||||
CsgOpNode(const std::vector<std::shared_ptr<CsgNode>> &children, OpType op);
|
||||
|
||||
CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children,
|
||||
Manifold::OpType op);
|
||||
CsgOpNode(std::vector<std::shared_ptr<CsgNode>> &&children, OpType op);
|
||||
|
||||
std::shared_ptr<CsgNode> Boolean(const std::shared_ptr<CsgNode> &second,
|
||||
OpType op) override;
|
||||
|
||||
std::shared_ptr<CsgNode> Transform(const glm::mat4x3 &m) const override;
|
||||
|
||||
std::shared_ptr<CsgLeafNode> ToLeafNode() const override;
|
||||
|
||||
CsgNodeType GetNodeType() const override { return impl_->op_; }
|
||||
CsgNodeType GetNodeType() const override { return op_; }
|
||||
|
||||
glm::mat4x3 GetTransform() const override;
|
||||
|
||||
private:
|
||||
struct Impl {
|
||||
CsgNodeType op_;
|
||||
mutable std::vector<std::shared_ptr<CsgNode>> children_;
|
||||
mutable bool simplified_ = false;
|
||||
mutable bool flattened_ = false;
|
||||
std::vector<std::shared_ptr<CsgNode>> children_;
|
||||
bool forcedToLeafNodes_ = false;
|
||||
};
|
||||
std::shared_ptr<Impl> impl_ = nullptr;
|
||||
mutable ConcurrentSharedPtr<Impl> impl_ = ConcurrentSharedPtr<Impl>(Impl{});
|
||||
CsgNodeType op_;
|
||||
glm::mat4x3 transform_ = glm::mat4x3(1.0f);
|
||||
// the following fields are for lazy evaluation, so they are mutable
|
||||
mutable std::shared_ptr<CsgLeafNode> cache_ = nullptr;
|
||||
|
||||
void SetOp(Manifold::OpType);
|
||||
void SetOp(OpType);
|
||||
bool IsOp(OpType op);
|
||||
|
||||
static void BatchBoolean(
|
||||
Manifold::OpType operation,
|
||||
static std::shared_ptr<Manifold::Impl> BatchBoolean(
|
||||
OpType operation,
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> &results);
|
||||
|
||||
void BatchUnion() const;
|
||||
|
||||
std::vector<std::shared_ptr<CsgNode>> &GetChildren(
|
||||
bool finalize = true) const;
|
||||
bool forceToLeafNodes = true) const;
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -60,19 +60,19 @@ struct ShortEdge {
|
||||
|
||||
struct FlagEdge {
|
||||
const Halfedge* halfedge;
|
||||
const BaryRef* triBary;
|
||||
const TriRef* triRef;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag redundant edges - those where the startVert is surrounded by only
|
||||
// two original triangles.
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const TriRef ref0 = triRef[edge / 3];
|
||||
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
|
||||
const BaryRef ref1 = triBary[current / 3];
|
||||
const TriRef ref1 = triRef[current / 3];
|
||||
while (current != edge) {
|
||||
current = NextHalfedge(halfedge[current].pairedHalfedge);
|
||||
int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
const TriRef ref = triRef[tri];
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return false;
|
||||
@@ -168,7 +168,7 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triBary.cptrD()})) -
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triRef.cptrD()})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
@@ -213,8 +213,11 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
halfedge_.push_back({outsideVert, endVert, -1, newFace});
|
||||
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, current);
|
||||
if (meshRelation_.triBary.size() > 0)
|
||||
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
newHalfedge += 3;
|
||||
@@ -227,8 +230,11 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, opposite);
|
||||
PairUp(newHalfedge, newHalfedge - 3);
|
||||
if (meshRelation_.triBary.size() > 0)
|
||||
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
break;
|
||||
@@ -301,7 +307,8 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
|
||||
const Halfedge toRemove = halfedge_[edge];
|
||||
if (toRemove.pairedHalfedge < 0) return;
|
||||
@@ -326,24 +333,32 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
|
||||
// Orbit startVert
|
||||
int start = halfedge_[tri1edge[1]].pairedHalfedge;
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const BaryRef ref1 = triBary[toRemove.pairedHalfedge / 3];
|
||||
if (!shortEdge) {
|
||||
current = start;
|
||||
TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];
|
||||
glm::vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
glm::vec3 pNext = vertPos_[halfedge_[current].endVert];
|
||||
const int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
const TriRef ref = triRef[tri];
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
// Don't collapse if the edge is not redundant (this may have changed due
|
||||
// to the collapse of neighbors).
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return;
|
||||
if (ref.meshID != refCheck.meshID || ref.tri != refCheck.tri) {
|
||||
refCheck = triRef[edge / 3];
|
||||
if (ref.meshID != refCheck.meshID || ref.tri != refCheck.tri) {
|
||||
return;
|
||||
} else {
|
||||
// Don't collapse if the edges separating the faces are not colinear
|
||||
// (can happen when the two faces are coplanar).
|
||||
if (CCW(projection * pOld, projection * pLast, projection * pNew,
|
||||
precision_) != 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't collapse edge if it would cause a triangle to invert.
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
if (CCW(projection * pNext, projection * pLast, projection * pNew,
|
||||
precision_) < 0)
|
||||
return;
|
||||
@@ -358,18 +373,30 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
CollapseTri(tri1edge);
|
||||
|
||||
// Orbit startVert
|
||||
const int tri0 = edge / 3;
|
||||
const int tri1 = toRemove.pairedHalfedge / 3;
|
||||
const int triVert0 = (edge + 1) % 3;
|
||||
const int triVert1 = toRemove.pairedHalfedge % 3;
|
||||
const int prop0 = triProp.size() > 0 ? triProp[tri0][edge % 3] : -1;
|
||||
const int prop1 = triProp.size() > 0
|
||||
? triProp[tri1][(toRemove.pairedHalfedge + 1) % 3]
|
||||
: -1;
|
||||
current = start;
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
|
||||
if (!shortEdge) {
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
triBary[tri].vertBary[vIdx] =
|
||||
(ref0.meshID == triBary[tri].meshID && ref0.tri == triBary[tri].tri)
|
||||
? ref0.vertBary[(edge + 1) % 3]
|
||||
: ref1.vertBary[toRemove.pairedHalfedge % 3];
|
||||
if (triProp.size() > 0) {
|
||||
if (triProp[tri][vIdx] == prop0) {
|
||||
triProp[tri][vIdx] = triProp[tri0][triVert0];
|
||||
} else if (triProp[tri][vIdx] == prop1) {
|
||||
triProp[tri][vIdx] = triProp[tri1][triVert1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int vert = halfedge_[current].endVert;
|
||||
@@ -391,7 +418,7 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
if (edge < 0) return;
|
||||
const int pair = halfedge_[edge].pairedHalfedge;
|
||||
@@ -431,23 +458,28 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
const int tri0 = halfedge_[tri0edge[0]].face;
|
||||
const int tri1 = halfedge_[tri1edge[0]].face;
|
||||
faceNormal_[tri0] = faceNormal_[tri1];
|
||||
triBary[tri0] = triBary[tri1];
|
||||
triBary[tri0].vertBary[perm0[1]] = triBary[tri1].vertBary[perm1[0]];
|
||||
triBary[tri0].vertBary[perm0[0]] = triBary[tri1].vertBary[perm1[2]];
|
||||
// Calculate a new barycentric coordinate for the split triangle.
|
||||
const glm::vec3 uvw0 = UVW(triBary[tri1].vertBary[perm1[0]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
const glm::vec3 uvw1 = UVW(triBary[tri1].vertBary[perm1[1]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
triRef[tri0] = triRef[tri1];
|
||||
const float l01 = glm::length(v[1] - v[0]);
|
||||
const float l02 = glm::length(v[2] - v[0]);
|
||||
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
|
||||
const glm::vec3 uvw2 = a * uvw0 + (1 - a) * uvw1;
|
||||
// And assign it.
|
||||
const int newBary = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.push_back(uvw2);
|
||||
triBary[tri1].vertBary[perm1[0]] = newBary;
|
||||
triBary[tri0].vertBary[perm0[2]] = newBary;
|
||||
// Update properties if applicable
|
||||
if (meshRelation_.properties.size() > 0) {
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
VecDH<float>& prop = meshRelation_.properties;
|
||||
triProp[tri0] = triProp[tri1];
|
||||
triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]];
|
||||
triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]];
|
||||
const int numProp = NumProp();
|
||||
const int newProp = prop.size() / numProp;
|
||||
const int propIdx0 = triProp[tri1][perm1[0]];
|
||||
const int propIdx1 = triProp[tri1][perm1[1]];
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
prop.push_back(a * prop[numProp * propIdx0 + p] +
|
||||
(1 - a) * prop[numProp * propIdx1 + p]);
|
||||
}
|
||||
triProp[tri1][perm1[0]] = newProp;
|
||||
triProp[tri0][perm0[2]] = newProp;
|
||||
}
|
||||
|
||||
// if the new edge already exists, duplicate the verts and split the mesh.
|
||||
int current = halfedge_[tri1edge[0]].pairedHalfedge;
|
||||
|
||||
@@ -30,15 +30,14 @@ namespace manifold {
|
||||
* faceNormal_ values are retained, repeated as necessary.
|
||||
*/
|
||||
void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
const VecDH<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary) {
|
||||
const VecDH<TriRef>& halfedgeRef) {
|
||||
VecDH<glm::ivec3> triVerts;
|
||||
VecDH<glm::vec3> triNormal;
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
triBary.resize(0);
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
triRef.resize(0);
|
||||
triVerts.reserve(halfedge_.size());
|
||||
triNormal.reserve(halfedge_.size());
|
||||
triBary.reserve(halfedge_.size());
|
||||
triRef.reserve(halfedge_.size());
|
||||
|
||||
for (int face = 0; face < faceEdge.size() - 1; ++face) {
|
||||
const int firstEdge = faceEdge[face];
|
||||
@@ -47,12 +46,6 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
ASSERT(numEdge >= 3, topologyErr, "face has less than three edges.");
|
||||
const glm::vec3 normal = faceNormal_[face];
|
||||
|
||||
auto linearSearch = [](const int* mapping, int value) {
|
||||
int i = 0;
|
||||
while (mapping[i] != value) ++i;
|
||||
return i;
|
||||
};
|
||||
|
||||
if (numEdge == 3) { // Single triangle
|
||||
int mapping[3] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -72,11 +65,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
int index = linearSearch(mapping, tri[k]);
|
||||
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
} else if (numEdge == 4) { // Pair of triangles
|
||||
int mapping[4] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -125,30 +114,19 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
for (auto tri : {tri0, tri1}) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
int index = linearSearch(mapping, tri[k]);
|
||||
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
}
|
||||
} else { // General triangulation
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
|
||||
|
||||
std::map<int, int> vertBary;
|
||||
for (int j = firstEdge; j < lastEdge; ++j)
|
||||
vertBary[halfedge_[j].startVert] = halfedgeBary[j];
|
||||
const PolygonsIdx polys = Face2Polygons(face, projection, faceEdge);
|
||||
|
||||
const Polygons polys = Face2Polygons(face, projection, faceEdge);
|
||||
|
||||
std::vector<glm::ivec3> newTris = Triangulate(polys, precision_);
|
||||
std::vector<glm::ivec3> newTris = TriangulateIdx(polys, precision_);
|
||||
|
||||
for (auto tri : newTris) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
triBary.back().vertBary[k] = vertBary[tri[k]];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,8 +138,8 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
* For the input face index, return a set of 2D polygons formed by the input
|
||||
* projection of the vertices.
|
||||
*/
|
||||
Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const {
|
||||
PolygonsIdx Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const {
|
||||
const int firstEdge = faceEdge[face];
|
||||
const int lastEdge = faceEdge[face + 1];
|
||||
|
||||
@@ -173,7 +151,7 @@ Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
ASSERT(inserted, topologyErr, "face has duplicate vertices.");
|
||||
}
|
||||
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
int startEdge = 0;
|
||||
int thisEdge = startEdge;
|
||||
while (1) {
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "graph.h"
|
||||
#include "hashtable.h"
|
||||
#include "par.h"
|
||||
|
||||
namespace {
|
||||
@@ -41,6 +43,12 @@ __host__ __device__ void AtomicAddVec3(glm::vec3& target,
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ int FlipHalfedge(int halfedge) {
|
||||
const int tri = halfedge / 3;
|
||||
const int vert = 2 - (halfedge - 3 * tri);
|
||||
return 3 * tri + vert;
|
||||
}
|
||||
|
||||
struct Normalize {
|
||||
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
|
||||
};
|
||||
@@ -63,6 +71,42 @@ struct TransformNormals {
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformTangents {
|
||||
const glm::mat3 transform;
|
||||
const bool invert;
|
||||
const glm::vec4* oldTangents;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::vec4&, int> inOut) {
|
||||
glm::vec4& tangent = thrust::get<0>(inOut);
|
||||
int edge = thrust::get<1>(inOut);
|
||||
if (invert) {
|
||||
edge = halfedge[FlipHalfedge(edge)].pairedHalfedge;
|
||||
}
|
||||
|
||||
tangent = glm::vec4(transform * glm::vec3(oldTangents[edge]),
|
||||
oldTangents[edge].w);
|
||||
}
|
||||
};
|
||||
|
||||
struct FlipTris {
|
||||
Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& bary = thrust::get<0>(inOut);
|
||||
const int tri = thrust::get<1>(inOut);
|
||||
|
||||
thrust::swap(halfedge[3 * tri], halfedge[3 * tri + 2]);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
thrust::swap(halfedge[3 * tri + i].startVert,
|
||||
halfedge[3 * tri + i].endVert);
|
||||
halfedge[3 * tri + i].pairedHalfedge =
|
||||
FlipHalfedge(halfedge[3 * tri + i].pairedHalfedge);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AssignNormals {
|
||||
glm::vec3* vertNormal;
|
||||
const glm::vec3* vertPos;
|
||||
@@ -157,45 +201,25 @@ struct ReindexTriVerts {
|
||||
}
|
||||
};
|
||||
|
||||
struct InitializeBaryRef {
|
||||
struct InitializeTriRef {
|
||||
const int meshID;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef&, int> inOut) {
|
||||
BaryRef& baryRef = thrust::get<0>(inOut);
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& baryRef = thrust::get<0>(inOut);
|
||||
int tri = thrust::get<1>(inOut);
|
||||
|
||||
baryRef.meshID = meshID;
|
||||
baryRef.originalID = meshID;
|
||||
baryRef.tri = tri;
|
||||
baryRef.vertBary = {-3, -2, -1};
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkMeshID {
|
||||
int* includesMeshID;
|
||||
|
||||
__host__ __device__ void operator()(BaryRef& ref) {
|
||||
includesMeshID[ref.meshID] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshID {
|
||||
const int* meshIDold2new;
|
||||
const int meshIDoffset;
|
||||
const HashTableD<uint32_t> meshIDold2new;
|
||||
|
||||
__host__ __device__ void operator()(BaryRef& ref) {
|
||||
ref.meshID = meshIDold2new[ref.meshID] + meshIDoffset;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckProperties {
|
||||
const int numSets;
|
||||
|
||||
__host__ __device__ bool operator()(glm::ivec3 triProp) {
|
||||
bool good = true;
|
||||
for (int i : {0, 1, 2}) good &= (triProp[i] >= 0 && triProp[i] < numSets);
|
||||
return good;
|
||||
__host__ __device__ void operator()(TriRef& ref) {
|
||||
ref.meshID = meshIDold2new[ref.meshID];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,6 +227,7 @@ struct CoplanarEdge {
|
||||
float* triArea;
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const TriRef* triRef;
|
||||
const glm::ivec3* triProp;
|
||||
const float* prop;
|
||||
const float* propTol;
|
||||
@@ -210,13 +235,17 @@ struct CoplanarEdge {
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<thrust::pair<int, int>&, int> inOut) {
|
||||
thrust::tuple<thrust::pair<int, int>&, thrust::pair<int, int>&, int>
|
||||
inOut) {
|
||||
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
|
||||
const int edgeIdx = thrust::get<1>(inOut);
|
||||
thrust::pair<int, int>& vert2vert = thrust::get<1>(inOut);
|
||||
const int edgeIdx = thrust::get<2>(inOut);
|
||||
|
||||
const Halfedge edge = halfedge[edgeIdx];
|
||||
if (!edge.IsForward()) return;
|
||||
const Halfedge pair = halfedge[edge.pairedHalfedge];
|
||||
if (triRef[edge.face].meshID != triRef[pair.face].meshID) return;
|
||||
|
||||
const glm::vec3 base = vertPos[edge.startVert];
|
||||
|
||||
const int baseNum = edgeIdx - 3 * edge.face;
|
||||
@@ -224,6 +253,23 @@ struct CoplanarEdge {
|
||||
const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
|
||||
const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
|
||||
|
||||
if (numProp > 0) {
|
||||
const int prop0 = triProp[edge.face][baseNum];
|
||||
const int prop1 = triProp[edge.face][edgeNum];
|
||||
bool propEqual = true;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
if (glm::abs(prop[numProp * prop0 + p] - prop[numProp * prop1 + p]) >
|
||||
propTol[p]) {
|
||||
propEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (propEqual) {
|
||||
vert2vert.first = prop0;
|
||||
vert2vert.second = prop1;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3 jointVec = vertPos[pair.startVert] - base;
|
||||
const glm::vec3 edgeVec =
|
||||
vertPos[halfedge[3 * edge.face + edgeNum].startVert] - base;
|
||||
@@ -236,6 +282,8 @@ struct CoplanarEdge {
|
||||
glm::vec3 normal = glm::cross(jointVec, edgeVec);
|
||||
const float area = glm::length(normal);
|
||||
const float areaPair = glm::length(glm::cross(pairVec, jointVec));
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
// Don't link degenerate triangles
|
||||
if (area < length * precision || areaPair < lengthPair * precision) return;
|
||||
|
||||
@@ -263,16 +311,14 @@ struct CoplanarEdge {
|
||||
pairVec + normal * scale * (pairProp - baseProp);
|
||||
|
||||
glm::vec3 cross = glm::cross(iJointVec, iEdgeVec);
|
||||
const float area = glm::max(
|
||||
const float areaP = glm::max(
|
||||
glm::length(cross), glm::length(glm::cross(iPairVec, iJointVec)));
|
||||
const float volume = glm::abs(glm::dot(cross, iPairVec));
|
||||
const float volumeP = glm::abs(glm::dot(cross, iPairVec));
|
||||
// Only operate on consistent triangles
|
||||
if (volume > area * precision) return;
|
||||
if (volumeP > areaP * precision) return;
|
||||
}
|
||||
}
|
||||
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
face2face.first = edge.face;
|
||||
face2face.second = pair.face;
|
||||
}
|
||||
@@ -288,44 +334,207 @@ struct EdgeBox {
|
||||
}
|
||||
};
|
||||
|
||||
int GetLabels(std::vector<int>& components,
|
||||
const VecDH<thrust::pair<int, int>>& edges, int numNodes) {
|
||||
Graph graph;
|
||||
for (int i = 0; i < numNodes; ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (int i = 0; i < edges.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = edges[i];
|
||||
if (edge.first < 0) continue;
|
||||
graph.add_edge(edge.first, edge.second);
|
||||
}
|
||||
|
||||
return ConnectedComponents(components, graph);
|
||||
}
|
||||
|
||||
void DedupePropVerts(manifold::VecDH<glm::ivec3>& triProp,
|
||||
const VecDH<thrust::pair<int, int>>& vert2vert) {
|
||||
std::vector<int> vertLabels;
|
||||
const int numLabels = GetLabels(vertLabels, vert2vert, vert2vert.size());
|
||||
|
||||
std::vector<int> label2vert(numLabels);
|
||||
for (int v = 0; v < vert2vert.size(); ++v) {
|
||||
label2vert[vertLabels[v]] = v;
|
||||
}
|
||||
for (int tri = 0; tri < triProp.size(); ++tri) {
|
||||
for (int i : {0, 1, 2})
|
||||
triProp[tri][i] = label2vert[vertLabels[triProp[tri][i]]];
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
std::atomic<int> Manifold::Impl::meshIDCounter_(1);
|
||||
std::atomic<uint32_t> Manifold::Impl::meshIDCounter_(1);
|
||||
|
||||
uint32_t Manifold::Impl::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::meshIDCounter_.fetch_add(n, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
std::vector<float> propertyTolerance) {
|
||||
Mesh mesh;
|
||||
const int numVert = meshGL.NumVert();
|
||||
const int numTri = meshGL.NumTri();
|
||||
|
||||
if (meshGL.numProp < 3) {
|
||||
MarkFailure(Error::MissingPositionProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.triVerts.resize(numTri);
|
||||
if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) {
|
||||
MarkFailure(Error::MergeVectorsDifferentLengths);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.runTransform.empty() &&
|
||||
12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) {
|
||||
MarkFailure(Error::TransformWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.runOriginalID.empty() && !meshGL.runIndex.empty() &&
|
||||
meshGL.runOriginalID.size() + 1 != meshGL.runIndex.size()) {
|
||||
MarkFailure(Error::RunIndexWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) {
|
||||
MarkFailure(Error::FaceIDWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> prop2vert(numVert);
|
||||
std::iota(prop2vert.begin(), prop2vert.end(), 0);
|
||||
for (int i = 0; i < meshGL.mergeFromVert.size(); ++i) {
|
||||
const int from = meshGL.mergeFromVert[i];
|
||||
const int to = meshGL.mergeToVert[i];
|
||||
if (from >= numVert || to >= numVert) {
|
||||
MarkFailure(Error::MergeIndexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
prop2vert[from] = to;
|
||||
}
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
const int vert = meshGL.triVerts[3 * i + j];
|
||||
if (vert < 0 || vert >= numVert) {
|
||||
MarkFailure(Error::VertexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
mesh.triVerts[i][j] = prop2vert[vert];
|
||||
}
|
||||
}
|
||||
|
||||
MeshRelationD relation;
|
||||
|
||||
if (meshGL.numProp > 3) {
|
||||
relation.triProperties.resize(numTri);
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
relation.triProperties[i][j] = meshGL.triVerts[3 * i + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int numProp = meshGL.numProp - 3;
|
||||
relation.numProp = numProp;
|
||||
relation.properties.resize(meshGL.NumVert() * numProp);
|
||||
// This will have unreferenced duplicate positions that will be removed by
|
||||
// Impl::RemoveUnreferencedVerts().
|
||||
mesh.vertPos.resize(meshGL.NumVert());
|
||||
|
||||
for (int i = 0; i < meshGL.NumVert(); ++i) {
|
||||
for (const int j : {0, 1, 2})
|
||||
mesh.vertPos[i][j] = meshGL.vertProperties[meshGL.numProp * i + j];
|
||||
for (int j = 0; j < numProp; ++j)
|
||||
relation.properties[i * numProp + j] =
|
||||
meshGL.vertProperties[meshGL.numProp * i + 3 + j];
|
||||
}
|
||||
|
||||
mesh.halfedgeTangent.resize(meshGL.halfedgeTangent.size() / 4);
|
||||
for (int i = 0; i < mesh.halfedgeTangent.size(); ++i) {
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
mesh.halfedgeTangent[i][j] = meshGL.halfedgeTangent[4 * i + j];
|
||||
}
|
||||
|
||||
if (meshGL.runOriginalID.empty()) {
|
||||
relation.originalID = Impl::ReserveIDs(1);
|
||||
} else {
|
||||
std::vector<uint32_t> runIndex = meshGL.runIndex;
|
||||
if (runIndex.empty()) {
|
||||
runIndex = {0, 3 * meshGL.NumTri()};
|
||||
}
|
||||
relation.triRef.resize(meshGL.NumTri());
|
||||
const int startID = Impl::ReserveIDs(meshGL.runOriginalID.size());
|
||||
for (int i = 0; i < meshGL.runOriginalID.size(); ++i) {
|
||||
const int meshID = startID + i;
|
||||
const int originalID = meshGL.runOriginalID[i];
|
||||
for (int tri = runIndex[i] / 3; tri < runIndex[i + 1] / 3; ++tri) {
|
||||
TriRef& ref = relation.triRef[tri];
|
||||
ref.meshID = meshID;
|
||||
ref.originalID = originalID;
|
||||
ref.tri = meshGL.faceID.empty() ? tri : meshGL.faceID[tri];
|
||||
}
|
||||
|
||||
if (meshGL.runTransform.empty()) {
|
||||
relation.meshIDtransform[meshID] = {originalID};
|
||||
} else {
|
||||
const float* m = meshGL.runTransform.data() + 12 * i;
|
||||
relation.meshIDtransform[meshID] = {
|
||||
originalID,
|
||||
{m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10],
|
||||
m[11]}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*this = Impl(mesh, relation, propertyTolerance, !meshGL.faceID.empty());
|
||||
|
||||
// A Manifold created from an input mesh is never an original - the input is
|
||||
// the original.
|
||||
meshRelation_.originalID = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a manifold from an input triangle Mesh. Will return an empty Manifold
|
||||
* and set an Error Status if the Mesh is not manifold or otherwise invalid.
|
||||
* TODO: update halfedgeTangent during SimplifyTopology.
|
||||
*/
|
||||
Manifold::Impl::Impl(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: vertPos_(mesh.vertPos), halfedgeTangent_(mesh.halfedgeTangent) {
|
||||
Manifold::Impl::Impl(const Mesh& mesh, const MeshRelationD& relation,
|
||||
const std::vector<float>& propertyTolerance,
|
||||
bool hasFaceIDs)
|
||||
: vertPos_(mesh.vertPos),
|
||||
halfedgeTangent_(mesh.halfedgeTangent),
|
||||
meshRelation_(relation) {
|
||||
VecDH<glm::ivec3> triVerts = mesh.triVerts;
|
||||
if (!IsIndexInBounds(triVerts)) {
|
||||
MarkFailure(Error::VERTEX_INDEX_OUT_OF_BOUNDS);
|
||||
MarkFailure(Error::VertexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
RemoveUnreferencedVerts(triVerts);
|
||||
|
||||
CalculateBBox();
|
||||
if (!IsFinite()) {
|
||||
MarkFailure(Error::NON_FINITE_VERTEX);
|
||||
MarkFailure(Error::NonFiniteVertex);
|
||||
return;
|
||||
}
|
||||
SetPrecision();
|
||||
|
||||
CreateHalfedges(triVerts);
|
||||
if (!IsManifold()) {
|
||||
MarkFailure(Error::NOT_MANIFOLD);
|
||||
MarkFailure(Error::NotManifold);
|
||||
return;
|
||||
}
|
||||
CalculateNormals();
|
||||
InitializeNewReference(triProperties, properties, propertyTolerance);
|
||||
if (status_ != Error::NO_ERROR) return;
|
||||
|
||||
InitializeOriginal();
|
||||
if (!hasFaceIDs) {
|
||||
CreateFaces(propertyTolerance);
|
||||
}
|
||||
|
||||
SimplifyTopology();
|
||||
Finish();
|
||||
@@ -339,14 +548,14 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
std::vector<glm::vec3> vertPos;
|
||||
std::vector<glm::ivec3> triVerts;
|
||||
switch (shape) {
|
||||
case Shape::TETRAHEDRON:
|
||||
case Shape::Tetrahedron:
|
||||
vertPos = {{-1.0f, -1.0f, 1.0f},
|
||||
{-1.0f, 1.0f, -1.0f},
|
||||
{1.0f, -1.0f, -1.0f},
|
||||
{1.0f, 1.0f, 1.0f}};
|
||||
triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
break;
|
||||
case Shape::CUBE:
|
||||
case Shape::Cube:
|
||||
vertPos = {{0.0f, 0.0f, 0.0f}, //
|
||||
{1.0f, 0.0f, 0.0f}, //
|
||||
{1.0f, 1.0f, 0.0f}, //
|
||||
@@ -362,7 +571,7 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
{2, 3, 7}, {2, 7, 6}, //
|
||||
{3, 0, 4}, {3, 4, 7}};
|
||||
break;
|
||||
case Shape::OCTAHEDRON:
|
||||
case Shape::Octahedron:
|
||||
vertPos = {{1.0f, 0.0f, 0.0f}, //
|
||||
{-1.0f, 0.0f, 0.0f}, //
|
||||
{0.0f, 1.0f, 0.0f}, //
|
||||
@@ -378,7 +587,9 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
vertPos_ = vertPos;
|
||||
CreateHalfedges(triVerts);
|
||||
Finish();
|
||||
InitializeNewReference();
|
||||
meshRelation_.originalID = ReserveIDs(1);
|
||||
InitializeOriginal();
|
||||
CreateFaces();
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
@@ -401,67 +612,42 @@ void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
ReindexTriVerts({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::ReinitializeReference(int meshID) {
|
||||
// instead of storing the meshID, we store 0 and set the mapping to
|
||||
// 0 -> meshID, because the meshID after boolean operation also starts from 0.
|
||||
void Manifold::Impl::InitializeOriginal() {
|
||||
const int meshID = meshRelation_.originalID;
|
||||
// Don't initialize if it's not an original
|
||||
if (meshID < 0) return;
|
||||
meshRelation_.triRef.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri()),
|
||||
zip(meshRelation_.triBary.begin(), countAt(0)), NumTri(),
|
||||
InitializeBaryRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.originalID = meshID;
|
||||
zip(meshRelation_.triRef.begin(), countAt(0)), NumTri(),
|
||||
InitializeTriRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.meshIDtransform.clear();
|
||||
meshRelation_.meshIDtransform[meshID] = {meshID};
|
||||
}
|
||||
|
||||
int Manifold::Impl::InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance) {
|
||||
meshRelation_.triBary.resize(NumTri());
|
||||
const int nextMeshID = meshIDCounter_.fetch_add(1, std::memory_order_relaxed);
|
||||
ReinitializeReference(nextMeshID);
|
||||
|
||||
const int numProps = propertyTolerance.size();
|
||||
|
||||
VecDH<glm::ivec3> triPropertiesD(triProperties);
|
||||
VecDH<float> propertiesD(properties);
|
||||
VecDH<float> propertyToleranceD(propertyTolerance);
|
||||
|
||||
if (numProps > 0) {
|
||||
if (triProperties.size() != NumTri() && triProperties.size() != 0) {
|
||||
MarkFailure(Error::TRI_PROPERTIES_WRONG_LENGTH);
|
||||
return nextMeshID;
|
||||
};
|
||||
if (properties.size() % numProps != 0) {
|
||||
MarkFailure(Error::PROPERTIES_WRONG_LENGTH);
|
||||
return nextMeshID;
|
||||
};
|
||||
|
||||
const int numSets = properties.size() / numProps;
|
||||
if (!all_of(autoPolicy(triProperties.size()), triPropertiesD.begin(),
|
||||
triPropertiesD.end(), CheckProperties({numSets}))) {
|
||||
MarkFailure(Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
return nextMeshID;
|
||||
};
|
||||
}
|
||||
void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
VecDH<float> propertyToleranceD =
|
||||
propertyTolerance.empty()
|
||||
? VecDH<float>(meshRelation_.numProp, kTolerance)
|
||||
: propertyTolerance;
|
||||
|
||||
VecDH<thrust::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
||||
VecDH<thrust::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
||||
VecDH<float> triArea(NumTri());
|
||||
for_each_n(autoPolicy(halfedge_.size()), zip(face2face.begin(), countAt(0)),
|
||||
halfedge_.size(),
|
||||
CoplanarEdge({triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
triPropertiesD.cptrD(), propertiesD.cptrD(),
|
||||
propertyToleranceD.cptrD(), numProps, precision_}));
|
||||
for_each_n(
|
||||
autoPolicy(halfedge_.size()),
|
||||
zip(face2face.begin(), vert2vert.begin(), countAt(0)), halfedge_.size(),
|
||||
CoplanarEdge(
|
||||
{triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
meshRelation_.triRef.cptrD(), meshRelation_.triProperties.cptrD(),
|
||||
meshRelation_.properties.cptrD(), propertyToleranceD.cptrD(),
|
||||
meshRelation_.numProp, precision_}));
|
||||
|
||||
Graph graph;
|
||||
for (int i = 0; i < NumTri(); ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (int i = 0; i < face2face.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = face2face[i];
|
||||
if (edge.first < 0) continue;
|
||||
graph.add_edge(edge.first, edge.second);
|
||||
if (meshRelation_.triProperties.size() > 0) {
|
||||
DedupePropVerts(meshRelation_.triProperties, vert2vert);
|
||||
}
|
||||
|
||||
std::vector<int> components;
|
||||
const int numComponent = ConnectedComponents(components, graph);
|
||||
const int numComponent = GetLabels(components, face2face, NumTri());
|
||||
|
||||
std::vector<int> comp2tri(numComponent, -1);
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
@@ -473,51 +659,9 @@ int Manifold::Impl::InitializeNewReference(
|
||||
}
|
||||
}
|
||||
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
std::map<std::pair<int, int>, int> triVert2bary;
|
||||
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int refTri = comp2tri[components[tri]];
|
||||
if (refTri == tri) continue;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_[3 * refTri + i].startVert;
|
||||
triPos[i] = vertPos_[vert];
|
||||
triVert2bary[{refTri, vert}] = i - 3;
|
||||
}
|
||||
|
||||
glm::ivec3 vertBary;
|
||||
bool coplanar = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_[3 * tri + i].startVert;
|
||||
if (triVert2bary.find({refTri, vert}) == triVert2bary.end()) {
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPos_[vert], triPos, precision_);
|
||||
if (isnan(uvw[0])) {
|
||||
coplanar = false;
|
||||
triVert2bary[{refTri, vert}] = -4;
|
||||
break;
|
||||
}
|
||||
triVert2bary[{refTri, vert}] = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.push_back(uvw);
|
||||
}
|
||||
const int bary = triVert2bary[{refTri, vert}];
|
||||
if (bary < -3) {
|
||||
coplanar = false;
|
||||
break;
|
||||
}
|
||||
vertBary[i] = bary;
|
||||
}
|
||||
|
||||
if (coplanar) {
|
||||
BaryRef& ref = triBary[tri];
|
||||
ref.tri = refTri;
|
||||
ref.vertBary = vertBary;
|
||||
}
|
||||
}
|
||||
|
||||
return nextMeshID;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
for (int tri = 0; tri < NumTri(); ++tri)
|
||||
triRef[tri].tri = comp2tri[components[tri]];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,8 +693,8 @@ void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full recalculation of the face bounding boxes, including updating the
|
||||
* collider, but does not resort the faces.
|
||||
* Does a full recalculation of the face bounding boxes, including updating
|
||||
* the collider, but does not resort the faces.
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
@@ -580,7 +724,12 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
result.precision_ = precision_;
|
||||
result.bBox_ = bBox_;
|
||||
result.halfedge_ = halfedge_;
|
||||
result.halfedgeTangent_ = halfedgeTangent_;
|
||||
result.halfedgeTangent_.resize(halfedgeTangent_.size());
|
||||
|
||||
result.meshRelation_.originalID = -1;
|
||||
for (auto& m : result.meshRelation_.meshIDtransform) {
|
||||
m.second.transform = transform_ * glm::mat4(m.second.transform);
|
||||
}
|
||||
|
||||
result.vertPos_.resize(NumVert());
|
||||
result.faceNormal_.resize(faceNormal_.size());
|
||||
@@ -588,12 +737,27 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(),
|
||||
Transform4x3({transform_}));
|
||||
|
||||
glm::mat3 normalTransform =
|
||||
glm::inverse(glm::transpose(glm::mat3(transform_)));
|
||||
glm::mat3 normalTransform = NormalTransform(transform_);
|
||||
transform(policy, faceNormal_.begin(), faceNormal_.end(),
|
||||
result.faceNormal_.begin(), TransformNormals({normalTransform}));
|
||||
transform(policy, vertNormal_.begin(), vertNormal_.end(),
|
||||
result.vertNormal_.begin(), TransformNormals({normalTransform}));
|
||||
|
||||
const bool invert = glm::determinant(glm::mat3(transform_)) < 0;
|
||||
|
||||
if (halfedgeTangent_.size() > 0) {
|
||||
for_each_n(
|
||||
policy, zip(result.halfedgeTangent_.begin(), countAt(0)),
|
||||
halfedgeTangent_.size(),
|
||||
TransformTangents({glm::mat3(transform_), invert,
|
||||
halfedgeTangent_.cptrD(), halfedge_.cptrD()}));
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
for_each_n(policy, zip(result.meshRelation_.triRef.begin(), countAt(0)),
|
||||
result.NumTri(), FlipTris({result.halfedge_.ptrD()}));
|
||||
}
|
||||
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
// axis-aligned.
|
||||
if (!result.collider_.Transform(transform_)) result.Update();
|
||||
@@ -610,8 +774,8 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the precision based on the bounding box, and limits its minimum value by
|
||||
* the optional input.
|
||||
* Sets the precision based on the bounding box, and limits its minimum value
|
||||
* by the optional input.
|
||||
*/
|
||||
void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
precision_ = glm::max(minPrecision, kTolerance * bBox_.Scale());
|
||||
@@ -621,13 +785,13 @@ void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
/**
|
||||
* If face normals are already present, this function uses them to compute
|
||||
* vertex normals (angle-weighted pseudo-normals); otherwise it also computes
|
||||
* the face normals. Face normals are only calculated when needed because nearly
|
||||
* degenerate faces will accrue rounding error, while the Boolean can retain
|
||||
* their original normal, which is more accurate and can help with merging
|
||||
* coplanar faces.
|
||||
* the face normals. Face normals are only calculated when needed because
|
||||
* nearly degenerate faces will accrue rounding error, while the Boolean can
|
||||
* retain their original normal, which is more accurate and can help with
|
||||
* merging coplanar faces.
|
||||
*
|
||||
* If the face normals have been invalidated by an operation like Warp(), ensure
|
||||
* you do faceNormal_.resize(0) before calling this function to force
|
||||
* If the face normals have been invalidated by an operation like Warp(),
|
||||
* ensure you do faceNormal_.resize(0) before calling this function to force
|
||||
* recalculation.
|
||||
*/
|
||||
void Manifold::Impl::CalculateNormals() {
|
||||
@@ -650,35 +814,26 @@ void Manifold::Impl::CalculateNormals() {
|
||||
* Remaps all the contained meshIDs to new unique values to represent new
|
||||
* instances of these meshes.
|
||||
*/
|
||||
void Manifold::Impl::IncrementMeshIDs(int start, int length) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
ASSERT(start >= 0 && length >= 0 && start + length <= triBary.size(),
|
||||
logicErr, "out of bounds");
|
||||
const auto policy = autoPolicy(length);
|
||||
// Use double the space since the Boolean has P and Q instances.
|
||||
VecDH<int> includesMeshID(2 * Manifold::Impl::meshIDCounter_, 0);
|
||||
void Manifold::Impl::IncrementMeshIDs() {
|
||||
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
|
||||
// Update keys of the transform map
|
||||
std::map<int, Relation> oldTransforms;
|
||||
std::swap(meshRelation_.meshIDtransform, oldTransforms);
|
||||
const int numMeshIDs = oldTransforms.size();
|
||||
int nextMeshID = ReserveIDs(numMeshIDs);
|
||||
for (const auto& pair : oldTransforms) {
|
||||
meshIDold2new.D().Insert(pair.first, nextMeshID);
|
||||
meshRelation_.meshIDtransform[nextMeshID++] = pair.second;
|
||||
}
|
||||
|
||||
auto begin = triBary.begin() + start;
|
||||
auto end = begin + length;
|
||||
|
||||
for_each(policy, begin, end, MarkMeshID({includesMeshID.ptrD()}));
|
||||
|
||||
inclusive_scan(autoPolicy(includesMeshID.size()), includesMeshID.begin(),
|
||||
includesMeshID.end(), includesMeshID.begin());
|
||||
|
||||
const int numMeshIDs = includesMeshID[includesMeshID.size() - 1];
|
||||
const int meshIDstart = Manifold::Impl::meshIDCounter_.fetch_add(
|
||||
numMeshIDs, std::memory_order_relaxed);
|
||||
|
||||
// We do start - 1 because the inclusive scan makes our first index 1 instead
|
||||
// of 0.
|
||||
for_each(policy, begin, end,
|
||||
UpdateMeshID({includesMeshID.cptrD(), meshIDstart - 1}));
|
||||
const int numTri = NumTri();
|
||||
for_each_n(autoPolicy(numTri), meshRelation_.triRef.begin(), numTri,
|
||||
UpdateMeshID({meshIDold2new.D()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sparse array of the bounding box overlaps between the edges of the
|
||||
* input manifold, Q and the faces of this manifold. Returned indices only
|
||||
* Returns a sparse array of the bounding box overlaps between the edges of
|
||||
* the input manifold, Q and the faces of this manifold. Returned indices only
|
||||
* point to forward halfedges.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q) const {
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "collider.h"
|
||||
#include "manifold.h"
|
||||
#include "optional_assert.h"
|
||||
#include "polygon.h"
|
||||
#include "shared.h"
|
||||
#include "sparse.h"
|
||||
#include "utils.h"
|
||||
@@ -25,16 +28,24 @@ namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
struct Manifold::Impl {
|
||||
struct MeshRelationD {
|
||||
VecDH<glm::vec3> barycentric;
|
||||
VecDH<BaryRef> triBary;
|
||||
/// The meshID of this Manifold if it is an original; -1 otherwise.
|
||||
struct Relation {
|
||||
int originalID = -1;
|
||||
glm::mat4x3 transform = glm::mat4x3(1);
|
||||
bool backSide = false;
|
||||
};
|
||||
struct MeshRelationD {
|
||||
/// The originalID of this Manifold if it is an original; -1 otherwise.
|
||||
int originalID = -1;
|
||||
int numProp = 0;
|
||||
VecDH<TriRef> triRef;
|
||||
VecDH<glm::ivec3> triProperties;
|
||||
VecDH<float> properties;
|
||||
std::map<int, Relation> meshIDtransform;
|
||||
};
|
||||
|
||||
Box bBox_;
|
||||
float precision_ = -1;
|
||||
Error status_ = Error::NO_ERROR;
|
||||
Error status_ = Error::NoError;
|
||||
VecDH<glm::vec3> vertPos_;
|
||||
VecDH<Halfedge> halfedge_;
|
||||
VecDH<glm::vec3> vertNormal_;
|
||||
@@ -43,27 +54,24 @@ struct Manifold::Impl {
|
||||
MeshRelationD meshRelation_;
|
||||
Collider collider_;
|
||||
|
||||
static std::atomic<int> meshIDCounter_;
|
||||
static std::atomic<uint32_t> meshIDCounter_;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
|
||||
Impl() {}
|
||||
enum class Shape { TETRAHEDRON, CUBE, OCTAHEDRON };
|
||||
enum class Shape { Tetrahedron, Cube, Octahedron };
|
||||
Impl(Shape);
|
||||
|
||||
Impl(const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
|
||||
int InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
Impl(const MeshGL&, std::vector<float> propertyTolerance = {});
|
||||
Impl(const Mesh&, const MeshRelationD& relation,
|
||||
const std::vector<float>& propertyTolerance = {},
|
||||
bool hasFaceIDs = false);
|
||||
|
||||
void CreateFaces(const std::vector<float>& propertyTolerance = {});
|
||||
void RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts);
|
||||
void ReinitializeReference(int meshID);
|
||||
void InitializeOriginal();
|
||||
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
|
||||
void CalculateNormals();
|
||||
void IncrementMeshIDs(int start, int length);
|
||||
void IncrementMeshIDs();
|
||||
|
||||
void Update();
|
||||
void MarkFailure(Error status);
|
||||
@@ -75,6 +83,11 @@ struct Manifold::Impl {
|
||||
int NumVert() const { return vertPos_.size(); }
|
||||
int NumEdge() const { return halfedge_.size() / 2; }
|
||||
int NumTri() const { return halfedge_.size() / 3; }
|
||||
int NumProp() const { return meshRelation_.numProp; }
|
||||
int NumPropVert() const {
|
||||
return NumProp() == 0 ? NumVert()
|
||||
: meshRelation_.properties.size() / NumProp();
|
||||
}
|
||||
|
||||
// properties.cu
|
||||
Properties GetProperties() const;
|
||||
@@ -92,16 +105,16 @@ struct Manifold::Impl {
|
||||
void Finish();
|
||||
void SortVerts();
|
||||
void ReindexVerts(const VecDH<int>& vertNew2Old, int numOldVert);
|
||||
void CompactProps();
|
||||
void GetFaceBoxMorton(VecDH<Box>& faceBox, VecDH<uint32_t>& faceMorton) const;
|
||||
void SortFaces(VecDH<Box>& faceBox, VecDH<uint32_t>& faceMorton);
|
||||
void GatherFaces(const VecDH<int>& faceNew2Old);
|
||||
void GatherFaces(const Impl& old, const VecDH<int>& faceNew2Old);
|
||||
|
||||
// face_op.cu
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary);
|
||||
Polygons Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<TriRef>& halfedgeRef);
|
||||
PolygonsIdx Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
|
||||
// edge_op.cu
|
||||
void SimplifyTopology();
|
||||
@@ -116,7 +129,7 @@ struct Manifold::Impl {
|
||||
|
||||
// smoothing.cu
|
||||
void CreateTangents(const std::vector<Smoothness>&);
|
||||
MeshRelationD Subdivide(int n);
|
||||
VecDH<Barycentric> Subdivide(int n);
|
||||
void Refine(int n);
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "boolean3.h"
|
||||
#include "csg_tree.h"
|
||||
#include "impl.h"
|
||||
@@ -67,6 +71,12 @@ Manifold::Manifold(std::shared_ptr<CsgNode> pNode) : pNode_(pNode) {}
|
||||
Manifold::Manifold(std::shared_ptr<Impl> pImpl_)
|
||||
: pNode_(std::make_shared<CsgLeafNode>(pImpl_)) {}
|
||||
|
||||
Manifold Manifold::Invalid() {
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
pImpl_->status_ = Error::InvalidConstruction;
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
Manifold& Manifold::operator=(const Manifold& other) {
|
||||
if (this != &other) {
|
||||
pNode_ = other.pNode_;
|
||||
@@ -75,44 +85,49 @@ Manifold& Manifold::operator=(const Manifold& other) {
|
||||
}
|
||||
|
||||
CsgLeafNode& Manifold::GetCsgLeafNode() const {
|
||||
if (pNode_->GetNodeType() != CsgNodeType::LEAF) {
|
||||
if (pNode_->GetNodeType() != CsgNodeType::Leaf) {
|
||||
pNode_ = pNode_->ToLeafNode();
|
||||
}
|
||||
return *std::static_pointer_cast<CsgLeafNode>(pNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a MeshGL into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will return an empty Manifold
|
||||
* and set an Error Status if the result is not an oriented 2-manifold. Will
|
||||
* collapse degenerate triangles and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless round-trip
|
||||
* of data from GetMeshGL. For multi-material input, use ReserveIDs to set a
|
||||
* unique originalID for each material, and sort the materials into triangle
|
||||
* runs.
|
||||
*
|
||||
* @param meshGL The input MeshGL.
|
||||
* @param propertyTolerance A vector of precision values for each property
|
||||
* beyond position. If specified, the propertyTolerance vector must have size =
|
||||
* numProp - 3. This is the amount of interpolation error allowed before two
|
||||
* neighboring triangles are considered to be on a property boundary edge.
|
||||
* Property boundary edges will be retained across operations even if the
|
||||
* triangles are coplanar. Defaults to 1e-5, which works well for most
|
||||
* properties in the [-1, 1] range.
|
||||
*/
|
||||
Manifold::Manifold(const MeshGL& meshGL,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: pNode_(std::make_shared<CsgLeafNode>(
|
||||
std::make_shared<Impl>(meshGL, propertyTolerance))) {}
|
||||
|
||||
/**
|
||||
* Convert a Mesh into a Manifold. Will return an empty Manifold
|
||||
* and set an Error Status if the Mesh is not an oriented 2-manifold. Will
|
||||
* collapse degenerate triangles and unnecessary vertices.
|
||||
*
|
||||
* The three optional inputs should all be specified if any are. These define
|
||||
* any properties you may have on this mesh. These properties are not saved in
|
||||
* the Manifold, but rather used to determine which coplanar triangles can be
|
||||
* safely merged due to all properties being colinear. Any edges that define
|
||||
* property boundaries will be retained in the output of arbitrary Boolean
|
||||
* operations so that these properties can be properly reapplied to the result
|
||||
* using the MeshRelation.
|
||||
*
|
||||
* @param mesh The input Mesh.
|
||||
* @param triProperties A vector of the same length as triVerts, filled with
|
||||
* references to the properties index. Note the same vertex can have different
|
||||
* properties in different triangles.
|
||||
* @param properties A vector whose length is the largest index in
|
||||
* triProperties times the length of propertyTolerance (the number of
|
||||
* properties). Think of it as a property matrix indexed as [index *
|
||||
* numProperties + propertyNum].
|
||||
* @param propertyTolerance A vector of precision values for each property.
|
||||
* This is the amount of interpolation error allowed before two neighboring
|
||||
* triangles are considered not coplanar. A good place to start is 1e-5 times
|
||||
* the largest value you expect this property to take.
|
||||
*/
|
||||
Manifold::Manifold(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(
|
||||
mesh, triProperties, properties, propertyTolerance))) {}
|
||||
Manifold::Manifold(const Mesh& mesh) {
|
||||
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
|
||||
pNode_ =
|
||||
std::make_shared<CsgLeafNode>(std::make_shared<Impl>(mesh, relation));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable for
|
||||
@@ -138,29 +153,34 @@ Mesh Manifold::GetMesh() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
MeshGL Manifold::GetMeshGL() const {
|
||||
/**
|
||||
* The most complete output of this library, returning a MeshGL that is designed
|
||||
* to easily push into a renderer, including all interleaved vertex properties
|
||||
* that may have been input. It also includes relations to all the input meshes
|
||||
* that form a part of this result and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original MeshGL inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output MeshGL to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* MeshGLs must use the same channels for their normals.
|
||||
*/
|
||||
MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
const Impl& impl = *GetCsgLeafNode().GetImpl();
|
||||
|
||||
const int numVert = NumVert();
|
||||
const int numProp = NumProp();
|
||||
const int numVert = NumPropVert();
|
||||
const int numTri = NumTri();
|
||||
|
||||
const bool isOriginal = impl.meshRelation_.originalID >= 0;
|
||||
const bool updateNormals =
|
||||
!isOriginal && glm::all(glm::greaterThan(normalIdx, glm::ivec3(2)));
|
||||
|
||||
MeshGL out;
|
||||
out.vertPos.resize(3 * numVert);
|
||||
out.vertNormal.resize(3 * numVert);
|
||||
out.numProp = 3 + numProp;
|
||||
out.triVerts.resize(3 * numTri);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
const glm::vec3 v = impl.vertPos_[i];
|
||||
out.vertPos[3 * i] = v.x;
|
||||
out.vertPos[3 * i + 1] = v.y;
|
||||
out.vertPos[3 * i + 2] = v.z;
|
||||
const glm::vec3 n = impl.vertNormal_[i];
|
||||
out.vertNormal[3 * i] = n.x;
|
||||
out.vertNormal[3 * i + 1] = n.y;
|
||||
out.vertNormal[3 * i + 2] = n.z;
|
||||
}
|
||||
for (int i = 0; i < numTri * 3; ++i) {
|
||||
out.triVerts[i] = impl.halfedge_[i].startVert;
|
||||
}
|
||||
|
||||
const int numHalfedge = impl.halfedgeTangent_.size();
|
||||
out.halfedgeTangent.resize(4 * numHalfedge);
|
||||
for (int i = 0; i < numHalfedge; ++i) {
|
||||
@@ -171,69 +191,127 @@ MeshGL Manifold::GetMeshGL() const {
|
||||
out.halfedgeTangent[4 * i + 3] = t.w;
|
||||
}
|
||||
|
||||
// Sort the triangles into runs
|
||||
out.faceID.resize(numTri);
|
||||
std::vector<int> triNew2Old(numTri);
|
||||
std::iota(triNew2Old.begin(), triNew2Old.end(), 0);
|
||||
const TriRef* triRef = impl.meshRelation_.triRef.cptrD();
|
||||
// Don't sort originals - keep them in order
|
||||
if (!isOriginal) {
|
||||
std::sort(triNew2Old.begin(), triNew2Old.end(), [triRef](int a, int b) {
|
||||
return triRef[a].originalID == triRef[b].originalID
|
||||
? triRef[a].meshID < triRef[b].meshID
|
||||
: triRef[a].originalID < triRef[b].originalID;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<glm::mat3> runNormalTransform;
|
||||
|
||||
auto addRun = [updateNormals, isOriginal](
|
||||
MeshGL& out, std::vector<glm::mat3>& runNormalTransform,
|
||||
int tri, const Impl::Relation& rel) {
|
||||
out.runIndex.push_back(3 * tri);
|
||||
out.runOriginalID.push_back(rel.originalID);
|
||||
if (updateNormals) {
|
||||
runNormalTransform.push_back(NormalTransform(rel.transform) *
|
||||
(rel.backSide ? -1.0f : 1.0f));
|
||||
}
|
||||
if (!isOriginal) {
|
||||
for (const int col : {0, 1, 2, 3}) {
|
||||
for (const int row : {0, 1, 2}) {
|
||||
out.runTransform.push_back(rel.transform[col][row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto meshIDtransform = impl.meshRelation_.meshIDtransform;
|
||||
int lastID = -1;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
const int oldTri = triNew2Old[tri];
|
||||
const auto ref = triRef[oldTri];
|
||||
const int meshID = ref.meshID;
|
||||
|
||||
out.faceID[tri] = ref.tri;
|
||||
for (const int i : {0, 1, 2})
|
||||
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
|
||||
|
||||
if (meshID != lastID) {
|
||||
addRun(out, runNormalTransform, tri, meshIDtransform.at(meshID));
|
||||
meshIDtransform.erase(meshID);
|
||||
lastID = meshID;
|
||||
}
|
||||
}
|
||||
// Add runs for originals that did not contribute any faces to the output
|
||||
for (const auto& pair : meshIDtransform) {
|
||||
addRun(out, runNormalTransform, numTri, pair.second);
|
||||
}
|
||||
out.runIndex.push_back(3 * numTri);
|
||||
|
||||
// Early return for no props
|
||||
if (numProp == 0) {
|
||||
out.vertProperties.resize(3 * numVert);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
const glm::vec3 v = impl.vertPos_[i];
|
||||
out.vertProperties[3 * i] = v.x;
|
||||
out.vertProperties[3 * i + 1] = v.y;
|
||||
out.vertProperties[3 * i + 2] = v.z;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Duplicate verts with different props
|
||||
std::vector<int> vert2idx(impl.NumVert(), -1);
|
||||
std::map<std::pair<int, int>, int> vertPropPair;
|
||||
for (int run = 0; run < out.runOriginalID.size(); ++run) {
|
||||
for (int tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3;
|
||||
++tri) {
|
||||
const glm::ivec3 triProp =
|
||||
impl.meshRelation_.triProperties[triNew2Old[tri]];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int prop = triProp[i];
|
||||
const int vert = out.triVerts[3 * tri + i];
|
||||
|
||||
const auto it = vertPropPair.find({vert, prop});
|
||||
if (it != vertPropPair.end()) {
|
||||
out.triVerts[3 * tri + i] = it->second;
|
||||
continue;
|
||||
}
|
||||
const int idx = out.vertProperties.size() / out.numProp;
|
||||
vertPropPair.insert({{vert, prop}, idx});
|
||||
out.triVerts[3 * tri + i] = idx;
|
||||
|
||||
for (int p : {0, 1, 2}) {
|
||||
out.vertProperties.push_back(impl.vertPos_[vert][p]);
|
||||
}
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
out.vertProperties.push_back(
|
||||
impl.meshRelation_.properties[prop * numProp + p]);
|
||||
}
|
||||
if (updateNormals) {
|
||||
glm::vec3 normal;
|
||||
const int start = out.vertProperties.size() - out.numProp;
|
||||
for (int i : {0, 1, 2}) {
|
||||
normal[i] = out.vertProperties[start + normalIdx[i]];
|
||||
}
|
||||
normal = glm::normalize(runNormalTransform[run] * normal);
|
||||
for (int i : {0, 1, 2}) {
|
||||
out.vertProperties[start + normalIdx[i]] = normal[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (vert2idx[vert] == -1) {
|
||||
vert2idx[vert] = idx;
|
||||
} else {
|
||||
out.mergeFromVert.push_back(idx);
|
||||
out.mergeToVert.push_back(vert2idx[vert]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
int Manifold::circularSegments_ = 0;
|
||||
float Manifold::circularAngle_ = 10.0f;
|
||||
float Manifold::circularEdgeLength_ = 1.0f;
|
||||
|
||||
/**
|
||||
* Sets an angle constraint the default number of circular segments for the
|
||||
* Cylinder(), Sphere(), and Revolve() constructors. The number of segments will
|
||||
* be rounded up to the nearest factor of four.
|
||||
*
|
||||
* @param angle The minimum angle in degrees between consecutive segments. The
|
||||
* angle will increase if the the segments hit the minimum edge length. Default
|
||||
* is 10 degrees.
|
||||
*/
|
||||
void Manifold::SetMinCircularAngle(float angle) {
|
||||
if (angle <= 0) return;
|
||||
Manifold::circularAngle_ = angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a length constraint the default number of circular segments for the
|
||||
* Cylinder(), Sphere(), and Revolve() constructors. The number of segments will
|
||||
* be rounded up to the nearest factor of four.
|
||||
*
|
||||
* @param length The minimum length of segments. The length will
|
||||
* increase if the the segments hit the minimum angle. Default is 1.0.
|
||||
*/
|
||||
void Manifold::SetMinCircularEdgeLength(float length) {
|
||||
if (length <= 0) return;
|
||||
Manifold::circularEdgeLength_ = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default number of circular segments for the
|
||||
* Cylinder(), Sphere(), and Revolve() constructors. Overrides the edge length
|
||||
* and angle constraints and sets the number of segments to exactly this value.
|
||||
*
|
||||
* @param number Number of circular segments. Default is 0, meaning no
|
||||
* constraint is applied.
|
||||
*/
|
||||
void Manifold::SetCircularSegments(int number) {
|
||||
if (number < 3 && number != 0) return;
|
||||
Manifold::circularSegments_ = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the result of the SetMinCircularAngle(),
|
||||
* SetMinCircularEdgeLength(), and SetCircularSegments() defaults.
|
||||
*
|
||||
* @param radius For a given radius of circle, determine how many default
|
||||
* segments there will be.
|
||||
*/
|
||||
int Manifold::GetCircularSegments(float radius) {
|
||||
if (Manifold::circularSegments_ > 0) return Manifold::circularSegments_;
|
||||
int nSegA = 360.0f / Manifold::circularAngle_;
|
||||
int nSegL = 2.0f * radius * glm::pi<float>() / Manifold::circularEdgeLength_;
|
||||
int nSeg = fmin(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return nSeg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
@@ -241,9 +319,9 @@ bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); }
|
||||
/**
|
||||
* Returns the reason for an input Mesh producing an empty Manifold. This Status
|
||||
* only applies to Manifolds newly-created from an input Mesh - once they are
|
||||
* combined into a new Manifold via operations, the status reverts to NO_ERROR,
|
||||
* combined into a new Manifold via operations, the status reverts to NoError,
|
||||
* simply processing the problem mesh as empty. Likewise, empty meshes may still
|
||||
* show NO_ERROR, for instance if they are small enough relative to their
|
||||
* show NoError, for instance if they are small enough relative to their
|
||||
* precision to be collapsed to nothing.
|
||||
*/
|
||||
Manifold::Error Manifold::Status() const {
|
||||
@@ -261,6 +339,18 @@ int Manifold::NumEdge() const { return GetCsgLeafNode().GetImpl()->NumEdge(); }
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
int Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }
|
||||
/**
|
||||
* The number of properties per vertex in the Manifold.
|
||||
*/
|
||||
int Manifold::NumProp() const { return GetCsgLeafNode().GetImpl()->NumProp(); }
|
||||
/**
|
||||
* The number of property vertices in the Manifold. This will always be >=
|
||||
* NumVert, as some physical vertices may be duplicated to account for different
|
||||
* properties on different neighboring triangles.
|
||||
*/
|
||||
int Manifold::NumPropVert() const {
|
||||
return GetCsgLeafNode().GetImpl()->NumPropVert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
@@ -310,30 +400,6 @@ Curvature Manifold::GetCurvature() const {
|
||||
return GetCsgLeafNode().GetImpl()->GetCurvature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous meshes, for the purpose of assigning
|
||||
* properties like texture coordinates. The triBary vector is the same length as
|
||||
* Mesh.triVerts: BaryRef.originalID indicates the source mesh and BaryRef.tri
|
||||
* is that mesh's triangle index to which these barycentric coordinates refer.
|
||||
* BaryRef.vertBary gives an index for each vertex into the barycentric vector
|
||||
* if that index is >= 0, indicating it is a new vertex. If the index is < 0,
|
||||
* this indicates it is an original vertex, the index + 3 vert of the referenced
|
||||
* triangle.
|
||||
*
|
||||
* BaryRef.meshID is a unique ID to the particular instance of a given mesh. For
|
||||
* instance, if you want to convert the triangle mesh to a polygon mesh, all the
|
||||
* triangles from a given face will have the same .meshID and .tri values.
|
||||
*/
|
||||
MeshRelation Manifold::GetMeshRelation() const {
|
||||
MeshRelation out;
|
||||
const auto& relation = GetCsgLeafNode().GetImpl()->meshRelation_;
|
||||
out.triBary.insert(out.triBary.end(), relation.triBary.begin(),
|
||||
relation.triBary.end());
|
||||
out.barycentric.insert(out.barycentric.end(), relation.barycentric.begin(),
|
||||
relation.barycentric.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
@@ -344,26 +410,30 @@ int Manifold::OriginalID() const {
|
||||
}
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
|
||||
* meaning it will now be referenced by its descendents instead of the meshes it
|
||||
* was built from, allowing you to differentiate the copies when applying your
|
||||
* properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to construct
|
||||
* a new manifold.
|
||||
* This function condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. In the process the relation to ancestor meshes is lost
|
||||
* and this new Manifold is marked an original. Properties are preserved, so if
|
||||
* they do not match across an edge, that edge will be kept.
|
||||
*/
|
||||
Manifold Manifold::AsOriginal() const {
|
||||
auto newImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
|
||||
newImpl->InitializeNewReference();
|
||||
newImpl->meshRelation_.originalID = ReserveIDs(1);
|
||||
newImpl->InitializeOriginal();
|
||||
newImpl->CreateFaces();
|
||||
newImpl->SimplifyTopology();
|
||||
newImpl->Finish();
|
||||
return Manifold(std::make_shared<CsgLeafNode>(newImpl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first of n sequential new unique mesh IDs for marking sets of
|
||||
* triangles that can be looked up after further operations. Assign to
|
||||
* MeshGL.runOriginalID vector.
|
||||
*/
|
||||
uint32_t Manifold::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::ReserveIDs(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should always be true. Also checks saneness of the internal data structures.
|
||||
*/
|
||||
@@ -453,6 +523,23 @@ Manifold Manifold::Transform(const glm::mat4x3& m) const {
|
||||
return Manifold(pNode_->Transform(m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror this Manifold over the plane described by the unit form of the given
|
||||
* normal vector. If the length of the normal is zero, an empty Manifold is
|
||||
* returned. This operation can be chained. Transforms are combined and applied
|
||||
* lazily.
|
||||
*
|
||||
* @param normal The normal vector of the plane to be mirrored over
|
||||
*/
|
||||
Manifold Manifold::Mirror(glm::vec3 normal) const {
|
||||
if (glm::length(normal) == 0.) {
|
||||
return Manifold();
|
||||
}
|
||||
auto n = glm::normalize(normal);
|
||||
auto m = glm::mat4x3(glm::mat3(1.0f) - 2.0f * glm::outerProduct(n, n));
|
||||
return Manifold(pNode_->Transform(m));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
@@ -504,8 +591,7 @@ Manifold Manifold::Refine(int n) const {
|
||||
* @param op The type of operation to perform.
|
||||
*/
|
||||
Manifold Manifold::Boolean(const Manifold& second, OpType op) const {
|
||||
std::vector<std::shared_ptr<CsgNode>> children({pNode_, second.pNode_});
|
||||
return Manifold(std::make_shared<CsgOpNode>(children, op));
|
||||
return Manifold(pNode_->Boolean(second.pNode_, op));
|
||||
}
|
||||
|
||||
Manifold Manifold::BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
@@ -524,7 +610,7 @@ Manifold Manifold::BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
* Shorthand for Boolean Union.
|
||||
*/
|
||||
Manifold Manifold::operator+(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::ADD);
|
||||
return Boolean(Q, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,7 +625,7 @@ Manifold& Manifold::operator+=(const Manifold& Q) {
|
||||
* Shorthand for Boolean Difference.
|
||||
*/
|
||||
Manifold Manifold::operator-(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::SUBTRACT);
|
||||
return Boolean(Q, OpType::Subtract);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,7 +640,7 @@ Manifold& Manifold::operator-=(const Manifold& Q) {
|
||||
* Shorthand for Boolean Intersection.
|
||||
*/
|
||||
Manifold Manifold::operator^(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::INTERSECT);
|
||||
return Boolean(Q, OpType::Intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,11 +662,11 @@ std::pair<Manifold, Manifold> Manifold::Split(const Manifold& cutter) const {
|
||||
auto impl1 = GetCsgLeafNode().GetImpl();
|
||||
auto impl2 = cutter.GetCsgLeafNode().GetImpl();
|
||||
|
||||
Boolean3 boolean(*impl1, *impl2, OpType::SUBTRACT);
|
||||
Boolean3 boolean(*impl1, *impl2, OpType::Subtract);
|
||||
auto result1 = std::make_shared<CsgLeafNode>(
|
||||
std::make_unique<Impl>(boolean.Result(OpType::INTERSECT)));
|
||||
std::make_unique<Impl>(boolean.Result(OpType::Intersect)));
|
||||
auto result2 = std::make_shared<CsgLeafNode>(
|
||||
std::make_unique<Impl>(boolean.Result(OpType::SUBTRACT)));
|
||||
std::make_unique<Impl>(boolean.Result(OpType::Subtract)));
|
||||
return std::make_pair(Manifold(result1), Manifold(result2));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ __host__ __device__ inline glm::vec3 UVW(int vert,
|
||||
return uvw;
|
||||
}
|
||||
|
||||
__host__ __device__ inline glm::mat3 NormalTransform(
|
||||
const glm::mat4x3& transform) {
|
||||
return glm::inverse(glm::transpose(glm::mat3(transform)));
|
||||
}
|
||||
|
||||
/**
|
||||
* By using the closest axis-aligned projection to the normal instead of a
|
||||
* projection along the normal, we avoid introducing any rounding error.
|
||||
@@ -78,34 +83,43 @@ __host__ __device__ inline glm::vec3 GetBarycentric(const glm::vec3& v,
|
||||
triPos[1] - triPos[0]);
|
||||
const glm::vec3 d2(glm::dot(edges[0], edges[0]), glm::dot(edges[1], edges[1]),
|
||||
glm::dot(edges[2], edges[2]));
|
||||
int longside = d2[0] > d2[1] && d2[0] > d2[2] ? 0 : d2[1] > d2[2] ? 1 : 2;
|
||||
const int longSide = d2[0] > d2[1] && d2[0] > d2[2] ? 0
|
||||
: d2[1] > d2[2] ? 1
|
||||
: 2;
|
||||
const glm::vec3 crossP = glm::cross(edges[0], edges[1]);
|
||||
const float area2 = glm::dot(crossP, crossP);
|
||||
const float tol2 = precision * precision;
|
||||
const float vol = glm::dot(crossP, v - triPos[2]);
|
||||
if (vol * vol > area2 * tol2) return glm::vec3(NAN);
|
||||
|
||||
if (d2[longside] < tol2) { // point
|
||||
glm::vec3 uvw(0);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const glm::vec3 dv = v - triPos[i];
|
||||
if (glm::dot(dv, dv) < tol2) {
|
||||
// Return exactly equal if within tolerance of vert.
|
||||
uvw[i] = 1;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
|
||||
if (d2[longSide] < tol2) { // point
|
||||
return glm::vec3(1, 0, 0);
|
||||
} else if (area2 > d2[longside] * tol2) { // triangle
|
||||
glm::vec3 uvw(0);
|
||||
for (int i : {0, 1, 2}) {
|
||||
int j = i + 1;
|
||||
if (j > 2) j -= 3;
|
||||
uvw[i] = glm::dot(glm::cross(edges[i], v - triPos[j]), crossP);
|
||||
} else if (area2 > d2[longSide] * tol2) { // triangle
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int j = Next3(i);
|
||||
const glm::vec3 crossPv = glm::cross(edges[i], v - triPos[j]);
|
||||
const float area2v = glm::dot(crossPv, crossPv);
|
||||
// Return exactly equal if within tolerance of edge.
|
||||
uvw[i] = area2v < d2[i] * tol2 ? 0 : glm::dot(crossPv, crossP);
|
||||
}
|
||||
uvw /= (uvw[0] + uvw[1] + uvw[2]);
|
||||
return uvw;
|
||||
} else { // line
|
||||
int nextside = longside + 1;
|
||||
if (nextside > 2) nextside -= 3;
|
||||
const int nextV = Next3(longSide);
|
||||
const float alpha =
|
||||
glm::dot(v - triPos[nextside], edges[longside]) / d2[longside];
|
||||
glm::vec3 uvw(0);
|
||||
uvw[longside] = 0;
|
||||
uvw[nextside++] = 1 - alpha;
|
||||
if (nextside > 2) nextside -= 3;
|
||||
uvw[nextside] = alpha;
|
||||
glm::dot(v - triPos[nextV], edges[longSide]) / d2[longSide];
|
||||
uvw[longSide] = 0;
|
||||
uvw[nextV] = 1 - alpha;
|
||||
const int lastV = Next3(nextV);
|
||||
uvw[lastV] = alpha;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
@@ -125,6 +139,24 @@ struct Halfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
glm::vec3 uvw;
|
||||
};
|
||||
|
||||
struct TriRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// The triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri].
|
||||
int tri;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a temporary edge structure which only stores edges forward and
|
||||
* references the halfedge it was created from.
|
||||
@@ -191,5 +223,14 @@ inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) {
|
||||
<< ", pairedHalfedge = " << edge.pairedHalfedge
|
||||
<< ", face = " << edge.face;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) {
|
||||
return stream << "tri = " << bary.tri << ", uvw = " << bary.uvw;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const TriRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri;
|
||||
}
|
||||
#endif
|
||||
} // namespace manifold
|
||||
|
||||
@@ -33,10 +33,23 @@ __host__ __device__ int VertsPerTri(int edgeVerts) {
|
||||
return (edgeVerts * edgeVerts + edgeVerts) / 2;
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
glm::vec3 uvw;
|
||||
};
|
||||
/**
|
||||
* Retained verts are part of several triangles, and it doesn't matter which one
|
||||
* the vertBary refers to. Here, whichever is last will win and it's done on the
|
||||
* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU
|
||||
* version if desired.
|
||||
*/
|
||||
void FillRetainedVerts(VecDH<Barycentric>& vertBary,
|
||||
const VecDH<Halfedge>& halfedge_) {
|
||||
const int numTri = halfedge_.size() / 3;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
glm::vec3 uvw(0);
|
||||
uvw[i] = 1;
|
||||
vertBary[halfedge_[3 * tri + i].startVert] = {tri, uvw};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReindexHalfedge {
|
||||
int* half2Edge;
|
||||
@@ -51,6 +64,7 @@ struct ReindexHalfedge {
|
||||
|
||||
struct EdgeVerts {
|
||||
glm::vec3* vertPos;
|
||||
Barycentric* vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
|
||||
@@ -59,35 +73,32 @@ struct EdgeVerts {
|
||||
TmpEdge edgeVerts = thrust::get<1>(in);
|
||||
|
||||
float invTotal = 1.0f / n;
|
||||
for (int i = 1; i < n; ++i)
|
||||
vertPos[startIdx + (n - 1) * edge + i - 1] =
|
||||
(float(n - i) * vertPos[edgeVerts.first] +
|
||||
float(i) * vertPos[edgeVerts.second]) *
|
||||
invTotal;
|
||||
for (int i = 1; i < n; ++i) {
|
||||
const int vert = startIdx + (n - 1) * edge + i - 1;
|
||||
const float v = i * invTotal;
|
||||
const float u = 1 - v;
|
||||
vertPos[vert] =
|
||||
u * vertPos[edgeVerts.first] + v * vertPos[edgeVerts.second];
|
||||
|
||||
const int tri = edgeVerts.halfedgeIdx / 3;
|
||||
const int idx = edgeVerts.halfedgeIdx - 3 * tri;
|
||||
glm::vec3 uvw(0);
|
||||
uvw[idx] = u;
|
||||
uvw[Next3(idx)] = v;
|
||||
vertBary[vert] = {tri, uvw};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InteriorVerts {
|
||||
glm::vec3* vertPos;
|
||||
glm::vec3* uvw;
|
||||
BaryRef* triBary;
|
||||
glm::vec3* uvwNew;
|
||||
BaryRef* triBaryNew;
|
||||
const glm::vec3* uvwOld;
|
||||
Barycentric* vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, BaryRef> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const BaryRef baryOld = thrust::get<1>(in);
|
||||
|
||||
glm::mat3 uvwOldTri;
|
||||
for (int i : {0, 1, 2}) uvwOldTri[i] = UVW(baryOld.vertBary[i], uvwOld);
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
const float invTotal = 1.0f / n;
|
||||
int posTri = tri * n * n;
|
||||
int posBary = tri * VertsPerTri(n + 1);
|
||||
int pos = startIdx + tri * VertsPerTri(n - 2);
|
||||
for (int i = 0; i <= n; ++i) {
|
||||
for (int j = 0; j <= n - i; ++j) {
|
||||
@@ -95,34 +106,13 @@ struct InteriorVerts {
|
||||
const float u = invTotal * j;
|
||||
const float v = invTotal * k;
|
||||
const float w = invTotal * i;
|
||||
const int first = posBary;
|
||||
uvw[posBary] = {u, v, w};
|
||||
uvwNew[posBary] = uvwOldTri * uvw[posBary];
|
||||
++posBary;
|
||||
if (j == n - i) continue;
|
||||
if (i == 0 || j == 0 || k == 0 || j == n - i) continue;
|
||||
|
||||
// The three retained verts are denoted by their index - 3. uvw entries
|
||||
// are added for them out of laziness of indexing only.
|
||||
const int a = (k == n) ? -2 : first;
|
||||
const int b = (i == n - 1) ? -1 : first + n - i + 1;
|
||||
const int c = (j == n - 1) ? -3 : first + 1;
|
||||
glm::ivec3 vertBary(c, a, b);
|
||||
triBary[posTri] = {-1, -1, tri, vertBary};
|
||||
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID, baryOld.tri,
|
||||
vertBary};
|
||||
if (j < n - 1 - i) {
|
||||
int d = b + 1; // d cannot be a retained vert
|
||||
vertBary = {b, d, c};
|
||||
triBary[posTri] = {-1, -1, tri, vertBary};
|
||||
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID,
|
||||
baryOld.tri, vertBary};
|
||||
}
|
||||
vertPos[pos] = u * vertPos[halfedge[3 * tri].startVert] + //
|
||||
v * vertPos[halfedge[3 * tri + 1].startVert] + //
|
||||
w * vertPos[halfedge[3 * tri + 2].startVert];
|
||||
|
||||
if (i == 0 || j == 0 || k == 0) continue;
|
||||
|
||||
vertPos[pos++] = u * vertPos[halfedge[3 * tri].startVert] + //
|
||||
v * vertPos[halfedge[3 * tri + 1].startVert] + //
|
||||
w * vertPos[halfedge[3 * tri + 2].startVert];
|
||||
vertBary[pos++] = {tri, {u, v, w}};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,24 +212,6 @@ struct SmoothBezier {
|
||||
}
|
||||
};
|
||||
|
||||
struct TriBary2Vert {
|
||||
Barycentric* vertBary;
|
||||
int* lock;
|
||||
const glm::vec3* uvw;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef, int> in) {
|
||||
const BaryRef baryRef = thrust::get<0>(in);
|
||||
const int tri = thrust::get<1>(in);
|
||||
|
||||
for (int i : {0, 1, 2}) {
|
||||
int vert = halfedge[3 * tri + i].startVert;
|
||||
if (AtomicAdd(lock[vert], 1) != 0) continue;
|
||||
vertBary[vert] = {baryRef.tri, UVW(baryRef.vertBary[i], uvw)};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InterpTri {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec4* halfedgeTangent;
|
||||
@@ -365,14 +337,14 @@ void Manifold::Impl::CreateTangents(
|
||||
vertNormal_.cptrD(), halfedge_.cptrD()}));
|
||||
|
||||
if (!sharpenedEdges.empty()) {
|
||||
const VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
const VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
// sharpenedEdges are referenced to the input Mesh, but the triangles have
|
||||
// been sorted in creating the Manifold, so the indices are converted using
|
||||
// meshRelation_.
|
||||
std::vector<int> oldHalfedge2New(halfedge_.size());
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
int oldTri = triBary[tri].tri;
|
||||
int oldTri = triRef[tri].tri;
|
||||
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
|
||||
}
|
||||
|
||||
@@ -459,24 +431,22 @@ void Manifold::Impl::CreateTangents(
|
||||
* run after the new vertices have moved, which is a likely scenario after
|
||||
* refinement (smoothing).
|
||||
*/
|
||||
Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return meshRelation_;
|
||||
VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return VecDH<Barycentric>();
|
||||
faceNormal_.resize(0);
|
||||
vertNormal_.resize(0);
|
||||
int numVert = NumVert();
|
||||
int numEdge = NumEdge();
|
||||
int numTri = NumTri();
|
||||
const int numVert = NumVert();
|
||||
const int numEdge = NumEdge();
|
||||
const int numTri = NumTri();
|
||||
// Append new verts
|
||||
int vertsPerEdge = n - 1;
|
||||
int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
const int vertsPerEdge = n - 1;
|
||||
const int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
vertPos_.resize(triVertStart + numTri * VertsPerTri(n - 2));
|
||||
VecDH<Barycentric> vertBary(vertPos_.size());
|
||||
FillRetainedVerts(vertBary, halfedge_);
|
||||
|
||||
MeshRelationD relation;
|
||||
relation.barycentric.resize(numTri * VertsPerTri(n + 1));
|
||||
relation.triBary.resize(n * n * numTri);
|
||||
MeshRelationD oldMeshRelation = std::move(meshRelation_);
|
||||
meshRelation_.barycentric.resize(relation.barycentric.size());
|
||||
meshRelation_.triBary.resize(relation.triBary.size());
|
||||
meshRelation_.triRef.resize(n * n * numTri);
|
||||
meshRelation_.originalID = oldMeshRelation.originalID;
|
||||
|
||||
VecDH<TmpEdge> edges = CreateTmpEdges(halfedge_);
|
||||
@@ -485,36 +455,40 @@ Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
ReindexHalfedge({half2Edge.ptrD()}));
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
EdgeVerts({vertPos_.ptrD(), numVert, n}));
|
||||
for_each_n(
|
||||
policy, zip(countAt(0), oldMeshRelation.triBary.begin()), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), relation.barycentric.ptrD(),
|
||||
relation.triBary.ptrD(), meshRelation_.barycentric.ptrD(),
|
||||
meshRelation_.triBary.ptrD(),
|
||||
oldMeshRelation.barycentric.cptrD(), triVertStart, n,
|
||||
halfedge_.ptrD()}));
|
||||
EdgeVerts({vertPos_.ptrD(), vertBary.ptrD(), numVert, n}));
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), vertBary.ptrD(), triVertStart, n,
|
||||
halfedge_.cptrD()}));
|
||||
// Create sub-triangles
|
||||
VecDH<glm::ivec3> triVerts(n * n * numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
SplitTris({triVerts.ptrD(), halfedge_.cptrD(), half2Edge.cptrD(),
|
||||
numVert, triVertStart, n}));
|
||||
CreateHalfedges(triVerts);
|
||||
return relation;
|
||||
// Make original since the subdivided faces are intended to be warped into
|
||||
// being non-coplanar, and hence not being related to the original faces.
|
||||
meshRelation_.originalID = ReserveIDs(1);
|
||||
InitializeOriginal();
|
||||
|
||||
if (meshRelation_.numProp > 0) {
|
||||
meshRelation_.properties.resize(meshRelation_.numProp * numVert);
|
||||
meshRelation_.triProperties.resize(meshRelation_.triRef.size());
|
||||
// Fill properties according to barycentric.
|
||||
// Set triProp to share properties on continuous edges.
|
||||
// Duplicate properties will be removed during sorting.
|
||||
}
|
||||
|
||||
return vertBary;
|
||||
}
|
||||
|
||||
void Manifold::Impl::Refine(int n) {
|
||||
Manifold::Impl old = *this;
|
||||
MeshRelationD relation = Subdivide(n);
|
||||
VecDH<Barycentric> vertBary = Subdivide(n);
|
||||
if (vertBary.size() == 0) return;
|
||||
|
||||
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
|
||||
VecDH<Barycentric> vertBary(NumVert());
|
||||
VecDH<int> lock(NumVert(), 0);
|
||||
auto policy = autoPolicy(NumTri());
|
||||
for_each_n(policy, zip(relation.triBary.begin(), countAt(0)), NumTri(),
|
||||
TriBary2Vert({vertBary.ptrD(), lock.ptrD(),
|
||||
relation.barycentric.cptrD(), halfedge_.cptrD()}));
|
||||
|
||||
for_each_n(policy, zip(vertPos_.begin(), vertBary.begin()), NumVert(),
|
||||
for_each_n(autoPolicy(NumTri()), zip(vertPos_.begin(), vertBary.begin()),
|
||||
NumVert(),
|
||||
InterpTri({old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
|
||||
old.vertPos_.cptrD()}));
|
||||
}
|
||||
|
||||
@@ -119,6 +119,42 @@ struct Reindex {
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkProp {
|
||||
int* keep;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3 triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
keep[triProp[i]] = 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GatherProps {
|
||||
float* properties;
|
||||
const float* oldProperties;
|
||||
const int numProp;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
|
||||
const int oldIdx = thrust::get<0>(in);
|
||||
const int newIdx = thrust::get<1>(in);
|
||||
const int keep = thrust::get<2>(in);
|
||||
if (keep == 0) return;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
properties[newIdx * numProp + p] = oldProperties[oldIdx * numProp + p];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReindexProps {
|
||||
const int* old2new;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3& triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triProp[i] = old2new[triProp[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
VecDH<T> tmp(std::move(inOut));
|
||||
@@ -127,7 +163,7 @@ void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
tmp.begin(), inOut.begin());
|
||||
}
|
||||
|
||||
template void Permute<BaryRef>(VecDH<BaryRef>&, const VecDH<int>&);
|
||||
template void Permute<TriRef>(VecDH<TriRef>&, const VecDH<int>&);
|
||||
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
|
||||
|
||||
struct ReindexFace {
|
||||
@@ -172,7 +208,7 @@ void Manifold::Impl::Finish() {
|
||||
SetPrecision(precision_);
|
||||
if (!bBox_.IsFinite()) {
|
||||
// Decimated out of existence - early out.
|
||||
MarkFailure(Error::NO_ERROR);
|
||||
MarkFailure(Error::NoError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,6 +218,7 @@ void Manifold::Impl::Finish() {
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
CompactProps();
|
||||
|
||||
ASSERT(halfedge_.size() % 6 == 0, topologyErr,
|
||||
"Not an even number of faces after sorting faces!");
|
||||
@@ -202,8 +239,8 @@ void Manifold::Impl::Finish() {
|
||||
"Halfedge index is negative!");
|
||||
ASSERT(extrema.pairedHalfedge < 2 * NumEdge(), topologyErr,
|
||||
"Halfedge index exceeds number of halfedges!");
|
||||
ASSERT(meshRelation_.triBary.size() == NumTri() ||
|
||||
meshRelation_.triBary.size() == 0,
|
||||
ASSERT(meshRelation_.triRef.size() == NumTri() ||
|
||||
meshRelation_.triRef.size() == 0,
|
||||
logicErr, "Mesh Relation doesn't fit!");
|
||||
ASSERT(faceNormal_.size() == NumTri() || faceNormal_.size() == 0, logicErr,
|
||||
"faceNormal size = " + std::to_string(faceNormal_.size()) +
|
||||
@@ -239,8 +276,8 @@ void Manifold::Impl::SortVerts() {
|
||||
// Verts were flagged for removal with NaNs and assigned kNoCode to sort
|
||||
// them to the end, which allows them to be removed.
|
||||
const int newNumVert =
|
||||
find<decltype(vertMorton.begin())>(policy, vertMorton.begin(),
|
||||
vertMorton.end(), kNoCode) -
|
||||
lower_bound<decltype(vertMorton.begin())>(policy, vertMorton.begin(),
|
||||
vertMorton.end(), kNoCode) -
|
||||
vertMorton.begin();
|
||||
vertPos_.resize(newNumVert);
|
||||
if (vertNormal_.size() == numVert) {
|
||||
@@ -263,6 +300,32 @@ void Manifold::Impl::ReindexVerts(const VecDH<int>& vertNew2Old,
|
||||
Reindex({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unreferenced property verts and reindexes triProperties.
|
||||
*/
|
||||
void Manifold::Impl::CompactProps() {
|
||||
if (meshRelation_.numProp == 0) return;
|
||||
|
||||
const int numVerts = meshRelation_.properties.size() / meshRelation_.numProp;
|
||||
VecDH<int> keep(numVerts, 0);
|
||||
auto policy = autoPolicy(numVerts);
|
||||
|
||||
for_each(policy, meshRelation_.triProperties.cbegin(),
|
||||
meshRelation_.triProperties.cend(), MarkProp({keep.ptrD()}));
|
||||
VecDH<int> propOld2New(numVerts + 1, 0);
|
||||
inclusive_scan(policy, keep.begin(), keep.end(), propOld2New.begin() + 1);
|
||||
|
||||
VecDH<float> oldProp = meshRelation_.properties;
|
||||
const int numVertsNew = propOld2New[numVerts];
|
||||
meshRelation_.properties.resize(meshRelation_.numProp * numVertsNew);
|
||||
for_each_n(policy, zip(countAt(0), propOld2New.cbegin(), keep.cbegin()),
|
||||
numVerts,
|
||||
GatherProps({meshRelation_.properties.ptrD(), oldProp.cptrD(),
|
||||
meshRelation_.numProp}));
|
||||
for_each_n(policy, meshRelation_.triProperties.begin(), NumTri(),
|
||||
ReindexProps({propOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the faceBox and faceMorton input with the bounding boxes and Morton
|
||||
* codes of the faces, respectively. The Morton code is based on the center of
|
||||
@@ -310,9 +373,10 @@ void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
|
||||
*/
|
||||
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
if (meshRelation_.triBary.size() == NumTri())
|
||||
Permute(meshRelation_.triBary, faceNew2Old);
|
||||
|
||||
if (meshRelation_.triRef.size() == NumTri())
|
||||
Permute(meshRelation_.triRef, faceNew2Old);
|
||||
if (meshRelation_.triProperties.size() == NumTri())
|
||||
Permute(meshRelation_.triProperties, faceNew2Old);
|
||||
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
|
||||
|
||||
VecDH<Halfedge> oldHalfedge(std::move(halfedge_));
|
||||
@@ -333,11 +397,23 @@ void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
void Manifold::Impl::GatherFaces(const Impl& old,
|
||||
const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
meshRelation_.triBary.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
meshRelation_.triRef.resize(numTri);
|
||||
gather(policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triBary.begin(), meshRelation_.triBary.begin());
|
||||
meshRelation_.barycentric = old.meshRelation_.barycentric;
|
||||
old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin());
|
||||
|
||||
for (const auto& pair : old.meshRelation_.meshIDtransform) {
|
||||
meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
if (old.meshRelation_.triProperties.size() > 0) {
|
||||
meshRelation_.triProperties.resize(numTri);
|
||||
gather(policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triProperties.begin(),
|
||||
meshRelation_.triProperties.begin());
|
||||
meshRelation_.properties = old.meshRelation_.properties;
|
||||
}
|
||||
|
||||
if (old.faceNormal_.size() == old.NumTri()) {
|
||||
faceNormal_.resize(numTri);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (polygon)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
@@ -25,4 +25,4 @@ target_link_libraries( ${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
@@ -22,8 +22,25 @@ namespace manifold {
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
std::vector<glm::ivec3> Triangulate(const Polygons &polys,
|
||||
float precision = -1);
|
||||
|
||||
/**
|
||||
* Polygon vertex.
|
||||
*/
|
||||
struct PolyVert {
|
||||
/// X-Y position
|
||||
glm::vec2 pos;
|
||||
/// ID or index into another vertex vector
|
||||
int idx;
|
||||
};
|
||||
|
||||
using SimplePolygonIdx = std::vector<PolyVert>;
|
||||
using PolygonsIdx = std::vector<SimplePolygonIdx>;
|
||||
|
||||
std::vector<glm::ivec3> TriangulateIdx(const PolygonsIdx &polys,
|
||||
float precision = -1);
|
||||
|
||||
std::vector<glm::ivec3> Triangulate(
|
||||
std::vector<std::vector<glm::vec2>> &polygons, float precision = -1);
|
||||
|
||||
ExecutionParams &PolygonParams();
|
||||
/** @} */
|
||||
|
||||
@@ -29,17 +29,14 @@ using namespace manifold;
|
||||
ExecutionParams params;
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
struct Polyedge {
|
||||
struct PolyEdge {
|
||||
int startVert, endVert;
|
||||
};
|
||||
|
||||
bool OverlapAssert(bool condition, const char *file, int line,
|
||||
const std::string &cond, const std::string &msg) {
|
||||
if (!params.processOverlaps && !condition) {
|
||||
std::ostringstream output;
|
||||
output << "Error in file: " << file << " (" << line << "): \'" << cond
|
||||
<< "\' is false: " << msg;
|
||||
throw geometryErr(output.str());
|
||||
if (!params.processOverlaps) {
|
||||
ASSERT(condition, geometryErr, msg);
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
@@ -66,8 +63,8 @@ bool OverlapAssert(bool condition, const char *file, int line,
|
||||
#define PRINT(msg) \
|
||||
if (params.verbose) std::cout << msg << std::endl;
|
||||
|
||||
std::vector<Polyedge> Polygons2Edges(const Polygons &polys) {
|
||||
std::vector<Polyedge> halfedges;
|
||||
std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx &polys) {
|
||||
std::vector<PolyEdge> halfedges;
|
||||
for (const auto &poly : polys) {
|
||||
for (int i = 1; i < poly.size(); ++i) {
|
||||
halfedges.push_back({poly[i - 1].idx, poly[i].idx});
|
||||
@@ -77,9 +74,9 @@ std::vector<Polyedge> Polygons2Edges(const Polygons &polys) {
|
||||
return halfedges;
|
||||
}
|
||||
|
||||
std::vector<Polyedge> Triangles2Edges(
|
||||
std::vector<PolyEdge> Triangles2Edges(
|
||||
const std::vector<glm::ivec3> &triangles) {
|
||||
std::vector<Polyedge> halfedges;
|
||||
std::vector<PolyEdge> halfedges;
|
||||
for (const glm::ivec3 &tri : triangles) {
|
||||
halfedges.push_back({tri[0], tri[1]});
|
||||
halfedges.push_back({tri[1], tri[2]});
|
||||
@@ -88,26 +85,26 @@ std::vector<Polyedge> Triangles2Edges(
|
||||
return halfedges;
|
||||
}
|
||||
|
||||
void CheckTopology(const std::vector<Polyedge> &halfedges) {
|
||||
void CheckTopology(const std::vector<PolyEdge> &halfedges) {
|
||||
ASSERT(halfedges.size() % 2 == 0, topologyErr, "Odd number of halfedges.");
|
||||
size_t n_edges = halfedges.size() / 2;
|
||||
std::vector<Polyedge> forward(halfedges.size()), backward(halfedges.size());
|
||||
std::vector<PolyEdge> forward(halfedges.size()), backward(halfedges.size());
|
||||
|
||||
auto end = std::copy_if(halfedges.begin(), halfedges.end(), forward.begin(),
|
||||
[](Polyedge e) { return e.endVert > e.startVert; });
|
||||
[](PolyEdge e) { return e.endVert > e.startVert; });
|
||||
ASSERT(std::distance(forward.begin(), end) == n_edges, topologyErr,
|
||||
"Half of halfedges should be forward.");
|
||||
forward.resize(n_edges);
|
||||
|
||||
end = std::copy_if(halfedges.begin(), halfedges.end(), backward.begin(),
|
||||
[](Polyedge e) { return e.endVert < e.startVert; });
|
||||
[](PolyEdge e) { return e.endVert < e.startVert; });
|
||||
ASSERT(std::distance(backward.begin(), end) == n_edges, topologyErr,
|
||||
"Half of halfedges should be backward.");
|
||||
backward.resize(n_edges);
|
||||
|
||||
std::for_each(backward.begin(), backward.end(),
|
||||
[](Polyedge &e) { std::swap(e.startVert, e.endVert); });
|
||||
auto cmp = [](const Polyedge &a, const Polyedge &b) {
|
||||
[](PolyEdge &e) { std::swap(e.startVert, e.endVert); });
|
||||
auto cmp = [](const PolyEdge &a, const PolyEdge &b) {
|
||||
return a.startVert < b.startVert ||
|
||||
(a.startVert == b.startVert && a.endVert < b.endVert);
|
||||
};
|
||||
@@ -129,17 +126,17 @@ void CheckTopology(const std::vector<Polyedge> &halfedges) {
|
||||
}
|
||||
|
||||
void CheckTopology(const std::vector<glm::ivec3> &triangles,
|
||||
const Polygons &polys) {
|
||||
std::vector<Polyedge> halfedges = Triangles2Edges(triangles);
|
||||
std::vector<Polyedge> openEdges = Polygons2Edges(polys);
|
||||
for (Polyedge e : openEdges) {
|
||||
const PolygonsIdx &polys) {
|
||||
std::vector<PolyEdge> halfedges = Triangles2Edges(triangles);
|
||||
std::vector<PolyEdge> openEdges = Polygons2Edges(polys);
|
||||
for (PolyEdge e : openEdges) {
|
||||
halfedges.push_back({e.endVert, e.startVert});
|
||||
}
|
||||
CheckTopology(halfedges);
|
||||
}
|
||||
|
||||
void CheckGeometry(const std::vector<glm::ivec3> &triangles,
|
||||
const Polygons &polys, float precision) {
|
||||
const PolygonsIdx &polys, float precision) {
|
||||
std::map<int, glm::vec2> vertPos;
|
||||
for (const auto &poly : polys) {
|
||||
for (int i = 0; i < poly.size(); ++i) {
|
||||
@@ -154,7 +151,7 @@ void CheckGeometry(const std::vector<glm::ivec3> &triangles,
|
||||
geometryErr, "triangulation is not entirely CCW!");
|
||||
}
|
||||
|
||||
void Dump(const Polygons &polys) {
|
||||
void Dump(const PolygonsIdx &polys) {
|
||||
for (auto poly : polys) {
|
||||
std::cout << "polys.push_back({" << std::setprecision(9) << std::endl;
|
||||
for (auto v : poly) {
|
||||
@@ -172,7 +169,7 @@ void Dump(const Polygons &polys) {
|
||||
}
|
||||
}
|
||||
|
||||
void PrintFailure(const std::exception &e, const Polygons &polys,
|
||||
void PrintFailure(const std::exception &e, const PolygonsIdx &polys,
|
||||
std::vector<glm::ivec3> &triangles, float precision) {
|
||||
std::cout << "-----------------------------------" << std::endl;
|
||||
std::cout << "Triangulation failed! Precision = " << precision << std::endl;
|
||||
@@ -196,10 +193,10 @@ void PrintFailure(const std::exception &e, const Polygons &polys,
|
||||
*/
|
||||
class Monotones {
|
||||
public:
|
||||
Monotones(const Polygons &polys, float precision) : precision_(precision) {
|
||||
Monotones(const PolygonsIdx &polys, float precision) : precision_(precision) {
|
||||
VertItr start, last, current;
|
||||
float bound = 0;
|
||||
for (const SimplePolygon &poly : polys) {
|
||||
for (const SimplePolygonIdx &poly : polys) {
|
||||
for (int i = 0; i < poly.size(); ++i) {
|
||||
monotones_.push_back({poly[i].pos, //
|
||||
poly[i].idx, //
|
||||
@@ -275,7 +272,7 @@ class Monotones {
|
||||
void Check() {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (!params.intermediateChecks) return;
|
||||
std::vector<Polyedge> edges;
|
||||
std::vector<PolyEdge> edges;
|
||||
for (VertItr vert = monotones_.begin(); vert != monotones_.end(); vert++) {
|
||||
vert->SetProcessed(false);
|
||||
edges.push_back({vert->mesh_idx, vert->right->mesh_idx});
|
||||
@@ -309,7 +306,7 @@ class Monotones {
|
||||
typedef std::list<VertAdj>::iterator VertItr;
|
||||
struct EdgePair;
|
||||
typedef std::list<EdgePair>::iterator PairItr;
|
||||
enum VertType { START, WESTSIDE, EASTSIDE, MERGE, END, SKIP };
|
||||
enum VertType { Start, WestSide, EastSide, Merge, End, Skip };
|
||||
|
||||
std::list<VertAdj> monotones_; // sweep-line list of verts
|
||||
std::list<EdgePair> activePairs_; // west to east list of monotone edge pairs
|
||||
@@ -489,8 +486,8 @@ class Monotones {
|
||||
}
|
||||
|
||||
PairItr GetPair(VertItr vert, VertType type) const {
|
||||
// MERGE returns westPair, as this is the one that will be removed.
|
||||
return type == WESTSIDE ? vert->eastPair : vert->westPair;
|
||||
// Merge returns westPair, as this is the one that will be removed.
|
||||
return type == WestSide ? vert->eastPair : vert->westPair;
|
||||
}
|
||||
|
||||
bool Coincident(glm::vec2 p0, glm::vec2 p1) const {
|
||||
@@ -518,46 +515,46 @@ class Monotones {
|
||||
if (vert->left->Processed()) {
|
||||
if (westPair == eastPair) {
|
||||
// facing in
|
||||
PRINT("END");
|
||||
PRINT("End");
|
||||
CloseEnd(vert);
|
||||
return END;
|
||||
return End;
|
||||
} else if (westPair != activePairs_.end() &&
|
||||
std::next(westPair) == eastPair) {
|
||||
// facing out
|
||||
PRINT("MERGE");
|
||||
PRINT("Merge");
|
||||
CloseEnd(vert);
|
||||
// westPair will be removed and eastPair takes over.
|
||||
SetVWest(eastPair, westPair->vWest);
|
||||
return MERGE;
|
||||
return Merge;
|
||||
} else { // not neighbors
|
||||
PRINT("SKIP");
|
||||
return SKIP;
|
||||
PRINT("Skip");
|
||||
return Skip;
|
||||
}
|
||||
} else {
|
||||
if (!eastPair->vEast->right->IsPast(vert, precision_) &&
|
||||
vert->IsPast(eastPair->vEast, precision_) &&
|
||||
vert->pos.x > eastPair->vEast->right->pos.x + precision_) {
|
||||
PRINT("SKIP WEST");
|
||||
return SKIP;
|
||||
PRINT("Skip WEST");
|
||||
return Skip;
|
||||
}
|
||||
SetVWest(eastPair, vert);
|
||||
PRINT("WESTSIDE");
|
||||
return WESTSIDE;
|
||||
PRINT("WestSide");
|
||||
return WestSide;
|
||||
}
|
||||
} else {
|
||||
if (vert->left->Processed()) {
|
||||
if (!westPair->vWest->left->IsPast(vert, precision_) &&
|
||||
vert->IsPast(westPair->vWest, precision_) &&
|
||||
vert->pos.x < westPair->vWest->left->pos.x - precision_) {
|
||||
PRINT("SKIP EAST");
|
||||
return SKIP;
|
||||
PRINT("Skip EAST");
|
||||
return Skip;
|
||||
}
|
||||
SetVEast(westPair, vert);
|
||||
PRINT("EASTSIDE");
|
||||
return EASTSIDE;
|
||||
PRINT("EastSide");
|
||||
return EastSide;
|
||||
} else {
|
||||
PRINT("START");
|
||||
return START;
|
||||
PRINT("Start");
|
||||
return Start;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,12 +570,12 @@ class Monotones {
|
||||
}
|
||||
|
||||
/**
|
||||
* When vert is a START, this determines if it is backwards (forming a void or
|
||||
* When vert is a Start, this determines if it is backwards (forming a void or
|
||||
* hole). Usually the first return is adequate, but if it is degenerate, the
|
||||
* function will continue to search up the neighbors until the degeneracy is
|
||||
* broken and a certain answer is returned. Like CCW, this function returns 1
|
||||
* for a hole, -1 for a start, and 0 only if the entire polygon degenerates to
|
||||
* a polyline.
|
||||
* a line.
|
||||
*/
|
||||
int IsHole(VertItr vert) const {
|
||||
VertItr left = vert->left;
|
||||
@@ -822,7 +819,7 @@ class Monotones {
|
||||
|
||||
PairItr newPair = activePairs_.end();
|
||||
bool isHole = false;
|
||||
if (type == START) {
|
||||
if (type == Start) {
|
||||
newPair = activePairs_.insert(
|
||||
activePairs_.begin(), {vert, vert, monotones_.end(),
|
||||
activePairs_.end(), false, false, false});
|
||||
@@ -839,13 +836,13 @@ class Monotones {
|
||||
}
|
||||
|
||||
const PairItr pair = GetPair(vert, type);
|
||||
OVERLAP_ASSERT(type == SKIP || pair != activePairs_.end(),
|
||||
OVERLAP_ASSERT(type == Skip || pair != activePairs_.end(),
|
||||
"No active pair!");
|
||||
|
||||
if (type != SKIP && ShiftEast(vert, pair, isHole)) type = SKIP;
|
||||
if (type != SKIP && ShiftWest(vert, pair, isHole)) type = SKIP;
|
||||
if (type != Skip && ShiftEast(vert, pair, isHole)) type = Skip;
|
||||
if (type != Skip && ShiftWest(vert, pair, isHole)) type = Skip;
|
||||
|
||||
if (type == SKIP) {
|
||||
if (type == Skip) {
|
||||
OVERLAP_ASSERT(std::next(insertAt) != monotones_.end(),
|
||||
"Not Geometrically Valid! Tried to skip final vert.");
|
||||
OVERLAP_ASSERT(
|
||||
@@ -868,23 +865,23 @@ class Monotones {
|
||||
monotones_.splice(insertAt, monotones_, vert);
|
||||
|
||||
switch (type) {
|
||||
case WESTSIDE:
|
||||
case WestSide:
|
||||
nextAttached.push(vert->left);
|
||||
break;
|
||||
case EASTSIDE:
|
||||
case EastSide:
|
||||
nextAttached.push(vert->right);
|
||||
break;
|
||||
case START:
|
||||
case Start:
|
||||
nextAttached.push(vert->left);
|
||||
nextAttached.push(vert->right);
|
||||
break;
|
||||
case MERGE:
|
||||
case Merge:
|
||||
// Mark merge as hole for sweep-back.
|
||||
pair->vMerge = vert;
|
||||
case END:
|
||||
case End:
|
||||
RemovePair(pair);
|
||||
break;
|
||||
case SKIP:
|
||||
case Skip:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -947,29 +944,29 @@ class Monotones {
|
||||
if (vert->Processed()) continue;
|
||||
|
||||
VertType type = ProcessVert(vert);
|
||||
OVERLAP_ASSERT(type != SKIP, "SKIP should not happen on reverse sweep!");
|
||||
OVERLAP_ASSERT(type != Skip, "Skip should not happen on reverse sweep!");
|
||||
|
||||
PairItr westPair = GetPair(vert, type);
|
||||
OVERLAP_ASSERT(westPair != activePairs_.end(), "No active pair!");
|
||||
|
||||
switch (type) {
|
||||
case MERGE: {
|
||||
case Merge: {
|
||||
PairItr eastPair = std::next(westPair);
|
||||
if (eastPair->vMerge != monotones_.end())
|
||||
vert = SplitVerts(vert, eastPair->vMerge);
|
||||
eastPair->vMerge = vert;
|
||||
}
|
||||
case END:
|
||||
case End:
|
||||
RemovePair(westPair);
|
||||
case WESTSIDE:
|
||||
case EASTSIDE:
|
||||
case WestSide:
|
||||
case EastSide:
|
||||
if (westPair->vMerge != monotones_.end()) {
|
||||
VertItr eastVert = SplitVerts(vert, westPair->vMerge);
|
||||
if (type == WESTSIDE) westPair->vWest = eastVert;
|
||||
if (type == WestSide) westPair->vWest = eastVert;
|
||||
westPair->vMerge = monotones_.end();
|
||||
}
|
||||
break;
|
||||
case START: {
|
||||
case Start: {
|
||||
// Due to sweeping in the opposite direction, east and west are
|
||||
// swapped and what was the next pair is now the previous pair and
|
||||
// begin and end are swapped.
|
||||
@@ -999,7 +996,7 @@ class Monotones {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SKIP:
|
||||
case Skip:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1045,7 +1042,8 @@ namespace manifold {
|
||||
* @return std::vector<glm::ivec3> The triangles, referencing the original
|
||||
* vertex indicies.
|
||||
*/
|
||||
std::vector<glm::ivec3> Triangulate(const Polygons &polys, float precision) {
|
||||
std::vector<glm::ivec3> TriangulateIdx(const PolygonsIdx &polys,
|
||||
float precision) {
|
||||
std::vector<glm::ivec3> triangles;
|
||||
try {
|
||||
Monotones monotones(polys, precision);
|
||||
@@ -1072,6 +1070,29 @@ std::vector<glm::ivec3> Triangulate(const Polygons &polys, float precision) {
|
||||
return triangles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Triangulates a set of /epsilon-valid polygons.
|
||||
*
|
||||
* @param polygons The set of polygons, wound CCW and representing multiple
|
||||
* polygons and/or holes.
|
||||
* @param precision The value of epsilon, bounding the uncertainty of the input
|
||||
* @return std::vector<glm::ivec3> The triangles, referencing the original
|
||||
* polygon points in order.
|
||||
*/
|
||||
std::vector<glm::ivec3> Triangulate(
|
||||
std::vector<std::vector<glm::vec2>> &polygons, float precision) {
|
||||
int idx = 0;
|
||||
PolygonsIdx polygonsIndexed;
|
||||
for (auto &poly : polygons) {
|
||||
SimplePolygonIdx simpleIndexed;
|
||||
for (const glm::vec2 &polyVert : poly) {
|
||||
simpleIndexed.push_back({polyVert, idx++});
|
||||
}
|
||||
polygonsIndexed.push_back(simpleIndexed);
|
||||
}
|
||||
return TriangulateIdx(polygonsIndexed, precision);
|
||||
}
|
||||
|
||||
ExecutionParams &PolygonParams() { return params; }
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -21,4 +21,4 @@ target_include_directories(${PROJECT_NAME}
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE utilities)
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE utilities)
|
||||
|
||||
@@ -14,37 +14,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hashtable.h"
|
||||
#include "optional"
|
||||
#include "par.h"
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
typedef unsigned long long int Uint64;
|
||||
|
||||
constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
|
||||
__host__ __device__ Uint64 AtomicCAS(Uint64& target, Uint64 compare,
|
||||
Uint64 val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return atomicCAS(&target, compare, val);
|
||||
#else
|
||||
std::atomic<Uint64>& tar = reinterpret_cast<std::atomic<Uint64>&>(target);
|
||||
tar.compare_exchange_strong(compare, val);
|
||||
return compare;
|
||||
#endif
|
||||
}
|
||||
|
||||
__host__ __device__ int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
__host__ __device__ int Prev3(int i) {
|
||||
constexpr glm::ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
__host__ __device__ Uint64 identity(Uint64 x) { return x; }
|
||||
|
||||
__host__ __device__ glm::ivec3 TetTri0(int i) {
|
||||
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
|
||||
@@ -135,7 +114,6 @@ __host__ __device__ glm::ivec4 DecodeMorton(Uint64 code) {
|
||||
}
|
||||
|
||||
struct GridVert {
|
||||
Uint64 key = kOpen;
|
||||
float distance = NAN;
|
||||
int edgeVerts[7] = {-1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
@@ -146,73 +124,11 @@ struct GridVert {
|
||||
}
|
||||
};
|
||||
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(VecDH<GridVert>& alloc, VecDH<uint32_t>& used, uint32_t step = 127)
|
||||
: step_{step}, table_{alloc}, used_{used} {}
|
||||
|
||||
__host__ __device__ int Size() const { return table_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const { return used_[0] * 2 > Size(); }
|
||||
|
||||
__host__ __device__ void Insert(const GridVert& vert) {
|
||||
uint32_t idx = vert.key & (Size() - 1);
|
||||
while (1) {
|
||||
Uint64& key = table_[idx].key;
|
||||
const Uint64 found = AtomicCAS(key, kOpen, vert.key);
|
||||
if (found == kOpen) {
|
||||
AtomicAdd(used_[0], 0x1u);
|
||||
table_[idx] = vert;
|
||||
return;
|
||||
}
|
||||
if (found == vert.key) return;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ GridVert operator[](Uint64 key) const {
|
||||
uint32_t idx = key & (Size() - 1);
|
||||
while (1) {
|
||||
const GridVert found = table_[idx];
|
||||
if (found.key == key || found.key == kOpen) return found;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ GridVert At(int idx) const { return table_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecD<GridVert> table_;
|
||||
VecD<uint32_t> used_;
|
||||
};
|
||||
|
||||
class HashTable {
|
||||
public:
|
||||
HashTable(uint32_t size, uint32_t step = 127)
|
||||
: alloc_{1 << (int)ceil(log2(size)), {}}, table_{alloc_, used_, step} {}
|
||||
|
||||
HashTableD D() { return table_; }
|
||||
|
||||
int Entries() const { return used_[0]; }
|
||||
|
||||
int Size() const { return table_.Size(); }
|
||||
|
||||
bool Full() const { return used_[0] * 2 > Size(); }
|
||||
|
||||
float FilledFraction() const { return static_cast<float>(used_[0]) / Size(); }
|
||||
|
||||
private:
|
||||
VecDH<GridVert> alloc_;
|
||||
VecDH<uint32_t> used_ = VecDH<uint32_t>(1, 0);
|
||||
HashTableD table_;
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
struct ComputeVerts {
|
||||
glm::vec3* vertPos;
|
||||
int* vertIndex;
|
||||
HashTableD gridVerts;
|
||||
HashTableD<GridVert, identity> gridVerts;
|
||||
const Func sdf;
|
||||
const glm::vec3 origin;
|
||||
const glm::ivec3 gridSize;
|
||||
@@ -247,7 +163,6 @@ struct ComputeVerts {
|
||||
const glm::vec3 position = Position(gridIndex);
|
||||
|
||||
GridVert gridVert;
|
||||
gridVert.key = mortonCode;
|
||||
gridVert.distance = BoundedSDF(gridIndex);
|
||||
|
||||
bool keep = false;
|
||||
@@ -270,14 +185,14 @@ struct ComputeVerts {
|
||||
gridVert.edgeVerts[i] = idx;
|
||||
}
|
||||
|
||||
if (keep) gridVerts.Insert(gridVert);
|
||||
if (keep) gridVerts.Insert(mortonCode, gridVert);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildTris {
|
||||
glm::ivec3* triVerts;
|
||||
int* triIndex;
|
||||
const HashTableD gridVerts;
|
||||
const HashTableD<GridVert, identity> gridVerts;
|
||||
|
||||
__host__ __device__ void CreateTri(const glm::ivec3& tri,
|
||||
const int edges[6]) {
|
||||
@@ -295,10 +210,11 @@ struct BuildTris {
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(int idx) {
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
if (base.key == kOpen) return;
|
||||
Uint64 basekey = gridVerts.KeyAt(idx);
|
||||
if (basekey == kOpen) return;
|
||||
|
||||
const glm::ivec4 baseIndex = DecodeMorton(base.key);
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
const glm::ivec4 baseIndex = DecodeMorton(basekey);
|
||||
|
||||
glm::ivec4 leadIndex = baseIndex;
|
||||
if (leadIndex.w == 0)
|
||||
@@ -355,6 +271,21 @@ struct BuildTris {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
void for_each_n_wrapper(ExecutionPolicy policy, int morton,
|
||||
ComputeVerts<Func> cv) {
|
||||
for_each_n(policy, countAt(0), morton, cv);
|
||||
}
|
||||
template <>
|
||||
void for_each_n_wrapper<std::function<float(glm::vec3)>>(
|
||||
ExecutionPolicy policy, int morton,
|
||||
ComputeVerts<std::function<float(glm::vec3)>> cv) {
|
||||
const auto pol =
|
||||
policy == ExecutionPolicy::ParUnseq ? ExecutionPolicy::Par : policy;
|
||||
for_each_n(pol, countAt(0), morton, cv);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
@@ -388,7 +319,8 @@ namespace manifold {
|
||||
* input to the Manifold constructor for further operations.
|
||||
*/
|
||||
template <typename Func>
|
||||
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
|
||||
std::optional<ExecutionPolicy> policy = std::nullopt) {
|
||||
Mesh out;
|
||||
|
||||
const glm::vec3 dim = bounds.Size();
|
||||
@@ -397,17 +329,27 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
const glm::vec3 spacing = dim / (glm::vec3(gridSize));
|
||||
|
||||
const Uint64 maxMorton = MortonCode(glm::ivec4(gridSize + 1, 1));
|
||||
const auto policy = autoPolicy(maxMorton);
|
||||
|
||||
// Parallel policies violate will crash language runtimes with runtime locks
|
||||
// that expect to not be called back by unregistered threads. This allows
|
||||
// bindings use LevelSet despite being compiled with MANIFOLD_PAR
|
||||
// active (CUDA is already avoided when Func is a function ptr).
|
||||
const auto pol =
|
||||
(!policy.has_value() ||
|
||||
(policy.value() == ExecutionPolicy::ParUnseq && !CudaEnabled()))
|
||||
? autoPolicy(maxMorton)
|
||||
: policy.value();
|
||||
|
||||
int tableSize = glm::min(
|
||||
2 * maxMorton, static_cast<Uint64>(10 * glm::pow(maxMorton, 0.667)));
|
||||
HashTable gridVerts(tableSize);
|
||||
HashTable<GridVert, identity> gridVerts(tableSize);
|
||||
VecDH<glm::vec3> vertPos(gridVerts.Size() * 7);
|
||||
|
||||
while (1) {
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(
|
||||
policy, countAt(0), maxMorton + 1,
|
||||
// avoid handing dynamic function pointers to CUDA
|
||||
for_each_n_wrapper<Func>(
|
||||
pol, maxMorton + 1,
|
||||
ComputeVerts<Func>({vertPos.ptrD(), index.ptrD(), gridVerts.D(), sdf,
|
||||
bounds.min, gridSize + 1, spacing, level}));
|
||||
|
||||
@@ -420,7 +362,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
tableSize *= 2;
|
||||
else
|
||||
tableSize *= ratio;
|
||||
gridVerts = HashTable(tableSize);
|
||||
gridVerts = HashTable<GridVert, identity>(tableSize);
|
||||
vertPos = VecDH<glm::vec3>(gridVerts.Size() * 7);
|
||||
} else { // Success
|
||||
vertPos.resize(index[0]);
|
||||
@@ -431,7 +373,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
VecDH<glm::ivec3> triVerts(gridVerts.Entries() * 12); // worst case
|
||||
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(policy, countAt(0), gridVerts.Size(),
|
||||
for_each_n(pol, countAt(0), gridVerts.Size(),
|
||||
BuildTris({triVerts.ptrD(), index.ptrD(), gridVerts.D()}));
|
||||
triVerts.resize(index[0]);
|
||||
|
||||
@@ -440,4 +382,4 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
return out;
|
||||
}
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
@@ -1 +1,24 @@
|
||||
# Copyright 2023 The Manifold Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
add_subdirectory(glm)
|
||||
add_subdirectory(graphlite)
|
||||
|
||||
set(CLIPPER2_UTILS OFF)
|
||||
set(CLIPPER2_EXAMPLES OFF)
|
||||
set(CLIPPER2_TESTS OFF)
|
||||
set(CLIPPER2_USINGZ "OFF" CACHE STRING "Preempt cache default of USINGZ (we only use 2d)")
|
||||
add_definitions(-D_HAS_EXCEPTIONS=0) # disable exceptions for STL
|
||||
|
||||
add_subdirectory(clipper2/CPP)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
project (graphlite)
|
||||
|
||||
add_library(${PROJECT_NAME} src/connected_components.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT src/connected_components.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
project(utilities)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
message("CUDA Support: ${MANIFOLD_USE_CUDA}")
|
||||
message("Parallel Backend: ${MANIFOLD_PAR}")
|
||||
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE ${THRUST_INC_DIR}/dependencies/libcudacxx/include)
|
||||
|
||||
if(MANIFOLD_PAR STREQUAL "OMP")
|
||||
find_package(OpenMP REQUIRED)
|
||||
@@ -40,7 +41,8 @@ else()
|
||||
"Should be one of \"TBB\", \"OMP\", \"NONE\"")
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${THRUST_INC_DIR} ${GLM_INC_DIR})
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${THRUST_INC_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace {
|
||||
typedef unsigned long long int Uint64;
|
||||
typedef Uint64 (*hash_fun_t)(Uint64);
|
||||
constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T AtomicCAS(T& target, T compare, T val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return atomicCAS(&target, compare, val);
|
||||
#else
|
||||
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
|
||||
tar.compare_exchange_strong(compare, val, std::memory_order_acq_rel);
|
||||
return compare;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ void AtomicStore(T& target, T val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
target = val;
|
||||
#else
|
||||
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
|
||||
// release is good enough, although not really something general
|
||||
tar.store(val, std::memory_order_release);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T AtomicLoad(const T& target) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return target;
|
||||
#else
|
||||
const std::atomic<T>& tar = reinterpret_cast<const std::atomic<T>&>(target);
|
||||
// acquire is good enough, although not general
|
||||
return tar.load(std::memory_order_acquire);
|
||||
#endif
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
|
||||
__host__ __device__ inline Uint64 hash64bit(Uint64 x) {
|
||||
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull;
|
||||
x = (x ^ (x >> 27)) * 0x94d049bb133111ebull;
|
||||
x = x ^ (x >> 31);
|
||||
return x;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(VecDH<Uint64>& keys, VecDH<V>& values, VecDH<uint32_t>& used,
|
||||
uint32_t step = 1)
|
||||
: step_{step}, keys_{keys}, values_{values}, used_{used} {}
|
||||
|
||||
__host__ __device__ int Size() const { return keys_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const {
|
||||
return AtomicLoad(used_[0]) * 2 > Size();
|
||||
}
|
||||
|
||||
__host__ __device__ void Insert(Uint64 key, const V& val) {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
if (Full()) return;
|
||||
Uint64& k = keys_[idx];
|
||||
const Uint64 found = AtomicCAS(k, kOpen, key);
|
||||
if (found == kOpen) {
|
||||
AtomicAdd(used_[0], 0x1u);
|
||||
values_[idx] = val;
|
||||
return;
|
||||
}
|
||||
if (found == key) return;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ V& operator[](Uint64 key) const {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
const Uint64 k = AtomicLoad(keys_[idx]);
|
||||
if (k == key || k == kOpen) {
|
||||
return values_[idx];
|
||||
}
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ Uint64 KeyAt(int idx) const {
|
||||
return AtomicLoad(keys_[idx]);
|
||||
}
|
||||
__host__ __device__ V& At(int idx) const { return values_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecD<Uint64> keys_;
|
||||
VecD<V> values_;
|
||||
VecD<uint32_t> used_;
|
||||
};
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTable {
|
||||
public:
|
||||
HashTable(uint32_t size, uint32_t step = 1)
|
||||
: keys_{1 << (int)ceil(log2(size)), kOpen},
|
||||
values_{1 << (int)ceil(log2(size)), {}},
|
||||
table_{keys_, values_, used_, step} {}
|
||||
|
||||
HashTableD<V, H> D() { return table_; }
|
||||
|
||||
int Entries() const { return AtomicLoad(used_[0]); }
|
||||
|
||||
int Size() const { return table_.Size(); }
|
||||
|
||||
bool Full() const { return AtomicLoad(used_[0]) * 2 > Size(); }
|
||||
|
||||
float FilledFraction() const {
|
||||
return static_cast<float>(AtomicLoad(used_[0])) / Size();
|
||||
}
|
||||
|
||||
VecDH<V>& GetValueStore() { return values_; }
|
||||
|
||||
static Uint64 Open() { return kOpen; }
|
||||
|
||||
private:
|
||||
VecDH<Uint64> keys_;
|
||||
VecDH<V> values_;
|
||||
VecDH<uint32_t> used_ = VecDH<uint32_t>(1, 0);
|
||||
HashTableD<V, H> table_;
|
||||
};
|
||||
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -49,10 +49,6 @@ enum class ExecutionPolicy {
|
||||
Seq,
|
||||
};
|
||||
|
||||
constexpr ExecutionPolicy ParUnseq = ExecutionPolicy::ParUnseq;
|
||||
constexpr ExecutionPolicy Par = ExecutionPolicy::Par;
|
||||
constexpr ExecutionPolicy Seq = ExecutionPolicy::Seq;
|
||||
|
||||
// ExecutionPolicy:
|
||||
// - Sequential for small workload,
|
||||
// - Parallel (CPU) for medium workload,
|
||||
@@ -60,12 +56,12 @@ constexpr ExecutionPolicy Seq = ExecutionPolicy::Seq;
|
||||
inline ExecutionPolicy autoPolicy(int size) {
|
||||
// some random numbers
|
||||
if (size <= (1 << 12)) {
|
||||
return Seq;
|
||||
return ExecutionPolicy::Seq;
|
||||
}
|
||||
if (size <= (1 << 16) || !CudaEnabled()) {
|
||||
return Par;
|
||||
return ExecutionPolicy::Par;
|
||||
}
|
||||
return ParUnseq;
|
||||
return ExecutionPolicy::ParUnseq;
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_USE_CUDA
|
||||
@@ -126,6 +122,23 @@ inline ExecutionPolicy autoPolicy(int size) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#define THRUST_DYNAMIC_BACKEND_HOST_VOID(NAME) \
|
||||
template <typename... Args> \
|
||||
void NAME##_host(ExecutionPolicy policy, Args... args) { \
|
||||
switch (policy) { \
|
||||
case ExecutionPolicy::ParUnseq: \
|
||||
case ExecutionPolicy::Par: \
|
||||
thrust::NAME(thrust::MANIFOLD_PAR_NS::par, args...); \
|
||||
break; \
|
||||
case ExecutionPolicy::Seq: \
|
||||
break; \
|
||||
} \
|
||||
thrust::NAME(thrust::cpp::par, args...); \
|
||||
}
|
||||
|
||||
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each)
|
||||
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each_n)
|
||||
|
||||
THRUST_DYNAMIC_BACKEND_VOID(gather)
|
||||
THRUST_DYNAMIC_BACKEND_VOID(scatter)
|
||||
THRUST_DYNAMIC_BACKEND_VOID(for_each)
|
||||
|
||||
@@ -89,11 +89,6 @@ inline HOST_DEVICE glm::mat4x3 RotateUp(glm::vec3 up) {
|
||||
return glm::mat4x3(glm::rotate(glm::mat4(1), angle, axis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 for positive values, -1 for negative, and 0 for exactly zero.
|
||||
*/
|
||||
inline HOST_DEVICE int Signum(float val) { return (val > 0) - (val < 0); }
|
||||
|
||||
/**
|
||||
* Determines if the three points are wound counter-clockwise, clockwise, or
|
||||
* colinear within the specified tolerance.
|
||||
@@ -117,27 +112,12 @@ inline HOST_DEVICE int CCW(glm::vec2 p0, glm::vec2 p1, glm::vec2 p2,
|
||||
return area > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
/** @defgroup Polygon
|
||||
* @brief Polygon data structures
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Polygon vertex.
|
||||
*/
|
||||
struct PolyVert {
|
||||
/// X-Y position
|
||||
glm::vec2 pos;
|
||||
/// ID or index into another vertex vector
|
||||
int idx;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single polygon contour, wound CCW. First and last point are implicitly
|
||||
* connected. Should ensure all input is
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using SimplePolygon = std::vector<PolyVert>;
|
||||
using SimplePolygon = std::vector<glm::vec2>;
|
||||
|
||||
/**
|
||||
* Set of polygons with holes. Order of contours is arbitrary. Can contain any
|
||||
@@ -146,23 +126,6 @@ using SimplePolygon = std::vector<PolyVert>;
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using Polygons = std::vector<SimplePolygon>;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of vertices
|
||||
int NumVert() const { return this->vertPos.size() / 3; };
|
||||
/// Number of triangles
|
||||
int NumTri() const { return this->triVerts.size() / 3; };
|
||||
|
||||
std::vector<float> vertPos;
|
||||
std::vector<float> vertNormal;
|
||||
std::vector<uint32_t> triVerts;
|
||||
std::vector<float> halfedgeTangent;
|
||||
};
|
||||
|
||||
/**
|
||||
* The triangle-mesh input and output of this library.
|
||||
@@ -182,45 +145,81 @@ struct Mesh {
|
||||
/// as 3 * tri + i, representing the tangent from Mesh.triVerts[tri][i] along
|
||||
/// the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<glm::vec4> halfedgeTangent;
|
||||
};
|
||||
|
||||
Mesh() = default;
|
||||
Mesh(const std::vector<glm::vec3>& vertPos_,
|
||||
const std::vector<glm::ivec3>& triVerts_,
|
||||
const std::vector<glm::vec3>& vertNormal_ = {},
|
||||
const std::vector<glm::vec4>& halfedgeTangent_ = {})
|
||||
: vertPos(vertPos_),
|
||||
triVerts(triVerts_),
|
||||
vertNormal(vertNormal_),
|
||||
halfedgeTangent(halfedgeTangent_) {}
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly. This may not be manifold since the verts are duplicated
|
||||
* along property boundaries that do not match. The additional merge vectors
|
||||
* store this missing information, allowing the manifold to be reconstructed.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of property vertices
|
||||
uint32_t NumVert() const { return vertProperties.size() / numProp; };
|
||||
/// Number of triangles
|
||||
uint32_t NumTri() const { return triVerts.size() / 3; };
|
||||
|
||||
Mesh(const MeshGL& in) {
|
||||
const int numTri = in.NumTri();
|
||||
const int numVert = in.NumVert();
|
||||
triVerts.resize(numTri);
|
||||
vertPos.resize(numVert);
|
||||
if (!in.vertNormal.empty()) {
|
||||
vertNormal.resize(numVert);
|
||||
/// Number of properties per vertex, always >= 3.
|
||||
uint32_t numProp = 3;
|
||||
/// Flat, GL-style interleaved list of all vertex properties: propVal =
|
||||
/// vertProperties[vert * numProp + propIdx]. The first three properties are
|
||||
/// always the position x, y, z.
|
||||
std::vector<float> vertProperties;
|
||||
/// The vertex indices of the three triangle corners in CCW (from the outside)
|
||||
/// order, for each triangle.
|
||||
std::vector<uint32_t> triVerts;
|
||||
/// Optional: A list of only the vertex indicies that need to be merged to
|
||||
/// reconstruct the manifold.
|
||||
std::vector<uint32_t> mergeFromVert;
|
||||
/// Optional: The same length as mergeFromVert, and the corresponding value
|
||||
/// contains the vertex to merge with. It will have an identical position, but
|
||||
/// the other properties may differ.
|
||||
std::vector<uint32_t> mergeToVert;
|
||||
/// Optional: Indicates runs of triangles that correspond to a particular
|
||||
/// input mesh instance. The runs encompass all of triVerts and are sorted
|
||||
/// by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at
|
||||
/// triVerts[runIndex[i+1]]. All runIndex values are divisible by 3.
|
||||
std::vector<uint32_t> runIndex;
|
||||
/// Optional: The OriginalID of the mesh this triangle run came from. This ID
|
||||
/// is ideal for reapplying materials to the output mesh. Multiple runs may
|
||||
/// have the same ID, e.g. representing different copies of the same input
|
||||
/// mesh. If you create an input MeshGL that you want to be able to reference
|
||||
/// as one or more originals, be sure to set unique values from ReserveIDs().
|
||||
std::vector<uint32_t> runOriginalID;
|
||||
/// Optional: For each run, a 3x4 transform is stored representing how the
|
||||
/// corresponding original mesh was transformed to create this triangle run.
|
||||
/// This matrix is stored in column-major order and the length of the overall
|
||||
/// vector is 12 * runOriginalID.size().
|
||||
std::vector<float> runTransform;
|
||||
/// Optional: Length NumTri, contains an ID of the source face this triangle
|
||||
/// comes from. When auto-generated, this ID will be a triangle index into the
|
||||
/// original mesh. All neighboring coplanar triangles from that input mesh
|
||||
/// will refer to a single triangle of that group as the faceID. When
|
||||
/// supplying faceIDs, ensure that triangles with the same ID are in fact
|
||||
/// coplanar and have consistent properties (within some tolerance) or the
|
||||
/// output will be surprising.
|
||||
std::vector<uint32_t> faceID;
|
||||
/// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If
|
||||
/// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
|
||||
/// as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value
|
||||
/// Mesh.triVerts[tri][i] along the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<float> halfedgeTangent;
|
||||
|
||||
MeshGL() = default;
|
||||
MeshGL(const Mesh& mesh) {
|
||||
numProp = 3;
|
||||
vertProperties.resize(numProp * mesh.vertPos.size());
|
||||
for (int i = 0; i < mesh.vertPos.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) vertProperties[3 * i + j] = mesh.vertPos[i][j];
|
||||
}
|
||||
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
vertPos[i] = {in.vertPos[3 * i], in.vertPos[3 * i + 1],
|
||||
in.vertPos[3 * i + 2]};
|
||||
if (!in.vertNormal.empty()) {
|
||||
vertNormal[i] = {in.vertNormal[3 * i], in.vertNormal[3 * i + 1],
|
||||
in.vertNormal[3 * i + 2]};
|
||||
}
|
||||
triVerts.resize(3 * mesh.triVerts.size());
|
||||
for (int i = 0; i < mesh.triVerts.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) triVerts[3 * i + j] = mesh.triVerts[i][j];
|
||||
}
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
triVerts[i] = {in.triVerts[3 * i], in.triVerts[3 * i + 1],
|
||||
in.triVerts[3 * i + 2]};
|
||||
if (!in.halfedgeTangent.empty()) {
|
||||
for (const int j : {0, 1, 2})
|
||||
halfedgeTangent[3 * i + j] = {
|
||||
in.halfedgeTangent[4 * (3 * i + j)],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 1],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 2],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 3]};
|
||||
}
|
||||
halfedgeTangent.resize(4 * mesh.halfedgeTangent.size());
|
||||
for (int i = 0; i < mesh.halfedgeTangent.size(); ++i) {
|
||||
for (int j : {0, 1, 2, 3})
|
||||
halfedgeTangent[4 * i + j] = mesh.halfedgeTangent[i][j];
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -255,64 +254,6 @@ struct Curvature {
|
||||
std::vector<float> vertMeanCurvature, vertGaussianCurvature;
|
||||
};
|
||||
|
||||
/**
|
||||
* Part of MeshRelation - represents a single triangle relation to an original
|
||||
* Mesh.
|
||||
*/
|
||||
struct BaryRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// The triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri].
|
||||
int tri;
|
||||
/// For the three corners of the output triangle, new (intersection) vertices
|
||||
/// store an index to the MeshRelation.barycentric vector, while original
|
||||
/// vertices have negative values referring to Mesh.triVerts[tri][i + 3].
|
||||
glm::ivec3 vertBary;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @ingroup Connections
|
||||
* Represents the relationship of this output Mesh to all input Meshes that
|
||||
* eventually led to it, see Manifold.GetMeshRelation().
|
||||
*/
|
||||
struct MeshRelation {
|
||||
/// A vector of shared barycentric coordinates representing the position of a
|
||||
/// vertex relative to its original triangle.
|
||||
std::vector<glm::vec3> barycentric;
|
||||
/// A vector matching Mesh.triVerts that contains the relation of each output
|
||||
/// triangle to a single input triangle.
|
||||
std::vector<BaryRef> triBary;
|
||||
|
||||
/**
|
||||
* A convenience function to get the barycentric coordinates of a given
|
||||
* corner.
|
||||
*
|
||||
* @param tri A valid triangle index of Mesh.triVerts.
|
||||
* @param vert The corner of the triangle: 0, 1, or 2.
|
||||
*/
|
||||
inline glm::vec3 UVW(int tri, int vert) {
|
||||
glm::vec3 uvw(0.0f);
|
||||
const int idx = triBary[tri].vertBary[vert];
|
||||
if (idx < 0) {
|
||||
uvw[idx + 3] = 1;
|
||||
} else {
|
||||
uvw = barycentric[idx];
|
||||
}
|
||||
return uvw;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup Connections
|
||||
* Axis-aligned bounding box
|
||||
*/
|
||||
struct Box {
|
||||
glm::vec3 min = glm::vec3(std::numeric_limits<float>::infinity());
|
||||
glm::vec3 max = glm::vec3(-std::numeric_limits<float>::infinity());
|
||||
@@ -460,6 +401,92 @@ struct Box {
|
||||
return glm::all(glm::isfinite(min)) && glm::all(glm::isfinite(max));
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Core
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Boolean operation type: Add (Union), Subtract (Difference), and Intersect.
|
||||
*/
|
||||
enum class OpType { Add, Subtract, Intersect };
|
||||
|
||||
/**
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
class Quality {
|
||||
private:
|
||||
inline static int circularSegments_ = 0;
|
||||
inline static float circularAngle_ = 10.0f;
|
||||
inline static float circularEdgeLength_ = 1.0f;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets an angle constraint the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. The number of segments will be rounded up
|
||||
* to the nearest factor of four.
|
||||
*
|
||||
* @param angle The minimum angle in degrees between consecutive segments. The
|
||||
* angle will increase if the the segments hit the minimum edge length.
|
||||
* Default is 10 degrees.
|
||||
*/
|
||||
static void SetMinCircularAngle(float angle) {
|
||||
if (angle <= 0) return;
|
||||
circularAngle_ = angle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a length constraint the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. The number of segments will be rounded up
|
||||
* to the nearest factor of four.
|
||||
*
|
||||
* @param length The minimum length of segments. The length will
|
||||
* increase if the the segments hit the minimum angle. Default is 1.0.
|
||||
*/
|
||||
static void SetMinCircularEdgeLength(float length) {
|
||||
if (length <= 0) return;
|
||||
circularEdgeLength_ = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default number of circular segments for the
|
||||
* CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and
|
||||
* Manifold::Revolve() constructors. Overrides the edge length and angle
|
||||
* constraints and sets the number of segments to exactly this value.
|
||||
*
|
||||
* @param number Number of circular segments. Default is 0, meaning no
|
||||
* constraint is applied.
|
||||
*/
|
||||
static void SetCircularSegments(int number) {
|
||||
if (number < 3 && number != 0) return;
|
||||
circularSegments_ = number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the result of the SetMinCircularAngle(),
|
||||
* SetMinCircularEdgeLength(), and SetCircularSegments() defaults.
|
||||
*
|
||||
* @param radius For a given radius of circle, determine how many default
|
||||
* segments there will be.
|
||||
*/
|
||||
static int GetCircularSegments(float radius) {
|
||||
if (circularSegments_ > 0) return circularSegments_;
|
||||
int nSegA = 360.0f / circularAngle_;
|
||||
int nSegL = 2.0f * radius * glm::pi<float>() / circularEdgeLength_;
|
||||
int nSeg = fmin(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return nSeg;
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @defgroup Debug
|
||||
* @brief Debugging features
|
||||
@@ -503,7 +530,7 @@ struct ExecutionParams {
|
||||
bool verbose = false;
|
||||
/// If processOverlaps is false, a geometric check will be performed to assert
|
||||
/// all triangles are CCW.
|
||||
bool processOverlaps = false;
|
||||
bool processOverlaps = true;
|
||||
/// Suppresses printed errors regarding CW triangles. Has no effect if
|
||||
/// processOverlaps is true.
|
||||
bool suppressErrors = false;
|
||||
@@ -541,12 +568,6 @@ inline std::ostream& operator<<(std::ostream& stream, const glm::mat4x3& mat) {
|
||||
<< tam[2] << std::endl;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const BaryRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri
|
||||
<< ", uvw idx: " << ref.vertBary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of this vector to standard output. Only exists if compiled
|
||||
* with MANIFOLD_DEBUG flag.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <thrust/tuple.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#include <chrono>
|
||||
@@ -103,6 +104,16 @@ thrust::counting_iterator<T> countAt(T i) {
|
||||
return thrust::make_counting_iterator(i);
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Prev3(int i) {
|
||||
constexpr glm::ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T AtomicAdd(T& target, T add) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
@@ -179,5 +190,35 @@ class strided_range {
|
||||
Iterator last;
|
||||
difference_type stride;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class ConcurrentSharedPtr {
|
||||
public:
|
||||
ConcurrentSharedPtr(T value) : impl(std::make_shared<T>(value)) {}
|
||||
ConcurrentSharedPtr(const ConcurrentSharedPtr<T>& other)
|
||||
: impl(other.impl), mutex(other.mutex) {}
|
||||
class SharedPtrGuard {
|
||||
public:
|
||||
SharedPtrGuard(std::recursive_mutex* mutex, T* content)
|
||||
: mutex(mutex), content(content) {
|
||||
mutex->lock();
|
||||
}
|
||||
~SharedPtrGuard() { mutex->unlock(); }
|
||||
|
||||
T& operator*() { return *content; }
|
||||
T* operator->() { return content; }
|
||||
|
||||
private:
|
||||
std::recursive_mutex* mutex;
|
||||
T* content;
|
||||
};
|
||||
SharedPtrGuard GetGuard() { return SharedPtrGuard(mutex.get(), impl.get()); };
|
||||
unsigned int UseCount() { return impl.use_count(); };
|
||||
|
||||
private:
|
||||
std::shared_ptr<T> impl;
|
||||
std::shared_ptr<std::recursive_mutex> mutex =
|
||||
std::make_shared<std::recursive_mutex>();
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
|
||||