Compare commits
53
Commits
weld
...
assimpSubmodule
| 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 |
@@ -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
|
||||
|
||||
@@ -20,23 +20,22 @@ jobs:
|
||||
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_PYBIND=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
|
||||
@@ -53,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_PYBIND=ON -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
|
||||
@@ -73,19 +72,18 @@ jobs:
|
||||
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
|
||||
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: true
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build C bindings with OMP and CUDA
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
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
|
||||
@@ -98,7 +96,7 @@ jobs:
|
||||
|
||||
build_wasm:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
@@ -107,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
|
||||
@@ -119,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
|
||||
@@ -138,7 +135,9 @@ 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
|
||||
@@ -152,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
|
||||
@@ -175,7 +174,7 @@ jobs:
|
||||
- name: Build ${{matrix.backend}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=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}}
|
||||
@@ -192,6 +191,10 @@ jobs:
|
||||
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
|
||||
@@ -200,14 +203,13 @@ 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_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
@@ -222,5 +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
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Google LLC
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
|
||||
+21
-13
@@ -18,25 +18,26 @@ 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_PYBIND ON)
|
||||
option(MANIFOLD_CBIND 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)
|
||||
|
||||
@@ -82,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)
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ endif()
|
||||
|
||||
target_link_libraries(
|
||||
manifoldc
|
||||
PRIVATE manifold sdf meshIO graphlite
|
||||
PRIVATE manifold sdf meshIO graphlite cross_section
|
||||
)
|
||||
|
||||
target_include_directories(manifoldc PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
+9
-18
@@ -17,26 +17,20 @@ 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 box = *from_c(b);
|
||||
auto v = box.Size();
|
||||
auto v = from_c(b)->Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
auto v = box.Center();
|
||||
auto v = from_c(b)->Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
return box.Scale();
|
||||
}
|
||||
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 box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return box.Contains(p);
|
||||
return from_c(b)->Contains(p);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
@@ -52,7 +46,7 @@ void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
auto box = (*from_c(a)).Union(*from_c(b));
|
||||
auto box = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
@@ -61,13 +55,12 @@ ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
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);
|
||||
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 box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
@@ -75,20 +68,18 @@ ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto box = *from_c(b);
|
||||
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 box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return box.DoesOverlap(p);
|
||||
return from_c(b)->DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return (*from_c(a)).DoesOverlap(*from_c(b));
|
||||
return from_c(a)->DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return (*from_c(b)).IsFinite(); }
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
|
||||
|
||||
+125
-29
@@ -3,6 +3,9 @@
|
||||
#include <meshIO.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "include/types.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
@@ -11,6 +14,18 @@ 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);
|
||||
}
|
||||
@@ -31,45 +46,58 @@ ManifoldCurvature *to_c(manifold::Curvature *m) {
|
||||
return reinterpret_cast<ManifoldCurvature *>(m);
|
||||
}
|
||||
|
||||
ManifoldComponents *to_c(manifold::Components *components) {
|
||||
return reinterpret_cast<ManifoldComponents *>(components);
|
||||
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 = NO_ERROR;
|
||||
ManifoldError e = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NO_ERROR:
|
||||
e = NO_ERROR;
|
||||
case Manifold::Error::NoError:
|
||||
break;
|
||||
case Manifold::Error::NON_FINITE_VERTEX:
|
||||
e = NON_FINITE_VERTEX;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
break;
|
||||
case Manifold::Error::NOT_MANIFOLD:
|
||||
e = NOT_MANIFOLD;
|
||||
case Manifold::Error::NotManifold:
|
||||
e = MANIFOLD_NOT_MANIFOLD;
|
||||
break;
|
||||
case Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS:
|
||||
e = VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::PROPERTIES_WRONG_LENGTH:
|
||||
e = PROPERTIES_WRONG_LENGTH;
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::MISSING_POSITION_PROPERTIES:
|
||||
e = MISSING_POSITION_PROPERTIES;
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
|
||||
break;
|
||||
case Manifold::Error::MERGE_VECTORS_DIFFERENT_LENGTHS:
|
||||
e = MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
break;
|
||||
case Manifold::Error::MERGE_INDEX_OUT_OF_BOUNDS:
|
||||
e = MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::TRANSFORM_WRONG_LENGTH:
|
||||
e = TRANSFORM_WRONG_LENGTH;
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::RUN_INDEX_WRONG_LENGTH:
|
||||
e = RUN_INDEX_WRONG_LENGTH;
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::FACE_ID_WRONG_LENGTH:
|
||||
e = FACE_ID_WRONG_LENGTH;
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
e = MANIFOLD_INVALID_CONSTRUCTION;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
@@ -79,6 +107,10 @@ 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);
|
||||
}
|
||||
@@ -87,6 +119,8 @@ 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}; }
|
||||
@@ -99,6 +133,18 @@ 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);
|
||||
}
|
||||
@@ -115,18 +161,66 @@ const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
|
||||
return reinterpret_cast<manifold::MeshGL const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *m) {
|
||||
return reinterpret_cast<manifold::Curvature const *>(m);
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *c) {
|
||||
return reinterpret_cast<manifold::Curvature const *>(c);
|
||||
}
|
||||
|
||||
const manifold::Components *from_c(ManifoldComponents *components) {
|
||||
return reinterpret_cast<manifold::Components *>(components);
|
||||
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);
|
||||
}
|
||||
@@ -135,6 +229,8 @@ 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); }
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -5,31 +5,49 @@
|
||||
#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);
|
||||
ManifoldComponents *to_c(manifold::Components *components);
|
||||
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);
|
||||
const manifold::Components *from_c(ManifoldComponents *components);
|
||||
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);
|
||||
|
||||
+168
-17
@@ -11,6 +11,16 @@ 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,
|
||||
@@ -39,7 +49,25 @@ ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
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,
|
||||
@@ -67,6 +95,8 @@ 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);
|
||||
@@ -86,19 +116,14 @@ 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, ManifoldPolygons *polygons,
|
||||
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, ManifoldPolygons *polygons,
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifold **ms,
|
||||
size_t length);
|
||||
size_t manifold_decompose_length(ManifoldManifold *m);
|
||||
ManifoldComponents *manifold_get_components(void *mem, ManifoldManifold *m);
|
||||
size_t manifold_components_length(ManifoldComponents *components);
|
||||
ManifoldManifold **manifold_decompose(void **mem, ManifoldManifold *m,
|
||||
ManifoldComponents *cs);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Manifold Info
|
||||
@@ -119,6 +144,121 @@ 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);
|
||||
@@ -156,7 +296,8 @@ 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_original_id_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);
|
||||
@@ -164,7 +305,8 @@ 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_original_id(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);
|
||||
|
||||
@@ -184,36 +326,45 @@ 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_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_components_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_manifold(ManifoldManifold *m);
|
||||
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_components(ManifoldComponents *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_manifold(ManifoldManifold *m);
|
||||
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_components(ManifoldComponents *c);
|
||||
void manifold_delete_material(ManifoldMaterial *m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions *options);
|
||||
|
||||
|
||||
+35
-18
@@ -2,13 +2,16 @@
|
||||
#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 ManifoldComponents ManifoldComponents;
|
||||
typedef struct ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldRect ManifoldRect;
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
|
||||
@@ -46,12 +49,6 @@ typedef struct ManifoldProperties {
|
||||
float volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
typedef struct ManifoldPolyVert {
|
||||
ManifoldVec2 pos;
|
||||
int idx;
|
||||
|
||||
} ManifoldPolyVert;
|
||||
|
||||
typedef struct ManifoldCurvatureBounds {
|
||||
float max_mean_curvature;
|
||||
float min_mean_curvature;
|
||||
@@ -59,16 +56,36 @@ typedef struct ManifoldCurvatureBounds {
|
||||
float min_gaussian_curvature;
|
||||
} ManifoldCurvatureBounds;
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
MANIFOLD_ADD,
|
||||
MANIFOLD_SUBTRACT,
|
||||
MANIFOLD_INTERSECT
|
||||
} ManifoldOpType;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
NO_ERROR,
|
||||
NON_FINITE_VERTEX,
|
||||
NOT_MANIFOLD,
|
||||
VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
PROPERTIES_WRONG_LENGTH,
|
||||
MISSING_POSITION_PROPERTIES,
|
||||
MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
TRANSFORM_WRONG_LENGTH,
|
||||
RUN_INDEX_WRONG_LENGTH,
|
||||
FACE_ID_WRONG_LENGTH,
|
||||
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;
|
||||
|
||||
+141
-46
@@ -1,14 +1,19 @@
|
||||
#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;
|
||||
@@ -22,7 +27,7 @@ ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(Seq) : std::nullopt;
|
||||
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));
|
||||
}
|
||||
@@ -34,9 +39,9 @@ extern "C" {
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<PolyVert>;
|
||||
auto vec = new (mem) std::vector<glm::vec2>;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back({{ps[i].x, ps[i].y}, i});
|
||||
vec->push_back({ps[i].x, ps[i].y});
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
@@ -51,16 +56,91 @@ ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
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));
|
||||
@@ -123,6 +203,12 @@ ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
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) {
|
||||
@@ -222,51 +308,30 @@ ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldPolygons *polygons,
|
||||
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(polygons), height, slices, twist_degrees,
|
||||
scale);
|
||||
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldPolygons *polygons,
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Revolve(*from_c(polygons), circular_segments);
|
||||
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifold **ms,
|
||||
size_t length) {
|
||||
auto vec = std::vector<Manifold>();
|
||||
auto manifolds = reinterpret_cast<Manifold **>(ms);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(*manifolds[i]);
|
||||
}
|
||||
auto composed = Manifold::Compose(vec);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms) {
|
||||
auto composed = Manifold::Compose(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
ManifoldComponents *manifold_get_components(void *mem, ManifoldManifold *m) {
|
||||
return to_c(new (mem) Components(from_c(m)->GetComponents()));
|
||||
}
|
||||
|
||||
size_t manifold_components_length(ManifoldComponents *components) {
|
||||
return from_c(components)->numComponents;
|
||||
}
|
||||
|
||||
ManifoldManifold **manifold_decompose(void **mem, ManifoldManifold *m,
|
||||
ManifoldComponents *cs) {
|
||||
auto components = *from_c(cs);
|
||||
auto manifolds = from_c(m)->Decompose(components);
|
||||
ManifoldManifold **ms = reinterpret_cast<ManifoldManifold **>(mem);
|
||||
for (int i = 0; i < components.numComponents; ++i) {
|
||||
ms[i] = to_c(new (mem[i]) Manifold(manifolds[i]));
|
||||
}
|
||||
return ms;
|
||||
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) {
|
||||
@@ -298,8 +363,12 @@ size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_original_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->originalID.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) {
|
||||
@@ -330,8 +399,12 @@ uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_original_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->originalID);
|
||||
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) {
|
||||
@@ -397,57 +470,79 @@ float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv) {
|
||||
}
|
||||
|
||||
// Static Quality Globals
|
||||
|
||||
void manifold_set_min_circular_angle(float degrees) {
|
||||
Manifold::SetMinCircularAngle(degrees);
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
Manifold::SetMinCircularEdgeLength(length);
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Manifold::SetCircularSegments(number);
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
return Manifold::GetCircularSegments(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); }
|
||||
size_t manifold_components_size() { return sizeof(Components); }
|
||||
|
||||
// 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); }
|
||||
void manifold_delete_components(ManifoldComponents *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();
|
||||
}
|
||||
void manifold_destruct_components(ManifoldComponents *c) {
|
||||
from_c(c)->~Components();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+372
-146
@@ -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,6 +37,43 @@ 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) {
|
||||
@@ -71,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",
|
||||
@@ -84,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).")
|
||||
@@ -100,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)>(
|
||||
@@ -111,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(
|
||||
@@ -127,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",
|
||||
@@ -141,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",
|
||||
@@ -162,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).")
|
||||
@@ -191,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("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\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"
|
||||
"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\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"
|
||||
":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\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.")
|
||||
"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) {
|
||||
@@ -270,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(
|
||||
@@ -286,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(
|
||||
@@ -298,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"
|
||||
@@ -308,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) {
|
||||
@@ -338,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};
|
||||
}
|
||||
@@ -377,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) {
|
||||
@@ -401,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,
|
||||
@@ -509,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);
|
||||
}
|
||||
|
||||
Vendored
+1
-1
Submodule bindings/python/third_party/pybind11 updated: ffa346860b...12852cd33b
@@ -19,10 +19,10 @@ 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_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
@@ -34,4 +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/)
|
||||
|
||||
+48
-39
@@ -20,6 +20,7 @@
|
||||
using namespace emscripten;
|
||||
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
using namespace manifold;
|
||||
@@ -31,35 +32,32 @@ 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);
|
||||
@@ -79,9 +77,9 @@ val GetMeshJS(const Manifold& manifold) {
|
||||
meshJS.set("runIndex",
|
||||
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("originalID", val(typed_memory_view(mesh.originalID.size(),
|
||||
mesh.originalID.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"));
|
||||
@@ -89,6 +87,9 @@ val GetMeshJS(const Manifold& manifold) {
|
||||
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;
|
||||
}
|
||||
@@ -110,8 +111,9 @@ MeshGL MeshJS2GL(const val& mesh) {
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["originalID"] != val::undefined()) {
|
||||
out.originalID = convertJSArrayToNumberVector<uint32_t>(mesh["originalID"]);
|
||||
if (mesh["runOriginalID"] != val::undefined()) {
|
||||
out.runOriginalID =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
@@ -120,6 +122,10 @@ MeshGL MeshJS2GL(const val& mesh) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
if (mesh["runTransform"] != val::undefined()) {
|
||||
out.runTransform =
|
||||
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -141,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);
|
||||
}
|
||||
|
||||
@@ -183,22 +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("MISSING_POSITION_PROPERTIES",
|
||||
Manifold::Error::MISSING_POSITION_PROPERTIES)
|
||||
.value("MERGE_VECTORS_DIFFERENT_LENGTHS",
|
||||
Manifold::Error::MERGE_VECTORS_DIFFERENT_LENGTHS)
|
||||
.value("MERGE_INDEX_OUT_OF_BOUNDS",
|
||||
Manifold::Error::MERGE_INDEX_OUT_OF_BOUNDS)
|
||||
.value("TRANSFORM_WRONG_LENGTH", Manifold::Error::TRANSFORM_WRONG_LENGTH)
|
||||
.value("RUN_INDEX_WRONG_LENGTH", Manifold::Error::RUN_INDEX_WRONG_LENGTH)
|
||||
.value("FACE_ID_WRONG_LENGTH", Manifold::Error::FACE_ID_WRONG_LENGTH);
|
||||
.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);
|
||||
|
||||
@@ -232,13 +237,15 @@ 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("_Mirror", &Manifold::Mirror)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
@@ -260,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);
|
||||
|
||||
@@ -268,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);
|
||||
}
|
||||
|
||||
+93
-69
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
var _ManifoldInitialized = false;
|
||||
Module.setup = function () {
|
||||
Module.setup = function() {
|
||||
if (_ManifoldInitialized) return;
|
||||
_ManifoldInitialized = true;
|
||||
|
||||
@@ -38,11 +38,11 @@ Module.setup = function () {
|
||||
polygons = [polygons];
|
||||
}
|
||||
return toVec(
|
||||
new Module.Vector2_vec2(), polygons,
|
||||
poly => toVec(new Module.Vector_vec2(), poly, p => {
|
||||
if (p instanceof Array) return { x: p[0], y: p[1] };
|
||||
return p;
|
||||
}));
|
||||
new Module.Vector2_vec2(), polygons,
|
||||
poly => toVec(new Module.Vector_vec2(), poly, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1]};
|
||||
return p;
|
||||
}));
|
||||
}
|
||||
|
||||
function disposePolygons(polygonsVec) {
|
||||
@@ -52,15 +52,15 @@ Module.setup = function () {
|
||||
|
||||
function vararg2vec(vec) {
|
||||
if (vec[0] instanceof Array)
|
||||
return { x: vec[0][0], y: vec[0][1], z: vec[0][2] };
|
||||
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
// default to 0
|
||||
return { x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0 };
|
||||
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
Module.Manifold.prototype.warp = function (func) {
|
||||
const wasmFuncPtr = addFunction(function (vec3Ptr) {
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
const y = getValue(vec3Ptr + 4, 'float');
|
||||
const z = getValue(vec3Ptr + 8, 'float');
|
||||
@@ -75,42 +75,37 @@ 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) {
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function (vec) {
|
||||
Module.Manifold.prototype.rotate = function(vec) {
|
||||
return this._Rotate(...vec);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function (vec) {
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({ x: vec, y: vec, z: vec });
|
||||
return this._Scale({x: vec, y: vec, z: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function () {
|
||||
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);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getCurvature = function () {
|
||||
Module.Manifold.prototype.getCurvature = function() {
|
||||
const result = this._getCurvature();
|
||||
const oldMeanCurvature = result.vertMeanCurvature;
|
||||
const oldGaussianCurvature = result.vertGaussianCurvature;
|
||||
@@ -129,9 +124,10 @@ Module.setup = function () {
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
originalID,
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent
|
||||
halfedgeTangent,
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
@@ -139,9 +135,10 @@ Module.setup = function () {
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.originalID = originalID;
|
||||
this.runOriginalID = runOriginalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
@@ -152,6 +149,10 @@ Module.setup = function () {
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
get numRun() {
|
||||
return this.runOriginalID.length;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
|
||||
}
|
||||
@@ -161,21 +162,35 @@ Module.setup = function () {
|
||||
}
|
||||
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(numProp * vert + 3, numProp * (vert + 1));
|
||||
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.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 () {
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
const result = this._boundingBox();
|
||||
return {
|
||||
min: ['x', 'y', 'z'].map(f => result.min[f]),
|
||||
@@ -186,35 +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.MISSING_POSITION_PROPERTIES.value:
|
||||
case Module.status.MissingPositionProperties.value:
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.MERGE_VECTORS_DIFFERENT_LENGTHS.value:
|
||||
case Module.status.MergeVectorsDifferentLengths.value:
|
||||
message = 'Merge vectors have different lengths';
|
||||
break;
|
||||
case Module.status.MERGE_INDEX_OUT_OF_BOUNDS.value:
|
||||
case Module.status.MergeIndexOutOfBounds.value:
|
||||
message = 'Merge index out of bounds';
|
||||
break;
|
||||
case Module.status.TRANSFORM_WRONG_LENGTH.value:
|
||||
case Module.status.TransformWrongLength.value:
|
||||
message = 'Transform vector has wrong length';
|
||||
break;
|
||||
case Module.status.RUN_INDEX_WRONG_LENGTH.value:
|
||||
case Module.status.RunIndexWrongLength.value:
|
||||
message = 'Run index vector has wrong length';
|
||||
break;
|
||||
case Module.status.FACE_ID_WRONG_LENGTH.value:
|
||||
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]);
|
||||
@@ -226,11 +243,11 @@ Module.setup = function () {
|
||||
|
||||
Module.ManifoldError.prototype = Object.create(Error.prototype, {
|
||||
constructor:
|
||||
{ value: Module.ManifoldError, writable: true, configurable: true }
|
||||
{value: Module.ManifoldError, writable: true, configurable: true}
|
||||
});
|
||||
|
||||
const ManifoldCtor = Module.Manifold;
|
||||
Module.Manifold = function (mesh) {
|
||||
Module.Manifold = function(mesh) {
|
||||
const manifold = new ManifoldCtor(mesh);
|
||||
|
||||
const status = manifold.status();
|
||||
@@ -243,30 +260,30 @@ Module.setup = function () {
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
|
||||
Module.cube = function (...args) {
|
||||
Module.cube = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = { x: 1, y: 1, z: 1 };
|
||||
size = {x: 1, y: 1, z: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = { x: args[0], y: args[0], z: args[0] };
|
||||
size = {x: args[0], y: args[0], z: args[0]};
|
||||
else
|
||||
size = vararg2vec(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Cube(size, center);
|
||||
};
|
||||
|
||||
Module.cylinder = function (
|
||||
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
|
||||
center = false) {
|
||||
Module.cylinder = function(
|
||||
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
|
||||
center = false) {
|
||||
return Module._Cylinder(
|
||||
height, radiusLow, radiusHigh, circularSegments, center);
|
||||
height, radiusLow, radiusHigh, circularSegments, center);
|
||||
};
|
||||
|
||||
Module.sphere = function (radius, circularSegments = 0) {
|
||||
Module.sphere = function(radius, circularSegments = 0) {
|
||||
return Module._Sphere(radius, circularSegments);
|
||||
};
|
||||
|
||||
Module.smooth = function (mesh, sharpenedEdges = []) {
|
||||
Module.smooth = function(mesh, sharpenedEdges = []) {
|
||||
const sharp = new Module.Vector_smoothness();
|
||||
toVec(sharp, sharpenedEdges);
|
||||
const result = Module._Smooth(mesh, sharp);
|
||||
@@ -274,25 +291,32 @@ Module.setup = function () {
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.extrude = function (
|
||||
polygons, height, nDivisions = 0, twistDegrees = 0.0,
|
||||
scaleTop = [1.0, 1.0]) {
|
||||
if (scaleTop instanceof Array) scaleTop = { x: scaleTop[0], y: scaleTop[1] };
|
||||
Module.extrude = function(
|
||||
polygons, height, nDivisions = 0, twistDegrees = 0.0,
|
||||
scaleTop = [1.0, 1.0]) {
|
||||
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Extrude(
|
||||
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
|
||||
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.revolve = function (polygons, circularSegments = 0) {
|
||||
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);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.compose = function (manifolds) {
|
||||
Module.compose = function(manifolds) {
|
||||
const vec = new Module.Vector_manifold();
|
||||
toVec(vec, manifolds);
|
||||
const result = Module._Compose(vec);
|
||||
@@ -300,12 +324,12 @@ Module.setup = function () {
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.levelSet = function (sdf, bounds, edgeLength, level = 0) {
|
||||
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
|
||||
const bounds2 = {
|
||||
min: { x: bounds.min[0], y: bounds.min[1], z: bounds.min[2] },
|
||||
max: { x: bounds.max[0], y: bounds.max[1], z: bounds.max[2] },
|
||||
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
|
||||
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
|
||||
};
|
||||
const wasmFuncPtr = addFunction(function (vec3Ptr) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
const y = getValue(vec3Ptr + 4, 'float');
|
||||
const z = getValue(vec3Ptr + 8, 'float');
|
||||
@@ -318,7 +342,7 @@ Module.setup = function () {
|
||||
};
|
||||
|
||||
function batchbool(name) {
|
||||
return function (...args) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_manifold();
|
||||
for (const m of args) v.push_back(m);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
manifold*
|
||||
@@ -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);
|
||||
}
|
||||
@@ -211,4 +221,4 @@ model-viewer {
|
||||
.col {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+3220
File diff suppressed because it is too large
Load Diff
@@ -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) {
|
||||
|
||||
+145
-151
@@ -12,41 +12,45 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
(function (exports) {
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples.
|
||||
// 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. 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
|
||||
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 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));
|
||||
|
||||
@@ -59,15 +63,15 @@
|
||||
}
|
||||
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function () {
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
@@ -76,23 +80,19 @@
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
let edge1 = union(corner, edge);
|
||||
edge1 = edge1.rotate([-90, 0, 0]).translate([
|
||||
const edge1 = union(corner, edge).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]));
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
let edge4 = edge2.rotate([0, 0, 90]);
|
||||
edge4 = union(edge4, edge2);
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
let frame = edge4.translate([0, 0, -edgeLength / 2]);
|
||||
frame = union(frame, frame.rotate([180, 0, 0]));
|
||||
|
||||
return frame;
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
@@ -101,9 +101,9 @@
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function () {
|
||||
// Smooth, complex manifolds can be created using the warp() function. This
|
||||
// example recreates the Exploitable Heart by Emmett Lalish:
|
||||
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) => {
|
||||
@@ -122,7 +122,8 @@
|
||||
// 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;
|
||||
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
|
||||
@@ -145,9 +146,9 @@
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function () {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with smooth()
|
||||
// and refine(), see more details at:
|
||||
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;
|
||||
@@ -157,9 +158,9 @@
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
@@ -167,35 +168,35 @@
|
||||
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));
|
||||
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 = triVerts.length + 1;
|
||||
sharpenedEdges.push({ halfedge, smoothness });
|
||||
triVerts.push(0, 2 + i, 2 + j);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({ halfedge, smoothness });
|
||||
triVerts.push(1, 2 + j, 2 + i);
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const scallop = new Mesh();
|
||||
scallop.triVerts = Uint32Array.from(triVerts);
|
||||
scallop.vertProperties = Float32Array.from(vertPos);
|
||||
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;
|
||||
},
|
||||
|
||||
TorusKnot: function () {
|
||||
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.
|
||||
// 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.
|
||||
@@ -210,8 +211,10 @@
|
||||
// Default makes roughly square facets.
|
||||
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
@@ -220,9 +223,9 @@
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
@@ -231,28 +234,21 @@
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
}
|
||||
|
||||
const point = new THREE.Vector3();
|
||||
const axis = new THREE.Vector3();
|
||||
|
||||
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;
|
||||
};
|
||||
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);
|
||||
};
|
||||
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
|
||||
@@ -274,25 +270,26 @@
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function () {
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
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]));
|
||||
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]
|
||||
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, vec2add(position, offset), depth + 1, maxDepth);
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
@@ -309,24 +306,18 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
const result = mengerSponge(3).trimByPlane([1, 1, 1], 0).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function () {
|
||||
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) {
|
||||
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)
|
||||
];
|
||||
}
|
||||
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
@@ -336,31 +327,31 @@
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor =
|
||||
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
|
||||
0, radius, 0
|
||||
]);
|
||||
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];
|
||||
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(rotate(p0, dPhiRad * i));
|
||||
stretch.push(rotate(p1, dPhiRad * i));
|
||||
stretch.push(rotate(p2, dPhiRad * i));
|
||||
stretch.push(rotate(p0, dPhiRad * 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) {
|
||||
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;
|
||||
@@ -369,24 +360,26 @@
|
||||
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));
|
||||
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 () {
|
||||
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.
|
||||
// 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;
|
||||
@@ -397,14 +390,14 @@
|
||||
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);
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: [-period, -period, -period],
|
||||
max: [period, period, period]
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
@@ -412,29 +405,30 @@
|
||||
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]));
|
||||
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)]);
|
||||
result =
|
||||
result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
exports.functionBodies = new Map();
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
for (const [func, code] of Object.entries(exports.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 six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
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 six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
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);
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 704 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 952 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
Binary file not shown.
|
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 header" 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>
|
||||
</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,12 +36,10 @@
|
||||
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
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>
|
||||
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>
|
||||
@@ -50,11 +47,10 @@
|
||||
</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 src="manifold.js"></script>
|
||||
<script type="module">
|
||||
import { Accessor, Document, WebIO } from 'https://cdn.skypack.dev/@gltf-transform/core';
|
||||
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';
|
||||
|
||||
const io = new WebIO();
|
||||
const doc = new Document();
|
||||
@@ -92,26 +88,24 @@
|
||||
const texturesLoad = loadTextures();
|
||||
const materialMap = new Map();
|
||||
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const space = cubeUV().scale(50);
|
||||
const moon = sphereUV().scale(60);
|
||||
materialMap.set(space.originalID(), spaceIndices);
|
||||
materialMap.set(moon.originalID(), moonIndices);
|
||||
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));
|
||||
};
|
||||
const csg = function (operation) {
|
||||
push2MV(wasm[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
});
|
||||
csg('difference');
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
@@ -120,7 +114,7 @@
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
for (const [i, id] of mesh.originalID.entries()) {
|
||||
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]));
|
||||
}
|
||||
@@ -149,7 +143,7 @@
|
||||
mv.src = objectURL;
|
||||
}
|
||||
|
||||
function cubeUV() {
|
||||
function cubeUV(id) {
|
||||
const numProp = 5;
|
||||
const numTri = 12;
|
||||
const numVert = 8;
|
||||
@@ -176,7 +170,9 @@
|
||||
const vertProperties = new Float32Array(numProp * numPropVert);
|
||||
const mergeFromVert = new Uint32Array(numPropVert - numVert);
|
||||
const mergeToVert = new Uint32Array(numPropVert - numVert);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert });
|
||||
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) {
|
||||
@@ -203,7 +199,7 @@
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
|
||||
function sphereUV() {
|
||||
function sphereUV(id) {
|
||||
const n = 100;
|
||||
const tol = 1 / (2 * n);
|
||||
const mesh = wasm.sphere(1, n).getMesh();
|
||||
@@ -216,7 +212,9 @@
|
||||
const vertProperties = new Float32Array(numProp * (numVert + nMerge));
|
||||
const mergeFromVert = new Uint32Array(nMerge);
|
||||
const mergeToVert = new Uint32Array(nMerge);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert });
|
||||
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) {
|
||||
|
||||
@@ -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,50 +44,55 @@
|
||||
</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://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.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
|
||||
@@ -110,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,52 +120,47 @@ 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) {
|
||||
console.log = function(...args) {
|
||||
let message = '';
|
||||
for (const arg of args) {
|
||||
if (typeof arg == 'object') {
|
||||
@@ -87,69 +169,73 @@ console.log = function (...args) {
|
||||
message += arg.toString();
|
||||
}
|
||||
}
|
||||
postMessage({ log: message });
|
||||
postMessage({log: message});
|
||||
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];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
|
||||
}
|
||||
console.log(`Bounding Box: X = ${size[0].toLocaleString()
|
||||
} mm, Y = ${size[1].toLocaleString()
|
||||
} mm, Z = ${size[2].toLocaleString()} mm`);
|
||||
console.log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
|
||||
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
|
||||
const volume = Math.round(manifold.getProperties().volume / 10);
|
||||
console.log(`Genus: ${manifold.genus().toLocaleString()
|
||||
}, Volume: ${(volume / 100).toLocaleString()} cm^3`);
|
||||
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.vertProperties, 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)});
|
||||
}
|
||||
+443
@@ -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;
|
||||
}
|
||||
+65
@@ -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[]
|
||||
};
|
||||
Vendored
+26
-411
@@ -12,419 +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 Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
import * as T from './manifold-encapsulated-types';
|
||||
export * from './manifold-global-types';
|
||||
|
||||
declare class Mesh {
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
mergeFromVert?: Uint32Array;
|
||||
mergeToVert?: Uint32Array;
|
||||
runIndex?: Uint32Array;
|
||||
originalID?: Uint32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
verts(tri: number): Uint32Array<3>;
|
||||
position(vert: number): Float32Array<3>;
|
||||
extras(vert: number): Float32Array;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>;
|
||||
|
||||
Generated
+2
-2
@@ -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"
|
||||
}
|
||||
}
|
||||
+117
-109
@@ -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, 10, '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, 10, '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, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 10, '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_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_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,7 +25,6 @@
|
||||
"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 = [
|
||||
|
||||
@@ -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)
|
||||
@@ -190,7 +190,7 @@ void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
bool validChannels = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
int c = options.mat.normalChannels[i];
|
||||
validChannels &= c >= 3 && c < mesh.numProp;
|
||||
validChannels &= c >= 3 && c < (int)mesh.numProp;
|
||||
}
|
||||
ASSERT(validChannels, userErr,
|
||||
"When faceted is false, valid normalChannels must be supplied.");
|
||||
@@ -201,7 +201,7 @@ void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
bool hasColor = false;
|
||||
for (int i : {0, 1, 2, 3}) {
|
||||
int c = options.mat.colorChannels[i];
|
||||
validChannels &= c < mesh.numProp;
|
||||
validChannels &= c < (int)mesh.numProp;
|
||||
hasColor |= c >= 0;
|
||||
}
|
||||
ASSERT(validChannels, userErr, "Invalid colorChannels.");
|
||||
Vendored
+27
@@ -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)
|
||||
+1
Submodule meshIO/third_party/assimp added at 9519a62dd2
@@ -52,4 +52,4 @@ Manifold Scallop();
|
||||
|
||||
Manifold GyroidModule(float size = 20, int n = 20);
|
||||
/** @} */ // end of Samples
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 428 KiB |
Binary file not shown.
|
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,10 +40,10 @@ 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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -24,8 +24,8 @@ endif()
|
||||
|
||||
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}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
@@ -62,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);
|
||||
///@}
|
||||
|
||||
@@ -74,46 +75,29 @@ class Manifold {
|
||||
*/
|
||||
///@{
|
||||
static Manifold Compose(const std::vector<Manifold>&);
|
||||
|
||||
Components GetComponents() const;
|
||||
std::vector<Manifold> Decompose(Components components) const;
|
||||
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,
|
||||
MISSING_POSITION_PROPERTIES,
|
||||
MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
TRANSFORM_WRONG_LENGTH,
|
||||
RUN_INDEX_WRONG_LENGTH,
|
||||
FACE_ID_WRONG_LENGTH,
|
||||
NoError,
|
||||
NonFiniteVertex,
|
||||
NotManifold,
|
||||
VertexOutOfBounds,
|
||||
PropertiesWrongLength,
|
||||
MissingPositionProperties,
|
||||
MergeVectorsDifferentLengths,
|
||||
MergeIndexOutOfBounds,
|
||||
TransformWrongLength,
|
||||
RunIndexWrongLength,
|
||||
FaceIDWrongLength,
|
||||
InvalidConstruction,
|
||||
};
|
||||
Error Status() const;
|
||||
int NumVert() const;
|
||||
@@ -146,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);
|
||||
@@ -156,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,
|
||||
@@ -191,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_;
|
||||
|
||||
@@ -412,7 +412,7 @@ struct MapTriRef {
|
||||
};
|
||||
|
||||
VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
const Manifold::Impl &inQ, bool invertQ,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<TriRef> refPQ = outR.meshRelation_.triRef;
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
@@ -425,6 +425,8 @@ VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
}
|
||||
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;
|
||||
}
|
||||
@@ -537,31 +539,31 @@ void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
|
||||
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());
|
||||
@@ -692,7 +694,7 @@ 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_, policy_);
|
||||
VecDH<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ, policy_);
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <thrust/sequence.h>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "csg_tree.h"
|
||||
#include "graph.h"
|
||||
#include "impl.h"
|
||||
@@ -137,18 +138,23 @@ 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.AsOriginal();
|
||||
@@ -169,15 +175,15 @@ 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)
|
||||
@@ -197,9 +203,12 @@ 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}));
|
||||
@@ -224,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);
|
||||
|
||||
@@ -237,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);
|
||||
@@ -252,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;
|
||||
@@ -261,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(
|
||||
@@ -273,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);
|
||||
@@ -300,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;
|
||||
}
|
||||
@@ -331,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});
|
||||
@@ -341,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);
|
||||
@@ -355,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,
|
||||
@@ -405,7 +429,12 @@ Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold(std::make_shared<Impl>(CsgLeafNode::Compose(children)));
|
||||
}
|
||||
|
||||
Components Manifold::GetComponents() const {
|
||||
/**
|
||||
* 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().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
Graph graph;
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
for (int i = 0; i < NumVert(); ++i) {
|
||||
@@ -415,23 +444,18 @@ Components Manifold::GetComponents() const {
|
||||
if (halfedge.IsForward())
|
||||
graph.add_edge(halfedge.startVert, halfedge.endVert);
|
||||
}
|
||||
std::vector<int> components;
|
||||
const int numLabel = ConnectedComponents(components, graph);
|
||||
return {components, numLabel};
|
||||
}
|
||||
std::vector<int> componentIndices;
|
||||
const int numComponents = ConnectedComponents(componentIndices, graph);
|
||||
|
||||
std::vector<Manifold> Manifold::Decompose(Components components) const {
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
|
||||
if (components.numComponents == 1) {
|
||||
if (numComponents == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
VecDH<int> vertLabel(components.indices);
|
||||
VecDH<int> vertLabel(componentIndices);
|
||||
|
||||
std::vector<Manifold> meshes;
|
||||
for (int i = 0; i < components.numComponents; ++i) {
|
||||
for (int i = 0; i < numComponents; ++i) {
|
||||
auto impl = std::make_shared<Impl>();
|
||||
// inherit original object's precision
|
||||
impl->precision_ = pImpl_->precision_;
|
||||
@@ -466,13 +490,4 @@ std::vector<Manifold> Manifold::Decompose(Components components) const {
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
return Decompose(GetComponents());
|
||||
}
|
||||
} // namespace manifold
|
||||
|
||||
+218
-130
@@ -73,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;
|
||||
@@ -127,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.
|
||||
@@ -138,6 +151,9 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 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 =
|
||||
@@ -148,6 +164,9 @@ 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();
|
||||
@@ -162,51 +181,66 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
combined.meshRelation_.triRef.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
int nextVert = 0;
|
||||
int nextEdge = 0;
|
||||
int nextTri = 0;
|
||||
int i = 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);
|
||||
}
|
||||
copy(policy, node->pImpl_->halfedgeTangent_.begin(),
|
||||
node->pImpl_->halfedgeTangent_.end(),
|
||||
combined.halfedgeTangent_.begin() + nextEdge);
|
||||
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 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() + nextTri,
|
||||
UpdateMeshIDs({offset}));
|
||||
// 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;
|
||||
|
||||
nextVert += node->pImpl_->NumVert();
|
||||
nextEdge += 2 * node->pImpl_->NumEdge();
|
||||
nextTri += node->pImpl_->NumTri();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// required to remove parts that are smaller than the precision
|
||||
@@ -219,53 +253,96 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
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());
|
||||
@@ -273,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
|
||||
@@ -295,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) {
|
||||
@@ -319,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>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,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)
|
||||
@@ -368,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());
|
||||
@@ -395,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
|
||||
|
||||
+21
-16
@@ -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
|
||||
|
||||
@@ -119,9 +119,9 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
} else { // General triangulation
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
|
||||
|
||||
const Polygons polys = Face2Polygons(face, projection, faceEdge);
|
||||
const PolygonsIdx 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);
|
||||
@@ -138,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];
|
||||
|
||||
@@ -151,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) {
|
||||
|
||||
+43
-69
@@ -215,21 +215,11 @@ struct InitializeTriRef {
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkMeshID {
|
||||
HashTableD<uint32_t> table;
|
||||
|
||||
__host__ __device__ void operator()(TriRef& ref) {
|
||||
if (table.Full()) return;
|
||||
table.Insert(ref.meshID, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshID {
|
||||
const HashTableD<uint32_t> meshIDold2new;
|
||||
const int meshIDoffset;
|
||||
|
||||
__host__ __device__ void operator()(TriRef& ref) {
|
||||
ref.meshID = meshIDold2new[ref.meshID] + meshIDoffset;
|
||||
ref.meshID = meshIDold2new[ref.meshID];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -390,30 +380,30 @@ Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
const int numTri = meshGL.NumTri();
|
||||
|
||||
if (meshGL.numProp < 3) {
|
||||
MarkFailure(Error::MISSING_POSITION_PROPERTIES);
|
||||
MarkFailure(Error::MissingPositionProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.triVerts.resize(numTri);
|
||||
if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) {
|
||||
MarkFailure(Error::MERGE_VECTORS_DIFFERENT_LENGTHS);
|
||||
MarkFailure(Error::MergeVectorsDifferentLengths);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.transform.empty() &&
|
||||
12 * meshGL.originalID.size() != meshGL.transform.size()) {
|
||||
MarkFailure(Error::TRANSFORM_WRONG_LENGTH);
|
||||
if (!meshGL.runTransform.empty() &&
|
||||
12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) {
|
||||
MarkFailure(Error::TransformWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.originalID.empty() && !meshGL.runIndex.empty() &&
|
||||
meshGL.originalID.size() + 1 != meshGL.runIndex.size()) {
|
||||
MarkFailure(Error::RUN_INDEX_WRONG_LENGTH);
|
||||
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::FACE_ID_WRONG_LENGTH);
|
||||
MarkFailure(Error::FaceIDWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -423,7 +413,7 @@ Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
const int from = meshGL.mergeFromVert[i];
|
||||
const int to = meshGL.mergeToVert[i];
|
||||
if (from >= numVert || to >= numVert) {
|
||||
MarkFailure(Error::MERGE_INDEX_OUT_OF_BOUNDS);
|
||||
MarkFailure(Error::MergeIndexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
prop2vert[from] = to;
|
||||
@@ -432,7 +422,7 @@ Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
for (const int j : {0, 1, 2}) {
|
||||
const int vert = meshGL.triVerts[3 * i + j];
|
||||
if (vert < 0 || vert >= numVert) {
|
||||
MarkFailure(Error::VERTEX_INDEX_OUT_OF_BOUNDS);
|
||||
MarkFailure(Error::VertexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
mesh.triVerts[i][j] = prop2vert[vert];
|
||||
@@ -471,7 +461,7 @@ Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
mesh.halfedgeTangent[i][j] = meshGL.halfedgeTangent[4 * i + j];
|
||||
}
|
||||
|
||||
if (meshGL.originalID.empty()) {
|
||||
if (meshGL.runOriginalID.empty()) {
|
||||
relation.originalID = Impl::ReserveIDs(1);
|
||||
} else {
|
||||
std::vector<uint32_t> runIndex = meshGL.runIndex;
|
||||
@@ -479,10 +469,10 @@ Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
runIndex = {0, 3 * meshGL.NumTri()};
|
||||
}
|
||||
relation.triRef.resize(meshGL.NumTri());
|
||||
const int startID = Impl::ReserveIDs(meshGL.originalID.size());
|
||||
for (int i = 0; i < meshGL.originalID.size(); ++i) {
|
||||
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.originalID[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;
|
||||
@@ -490,13 +480,14 @@ Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
ref.tri = meshGL.faceID.empty() ? tri : meshGL.faceID[tri];
|
||||
}
|
||||
|
||||
if (meshGL.transform.empty()) {
|
||||
relation.meshIDtransform[meshID] = glm::mat4x3(1);
|
||||
if (meshGL.runTransform.empty()) {
|
||||
relation.meshIDtransform[meshID] = {originalID};
|
||||
} else {
|
||||
const float* m = meshGL.transform.data() + 12 * i;
|
||||
relation.meshIDtransform[meshID] = {m[0], m[1], m[2], m[3],
|
||||
m[4], m[5], m[6], m[7],
|
||||
m[8], m[9], m[10], m[11]};
|
||||
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]}};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -521,21 +512,21 @@ Manifold::Impl::Impl(const Mesh& mesh, const MeshRelationD& relation,
|
||||
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();
|
||||
@@ -557,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}, //
|
||||
@@ -580,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}, //
|
||||
@@ -630,7 +621,7 @@ void Manifold::Impl::InitializeOriginal() {
|
||||
zip(meshRelation_.triRef.begin(), countAt(0)), NumTri(),
|
||||
InitializeTriRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.meshIDtransform.clear();
|
||||
meshRelation_.meshIDtransform[meshID] = glm::mat4x3(1);
|
||||
meshRelation_.meshIDtransform[meshID] = {meshID};
|
||||
}
|
||||
|
||||
void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
@@ -735,8 +726,9 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
result.halfedge_ = halfedge_;
|
||||
result.halfedgeTangent_.resize(halfedgeTangent_.size());
|
||||
|
||||
result.meshRelation_.originalID = -1;
|
||||
for (auto& m : result.meshRelation_.meshIDtransform) {
|
||||
m.second = transform_ * glm::mat4(m.second);
|
||||
m.second.transform = transform_ * glm::mat4(m.second.transform);
|
||||
}
|
||||
|
||||
result.vertPos_.resize(NumVert());
|
||||
@@ -745,8 +737,7 @@ 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(),
|
||||
@@ -824,37 +815,20 @@ void Manifold::Impl::CalculateNormals() {
|
||||
* instances of these meshes.
|
||||
*/
|
||||
void Manifold::Impl::IncrementMeshIDs() {
|
||||
const int numTri = NumTri();
|
||||
const auto policy = autoPolicy(numTri);
|
||||
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
|
||||
|
||||
while (1) {
|
||||
for_each_n(policy, meshRelation_.triRef.begin(), numTri,
|
||||
MarkMeshID({meshIDold2new.D()}));
|
||||
if (!meshIDold2new.Full()) break;
|
||||
meshIDold2new = HashTable<uint32_t>(meshIDold2new.Size() * 2);
|
||||
}
|
||||
inclusive_scan(autoPolicy(meshIDold2new.Size()),
|
||||
meshIDold2new.GetValueStore().begin(),
|
||||
meshIDold2new.GetValueStore().end(),
|
||||
meshIDold2new.GetValueStore().begin());
|
||||
const int numMeshIDs = meshIDold2new.GetValueStore().back();
|
||||
const int meshIDstart = ReserveIDs(numMeshIDs);
|
||||
// We do start - 1 because the inclusive scan makes our first index 1
|
||||
// instead of 0.
|
||||
for_each_n(policy, meshRelation_.triRef.begin(), numTri,
|
||||
UpdateMeshID({meshIDold2new.D(), meshIDstart}));
|
||||
// Update keys of the transform map
|
||||
std::map<int, glm::mat4x3> oldTransforms;
|
||||
std::map<int, Relation> oldTransforms;
|
||||
std::swap(meshRelation_.meshIDtransform, oldTransforms);
|
||||
const int tableSize = meshIDold2new.Size();
|
||||
for (int i = 0; i < tableSize; ++i) {
|
||||
const auto oldID = meshIDold2new.D().KeyAt(i);
|
||||
if (oldID != HashTable<uint32_t>::Open()) {
|
||||
meshRelation_.meshIDtransform[meshIDold2new.D().At(i) + meshIDstart] =
|
||||
oldTransforms[oldID];
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
const int numTri = NumTri();
|
||||
for_each_n(autoPolicy(numTri), meshRelation_.triRef.begin(), numTri,
|
||||
UpdateMeshID({meshIDold2new.D()}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+11
-5
@@ -18,6 +18,7 @@
|
||||
#include "collider.h"
|
||||
#include "manifold.h"
|
||||
#include "optional_assert.h"
|
||||
#include "polygon.h"
|
||||
#include "shared.h"
|
||||
#include "sparse.h"
|
||||
#include "utils.h"
|
||||
@@ -27,6 +28,11 @@ namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
struct Manifold::Impl {
|
||||
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;
|
||||
@@ -34,12 +40,12 @@ struct Manifold::Impl {
|
||||
VecDH<TriRef> triRef;
|
||||
VecDH<glm::ivec3> triProperties;
|
||||
VecDH<float> properties;
|
||||
std::map<int, glm::mat4x3> meshIDtransform;
|
||||
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_;
|
||||
@@ -52,7 +58,7 @@ struct Manifold::Impl {
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
|
||||
Impl() {}
|
||||
enum class Shape { TETRAHEDRON, CUBE, OCTAHEDRON };
|
||||
enum class Shape { Tetrahedron, Cube, Octahedron };
|
||||
Impl(Shape);
|
||||
|
||||
Impl(const MeshGL&, std::vector<float> propertyTolerance = {});
|
||||
@@ -107,8 +113,8 @@ struct Manifold::Impl {
|
||||
|
||||
// face_op.cu
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<TriRef>& halfedgeRef);
|
||||
Polygons Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
PolygonsIdx Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
|
||||
// edge_op.cu
|
||||
void SimplifyTopology();
|
||||
|
||||
+120
-113
@@ -71,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_;
|
||||
@@ -79,7 +85,7 @@ 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_);
|
||||
@@ -152,14 +158,25 @@ Mesh Manifold::GetMesh() const {
|
||||
* 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() const {
|
||||
MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
const Impl& impl = *GetCsgLeafNode().GetImpl();
|
||||
|
||||
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.numProp = 3 + numProp;
|
||||
out.triVerts.resize(3 * numTri);
|
||||
@@ -180,34 +197,54 @@ MeshGL Manifold::GetMeshGL() const {
|
||||
std::iota(triNew2Old.begin(), triNew2Old.end(), 0);
|
||||
const TriRef* triRef = impl.meshRelation_.triRef.cptrD();
|
||||
// Don't sort originals - keep them in order
|
||||
if (impl.meshRelation_.originalID < 0) {
|
||||
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;
|
||||
if (meshID != lastID) {
|
||||
out.runIndex.push_back(3 * tri);
|
||||
out.originalID.push_back(ref.originalID);
|
||||
if (impl.meshRelation_.originalID < 0) {
|
||||
const glm::mat4x3& m = impl.meshRelation_.meshIDtransform.at(meshID);
|
||||
for (const int col : {0, 1, 2, 3}) {
|
||||
for (const int row : {0, 1, 2}) {
|
||||
out.transform.push_back(m[col][row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastID = 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);
|
||||
|
||||
@@ -226,101 +263,55 @@ MeshGL Manifold::GetMeshGL() const {
|
||||
// Duplicate verts with different props
|
||||
std::vector<int> vert2idx(impl.NumVert(), -1);
|
||||
std::map<std::pair<int, int>, int> vertPropPair;
|
||||
for (int tri = 0; tri < numTri; ++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];
|
||||
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;
|
||||
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]);
|
||||
}
|
||||
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]);
|
||||
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?
|
||||
*/
|
||||
@@ -328,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 {
|
||||
@@ -437,7 +428,7 @@ Manifold Manifold::AsOriginal() const {
|
||||
/**
|
||||
* 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.originalID vector.
|
||||
* MeshGL.runOriginalID vector.
|
||||
*/
|
||||
uint32_t Manifold::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::ReserveIDs(n);
|
||||
@@ -532,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
|
||||
@@ -583,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,
|
||||
@@ -603,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -618,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -655,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.
|
||||
|
||||
@@ -208,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;
|
||||
}
|
||||
|
||||
@@ -276,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) {
|
||||
|
||||
@@ -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();
|
||||
/** @} */
|
||||
|
||||
+88
-67
@@ -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
|
||||
|
||||
@@ -281,7 +281,8 @@ 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 == ParUnseq ? Par : policy;
|
||||
const auto pol =
|
||||
policy == ExecutionPolicy::ParUnseq ? ExecutionPolicy::Par : policy;
|
||||
for_each_n(pol, countAt(0), morton, cv);
|
||||
}
|
||||
|
||||
@@ -334,7 +335,8 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
|
||||
// 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() == ParUnseq && !CudaEnabled()))
|
||||
(!policy.has_value() ||
|
||||
(policy.value() == ExecutionPolicy::ParUnseq && !CudaEnabled()))
|
||||
? autoPolicy(maxMorton)
|
||||
: policy.value();
|
||||
|
||||
|
||||
Vendored
+23
@@ -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
Submodule src/third_party/clipper2 added at fb8fdd1805
Vendored
+1
-1
Submodule src/third_party/thrust updated: d997cd37a9...3cd56842c9
@@ -21,6 +21,7 @@ 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)
|
||||
|
||||
@@ -31,11 +31,33 @@ __host__ __device__ T AtomicCAS(T& target, T compare, T val) {
|
||||
return atomicCAS(&target, compare, val);
|
||||
#else
|
||||
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
|
||||
tar.compare_exchange_strong(compare, val);
|
||||
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;
|
||||
@@ -59,11 +81,14 @@ class HashTableD {
|
||||
|
||||
__host__ __device__ int Size() const { return keys_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const { return used_[0] * 2 > 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) {
|
||||
@@ -79,12 +104,17 @@ class HashTableD {
|
||||
__host__ __device__ V& operator[](Uint64 key) const {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
if (keys_[idx] == key || keys_[idx] == kOpen) return values_[idx];
|
||||
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 keys_[idx]; }
|
||||
__host__ __device__ Uint64 KeyAt(int idx) const {
|
||||
return AtomicLoad(keys_[idx]);
|
||||
}
|
||||
__host__ __device__ V& At(int idx) const { return values_[idx]; }
|
||||
|
||||
private:
|
||||
@@ -104,13 +134,15 @@ class HashTable {
|
||||
|
||||
HashTableD<V, H> D() { return table_; }
|
||||
|
||||
int Entries() const { return used_[0]; }
|
||||
int Entries() const { return AtomicLoad(used_[0]); }
|
||||
|
||||
int Size() const { return table_.Size(); }
|
||||
|
||||
bool Full() const { return used_[0] * 2 > Size(); }
|
||||
bool Full() const { return AtomicLoad(used_[0]) * 2 > Size(); }
|
||||
|
||||
float FilledFraction() const { return static_cast<float>(used_[0]) / Size(); }
|
||||
float FilledFraction() const {
|
||||
return static_cast<float>(AtomicLoad(used_[0])) / Size();
|
||||
}
|
||||
|
||||
VecDH<V>& GetValueStore() { return values_; }
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -112,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
|
||||
@@ -141,7 +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>;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* The triangle-mesh input and output of this library.
|
||||
@@ -193,7 +177,7 @@ struct MeshGL {
|
||||
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 originalID. Run i begins at triVerts[runIndex[i]] and ends at
|
||||
/// 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
|
||||
@@ -201,12 +185,12 @@ struct MeshGL {
|
||||
/// 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> originalID;
|
||||
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 * originalID.size().
|
||||
std::vector<float> transform;
|
||||
/// 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
|
||||
@@ -270,20 +254,6 @@ struct Curvature {
|
||||
std::vector<float> vertMeanCurvature, vertGaussianCurvature;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connected component indices and number of discrete components. Intermediate
|
||||
* step for Manifold.Decompose(), completed by Manifold.GetComponents()
|
||||
*/
|
||||
struct Components {
|
||||
std::vector<int> indices;
|
||||
int numComponents;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @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());
|
||||
@@ -431,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
|
||||
@@ -474,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;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <thrust/tuple.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#include <chrono>
|
||||
@@ -189,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
|
||||
|
||||
+3
-4
@@ -18,27 +18,26 @@ add_subdirectory(third_party)
|
||||
|
||||
enable_testing()
|
||||
|
||||
set(SOURCE_FILES polygon_test.cpp mesh_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
|
||||
set(SOURCE_FILES polygon_test.cpp cross_section_test.cpp manifold_test.cpp boolean_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
list(APPEND SOURCE_FILES manifoldc_test.cpp)
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold samples samplesGPU)
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold sdf samples samplesGPU cross_section)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
target_link_libraries(${PROJECT_NAME} manifoldc)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_subdirectory(meshIO)
|
||||
target_link_libraries(${PROJECT_NAME} meshIO)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set(CUDA_FILES mesh_test.cpp sdf_test.cpp)
|
||||
set(CUDA_FILES test_main.cpp sdf_test.cpp)
|
||||
if(MANIFOLD_CBIND)
|
||||
list(APPEND CUDA_FILES manifoldc_test.cpp)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,627 @@
|
||||
// Copyright 2021 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 "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "test.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
/**
|
||||
* The very simplest Boolean operation test.
|
||||
*/
|
||||
TEST(Boolean, Tetra) {
|
||||
Manifold tetra = Manifold::Tetrahedron();
|
||||
MeshGL tetraGL = WithPositionColors(tetra);
|
||||
tetra = Manifold(tetraGL);
|
||||
EXPECT_TRUE(!tetra.IsEmpty());
|
||||
|
||||
Manifold tetra2 = tetra.Translate(glm::vec3(0.5f));
|
||||
Manifold result = tetra2 - tetra;
|
||||
|
||||
ExpectMeshes(result, {{8, 12, 3, 11}});
|
||||
|
||||
RelatedGL(result, {tetraGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MeshGLRoundTrip) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2));
|
||||
ASSERT_GE(cube.OriginalID(), 0);
|
||||
const MeshGL original = cube.GetMeshGL();
|
||||
|
||||
Manifold result = cube + cube.Translate({1, 1, 0});
|
||||
|
||||
ASSERT_LT(result.OriginalID(), 0);
|
||||
ExpectMeshes(result, {{18, 32}});
|
||||
RelatedGL(result, {original});
|
||||
|
||||
MeshGL inGL = result.GetMeshGL();
|
||||
ASSERT_EQ(inGL.runOriginalID.size(), 2);
|
||||
const Manifold result2(inGL);
|
||||
|
||||
ASSERT_LT(result2.OriginalID(), 0);
|
||||
ExpectMeshes(result2, {{18, 32}});
|
||||
RelatedGL(result2, {original});
|
||||
|
||||
const MeshGL outGL = result2.GetMeshGL();
|
||||
ASSERT_EQ(outGL.runOriginalID.size(), 2);
|
||||
}
|
||||
|
||||
TEST(Boolean, Normals) {
|
||||
const MeshGL boxGL = WithNormals(Manifold::Cube({200, 200, 100}, true));
|
||||
const Manifold box(boxGL);
|
||||
const MeshGL sphereGL = WithNormals(Manifold::Sphere(60));
|
||||
const Manifold sphere(sphereGL);
|
||||
|
||||
Manifold cube = box ^ box.Rotate(90) ^ box.Rotate(0, 90);
|
||||
|
||||
Manifold result =
|
||||
cube - (sphere.Rotate(180) -
|
||||
sphere.Scale(glm::vec3(0.5)).Rotate(90).Translate({40, 40, 40}));
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.faceted = false;
|
||||
opt.mat.roughness = 0;
|
||||
opt.mat.normalChannels = {3, 4, 5};
|
||||
if (options.exportModels)
|
||||
ExportMesh("normals.glb", result.GetMeshGL({3, 4, 5}), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(result, {boxGL, sphereGL}, true);
|
||||
}
|
||||
|
||||
TEST(Boolean, EmptyOriginal) {
|
||||
const Manifold cube = Manifold::Cube();
|
||||
const Manifold tet = Manifold::Tetrahedron();
|
||||
const Manifold result = tet - cube.Translate({3, 4, 5});
|
||||
const MeshGL mesh = result.GetMeshGL();
|
||||
ASSERT_EQ(mesh.runIndex.size(), 3);
|
||||
EXPECT_EQ(mesh.runIndex[0], 0);
|
||||
EXPECT_EQ(mesh.runIndex[1], mesh.triVerts.size());
|
||||
EXPECT_EQ(mesh.runIndex[2], mesh.triVerts.size());
|
||||
ASSERT_EQ(mesh.runOriginalID.size(), 2);
|
||||
EXPECT_EQ(mesh.runOriginalID[0], tet.OriginalID());
|
||||
EXPECT_EQ(mesh.runOriginalID[1], cube.OriginalID());
|
||||
ASSERT_EQ(mesh.runTransform.size(), 24);
|
||||
EXPECT_EQ(mesh.runTransform[9], 0);
|
||||
EXPECT_EQ(mesh.runTransform[10], 0);
|
||||
EXPECT_EQ(mesh.runTransform[11], 0);
|
||||
EXPECT_EQ(mesh.runTransform[12 + 9], 3);
|
||||
EXPECT_EQ(mesh.runTransform[12 + 10], 4);
|
||||
EXPECT_EQ(mesh.runTransform[12 + 11], 5);
|
||||
}
|
||||
|
||||
TEST(Boolean, Mirrored) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(1)).Scale({1, -1, 1});
|
||||
EXPECT_TRUE(cube.IsManifold());
|
||||
EXPECT_TRUE(cube.MatchesTriNormals());
|
||||
|
||||
Manifold cube2 = Manifold::Cube(glm::vec3(1)).Scale({0.5, -1, 0.5});
|
||||
Manifold result = cube - cube2;
|
||||
|
||||
ExpectMeshes(result, {{12, 20}});
|
||||
|
||||
auto prop = result.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 0.75);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 5.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests check Boolean operations on coplanar faces.
|
||||
*/
|
||||
TEST(Boolean, SelfSubtract) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
Manifold empty = cube - cube;
|
||||
EXPECT_TRUE(empty.IsManifold());
|
||||
EXPECT_TRUE(empty.IsEmpty());
|
||||
|
||||
auto prop = empty.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 0.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 0.0f);
|
||||
}
|
||||
|
||||
TEST(Boolean, Perturb) {
|
||||
Mesh tmp;
|
||||
tmp.vertPos = {{0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 1.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f},
|
||||
{0.0f, 0.0f, 1.0f}};
|
||||
tmp.triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
Manifold corner(tmp);
|
||||
Manifold empty = corner - corner;
|
||||
EXPECT_TRUE(empty.IsManifold());
|
||||
EXPECT_TRUE(empty.IsEmpty());
|
||||
|
||||
auto prop = empty.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 0.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 0.0f);
|
||||
}
|
||||
|
||||
TEST(Boolean, Coplanar) {
|
||||
Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f);
|
||||
MeshGL cylinderGL = WithPositionColors(cylinder);
|
||||
cylinder = Manifold(cylinderGL);
|
||||
|
||||
Manifold cylinder2 = cylinder.Scale({0.5f, 0.5f, 1.0f})
|
||||
.Rotate(0, 0, 15)
|
||||
.Translate({0.25f, 0.25f, 0.0f});
|
||||
Manifold out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{32, 64, 3, 49}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(out, {cylinderGL});
|
||||
}
|
||||
|
||||
/**
|
||||
* Colinear edges are not collapsed like above due to non-coplanar properties.
|
||||
*/
|
||||
TEST(Boolean, CoplanarProp) {
|
||||
Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f);
|
||||
MeshGL cylinderGL = WithIndexColors(cylinder.GetMesh());
|
||||
cylinder = Manifold(cylinderGL);
|
||||
|
||||
Manifold cylinder2 = cylinder.Scale({0.5f, 0.5f, 1.0f})
|
||||
.Rotate(0, 0, 15)
|
||||
.Translate({0.25f, 0.25f, 0.0f});
|
||||
Manifold out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{42, 84, 3, 68}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(out, {cylinderGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MultiCoplanar) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
Manifold first = cube - cube.Translate({0.3f, 0.3f, 0.0f});
|
||||
cube = cube.Translate({-0.3f, -0.3f, 0.0f});
|
||||
Manifold out = first - cube;
|
||||
CheckStrictly(out);
|
||||
EXPECT_EQ(out.Genus(), -1);
|
||||
auto prop = out.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 0.18, 1e-5);
|
||||
EXPECT_NEAR(prop.surfaceArea, 2.76, 1e-5);
|
||||
}
|
||||
|
||||
TEST(Boolean, FaceUnion) {
|
||||
Manifold cubes = Manifold::Cube();
|
||||
cubes += cubes.Translate({1, 0, 0});
|
||||
EXPECT_EQ(cubes.Genus(), 0);
|
||||
ExpectMeshes(cubes, {{12, 20}});
|
||||
auto prop = cubes.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 2, 1e-5);
|
||||
EXPECT_NEAR(prop.surfaceArea, 10, 1e-5);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("faceUnion.glb", cubes.GetMesh(), {});
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Boolean, EdgeUnion) {
|
||||
Manifold cubes = Manifold::Cube();
|
||||
cubes += cubes.Translate({1, 1, 0});
|
||||
ExpectMeshes(cubes, {{8, 12}, {8, 12}});
|
||||
}
|
||||
|
||||
TEST(Boolean, EdgeUnion2) {
|
||||
Manifold tets = Manifold::Tetrahedron();
|
||||
Manifold cube2 = tets;
|
||||
tets = tets.Translate({0, 0, -1});
|
||||
tets += cube2.Translate({0, 0, 1}).Rotate(0, 0, 90);
|
||||
ExpectMeshes(tets, {{4, 4}, {4, 4}});
|
||||
}
|
||||
|
||||
TEST(Boolean, CornerUnion) {
|
||||
Manifold cubes = Manifold::Cube();
|
||||
cubes += cubes.Translate({1, 1, 1});
|
||||
ExpectMeshes(cubes, {{8, 12}, {8, 12}});
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests verify that the spliting helper functions return meshes with
|
||||
* volumes that make sense.
|
||||
*/
|
||||
TEST(Boolean, Split) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2.0f), true);
|
||||
Manifold oct = Manifold::Sphere(1, 4).Translate(glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
std::pair<Manifold, Manifold> splits = cube.Split(oct);
|
||||
CheckStrictly(splits.first);
|
||||
CheckStrictly(splits.second);
|
||||
EXPECT_FLOAT_EQ(splits.first.GetProperties().volume +
|
||||
splits.second.GetProperties().volume,
|
||||
cube.GetProperties().volume);
|
||||
}
|
||||
|
||||
TEST(Boolean, SplitByPlane) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2.0f), true);
|
||||
cube = cube.Translate({0.0f, 1.0f, 0.0f});
|
||||
cube = cube.Rotate(90.0f, 0.0f, 0.0f);
|
||||
std::pair<Manifold, Manifold> splits =
|
||||
cube.SplitByPlane({0.0f, 0.0f, 1.0f}, 1.0f);
|
||||
CheckStrictly(splits.first);
|
||||
CheckStrictly(splits.second);
|
||||
EXPECT_NEAR(splits.first.GetProperties().volume,
|
||||
splits.second.GetProperties().volume, 1e-5);
|
||||
|
||||
Manifold first = cube.TrimByPlane({0.0f, 0.0f, 1.0f}, 1.0f);
|
||||
// Verify trim returns the same result as the first split by checking that
|
||||
// their bounding boxes contain each other, thus they are equal.
|
||||
EXPECT_TRUE(splits.first.BoundingBox().Contains(first.BoundingBox()));
|
||||
EXPECT_TRUE(first.BoundingBox().Contains(splits.first.BoundingBox()));
|
||||
}
|
||||
|
||||
TEST(Boolean, SplitByPlane60) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2.0f), true);
|
||||
cube = cube.Translate({0.0f, 1.0f, 0.0f});
|
||||
cube = cube.Rotate(0.0f, 0.0f, -60.0f);
|
||||
cube = cube.Translate({2.0f, 0.0f, 0.0f});
|
||||
float phi = 30.0f;
|
||||
std::pair<Manifold, Manifold> splits =
|
||||
cube.SplitByPlane({sind(phi), -cosd(phi), 0.0f}, 1.0f);
|
||||
CheckStrictly(splits.first);
|
||||
CheckStrictly(splits.second);
|
||||
EXPECT_NEAR(splits.first.GetProperties().volume,
|
||||
splits.second.GetProperties().volume, 1e-5);
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests that non-intersecting geometry is properly retained.
|
||||
*/
|
||||
TEST(Boolean, Vug) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(4.0f), true);
|
||||
Manifold vug = cube - Manifold::Cube();
|
||||
|
||||
EXPECT_EQ(vug.Genus(), -1);
|
||||
|
||||
Manifold half = vug.SplitByPlane({0.0f, 0.0f, 1.0f}, -1.0f).first;
|
||||
CheckStrictly(half);
|
||||
EXPECT_EQ(half.Genus(), -1);
|
||||
|
||||
auto prop = half.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 4.0 * 4.0 * 3.0 - 1.0);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 16.0 * 2 + 12.0 * 4 + 6.0);
|
||||
}
|
||||
|
||||
TEST(Boolean, Empty) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
float cubeVol = cube.GetProperties().volume;
|
||||
Manifold empty;
|
||||
|
||||
EXPECT_EQ((cube + empty).GetProperties().volume, cubeVol);
|
||||
EXPECT_EQ((cube - empty).GetProperties().volume, cubeVol);
|
||||
EXPECT_TRUE((empty - cube).IsEmpty());
|
||||
EXPECT_TRUE((cube ^ empty).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(Boolean, Winding) {
|
||||
std::vector<Manifold> cubes;
|
||||
cubes.emplace_back(Manifold::Cube(glm::vec3(3.0f), true));
|
||||
cubes.emplace_back(Manifold::Cube(glm::vec3(2.0f), true));
|
||||
Manifold doubled = Manifold::Compose(cubes);
|
||||
|
||||
Manifold cube = Manifold::Cube(glm::vec3(1.0f), true);
|
||||
EXPECT_TRUE((cube ^= doubled).IsManifold());
|
||||
}
|
||||
|
||||
TEST(Boolean, NonIntersecting) {
|
||||
Manifold cube1 = Manifold::Cube();
|
||||
float vol1 = cube1.GetProperties().volume;
|
||||
Manifold cube2 = cube1.Scale(glm::vec3(2)).Translate({3, 0, 0});
|
||||
float vol2 = cube2.GetProperties().volume;
|
||||
|
||||
EXPECT_EQ((cube1 + cube2).GetProperties().volume, vol1 + vol2);
|
||||
EXPECT_EQ((cube1 - cube2).GetProperties().volume, vol1);
|
||||
EXPECT_TRUE((cube1 ^ cube2).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(Boolean, Precision) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
Manifold cube2 = cube;
|
||||
Manifold cube3 = cube;
|
||||
float distance = 100;
|
||||
float scale = distance * kTolerance;
|
||||
cube2 = cube2.Scale(glm::vec3(scale)).Translate({distance, 0, 0});
|
||||
|
||||
cube += cube2;
|
||||
ExpectMeshes(cube, {{8, 12}});
|
||||
|
||||
cube3 = cube3.Scale(glm::vec3(2 * scale)).Translate({distance, 0, 0});
|
||||
cube += cube3;
|
||||
ExpectMeshes(cube, {{8, 12}, {8, 12}});
|
||||
}
|
||||
|
||||
TEST(Boolean, Precision2) {
|
||||
float scale = 1000;
|
||||
Manifold cube = Manifold::Cube(glm::vec3(scale));
|
||||
Manifold cube2 = cube;
|
||||
float distance = scale * (1 - kTolerance / 2);
|
||||
|
||||
cube2 = cube2.Translate(glm::vec3(-distance));
|
||||
EXPECT_TRUE((cube ^ cube2).IsEmpty());
|
||||
|
||||
cube2 = cube2.Translate(glm::vec3(scale * kTolerance));
|
||||
EXPECT_FALSE((cube ^ cube2).IsEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests verify correct topology and geometry for complex boolean
|
||||
* operations between valid shapes with many faces.
|
||||
*/
|
||||
TEST(Boolean, Sphere) {
|
||||
Manifold sphere = Manifold::Sphere(1.0f, 12);
|
||||
MeshGL sphereGL = WithPositionColors(sphere);
|
||||
sphere = Manifold(sphereGL);
|
||||
|
||||
Manifold sphere2 = sphere.Translate(glm::vec3(0.5));
|
||||
Manifold result = sphere - sphere2;
|
||||
|
||||
ExpectMeshes(result, {{74, 144, 3, 110}});
|
||||
EXPECT_EQ(result.NumDegenerateTris(), 0);
|
||||
|
||||
RelatedGL(result, {sphereGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MeshRelation) {
|
||||
Mesh gyroidMesh = Gyroid();
|
||||
MeshGL gyroidMeshGL = WithPositionColors(gyroidMesh);
|
||||
Manifold gyroid(gyroidMeshGL);
|
||||
|
||||
Manifold gyroid2 = gyroid.Translate(glm::vec3(2.0f));
|
||||
|
||||
EXPECT_FALSE(gyroid.IsEmpty());
|
||||
EXPECT_TRUE(gyroid.IsManifold());
|
||||
EXPECT_TRUE(gyroid.MatchesTriNormals());
|
||||
EXPECT_LE(gyroid.NumDegenerateTris(), 0);
|
||||
Manifold result = gyroid + gyroid2;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels)
|
||||
ExportMesh("gyroidUnion.glb", result.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
EXPECT_TRUE(result.MatchesTriNormals());
|
||||
EXPECT_LE(result.NumDegenerateTris(), 1);
|
||||
EXPECT_EQ(result.Decompose().size(), 1);
|
||||
auto prop = result.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 226, 1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 387, 1);
|
||||
|
||||
RelatedGL(result, {gyroidMeshGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, Cylinders) {
|
||||
Manifold rod = Manifold::Cylinder(1.0, 0.4, -1.0, 12);
|
||||
float arrays1[][12] = {
|
||||
{0, 0, 1, 3, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 6}, //
|
||||
{0, 0, 1, 2, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 8}, //
|
||||
|
||||
{0, 0, 1, 1, //
|
||||
-1, 0, 0, 2, //
|
||||
0, -1, 0, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 2, //
|
||||
0, 0, 1, 6}, //
|
||||
{0, 0, 1, 3, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 7}, //
|
||||
{0, 0, 1, 1, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 0, 1, 4, //
|
||||
0, -1, 0, 6}, //
|
||||
{1, 0, 0, 4, //
|
||||
0, 0, 1, 4, //
|
||||
0, -1, 0, 6}, //
|
||||
};
|
||||
float arrays2[][12] = {
|
||||
{1, 0, 0, 3, //
|
||||
0, 0, 1, 2, //
|
||||
0, -1, 0, 6}, //
|
||||
{1, 0, 0, 4, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 6}, //
|
||||
|
||||
{0, 0, 1, 2, //
|
||||
-1, 0, 0, 2, //
|
||||
0, -1, 0, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 2, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 1, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 4, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 5, //
|
||||
0, 0, 1, 6}, //
|
||||
{0, 0, 1, 3, //
|
||||
-1, 0, 0, 4, //
|
||||
0, -1, 0, 6}, //
|
||||
};
|
||||
|
||||
Manifold m1;
|
||||
for (auto& array : arrays1) {
|
||||
glm::mat4x3 mat;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
mat[i][j] = array[j * 4 + i];
|
||||
}
|
||||
}
|
||||
m1 += rod.Transform(mat);
|
||||
}
|
||||
|
||||
Manifold m2;
|
||||
for (auto& array : arrays2) {
|
||||
glm::mat4x3 mat;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
mat[i][j] = array[j * 4 + i];
|
||||
}
|
||||
}
|
||||
m2 += rod.Transform(mat);
|
||||
}
|
||||
m1 += m2;
|
||||
|
||||
EXPECT_TRUE(m1.IsManifold());
|
||||
EXPECT_TRUE(m1.MatchesTriNormals());
|
||||
EXPECT_LE(m1.NumDegenerateTris(), 12);
|
||||
}
|
||||
|
||||
TEST(Boolean, Cubes) {
|
||||
Manifold result = Manifold::Cube({1.2, 1, 1}, true).Translate({0, -0.5, 0.5});
|
||||
result += Manifold::Cube({1, 0.8, 0.5}).Translate({-0.5, 0, 0.5});
|
||||
result += Manifold::Cube({1.2, 0.1, 0.5}).Translate({-0.6, -0.1, 0});
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
EXPECT_TRUE(result.MatchesTriNormals());
|
||||
EXPECT_LE(result.NumDegenerateTris(), 0);
|
||||
auto prop = result.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 1.6, 0.001);
|
||||
EXPECT_NEAR(prop.surfaceArea, 9.2, 0.01);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("cubes.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Boolean, Subtract) {
|
||||
Mesh firstMesh;
|
||||
firstMesh.vertPos = {{0, 0, 0}, {1540, 0, 0},
|
||||
{1540, 70, 0}, {0, 70, 0},
|
||||
{0, 0, -278.282}, {1540, 70, -278.282},
|
||||
{1540, 0, -278.282}, {0, 70, -278.282}};
|
||||
firstMesh.triVerts = {
|
||||
{0, 1, 2}, {2, 3, 0}, {4, 5, 6}, {5, 4, 7}, {6, 2, 1}, {6, 5, 2},
|
||||
{5, 3, 2}, {5, 7, 3}, {7, 0, 3}, {7, 4, 0}, {4, 1, 0}, {4, 6, 1},
|
||||
};
|
||||
|
||||
Mesh secondMesh;
|
||||
secondMesh.vertPos = {
|
||||
{2.04636e-12, 70, 50000}, {2.04636e-12, -1.27898e-13, 50000},
|
||||
{1470, -1.27898e-13, 50000}, {1540, 70, 50000},
|
||||
{2.04636e-12, 70, -28.2818}, {1470, -1.27898e-13, 0},
|
||||
{2.04636e-12, -1.27898e-13, 0}, {1540, 70, -28.2818}};
|
||||
secondMesh.triVerts = {{0, 1, 2}, {2, 3, 0}, {4, 5, 6}, {5, 4, 7},
|
||||
{6, 2, 1}, {6, 5, 2}, {5, 3, 2}, {5, 7, 3},
|
||||
{7, 0, 3}, {7, 4, 0}, {4, 1, 0}, {4, 6, 1}};
|
||||
|
||||
Manifold first(firstMesh);
|
||||
Manifold second(secondMesh);
|
||||
|
||||
first -= second;
|
||||
first.GetMesh();
|
||||
}
|
||||
|
||||
// FIXME: test is failing on Mac CI (passing on others)
|
||||
TEST(Boolean, Close) {
|
||||
PolygonParams().processOverlaps = true;
|
||||
|
||||
const float r = 10;
|
||||
Manifold a = Manifold::Sphere(r, 256);
|
||||
Manifold result = a;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// std::cout << i << std::endl;
|
||||
result ^= a.Translate({a.Precision() / 10 * i, 0.0, 0.0});
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
}
|
||||
auto prop = result.GetProperties();
|
||||
const float tol = 0.004;
|
||||
EXPECT_NEAR(prop.volume, (4.0f / 3.0f) * glm::pi<float>() * r * r * r,
|
||||
tol * r * r * r);
|
||||
EXPECT_NEAR(prop.surfaceArea, 4 * glm::pi<float>() * r * r, tol * r * r);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("close.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
PolygonParams().processOverlaps = false;
|
||||
}
|
||||
|
||||
TEST(Boolean, UnionDifference) {
|
||||
Manifold block = Manifold::Cube({1, 1, 1}, true) - Manifold::Cylinder(1, 0.5);
|
||||
Manifold result = block + block.Translate({0, 0, 1});
|
||||
float resultsize = result.GetProperties().volume;
|
||||
float blocksize = block.GetProperties().volume;
|
||||
EXPECT_NEAR(resultsize, blocksize * 2, 0.0001);
|
||||
}
|
||||
|
||||
TEST(Boolean, BooleanVolumes) {
|
||||
glm::mat4 m = glm::translate(glm::mat4(1.0f), glm::vec3(1.0f));
|
||||
|
||||
// Define solids which volumes are easy to compute w/ bit arithmetics:
|
||||
// m1, m2, m4 are unique, non intersecting "bits" (of volume 1, 2, 4)
|
||||
// m3 = m1 + m2
|
||||
// m7 = m1 + m2 + m3
|
||||
auto m1 = Manifold::Cube({1, 1, 1});
|
||||
auto m2 = Manifold::Cube({2, 1, 1}).Transform(
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 0, 0)));
|
||||
auto m4 = Manifold::Cube({4, 1, 1}).Transform(
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3(3.0f, 0, 0)));
|
||||
auto m3 = Manifold::Cube({3, 1, 1});
|
||||
auto m7 = Manifold::Cube({7, 1, 1});
|
||||
|
||||
EXPECT_FLOAT_EQ((m1 ^ m2).GetProperties().volume, 0);
|
||||
EXPECT_FLOAT_EQ((m1 + m2 + m4).GetProperties().volume, 7);
|
||||
EXPECT_FLOAT_EQ((m1 + m2 - m4).GetProperties().volume, 3);
|
||||
EXPECT_FLOAT_EQ((m1 + (m2 ^ m4)).GetProperties().volume, 1);
|
||||
EXPECT_FLOAT_EQ((m7 ^ m4).GetProperties().volume, 4);
|
||||
EXPECT_FLOAT_EQ((m7 ^ m3 ^ m1).GetProperties().volume, 1);
|
||||
EXPECT_FLOAT_EQ((m7 ^ (m1 + m2)).GetProperties().volume, 3);
|
||||
EXPECT_FLOAT_EQ((m7 - m4).GetProperties().volume, 3);
|
||||
EXPECT_FLOAT_EQ((m7 - m4 - m2).GetProperties().volume, 1);
|
||||
EXPECT_FLOAT_EQ((m7 - (m7 - m1)).GetProperties().volume, 1);
|
||||
EXPECT_FLOAT_EQ((m7 - (m1 + m2)).GetProperties().volume, 4);
|
||||
}
|
||||
|
||||
TEST(Boolean, TreeTransforms) {
|
||||
auto a = (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1}))
|
||||
.Translate({1, 0, 0});
|
||||
auto b = (Manifold::Cube({1, 1, 1}) + Manifold::Cube({1, 1, 1}));
|
||||
|
||||
EXPECT_FLOAT_EQ((a + b).GetProperties().volume, 2);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright 2021 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"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "glm/geometric.hpp"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "public.h"
|
||||
#include "test.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
TEST(CrossSection, Square) {
|
||||
auto a = Manifold::Cube({5, 5, 5});
|
||||
auto b = Manifold::Extrude(CrossSection::Square({5, 5}), 5);
|
||||
|
||||
EXPECT_FLOAT_EQ((a - b).GetProperties().volume, 0.);
|
||||
}
|
||||
|
||||
TEST(CrossSection, MirrorUnion) {
|
||||
auto a = CrossSection::Square({5., 5.}, true);
|
||||
auto b = a.Translate({2.5, 2.5});
|
||||
auto cross = a + b + b.Mirror({1, 1});
|
||||
auto result = Manifold::Extrude(cross, 5.);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("cross_section_mirror_union.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_FLOAT_EQ(2.5 * a.Area(), cross.Area());
|
||||
EXPECT_TRUE(a.Mirror(glm::vec2(0)).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(CrossSection, RoundOffset) {
|
||||
auto a = CrossSection::Square({20., 20.}, true);
|
||||
auto rounded = a.Offset(5., CrossSection::JoinType::Round);
|
||||
auto result = Manifold::Extrude(rounded, 5.);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("cross_section_round_offset.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Empty) {
|
||||
Polygons polys(2);
|
||||
auto e = CrossSection(polys);
|
||||
EXPECT_TRUE(e.IsEmpty());
|
||||
}
|
||||
|
||||
TEST(CrossSection, RectClip) {
|
||||
auto sq = CrossSection::Square({10, 10});
|
||||
auto rect = Rect({0, 0}, {10, 5});
|
||||
auto clipped = sq.RectClip(rect);
|
||||
|
||||
EXPECT_EQ(sq.Area() / 2, clipped.Area());
|
||||
EXPECT_TRUE(rect.Contains({5, 5}));
|
||||
EXPECT_TRUE(rect.Contains(Rect()));
|
||||
EXPECT_TRUE(rect.DoesOverlap(Rect({5, 5}, {15, 15})));
|
||||
EXPECT_TRUE(Rect().IsEmpty());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Transform) {
|
||||
auto sq = CrossSection::Square({10., 10.});
|
||||
auto a = sq.Rotate(45).Scale({2, 3}).Translate({4, 5});
|
||||
|
||||
glm::mat3x3 trans(1.0f, 0.0f, 0.0f, //
|
||||
0.0f, 1.0f, 0.0f, //
|
||||
4.0f, 5.0f, 1.0f);
|
||||
glm::mat3x3 rot(cosd(45), sind(45), 0.0f, //
|
||||
-sind(45), cosd(45), 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
glm::mat3x3 scale(2.0f, 0.0f, 0.0f, //
|
||||
0.0f, 3.0f, 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
|
||||
auto b = sq.Transform(trans * scale * rot);
|
||||
auto b_copy = CrossSection(b);
|
||||
|
||||
auto ex_b = Manifold::Extrude(b, 1.).GetMesh();
|
||||
Identical(Manifold::Extrude(a, 1.).GetMesh(), ex_b);
|
||||
|
||||
// same transformations are applied in b_copy (giving same result)
|
||||
Identical(ex_b, Manifold::Extrude(b_copy, 1.).GetMesh());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Warp) {
|
||||
auto sq = CrossSection::Square({10., 10.});
|
||||
auto a = sq.Scale({2, 3}).Translate({4, 5});
|
||||
auto b = sq.Warp([](glm::vec2 &v) {
|
||||
v.x = v.x * 2 + 4;
|
||||
v.y = v.y * 3 + 5;
|
||||
});
|
||||
|
||||
EXPECT_EQ(sq.NumVert(), 4);
|
||||
EXPECT_EQ(sq.NumContour(), 1);
|
||||
Identical(Manifold::Extrude(a, 1.).GetMesh(),
|
||||
Manifold::Extrude(b, 1.).GetMesh());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Decompose) {
|
||||
auto a = CrossSection::Square({2., 2.}, true) -
|
||||
CrossSection::Square({1., 1.}, true);
|
||||
auto b = a.Translate({4, 4});
|
||||
auto ab = a + b;
|
||||
auto decomp = ab.Decompose();
|
||||
auto recomp = CrossSection::Compose(decomp);
|
||||
|
||||
EXPECT_EQ(decomp.size(), 2);
|
||||
EXPECT_EQ(decomp[0].NumContour(), 2);
|
||||
EXPECT_EQ(decomp[1].NumContour(), 2);
|
||||
|
||||
Identical(Manifold::Extrude(a, 1.).GetMesh(),
|
||||
Manifold::Extrude(decomp[0], 1.).GetMesh());
|
||||
Identical(Manifold::Extrude(b, 1.).GetMesh(),
|
||||
Manifold::Extrude(decomp[1], 1.).GetMesh());
|
||||
Identical(Manifold::Extrude(ab, 1.).GetMesh(),
|
||||
Manifold::Extrude(recomp, 1.).GetMesh());
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
// Copyright 2021 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 "manifold.h"
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "test.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
template <typename T>
|
||||
int NumUnique(const std::vector<T>& in) {
|
||||
std::set<int> unique;
|
||||
for (const T& v : in) {
|
||||
unique.emplace(v);
|
||||
}
|
||||
return unique.size();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* This tests that turning a mesh into a manifold and returning it to a mesh
|
||||
* produces a consistent result.
|
||||
*/
|
||||
TEST(Manifold, GetMesh) {
|
||||
Manifold manifold = Manifold::Sphere(1);
|
||||
Mesh mesh_out = manifold.GetMesh();
|
||||
Manifold manifold2(mesh_out);
|
||||
Mesh mesh_out2 = manifold2.GetMesh();
|
||||
Identical(mesh_out, mesh_out2);
|
||||
}
|
||||
|
||||
TEST(Manifold, GetMeshGL) {
|
||||
Manifold manifold = Manifold::Sphere(1);
|
||||
Mesh mesh_out = manifold.GetMesh();
|
||||
MeshGL meshGL_out = manifold.GetMeshGL();
|
||||
ASSERT_EQ(meshGL_out.NumVert(), mesh_out.vertPos.size());
|
||||
ASSERT_EQ(meshGL_out.NumTri(), mesh_out.triVerts.size());
|
||||
for (int i = 0; i < meshGL_out.NumVert(); ++i) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
ASSERT_EQ(meshGL_out.vertProperties[3 * i + j], mesh_out.vertPos[i][j]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < meshGL_out.NumTri(); ++i) {
|
||||
for (const int j : {0, 1, 2})
|
||||
ASSERT_EQ(meshGL_out.triVerts[3 * i + j], mesh_out.triVerts[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Manifold, Empty) {
|
||||
Mesh emptyMesh;
|
||||
Manifold empty(emptyMesh);
|
||||
|
||||
EXPECT_TRUE(empty.IsEmpty());
|
||||
EXPECT_EQ(empty.Status(), Manifold::Error::NoError);
|
||||
EXPECT_TRUE(empty.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, ValidInput) {
|
||||
std::vector<float> propTol = {0.1, 0.2};
|
||||
MeshGL tetGL = TetGL();
|
||||
Manifold tet(tetGL, propTol);
|
||||
EXPECT_FALSE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NoError);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput1) {
|
||||
Mesh in = Tet();
|
||||
in.vertPos[2][1] = NAN;
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NonFiniteVertex);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput2) {
|
||||
Mesh in = Tet();
|
||||
std::swap(in.triVerts[2][1], in.triVerts[2][2]);
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NotManifold);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput3) {
|
||||
Mesh in = Tet();
|
||||
for (glm::ivec3& tri : in.triVerts) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
if (tri[i] == 2) tri[i] = -2;
|
||||
}
|
||||
}
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput4) {
|
||||
Mesh in = Tet();
|
||||
for (glm::ivec3& tri : in.triVerts) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
if (tri[i] == 2) tri[i] = 4;
|
||||
}
|
||||
}
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput5) {
|
||||
MeshGL tetGL = TetGL();
|
||||
tetGL.mergeFromVert[tetGL.mergeFromVert.size() - 1] = 7;
|
||||
Manifold tet(tetGL);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::MergeIndexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput7) {
|
||||
MeshGL tetGL = TetGL();
|
||||
tetGL.triVerts[tetGL.triVerts.size() - 1] = 7;
|
||||
Manifold tet(tetGL);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
/**
|
||||
* ExpectMeshes performs a decomposition, so this test ensures that compose and
|
||||
* decompose are inverse operations.
|
||||
*/
|
||||
TEST(Manifold, Decompose) {
|
||||
std::vector<Manifold> manifoldList;
|
||||
manifoldList.emplace_back(Manifold::Tetrahedron());
|
||||
manifoldList.emplace_back(Manifold::Cube());
|
||||
manifoldList.emplace_back(Manifold::Sphere(1, 4));
|
||||
Manifold manifolds = Manifold::Compose(manifoldList);
|
||||
|
||||
ExpectMeshes(manifolds, {{8, 12}, {6, 8}, {4, 4}});
|
||||
|
||||
std::vector<MeshGL> input;
|
||||
|
||||
for (const Manifold& manifold : manifoldList) {
|
||||
EXPECT_GE(manifold.OriginalID(), 0);
|
||||
input.emplace_back(manifold.GetMeshGL());
|
||||
}
|
||||
|
||||
RelatedGL(manifolds, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests check the various manifold constructors.
|
||||
*/
|
||||
TEST(Manifold, Sphere) {
|
||||
int n = 25;
|
||||
Manifold sphere = Manifold::Sphere(1.0f, 4 * n);
|
||||
EXPECT_TRUE(sphere.IsManifold());
|
||||
EXPECT_EQ(sphere.NumTri(), n * n * 8);
|
||||
}
|
||||
|
||||
TEST(Manifold, Normals) {
|
||||
Mesh cube = Manifold::Cube(glm::vec3(1), true).GetMesh();
|
||||
const int nVert = cube.vertPos.size();
|
||||
for (int i = 0; i < nVert; ++i) {
|
||||
glm::vec3 v = glm::normalize(cube.vertPos[i]);
|
||||
glm::vec3& n = cube.vertNormal[i];
|
||||
EXPECT_FLOAT_EQ(v.x, n.x);
|
||||
EXPECT_FLOAT_EQ(v.y, n.y);
|
||||
EXPECT_FLOAT_EQ(v.z, n.z);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Manifold, Extrude) {
|
||||
Polygons polys = SquareHole();
|
||||
Manifold donut = Manifold::Extrude(polys, 1.0f, 3);
|
||||
EXPECT_TRUE(donut.IsManifold());
|
||||
EXPECT_EQ(donut.Genus(), 1);
|
||||
auto prop = donut.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 12.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 48.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, ExtrudeCone) {
|
||||
Polygons polys = SquareHole();
|
||||
Manifold donut = Manifold::Extrude(polys, 1.0f, 0, 0, glm::vec2(0.0f));
|
||||
EXPECT_TRUE(donut.IsManifold());
|
||||
EXPECT_EQ(donut.Genus(), 0);
|
||||
EXPECT_FLOAT_EQ(donut.GetProperties().volume, 4.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Revolve) {
|
||||
Polygons polys = SquareHole();
|
||||
Manifold vug = Manifold::Revolve(polys, 48);
|
||||
EXPECT_TRUE(vug.IsManifold());
|
||||
EXPECT_EQ(vug.Genus(), -1);
|
||||
auto prop = vug.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 14.0f * glm::pi<float>(), 0.2f);
|
||||
EXPECT_NEAR(prop.surfaceArea, 30.0f * glm::pi<float>(), 0.2f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Revolve2) {
|
||||
Polygons polys = SquareHole(2.0f);
|
||||
Manifold donutHole = Manifold::Revolve(polys, 48);
|
||||
EXPECT_TRUE(donutHole.IsManifold());
|
||||
EXPECT_EQ(donutHole.Genus(), 0);
|
||||
auto prop = donutHole.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 48.0f * glm::pi<float>(), 1.0f);
|
||||
EXPECT_NEAR(prop.surfaceArea, 96.0f * glm::pi<float>(), 1.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Smooth) {
|
||||
Manifold tet = Manifold::Tetrahedron();
|
||||
Manifold smooth = Manifold::Smooth(tet.GetMesh());
|
||||
smooth = smooth.Refine(100);
|
||||
ExpectMeshes(smooth, {{20002, 40000}});
|
||||
auto prop = smooth.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 17.38, 0.1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 33.38, 0.1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("smoothTet.glb", smooth.GetMesh(), {});
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Manifold, SmoothSphere) {
|
||||
int n[5] = {4, 8, 16, 32, 64};
|
||||
float precision[5] = {0.03, 0.003, 0.003, 0.0005, 0.00006};
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Manifold sphere = Manifold::Sphere(1, n[i]);
|
||||
// Refine(odd) puts a center point in the triangle, which is the worst case.
|
||||
Manifold smoothed = Manifold::Smooth(sphere.GetMesh()).Refine(7);
|
||||
Mesh out = smoothed.GetMesh();
|
||||
auto bounds =
|
||||
std::minmax_element(out.vertPos.begin(), out.vertPos.end(),
|
||||
[](const glm::vec3& a, const glm::vec3& b) {
|
||||
return glm::dot(a, a) < glm::dot(b, b);
|
||||
});
|
||||
float min = glm::length(*bounds.first);
|
||||
float max = glm::length(*bounds.second);
|
||||
EXPECT_NEAR(min, 1, precision[i]);
|
||||
EXPECT_NEAR(max, 1, precision[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Manifold, ManualSmooth) {
|
||||
// Unit Octahedron
|
||||
const Mesh oct = Manifold::Sphere(1, 4).GetMesh();
|
||||
Mesh smooth = Manifold::Smooth(oct).GetMesh();
|
||||
// Sharpen the edge from vert 4 to 5
|
||||
smooth.halfedgeTangent[6] = {0, 0, 0, 1};
|
||||
smooth.halfedgeTangent[22] = {0, 0, 0, 1};
|
||||
smooth.halfedgeTangent[16] = {0, 0, 0, 1};
|
||||
smooth.halfedgeTangent[18] = {0, 0, 0, 1};
|
||||
Manifold interp(smooth);
|
||||
interp = interp.Refine(100);
|
||||
|
||||
ExpectMeshes(interp, {{40002, 80000}});
|
||||
auto prop = interp.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 3.53, 0.01);
|
||||
EXPECT_NEAR(prop.surfaceArea, 11.39, 0.01);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) {
|
||||
const Mesh out = interp.GetMesh();
|
||||
ExportOptions options;
|
||||
options.faceted = false;
|
||||
options.mat.roughness = 0.1;
|
||||
|
||||
options.mat.vertColor.resize(interp.NumVert());
|
||||
const glm::vec4 red(1, 0, 0, 1);
|
||||
const glm::vec4 purple(1, 0, 1, 1);
|
||||
for (int tri = 0; tri < interp.NumTri(); ++tri) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
const glm::vec3& uvw = {0.5, 0.5, 0.0};
|
||||
const float alpha = glm::min(uvw[0], glm::min(uvw[1], uvw[2]));
|
||||
options.mat.vertColor[out.triVerts[tri][i]] =
|
||||
glm::mix(purple, red, glm::smoothstep(0.0f, 0.2f, alpha));
|
||||
}
|
||||
}
|
||||
ExportMesh("sharpenedSphere.glb", out, options);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Manifold, SmoothMirrored) {
|
||||
const Mesh tet = Manifold::Tetrahedron().GetMesh();
|
||||
Manifold smooth = Manifold::Smooth(tet);
|
||||
Manifold mirror = smooth.Scale({-1, 1, 2}).Refine(10);
|
||||
smooth = smooth.Refine(10).Scale({1, 1, 2});
|
||||
|
||||
auto prop0 = smooth.GetProperties();
|
||||
auto prop1 = mirror.GetProperties();
|
||||
EXPECT_NEAR(prop0.volume, prop1.volume, 0.1);
|
||||
EXPECT_NEAR(prop0.surfaceArea, prop1.surfaceArea, 0.1);
|
||||
}
|
||||
|
||||
TEST(Manifold, Csaszar) {
|
||||
Manifold csaszar = Manifold::Smooth(Csaszar());
|
||||
csaszar = csaszar.Refine(100);
|
||||
ExpectMeshes(csaszar, {{70000, 140000}});
|
||||
auto prop = csaszar.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 84699, 10);
|
||||
EXPECT_NEAR(prop.surfaceArea, 14796, 10);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) {
|
||||
const Mesh out = csaszar.GetMesh();
|
||||
ExportOptions options;
|
||||
options.faceted = false;
|
||||
options.mat.roughness = 0.1;
|
||||
|
||||
options.mat.vertColor.resize(csaszar.NumVert());
|
||||
const glm::vec4 blue(0, 0, 1, 1);
|
||||
const glm::vec4 yellow(1, 1, 0, 1);
|
||||
for (int tri = 0; tri < csaszar.NumTri(); ++tri) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
const glm::vec3& uvw = {0.5, 0.5, 0.0};
|
||||
const float alpha = glm::min(uvw[0], glm::min(uvw[1], uvw[2]));
|
||||
options.mat.vertColor[out.triVerts[tri][i]] =
|
||||
glm::mix(yellow, blue, glm::smoothstep(0.0f, 0.2f, alpha));
|
||||
}
|
||||
}
|
||||
ExportMesh("smoothCsaszar.glb", out, options);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests verify the calculation of a manifold's geometric properties.
|
||||
*/
|
||||
TEST(Manifold, GetProperties) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
EXPECT_TRUE(cube.IsManifold());
|
||||
auto prop = cube.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 1.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f);
|
||||
|
||||
cube = cube.Scale(glm::vec3(-1.0f));
|
||||
prop = cube.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 1.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Precision) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), kTolerance);
|
||||
cube = cube.Scale({0.1, 1, 10});
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), 10 * kTolerance);
|
||||
cube = cube.Translate({-100, -10, -1});
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), 100 * kTolerance);
|
||||
}
|
||||
|
||||
TEST(Manifold, Precision2) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
cube = cube.Translate({-0.5, 0, 0}).Scale({2, 1, 1});
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), 2 * kTolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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. Here we check our discrete approximations calculated
|
||||
* at each vertex against the constant expected values of spheres of different
|
||||
* radii and at different mesh resolutions.
|
||||
*/
|
||||
TEST(Manifold, GetCurvature) {
|
||||
const float precision = 0.015;
|
||||
for (int n = 4; n < 100; n *= 2) {
|
||||
Manifold sphere = Manifold::Sphere(1, 64);
|
||||
Curvature curvature = sphere.GetCurvature();
|
||||
EXPECT_NEAR(curvature.minMeanCurvature, 2, 2 * precision);
|
||||
EXPECT_NEAR(curvature.maxMeanCurvature, 2, 2 * precision);
|
||||
EXPECT_NEAR(curvature.minGaussianCurvature, 1, precision);
|
||||
EXPECT_NEAR(curvature.maxGaussianCurvature, 1, precision);
|
||||
|
||||
sphere = sphere.Scale(glm::vec3(2.0f));
|
||||
curvature = sphere.GetCurvature();
|
||||
EXPECT_NEAR(curvature.minMeanCurvature, 1, precision);
|
||||
EXPECT_NEAR(curvature.maxMeanCurvature, 1, precision);
|
||||
EXPECT_NEAR(curvature.minGaussianCurvature, 0.25, 0.25 * precision);
|
||||
EXPECT_NEAR(curvature.maxGaussianCurvature, 0.25, 0.25 * precision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing more advanced Manifold operations.
|
||||
*/
|
||||
|
||||
TEST(Manifold, Transform) {
|
||||
Manifold cube = Manifold::Cube({1, 2, 3});
|
||||
Manifold cube2 = cube;
|
||||
cube = cube.Rotate(30, 40, 50).Scale({6, 5, 4}).Translate({1, 2, 3});
|
||||
|
||||
glm::mat3 rX(1.0f, 0.0f, 0.0f, //
|
||||
0.0f, cosd(30), sind(30), //
|
||||
0.0f, -sind(30), cosd(30));
|
||||
glm::mat3 rY(cosd(40), 0.0f, -sind(40), //
|
||||
0.0f, 1.0f, 0.0f, //
|
||||
sind(40), 0.0f, cosd(40));
|
||||
glm::mat3 rZ(cosd(50), sind(50), 0.0f, //
|
||||
-sind(50), cosd(50), 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
glm::mat3 s = glm::mat3(1.0f);
|
||||
s[0][0] = 6;
|
||||
s[1][1] = 5;
|
||||
s[2][2] = 4;
|
||||
glm::mat4x3 transform = glm::mat4x3(s * rZ * rY * rX);
|
||||
transform[3] = glm::vec3(1, 2, 3);
|
||||
cube2 = cube2.Transform(transform);
|
||||
|
||||
Identical(cube.GetMesh(), cube2.GetMesh());
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelation) {
|
||||
Mesh gyroidMesh = Gyroid();
|
||||
MeshGL gyroidMeshGL = WithIndexColors(gyroidMesh);
|
||||
Manifold gyroid(gyroidMeshGL);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("gyroid.glb", gyroid.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(gyroid, {gyroidMeshGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelationTransform) {
|
||||
const Manifold cube = Manifold::Cube();
|
||||
const MeshGL cubeGL = cube.GetMeshGL();
|
||||
const Manifold turned = cube.Rotate(45, 90);
|
||||
|
||||
RelatedGL(turned, {cubeGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelationRefine) {
|
||||
const Mesh in = Csaszar();
|
||||
MeshGL inGL = WithIndexColors(in);
|
||||
Manifold csaszar(inGL);
|
||||
|
||||
RelatedGL(csaszar, {inGL});
|
||||
csaszar.Refine(4);
|
||||
RelatedGL(csaszar, {inGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshGLRoundTrip) {
|
||||
const Manifold cylinder = Manifold::Cylinder(2, 1);
|
||||
ASSERT_GE(cylinder.OriginalID(), 0);
|
||||
MeshGL inGL = cylinder.GetMeshGL();
|
||||
const Manifold cylinder2(inGL);
|
||||
const MeshGL outGL = cylinder2.GetMeshGL();
|
||||
|
||||
ASSERT_EQ(inGL.runOriginalID.size(), 1);
|
||||
ASSERT_EQ(outGL.runOriginalID.size(), 1);
|
||||
ASSERT_EQ(outGL.runOriginalID[0], inGL.runOriginalID[0]);
|
||||
|
||||
RelatedGL(cylinder2, {inGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, FaceIDRoundTrip) {
|
||||
const Manifold cube = Manifold::Cube();
|
||||
ASSERT_GE(cube.OriginalID(), 0);
|
||||
MeshGL inGL = cube.GetMeshGL();
|
||||
ASSERT_EQ(NumUnique(inGL.faceID), 6);
|
||||
inGL.faceID = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||
|
||||
const Manifold cube2(inGL);
|
||||
const MeshGL outGL = cube2.GetMeshGL();
|
||||
ASSERT_EQ(NumUnique(outGL.faceID), 12);
|
||||
}
|
||||
|
||||
TEST(Manifold, MirrorUnion) {
|
||||
auto a = Manifold::Cube({5., 5., 5.}, true);
|
||||
auto b = a.Translate({2.5, 2.5, 2.5});
|
||||
auto result = a + b + b.Mirror({1, 1, 0});
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("manifold_mirror_union.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
auto vol_a = a.GetProperties().volume;
|
||||
EXPECT_FLOAT_EQ(vol_a * 2.75, result.GetProperties().volume);
|
||||
EXPECT_TRUE(a.Mirror(glm::vec3(0)).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(Manifold, Invalid) {
|
||||
auto invalid = Manifold::Error::InvalidConstruction;
|
||||
auto circ = CrossSection::Circle(10.);
|
||||
auto empty_circ = CrossSection::Circle(-2.);
|
||||
auto empty_sq = CrossSection::Square(glm::vec3(0.0f));
|
||||
|
||||
EXPECT_EQ(Manifold::Sphere(0).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Cylinder(0, 5).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Cylinder(2, -5).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Cube(glm::vec3(0.0f)).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Cube({-1, 1, 1}).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Extrude(circ, 0.).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Extrude(empty_circ, 10.).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Revolve(empty_sq).Status(), invalid);
|
||||
}
|
||||
+36
-18
@@ -4,6 +4,7 @@
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
@@ -25,7 +26,7 @@ TEST(CBIND, sphere) {
|
||||
size_t sz = manifold_manifold_size();
|
||||
ManifoldManifold *sphere = manifold_sphere(malloc(sz), 1.0f, 4 * n);
|
||||
|
||||
EXPECT_EQ(manifold_status(sphere), NO_ERROR);
|
||||
EXPECT_EQ(manifold_status(sphere), MANIFOLD_NO_ERROR);
|
||||
EXPECT_EQ(manifold_num_tri(sphere), n * n * 8);
|
||||
|
||||
manifold_delete_manifold(sphere);
|
||||
@@ -93,7 +94,7 @@ TEST(CBIND, level_set) {
|
||||
manifold_delete_export_options(options);
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(manifold_status(sdf_man), NO_ERROR);
|
||||
EXPECT_EQ(manifold_status(sdf_man), MANIFOLD_NO_ERROR);
|
||||
|
||||
manifold_delete_meshgl(sdf_mesh);
|
||||
manifold_delete_manifold(sdf_man);
|
||||
@@ -108,10 +109,13 @@ TEST(CBIND, extrude) {
|
||||
malloc(manifold_simple_polygon_size()), &pts[0], 4)};
|
||||
ManifoldPolygons *polys =
|
||||
manifold_polygons(malloc(manifold_polygons_size()), sq, 1);
|
||||
ManifoldCrossSection *cross =
|
||||
manifold_cross_section_of_polygons(malloc(manifold_cross_section_size()),
|
||||
polys, MANIFOLD_FILL_RULE_POSITIVE);
|
||||
|
||||
ManifoldManifold *cube = manifold_cube(malloc(sz), 1., 1., 1., 0);
|
||||
ManifoldManifold *extrusion =
|
||||
manifold_extrude(malloc(sz), polys, 1, 0, 0, 1, 1);
|
||||
manifold_extrude(malloc(sz), cross, 1, 0, 0, 1, 1);
|
||||
|
||||
ManifoldManifold *diff = manifold_difference(malloc(sz), cube, extrusion);
|
||||
ManifoldProperties props = manifold_get_properties(diff);
|
||||
@@ -130,25 +134,39 @@ TEST(CBIND, compose_decompose) {
|
||||
|
||||
ManifoldManifold *s1 = manifold_sphere(malloc(sz), 1.0f, 100);
|
||||
ManifoldManifold *s2 = manifold_translate(malloc(sz), s1, 2., 2., 2.);
|
||||
ManifoldManifold *ss[] = {s1, s2};
|
||||
ManifoldManifold *composed = manifold_compose(malloc(sz), ss, 2);
|
||||
ManifoldManifoldVec *ss =
|
||||
manifold_manifold_vec(malloc(manifold_manifold_vec_size()), 2);
|
||||
manifold_manifold_vec_set(ss, 0, s1);
|
||||
manifold_manifold_vec_set(ss, 1, s2);
|
||||
ManifoldManifold *composed = manifold_compose(malloc(sz), ss);
|
||||
|
||||
ManifoldComponents *cs =
|
||||
manifold_get_components(malloc(manifold_components_size()), composed);
|
||||
size_t len = manifold_components_length(cs);
|
||||
void *bufs[len];
|
||||
for (int i = 0; i < len; ++i) {
|
||||
bufs[i] = malloc(sz);
|
||||
}
|
||||
ManifoldManifold **decomposed = manifold_decompose(bufs, composed, cs);
|
||||
ManifoldManifoldVec *decomposed =
|
||||
manifold_decompose(malloc(manifold_manifold_vec_size()), composed);
|
||||
|
||||
EXPECT_EQ(len, 2);
|
||||
EXPECT_EQ(manifold_manifold_vec_length(decomposed), 2);
|
||||
|
||||
manifold_delete_manifold(s1);
|
||||
manifold_delete_manifold(s2);
|
||||
manifold_delete_manifold_vec(ss);
|
||||
manifold_delete_manifold(composed);
|
||||
manifold_delete_components(cs);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
manifold_delete_manifold(decomposed[i]);
|
||||
}
|
||||
manifold_delete_manifold_vec(decomposed);
|
||||
}
|
||||
|
||||
TEST(CBIND, polygons) {
|
||||
ManifoldVec2 vs[] = {{0, 0}, {1, 1}, {2, 2}};
|
||||
ManifoldSimplePolygon *sp =
|
||||
manifold_simple_polygon(malloc(manifold_simple_polygon_size()), vs, 3);
|
||||
ManifoldSimplePolygon *sps[] = {sp};
|
||||
ManifoldPolygons *ps =
|
||||
manifold_polygons(malloc(manifold_polygons_size()), sps, 1);
|
||||
|
||||
EXPECT_EQ(vs[0].x, manifold_simple_polygon_get_point(sp, 0).x);
|
||||
EXPECT_EQ(vs[1].x, manifold_simple_polygon_get_point(sp, 1).x);
|
||||
EXPECT_EQ(vs[2].x, manifold_simple_polygon_get_point(sp, 2).x);
|
||||
EXPECT_EQ(vs[0].x, manifold_polygons_get_point(ps, 0, 0).x);
|
||||
EXPECT_EQ(vs[1].x, manifold_polygons_get_point(ps, 0, 1).x);
|
||||
EXPECT_EQ(vs[2].x, manifold_polygons_get_point(ps, 0, 2).x);
|
||||
|
||||
manifold_delete_simple_polygon(sp);
|
||||
manifold_delete_polygons(ps);
|
||||
}
|
||||
|
||||
-1263
File diff suppressed because it is too large
Load Diff
+91
-64
@@ -24,22 +24,22 @@ namespace {
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
void StandardizePoly(SimplePolygon &p) {
|
||||
void StandardizePoly(SimplePolygonIdx &p) {
|
||||
auto start = std::min_element(
|
||||
p.begin(), p.end(),
|
||||
[](const PolyVert &v1, const PolyVert &v2) { return v1.idx < v2.idx; });
|
||||
std::rotate(p.begin(), start, p.end());
|
||||
}
|
||||
|
||||
void StandardizePolys(Polygons &polys) {
|
||||
void StandardizePolys(PolygonsIdx &polys) {
|
||||
for (auto &p : polys) StandardizePoly(p);
|
||||
std::sort(polys.begin(), polys.end(),
|
||||
[](SimplePolygon &p1, SimplePolygon &p2) {
|
||||
[](SimplePolygonIdx &p1, SimplePolygonIdx &p2) {
|
||||
return p1[0].idx < p2[0].idx;
|
||||
});
|
||||
}
|
||||
|
||||
void Identical(Polygons p1, Polygons p2) {
|
||||
void Identical(PolygonsIdx p1, PolygonsIdx p2) {
|
||||
ASSERT_EQ(p1.size(), p2.size());
|
||||
StandardizePolys(p1);
|
||||
StandardizePolys(p2);
|
||||
@@ -51,8 +51,8 @@ void Identical(Polygons p1, Polygons p2) {
|
||||
}
|
||||
}
|
||||
|
||||
Polygons Turn180(Polygons polys) {
|
||||
for (SimplePolygon &poly : polys) {
|
||||
PolygonsIdx Turn180(PolygonsIdx polys) {
|
||||
for (SimplePolygonIdx &poly : polys) {
|
||||
for (PolyVert &vert : poly) {
|
||||
vert.pos *= -1;
|
||||
}
|
||||
@@ -60,11 +60,11 @@ Polygons Turn180(Polygons polys) {
|
||||
return polys;
|
||||
}
|
||||
|
||||
Polygons Duplicate(Polygons polys) {
|
||||
PolygonsIdx Duplicate(PolygonsIdx polys) {
|
||||
float xMin = std::numeric_limits<float>::infinity();
|
||||
float xMax = -std::numeric_limits<float>::infinity();
|
||||
int indexMax = 0;
|
||||
for (SimplePolygon &poly : polys) {
|
||||
for (SimplePolygonIdx &poly : polys) {
|
||||
for (PolyVert &vert : poly) {
|
||||
xMin = std::min(xMin, vert.pos.x);
|
||||
xMax = std::max(xMax, vert.pos.x);
|
||||
@@ -76,7 +76,7 @@ Polygons Duplicate(Polygons polys) {
|
||||
|
||||
const int nPolys = polys.size();
|
||||
for (int i = 0; i < nPolys; ++i) {
|
||||
SimplePolygon poly = polys[i];
|
||||
SimplePolygonIdx poly = polys[i];
|
||||
for (PolyVert &vert : poly) {
|
||||
vert.pos.x += shift;
|
||||
vert.idx += indexMax;
|
||||
@@ -86,19 +86,19 @@ Polygons Duplicate(Polygons polys) {
|
||||
return polys;
|
||||
}
|
||||
|
||||
void TestPoly(const Polygons &polys, int expectedNumTri,
|
||||
void TestPoly(const PolygonsIdx &polys, int expectedNumTri,
|
||||
float precision = -1.0f) {
|
||||
PolygonParams().verbose = options.params.verbose;
|
||||
|
||||
std::vector<glm::ivec3> triangles;
|
||||
EXPECT_NO_THROW(triangles = Triangulate(polys, precision));
|
||||
EXPECT_NO_THROW(triangles = TriangulateIdx(polys, precision));
|
||||
EXPECT_EQ(triangles.size(), expectedNumTri) << "Basic";
|
||||
|
||||
// EXPECT_NO_THROW(triangles = Triangulate(Turn180(polys), precision));
|
||||
// EXPECT_EQ(triangles.size(), expectedNumTri) << "Turn 180";
|
||||
EXPECT_NO_THROW(triangles = TriangulateIdx(Turn180(polys), precision));
|
||||
EXPECT_EQ(triangles.size(), expectedNumTri) << "Turn 180";
|
||||
|
||||
// EXPECT_NO_THROW(triangles = Triangulate(Duplicate(polys), precision));
|
||||
// EXPECT_EQ(triangles.size(), 2 * expectedNumTri) << "Duplicate";
|
||||
EXPECT_NO_THROW(triangles = TriangulateIdx(Duplicate(polys), precision));
|
||||
EXPECT_EQ(triangles.size(), 2 * expectedNumTri) << "Duplicate";
|
||||
|
||||
PolygonParams().verbose = false;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ void TestPoly(const Polygons &polys, int expectedNumTri,
|
||||
* tolerance.
|
||||
*/
|
||||
TEST(Polygon, SimpleHole) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, -2), 0}, //
|
||||
{glm::vec2(2, 2), 1}, //
|
||||
@@ -126,7 +126,7 @@ TEST(Polygon, SimpleHole) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SimpleHole2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, 1.63299), 0}, //
|
||||
{glm::vec2(-1.41421, -0.816496), 1}, //
|
||||
@@ -141,7 +141,7 @@ TEST(Polygon, SimpleHole2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, MultiMerge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-7, 0), 0}, //
|
||||
{glm::vec2(-6, 3), 1}, //
|
||||
@@ -163,7 +163,7 @@ TEST(Polygon, MultiMerge) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Colinear) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-5.48368, -3.73905), 0}, //
|
||||
{glm::vec2(-4.9881, -4.51552), 1}, //
|
||||
@@ -196,7 +196,7 @@ TEST(Polygon, Colinear) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Merges) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-3.22039, 10.2769), 0}, //
|
||||
{glm::vec2(-3.12437, 10.4147), 1}, //
|
||||
@@ -214,7 +214,7 @@ TEST(Polygon, Merges) {
|
||||
}
|
||||
|
||||
TEST(Polygon, ExtraTriangle) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1.23141634, -0.493547261), 0}, //
|
||||
{glm::vec2(1.23142254, -0.493540883), 1}, //
|
||||
@@ -253,7 +253,7 @@ TEST(Polygon, ExtraTriangle) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SpongeThin) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.5, -0.475308657), 11}, //
|
||||
{glm::vec2(-0.487654328, -0.475461066), 12}, //
|
||||
@@ -308,7 +308,7 @@ TEST(Polygon, SpongeThin) {
|
||||
}
|
||||
|
||||
TEST(Polygon, ColinearY) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, 0), 0}, //
|
||||
{glm::vec2(1, 1), 1}, //
|
||||
@@ -333,7 +333,7 @@ TEST(Polygon, ColinearY) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Concave) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.707107008, -0.707107008), 0}, //
|
||||
{glm::vec2(1, 0), 1}, //
|
||||
@@ -349,7 +349,7 @@ TEST(Polygon, Concave) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Concave2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4, 0), 1}, //
|
||||
{glm::vec2(3, 2), 3}, //
|
||||
@@ -368,7 +368,7 @@ TEST(Polygon, Concave2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(2.82002997, 0), 0}, //
|
||||
{glm::vec2(2.82002997, 0), 1}, //
|
||||
@@ -384,7 +384,7 @@ TEST(Polygon, Sliver) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Duplicate) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-32.0774002, -10.4309998), 0}, //
|
||||
{glm::vec2(-31.7346992, -6.10348988), 1}, //
|
||||
@@ -413,7 +413,7 @@ TEST(Polygon, Duplicate) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Folded) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(2.82002997, 0), 0}, //
|
||||
{glm::vec2(1.23706996, 4.20994997), 1}, //
|
||||
@@ -431,7 +431,7 @@ TEST(Polygon, Folded) {
|
||||
}
|
||||
|
||||
TEST(Polygon, NearlyLinear) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(2.82002997, -8.22814036e-05), 0}, //
|
||||
{glm::vec2(2.82002997, -8.22814036e-05), 1}, //
|
||||
@@ -445,7 +445,7 @@ TEST(Polygon, NearlyLinear) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(27.4996014, 8.6873703), 74}, //
|
||||
{glm::vec2(28.27701, 9.52887344), 76}, //
|
||||
@@ -458,7 +458,7 @@ TEST(Polygon, Sliver2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver3) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, -2.65168381), 369}, //
|
||||
{glm::vec2(0, -0.792692184), 1889}, //
|
||||
@@ -471,7 +471,7 @@ TEST(Polygon, Sliver3) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver4) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.375669807, 8.90489388), 7474}, //
|
||||
{glm::vec2(0, 8.39722729), 7459}, //
|
||||
@@ -486,7 +486,7 @@ TEST(Polygon, Sliver4) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver5) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-60, 0), 19}, //
|
||||
{glm::vec2(-50, 0), 21}, //
|
||||
@@ -504,7 +504,7 @@ TEST(Polygon, Sliver5) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver6) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(10, 0), 5}, //
|
||||
{glm::vec2(0, 10), 9}, //
|
||||
@@ -517,7 +517,7 @@ TEST(Polygon, Sliver6) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver7) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(50, -10), 0}, //
|
||||
{glm::vec2(60, 0), 25}, //
|
||||
@@ -531,8 +531,35 @@ TEST(Polygon, Sliver7) {
|
||||
TestPoly(polys, 6);
|
||||
}
|
||||
|
||||
TEST(Polygon, DISABLED_Sliver8) {
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(27.9279995, 4.9749999), 4}, //
|
||||
{glm::vec2(31.0610008, 2.32299995), 6}, //
|
||||
{glm::vec2(31.0610008, 2.32299995), 18}, //
|
||||
{glm::vec2(27.9279995, 4.9749999), 16}, //
|
||||
{glm::vec2(27.9279995, 4.9749999), 15}, //
|
||||
{glm::vec2(31.0610008, 2.32299995), 28}, //
|
||||
{glm::vec2(30.4400005, 5.34100008), 17}, //
|
||||
});
|
||||
TestPoly(polys, 5);
|
||||
}
|
||||
|
||||
TEST(Polygon, DISABLED_Sliver9) {
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1.49183154, -0.479596376), 16194}, //
|
||||
{glm::vec2(1.71669781, -0.246418983), 16195}, //
|
||||
{glm::vec2(1.70233643, -0.246410117), 16196}, //
|
||||
{glm::vec2(1.47253978, -0.246277586), 16197}, //
|
||||
{glm::vec2(1.47254002, -0.246287003), 17997}, //
|
||||
{glm::vec2(1.4869014, -0.246295869), 17996}, //
|
||||
});
|
||||
TestPoly(polys, 4);
|
||||
}
|
||||
|
||||
TEST(Polygon, Colinear2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(11.7864399, -7.4572401), 4176}, //
|
||||
{glm::vec2(11.6818037, -7.30982304), 24873}, //
|
||||
@@ -545,7 +572,7 @@ TEST(Polygon, Colinear2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Split) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.707106769, -0.707106769), 1}, //
|
||||
{glm::vec2(1, 0), 14}, //
|
||||
@@ -561,7 +588,7 @@ TEST(Polygon, Split) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Duplicates) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-15, -8.10255623), 1648}, //
|
||||
{glm::vec2(-15, -9.02439785), 1650}, //
|
||||
@@ -574,7 +601,7 @@ TEST(Polygon, Duplicates) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple1) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4.04059982, -4.01843977), 2872}, //
|
||||
{glm::vec2(3.95867562, -4.25263977), 24604}, //
|
||||
@@ -586,7 +613,7 @@ TEST(Polygon, Simple1) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-1, -1), 1}, //
|
||||
{glm::vec2(-0.5, -0.5), 9}, //
|
||||
@@ -600,7 +627,7 @@ TEST(Polygon, Simple2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple3) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(19.7193489, 6.15445995), 19798}, //
|
||||
{glm::vec2(20.2308197, 5.64299059), 31187}, //
|
||||
@@ -613,7 +640,7 @@ TEST(Polygon, Simple3) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple4) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(15, -12.7135563), 287}, //
|
||||
{glm::vec2(15, -10.6843739), 288}, //
|
||||
@@ -629,7 +656,7 @@ TEST(Polygon, Simple4) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple5) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-27.3845081, 0.375669748), 364}, //
|
||||
{glm::vec2(-27.6389656, 0), 365}, //
|
||||
@@ -644,7 +671,7 @@ TEST(Polygon, Simple5) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple6) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-7.99813318, 12.8888826), 25009}, //
|
||||
{glm::vec2(-7.85714436, 12.9125195), 25006}, //
|
||||
@@ -656,7 +683,7 @@ TEST(Polygon, Simple6) {
|
||||
}
|
||||
|
||||
TEST(Polygon, TouchingHole) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-2, -1), 0}, //
|
||||
{glm::vec2(2, -1), 1}, //
|
||||
@@ -673,7 +700,7 @@ TEST(Polygon, TouchingHole) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, -1), 0}, //
|
||||
{glm::vec2(1, 1), 1}, //
|
||||
@@ -687,7 +714,7 @@ TEST(Polygon, Degenerate) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.0740740597, -0.314814836), 4829}, //
|
||||
{glm::vec2(0.0925925896, -0.314814806), 4828}, //
|
||||
@@ -698,7 +725,7 @@ TEST(Polygon, Degenerate2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate3) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.413580239, -0.216049403), 49696}, //
|
||||
{glm::vec2(-0.42592591, -0.216049403), 49690}, //
|
||||
@@ -712,7 +739,7 @@ TEST(Polygon, Degenerate3) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate4) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, 10), 213}, //
|
||||
{glm::vec2(-0.0696326792, 9.99390793), 360}, //
|
||||
@@ -723,7 +750,7 @@ TEST(Polygon, Degenerate4) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate5) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, 0), 3}, //
|
||||
{glm::vec2(1, 1), 7}, //
|
||||
@@ -738,7 +765,7 @@ TEST(Polygon, Degenerate5) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate6) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4.37113897e-07, -4.37113897e-07), 0}, //
|
||||
{glm::vec2(4.37113897e-07, 0), 18}, //
|
||||
@@ -752,7 +779,7 @@ TEST(Polygon, Degenerate6) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Tricky) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, 0), 0}, //
|
||||
{glm::vec2(2, 1), 1}, //
|
||||
@@ -768,7 +795,7 @@ TEST(Polygon, Tricky) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Tricky2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, 0), 0}, //
|
||||
{glm::vec2(3, 1), 1}, //
|
||||
@@ -785,7 +812,7 @@ TEST(Polygon, Tricky2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Slit) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(27.7069321, 13.5144091), 286}, //
|
||||
{glm::vec2(28.664566, 10.8102894), 267}, //
|
||||
@@ -807,7 +834,7 @@ TEST(Polygon, Slit) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SharedEdge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.265432119, 0.0061728349), 61185}, //
|
||||
{glm::vec2(0.277777791, -3.7252903e-09), 61180}, //
|
||||
@@ -820,7 +847,7 @@ TEST(Polygon, SharedEdge) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Precision) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.98486793, -0.492948532), 0}, //
|
||||
{glm::vec2(-0.984859049, -0.492013603), 1}, //
|
||||
@@ -838,7 +865,7 @@ TEST(Polygon, Precision2) {
|
||||
const bool intermediateChecks = PolygonParams().intermediateChecks;
|
||||
PolygonParams().intermediateChecks = false;
|
||||
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4.98093176, -0.247938812), 11113}, //
|
||||
{glm::vec2(4.94630527, -0.0826399028), 22736}, //
|
||||
@@ -866,7 +893,7 @@ TEST(Polygon, Precision2) {
|
||||
};
|
||||
|
||||
TEST(Polygon, Comb) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.462962955, -0.427297652), 18}, //
|
||||
{glm::vec2(0.5, -0.42592591), 17}, //
|
||||
@@ -912,7 +939,7 @@ TEST(Polygon, Comb) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Comb2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.5, -0.462962955), 2}, //
|
||||
{glm::vec2(-0.462962955, -0.461591214), 4}, //
|
||||
@@ -958,7 +985,7 @@ TEST(Polygon, Comb2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, PointPoly) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, -15.9780979), 32}, //
|
||||
{glm::vec2(5.08144999, -14.2678728), 244}, //
|
||||
@@ -991,7 +1018,7 @@ TEST(Polygon, PointPoly) {
|
||||
};
|
||||
|
||||
TEST(Polygon, KissingZigzag) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(37.4667358, 0), 0}, //
|
||||
{glm::vec2(27.8904877, -3.04520559), 1}, //
|
||||
@@ -1106,7 +1133,7 @@ TEST(Polygon, KissingZigzag) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sponge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.5, -0.5), 22}, //
|
||||
{glm::vec2(-0.388888896, -0.388888896), 23}, //
|
||||
@@ -1164,7 +1191,7 @@ TEST(Polygon, Sponge) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SquareHoles) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.388888896, -0.277777791), 10}, //
|
||||
{glm::vec2(0.388888896, -0.388888896), 8}, //
|
||||
@@ -1229,7 +1256,7 @@ TEST(Polygon, SquareHoles) {
|
||||
}
|
||||
|
||||
TEST(Polygon, BigSponge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.5, 0.5), 1}, //
|
||||
{glm::vec2(0.487654328, 0.487654328), 13834}, //
|
||||
|
||||
+11
-8
@@ -23,14 +23,6 @@
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
void CheckManifold(const Manifold& manifold) {
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
for (const glm::vec3& normal : manifold.GetMesh().vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> EdgePairs(const Mesh in) {
|
||||
const int numHalfedge = 3 * in.triVerts.size();
|
||||
std::vector<int> edgePair(numHalfedge);
|
||||
@@ -62,6 +54,7 @@ TEST(Samples, Knot13) {
|
||||
auto prop = knot13.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 20786, 1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 11177, 1);
|
||||
CheckGL(knot13);
|
||||
}
|
||||
|
||||
// This creates two interlinked knots.
|
||||
@@ -79,6 +72,7 @@ TEST(Samples, Knot42) {
|
||||
auto prop1 = knots[1].GetProperties();
|
||||
EXPECT_NEAR(prop0.volume, prop1.volume, 1);
|
||||
EXPECT_NEAR(prop0.surfaceArea, prop1.surfaceArea, 1);
|
||||
CheckGL(knot42);
|
||||
}
|
||||
|
||||
TEST(Samples, Scallop) {
|
||||
@@ -114,6 +108,7 @@ TEST(Samples, Scallop) {
|
||||
auto prop = scallop.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 41.3, 0.1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 78.1, 0.1);
|
||||
CheckGL(scallop);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) {
|
||||
@@ -137,6 +132,8 @@ TEST(Samples, TetPuzzle) {
|
||||
Manifold puzzle = TetPuzzle(50, 0.2, 50);
|
||||
CheckManifold(puzzle);
|
||||
EXPECT_LE(puzzle.NumDegenerateTris(), 2);
|
||||
CheckGL(puzzle);
|
||||
|
||||
Manifold puzzle2 = puzzle.Rotate(0, 0, 180);
|
||||
EXPECT_TRUE((puzzle ^ puzzle2).IsEmpty());
|
||||
puzzle = puzzle.Transform(RotateUp({1, -1, -1}));
|
||||
@@ -153,6 +150,7 @@ TEST(Samples, FrameReduced) {
|
||||
auto prop = frame.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 227333, 10);
|
||||
EXPECT_NEAR(prop.surfaceArea, 62635, 1);
|
||||
CheckGL(frame);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("roundedFrameReduced.glb", frame.GetMesh(), {});
|
||||
@@ -164,6 +162,7 @@ TEST(Samples, Frame) {
|
||||
CheckManifold(frame);
|
||||
EXPECT_EQ(frame.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(frame.Genus(), 5);
|
||||
CheckGL(frame);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("roundedFrame.glb", frame.GetMesh(), {});
|
||||
#endif
|
||||
@@ -176,6 +175,7 @@ TEST(Samples, Bracelet) {
|
||||
CheckManifold(bracelet);
|
||||
EXPECT_LE(bracelet.NumDegenerateTris(), 22);
|
||||
EXPECT_EQ(bracelet.Genus(), 1);
|
||||
CheckGL(bracelet);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("bracelet.glb", bracelet.GetMesh(), {});
|
||||
#endif
|
||||
@@ -187,6 +187,7 @@ TEST(Samples, GyroidModule) {
|
||||
CheckManifold(gyroid);
|
||||
EXPECT_LE(gyroid.NumDegenerateTris(), 2);
|
||||
EXPECT_EQ(gyroid.Genus(), 15);
|
||||
CheckGL(gyroid);
|
||||
|
||||
const Box bounds = gyroid.BoundingBox();
|
||||
const float precision = gyroid.Precision();
|
||||
@@ -204,6 +205,7 @@ TEST(Samples, Sponge1) {
|
||||
EXPECT_EQ(sponge.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(sponge.NumVert(), 40);
|
||||
EXPECT_EQ(sponge.Genus(), 5);
|
||||
CheckGL(sponge);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("mengerSponge1.glb", sponge.GetMesh(), {});
|
||||
@@ -221,6 +223,7 @@ TEST(Samples, Sponge4) {
|
||||
// FIXME: limit NumDegenerateTris
|
||||
EXPECT_LE(sponge.NumDegenerateTris(), 40000);
|
||||
EXPECT_EQ(sponge.Genus(), 26433); // should be 1:5, 2:81, 3:1409, 4:26433
|
||||
CheckGL(sponge);
|
||||
|
||||
std::pair<Manifold, Manifold> cutSponge = sponge.SplitByPlane({1, 1, 1}, 0);
|
||||
EXPECT_TRUE(cutSponge.first.IsManifold());
|
||||
|
||||
+3
-3
@@ -66,7 +66,7 @@ TEST(SDF, Bounds) {
|
||||
if (options.exportModels) ExportMesh("cubeVoid.gltf", levelSet, {});
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NoError);
|
||||
EXPECT_EQ(cubeVoid.Genus(), -1);
|
||||
const float outerBound = size / 2 + edgeLength / 2;
|
||||
EXPECT_NEAR(bounds.min.x, -outerBound, precision);
|
||||
@@ -92,7 +92,7 @@ TEST(SDF, Surface) {
|
||||
if (options.exportModels) ExportMesh("cube.gltf", cube.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NoError);
|
||||
EXPECT_EQ(cube.Genus(), 0);
|
||||
auto prop = cube.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 8, 0.001);
|
||||
@@ -112,6 +112,6 @@ TEST(SDF, Resize) {
|
||||
if (options.exportModels) ExportMesh("layers.gltf", layers.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(layers.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_EQ(layers.Status(), Manifold::Error::NoError);
|
||||
EXPECT_EQ(layers.Genus(), -8);
|
||||
}
|
||||
+27
-1
@@ -14,11 +14,37 @@
|
||||
|
||||
#pragma once
|
||||
#include "gtest/gtest.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
struct Options {
|
||||
bool exportModels = false;
|
||||
manifold::ExecutionParams params = {};
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
extern Options options;
|
||||
|
||||
struct MeshSize {
|
||||
int numVert, numTri;
|
||||
int numProp = 0;
|
||||
int numPropVert = numVert;
|
||||
};
|
||||
|
||||
Polygons SquareHole(float xOffset = 0.0);
|
||||
Mesh Csaszar();
|
||||
Mesh Gyroid();
|
||||
Mesh Tet();
|
||||
MeshGL TetGL();
|
||||
MeshGL WithIndexColors(const Mesh& in);
|
||||
MeshGL WithPositionColors(const Manifold& in);
|
||||
MeshGL WithNormals(const Manifold& in);
|
||||
void Identical(const Mesh& mesh1, const Mesh& mesh2);
|
||||
void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
|
||||
bool checkNormals = false);
|
||||
void ExpectMeshes(const Manifold& manifold,
|
||||
const std::vector<MeshSize>& meshSize);
|
||||
void CheckManifold(const Manifold& manifold);
|
||||
void CheckStrictly(const Manifold& manifold);
|
||||
void CheckGL(const Manifold& manifold);
|
||||
|
||||
+308
-1
@@ -14,8 +14,11 @@
|
||||
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
Options options;
|
||||
|
||||
void print_usage() {
|
||||
@@ -28,7 +31,7 @@ void print_usage() {
|
||||
"flag)\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
@@ -62,6 +65,310 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
manifold::PolygonParams().intermediateChecks = true;
|
||||
manifold::PolygonParams().processOverlaps = false;
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
Polygons SquareHole(float xOffset) {
|
||||
Polygons polys;
|
||||
polys.push_back({
|
||||
{2 + xOffset, 2}, //
|
||||
{-2 + xOffset, 2}, //
|
||||
{-2 + xOffset, -2}, //
|
||||
{2 + xOffset, -2}, //
|
||||
});
|
||||
polys.push_back({
|
||||
{-1 + xOffset, 1}, //
|
||||
{1 + xOffset, 1}, //
|
||||
{1 + xOffset, -1}, //
|
||||
{-1 + xOffset, -1}, //
|
||||
});
|
||||
return polys;
|
||||
}
|
||||
|
||||
Mesh Csaszar() {
|
||||
Mesh csaszar;
|
||||
csaszar.vertPos = {{-20, -20, -10}, //
|
||||
{-20, 20, -15}, //
|
||||
{-5, -8, 8}, //
|
||||
{0, 0, 30}, //
|
||||
{5, 8, 8}, //
|
||||
{20, -20, -15}, //
|
||||
{20, 20, -10}};
|
||||
csaszar.triVerts = {{1, 3, 6}, //
|
||||
{1, 6, 5}, //
|
||||
{2, 5, 6}, //
|
||||
{0, 2, 6}, //
|
||||
{0, 6, 4}, //
|
||||
{3, 4, 6}, //
|
||||
{1, 2, 3}, //
|
||||
{1, 4, 2}, //
|
||||
{1, 0, 4}, //
|
||||
{1, 5, 0}, //
|
||||
{3, 5, 4}, //
|
||||
{0, 5, 3}, //
|
||||
{0, 3, 2}, //
|
||||
{2, 4, 5}};
|
||||
return csaszar;
|
||||
}
|
||||
|
||||
struct GyroidSDF {
|
||||
__host__ __device__ float operator()(glm::vec3 p) const {
|
||||
const glm::vec3 min = p;
|
||||
const glm::vec3 max = glm::vec3(glm::two_pi<float>()) - p;
|
||||
const float min3 = glm::min(min.x, glm::min(min.y, min.z));
|
||||
const float max3 = glm::min(max.x, glm::min(max.y, max.z));
|
||||
const float bound = glm::min(min3, max3);
|
||||
const float gyroid =
|
||||
cos(p.x) * sin(p.y) + cos(p.y) * sin(p.z) + cos(p.z) * sin(p.x);
|
||||
return glm::min(gyroid, bound);
|
||||
}
|
||||
};
|
||||
|
||||
Mesh Gyroid() {
|
||||
const float period = glm::two_pi<float>();
|
||||
return LevelSet(GyroidSDF(), {glm::vec3(0), glm::vec3(period)}, 0.5);
|
||||
}
|
||||
|
||||
Mesh Tet() {
|
||||
Mesh tet;
|
||||
tet.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}};
|
||||
tet.triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
return tet;
|
||||
}
|
||||
|
||||
MeshGL TetGL() {
|
||||
MeshGL tet;
|
||||
tet.numProp = 5;
|
||||
tet.vertProperties = {-1, -1, 1, 0, 0, //
|
||||
-1, 1, -1, 1, -1, //
|
||||
1, -1, -1, 2, -2, //
|
||||
1, 1, 1, 3, -3, //
|
||||
-1, 1, -1, 4, -4, //
|
||||
1, -1, -1, 5, -5, //
|
||||
1, 1, 1, 6, -6};
|
||||
tet.triVerts = {2, 0, 1, 0, 3, 1, 2, 3, 0, 6, 5, 4};
|
||||
tet.mergeFromVert = {4, 5, 6};
|
||||
tet.mergeToVert = {1, 2, 3};
|
||||
return tet;
|
||||
}
|
||||
|
||||
MeshGL WithIndexColors(const Mesh& in) {
|
||||
MeshGL inGL(in);
|
||||
inGL.runOriginalID = {Manifold::ReserveIDs(1)};
|
||||
const int numVert = in.vertPos.size();
|
||||
inGL.numProp = 6;
|
||||
inGL.vertProperties.resize(6 * numVert);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
for (int j : {0, 1, 2}) inGL.vertProperties[6 * i + j] = in.vertPos[i][j];
|
||||
// vertex colors
|
||||
double a;
|
||||
inGL.vertProperties[6 * i + 3] = powf(modf(i * sqrt(2.0), &a), 2.2);
|
||||
inGL.vertProperties[6 * i + 4] = powf(modf(i * sqrt(3.0), &a), 2.2);
|
||||
inGL.vertProperties[6 * i + 5] = powf(modf(i * sqrt(5.0), &a), 2.2);
|
||||
}
|
||||
return inGL;
|
||||
}
|
||||
|
||||
MeshGL WithPositionColors(const Manifold& in) {
|
||||
MeshGL inGL = in.GetMeshGL();
|
||||
inGL.runIndex.clear();
|
||||
inGL.runOriginalID.clear();
|
||||
inGL.runTransform.clear();
|
||||
inGL.faceID.clear();
|
||||
inGL.runOriginalID = {Manifold::ReserveIDs(1)};
|
||||
const int numVert = in.NumVert();
|
||||
const Box bbox = in.BoundingBox();
|
||||
const glm::vec3 size = bbox.Size();
|
||||
const std::vector<float> oldProp = inGL.vertProperties;
|
||||
inGL.numProp = 6;
|
||||
inGL.vertProperties.resize(6 * numVert);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
for (int j : {0, 1, 2}) inGL.vertProperties[6 * i + j] = oldProp[3 * i + j];
|
||||
// vertex colors
|
||||
inGL.vertProperties[6 * i + 3] = (oldProp[3 * i] - bbox.min.x) / size.x;
|
||||
inGL.vertProperties[6 * i + 4] = (oldProp[3 * i + 1] - bbox.min.y) / size.y;
|
||||
inGL.vertProperties[6 * i + 5] = (oldProp[3 * i + 2] - bbox.min.z) / size.z;
|
||||
}
|
||||
return inGL;
|
||||
}
|
||||
|
||||
MeshGL WithNormals(const Manifold& in) {
|
||||
const Mesh mesh = in.GetMesh();
|
||||
MeshGL out;
|
||||
out.runOriginalID = {Manifold::ReserveIDs(1)};
|
||||
out.numProp = 6;
|
||||
out.vertProperties.resize(out.numProp * mesh.vertPos.size());
|
||||
for (int i = 0; i < mesh.vertPos.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) {
|
||||
out.vertProperties[6 * i + j] = mesh.vertPos[i][j];
|
||||
out.vertProperties[6 * i + 3 + j] = mesh.vertNormal[i][j];
|
||||
}
|
||||
}
|
||||
out.triVerts.resize(3 * mesh.triVerts.size());
|
||||
for (int i = 0; i < mesh.triVerts.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) out.triVerts[3 * i + j] = mesh.triVerts[i][j];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void Identical(const Mesh& mesh1, const Mesh& mesh2) {
|
||||
ASSERT_EQ(mesh1.vertPos.size(), mesh2.vertPos.size());
|
||||
for (int i = 0; i < mesh1.vertPos.size(); ++i)
|
||||
for (int j : {0, 1, 2})
|
||||
ASSERT_NEAR(mesh1.vertPos[i][j], mesh2.vertPos[i][j], 0.0001);
|
||||
|
||||
ASSERT_EQ(mesh1.triVerts.size(), mesh2.triVerts.size());
|
||||
for (int i = 0; i < mesh1.triVerts.size(); ++i)
|
||||
ASSERT_EQ(mesh1.triVerts[i], mesh2.triVerts[i]);
|
||||
}
|
||||
|
||||
void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
|
||||
bool checkNormals) {
|
||||
ASSERT_FALSE(out.IsEmpty());
|
||||
const glm::ivec3 normalIdx =
|
||||
checkNormals ? glm::ivec3(3, 4, 5) : glm::ivec3(0);
|
||||
MeshGL output = out.GetMeshGL(normalIdx);
|
||||
for (int run = 0; run < output.runOriginalID.size(); ++run) {
|
||||
const float* m = output.runTransform.data() + 12 * run;
|
||||
const glm::mat4x3 transform =
|
||||
output.runTransform.empty()
|
||||
? glm::mat4x3(1.0f)
|
||||
: glm::mat4x3(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8],
|
||||
m[9], m[10], m[11]);
|
||||
int i = 0;
|
||||
for (; i < originals.size(); ++i) {
|
||||
ASSERT_EQ(originals[i].runOriginalID.size(), 1);
|
||||
if (originals[i].runOriginalID[0] == output.runOriginalID[run]) break;
|
||||
}
|
||||
ASSERT_LT(i, originals.size());
|
||||
const MeshGL& inMesh = originals[i];
|
||||
for (int tri = output.runIndex[run] / 3; tri < output.runIndex[run + 1] / 3;
|
||||
++tri) {
|
||||
if (!output.faceID.empty()) {
|
||||
ASSERT_LT(tri, output.faceID.size());
|
||||
}
|
||||
const int inTri = output.faceID.empty() ? tri : output.faceID[tri];
|
||||
ASSERT_LT(inTri, inMesh.triVerts.size() / 3);
|
||||
glm::ivec3 inTriangle = {inMesh.triVerts[3 * inTri],
|
||||
inMesh.triVerts[3 * inTri + 1],
|
||||
inMesh.triVerts[3 * inTri + 2]};
|
||||
inTriangle *= inMesh.numProp;
|
||||
|
||||
glm::mat3 inTriPos;
|
||||
glm::mat3 outTriPos;
|
||||
for (int j : {0, 1, 2}) {
|
||||
const int vert = output.triVerts[3 * tri + j];
|
||||
glm::vec4 pos;
|
||||
for (int k : {0, 1, 2}) {
|
||||
pos[k] = inMesh.vertProperties[inTriangle[j] + k];
|
||||
outTriPos[j][k] = output.vertProperties[vert * output.numProp + k];
|
||||
}
|
||||
pos[3] = 1;
|
||||
inTriPos[j] = transform * pos;
|
||||
}
|
||||
glm::vec3 outNormal =
|
||||
glm::cross(outTriPos[1] - outTriPos[0], outTriPos[2] - outTriPos[0]);
|
||||
glm::vec3 inNormal =
|
||||
glm::cross(inTriPos[1] - inTriPos[0], inTriPos[2] - inTriPos[0]);
|
||||
const float area = glm::length(inNormal);
|
||||
if (area == 0) continue;
|
||||
inNormal /= area;
|
||||
|
||||
for (int j : {0, 1, 2}) {
|
||||
const int vert = output.triVerts[3 * tri + j];
|
||||
glm::vec3 edges[3];
|
||||
for (int k : {0, 1, 2}) edges[k] = inTriPos[k] - outTriPos[j];
|
||||
const float volume = glm::dot(edges[0], glm::cross(edges[1], edges[2]));
|
||||
ASSERT_LE(volume, area * 100 * out.Precision());
|
||||
|
||||
if (checkNormals) {
|
||||
glm::vec3 normal;
|
||||
for (int k : {0, 1, 2})
|
||||
normal[k] =
|
||||
output.vertProperties[vert * output.numProp + normalIdx[k]];
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
ASSERT_GT(glm::dot(normal, outNormal), 0);
|
||||
} else {
|
||||
for (int p = 3; p < output.numProp; ++p) {
|
||||
const float propOut =
|
||||
output.vertProperties[vert * output.numProp + p];
|
||||
|
||||
glm::vec3 inProp = {inMesh.vertProperties[inTriangle[0] + p],
|
||||
inMesh.vertProperties[inTriangle[1] + p],
|
||||
inMesh.vertProperties[inTriangle[2] + p]};
|
||||
glm::vec3 edgesP[3];
|
||||
for (int k : {0, 1, 2}) {
|
||||
edgesP[k] = edges[k] + inNormal * inProp[k] - inNormal * propOut;
|
||||
}
|
||||
const float volumeP =
|
||||
glm::dot(edgesP[0], glm::cross(edgesP[1], edgesP[2]));
|
||||
|
||||
ASSERT_LE(volumeP, area * 100 * out.Precision());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExpectMeshes(const Manifold& manifold,
|
||||
const std::vector<MeshSize>& meshSize) {
|
||||
EXPECT_FALSE(manifold.IsEmpty());
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
std::vector<Manifold> manifolds = manifold.Decompose();
|
||||
ASSERT_EQ(manifolds.size(), meshSize.size());
|
||||
std::sort(manifolds.begin(), manifolds.end(),
|
||||
[](const Manifold& a, const Manifold& b) {
|
||||
return a.NumVert() != b.NumVert() ? a.NumVert() > b.NumVert()
|
||||
: a.NumTri() > b.NumTri();
|
||||
});
|
||||
for (int i = 0; i < manifolds.size(); ++i) {
|
||||
EXPECT_TRUE(manifolds[i].IsManifold());
|
||||
EXPECT_EQ(manifolds[i].NumVert(), meshSize[i].numVert);
|
||||
EXPECT_EQ(manifolds[i].NumTri(), meshSize[i].numTri);
|
||||
EXPECT_EQ(manifolds[i].NumProp(), meshSize[i].numProp);
|
||||
EXPECT_EQ(manifolds[i].NumPropVert(), meshSize[i].numPropVert);
|
||||
const MeshGL meshGL = manifolds[i].GetMeshGL();
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.mergeToVert.size());
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(),
|
||||
meshGL.NumVert() - manifolds[i].NumVert());
|
||||
const Mesh mesh = manifolds[i].GetMesh();
|
||||
for (const glm::vec3& normal : mesh.vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckManifold(const Manifold& manifold) {
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
for (const glm::vec3& normal : manifold.GetMesh().vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckStrictly(const Manifold& manifold) {
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
EXPECT_EQ(manifold.NumDegenerateTris(), 0);
|
||||
}
|
||||
|
||||
void CheckGL(const Manifold& manifold) {
|
||||
ASSERT_FALSE(manifold.IsEmpty());
|
||||
const MeshGL meshGL = manifold.GetMeshGL();
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.mergeToVert.size());
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.NumVert() - manifold.NumVert());
|
||||
EXPECT_EQ(meshGL.runIndex.size(), meshGL.runOriginalID.size() + 1);
|
||||
EXPECT_EQ(meshGL.runIndex.front(), 0);
|
||||
EXPECT_EQ(meshGL.runIndex.back(), 3 * meshGL.NumTri());
|
||||
if (!meshGL.runTransform.empty()) {
|
||||
EXPECT_EQ(meshGL.runTransform.size(), 12 * meshGL.runOriginalID.size());
|
||||
}
|
||||
EXPECT_EQ(meshGL.faceID.size(), meshGL.NumTri());
|
||||
}
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
diff --git a/src/third_party/thrust/thrust/detail/type_traits.h b/src/third_party/thrust/thrust/detail/type_traits.h
|
||||
index d147f832..60b57654 100644
|
||||
--- a/src/third_party/thrust/thrust/detail/type_traits.h
|
||||
+++ b/src/third_party/thrust/thrust/detail/type_traits.h
|
||||
@@ -144,11 +144,11 @@ template<typename T> struct has_trivial_constructor
|
||||
is_pod<T>::value
|
||||
#if THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_MSVC || \
|
||||
THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_CLANG
|
||||
- || __has_trivial_constructor(T)
|
||||
+ || __is_trivially_constructible(T)
|
||||
#elif THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_GCC
|
||||
// only use the intrinsic for >= 4.3
|
||||
#if (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
|
||||
- || __has_trivial_constructor(T)
|
||||
+ || __is_trivially_constructible(T)
|
||||
#endif // GCC VERSION
|
||||
#endif // THRUST_HOST_COMPILER
|
||||
>
|
||||
@@ -160,11 +160,11 @@ template<typename T> struct has_trivial_copy_constructor
|
||||
is_pod<T>::value
|
||||
#if THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_MSVC || \
|
||||
THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_CLANG
|
||||
- || __has_trivial_copy(T)
|
||||
+ || __is_trivially_constructible(T)
|
||||
#elif THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_GCC
|
||||
// only use the intrinsic for >= 4.3
|
||||
#if (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
|
||||
- || __has_trivial_copy(T)
|
||||
+ || __is_trivially_constructible(T)
|
||||
#endif // GCC VERSION
|
||||
#endif // THRUST_HOST_COMPILER
|
||||
>
|
||||
diff --git a/src/third_party/thrust/thrust/detail/type_traits/has_trivial_assign.h b/src/third_party/thrust/thrust/detail/type_traits/has_trivial_assign.h
|
||||
index 8aa55165..73c8f776 100644
|
||||
--- a/src/third_party/thrust/thrust/detail/type_traits/has_trivial_assign.h
|
||||
+++ b/src/third_party/thrust/thrust/detail/type_traits/has_trivial_assign.h
|
||||
@@ -39,10 +39,10 @@ template<typename T> struct has_trivial_assign
|
||||
#elif THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_GCC
|
||||
// only use the intrinsic for >= 4.3
|
||||
#if (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 3)
|
||||
- || __has_trivial_assign(T)
|
||||
+ || std::is_trivially_assignable<T&, T&>::value
|
||||
#endif // GCC VERSION
|
||||
#elif THRUST_HOST_COMPILER == THRUST_HOST_COMPILER_CLANG
|
||||
- || __has_trivial_assign(T)
|
||||
+ || std::is_trivially_assignable<T&, T&>::value
|
||||
#endif // THRUST_HOST_COMPILER
|
||||
>
|
||||
{};
|
||||
Reference in New Issue
Block a user