Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
311d25a8cf | ||
|
|
6fa0e7becb | ||
|
|
e425761d8e | ||
|
|
a4cc9ee54b | ||
|
|
2a7f9cf48f | ||
|
|
99740dafcb | ||
|
|
9157350a11 | ||
|
|
a8626f907c | ||
|
|
2f22b92352 | ||
|
|
a3d7991563 | ||
|
|
2aef6e4728 | ||
|
|
c167a48d36 | ||
|
|
f6cf8d23d3 | ||
|
|
da3f323113 | ||
|
|
8b7bf4231b | ||
|
|
ac2cfd857f | ||
|
|
285bb0754b | ||
|
|
ba5fead6d5 | ||
|
|
96c57c0591 | ||
|
|
4b65ff3afe | ||
|
|
709021d9d2 | ||
|
|
6cec951aed | ||
|
|
dd3445ddb1 | ||
|
|
37db0d0182 | ||
|
|
1b9243df2a | ||
|
|
8b4c79c485 | ||
|
|
7a197af83d | ||
|
|
a65fedb2f9 | ||
|
|
6ae9dee640 | ||
|
|
79daba1200 | ||
|
|
2f8e3174f7 | ||
|
|
cce35aa5a0 | ||
|
|
e727b115c0 | ||
|
|
f6431112a8 | ||
|
|
2643c291ed | ||
|
|
e024edf103 | ||
|
|
cbfeed1672 | ||
|
|
9dac47a76a | ||
|
|
eb41b9d36a | ||
|
|
72935213e8 | ||
|
|
e1ebc66425 | ||
|
|
ef52b0cce2 | ||
|
|
812ca5c30f | ||
|
|
f064b2a5fd | ||
|
|
18abd3ffcf | ||
|
|
1615b72e83 | ||
|
|
58fb7e3300 | ||
|
|
1959a30560 | ||
|
|
4bdb761d5e | ||
|
|
ba042ee8db | ||
|
|
97317888bc | ||
|
|
a9bf836574 | ||
|
|
29dc753513 | ||
|
|
3a517a490e | ||
|
|
aeba26760b | ||
|
|
85d74b4ccf | ||
|
|
a5f92e2522 | ||
|
|
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 |
@@ -16,6 +16,6 @@ jobs:
|
||||
with:
|
||||
source: '.'
|
||||
exclude: '*/third_party'
|
||||
extensions: 'h,cpp'
|
||||
extensions: 'h,cpp,js,ts,html'
|
||||
clangFormatVersion: 12
|
||||
|
||||
|
||||
@@ -20,9 +20,8 @@ jobs:
|
||||
mkdir public
|
||||
mv ./samples ./public
|
||||
mv ./docs ./public
|
||||
mv ./bindings/wasm/examples/* ./public
|
||||
|
||||
- name: Download WASM files
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
|
||||
@@ -12,7 +12,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
@@ -29,17 +29,18 @@ jobs:
|
||||
pip install trimesh
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=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
|
||||
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
|
||||
run: |
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
cd build/test
|
||||
@@ -52,8 +53,9 @@ jobs:
|
||||
# perhaps issue related to invalid memory access?
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
run: |
|
||||
mkdir build
|
||||
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
|
||||
@@ -81,11 +83,10 @@ jobs:
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-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
|
||||
@@ -107,7 +108,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 +120,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
|
||||
@@ -127,19 +127,17 @@ jobs:
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
cd ../bindings/wasm
|
||||
npm install
|
||||
- name: Test examples
|
||||
run: |
|
||||
cd bindings/wasm/examples
|
||||
npm ci
|
||||
npm run build
|
||||
npm test
|
||||
- name: Upload WASM files
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wasm
|
||||
path: |
|
||||
build/bindings/wasm/manifold.js
|
||||
build/bindings/wasm/manifold.wasm
|
||||
build/bindings/wasm/manifold.d.ts
|
||||
build/bindings/wasm/manifold-global-types.d.ts
|
||||
build/bindings/wasm/manifold-encapsulated-types.d.ts
|
||||
path: bindings/wasm/examples/dist/
|
||||
retention-days: 90
|
||||
|
||||
build_windows:
|
||||
@@ -154,7 +152,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
|
||||
@@ -177,7 +175,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}}
|
||||
@@ -195,7 +193,9 @@ jobs:
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: brew install pkg-config
|
||||
run: |
|
||||
brew install pkg-config assimp
|
||||
pip install trimesh
|
||||
- name: Install OpenMP
|
||||
if: matrix.parallel_backend == 'OMP'
|
||||
run: brew install libomp
|
||||
@@ -204,14 +204,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_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
@@ -226,5 +225,7 @@ 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}}'
|
||||
|
||||
@@ -109,8 +109,14 @@ build
|
||||
buildWASM
|
||||
docs/html
|
||||
node_modules/
|
||||
bindings/wasm/examples/built
|
||||
bindings/wasm/examples/dist
|
||||
bindings/wasm/examples/public/manifold*
|
||||
__pycache__
|
||||
.vscode/c_cpp_properties.json
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.cache
|
||||
.cache
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
@@ -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 "src/third_party/quickhull"]
|
||||
path = src/third_party/quickhull
|
||||
url = https://github.com/akuukka/quickhull
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_break_on_failure",
|
||||
"--gtest_filter=Manifold.GetMesh"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
"environment": [
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_break_on_failure",
|
||||
"--gtest_filter=Manifold.GetMesh"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
"environment": [
|
||||
{
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -98,13 +98,24 @@
|
||||
"concepts": "cpp",
|
||||
"queue": "cpp",
|
||||
"stack": "cpp",
|
||||
"__hash_table": "cpp"
|
||||
"__hash_table": "cpp",
|
||||
"__std_stream": "cpp",
|
||||
"__verbose_abort": "cpp",
|
||||
"any": "cpp",
|
||||
"barrier": "cpp",
|
||||
"charconv": "cpp",
|
||||
"coroutine": "cpp",
|
||||
"latch": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"__sso_allocator": "cpp"
|
||||
},
|
||||
"C_Cpp.clang_format_fallbackStyle": "google",
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DBUILD_SHARED_LIBS=ON",
|
||||
"-DMANIFOLD_EXPORT=OFF",
|
||||
"-DMANIFOLD_CBIND=ON",
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=NONE",
|
||||
@@ -120,6 +131,7 @@
|
||||
"editor.detectIndentation": false,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"Coplanarity",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Google LLC
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
|
||||
@@ -18,33 +18,29 @@ 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)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
set(MANIFOLD_EXPORT ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
|
||||
# Allow shared libraries to be relocatable (add Place Independent Code flag).
|
||||
# Also include when statically linking C bindings to avoid issues with bundling
|
||||
@@ -83,7 +79,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,39 +1,60 @@
|
||||
[](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.
|
||||
|
||||

|
||||
|
||||
### Note for Firefox users
|
||||
|
||||
If you find the editor is stuck on **Loading...**, setting
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository may solve the problem.
|
||||
|
||||
# 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
|
||||
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
|
||||
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
|
||||
|
||||
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.
|
||||
|
||||
## Building
|
||||
|
||||
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
|
||||
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
|
||||
|
||||
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
|
||||
@@ -77,7 +98,7 @@ node test/manifold_test.js
|
||||
|
||||
### Python
|
||||
|
||||
The CMake script will build the python binding `pymanifold` automatically. To
|
||||
The CMake script will build the python binding `manifold3d` automatically. To
|
||||
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
|
||||
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
|
||||
can be found in `bindings/python/examples`. To see exported samples, run:
|
||||
@@ -91,8 +112,8 @@ Run the following code in the interpreter for
|
||||
python binding documentation:
|
||||
|
||||
```
|
||||
>>> import pymanifold
|
||||
>>> help(pymanifold)
|
||||
>>> import manifold3d
|
||||
>>> help(manifold3d)
|
||||
```
|
||||
|
||||
For more detailed documentation, please refer to the C++ API.
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
project(manifoldc)
|
||||
|
||||
|
||||
add_library(manifoldc manifoldc.cpp conv.cpp)
|
||||
add_library(${PROJECT_NAME} manifoldc.cpp conv.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET manifoldc PROPERTY CUDA_ARCHITECTURES 61)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE meshIO)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
manifoldc
|
||||
PRIVATE manifold sdf meshIO graphlite
|
||||
${PROJECT_NAME}
|
||||
PRIVATE manifold sdf graphlite cross_section
|
||||
)
|
||||
|
||||
target_include_directories(manifoldc PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(manifoldc PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(manifoldc PRIVATE cxx_std_17)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "include/types.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
@@ -11,6 +13,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);
|
||||
}
|
||||
@@ -27,19 +41,25 @@ ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
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 = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
e = MANIFOLD_NO_ERROR;
|
||||
break;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
@@ -71,6 +91,9 @@ ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
e = MANIFOLD_INVALID_CONSTRUCTION;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
@@ -79,13 +102,11 @@ ManifoldBox *to_c(manifold::Box *m) {
|
||||
return reinterpret_cast<ManifoldBox *>(m);
|
||||
}
|
||||
|
||||
ManifoldMaterial *to_c(manifold::Material *m) {
|
||||
return reinterpret_cast<ManifoldMaterial *>(m);
|
||||
ManifoldRect *to_c(manifold::Rect *m) {
|
||||
return reinterpret_cast<ManifoldRect *>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
|
||||
return reinterpret_cast<ManifoldExportOptions *>(m);
|
||||
}
|
||||
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
|
||||
|
||||
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
@@ -99,6 +120,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,25 +148,63 @@ 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);
|
||||
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;
|
||||
}
|
||||
|
||||
const manifold::Components *from_c(ManifoldComponents *components) {
|
||||
return reinterpret_cast<manifold::Components *>(components);
|
||||
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);
|
||||
}
|
||||
|
||||
manifold::Material *from_c(ManifoldMaterial *mat) {
|
||||
return reinterpret_cast<manifold::Material *>(mat);
|
||||
const manifold::Rect *from_c(ManifoldRect *m) {
|
||||
return reinterpret_cast<manifold::Rect const *>(m);
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
#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_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, int circular_segments) {
|
||||
auto offset =
|
||||
from_c(cs)->Offset(delta, from_c(jt), miter_limit, circular_segments);
|
||||
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));
|
||||
}
|
||||
@@ -1,35 +1,46 @@
|
||||
#pragma once
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include <types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m);
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms);
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs);
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m);
|
||||
ManifoldComponents *to_c(manifold::Components *components);
|
||||
ManifoldRect *to_c(manifold::Rect *m);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldMaterial *to_c(manifold::Material *m);
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m);
|
||||
ManifoldVec2 to_c(glm::vec2 v);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms);
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
OpType from_c(ManifoldOpType op);
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
|
||||
CrossSection::JoinType from_c(ManifoldJoinType jt);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *m);
|
||||
const manifold::Components *from_c(ManifoldComponents *components);
|
||||
manifold::Material *from_c(ManifoldMaterial *mat);
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options);
|
||||
const manifold::Rect *from_c(ManifoldRect *r);
|
||||
glm::vec2 from_c(ManifoldVec2 v);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -11,6 +12,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,
|
||||
@@ -38,8 +49,35 @@ ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
// The _context variants of manifold_level_set allow a pointer to be passed
|
||||
// back to each invocation of the sdf function pointer, for languages that
|
||||
// need additional data.
|
||||
ManifoldMeshGL *manifold_level_set_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx);
|
||||
ManifoldMeshGL *manifold_level_set_seq_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx);
|
||||
|
||||
// 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 +105,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 +126,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
|
||||
@@ -111,13 +146,126 @@ ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
|
||||
float manifold_precision(ManifoldManifold *m);
|
||||
int manifold_genus(ManifoldManifold *m);
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
|
||||
ManifoldCurvature *manifold_get_curvature(void *mem, ManifoldManifold *m);
|
||||
ManifoldCurvatureBounds manifold_curvature_bounds(ManifoldCurvature *curv);
|
||||
size_t manifold_curvature_vert_length(ManifoldCurvature *curv);
|
||||
float *manifold_curvature_vert_mean(void *mem, ManifoldCurvature *curv);
|
||||
float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv);
|
||||
int manifold_get_circular_segments(float radius);
|
||||
int manifold_original_id(ManifoldManifold *m);
|
||||
uint32_t manifold_reserve_ids(uint32_t n);
|
||||
ManifoldManifold *manifold_set_properties(
|
||||
void *mem, ManifoldManifold *m, int num_prop,
|
||||
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop));
|
||||
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
|
||||
int gaussian_idx, int mean_idx);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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, int circular_segments);
|
||||
|
||||
// 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,
|
||||
@@ -170,7 +318,43 @@ 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);
|
||||
|
||||
// memory size
|
||||
size_t manifold_manifold_size();
|
||||
size_t manifold_manifold_vec_size();
|
||||
size_t manifold_cross_section_size();
|
||||
size_t manifold_cross_section_vec_size();
|
||||
size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
size_t manifold_meshgl_size();
|
||||
size_t manifold_box_size();
|
||||
size_t manifold_rect_size();
|
||||
size_t manifold_curvature_size();
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_manifold(ManifoldManifold *m);
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms);
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *m);
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_destruct_box(ManifoldBox *b);
|
||||
void manifold_destruct_rect(ManifoldRect *b);
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_manifold(ManifoldManifold *m);
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms);
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *cs);
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_delete_polygons(ManifoldPolygons *p);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_delete_box(ManifoldBox *b);
|
||||
void manifold_delete_rect(ManifoldRect *b);
|
||||
|
||||
// MeshIO / Export
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ManifoldMaterial *manifold_material(void *mem);
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
|
||||
@@ -184,40 +368,18 @@ void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat);
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options);
|
||||
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
|
||||
int force_cleanup);
|
||||
|
||||
// memory 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_curvature_size();
|
||||
size_t manifold_components_size();
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
// destruction
|
||||
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_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_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_curvature(ManifoldCurvature *c);
|
||||
void manifold_delete_components(ManifoldComponents *c);
|
||||
void manifold_delete_material(ManifoldMaterial *m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions *options);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
// opaque pointers
|
||||
|
||||
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;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
#endif
|
||||
|
||||
// structs
|
||||
|
||||
typedef struct ManifoldManifoldPair {
|
||||
ManifoldManifold* first;
|
||||
@@ -46,12 +55,13 @@ typedef struct ManifoldProperties {
|
||||
float volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
typedef struct ManifoldCurvatureBounds {
|
||||
float max_mean_curvature;
|
||||
float min_mean_curvature;
|
||||
float max_gaussian_curvature;
|
||||
float min_gaussian_curvature;
|
||||
} ManifoldCurvatureBounds;
|
||||
// enums
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
MANIFOLD_ADD,
|
||||
MANIFOLD_SUBTRACT,
|
||||
MANIFOLD_INTERSECT
|
||||
} ManifoldOpType;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
MANIFOLD_NO_ERROR,
|
||||
@@ -65,4 +75,18 @@ typedef enum ManifoldError {
|
||||
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;
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
#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"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshio.cpp"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
@@ -26,6 +33,20 @@ ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
ManifoldMeshGL *level_set_context(
|
||||
void *mem, float (*sdf_context)(float, float, float, void *),
|
||||
ManifoldBox *bounds, float edge_length, float level, bool seq, void *ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<float(float, float, float)> sdf =
|
||||
std::bind(sdf_context, _1, _2, _3, ctx);
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -51,16 +72,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 +219,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) {
|
||||
@@ -145,6 +247,18 @@ ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
return level_set(mem, sdf, bounds, edge_length, level, true);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx) {
|
||||
return level_set_context(mem, sdf, bounds, edge_length, level, false, ctx);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx) {
|
||||
return level_set_context(mem, sdf, bounds, edge_length, level, true, ctx);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
|
||||
auto refined = from_c(m)->Refine(refine);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
@@ -222,51 +336,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) {
|
||||
@@ -381,81 +474,97 @@ ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
|
||||
}
|
||||
|
||||
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
|
||||
ManifoldCurvature *manifold_get_curvature(void *mem, ManifoldManifold *m) {
|
||||
auto curv = from_c(m)->GetCurvature();
|
||||
return to_c(new (mem) Curvature(curv));
|
||||
}
|
||||
|
||||
ManifoldCurvatureBounds manifold_curvature_bounds(ManifoldCurvature *curv) {
|
||||
auto c = *from_c(curv);
|
||||
return {c.maxMeanCurvature, c.minMeanCurvature, c.maxGaussianCurvature,
|
||||
c.minGaussianCurvature};
|
||||
}
|
||||
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
|
||||
|
||||
size_t manifold_curvature_vert_length(ManifoldCurvature *curv) {
|
||||
return from_c(curv)->vertMeanCurvature.size();
|
||||
}
|
||||
ManifoldManifold *manifold_set_properties(void *mem, ManifoldManifold *m,
|
||||
int num_prop,
|
||||
void (*fun)(float *new_prop,
|
||||
ManifoldVec3 position,
|
||||
const float *old_prop)) {
|
||||
std::function<void(float *, glm::vec3, const float *)> f =
|
||||
[fun](float *new_prop, glm::vec3 v, const float *old_prop) {
|
||||
fun(new_prop, to_c(v), old_prop);
|
||||
};
|
||||
auto man = from_c(m)->SetProperties(num_prop, f);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
};
|
||||
|
||||
float *manifold_curvature_vert_mean(void *mem, ManifoldCurvature *curv) {
|
||||
return copy_data(mem, from_c(curv)->vertMeanCurvature);
|
||||
}
|
||||
|
||||
float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv) {
|
||||
return copy_data(mem, from_c(curv)->vertGaussianCurvature);
|
||||
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
|
||||
int gaussian_idx, int mean_idx) {
|
||||
auto man = from_c(m)->CalculateCurvature(gaussian_idx, mean_idx);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
}
|
||||
|
||||
// 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_curvature_size() { return sizeof(Curvature); }
|
||||
size_t manifold_components_size() { return sizeof(Components); }
|
||||
size_t manifold_rect_size() { return sizeof(Rect); }
|
||||
|
||||
// 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_curvature(ManifoldCurvature *c) { delete from_c(c); }
|
||||
void manifold_delete_components(ManifoldComponents *c) { delete from_c(c); }
|
||||
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
|
||||
|
||||
// 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_curvature(ManifoldCurvature *c) {
|
||||
from_c(c)->~Curvature();
|
||||
}
|
||||
void manifold_destruct_components(ManifoldComponents *c) {
|
||||
from_c(c)->~Components();
|
||||
}
|
||||
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -2,10 +2,32 @@
|
||||
|
||||
#include <conv.h>
|
||||
|
||||
#include "include/types.h"
|
||||
|
||||
// C <-> C++ conversions
|
||||
|
||||
ManifoldMaterial *to_c(manifold::Material *m) {
|
||||
return reinterpret_cast<ManifoldMaterial *>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
|
||||
return reinterpret_cast<ManifoldExportOptions *>(m);
|
||||
}
|
||||
|
||||
manifold::Material *from_c(ManifoldMaterial *mat) {
|
||||
return reinterpret_cast<manifold::Material *>(mat);
|
||||
}
|
||||
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options) {
|
||||
return reinterpret_cast<manifold::ExportOptions *>(options);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// material
|
||||
|
||||
ManifoldMaterial *manifold_material(void *mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
@@ -27,6 +49,8 @@ void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
// export options
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
@@ -41,11 +65,19 @@ void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
// mesh IO
|
||||
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
|
||||
int force_cleanup) {
|
||||
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
|
||||
return to_c(new (mem) manifold::MeshGL(m));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -15,10 +15,10 @@
|
||||
project(python)
|
||||
|
||||
add_subdirectory(third_party)
|
||||
pybind11_add_module(pymanifold pymanifold.cpp)
|
||||
target_link_libraries(pymanifold PRIVATE manifold)
|
||||
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_17)
|
||||
target_include_directories(pymanifold
|
||||
pybind11_add_module(manifold3d manifold3d.cpp)
|
||||
target_link_libraries(manifold3d PRIVATE manifold)
|
||||
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(manifold3d PUBLIC cxx_std_17)
|
||||
target_include_directories(manifold3d
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from pymanifold import Manifold
|
||||
from manifold3d import Manifold
|
||||
|
||||
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from pymanifold import Manifold
|
||||
from manifold3d import Manifold
|
||||
from functools import reduce
|
||||
|
||||
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
from manifold3d import CrossSection, FillRule
|
||||
|
||||
|
||||
def run():
|
||||
# create a polygon
|
||||
polygon_points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
||||
polygons_points = [polygon_points]
|
||||
|
||||
# create a cross-section
|
||||
cross_section = CrossSection(polygons_points)
|
||||
polygons = cross_section.to_polygons()
|
||||
polygon = polygons[0]
|
||||
if set(polygon) != set(polygon_points):
|
||||
raise Exception(f"{polygon=} differs from {polygon_points=}")
|
||||
|
||||
# extrude a polygon to create a manifold
|
||||
extruded_polygon = cross_section.extrude(10.0)
|
||||
eps = 0.001
|
||||
observed_volume = extruded_polygon.get_volume()
|
||||
expected_volume = 10.0
|
||||
if abs(observed_volume - expected_volume) > eps:
|
||||
raise Exception(f"{observed_volume=} differs from {expected_volume=}")
|
||||
observed_surface_area = extruded_polygon.get_surface_area()
|
||||
expected_surface_area = 42.0
|
||||
if abs(observed_surface_area - expected_surface_area) > eps:
|
||||
raise Exception(f"{observed_surface_area=} differs from {expected_surface_area=}")
|
||||
|
||||
# get bounding box from manifold
|
||||
observed_bbox = extruded_polygon.bounding_box
|
||||
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
|
||||
if observed_bbox != expected_bbox:
|
||||
raise Exception(f"{observed_bbox=} differs from {expected_bbox=}")
|
||||
|
||||
return extruded_polygon
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -1,7 +1,7 @@
|
||||
# One of the benchmarks in in https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355
|
||||
# https://www.thingiverse.com/groups/openscad/forums/general/topic:34699
|
||||
|
||||
from pymanifold import Manifold
|
||||
from manifold3d import Manifold
|
||||
import numpy as np
|
||||
|
||||
n = 10
|
||||
|
||||
@@ -18,6 +18,7 @@ import pathlib
|
||||
import sys
|
||||
import importlib
|
||||
import trimesh
|
||||
import numpy as np
|
||||
from time import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -34,8 +35,11 @@ if __name__ == "__main__":
|
||||
model = module.run()
|
||||
mesh = model.to_mesh()
|
||||
if export_models:
|
||||
meshOut = trimesh.Trimesh(
|
||||
vertices=mesh.vert_pos, faces=mesh.tri_verts)
|
||||
if mesh.vert_properties.shape[1] > 3:
|
||||
vertices = np.hsplit(mesh.vert_properties, 3)[0]
|
||||
else:
|
||||
vertices = mesh.vert_properties
|
||||
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
|
||||
print(f'Exported model to {f}.glb')
|
||||
t1 = time()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from pymanifold import Manifold
|
||||
from manifold3d import Manifold
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
|
||||
@@ -0,0 +1,876 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "pybind11/functional.h"
|
||||
#include "pybind11/numpy.h"
|
||||
#include "pybind11/operators.h"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/stl.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
typedef std::tuple<float, float> Float2;
|
||||
typedef std::tuple<float, float, float> Float3;
|
||||
|
||||
constexpr auto pyArrayFlags = py::array::c_style | py::array::forcecast;
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<int>);
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<uint32_t>);
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<float>);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> toVector(const py::array_t<T> &arr) {
|
||||
return std::vector<T>(arr.data(), arr.data() + arr.size());
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(manifold3d, m) {
|
||||
m.doc() = "Python binding for the Manifold library.";
|
||||
|
||||
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
|
||||
"Sets an angle constraint the default number of circular segments for "
|
||||
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
|
||||
"and Manifold::Revolve() constructors. The number of segments will be "
|
||||
"rounded up to the nearest factor of four."
|
||||
"\n\n"
|
||||
":param angle: The minimum angle in degrees between consecutive "
|
||||
"segments. The angle will increase if the the segments hit the minimum "
|
||||
"edge length.\n"
|
||||
"Default is 10 degrees.");
|
||||
|
||||
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
|
||||
"Sets a length constraint the default number of circular segments for "
|
||||
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
|
||||
"and Manifold::Revolve() constructors. The number of segments will be "
|
||||
"rounded up to the nearest factor of four."
|
||||
"\n\n"
|
||||
":param length: The minimum length of segments. The length will "
|
||||
"increase if the the segments hit the minimum angle. Default is 1.0.");
|
||||
|
||||
m.def("set_circular_segments", Quality::SetCircularSegments,
|
||||
"Sets the default number of circular segments for the "
|
||||
"CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and "
|
||||
"Manifold::Revolve() constructors. Overrides the edge length and angle "
|
||||
"constraints and sets the number of segments to exactly this value."
|
||||
"\n\n"
|
||||
":param number: Number of circular segments. Default is 0, meaning no "
|
||||
"constraint is applied.");
|
||||
|
||||
m.def("get_circular_segments", Quality::GetCircularSegments,
|
||||
"Determine the result of the SetMinCircularAngle(), "
|
||||
"SetMinCircularEdgeLength(), and SetCircularSegments() defaults."
|
||||
"\n\n"
|
||||
":param radius: For a given radius of circle, determine how many "
|
||||
"default");
|
||||
|
||||
py::class_<Manifold>(m, "Manifold")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
return first;
|
||||
} else {
|
||||
return Manifold();
|
||||
}
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def(
|
||||
"transform",
|
||||
[](Manifold &self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat.ndim() != 2 || mat.shape(0) != 3 || mat.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid matrix shape, expected (3, 4)");
|
||||
glm::mat4x3 mat_glm;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
},
|
||||
py::arg("m"),
|
||||
"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.\n"
|
||||
"\n\n"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold &self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
|
||||
return self.Translate(glm::vec3(x, y, z));
|
||||
},
|
||||
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 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).")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold &self, py::array_t<float> &t) {
|
||||
auto t_view = t.unchecked<1>();
|
||||
if (t.ndim() != 1 || t.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
|
||||
},
|
||||
py::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold &self, float scale) {
|
||||
return self.Scale(glm::vec3(scale));
|
||||
},
|
||||
py::arg("scale"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param scale: The scalar multiplier for each component of every "
|
||||
"vertices.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold &self, py::array_t<float> &scale) {
|
||||
auto scale_view = scale.unchecked<1>();
|
||||
if (scale.ndim() != 1 || scale.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
|
||||
return self.Scale(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def(
|
||||
"mirror",
|
||||
[](Manifold &self, py::array_t<float> &mirror) {
|
||||
auto v_view = mirror.unchecked<1>();
|
||||
if (mirror.ndim() != 1 || mirror.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
glm::vec3 v(v_view(0), v_view(1), v_view(2));
|
||||
return self.Mirror(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Mirror this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param mirror: The vector defining the axis of mirroring.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold &self, py::array_t<float> &v) {
|
||||
auto v_view = v.unchecked<1>();
|
||||
if (v.ndim() != 1 || v.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
return self.Rotate(v_view(0), v_view(1), v_view(2));
|
||||
},
|
||||
py::arg("v"),
|
||||
"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."
|
||||
"\n\n"
|
||||
":param v: [X, Y, Z] rotation in degrees.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold &self, float xDegrees = 0.0f, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f) {
|
||||
return self.Rotate(xDegrees, yDegrees, zDegrees);
|
||||
},
|
||||
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 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).")
|
||||
.def(
|
||||
"warp",
|
||||
[](Manifold &self, const std::function<Float3(Float3)> &f) {
|
||||
return self.Warp([&f](glm::vec3 &v) {
|
||||
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
|
||||
v.x = std::get<0>(fv);
|
||||
v.y = std::get<1>(fv);
|
||||
v.z = std::get<2>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"),
|
||||
"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."
|
||||
"\n\n"
|
||||
":param warpFunc: A function that modifies a given vertex position.")
|
||||
.def(
|
||||
"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 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 &self, std::optional<py::array_t<uint32_t>> &normalIdx) {
|
||||
glm::ivec3 v(0);
|
||||
if (normalIdx.has_value()) {
|
||||
if (normalIdx.value().ndim() != 1 ||
|
||||
normalIdx.value().shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
auto normalIdx_view = normalIdx.value().unchecked<1>();
|
||||
v = glm::ivec3(normalIdx_view(0), normalIdx_view(1),
|
||||
normalIdx_view(2));
|
||||
}
|
||||
return self.GetMeshGL(v);
|
||||
},
|
||||
"The most complete output of this library, returning a MeshGL that "
|
||||
"is designed to easily push into a renderer, including all "
|
||||
"interleaved vertex properties that may have been input. It also "
|
||||
"includes relations to all the input meshes that form a part of "
|
||||
"this result and the transforms applied to each."
|
||||
"\n\n"
|
||||
":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.",
|
||||
py::arg("normalIdx") = py::none())
|
||||
.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_property_readonly(
|
||||
"bounding_box",
|
||||
[](Manifold &self) {
|
||||
auto b = self.BoundingBox();
|
||||
py::tuple box = py::make_tuple(b.min[0], b.min[1], b.min[2],
|
||||
b.max[0], b.max[1], b.max[2]);
|
||||
return box;
|
||||
},
|
||||
"Gets the manifold bounding box as a tuple "
|
||||
"(xmin, ymin, zmin, xmax, ymax, zmax).")
|
||||
.def_static(
|
||||
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
|
||||
"Constructs a smooth version of the input mesh by creating tangents; "
|
||||
"this method will throw if you have supplied tangents with your "
|
||||
"mesh already. The actual triangle resolution is unchanged; use the "
|
||||
"Refine() method to interpolate to a higher-resolution curve."
|
||||
"\n\n"
|
||||
"By default, every edge is calculated for maximum smoothness (very "
|
||||
"much approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude. No higher-order derivatives are considered, "
|
||||
"as the interpolation is independent per triangle, only sharing "
|
||||
"constraints on their boundaries."
|
||||
"\n\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector of "
|
||||
"sharpened halfedges, which should in general be a small subset of "
|
||||
"all halfedges. Order of entries doesn't matter, as each one "
|
||||
"specifies the desired smoothness (between zero and one, with one "
|
||||
"the default for all unspecified halfedges) and the halfedge index "
|
||||
"(3 * triangle index + [0,1,2] where 0 is the edge between triVert 0 "
|
||||
"and 1, etc)."
|
||||
"\n\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is interpolated along each edge, so the specified value "
|
||||
"should be thought of as an average. Where exactly two sharpened "
|
||||
"edges meet at a vertex, their tangents are rotated to be colinear "
|
||||
"so that the sharpened edge can be continuous. Vertices with only "
|
||||
"one sharpened edge are completely smooth, allowing sharpened edges "
|
||||
"to smoothly vanish at termination. A single vertex can be sharpened "
|
||||
"by sharping all edges that are incident on it, allowing cones to be "
|
||||
"formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const MeshGL &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) and the rest at similarly symmetric points.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](Float3 size, bool center = false) {
|
||||
return Manifold::Cube(
|
||||
glm::vec3(std::get<0>(size), std::get<1>(size),
|
||||
std::get<2>(size)),
|
||||
center);
|
||||
},
|
||||
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 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(
|
||||
"cube",
|
||||
[](py::array_t<float> &size, bool center = false) {
|
||||
auto size_view = size.unchecked<1>();
|
||||
if (size.ndim() != 1 || size.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
return Manifold::Cube(
|
||||
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
|
||||
},
|
||||
py::arg("size"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"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(
|
||||
"cube",
|
||||
[](float x, float y, float z, bool center = false) {
|
||||
return Manifold::Cube(glm::vec3(x, y, z), center);
|
||||
},
|
||||
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 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"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cylinder",
|
||||
[](float height, float radiusLow, float radiusHigh = -1.0f,
|
||||
int circularSegments = 0, bool center = false) {
|
||||
return Manifold::Cylinder(height, radiusLow, radiusHigh,
|
||||
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 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 radiusLow.\n"
|
||||
":param circularSegments: How many line segments to use around the "
|
||||
"circle. Default (-1) is calculated by the static Defaults.\n"
|
||||
":param center: Set to true to shift the center to the origin. "
|
||||
"Default is origin at the bottom.")
|
||||
.def_static(
|
||||
"sphere",
|
||||
[](float radius, int circularSegments = 0) {
|
||||
return Manifold::Sphere(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circular_segments") = 0,
|
||||
"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 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.")
|
||||
.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_<MeshGL>(m, "Mesh")
|
||||
.def(
|
||||
// note that reshape requires mutable array_t, but this will not
|
||||
// affect the original array passed into the function
|
||||
py::init(
|
||||
[](py::array_t<float, pyArrayFlags> &vertProp,
|
||||
py::array_t<int, pyArrayFlags> &triVerts,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&mergeFromVert,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&mergeToVert,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&runIndex,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&runOriginalID,
|
||||
std::optional<py::array_t<float, pyArrayFlags>> &runTransform,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&faceID,
|
||||
const std::optional<py::array_t<float, pyArrayFlags>>
|
||||
&halfedgeTangent,
|
||||
float precision) {
|
||||
MeshGL out;
|
||||
|
||||
out.numProp = vertProp.shape(1);
|
||||
vertProp = vertProp.reshape({-1});
|
||||
out.vertProperties = toVector<float>(vertProp);
|
||||
|
||||
if (triVerts.ndim() != 2 || triVerts.shape(1) != 3)
|
||||
throw std::runtime_error(
|
||||
"Invalid tri_verts shape, expected (-1, 3)");
|
||||
triVerts = triVerts.reshape({-1});
|
||||
out.triVerts = toVector<uint32_t>(triVerts);
|
||||
|
||||
if (mergeFromVert.has_value())
|
||||
out.mergeFromVert = toVector<uint32_t>(mergeFromVert.value());
|
||||
|
||||
if (mergeToVert.has_value())
|
||||
out.mergeToVert = toVector<uint32_t>(mergeToVert.value());
|
||||
|
||||
if (runIndex.has_value())
|
||||
out.runIndex = toVector<uint32_t>(runIndex.value());
|
||||
|
||||
if (runOriginalID.has_value())
|
||||
out.runOriginalID = toVector<uint32_t>(runOriginalID.value());
|
||||
|
||||
if (runTransform.has_value()) {
|
||||
auto runTransform1 = runTransform.value();
|
||||
if (runTransform1.ndim() != 3 ||
|
||||
runTransform1.shape(1) != 4 ||
|
||||
runTransform1.shape(2) != 3)
|
||||
throw std::runtime_error(
|
||||
"Invalid run_transform shape, expected (-1, 4, 3)");
|
||||
runTransform1 = runTransform1.reshape({-1});
|
||||
out.runTransform = toVector<float>(runTransform1);
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector<uint32_t>(faceID.value());
|
||||
|
||||
if (halfedgeTangent.has_value()) {
|
||||
auto halfedgeTangent1 = halfedgeTangent.value();
|
||||
if (halfedgeTangent1.ndim() != 3 ||
|
||||
halfedgeTangent1.shape(1) != 3 ||
|
||||
halfedgeTangent1.shape(2) != 4)
|
||||
throw std::runtime_error(
|
||||
"Invalid halfedge_tangent shape, expected (-1, 3, 4)");
|
||||
halfedgeTangent1 = halfedgeTangent1.reshape({-1});
|
||||
out.halfedgeTangent = toVector<float>(halfedgeTangent1);
|
||||
}
|
||||
|
||||
return out;
|
||||
}),
|
||||
py::arg("vert_properties"), py::arg("tri_verts"),
|
||||
py::arg("merge_from_vert") = py::none(),
|
||||
py::arg("merge_to_vert") = py::none(),
|
||||
py::arg("run_index") = py::none(),
|
||||
py::arg("run_original_id") = py::none(),
|
||||
py::arg("run_transform") = py::none(),
|
||||
py::arg("face_id") = py::none(),
|
||||
py::arg("halfedge_tangent") = py::none(), py::arg("precision") = 0)
|
||||
.def_property_readonly(
|
||||
"vert_properties",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.vertProperties.size(),
|
||||
self.vertProperties.data())
|
||||
.reshape(std::array<int, 2>{-1, (int)self.numProp});
|
||||
})
|
||||
.def_property_readonly("tri_verts",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.triVerts.size(),
|
||||
self.triVerts.data())
|
||||
.reshape(std::array<int, 2>{-1, 3});
|
||||
})
|
||||
.def_property_readonly("run_transform",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.runTransform.size(),
|
||||
self.runTransform.data())
|
||||
.reshape(std::array<int, 3>{-1, 4, 3});
|
||||
})
|
||||
.def_property_readonly("halfedge_tangent",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.halfedgeTangent.size(),
|
||||
self.halfedgeTangent.data())
|
||||
.reshape(std::array<int, 3>{-1, 3, 4});
|
||||
})
|
||||
.def_readonly("merge_from_vert", &MeshGL::mergeFromVert)
|
||||
.def_readonly("merge_to_vert", &MeshGL::mergeToVert)
|
||||
.def_readonly("run_index", &MeshGL::runIndex)
|
||||
.def_readonly("run_original_id", &MeshGL::runOriginalID)
|
||||
.def_readonly("face_id", &MeshGL::faceID);
|
||||
|
||||
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([](std::vector<std::vector<Float2>> &polygons,
|
||||
CrossSection::FillRule fillrule) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
simplePolygons[i] = std::vector<glm::vec2>(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
simplePolygons[i][j] = {std::get<0>(polygons[i][j]),
|
||||
std::get<1>(polygons[i][j])};
|
||||
}
|
||||
}
|
||||
return CrossSection(simplePolygons, fillrule);
|
||||
}),
|
||||
py::arg("polygons"),
|
||||
py::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
"Create a 2d cross-section from a set of contours (complex "
|
||||
"polygons). A boolean union operation (with Positive filling rule "
|
||||
"by default) performed to combine overlapping polygons and ensure "
|
||||
"the resulting CrossSection is free of intersections."
|
||||
"\n\n"
|
||||
":param contours: A set of closed paths describing zero or more "
|
||||
"complex polygons.\n"
|
||||
":param fillrule: The filling rule used to interpret polygon "
|
||||
"sub-regions in contours.")
|
||||
.def("area", &CrossSection::Area,
|
||||
"Return the total area covered by complex polygons making up the "
|
||||
"CrossSection.")
|
||||
.def("num_vert", &CrossSection::NumVert,
|
||||
"Return the number of vertices in the CrossSection.")
|
||||
.def("num_contour", &CrossSection::NumContour,
|
||||
"Return the number of contours (both outer and inner paths) in the "
|
||||
"CrossSection.")
|
||||
.def("is_empty", &CrossSection::IsEmpty,
|
||||
"Does the CrossSection contain any contours?")
|
||||
.def(
|
||||
"translate",
|
||||
[](CrossSection self, Float2 v) {
|
||||
return self.Translate({std::get<0>(v), std::get<1>(v)});
|
||||
},
|
||||
"Move this CrossSection in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def("rotate", &CrossSection::Rotate,
|
||||
"Applies a (Z-axis) rotation to the CrossSection, in degrees. This "
|
||||
"operation can be chained. Transforms are combined and applied "
|
||||
"lazily."
|
||||
"\n\n"
|
||||
":param degrees: degrees about the Z-axis to rotate.")
|
||||
.def(
|
||||
"scale",
|
||||
[](CrossSection self, Float2 s) {
|
||||
return self.Scale({std::get<0>(s), std::get<1>(s)});
|
||||
},
|
||||
"Scale this CrossSection in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by per component.")
|
||||
.def(
|
||||
"mirror",
|
||||
[](CrossSection self, Float2 ax) {
|
||||
return self.Mirror({std::get<0>(ax), std::get<1>(ax)});
|
||||
},
|
||||
"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."
|
||||
"\n\n"
|
||||
":param ax: the axis to be mirrored over")
|
||||
.def(
|
||||
"transform",
|
||||
[](CrossSection self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat.ndim() != 2 || mat.shape(0) != 2 || mat.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid matrix shape, expected (2, 3)");
|
||||
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);
|
||||
},
|
||||
"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."
|
||||
"\n\n"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.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"),
|
||||
"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."
|
||||
"\n\n"
|
||||
":param warpFunc: A function that modifies a given vertex position.")
|
||||
.def("simplify", &CrossSection::Simplify,
|
||||
"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."
|
||||
"\n\n"
|
||||
"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.")
|
||||
.def("offset", &CrossSection::Offset, py::arg("delta"),
|
||||
py::arg("join_type"), py::arg("miter_limit") = 2.0,
|
||||
py::arg("arc_tolerance") = 0.0,
|
||||
"Inflate the contours in CrossSection by the specified delta, "
|
||||
"handling corners according to the given JoinType."
|
||||
"\n\n"
|
||||
":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.\n"
|
||||
":param jt: The join type specifying the treatment of contour joins "
|
||||
"(corners).\n"
|
||||
":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.\n"
|
||||
":param circularSegments: Number of segments per 360 degrees of "
|
||||
"<B>JoinType::Round</B> corners (roughly, the number of vertices "
|
||||
"that will be added to each contour). Default is calculated by the "
|
||||
"static Quality defaults according to the radius.")
|
||||
.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,
|
||||
"This operation returns a vector of CrossSections that are "
|
||||
"topologically disconnected, each containing one outline contour "
|
||||
"with zero or more holes.")
|
||||
.def(
|
||||
"to_polygons",
|
||||
[](CrossSection self) {
|
||||
const Polygons &data = self.ToPolygons();
|
||||
py::list polygon_list;
|
||||
for (int i = 0; i < data.size(); ++i) {
|
||||
py::list polygon;
|
||||
for (int j = 0; j < data[i].size(); ++j) {
|
||||
auto f = data[i][j];
|
||||
py::tuple vertex = py::make_tuple(f[0], f[1]);
|
||||
polygon.append(vertex);
|
||||
}
|
||||
polygon_list.append(polygon);
|
||||
}
|
||||
return polygon_list;
|
||||
},
|
||||
"Returns the vertices of the cross-section's polygons as a "
|
||||
"List[List[Tuple[float, float]]].")
|
||||
.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,
|
||||
"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."
|
||||
"\n\n"
|
||||
":param size: The X, and Y dimensions of the square.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"circle",
|
||||
[](float radius, int circularSegments) {
|
||||
return CrossSection::Circle(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circularSegments") = 0,
|
||||
"Constructs a circle of a given radius."
|
||||
"\n\n"
|
||||
":param radius: Radius of the circle. Must be positive.\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is calculated by the static Quality defaults according to "
|
||||
"the radius.");
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold.h"
|
||||
#include "pybind11/functional.h"
|
||||
#include "pybind11/numpy.h"
|
||||
#include "pybind11/operators.h"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/stl.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
typedef std::tuple<float, float> Float2;
|
||||
typedef std::tuple<float, float, float> Float3;
|
||||
|
||||
struct PolygonsWrapper {
|
||||
std::unique_ptr<Polygons> polygons;
|
||||
};
|
||||
|
||||
PYBIND11_MODULE(pymanifold, m) {
|
||||
m.doc() =
|
||||
"Python binding for the manifold library. Please check the C++ "
|
||||
"documentation for APIs.\n"
|
||||
"This binding will perform copying to make the API more familiar to "
|
||||
"OpenSCAD users.";
|
||||
py::class_<Manifold>(m, "Manifold")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
return first;
|
||||
} else {
|
||||
return Manifold();
|
||||
}
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def(
|
||||
"transform",
|
||||
[](Manifold self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat_view.shape(0) != 3 || mat_view.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid matrix shape");
|
||||
glm::mat4x3 mat_glm;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
},
|
||||
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"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
|
||||
return self.Translate(glm::vec3(x, y, z));
|
||||
},
|
||||
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"
|
||||
":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).")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold self, py::array_t<float> &t) {
|
||||
auto t_view = t.unchecked<1>();
|
||||
if (t.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
|
||||
},
|
||||
py::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def("scale",
|
||||
static_cast<Manifold (*)(Manifold, float)>(
|
||||
[](Manifold self, float scale) {
|
||||
return self.Scale(glm::vec3(scale));
|
||||
}),
|
||||
py::arg("scale"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param scale: The scalar multiplier for each component of every "
|
||||
"vertices.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold self, py::array_t<float> &scale) {
|
||||
auto scale_view = scale.unchecked<1>();
|
||||
if (scale_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
|
||||
return self.Scale(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are\n"
|
||||
"combined and applied lazily.\n"
|
||||
"\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold self, py::array_t<float> &v) {
|
||||
auto v_view = v.unchecked<1>();
|
||||
if (v_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Rotate(v_view(0), v_view(1), v_view(2));
|
||||
},
|
||||
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"
|
||||
":param v: [X, Y, Z] rotation in degrees.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold self, float xDegrees = 0.0f, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f) {
|
||||
return self.Rotate(xDegrees, yDegrees, zDegrees);
|
||||
},
|
||||
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"
|
||||
":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).")
|
||||
.def(
|
||||
"warp",
|
||||
[](Manifold self, const std::function<Float3(Float3)> &f) {
|
||||
return self.Warp([&f](glm::vec3 &v) {
|
||||
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
|
||||
v.x = std::get<0>(fv);
|
||||
v.y = std::get<1>(fv);
|
||||
v.z = std::get<2>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"))
|
||||
.def(
|
||||
"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"
|
||||
"\n"
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def("to_mesh", &Manifold::GetMesh)
|
||||
.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"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_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.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](Float3 size, bool center = false) {
|
||||
return Manifold::Cube(
|
||||
glm::vec3(std::get<0>(size), std::get<1>(size),
|
||||
std::get<2>(size)),
|
||||
center);
|
||||
},
|
||||
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"
|
||||
":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(
|
||||
"cube",
|
||||
[](py::array_t<float> &size, bool center = false) {
|
||||
auto size_view = size.unchecked<1>();
|
||||
if (size_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return Manifold::Cube(
|
||||
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
|
||||
},
|
||||
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"
|
||||
":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(
|
||||
"cube",
|
||||
[](float x, float y, float z, bool center = false) {
|
||||
return Manifold::Cube(glm::vec3(x, y, z), center);
|
||||
},
|
||||
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"
|
||||
":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"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cylinder",
|
||||
[](float height, float radiusLow, float radiusHigh = -1.0f,
|
||||
int circularSegments = 0) {
|
||||
return Manifold::Cylinder(height, radiusLow, radiusHigh,
|
||||
circularSegments);
|
||||
},
|
||||
py::arg("height"), py::arg("radius_low"),
|
||||
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
|
||||
"A convenience constructor for the common case of extruding a "
|
||||
"circle. Can also\n"
|
||||
"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"
|
||||
":param circularSegments: How many line segments to use around the "
|
||||
"circle.\n"
|
||||
"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.")
|
||||
.def_static(
|
||||
"sphere",
|
||||
[](float radius, int circularSegments = 0) {
|
||||
return Manifold::Sphere(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circular_segments") = 0,
|
||||
"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"
|
||||
"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.");
|
||||
|
||||
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<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])};
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return PolygonsWrapper{std::make_unique<Polygons>(simplePolygons)};
|
||||
}),
|
||||
py::arg("polygons"),
|
||||
"Construct the Polygons object from a list of simple polygon, "
|
||||
"where each simple polygon is a list of points (pair of floats).")
|
||||
.def(
|
||||
"extrude",
|
||||
[](PolygonsWrapper &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.polygons, 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\n"
|
||||
"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"
|
||||
":param twistDegrees: Amount to twist the top crossSection relative "
|
||||
"to the\n"
|
||||
"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).")
|
||||
.def(
|
||||
"revolve",
|
||||
[](PolygonsWrapper &self, int circularSegments = 0) {
|
||||
return Manifold::Revolve(*self.polygons, circularSegments);
|
||||
},
|
||||
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"
|
||||
"\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is\n"
|
||||
"calculated by the static Defaults.");
|
||||
|
||||
py::class_<Mesh>(m, "Mesh")
|
||||
.def(py::init([](py::array_t<float> &vertPos, py::array_t<int> &triVerts,
|
||||
py::array_t<float> &vertNormal,
|
||||
py::array_t<float> &halfedgeTangent) {
|
||||
auto vertPos_view = vertPos.unchecked<2>();
|
||||
auto triVerts_view = triVerts.unchecked<2>();
|
||||
auto vertNormal_view = vertNormal.unchecked<2>();
|
||||
auto halfedgeTangent_view = halfedgeTangent.unchecked<2>();
|
||||
if (vertPos_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid vert_pos shape");
|
||||
if (triVerts_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid tri_verts shape");
|
||||
if (vertNormal_view.shape(0) != 0) {
|
||||
if (vertNormal_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid vert_normal shape");
|
||||
if (vertNormal_view.shape(0) != vertPos_view.shape(0))
|
||||
throw std::runtime_error(
|
||||
"vert_normal must have the same length as vert_pos");
|
||||
}
|
||||
if (halfedgeTangent_view.shape(0) != 0) {
|
||||
if (halfedgeTangent_view.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid halfedge_tangent shape");
|
||||
if (halfedgeTangent_view.shape(0) != triVerts_view.shape(0) * 3)
|
||||
throw std::runtime_error(
|
||||
"halfedge_tangent must be three times as long as "
|
||||
"tri_verts");
|
||||
}
|
||||
std::vector<glm::vec3> vertPos_vec(vertPos_view.shape(0));
|
||||
std::vector<glm::ivec3> triVerts_vec(triVerts_view.shape(0));
|
||||
std::vector<glm::vec3> vertNormal_vec(vertNormal_view.shape(0));
|
||||
std::vector<glm::vec4> halfedgeTangent_vec(
|
||||
halfedgeTangent_view.shape(0));
|
||||
for (int i = 0; i < vertPos_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
vertPos_vec[i][j] = vertPos_view(i, j);
|
||||
for (int i = 0; i < triVerts_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
triVerts_vec[i][j] = triVerts_view(i, j);
|
||||
for (int i = 0; i < vertNormal_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
vertNormal_vec[i][j] = vertNormal_view(i, j);
|
||||
for (int i = 0; i < halfedgeTangent_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedgeTangent_vec[i][j] = halfedgeTangent_view(i, j);
|
||||
return Mesh({vertPos_vec, triVerts_vec, vertNormal_vec,
|
||||
halfedgeTangent_vec});
|
||||
}),
|
||||
py::arg("vert_pos"), py::arg("tri_verts"), py::arg("vert_normal"),
|
||||
py::arg("halfedge_tangent"))
|
||||
.def_property_readonly("vert_pos",
|
||||
[](Mesh &self) {
|
||||
const int numVert = self.vertPos.size();
|
||||
py::array_t<float> vert_pos({numVert, 3});
|
||||
auto vert_pos_view =
|
||||
vert_pos.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numVert; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
vert_pos_view(i, j) = self.vertPos[i][j];
|
||||
return vert_pos;
|
||||
})
|
||||
.def_property_readonly("tri_verts",
|
||||
[](Mesh &self) {
|
||||
const int numTri = self.triVerts.size();
|
||||
py::array_t<int> tri_verts({numTri, 3});
|
||||
auto tri_verts_view =
|
||||
tri_verts.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numTri; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
tri_verts_view(i, j) = self.triVerts[i][j];
|
||||
return tri_verts;
|
||||
})
|
||||
.def_property_readonly(
|
||||
"vert_normal",
|
||||
[](Mesh &self) {
|
||||
const int numVert = self.vertNormal.size();
|
||||
py::array_t<float> vert_normal({numVert, 3});
|
||||
auto vert_normal_view = vert_normal.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numVert; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
vert_normal_view(i, j) = self.vertNormal[i][j];
|
||||
return vert_normal;
|
||||
})
|
||||
.def_property_readonly("halfedge_tangent", [](Mesh &self) {
|
||||
const int numEdge = self.halfedgeTangent.size();
|
||||
py::array_t<float> halfedge_tangent({numEdge, 4});
|
||||
auto halfedge_tangent_view = halfedge_tangent.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numEdge; ++i)
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedge_tangent_view(i, j) = self.halfedgeTangent[i][j];
|
||||
return halfedge_tangent;
|
||||
});
|
||||
}
|
||||
@@ -16,23 +16,31 @@ project(wasm)
|
||||
|
||||
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_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
|
||||
set_source_files_properties(bindings.cpp PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
|
||||
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS})
|
||||
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 -sEXPORT_ES6=1)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS examples;test)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
|
||||
|
||||
# ensure that interface files are copied over when modified
|
||||
add_custom_target(js_deps ALL
|
||||
DEPENDS manifoldjs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)
|
||||
|
||||
add_custom_command(
|
||||
TARGET manifoldjs POST_BUILD
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold-*.d.ts
|
||||
${CMAKE_CURRENT_BINARY_DIR}/examples/)
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
|
||||
|
||||
add_custom_command(
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
|
||||
|
||||
@@ -20,7 +20,11 @@ The most significant contribution here is a guaranteed-manifold [mesh Boolean](h
|
||||
|
||||
## Note on memory management
|
||||
|
||||
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each manifold object constructed by your scripts, see https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287.
|
||||
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each manifold object constructed by your scripts, see [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
|
||||
|
||||
## Examples
|
||||
|
||||
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
|
||||
## About the author
|
||||
|
||||
|
||||
@@ -14,159 +14,18 @@
|
||||
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "helpers.cpp"
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
#include <manifold.h>
|
||||
#include <sdf.h>
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::Subtract);
|
||||
}
|
||||
|
||||
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::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<glm::vec2> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = polygons[i][j];
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return simplePolygons;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
|
||||
MeshGL mesh = manifold.GetMeshGL(normalIdx);
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("numProp", mesh.numProp);
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertProperties",
|
||||
val(typed_memory_view(mesh.vertProperties.size(),
|
||||
mesh.vertProperties.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
|
||||
mesh.mergeFromVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
|
||||
mesh.mergeToVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runIndex",
|
||||
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
|
||||
mesh.runOriginalID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("faceID",
|
||||
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
|
||||
mesh.runTransform.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.numProp = mesh["numProp"].as<int>();
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertProperties =
|
||||
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
|
||||
if (mesh["mergeFromVert"] != val::undefined()) {
|
||||
out.mergeFromVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
|
||||
}
|
||||
if (mesh["mergeToVert"] != val::undefined()) {
|
||||
out.mergeToVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
|
||||
}
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["runOriginalID"] != val::undefined()) {
|
||||
out.runOriginalID =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
if (mesh["runTransform"] != val::undefined()) {
|
||||
out.runTransform =
|
||||
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Manifold FromMeshJS(const val& mesh) { return Manifold(MeshJS2GL(mesh)); }
|
||||
|
||||
Manifold Smooth(const val& mesh,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {}) {
|
||||
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
|
||||
}
|
||||
|
||||
Manifold Extrude(std::vector<std::vector<glm::vec2>>& polygons, float height,
|
||||
int nDivisions, float twistDegrees, glm::vec2 scaleTop) {
|
||||
return Manifold::Extrude(ToPolygon(polygons), height, nDivisions,
|
||||
twistDegrees, scaleTop);
|
||||
}
|
||||
|
||||
Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
|
||||
int circularSegments) {
|
||||
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat4x3 matrix;
|
||||
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);
|
||||
}
|
||||
|
||||
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
|
||||
return manifold.Warp(f);
|
||||
}
|
||||
|
||||
Manifold LevelSetJs(uintptr_t funcPtr, Box bounds, float edgeLength,
|
||||
float level) {
|
||||
float (*f)(const glm::vec3&) =
|
||||
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
|
||||
Mesh m = LevelSet(f, bounds, edgeLength, level);
|
||||
return Manifold(m);
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(whatever) {
|
||||
value_object<glm::vec2>("vec2")
|
||||
.field("x", &glm::vec2::x)
|
||||
@@ -201,8 +60,21 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength);
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
enum_<CrossSection::FillRule>("fillrule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
|
||||
.value("NonZero", CrossSection::FillRule::NonZero)
|
||||
.value("Positive", CrossSection::FillRule::Positive)
|
||||
.value("Negative", CrossSection::FillRule::Negative);
|
||||
|
||||
enum_<CrossSection::JoinType>("jointype")
|
||||
.value("Square", CrossSection::JoinType::Square)
|
||||
.value("Round", CrossSection::JoinType::Round)
|
||||
.value("Miter", CrossSection::JoinType::Miter);
|
||||
|
||||
value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
|
||||
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
value_object<Smoothness>("smoothness")
|
||||
@@ -213,35 +85,64 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
value_object<Curvature>("curvature")
|
||||
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
|
||||
.field("minMeanCurvature", &Curvature::minMeanCurvature)
|
||||
.field("maxGaussianCurvature", &Curvature::maxGaussianCurvature)
|
||||
.field("minGaussianCurvature", &Curvature::minGaussianCurvature)
|
||||
.field("vertMeanCurvature", &Curvature::vertMeanCurvature)
|
||||
.field("vertGaussianCurvature", &Curvature::vertGaussianCurvature);
|
||||
|
||||
register_vector<glm::ivec3>("Vector_ivec3");
|
||||
register_vector<glm::vec3>("Vector_vec3");
|
||||
register_vector<glm::vec2>("Vector_vec2");
|
||||
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<CrossSection>("Vector_crossSection");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
|
||||
class_<CrossSection>("CrossSection")
|
||||
.constructor(&cross_js::OfPolygons)
|
||||
.function("_add", &cross_js::Union)
|
||||
.function("_subtract", &cross_js::Difference)
|
||||
.function("_intersect", &cross_js::Intersection)
|
||||
.function("_Warp", &cross_js::Warp)
|
||||
.function("transform", &cross_js::Transform)
|
||||
.function("_Translate", &CrossSection::Translate)
|
||||
.function("rotate", &CrossSection::Rotate)
|
||||
.function("_Scale", &CrossSection::Scale)
|
||||
.function("_Mirror", &CrossSection::Mirror)
|
||||
.function("_Decompose", &CrossSection::Decompose)
|
||||
.function("isEmpty", &CrossSection::IsEmpty)
|
||||
.function("area", &CrossSection::Area)
|
||||
.function("numVert", &CrossSection::NumVert)
|
||||
.function("numContour", &CrossSection::NumContour)
|
||||
.function("_Bounds", &CrossSection::Bounds)
|
||||
.function("simplify", &CrossSection::Simplify)
|
||||
.function("_Offset", &cross_js::Offset)
|
||||
.function("_ToPolygons", &CrossSection::ToPolygons)
|
||||
.function("hull",
|
||||
select_overload<CrossSection() const>(&CrossSection::Hull));
|
||||
|
||||
// CrossSection Static Methods
|
||||
function("_Square", &CrossSection::Square);
|
||||
function("_Circle", &CrossSection::Circle);
|
||||
function("_crossSectionCompose", &CrossSection::Compose);
|
||||
function("_crossSectionUnionN", &cross_js::UnionN);
|
||||
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
|
||||
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
.constructor(&FromMeshJS)
|
||||
.function("add", &Union)
|
||||
.function("subtract", &Difference)
|
||||
.function("intersect", &Intersection)
|
||||
.function("_GetMeshJS", &GetMeshJS)
|
||||
.constructor(&man_js::FromMeshJS)
|
||||
.function("add", &man_js::Union)
|
||||
.function("subtract", &man_js::Difference)
|
||||
.function("intersect", &man_js::Intersection)
|
||||
.function("_Split", &man_js::Split)
|
||||
.function("_SplitByPlane", &man_js::SplitByPlane)
|
||||
.function("_TrimByPlane", &Manifold::TrimByPlane)
|
||||
.function("_GetMeshJS", &js::GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("_Warp", &Warp)
|
||||
.function("_Transform", &Transform)
|
||||
.function("_Warp", &man_js::Warp)
|
||||
.function("_SetProperties", &man_js::SetProperties)
|
||||
.function("transform", &man_js::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)
|
||||
@@ -249,31 +150,36 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
.function("numEdge", &Manifold::NumEdge)
|
||||
.function("numTri", &Manifold::NumTri)
|
||||
.function("numProp", &Manifold::NumProp)
|
||||
.function("numPropVert", &Manifold::NumPropVert)
|
||||
.function("_boundingBox", &Manifold::BoundingBox)
|
||||
.function("precision", &Manifold::Precision)
|
||||
.function("genus", &Manifold::Genus)
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("_getCurvature", &Manifold::GetCurvature)
|
||||
.function("calculateCurvature", &Manifold::CalculateCurvature)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
// Manifold Static Methods
|
||||
function("_Cube", &Manifold::Cube);
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
function("_Sphere", &Manifold::Sphere);
|
||||
function("tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &Smooth);
|
||||
function("_Extrude", &Extrude);
|
||||
function("_Revolve", &Revolve);
|
||||
function("_LevelSet", &LevelSetJs);
|
||||
function("_Tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &js::Smooth);
|
||||
function("_Extrude", &Manifold::Extrude);
|
||||
function("_Triangulate", &Triangulate);
|
||||
function("_Revolve", &Manifold::Revolve);
|
||||
function("_LevelSet", &man_js::LevelSet);
|
||||
function("_Merge", &js::Merge);
|
||||
function("_ReserveIDs", &Manifold::ReserveIDs);
|
||||
function("_manifoldCompose", &Manifold::Compose);
|
||||
function("_manifoldUnionN", &man_js::UnionN);
|
||||
function("_manifoldDifferenceN", &man_js::DifferenceN);
|
||||
function("_manifoldIntersectionN", &man_js::IntersectionN);
|
||||
|
||||
function("_unionN", &UnionN);
|
||||
function("_differenceN", &DifferenceN);
|
||||
function("_intersectionN", &IntersectionN);
|
||||
function("_Compose", &Manifold::Compose);
|
||||
|
||||
function("setMinCircularAngle", &Manifold::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Manifold::SetCircularSegments);
|
||||
function("getCircularSegments", &Manifold::GetCircularSegments);
|
||||
function("reserveIDs", &Manifold::ReserveIDs);
|
||||
// Quality Globals
|
||||
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Quality::SetCircularSegments);
|
||||
function("getCircularSegments", &Quality::GetCircularSegments);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ Module.setup = function() {
|
||||
if (_ManifoldInitialized) return;
|
||||
_ManifoldInitialized = true;
|
||||
|
||||
// conversion utilities
|
||||
|
||||
function toVec(vec, list, f = x => x) {
|
||||
if (list) {
|
||||
for (let x of list) {
|
||||
@@ -33,6 +35,21 @@ Module.setup = function() {
|
||||
return result;
|
||||
}
|
||||
|
||||
function vec2polygons(vec, f = x => x) {
|
||||
const result = [];
|
||||
const nPoly = vec.size();
|
||||
for (let i = 0; i < nPoly; i++) {
|
||||
const v = vec.get(i);
|
||||
const nPts = v.size();
|
||||
const poly = [];
|
||||
for (let j = 0; j < nPts; j++) {
|
||||
poly.push(f(v.get(j)));
|
||||
}
|
||||
result.push(poly);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function polygons2vec(polygons) {
|
||||
if (polygons[0].length < 3) {
|
||||
polygons = [polygons];
|
||||
@@ -50,7 +67,15 @@ Module.setup = function() {
|
||||
polygonsVec.delete();
|
||||
}
|
||||
|
||||
function vararg2vec(vec) {
|
||||
function vararg2vec2(vec) {
|
||||
if (vec[0] instanceof Array) return {x: vec[0][0], y: vec[0][1]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
// default to 0
|
||||
return {x: vec[0] || 0, y: vec[1] || 0};
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
function vararg2vec3(vec) {
|
||||
if (vec[0] instanceof Array)
|
||||
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
@@ -59,6 +84,118 @@ Module.setup = function() {
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
function fillRuleToInt(fillRule) {
|
||||
return fillRule == 'EvenOdd' ? 0 :
|
||||
fillRule == 'NonZero' ? 1 :
|
||||
fillRule == 'Negative' ? 3 :
|
||||
/* Positive */ 2;
|
||||
}
|
||||
|
||||
function joinTypeToInt(joinType) {
|
||||
return joinType == 'Round' ? 1 : joinType == 'Miter' ? 2 : /* Square */ 0;
|
||||
}
|
||||
|
||||
// CrossSection methods
|
||||
|
||||
const CrossSectionCtor = Module.CrossSection;
|
||||
|
||||
function cross(polygons, fillRule = 'Positive') {
|
||||
if (polygons instanceof CrossSectionCtor) {
|
||||
return polygons;
|
||||
} else {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
}
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec2(vec));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.scale = function(vec) {
|
||||
// if only one factor provided, scale both x and y with it
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec2([vec]));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec2([vec]));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec2Ptr) {
|
||||
const x = getValue(vec2Ptr, 'float');
|
||||
const y = getValue(vec2Ptr + 4, 'float');
|
||||
const vert = [x, y];
|
||||
func(vert);
|
||||
setValue(vec2Ptr, vert[0], 'float');
|
||||
setValue(vec2Ptr + 4, vert[1], 'float');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.bounds = function() {
|
||||
const result = this._Bounds();
|
||||
return {
|
||||
min: ['x', 'y'].map(f => result.min[f]),
|
||||
max: ['x', 'y'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.offset = function(
|
||||
delta, joinType = 'Square', miterLimit = 2.0, circularSegments = 0) {
|
||||
return this._Offset(
|
||||
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.extrude = function(
|
||||
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
|
||||
center = false) {
|
||||
scaleTop = vararg2vec2([scaleTop]);
|
||||
const man =
|
||||
Module._Extrude(this, height, nDivisions, twistDegrees, scaleTop);
|
||||
return (center ? man.translate([0., 0., -height / 2.]) : man);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.revolve = function(
|
||||
circularSegments = 0, revolveDegrees = 360.0) {
|
||||
return Module._Revolve(this, circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.add = function(other) {
|
||||
return this._add(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.subtract = function(other) {
|
||||
return this._subtract(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.intersect = function(other) {
|
||||
return this._intersect(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.toPolygons = function() {
|
||||
const vec = this._ToPolygons();
|
||||
const result = vec2polygons(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
// Manifold methods
|
||||
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
@@ -72,11 +209,43 @@ Module.setup = function() {
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
|
||||
const status = out.status();
|
||||
if (status.value !== 0) {
|
||||
throw new Module.ManifoldError(status.value);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.setProperties = function(numProp, func) {
|
||||
const oldNumProp = this.numProp();
|
||||
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
|
||||
const newProp = [];
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
newProp[i] = getValue(newPtr + 4 * i, 'float');
|
||||
}
|
||||
const pos = [];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
pos[i] = getValue(vec3Ptr + 4 * i, 'float');
|
||||
}
|
||||
const oldProp = [];
|
||||
for (let i = 0; i < oldNumProp; ++i) {
|
||||
oldProp[i] = getValue(oldPtr + 4 * i, 'float');
|
||||
}
|
||||
|
||||
func(newProp, pos, oldProp);
|
||||
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
setValue(newPtr + 4 * i, newProp[i], 'float');
|
||||
}
|
||||
}, 'viii');
|
||||
const out = this._SetProperties(numProp, wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec(vec));
|
||||
return this._Translate(vararg2vec3(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function(vec) {
|
||||
@@ -84,10 +253,33 @@ Module.setup = function() {
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
// if only one factor provided, scale all three dimensions (xyz) with it
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec, z: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec([vec]));
|
||||
return this._Scale(vararg2vec3([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec3([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.trimByPlane = function(normal, offset = 0.) {
|
||||
return this._TrimByPlane(vararg2vec3([normal]), offset);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.split = function(manifold) {
|
||||
const vec = this._split(manifold);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
|
||||
const vec = this._splitByPlane(vararg2vec3([normal]), offset);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function() {
|
||||
@@ -97,15 +289,12 @@ Module.setup = function() {
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getCurvature = function() {
|
||||
const result = this._getCurvature();
|
||||
const oldMeanCurvature = result.vertMeanCurvature;
|
||||
const oldGaussianCurvature = result.vertGaussianCurvature;
|
||||
result.vertMeanCurvature = fromVec(oldMeanCurvature);
|
||||
result.vertGaussianCurvature = fromVec(oldGaussianCurvature);
|
||||
oldMeanCurvature.delete();
|
||||
oldGaussianCurvature.delete();
|
||||
return result;
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
const result = this._boundingBox();
|
||||
return {
|
||||
min: ['x', 'y', 'z'].map(f => result.min[f]),
|
||||
max: ['x', 'y', 'z'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
@@ -145,6 +334,12 @@ Module.setup = function() {
|
||||
return this.runOriginalID.length;
|
||||
}
|
||||
|
||||
merge() {
|
||||
const {changed, mesh} = Module._Merge(this);
|
||||
Object.assign(this, {...mesh});
|
||||
return changed;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
|
||||
}
|
||||
@@ -182,14 +377,6 @@ Module.setup = function() {
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
const result = this._boundingBox();
|
||||
return {
|
||||
min: ['x', 'y', 'z'].map(f => result.min[f]),
|
||||
max: ['x', 'y', 'z'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
@@ -222,6 +409,8 @@ Module.setup = function() {
|
||||
break;
|
||||
case Module.status.FaceIDWrongLength.value:
|
||||
message = 'Face ID vector has wrong length';
|
||||
case Module.status.InvalidConstruction.value:
|
||||
message = 'Manifold constructed with invalid parameters';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -236,6 +425,65 @@ Module.setup = function() {
|
||||
{value: Module.ManifoldError, writable: true, configurable: true}
|
||||
});
|
||||
|
||||
// CrossSection Constructors
|
||||
|
||||
Module.CrossSection = function(polygons, fillRule = 'Positive') {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
};
|
||||
|
||||
Module.CrossSection.ofPolygons = function(polygons, fillRule = 'Positive') {
|
||||
return new Module.CrossSection(polygons, fillRule);
|
||||
};
|
||||
|
||||
Module.CrossSection.square = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = {x: 1, y: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = {x: args[0], y: args[0]};
|
||||
else
|
||||
size = vararg2vec2(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Square(size, center);
|
||||
};
|
||||
|
||||
Module.CrossSection.circle = function(radius, circularSegments = 0) {
|
||||
return Module._Circle(radius, circularSegments);
|
||||
};
|
||||
|
||||
// allows args to be either CrossSection or polygons (constructed with
|
||||
// Positive fill)
|
||||
function crossSectionBatchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_crossSection();
|
||||
for (const cs of args) v.push_back(cross(cs));
|
||||
const result = Module['_crossSection' + name](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Module.CrossSection.compose = crossSectionBatchbool('Compose');
|
||||
Module.CrossSection.union = crossSectionBatchbool('UnionN');
|
||||
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
|
||||
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
|
||||
|
||||
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
|
||||
|
||||
// Because the constructor and prototype are being replaced, instanceof will
|
||||
// not work as desired unless we refer back to the original like this
|
||||
Object.defineProperty(Module.CrossSection, Symbol.hasInstance, {
|
||||
get: () => (t) => {
|
||||
return (t instanceof CrossSectionCtor);
|
||||
}
|
||||
});
|
||||
|
||||
// Manifold Constructors
|
||||
|
||||
const ManifoldCtor = Module.Manifold;
|
||||
Module.Manifold = function(mesh) {
|
||||
const manifold = new ManifoldCtor(mesh);
|
||||
@@ -248,32 +496,38 @@ Module.setup = function() {
|
||||
return manifold;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
Module.Manifold.ofMesh = function(mesh) {
|
||||
return new Module.Manifold(mesh);
|
||||
};
|
||||
|
||||
Module.cube = function(...args) {
|
||||
Module.Manifold.tetrahedron = function() {
|
||||
return Module._Tetrahedron();
|
||||
};
|
||||
|
||||
Module.Manifold.cube = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = {x: 1, y: 1, z: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = {x: args[0], y: args[0], z: args[0]};
|
||||
else
|
||||
size = vararg2vec(args);
|
||||
size = vararg2vec3(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Cube(size, center);
|
||||
};
|
||||
|
||||
Module.cylinder = function(
|
||||
Module.Manifold.cylinder = function(
|
||||
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
|
||||
center = false) {
|
||||
return Module._Cylinder(
|
||||
height, radiusLow, radiusHigh, circularSegments, center);
|
||||
};
|
||||
|
||||
Module.sphere = function(radius, circularSegments = 0) {
|
||||
Module.Manifold.sphere = function(radius, circularSegments = 0) {
|
||||
return Module._Sphere(radius, circularSegments);
|
||||
};
|
||||
|
||||
Module.smooth = function(mesh, sharpenedEdges = []) {
|
||||
Module.Manifold.smooth = function(mesh, sharpenedEdges = []) {
|
||||
const sharp = new Module.Vector_smoothness();
|
||||
toVec(sharp, sharpenedEdges);
|
||||
const result = Module._Smooth(mesh, sharp);
|
||||
@@ -281,33 +535,51 @@ Module.setup = function() {
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.extrude = function(
|
||||
Module.Manifold.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);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
scaleTop = [1.0, 1.0], center = false) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
|
||||
};
|
||||
|
||||
Module.revolve = function(polygons, circularSegments = 0) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Revolve(polygonsVec, circularSegments);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
Module.Manifold.revolve = function(
|
||||
polygons, circularSegments = 0, revolveDegrees = 360.0) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.revolve(circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.compose = function(manifolds) {
|
||||
Module.Manifold.reserveIDs = function(n) {
|
||||
return Module._ReserveIDs(n);
|
||||
};
|
||||
|
||||
Module.Manifold.compose = function(manifolds) {
|
||||
const vec = new Module.Vector_manifold();
|
||||
toVec(vec, manifolds);
|
||||
const result = Module._Compose(vec);
|
||||
const result = Module._manifoldCompose(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
|
||||
function manifoldBatchbool(name) {
|
||||
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);
|
||||
const result = Module['_manifold' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Module.Manifold.union = manifoldBatchbool('Union');
|
||||
Module.Manifold.difference = manifoldBatchbool('Difference');
|
||||
Module.Manifold.intersection = manifoldBatchbool('Intersection');
|
||||
|
||||
Module.Manifold.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]},
|
||||
@@ -324,18 +596,23 @@ Module.setup = function() {
|
||||
return out;
|
||||
};
|
||||
|
||||
function batchbool(name) {
|
||||
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);
|
||||
const result = Module['_' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
|
||||
Module.union = batchbool('union');
|
||||
Module.difference = batchbool('difference');
|
||||
Module.intersection = batchbool('intersection');
|
||||
// Because the constructor and prototype are being replaced, instanceof will
|
||||
// not work as desired unless we refer back to the original like this
|
||||
Object.defineProperty(Module.Manifold, Symbol.hasInstance, {
|
||||
get: () => (t) => {
|
||||
return (t instanceof ManifoldCtor);
|
||||
}
|
||||
});
|
||||
|
||||
// Top-level functions
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# WASM/JS Examples
|
||||
|
||||
This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, `<model-viewer>`, and `glTF`. Included are `manifold-gltf.js` and `gltf-io.js`, which are intended to be fairly general-purpose libraries for interfacing between manifolds and glTF. We should probably make them into their own npm package at some point.
|
||||
|
||||
## Local development
|
||||
|
||||
First, follow the directions in the root README to get your C++ build environment set up and working for WASM. From this directory (`bindings/wasm/examples/`) you can test the JS bindings by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
To develop the manifoldCAD.org editor as well as our other example pages, run
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
See `package.json` for other useful scripts.
|
||||
|
||||
Note that the `emcmake` command automatically copies your WASM build into `built/`, (here, not just under the `buildWASM` directory) which is then packaged by Vite into `dist/assets/`.
|
||||
|
||||
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
|
||||
```
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
|
||||
```
|
||||
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
|
||||
|
||||
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
|
||||
deployed version) note that it uses a service worker for faster loading. This
|
||||
means you need to open the page twice to see updates (the first time loads the
|
||||
old version and caches the new one, the second time loads the new version from
|
||||
cache). To see changes on each reload, open Chrome dev tools, go to the
|
||||
Application tab and check "update on reload".
|
||||
|
||||
### Note for firefox users
|
||||
|
||||
To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository.
|
||||
@@ -180,19 +180,19 @@ model-viewer {
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background-image: url(icons/close.png);
|
||||
background-image: url(/icons/close.png);
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.edit {
|
||||
background-image: url(icons/pencil.png);
|
||||
background-image: url(/icons/pencil.png);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
background-image: url(icons/trash.png);
|
||||
background-image: url(/icons/trash.png);
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {examples} from './examples.js';
|
||||
const exampleFunctions = examples.functionBodies;
|
||||
import ManifoldWorker from './worker?worker';
|
||||
|
||||
// Loaded globally by examples.js
|
||||
const exampleFunctions = self.examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register('service-worker.js');
|
||||
navigator.serviceWorker.register(
|
||||
'/service-worker.js', {scope: './index.html'});
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
@@ -98,6 +101,10 @@ function createDropdownItem(name) {
|
||||
saveCurrent();
|
||||
switchTo(label.textContent);
|
||||
};
|
||||
// Stop text input spaces from triggering the button
|
||||
button.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -215,50 +222,44 @@ function initializeRun() {
|
||||
let tsWorker = undefined;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('manifold-global-types.d.ts')
|
||||
const global = await fetch('/manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('manifold-encapsulated-types.d.ts')
|
||||
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 interface ManifoldToplevel {
|
||||
CrossSection: typeof T.CrossSection;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
triangulate: typeof T.triangulate;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
setup: () => void;
|
||||
}
|
||||
declare const module: ManifoldStatic;
|
||||
declare const module: ManifoldToplevel;
|
||||
`;
|
||||
}
|
||||
|
||||
async function getEditorDTS() {
|
||||
const global = await fetch('/editor.d.ts').then(response => response.text());
|
||||
return `${global.replace(/^import.*$/gm, '')}`;
|
||||
}
|
||||
|
||||
require.config({
|
||||
paths:
|
||||
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
|
||||
});
|
||||
require(['vs/editor/editor.main'], async function() {
|
||||
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');
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getEditorDTS());
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
@@ -350,7 +351,7 @@ let objectURL = null;
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new Worker('worker.js', {type: 'module'});
|
||||
manifoldWorker = new ManifoldWorker();
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
@@ -368,7 +369,6 @@ function createWorker() {
|
||||
|
||||
finishRun();
|
||||
runButton.disabled = true;
|
||||
setScript('safe', 'true');
|
||||
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
@@ -376,6 +376,9 @@ function createWorker() {
|
||||
if (objectURL == null) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
createWorker();
|
||||
} else {
|
||||
setScript('safe', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
// 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 {Accessor} from '@gltf-transform/core';
|
||||
|
||||
import {EXTManifold} from './manifold-gltf.js';
|
||||
|
||||
export const attributeDefs = {
|
||||
'POSITION': {type: Accessor.Type.VEC3, components: 3},
|
||||
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
|
||||
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
|
||||
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
|
||||
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
|
||||
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
|
||||
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'SKIP_1': {type: null, components: 1},
|
||||
'SKIP_2': {type: null, components: 2},
|
||||
'SKIP_3': {type: null, components: 3},
|
||||
'SKIP_4': {type: null, components: 4},
|
||||
};
|
||||
|
||||
export function setupIO(io) {
|
||||
return io.registerExtensions([EXTManifold]);
|
||||
}
|
||||
|
||||
function readPrimitive(primitive, numProp, attributes) {
|
||||
const position = primitive.getAttribute('POSITION');
|
||||
const numVert = position.getCount();
|
||||
const vertProperties = [];
|
||||
let offset = 0;
|
||||
for (const attribute of attributes) {
|
||||
const size = attributeDefs[attribute].components;
|
||||
if (attribute === 'SKIP') {
|
||||
offset += size;
|
||||
continue;
|
||||
}
|
||||
const accessor = primitive.getAttribute(attribute);
|
||||
if (accessor) {
|
||||
const array = accessor.getArray();
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = array[i * size + j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
return vertProperties;
|
||||
}
|
||||
|
||||
export function readMesh(mesh, attributes, materials) {
|
||||
const primitives = mesh.listPrimitives();
|
||||
if (primitives.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (attributes.length === 0) {
|
||||
const attributeSet = new Set();
|
||||
for (const primitive of primitives) {
|
||||
const semantics = primitive.listSemantics();
|
||||
for (const semantic of semantics) {
|
||||
attributeSet.add(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic in attributeDefs) {
|
||||
if (attributeSet.has(semantic)) {
|
||||
attributes.push(semantic);
|
||||
attributeSet.delete(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic of attributeSet.keys()) {
|
||||
attributes.push(semantic);
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes.length < 1 || attributes[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
const numProp = attributes.map((def) => attributeDefs[def].components)
|
||||
.reduce((a, b) => a + b);
|
||||
|
||||
const manifoldPrimitive = mesh.getExtension('EXT_manifold');
|
||||
|
||||
let vertPropArray = [];
|
||||
let triVertArray = [];
|
||||
const runIndexArray = [0];
|
||||
const mergeFromVert = [];
|
||||
const mergeToVert = [];
|
||||
if (manifoldPrimitive != null) {
|
||||
vertPropArray = readPrimitive(primitives[0], numProp, attributes);
|
||||
for (const primitive of primitives) {
|
||||
triVertArray = [...triVertArray, ...primitive.getIndices().getArray()];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
materials.push(primitive.getMaterial());
|
||||
}
|
||||
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
|
||||
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
|
||||
const vert2merge = new Map();
|
||||
for (const [i, idx] of mergeTriVert.entries()) {
|
||||
vert2merge.set(triVertArray[idx], mergeTo[i]);
|
||||
}
|
||||
for (const [from, to] of vert2merge.entries()) {
|
||||
mergeFromVert.push(from);
|
||||
mergeToVert.push(to);
|
||||
}
|
||||
} else {
|
||||
for (const primitive of primitives) {
|
||||
const numVert = vertPropArray.length / numProp;
|
||||
vertPropArray =
|
||||
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
|
||||
triVertArray = [
|
||||
...triVertArray,
|
||||
...primitive.getIndices().getArray().map((i) => i + numVert)
|
||||
];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
materials.push(primitive.getMaterial());
|
||||
}
|
||||
}
|
||||
const vertProperties = new Float32Array(vertPropArray);
|
||||
const triVerts = new Uint32Array(triVertArray);
|
||||
const runIndex = new Uint32Array(runIndexArray);
|
||||
|
||||
return {
|
||||
numProp,
|
||||
triVerts,
|
||||
vertProperties,
|
||||
runIndex,
|
||||
mergeFromVert,
|
||||
mergeToVert
|
||||
};
|
||||
}
|
||||
|
||||
export function writeMesh(doc, manifoldMesh, attributes, materials) {
|
||||
if (doc.getRoot().listBuffers().length === 0) {
|
||||
doc.createBuffer();
|
||||
}
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
|
||||
const attributeUnion = [];
|
||||
for (const matAttributes of attributes) {
|
||||
matAttributes.forEach((attribute, i) => {
|
||||
if (i >= attributeUnion.length) {
|
||||
attributeUnion.push(attribute);
|
||||
} else {
|
||||
const size = attributeDefs[attribute].components;
|
||||
const unionSize = attributeDefs[attributeUnion[i]].components;
|
||||
if (size != unionSize) {
|
||||
throw new Error(
|
||||
'Attribute sizes do not correspond: ', attribute, ' and ',
|
||||
attributeUnion[i]);
|
||||
}
|
||||
if (attributeDefs[attributeUnion[i]].type == null) {
|
||||
attributeUnion[i] = attribute;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (attributeUnion.length < 1 || attributeUnion[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
const mesh = doc.createMesh();
|
||||
const numPrimitive = manifoldMesh.runIndex.length - 1;
|
||||
for (let run = 0; run < numPrimitive; ++run) {
|
||||
const indices = doc.createAccessor('indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array());
|
||||
const primitive = doc.createPrimitive().setIndices(indices);
|
||||
const material = materials[run];
|
||||
if (material) {
|
||||
primitive.setMaterial(material);
|
||||
}
|
||||
mesh.addPrimitive(primitive);
|
||||
}
|
||||
|
||||
const numVert = manifoldMesh.numVert;
|
||||
const numProp = manifoldMesh.numProp;
|
||||
let offset = 0;
|
||||
attributeUnion.forEach((attribute, aIdx) => {
|
||||
if (attribute === 'SKIP') {
|
||||
++offset;
|
||||
return;
|
||||
}
|
||||
const def = attributeDefs[attribute];
|
||||
if (def == null)
|
||||
throw new Error(attribute + ' is not a recognized attribute.');
|
||||
|
||||
const n = def.components;
|
||||
if (offset + n > numProp) throw new Error('Too many attribute channels.');
|
||||
|
||||
const array = new Float32Array(n * numVert);
|
||||
for (let v = 0; v < numVert; ++v) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
array[n * v + i] =
|
||||
manifoldMesh.vertProperties[numProp * v + offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
const accessor =
|
||||
doc.createAccessor().setBuffer(buffer).setType(def.type).setArray(
|
||||
array);
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, pIdx) => {
|
||||
if (attributes[pIdx].length > aIdx &&
|
||||
attributeDefs[attributes[pIdx][aIdx]].type != null) {
|
||||
primitive.setAttribute(attribute, accessor);
|
||||
}
|
||||
});
|
||||
offset += n;
|
||||
});
|
||||
|
||||
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
|
||||
mesh.setExtension('EXT_manifold', manifoldPrimitive);
|
||||
|
||||
const indices = doc.createAccessor('indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts);
|
||||
manifoldPrimitive.setIndices(indices);
|
||||
manifoldPrimitive.setRunIndex(manifoldMesh.runIndex);
|
||||
|
||||
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
|
||||
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
|
||||
vert2merge[from] = manifoldMesh.mergeToVert[i];
|
||||
}
|
||||
const ind = [];
|
||||
const val = [];
|
||||
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
|
||||
const newVert = vert2merge[vert];
|
||||
if (vert !== newVert) {
|
||||
ind.push(i);
|
||||
val.push(newVert);
|
||||
}
|
||||
}
|
||||
const indicesAccessor = doc.createAccessor()
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(ind));
|
||||
const valuesAccessor = doc.createAccessor()
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(val));
|
||||
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
export function disposeMesh(mesh) {
|
||||
if (!mesh) return;
|
||||
const primitives = mesh.listPrimitives();
|
||||
for (const primitive of primitives) {
|
||||
primitive.getIndices()?.dispose();
|
||||
for (const accessor of primitive.listAttributes()) {
|
||||
accessor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
export async function loadTexture(texture, uri) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
@@ -29,6 +29,11 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
self.ModelViewerElement = self.ModelViewerElement || {};
|
||||
self.ModelViewerElement.modelCacheSize = 0;
|
||||
</script>
|
||||
<script type="module" src="/examples.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -67,6 +72,6 @@
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
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>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,189 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Make manifold glTF</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" />
|
||||
<style>
|
||||
body {
|
||||
background-color: #f7f7f7;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(0, 0, 0, .87);
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 700px;
|
||||
margin: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
model-viewer {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
border: 3px solid black;
|
||||
border-radius: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#poster {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Load a glTF/GLB model and our <a href="https://github.com/elalish/manifold">Manifold</a> library will attempt to
|
||||
merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have
|
||||
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_manifold</a> extension, thus preserving the
|
||||
manifold data without losing any mesh properties.</p>
|
||||
<p> If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a
|
||||
solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the
|
||||
download button is still disabled, it means there were no manifold meshes found. </p>
|
||||
<input type="file" id="input">
|
||||
<button id="download" disabled>Download manifold GLB</button>
|
||||
<input type="checkbox" id="viewFinal" disabled>
|
||||
<label for="viewFinal">View Manifold GLB</label>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Loaded GLB model">
|
||||
<span id="poster" slot="poster">Drop a GLB here</span>
|
||||
</model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { WebIO } from '@gltf-transform/core';
|
||||
import { prune } from '@gltf-transform/functions';
|
||||
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
|
||||
import { SimpleDropzone } from 'simple-dropzone';
|
||||
import { readMesh, writeMesh, setupIO } from './gltf-io.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const inputEl = document.querySelector('#input');
|
||||
const downloadButton = document.querySelector('#download');
|
||||
const checkbox = document.querySelector('#viewFinal');
|
||||
const dropCtrl = new SimpleDropzone(mv, inputEl);
|
||||
|
||||
let inputGLBurl = null;
|
||||
let outputGLBurl = null;
|
||||
let allManifold = true;
|
||||
let anyManifold = false;
|
||||
|
||||
dropCtrl.on('drop', async ({ files }) => {
|
||||
for (const [path, file] of files) {
|
||||
const filename = file.name.toLowerCase();
|
||||
if (filename.match(/\.(gltf|glb)$/)) {
|
||||
URL.revokeObjectURL(inputGLBurl);
|
||||
inputGLBurl = URL.createObjectURL(file);
|
||||
await writeGLB(await readGLB(inputGLBurl));
|
||||
updateUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateUI() {
|
||||
if (allManifold) {
|
||||
checkbox.checked = true;
|
||||
checkbox.disabled = true;
|
||||
} else if (anyManifold) {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = false;
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
onClick();
|
||||
}
|
||||
|
||||
checkbox.onclick = onClick;
|
||||
|
||||
function onClick() {
|
||||
mv.src = checkbox.checked ? outputGLBurl : inputGLBurl;
|
||||
downloadButton.disabled = !checkbox.checked;
|
||||
};
|
||||
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = outputGLBurl;
|
||||
link.click();
|
||||
};
|
||||
|
||||
async function writeGLB(doc) {
|
||||
URL.revokeObjectURL(outputGLBurl);
|
||||
if (!anyManifold) {
|
||||
return;
|
||||
}
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
outputGLBurl = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
async function readGLB(url) {
|
||||
allManifold = false;
|
||||
anyManifold = false;
|
||||
updateUI();
|
||||
allManifold = true;
|
||||
const docIn = await io.read(url);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const attributes = [];
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const materials = [];
|
||||
const tmpMesh = readMesh(mesh, attributes, materials);
|
||||
tmpMesh.runOriginalID = [];
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
for (let i = 0; i < materials.length; ++i) {
|
||||
tmpMesh.runOriginalID.push(firstID + i);
|
||||
}
|
||||
const manifoldMesh = new Mesh(tmpMesh);
|
||||
mesh.dispose();
|
||||
|
||||
const matAttributes = [];
|
||||
materials.forEach(() => matAttributes.push(attributes));
|
||||
|
||||
manifoldMesh.merge();
|
||||
|
||||
try {
|
||||
// Test manifoldness - will throw if not.
|
||||
const manifold = Manifold(manifoldMesh);
|
||||
node.setMesh(writeMesh(docIn, manifold.getMesh(), matAttributes, materials));
|
||||
manifold.delete();
|
||||
anyManifold = true;
|
||||
} catch (e) {
|
||||
console.log(mesh.getName(), e);
|
||||
allManifold = false;
|
||||
}
|
||||
}
|
||||
|
||||
await docIn.transform(prune());
|
||||
|
||||
return docIn;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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 {Extension, ExtensionProperty, PropertyType} from '@gltf-transform/core';
|
||||
|
||||
const NAME = 'EXT_manifold';
|
||||
|
||||
export class ManifoldPrimitive extends ExtensionProperty {
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
init() {
|
||||
this.EXTENSION_NAME = NAME;
|
||||
this.propertyType = 'ManifoldPrimitive';
|
||||
this.parentTypes = [PropertyType.MESH];
|
||||
}
|
||||
|
||||
getDefaults() {
|
||||
return Object.assign(
|
||||
super.getDefaults(),
|
||||
{manifoldPrimitive: null, mergeIndices: null, mergeValues: null});
|
||||
}
|
||||
|
||||
getMergeIndices() {
|
||||
return this.getRef('mergeIndices');
|
||||
}
|
||||
|
||||
getMergeValues() {
|
||||
return this.getRef('mergeValues');
|
||||
}
|
||||
|
||||
setMerge(indicesAccessor, valuesAccessor) {
|
||||
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
|
||||
throw new Error('merge vectors must be the same length.');
|
||||
this.setRef('mergeIndices', indicesAccessor);
|
||||
return this.setRef('mergeValues', valuesAccessor);
|
||||
}
|
||||
|
||||
getRunIndex() {
|
||||
return this.get('runIndex');
|
||||
}
|
||||
|
||||
setRunIndex(runIndex) {
|
||||
return this.set('runIndex', runIndex);
|
||||
}
|
||||
|
||||
setIndices(indices) {
|
||||
return this.setRef('indices', indices);
|
||||
}
|
||||
|
||||
getIndices() {
|
||||
return this.getRef('indices');
|
||||
}
|
||||
}
|
||||
|
||||
export class EXTManifold extends Extension {
|
||||
extensionName = NAME;
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
createManifoldPrimitive() {
|
||||
return new ManifoldPrimitive(this.document.getGraph());
|
||||
}
|
||||
|
||||
read(context) {
|
||||
const {json} = context.jsonDoc;
|
||||
const meshDefs = json.meshes || [];
|
||||
|
||||
meshDefs.forEach((meshDef, meshIndex) => {
|
||||
if (!meshDef.extensions || !meshDef.extensions[NAME]) return;
|
||||
|
||||
const mesh = context.meshes[meshIndex];
|
||||
const manifoldPrimitive = this.createManifoldPrimitive();
|
||||
mesh.setExtension(NAME, manifoldPrimitive);
|
||||
|
||||
const manifoldDef = meshDef.extensions[NAME];
|
||||
|
||||
if (manifoldDef.manifoldPrimitive) {
|
||||
let count = 0;
|
||||
const runIndex = [];
|
||||
runIndex.push(count);
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
count += primitive.getIndices().getCount();
|
||||
runIndex.push(count);
|
||||
}
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
}
|
||||
|
||||
if (manifoldDef.mergeIndices && manifoldDef.mergeValues) {
|
||||
manifoldPrimitive.setMerge(
|
||||
context.accessors[manifoldDef.mergeIndices],
|
||||
context.accessors[manifoldDef.mergeValues]);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context) {
|
||||
const {json} = context.jsonDoc;
|
||||
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME);
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const meshIndex = context.meshIndexMap.get(mesh);
|
||||
const meshDef = json.meshes[meshIndex];
|
||||
|
||||
const runIndex = manifoldPrimitive.getRunIndex();
|
||||
const numPrimitive = runIndex.length - 1;
|
||||
|
||||
if (numPrimitive !== meshDef.primitives.length) {
|
||||
throw new Error(
|
||||
'The number of primitives must be exactly one less than the length of runIndex.');
|
||||
}
|
||||
|
||||
const mergeIndicesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices());
|
||||
const mergeValuesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues());
|
||||
const mergeIndices = json.accessors[mergeIndicesIndex];
|
||||
const mergeValues = json.accessors[mergeValuesIndex];
|
||||
|
||||
const existingPrimitive = meshDef.primitives[0];
|
||||
const primitive = {
|
||||
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices()),
|
||||
mode: existingPrimitive.mode,
|
||||
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
|
||||
};
|
||||
|
||||
const indices = json.accessors[primitive.indices];
|
||||
if (!indices) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergeIndices && mergeValues) {
|
||||
indices.sparse = {
|
||||
count: mergeIndices.count,
|
||||
indices: {
|
||||
bufferView: mergeIndices.bufferView,
|
||||
byteOffset: mergeIndices.byteOffset,
|
||||
componentType: mergeIndices.componentType
|
||||
},
|
||||
values: {
|
||||
bufferView: mergeValues.bufferView,
|
||||
byteOffset: mergeValues.byteOffset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < numPrimitive; ++i) {
|
||||
const accessor = json.accessors[meshDef.primitives[i].indices];
|
||||
accessor.bufferView = indices.bufferView;
|
||||
accessor.byteOffset = indices.byteOffset + 4 * runIndex[i];
|
||||
accessor.count = runIndex[i + 1] - runIndex[i];
|
||||
}
|
||||
|
||||
meshDef.extensions = meshDef.extensions || {};
|
||||
meshDef.extensions[NAME] = {
|
||||
manifoldPrimitive: primitive,
|
||||
mergeIndices: mergeIndicesIndex,
|
||||
mergeValues: mergeValuesIndex
|
||||
};
|
||||
|
||||
// Test the manifold primitive by replacing the material primitives
|
||||
// meshDef.primitives = [primitive];
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -45,60 +45,66 @@
|
||||
<option value="intersection">Intersection</option>
|
||||
<option value="union">Union</option>
|
||||
</select>
|
||||
<button id="download">Download manifold GLB</button>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { Accessor, Document, WebIO } from 'https://cdn.skypack.dev/pin/@gltf-transform/core@v3.0.0-SfbIFhNPTRdr1UE2VSan/mode=imports,min/optimized/@gltf-transform/core.js';
|
||||
import Module from '../manifold.js';
|
||||
import { Document, WebIO } from '@gltf-transform/core';
|
||||
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
|
||||
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const io = new WebIO();
|
||||
const io = setupIO(new WebIO());
|
||||
const doc = new Document();
|
||||
|
||||
const buffer = doc.createBuffer();
|
||||
const position = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const uv = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC2);
|
||||
const moonIndices = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const spaceIndices = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const moonTexture = doc.createTexture('moon');
|
||||
const spaceTexture = doc.createTexture('space');
|
||||
const moonMaterial = doc.createMaterial('moon').setBaseColorTexture(moonTexture);
|
||||
const spaceMaterial = doc.createMaterial('moon').setBaseColorTexture(spaceTexture);
|
||||
const moonPrimitive = doc.createPrimitive().setMaterial(moonMaterial)
|
||||
.setIndices(moonIndices).setAttribute('POSITION', position).setAttribute('TEXCOORD_0', uv);
|
||||
const spacePrimitive = doc.createPrimitive().setMaterial(spaceMaterial)
|
||||
.setIndices(spaceIndices).setAttribute('POSITION', position).setAttribute('TEXCOORD_0', uv);
|
||||
const mesh = doc.createMesh('result').addPrimitive(moonPrimitive).addPrimitive(spacePrimitive);
|
||||
const node = doc.createNode().setMesh(mesh);
|
||||
const scene = doc.createScene().addChild(node);
|
||||
|
||||
async function loadTexture(texture, uri) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
|
||||
async function loadTextures() {
|
||||
const moonLoads = loadTexture(moonTexture, './moonAltitude.jpg');
|
||||
const spaceLoads = loadTexture(spaceTexture, './hubbleDeepField.jpg');
|
||||
await Promise.all([moonLoads, spaceLoads]);
|
||||
}
|
||||
|
||||
const texturesLoad = loadTextures();
|
||||
const materialMap = new Map();
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const firstID = wasm.reserveIDs(2);
|
||||
const space = cubeUV(firstID).scale(50);
|
||||
const moon = sphereUV(firstID + 1).scale(60);
|
||||
materialMap.set(firstID, spaceIndices);
|
||||
materialMap.set(firstID + 1, moonIndices);
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const csg = function (operation) {
|
||||
push2MV(wasm[operation](space, moon));
|
||||
const attributes = ['POSITION', 'TEXCOORD_0'];
|
||||
const materialMap = new Map();
|
||||
|
||||
async function readGLB(url, attributes, materialMap) {
|
||||
const manifolds = [];
|
||||
const docIn = await io.read(url);
|
||||
await docIn.transform(flatten());
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
const ids = [];
|
||||
for (const node of nodes) {
|
||||
clearNodeTransform(node);
|
||||
const materials = [];
|
||||
const manifoldMesh = readMesh(node.getMesh(), attributes, materials);
|
||||
manifoldMesh.runOriginalID = [];
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
for (let i = 0; i < materials.length; ++i) {
|
||||
manifoldMesh.runOriginalID.push(firstID + i);
|
||||
ids.push(firstID + i);
|
||||
}
|
||||
manifolds.push(Manifold(new Mesh(manifoldMesh)));
|
||||
}
|
||||
// pull in materials, TODO: replace with transfer() when available
|
||||
const startIdx = doc.getRoot().listMaterials().length;
|
||||
doc.merge(docIn);
|
||||
doc.getRoot().listScenes().forEach((s) => s.dispose());
|
||||
doc.getRoot().listBuffers().forEach((s) => s.dispose());
|
||||
for (const [i, id] of ids.entries()) {
|
||||
const material = doc.getRoot().listMaterials()[startIdx + i];
|
||||
materialMap.set(id, material);
|
||||
}
|
||||
|
||||
return Manifold.union(manifolds);
|
||||
}
|
||||
|
||||
const space = await readGLB('/models/space.glb', attributes, materialMap);
|
||||
const moon = await readGLB('/models/moon.glb', attributes, materialMap);
|
||||
|
||||
const node = doc.createNode();
|
||||
doc.createScene().addChild(node);
|
||||
|
||||
function csg(operation) {
|
||||
push2MV(Manifold[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
@@ -110,30 +116,28 @@
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
for (const [i, id] of mesh.runOriginalID.entries()) {
|
||||
const indices = materialMap.get(id);
|
||||
indices.setArray(mesh.triVerts.subarray(mesh.runIndex[i], mesh.runIndex[i + 1]));
|
||||
disposeMesh(node.getMesh());
|
||||
const materials = [];
|
||||
const matAttributes = [];
|
||||
for (const id of manifoldMesh.runOriginalID) {
|
||||
materials.push(materialMap.get(id));
|
||||
matAttributes.push(attributes);
|
||||
}
|
||||
|
||||
const numVert = mesh.numVert;
|
||||
const numProp = mesh.numProp;
|
||||
const posArray = new Float32Array(3 * numVert);
|
||||
const uvArray = new Float32Array(2 * numVert);
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
posArray[3 * i] = mesh.vertProperties[numProp * i];
|
||||
posArray[3 * i + 1] = mesh.vertProperties[numProp * i + 1];
|
||||
posArray[3 * i + 2] = mesh.vertProperties[numProp * i + 2];
|
||||
uvArray[2 * i] = mesh.vertProperties[numProp * i + 3];
|
||||
uvArray[2 * i + 1] = mesh.vertProperties[numProp * i + 4];
|
||||
}
|
||||
position.setArray(posArray);
|
||||
uv.setArray(uvArray);
|
||||
|
||||
await texturesLoad;
|
||||
const mesh = writeMesh(doc, manifoldMesh, matAttributes, materials);
|
||||
node.setMesh(mesh);
|
||||
await doc.transform(prune());
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
@@ -142,126 +146,6 @@
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
}
|
||||
|
||||
function cubeUV(id) {
|
||||
const numProp = 5;
|
||||
const numTri = 12;
|
||||
const numVert = 8;
|
||||
const numPropVert = 24;
|
||||
const vertPos =
|
||||
[-1, -1, -1,
|
||||
1, -1, -1,
|
||||
1, 1, -1,
|
||||
-1, 1, -1,
|
||||
-1, -1, 1,
|
||||
1, -1, 1,
|
||||
1, 1, 1,
|
||||
-1, 1, 1];
|
||||
const uvs = [0, 0, 0, 1, 1, 1, 1, 0];
|
||||
const triVerts = new Uint32Array(
|
||||
[0, 3, 2, 0, 2, 1,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
3, 0, 4, 3, 4, 7]);
|
||||
const v4 = [0, 1, 2, 5];
|
||||
const v6 = [0, 1, 2, 0, 2, 3];
|
||||
const vertProperties = new Float32Array(numProp * numPropVert);
|
||||
const mergeFromVert = new Uint32Array(numPropVert - numVert);
|
||||
const mergeToVert = new Uint32Array(numPropVert - numVert);
|
||||
const runOriginalID = new Uint32Array(1);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
|
||||
runOriginalID[0] = id;
|
||||
const vert2vert = [-1, -1, -1, -1, -1, -1, -1, -1];
|
||||
let idx = 0;
|
||||
for (let face = 0; face < 6; ++face) {
|
||||
for (let v = 0; v < 4; ++v) {
|
||||
const vIdx = 4 * face + v;
|
||||
const vOld = triVerts[6 * face + v4[v]];
|
||||
if (vert2vert[vOld] === -1) {
|
||||
vert2vert[vOld] = vIdx;
|
||||
} else {
|
||||
mergeFromVert[idx] = vIdx;
|
||||
mergeToVert[idx] = vert2vert[vOld];
|
||||
++idx;
|
||||
}
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
vertProperties[numProp * vIdx + i] = vertPos[3 * vOld + i];
|
||||
}
|
||||
vertProperties[numProp * vIdx + 3] = uvs[2 * v];
|
||||
vertProperties[numProp * vIdx + 4] = uvs[2 * v + 1];
|
||||
}
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
triVerts[6 * face + i] = 4 * face + v6[i];
|
||||
}
|
||||
}
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
|
||||
function sphereUV(id) {
|
||||
const n = 100;
|
||||
const tol = 1 / (2 * n);
|
||||
const mesh = wasm.sphere(1, n).getMesh();
|
||||
const nMerge = n / 2 - 1;
|
||||
const numVert = mesh.numVert;
|
||||
const numTri = mesh.numTri;
|
||||
const numPropIn = mesh.numProp;
|
||||
const numProp = 5;
|
||||
const triVerts = new Uint32Array(3 * numTri);
|
||||
const vertProperties = new Float32Array(numProp * (numVert + nMerge));
|
||||
const mergeFromVert = new Uint32Array(nMerge);
|
||||
const mergeToVert = new Uint32Array(nMerge);
|
||||
const runOriginalID = new Uint32Array(1);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
|
||||
runOriginalID[0] = id;
|
||||
const vert2vert = new Uint32Array(numVert);
|
||||
let idx = 0;
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
const x = mesh.vertProperties[numPropIn * i];
|
||||
const y = mesh.vertProperties[numPropIn * i + 1];
|
||||
const z = mesh.vertProperties[numPropIn * i + 2];
|
||||
vertProperties[numProp * i] = x;
|
||||
vertProperties[numProp * i + 1] = y;
|
||||
vertProperties[numProp * i + 2] = z;
|
||||
const u = Math.atan2(y, x) / (2 * Math.PI) + 0.5;
|
||||
const v = Math.acos(z) / Math.PI;
|
||||
vertProperties[numProp * i + 4] = v;
|
||||
if (Math.abs(y) < tol && x < -tol) {// Seam
|
||||
const iNew = numVert + idx;
|
||||
mergeFromVert[idx] = iNew;
|
||||
mergeToVert[idx] = i;
|
||||
vert2vert[i] = iNew;
|
||||
vertProperties[numProp * iNew] = x;
|
||||
vertProperties[numProp * iNew + 1] = y;
|
||||
vertProperties[numProp * iNew + 2] = z;
|
||||
vertProperties[numProp * iNew + 3] = 1;
|
||||
vertProperties[numProp * iNew + 4] = v;
|
||||
vertProperties[numProp * i + 3] = 0;
|
||||
++idx;
|
||||
} else {
|
||||
vert2vert[i] = i;
|
||||
vertProperties[numProp * i + 3] = u;
|
||||
}
|
||||
}
|
||||
for (let tri = 0; tri < numTri; ++tri) {
|
||||
const v0 = mesh.triVerts[3 * tri];
|
||||
const v1 = mesh.triVerts[3 * tri + 1];
|
||||
const v2 = mesh.triVerts[3 * tri + 2];
|
||||
if ((vert2vert[v0] !== v0 || vert2vert[v1] !== v1 || vert2vert[v2] !== v2) &&
|
||||
(vertProperties[numProp * v0 + 1] > tol || vertProperties[numProp * v1 + 1] > tol ||
|
||||
vertProperties[numProp * v2 + 1] > tol)) {
|
||||
triVerts[3 * tri] = vert2vert[v0];
|
||||
triVerts[3 * tri + 1] = vert2vert[v1];
|
||||
triVerts[3 * tri + 2] = vert2vert[v2];
|
||||
} else {
|
||||
triVerts[3 * tri] = v0;
|
||||
triVerts[3 * tri + 1] = v1;
|
||||
triVerts[3 * tri + 2] = v2;
|
||||
}
|
||||
}
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "manifold-examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:run": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.2.1",
|
||||
"@gltf-transform/extensions": "^3.2.1",
|
||||
"@gltf-transform/functions": "^3.2.1",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"simple-dropzone": "0.8.3",
|
||||
"three": "0.151.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.31.1",
|
||||
"@vitest/web-worker": "^0.31.1",
|
||||
"typescript": "5.0.4",
|
||||
"vite": "^4.3.2",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
@@ -12,24 +12,58 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/**
|
||||
* Wrap any object with this method to display it and any copies in transparent
|
||||
* red. This is particularly useful for debugging subtract() as it will allow
|
||||
* you find the object even if it doesn't currently intersect the result.
|
||||
*
|
||||
* @param manifold The object to show - returned for chaining.
|
||||
*/
|
||||
declare function show(manifold: Manifold): Manifold;
|
||||
import {Manifold, Mat4, Vec2, Vec3} from './manifold';
|
||||
|
||||
declare class GLTFNode {
|
||||
manifold?: Manifold;
|
||||
translation?: Vec3|((t: number) => Vec3);
|
||||
rotation?: Vec3|((t: number) => Vec3);
|
||||
scale?: Vec3|((t: number) => Vec3);
|
||||
material?: GLTFMaterial;
|
||||
name?: string;
|
||||
constructor(parent?: GLTFNode);
|
||||
clone(parent?: GLTFNode): GLTFNode;
|
||||
}
|
||||
|
||||
declare class GLTFMaterial {
|
||||
attributes?: string[];
|
||||
roughness?: number;
|
||||
metallic?: number;
|
||||
baseColorFactor?: [number, number, number];
|
||||
alpha?: number;
|
||||
unlit?: boolean;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap any object with this method to display it and any copies as the result,
|
||||
* while ghosting out the final result in transparent gray. Helpful for
|
||||
* Set material properties on the input manifold. They will be carried along
|
||||
* through operations.
|
||||
*
|
||||
* @param manifold The object to add properties to - returned for chaining.
|
||||
* @param material A set of material properties to apply to this manifold.
|
||||
*/
|
||||
declare function setMaterial(
|
||||
manifold: Manifold, material: GLTFMaterial): Manifold;
|
||||
|
||||
/**
|
||||
* Wrap any shape object with this method to display it and any copies in
|
||||
* transparent red. This is particularly useful for debugging subtract() as it
|
||||
* will allow you find the object even if it doesn't currently intersect the
|
||||
* result.
|
||||
*
|
||||
* @param shape The object to show - returned for chaining.
|
||||
*/
|
||||
declare function show(shape: CrossSection|Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Wrap any shape object with this method to display it and any copies as the
|
||||
* result, while ghosting out the final result in transparent gray. Helpful for
|
||||
* debugging as it allows you to see objects that may be hidden in the interior
|
||||
* of the result. Multiple objects marked only() will all be shown.
|
||||
*
|
||||
* @param manifold The object to show - returned for chaining.
|
||||
* @param shape The object to show - returned for chaining.
|
||||
*/
|
||||
declare function only(manifold: Manifold): Manifold;
|
||||
declare function only(shape: CrossSection|Manifold): Manifold;
|
||||
|
||||
// Type definitions for gl-matrix 3.4.3 Project:
|
||||
// https://github.com/toji/gl-matrix
|
||||
@@ -16,14 +16,16 @@ 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.
|
||||
|
||||
// API docs. Type e.g. "box." to see the Manifold API. Type
|
||||
// "CrossSection." or "Manifold." to list the 2D and 3D constructors,
|
||||
// respectively. 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 {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
// You must name your final output "result", or create at least one
|
||||
// GLTFNode - see Menger Sponge and Gyroid Module examples.
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// For visual debug, wrap any shape with show() and it and all of its
|
||||
@@ -38,6 +40,11 @@ export const examples = {
|
||||
// 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.
|
||||
|
||||
// Use GLTFNode for disjoint manifolds rather than compose(), as this will
|
||||
// keep them better organized in the GLB. This will also allow you to
|
||||
// specify material properties, and even vertex colors via
|
||||
// setProperties(). See Tetrahedron Puzzle example.
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -54,20 +61,46 @@ export const examples = {
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = tetrahedron().scale(scale);
|
||||
const tet = Manifold.tetrahedron().scale(scale);
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
box.push([1, -1], [1, 1]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
box.push([gap / (4 * scale), 1 - i * 2 / nDivisions]);
|
||||
}
|
||||
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
const cyan = [0, 1, 1];
|
||||
const magenta = [1, 0, 1];
|
||||
const fade = (color, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = cyan[i] * pos[2] + magenta[i] * (1 - pos[2]);
|
||||
}
|
||||
};
|
||||
|
||||
const result = tet.intersect(screw);
|
||||
// setProperties(3, fade) creates three channels of vertex properties
|
||||
// according to the above fade function. setMaterial assigns these
|
||||
// channels as colors, and sets the factor to white, since our default is
|
||||
// yellow.
|
||||
const screw = setMaterial(
|
||||
Manifold.extrude(box, 1, nDivisions, 270).setProperties(3, fade),
|
||||
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
|
||||
|
||||
const result = tet.intersect(
|
||||
screw.rotate([0, 0, -45]).translate([0, 0, -0.5]).scale(2 * scale));
|
||||
|
||||
// Assigned materials are only applied to a GLTFNode. Note that material
|
||||
// definitions cascade, applying recursively to all child surfaces, but
|
||||
// overridden by any materials defined lower down. Our default material:
|
||||
// {
|
||||
// roughness = 0.2,
|
||||
// metallic = 1,
|
||||
// baseColorFactor = [1, 1, 0],
|
||||
// alpha = 1,
|
||||
// unlit = false,
|
||||
// name = ''
|
||||
// }
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -75,6 +108,7 @@ export const examples = {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
const {sphere, cylinder, union} = Manifold;
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
@@ -139,7 +173,7 @@ export const examples = {
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const ball = Manifold.sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
@@ -187,7 +221,28 @@ export const examples = {
|
||||
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);
|
||||
|
||||
const colorCurvature = (color, pos, oldProp) => {
|
||||
const a = Math.max(0, Math.min(1, oldProp[0] / 3 + 0.5));
|
||||
const b = a * a * (3 - 2 * a);
|
||||
const red = [1, 0, 0];
|
||||
const blue = [0, 0, 1];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = (1 - b) * blue[i] + b * red[i];
|
||||
}
|
||||
};
|
||||
const result = Manifold.smooth(scallop, sharpenedEdges)
|
||||
.refine(n)
|
||||
.calculateCurvature(-1, 0)
|
||||
.setProperties(3, colorCurvature);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {
|
||||
baseColorFactor: [1, 1, 1],
|
||||
metallic: 0,
|
||||
attributes: ['COLOR_0']
|
||||
};
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -227,12 +282,8 @@ export const examples = {
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
const offset = 2;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
}
|
||||
const offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
@@ -250,14 +301,14 @@ export const examples = {
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
let knot = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = compose(knots);
|
||||
knot = Manifold.compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
@@ -293,20 +344,35 @@ export const examples = {
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
let result = Manifold.cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
const hole = Manifold.compose(holes);
|
||||
|
||||
result = Manifold.difference(
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
const posColors = (newProp, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
newProp[i] = (1 - pos[i]) / 2;
|
||||
}
|
||||
};
|
||||
|
||||
const result = mengerSponge(3)
|
||||
.trimByPlane([1, 1, 1], 0)
|
||||
.setProperties(3, posColors)
|
||||
.scale(100);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']};
|
||||
return result;
|
||||
},
|
||||
|
||||
@@ -318,7 +384,7 @@ export const examples = {
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
let b = Manifold.cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
@@ -327,7 +393,7 @@ export const examples = {
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = extrude(circle, width, nDivision, 180)
|
||||
let decor = Manifold.extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
@@ -345,8 +411,9 @@ export const examples = {
|
||||
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;
|
||||
const result =
|
||||
Manifold.intersection(Manifold.extrude(stretch, width), b);
|
||||
return result;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
@@ -359,7 +426,7 @@ export const examples = {
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
return Manifold.difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
@@ -381,8 +448,13 @@ export const examples = {
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
// number of modules along pyramid edge (use 1 for print orientation)
|
||||
const m = 4;
|
||||
// module size
|
||||
const size = 20;
|
||||
// SDF resolution
|
||||
const n = 20;
|
||||
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
@@ -399,20 +471,41 @@ export const examples = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
return Manifold.levelSet(gyroid, box, period / n, level)
|
||||
.scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const box = Manifold.cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4));
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result =
|
||||
result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
const gyroidModule = rhombicDodecahedron()
|
||||
.intersect(gyroidOffset(-0.4))
|
||||
.subtract(gyroidOffset(0.4));
|
||||
|
||||
if (m > 1) {
|
||||
for (let i = 0; i < m; ++i) {
|
||||
for (let j = i; j < m; ++j) {
|
||||
for (let k = j; k < m; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = gyroidModule;
|
||||
node.translation =
|
||||
[(k + i - j) * size, (k - i) * size, (-j) * size];
|
||||
node.material = {
|
||||
baseColorFactor:
|
||||
[(k + i - j + 1) / m, (k - i + 1) / m, (j + 1) / m]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = gyroidModule.rotate([-45, 0, 90]).translate([
|
||||
0, 0, size / Math.sqrt(2)
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
@@ -431,4 +524,8 @@ for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
examples.functionBodies.set(name, body);
|
||||
};
|
||||
};
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
self.examples = examples;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 539 B After Width: | Height: | Size: 539 B |
|
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 952 KiB After Width: | Height: | Size: 952 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 271 KiB |
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 110 KiB |
@@ -44,21 +44,16 @@
|
||||
</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';
|
||||
import * as THREE from 'three';
|
||||
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
@@ -71,12 +66,12 @@
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const manifold_1 = Manifold.cube([0.2, 0.2, 0.2], true);
|
||||
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
mesh.geometry = mesh2geometry(Manifold[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
@@ -99,7 +94,7 @@
|
||||
function geometry2mesh(geometry) {
|
||||
const vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
return new Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": [
|
||||
"./*.ts"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// vite.config.js
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
test: {testTimeout: 15000},
|
||||
worker: {
|
||||
format: 'es',
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
manifoldCAD: resolve(__dirname, 'index.html'),
|
||||
makeManifold: resolve(__dirname, 'make-manifold.html'),
|
||||
modelViewer: resolve(__dirname, 'model-viewer.html'),
|
||||
three: resolve(__dirname, 'three.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -1,241 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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'
|
||||
];
|
||||
// top level functions that constructs a new manifold
|
||||
const constructors = [
|
||||
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
|
||||
'difference', 'intersection', 'compose', 'levelSet', 'smooth', 'show', 'only'
|
||||
];
|
||||
const utils = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
'getCircularSegments', 'Mesh'
|
||||
];
|
||||
const exposedFunctions = constructors.concat(utils);
|
||||
|
||||
// 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.
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// Setup complete
|
||||
postMessage(null);
|
||||
|
||||
const oldLog = console.log;
|
||||
console.log = function(...args) {
|
||||
let message = '';
|
||||
for (const arg of args) {
|
||||
if (typeof arg == 'object') {
|
||||
message += JSON.stringify(arg, null, 4);
|
||||
} else {
|
||||
message += arg.toString();
|
||||
}
|
||||
}
|
||||
postMessage({log: message});
|
||||
oldLog(...args);
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
function outputMesh(indices, position, mesh) {
|
||||
indices.setArray(mesh.triVerts);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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`);
|
||||
const volume = Math.round(manifold.getProperties().volume / 10);
|
||||
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
|
||||
(volume / 100).toLocaleString()} cm^3`);
|
||||
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
outputMesh(indices, position, mesh);
|
||||
|
||||
for (const [run, id] of mesh.runOriginalID.entries()) {
|
||||
const outMesh = shown.get(id);
|
||||
if (outMesh == null) {
|
||||
continue;
|
||||
}
|
||||
const node =
|
||||
doc.createNode('debug').setMesh(outMesh).setMatrix(mesh.transform(run));
|
||||
scene.addChild(node);
|
||||
}
|
||||
|
||||
resultPrimitive.setMaterial(ghost ? ghostMaterial : resultMaterial);
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import '@vitest/web-worker';
|
||||
|
||||
import {WebIO} from '@gltf-transform/core';
|
||||
import {expect, suite, test} from 'vitest';
|
||||
|
||||
import Module from './built/manifold.js';
|
||||
import {readMesh, setupIO} from './gltf-io.js';
|
||||
import {examples} from './public/examples.js';
|
||||
import ManifoldWorker from './worker?worker';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
function initialized(worker) {
|
||||
return new Promise((resolve) => {
|
||||
worker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let objectURL = null;
|
||||
|
||||
async function runExample(name) {
|
||||
const worker = new ManifoldWorker();
|
||||
await initialized(worker);
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.onmessageerror = function({data}) {
|
||||
reject(data);
|
||||
};
|
||||
|
||||
worker.onerror = function(e) {
|
||||
reject(e);
|
||||
};
|
||||
|
||||
worker.onmessage = async function(e) {
|
||||
try {
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
if (objectURL == null) {
|
||||
reject('no objectURL');
|
||||
}
|
||||
const docIn = await io.read(objectURL);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
const attributes = [];
|
||||
const materials = [];
|
||||
const manifoldMesh = readMesh(mesh, attributes, materials);
|
||||
const manifold = wasm.Manifold(manifoldMesh);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
console.log(genus);
|
||||
manifold.delete();
|
||||
resolve({...prop, genus});
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
worker.postMessage(examples.functionBodies.get(name));
|
||||
});
|
||||
}
|
||||
|
||||
suite('Examples', () => {
|
||||
test('Intro', async () => {
|
||||
const result = await 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('Tetrahedron Puzzle', async () => {
|
||||
const result = await 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('Rounded Frame', async () => {
|
||||
const result = await 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('Heart', async () => {
|
||||
const result = await 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('Scallop', async () => {
|
||||
const result = await 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('Torus Knot', async () => {
|
||||
const result = await runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20786, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11177, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Menger Sponge', async () => {
|
||||
const result = await 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('Stretchy Bracelet', async () => {
|
||||
const result = await 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', async () => {
|
||||
const result = await 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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,464 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, Material, Node, WebIO} from '@gltf-transform/core';
|
||||
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
import * as glMatrix from 'gl-matrix';
|
||||
|
||||
import Module from './built/manifold';
|
||||
//@ts-ignore
|
||||
import {setupIO, writeMesh} from './gltf-io';
|
||||
import type {GLTFMaterial, Quat} from './public/editor';
|
||||
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
|
||||
|
||||
interface WorkerStatic extends ManifoldToplevel {
|
||||
GLTFNode: typeof GLTFNode;
|
||||
show(manifold: Manifold): Manifold;
|
||||
only(manifold: Manifold): Manifold;
|
||||
setMaterial(manifold: Manifold, material: GLTFMaterial): void;
|
||||
cleanup(): void;
|
||||
}
|
||||
|
||||
const module = await Module() as unknown as WorkerStatic;
|
||||
module.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
glMatrix.glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
// manifold static methods (that return a new manifold)
|
||||
const manifoldStaticFunctions = [
|
||||
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'compose',
|
||||
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh'
|
||||
];
|
||||
// manifold member functions (that return a new manifold)
|
||||
const manifoldMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'decompose', 'warp', 'transform', 'translate',
|
||||
'rotate', 'scale', 'mirror', 'refine', 'setProperties', 'asOriginal',
|
||||
'trimByPlane', 'split', 'splitByPlane'
|
||||
];
|
||||
// CrossSection static methods (that return a new cross-section)
|
||||
const crossSectionStaticFunctions = [
|
||||
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
|
||||
'ofPolygons'
|
||||
];
|
||||
// CrossSection member functions (that return a new cross-section)
|
||||
const crossSectionMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
|
||||
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset'
|
||||
];
|
||||
// top level functions that construct a new manifold/mesh
|
||||
const toplevelConstructors = ['show', 'only', 'setMaterial'];
|
||||
const toplevel = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
'getCircularSegments', 'Mesh', 'GLTFNode', 'Manifold', 'CrossSection'
|
||||
];
|
||||
const exposedFunctions = toplevelConstructors.concat(toplevel);
|
||||
|
||||
// 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.
|
||||
|
||||
const memoryRegistry = new Array<Manifold|CrossSection>();
|
||||
|
||||
function addMembers(
|
||||
className: string, methodNames: Array<string>, areStatic: boolean) {
|
||||
//@ts-ignore
|
||||
const cls = module[className];
|
||||
const obj = areStatic ? cls : cls.prototype;
|
||||
for (const name of methodNames) {
|
||||
if (name != 'cylinder') {
|
||||
const originalFn = obj[name];
|
||||
obj[name] = function(...args: any) {
|
||||
//@ts-ignore
|
||||
const result = originalFn(...args);
|
||||
memoryRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addMembers('Manifold', manifoldMemberFunctions, false);
|
||||
addMembers('Manifold', manifoldStaticFunctions, true);
|
||||
addMembers('CrossSection', crossSectionMemberFunctions, false);
|
||||
addMembers('CrossSection', crossSectionStaticFunctions, true);
|
||||
|
||||
for (const name of toplevelConstructors) {
|
||||
//@ts-ignore
|
||||
const originalFn = module[name];
|
||||
//@ts-ignore
|
||||
module[name] = function(...args: any) {
|
||||
const result = originalFn(...args);
|
||||
memoryRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
module.cleanup = function() {
|
||||
for (const obj of memoryRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
memoryRegistry.length = 0;
|
||||
};
|
||||
|
||||
// Debug setup to show source meshes
|
||||
let ghost = false;
|
||||
const shown = new Map<number, Mesh>();
|
||||
const singles = new Map<number, Mesh>();
|
||||
|
||||
const SHOW = {
|
||||
baseColorFactor: [1, 0, 0],
|
||||
alpha: 0.25,
|
||||
roughness: 1,
|
||||
metallic: 0
|
||||
} as GLTFMaterial;
|
||||
|
||||
const GHOST = {
|
||||
baseColorFactor: [0.5, 0.5, 0.5],
|
||||
alpha: 0.25,
|
||||
roughness: 1,
|
||||
metallic: 0
|
||||
} as GLTFMaterial;
|
||||
|
||||
const nodes = new Array<GLTFNode>();
|
||||
const id2material = new Map<number, GLTFMaterial>();
|
||||
const materialCache = new Map<GLTFMaterial, Material>();
|
||||
|
||||
function cleanup() {
|
||||
ghost = false;
|
||||
shown.clear();
|
||||
singles.clear();
|
||||
nodes.length = 0;
|
||||
id2material.clear();
|
||||
materialCache.clear();
|
||||
}
|
||||
|
||||
class GLTFNode {
|
||||
private _parent?: GLTFNode;
|
||||
manifold?: Manifold;
|
||||
translation?: Vec3;
|
||||
rotation?: Vec3;
|
||||
scale?: Vec3;
|
||||
material?: GLTFMaterial;
|
||||
name?: string;
|
||||
|
||||
constructor(parent?: GLTFNode) {
|
||||
this._parent = parent;
|
||||
nodes.push(this);
|
||||
}
|
||||
clone(parent?: GLTFNode) {
|
||||
const copy = {...this};
|
||||
copy._parent = parent;
|
||||
nodes.push(copy);
|
||||
return copy;
|
||||
}
|
||||
get parent() {
|
||||
return this._parent;
|
||||
}
|
||||
}
|
||||
|
||||
module.GLTFNode = GLTFNode;
|
||||
|
||||
module.setMaterial = (manifold: Manifold, material: GLTFMaterial): Manifold => {
|
||||
const out = manifold.asOriginal();
|
||||
id2material.set(out.originalID(), material);
|
||||
return out;
|
||||
};
|
||||
|
||||
function debug(manifold: Manifold, map: Map<number, Mesh>) {
|
||||
let result = manifold.asOriginal();
|
||||
map.set(result.originalID(), result.getMesh());
|
||||
return result;
|
||||
};
|
||||
|
||||
module.show = (manifold) => {
|
||||
return debug(manifold, shown);
|
||||
};
|
||||
|
||||
module.only = (manifold) => {
|
||||
ghost = true;
|
||||
return debug(manifold, singles);
|
||||
};
|
||||
|
||||
// Setup complete
|
||||
self.postMessage(null);
|
||||
|
||||
if (self.console) {
|
||||
const oldLog = self.console.log;
|
||||
self.console.log = function(...args) {
|
||||
let message = '';
|
||||
for (const arg of args) {
|
||||
if (arg == null) {
|
||||
message += 'undefined';
|
||||
} else if (typeof arg == 'object') {
|
||||
message += JSON.stringify(arg, null, 4);
|
||||
} else {
|
||||
message += arg.toString();
|
||||
}
|
||||
}
|
||||
self.postMessage({log: message});
|
||||
oldLog(...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Swallow informational logs in testing framework
|
||||
function log(...args: any[]) {
|
||||
if (self.console) {
|
||||
self.console.log(...args);
|
||||
}
|
||||
}
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
const content = e.data +
|
||||
'\nreturn exportGLB(typeof result === "undefined" ? undefined : result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportGLB, glMatrix, module, //@ts-ignore
|
||||
...exposedFunctions.map(name => module[name]));
|
||||
} catch (error: any) {
|
||||
console.log(error.toString());
|
||||
self.postMessage({objectURL: null});
|
||||
} finally {
|
||||
module.cleanup();
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
function createGLTFnode(doc: Document, node: GLTFNode) {
|
||||
const out = doc.createNode(node.name);
|
||||
if (node.translation) {
|
||||
out.setTranslation(node.translation);
|
||||
}
|
||||
if (node.rotation) {
|
||||
const {quat} = glMatrix;
|
||||
const deg2rad = Math.PI / 180;
|
||||
const q = quat.create() as Quat;
|
||||
quat.rotateX(q, q, deg2rad * node.rotation[0]);
|
||||
quat.rotateY(q, q, deg2rad * node.rotation[1]);
|
||||
quat.rotateZ(q, q, deg2rad * node.rotation[2]);
|
||||
out.setRotation(q);
|
||||
}
|
||||
if (node.scale) {
|
||||
out.setScale(node.scale);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function getBackupMaterial(node?: GLTFNode): GLTFMaterial {
|
||||
if (node == null) {
|
||||
return {};
|
||||
}
|
||||
if (node.material == null) {
|
||||
node.material = getBackupMaterial(node.parent);
|
||||
}
|
||||
return node.material;
|
||||
}
|
||||
|
||||
function makeDefaultedMaterial(doc: Document, {
|
||||
roughness = 0.2,
|
||||
metallic = 1,
|
||||
baseColorFactor = [1, 1, 0],
|
||||
alpha = 1,
|
||||
unlit = false,
|
||||
name = ''
|
||||
}: GLTFMaterial = {}): Material {
|
||||
const material = doc.createMaterial(name);
|
||||
|
||||
if (unlit) {
|
||||
const unlit = doc.createExtension(KHRMaterialsUnlit).createUnlit();
|
||||
material.setExtension('KHR_materials_unlit', unlit);
|
||||
}
|
||||
|
||||
if (alpha < 1) {
|
||||
material.setAlphaMode(Material.AlphaMode.BLEND).setDoubleSided(true);
|
||||
}
|
||||
|
||||
return material.setRoughnessFactor(roughness)
|
||||
.setMetallicFactor(metallic)
|
||||
.setBaseColorFactor([...baseColorFactor, alpha]);
|
||||
}
|
||||
|
||||
function getCachedMaterial(doc: Document, matDef: GLTFMaterial): Material {
|
||||
if (!materialCache.has(matDef)) {
|
||||
materialCache.set(matDef, makeDefaultedMaterial(doc, matDef));
|
||||
}
|
||||
return materialCache.get(matDef)!;
|
||||
}
|
||||
|
||||
function addMesh(
|
||||
doc: Document, node: Node, manifold: Manifold,
|
||||
backupMaterial: GLTFMaterial = {}) {
|
||||
const numTri = manifold.numTri();
|
||||
if (numTri == 0) {
|
||||
log('Empty manifold, skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Triangles: ${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;
|
||||
}
|
||||
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);
|
||||
log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
|
||||
(volume / 100).toLocaleString()} cm^3`);
|
||||
|
||||
// From Z-up to Y-up (glTF)
|
||||
const manifoldMesh = manifold.getMesh();
|
||||
|
||||
const materials = new Array<GLTFMaterial>();
|
||||
const attributes = new Array<Array<string>>();
|
||||
for (const id of manifoldMesh.runOriginalID!) {
|
||||
const material = id2material.get(id) || backupMaterial;
|
||||
materials.push(material);
|
||||
attributes.push(['POSITION', ...material.attributes ?? []]);
|
||||
}
|
||||
|
||||
const gltfMaterials = materials.map((matDef) => {
|
||||
return getCachedMaterial(doc, ghost ? GHOST : matDef);
|
||||
});
|
||||
|
||||
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
|
||||
|
||||
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
|
||||
let inMesh = shown.get(id);
|
||||
let single = false;
|
||||
if (inMesh == null) {
|
||||
single = true;
|
||||
inMesh = singles.get(id);
|
||||
}
|
||||
if (inMesh == null) {
|
||||
continue;
|
||||
}
|
||||
const mat = single ? materials[run] : SHOW;
|
||||
const debugNode =
|
||||
doc.createNode('debug')
|
||||
.setMesh(writeMesh(
|
||||
doc, inMesh, attributes, [getCachedMaterial(doc, mat)]))
|
||||
.setMatrix(manifoldMesh.transform(run));
|
||||
node.addChild(debugNode);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneNode(toNode: Node, fromNode: Node) {
|
||||
toNode.setMesh(fromNode.getMesh());
|
||||
fromNode.listChildren().forEach((child) => {
|
||||
const clone = child.clone();
|
||||
toNode.addChild(clone);
|
||||
});
|
||||
}
|
||||
|
||||
function cloneNodeNewMaterial(
|
||||
doc: Document, toNode: Node, fromNode: Node, backupMaterial: Material,
|
||||
oldBackupMaterial: Material) {
|
||||
cloneNode(toNode, fromNode);
|
||||
const mesh = doc.createMesh();
|
||||
toNode.setMesh(mesh);
|
||||
fromNode.getMesh()!.listPrimitives().forEach((primitive) => {
|
||||
const newPrimitive = primitive.clone();
|
||||
if (primitive.getMaterial() === oldBackupMaterial) {
|
||||
newPrimitive.setMaterial(backupMaterial);
|
||||
}
|
||||
mesh.addPrimitive(newPrimitive);
|
||||
});
|
||||
}
|
||||
|
||||
function createNodeFromCache(
|
||||
doc: Document, nodeDef: GLTFNode,
|
||||
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
|
||||
const node = createGLTFnode(doc, nodeDef);
|
||||
if (nodeDef.manifold != null) {
|
||||
const backupMaterial = getBackupMaterial(nodeDef);
|
||||
const cachedNodes = manifold2node.get(nodeDef.manifold);
|
||||
if (cachedNodes == null) {
|
||||
addMesh(doc, node, nodeDef.manifold, backupMaterial);
|
||||
const cache = new Map<GLTFMaterial, Node>();
|
||||
cache.set(backupMaterial, node);
|
||||
manifold2node.set(nodeDef.manifold, cache);
|
||||
} else {
|
||||
const cachedNode = cachedNodes.get(backupMaterial);
|
||||
if (cachedNode == null) {
|
||||
const [oldBackupMaterial, oldNode] = cachedNodes.entries().next().value;
|
||||
cloneNodeNewMaterial(
|
||||
doc, node, oldNode, getCachedMaterial(doc, backupMaterial),
|
||||
getCachedMaterial(doc, oldBackupMaterial));
|
||||
cachedNodes.set(backupMaterial, node);
|
||||
} else {
|
||||
cloneNode(node, cachedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
async function exportGLB(manifold?: Manifold) {
|
||||
const doc = new Document();
|
||||
const halfRoot2 = Math.sqrt(2) / 2;
|
||||
const mm2m = 1 / 1000;
|
||||
const wrapper = doc.createNode('wrapper')
|
||||
.setRotation([-halfRoot2, 0, 0, halfRoot2])
|
||||
.setScale([mm2m, mm2m, mm2m]);
|
||||
doc.createScene().addChild(wrapper);
|
||||
|
||||
if (nodes.length > 0) {
|
||||
const node2gltf = new Map<GLTFNode, Node>();
|
||||
const manifold2node = new Map<Manifold, Map<GLTFMaterial, Node>>();
|
||||
let leafNodes = 0;
|
||||
|
||||
for (const nodeDef of nodes) {
|
||||
node2gltf.set(nodeDef, createNodeFromCache(doc, nodeDef, manifold2node));
|
||||
if (nodeDef.manifold) {
|
||||
++leafNodes;
|
||||
}
|
||||
}
|
||||
|
||||
for (const nodeDef of nodes) {
|
||||
const gltfNode = node2gltf.get(nodeDef)!;
|
||||
if (nodeDef.parent == null) {
|
||||
wrapper.addChild(gltfNode);
|
||||
} else {
|
||||
node2gltf.get(nodeDef.parent)!.addChild(gltfNode);
|
||||
}
|
||||
}
|
||||
|
||||
log('Total glTF nodes: ', nodes.length,
|
||||
', Total mesh references: ', leafNodes);
|
||||
} else {
|
||||
if (manifold == null) {
|
||||
log('No output because "result" is undefined and no "GLTFNode"s were created.');
|
||||
return;
|
||||
}
|
||||
const node = doc.createNode();
|
||||
addMesh(doc, node, manifold);
|
||||
wrapper.addChild(node);
|
||||
}
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
self.postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
|
||||
namespace js {
|
||||
val MeshGL2JS(const MeshGL& mesh) {
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("numProp", mesh.numProp);
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertProperties",
|
||||
val(typed_memory_view(mesh.vertProperties.size(),
|
||||
mesh.vertProperties.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
|
||||
mesh.mergeFromVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
|
||||
mesh.mergeToVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runIndex",
|
||||
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
|
||||
mesh.runOriginalID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("faceID",
|
||||
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
|
||||
mesh.runTransform.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.numProp = mesh["numProp"].as<int>();
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertProperties =
|
||||
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
|
||||
if (mesh["mergeFromVert"] != val::undefined()) {
|
||||
out.mergeFromVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
|
||||
}
|
||||
if (mesh["mergeToVert"] != val::undefined()) {
|
||||
out.mergeToVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
|
||||
}
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["runOriginalID"] != val::undefined()) {
|
||||
out.runOriginalID =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
if (mesh["runTransform"] != val::undefined()) {
|
||||
out.runTransform =
|
||||
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
|
||||
MeshGL mesh = manifold.GetMeshGL(normalIdx);
|
||||
return MeshGL2JS(mesh);
|
||||
}
|
||||
|
||||
val Merge(const val& mesh) {
|
||||
val out = val::object();
|
||||
MeshGL meshGL = MeshJS2GL(mesh);
|
||||
bool changed = meshGL.Merge();
|
||||
out.set("changed", changed);
|
||||
out.set("mesh", changed ? MeshGL2JS(meshGL) : mesh);
|
||||
return out;
|
||||
}
|
||||
|
||||
Manifold Smooth(const val& mesh,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {}) {
|
||||
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
namespace cross_js {
|
||||
CrossSection OfPolygons(std::vector<std::vector<glm::vec2>> polygons,
|
||||
int fill_rule) {
|
||||
auto fr = fill_rule == 0 ? CrossSection::FillRule::EvenOdd
|
||||
: fill_rule == 1 ? CrossSection::FillRule::NonZero
|
||||
: fill_rule == 2 ? CrossSection::FillRule::Positive
|
||||
: CrossSection::FillRule::Negative;
|
||||
return CrossSection(polygons, fr);
|
||||
}
|
||||
|
||||
CrossSection Union(const CrossSection& a, const CrossSection& b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
CrossSection Difference(const CrossSection& a, const CrossSection& b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
CrossSection Intersection(const CrossSection& a, const CrossSection& b) {
|
||||
return a ^ b;
|
||||
}
|
||||
|
||||
CrossSection UnionN(const std::vector<CrossSection>& cross_sections) {
|
||||
return CrossSection::BatchBoolean(cross_sections, OpType::Add);
|
||||
}
|
||||
|
||||
CrossSection DifferenceN(const std::vector<CrossSection>& cross_sections) {
|
||||
return CrossSection::BatchBoolean(cross_sections, OpType::Subtract);
|
||||
}
|
||||
|
||||
CrossSection IntersectionN(const std::vector<CrossSection>& cross_sections) {
|
||||
return CrossSection::BatchBoolean(cross_sections, OpType::Intersect);
|
||||
}
|
||||
|
||||
CrossSection Transform(CrossSection& cross_section, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat3x2 matrix;
|
||||
for (const int col : {0, 1, 2})
|
||||
for (const int row : {0, 1}) matrix[col][row] = array[col * 3 + row];
|
||||
return cross_section.Transform(matrix);
|
||||
}
|
||||
|
||||
CrossSection Warp(CrossSection& cross_section, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec2&) = reinterpret_cast<void (*)(glm::vec2&)>(funcPtr);
|
||||
return cross_section.Warp(f);
|
||||
}
|
||||
|
||||
CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
|
||||
double miter_limit, double arc_tolerance) {
|
||||
auto jt = join_type == 0 ? CrossSection::JoinType::Square
|
||||
: join_type == 1 ? CrossSection::JoinType::Round
|
||||
: CrossSection::JoinType::Miter;
|
||||
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
|
||||
}
|
||||
} // namespace cross_js
|
||||
|
||||
namespace man_js {
|
||||
Manifold FromMeshJS(const val& mesh) { return Manifold(js::MeshJS2GL(mesh)); }
|
||||
|
||||
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
|
||||
|
||||
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, OpType::Add);
|
||||
}
|
||||
|
||||
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Subtract);
|
||||
}
|
||||
|
||||
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Intersect);
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat4x3 matrix;
|
||||
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);
|
||||
}
|
||||
|
||||
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
|
||||
return manifold.Warp(f);
|
||||
}
|
||||
|
||||
Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) {
|
||||
void (*f)(float*, glm::vec3, const float*) =
|
||||
reinterpret_cast<void (*)(float*, glm::vec3, const float*)>(funcPtr);
|
||||
return manifold.SetProperties(numProp, f);
|
||||
}
|
||||
|
||||
Manifold LevelSet(uintptr_t funcPtr, Box bounds, float edgeLength,
|
||||
float level) {
|
||||
float (*f)(const glm::vec3&) =
|
||||
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
|
||||
Mesh m = LevelSet(f, bounds, edgeLength, level);
|
||||
return Manifold(m);
|
||||
}
|
||||
|
||||
std::vector<Manifold> Split(Manifold& a, Manifold& b) {
|
||||
auto [r1, r2] = a.Split(b);
|
||||
return {r1, r2};
|
||||
}
|
||||
|
||||
std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
|
||||
float originOffset) {
|
||||
auto [a, b] = m.SplitByPlane(normal, originOffset);
|
||||
return {a, b};
|
||||
}
|
||||
} // namespace man_js
|
||||
@@ -12,153 +12,17 @@
|
||||
// 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';
|
||||
import {Box, FillRule, JoinType, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
* Triangulates a set of /epsilon-valid polygons.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
* @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 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
@@ -176,12 +40,284 @@ 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 CrossSection {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
constructor(polygons: Polygons, fillRule?: FillRule);
|
||||
|
||||
// Shapes
|
||||
|
||||
static square(size?: Vec2|number, center?: boolean): CrossSection;
|
||||
|
||||
static circle(radius: number, circularSegments?: number): CrossSection;
|
||||
|
||||
// Extrusions (2d to 3d manifold)
|
||||
|
||||
/**
|
||||
* Constructs a manifold by extruding the cross-section along Z-axis.
|
||||
*
|
||||
* @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}.
|
||||
* @param center If true, the extrusion is centered on the z-axis through the
|
||||
* origin
|
||||
* as opposed to resting on the XY plane as is default.
|
||||
*/
|
||||
extrude(
|
||||
height: number, nDivisions?: number, twistDegrees?: number,
|
||||
scaleTop?: Vec2|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold by revolving this cross-section around its Y-axis and
|
||||
* then setting this as the Z-axis of the resulting manifold. If the contours
|
||||
* cross the Y-axis, only the part on the positive X side is used.
|
||||
* Geometrically valid input will result in geometrically valid output.
|
||||
*
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
revolve(circularSegments?: number, revolveDegrees?: number): Manifold;
|
||||
|
||||
// Transformations
|
||||
|
||||
/**
|
||||
* Transform this CrossSection 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: Mat3): CrossSection;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
translate(v: Vec2): CrossSection;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
rotate(v: number): CrossSection;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
scale(v: Vec2|number): CrossSection;
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
mirror(v: Vec2): CrossSection;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec2) => void): CrossSection;
|
||||
|
||||
/**
|
||||
* 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 joinType The join type specifying the treatment of contour joins
|
||||
* (corners).
|
||||
* @param miterLimit The maximum distance in multiples of delta that vertices
|
||||
* can be offset from their original positions with before squaring is
|
||||
* applied, **when the join type is Miter** (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 circularSegments Number of segments per 360 degrees of
|
||||
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
|
||||
* will be added to each contour). Default is calculated by the static Quality
|
||||
* defaults according to the radius.
|
||||
*/
|
||||
offset(
|
||||
delta: number, joinType?: JoinType, miterLimit?: number,
|
||||
circularSegments?: number): CrossSection;
|
||||
|
||||
/**
|
||||
* Compute the convex hull of the contours in this CrossSection.
|
||||
*/
|
||||
hull(): CrossSection;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param epsilon minimum distance vertices must diverge from the hypothetical
|
||||
* outline without them in order to be included in the output (default
|
||||
* 1e-6)
|
||||
*/
|
||||
simplify(epsilon?: number): CrossSection;
|
||||
|
||||
// Clipping Operations
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: CrossSection|Polygons): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: CrossSection|Polygons): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: CrossSection|Polygons): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean union of the cross-sections a and b
|
||||
*/
|
||||
static union(a: CrossSection|Polygons, b: CrossSection|Polygons):
|
||||
CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean difference of the cross-section b from the cross-section a
|
||||
*/
|
||||
static difference(a: CrossSection|Polygons, b: CrossSection|Polygons):
|
||||
CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean intersection of the cross-sections a and b
|
||||
*/
|
||||
static intersection(a: CrossSection|Polygons, b: CrossSection|Polygons):
|
||||
CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean union of a list of cross-sections
|
||||
*/
|
||||
static union(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean difference of the tail of a list of cross-sections from its head
|
||||
*/
|
||||
static difference(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean intersection of a list of cross-sections
|
||||
*/
|
||||
static intersection(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
// Topological Operations
|
||||
|
||||
/**
|
||||
* Construct a CrossSection from a vector of other Polygons (batch
|
||||
* boolean union).
|
||||
*/
|
||||
static compose(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of CrossSections that are topologically
|
||||
* disconnected, each containing one outline contour with zero or more
|
||||
* holes.
|
||||
*/
|
||||
decompose(): CrossSection[];
|
||||
|
||||
// Polygon Conversion
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static ofPolygons(polygons: Polygons, fillRule?: FillRule): CrossSection;
|
||||
|
||||
/**
|
||||
* Return the contours of this CrossSection as a list of simple polygons.
|
||||
*/
|
||||
toPolygons(): SimplePolygon[];
|
||||
|
||||
// Properties
|
||||
|
||||
/**
|
||||
* Return the total area covered by complex polygons making up the
|
||||
* CrossSection.
|
||||
*/
|
||||
area(): number;
|
||||
|
||||
/**
|
||||
* Does the CrossSection (not) have any contours?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the CrossSection.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of contours in the CrossSection.
|
||||
*/
|
||||
numContour(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding rectangle of all the CrossSection's
|
||||
* vertices.
|
||||
*/
|
||||
bounds(): Rect;
|
||||
|
||||
// Memory
|
||||
|
||||
/**
|
||||
* Frees the WASM memory of this CrossSection, since these cannot be
|
||||
* garbage-collected automatically.
|
||||
*/
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
export class Manifold {
|
||||
/**
|
||||
@@ -196,6 +332,165 @@ export class Manifold {
|
||||
* materials into triangle runs.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
|
||||
// Shapes
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
static tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static 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.
|
||||
*/
|
||||
static 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.
|
||||
*/
|
||||
static sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
// Extrusions from 2d shapes
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons/cross-section by extruding
|
||||
* them along the Z-axis.
|
||||
*
|
||||
* @param polygons 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}.
|
||||
* @param center If true, the extrusion is centered on the z-axis through the
|
||||
* origin
|
||||
* as opposed to resting on the XY plane as is default.
|
||||
*/
|
||||
static extrude(
|
||||
polygons: CrossSection|Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2|number,
|
||||
center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons/cross-section by revolving
|
||||
* them around the 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 polygons A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
|
||||
*/
|
||||
static revolve(
|
||||
polygons: CrossSection|Polygons, circularSegments?: number,
|
||||
revolveDegrees?: number): Manifold;
|
||||
|
||||
// Mesh Conversion
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static ofMesh(mesh: Mesh): 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.
|
||||
*/
|
||||
static smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
// Signed Distance Functions
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
// Transformations
|
||||
|
||||
/**
|
||||
* Transform this Manifold in space. Stored in column-major order. This
|
||||
* operation can be chained. Transforms are combined and applied lazily.
|
||||
@@ -233,6 +528,76 @@ export class Manifold {
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Create a new copy of this manifold with updated vertex properties by
|
||||
* supplying a function that takes the existing position and properties as
|
||||
* input. You may specify any number of output properties, allowing creation
|
||||
* and removal of channels. Note: undefined behavior will result if you read
|
||||
* past the number of input properties or write past the number of output
|
||||
* properties.
|
||||
*
|
||||
* @param numProp The new number of properties per vertex.
|
||||
* @param propFunc A function that modifies the properties of a given vertex.
|
||||
*/
|
||||
setProperties(
|
||||
numProp: number,
|
||||
propFunc: (newProp: number[], position: Vec3, oldProp: number[]) => void):
|
||||
Manifold;
|
||||
|
||||
/**
|
||||
* 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 and assigns
|
||||
* them as vertex properties on the given channels.
|
||||
*
|
||||
* @param gaussianIdx The property channel index in which to store the
|
||||
* Gaussian curvature. An index < 0 will be ignored (stores nothing). The
|
||||
* property set will be automatically expanded to include the channel
|
||||
* index specified.
|
||||
* @param meanIdx The property channel index in which to store the mean
|
||||
* curvature. An index < 0 will be ignored (stores nothing). The property
|
||||
* set will be automatically expanded to include the channel index
|
||||
* specified.
|
||||
*/
|
||||
calculateCurvature(gaussianIdx: number, meanIdx: number): Manifold;
|
||||
|
||||
// Boolean Operations
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
@@ -249,27 +614,77 @@ export class Manifold {
|
||||
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.
|
||||
* Boolean union of the manifolds a and b
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
static union(a: Manifold, b: Manifold): 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.
|
||||
* Boolean difference of the manifold b from the manifold a
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
static difference(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection of the manifolds a and b
|
||||
*/
|
||||
static intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union of a list of manifolds
|
||||
*/
|
||||
static union(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference of the tail of a list of manifolds from its head
|
||||
*/
|
||||
static difference(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection of a list of manifolds
|
||||
*/
|
||||
static intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param cutter
|
||||
*/
|
||||
split(cutter: Manifold): Manifold[];
|
||||
|
||||
/**
|
||||
* Convenient version of Split() for a half-space.
|
||||
*
|
||||
* @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.
|
||||
* @param originOffset The distance of the plane from the origin in the
|
||||
* direction of the normal vector.
|
||||
*/
|
||||
splitByPlane(normal: Vec3, originOffset: number): 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;
|
||||
|
||||
// Topological Operations
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
static compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
@@ -279,6 +694,8 @@ export class Manifold {
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
// Property Access
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
@@ -299,6 +716,18 @@ export class Manifold {
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* The number of properties per vertex in the Manifold.
|
||||
*/
|
||||
numProp(): number;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
numPropVert(): number
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
@@ -329,16 +758,7 @@ export class Manifold {
|
||||
*/
|
||||
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;
|
||||
// Export
|
||||
|
||||
/**
|
||||
* Returns a Mesh that is designed to easily push into a renderer, including
|
||||
@@ -355,6 +775,8 @@ export class Manifold {
|
||||
*/
|
||||
getMesh(normalIdx?: Vec3): Mesh;
|
||||
|
||||
// ID Management
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@@ -376,6 +798,21 @@ export class Manifold {
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): 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.
|
||||
*/
|
||||
static reserveIDs(count: number): number;
|
||||
|
||||
// Memory
|
||||
|
||||
/**
|
||||
* Frees the WASM memory of this Manifold, since these cannot be
|
||||
* garbage-collected automatically.
|
||||
*/
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
@@ -404,9 +841,10 @@ export class Mesh {
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
get numRun(): number;
|
||||
merge(): boolean;
|
||||
verts(tri: number): SealedUint32Array<3>;
|
||||
position(vert: number): SealedFloat32Array<3>;
|
||||
extras(vert: number): Float32Array;
|
||||
tangent(halfedge: number): SealedFloat32Array<4>;
|
||||
transform(run: number): Mat4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ export type Mat4 = [
|
||||
];
|
||||
export type SimplePolygon = Vec2[];
|
||||
export type Polygons = SimplePolygon|SimplePolygon[];
|
||||
export type Rect = {
|
||||
min: Vec2,
|
||||
max: Vec2
|
||||
};
|
||||
export type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
@@ -55,11 +59,5 @@ export type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
export type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative'
|
||||
export type JoinType = 'Square'|'Round'|'Miter'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,30 +15,21 @@
|
||||
import * as T from './manifold-encapsulated-types';
|
||||
export * from './manifold-global-types';
|
||||
|
||||
export type CrossSection = T.CrossSection;
|
||||
export type Manifold = T.Manifold;
|
||||
export type Mesh = T.Mesh;
|
||||
|
||||
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;
|
||||
revolve: typeof T.revolve;
|
||||
union: typeof T.union;
|
||||
difference: typeof T.difference;
|
||||
intersection: typeof T.intersection;
|
||||
compose: typeof T.compose;
|
||||
levelSet: typeof T.levelSet;
|
||||
export interface ManifoldToplevel {
|
||||
CrossSection: typeof T.CrossSection;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
triangulate: typeof T.triangulate;
|
||||
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;
|
||||
}
|
||||
|
||||
export default function Module(): Promise<ManifoldStatic>;
|
||||
export default function Module(config?: {locateFile: () => string}):
|
||||
Promise<ManifoldToplevel>;
|
||||
|
||||
@@ -8,9 +8,7 @@
|
||||
"manifold.wasm",
|
||||
"manifold.d.ts",
|
||||
"manifold-encapsulated-types.d.ts",
|
||||
"manifold-global-types.d.ts",
|
||||
"examples/examples.js",
|
||||
"test/test.js"
|
||||
"manifold-global-types.d.ts"
|
||||
],
|
||||
"typings": "manifold.d.ts",
|
||||
"types": "manifold.d.ts",
|
||||
@@ -37,13 +35,5 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/elalish/manifold/issues"
|
||||
},
|
||||
"homepage": "https://github.com/elalish/manifold#readme",
|
||||
"scripts": {
|
||||
"test": "mocha -u tdd -t 30000"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "*",
|
||||
"mocha": "*",
|
||||
"gl-matrix": "3.4.3"
|
||||
}
|
||||
"homepage": "https://github.com/elalish/manifold#readme"
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
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'
|
||||
];
|
||||
// top level functions that constructs a new manifold
|
||||
const constructors = [
|
||||
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
|
||||
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
|
||||
];
|
||||
const utils = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
'getCircularSegments', 'Mesh'
|
||||
];
|
||||
const exposedFunctions = constructors.concat(utils);
|
||||
|
||||
// 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;
|
||||
};
|
||||
}
|
||||
|
||||
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 = [];
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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('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('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('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('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('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('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');
|
||||
});
|
||||
});
|
||||
@@ -16,18 +16,27 @@ 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(MANIFOLD_EXPORT)
|
||||
add_executable(convertFile convert_file.cpp)
|
||||
target_link_libraries(convertFile manifold meshIO)
|
||||
target_compile_options(convertFile PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(convertFile PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
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,67 @@
|
||||
// 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 <iostream>
|
||||
|
||||
#include "manifold.h"
|
||||
#include "meshIO.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
/*
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::cout << "Specify an input filename." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
manifold::ManifoldParams().verbose = true;
|
||||
|
||||
const std::string filename(argv[1]);
|
||||
|
||||
MeshGL input = ImportMesh(filename);
|
||||
std::cout << input.NumVert() << " vertices, " << input.NumTri()
|
||||
<< " triangles" << std::endl;
|
||||
|
||||
if (input.Merge())
|
||||
std::cout << filename << " is not manifold, attempting to fix."
|
||||
<< std::endl;
|
||||
|
||||
const Manifold manifold(input);
|
||||
if (manifold.Status() != Manifold::Error::NoError) {
|
||||
std::cout << "Could not make a valid manifold, error: "
|
||||
<< (int)manifold.Status() << std::endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
const std::vector<Manifold> parts = manifold.Decompose();
|
||||
std::cout << parts.size() << " objects:" << std::endl;
|
||||
for (const Manifold& part : parts) {
|
||||
auto prop = part.GetProperties();
|
||||
std::cout << part.NumVert() << " vertices, " << part.NumTri()
|
||||
<< " triangles, volume = " << prop.volume
|
||||
<< ", surface area = " << prop.surfaceArea << std::endl;
|
||||
}
|
||||
|
||||
if (argc == 3) {
|
||||
std::string outName = argv[2];
|
||||
|
||||
std::cout << "Writing " << outName << std::endl;
|
||||
|
||||
ExportMesh(outName, manifold.GetMeshGL(), {});
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -17,16 +17,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1657296039,
|
||||
"narHash": "sha256-Ghh39+aS+pw5sTP/ZO8VIKE6sBhMadDaQZtf+3yu4Vc=",
|
||||
"lastModified": 1689008574,
|
||||
"narHash": "sha256-VFMgyHDiqsGDkRg73alv6OdHJAqhybryWHv77bSCGIw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "71d7a4c037dc4f3e98d5c4a81b941933cf5bf675",
|
||||
"rev": "4a729ce4b1fe5ec4fffc71c67c96aa5184ebb462",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-22.05",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
@@ -25,8 +25,7 @@
|
||||
"manifold-${parallel-backend}";
|
||||
version = "beta";
|
||||
src = self;
|
||||
patches = [ ./thrust.diff ];
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages(ps: with ps; [trimesh])) ]) ++ build-tools ++
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages (ps: with ps; [ trimesh ])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PYBIND=ON"
|
||||
@@ -44,7 +43,7 @@
|
||||
mkdir -p $out
|
||||
cp src/manifold/libmanifold.a $out/
|
||||
cp extras/perfTest $out
|
||||
cp bindings/python/pymanifold* $out
|
||||
cp bindings/python/manifold3d* $out
|
||||
'';
|
||||
};
|
||||
parallelBackends = [
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
project(meshIO)
|
||||
|
||||
find_package(assimp REQUIRED)
|
||||
|
||||
add_library(${PROJECT_NAME} src/meshIO.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
@@ -21,8 +23,8 @@ target_include_directories(${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE assimp
|
||||
PUBLIC manifold
|
||||
PRIVATE assimp::assimp
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
@@ -15,14 +15,10 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#include "public.h"
|
||||
#include "manifold.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Debug
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup MeshIO
|
||||
* @brief 3D model file I/O based on Assimp
|
||||
* @{
|
||||
@@ -70,5 +66,4 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options);
|
||||
/** @} */
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -79,6 +79,13 @@ void ExportScene(aiScene* scene, const std::string& filename) {
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
auto ext = filename.substr(filename.length() - 4, 4);
|
||||
if (ext == ".3mf") {
|
||||
// Workaround https://github.com/assimp/assimp/issues/3816
|
||||
aiNode* old_root = scene->mRootNode;
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->addChildren(1, &old_root);
|
||||
}
|
||||
|
||||
auto result = exporter.Export(scene, GetType(filename), filename);
|
||||
|
||||
@@ -52,4 +52,4 @@ Manifold Scallop();
|
||||
|
||||
Manifold GyroidModule(float size = 20, int n = 20);
|
||||
/** @} */ // end of Samples
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
|
After Width: | Height: | Size: 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,12 +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({decorRadius * cosd(dPhiDeg * i) + twistRadius,
|
||||
decorRadius * sind(dPhiDeg * i)});
|
||||
}
|
||||
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});
|
||||
|
||||
@@ -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({cosd(dPhi * i) + 2.0f, sind(dPhi * i)});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -24,12 +24,9 @@ class Collider {
|
||||
public:
|
||||
Collider() {}
|
||||
Collider(const VecDH<Box>& leafBB, const VecDH<uint32_t>& leafMorton);
|
||||
// Aborts and returns false if transform is not axis aligned.
|
||||
bool Transform(glm::mat4x3);
|
||||
void UpdateBoxes(const VecDH<Box>& leafBB);
|
||||
// Collisions returns a sparse result, where i is the query index and j is
|
||||
// the leaf index where their bounding boxes overlap.
|
||||
template <typename T>
|
||||
template <const bool selfCollision = false, typename T>
|
||||
SparseIndices Collisions(const VecDH<T>& queriesIn) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -157,7 +157,7 @@ struct CreateRadixTree {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, const bool allocateOnly>
|
||||
template <typename T, const bool allocateOnly, const bool selfCollision>
|
||||
struct FindCollisions {
|
||||
thrust::pair<int*, int*> queryTri_;
|
||||
int* counts;
|
||||
@@ -172,12 +172,15 @@ struct FindCollisions {
|
||||
|
||||
bool overlaps = nodeBBox_[node].DoesOverlap(queryObj);
|
||||
if (overlaps && IsLeaf(node)) {
|
||||
if (allocateOnly) {
|
||||
count++;
|
||||
} else {
|
||||
int pos = count++;
|
||||
queryTri_.first[pos] = queryIdx;
|
||||
queryTri_.second[pos] = Node2Leaf(node);
|
||||
const int leafIdx = Node2Leaf(node);
|
||||
if (!selfCollision || leafIdx != queryIdx) {
|
||||
if (allocateOnly) {
|
||||
count++;
|
||||
} else {
|
||||
int pos = count++;
|
||||
queryTri_.first[pos] = queryIdx;
|
||||
queryTri_.second[pos] = leafIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
return overlaps && IsInternal(node); // Should traverse into node
|
||||
@@ -267,9 +270,11 @@ Collider::Collider(const VecDH<Box>& leafBB,
|
||||
* For a vector of query objects, this returns a sparse array of overlaps
|
||||
* between the queries and the bounding boxes of the collider. Queries are
|
||||
* normally axis-aligned bounding boxes. Points can also be used, and this case
|
||||
* overlaps are defined as lying in the XY projection of the bounding box.
|
||||
* overlaps are defined as lying in the XY projection of the bounding box. If
|
||||
* the query vector is the leaf vector, set selfCollision to true, which will
|
||||
* then not report any collisions between an index and itself.
|
||||
*/
|
||||
template <typename T>
|
||||
template <const bool selfCollision, typename T>
|
||||
SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
|
||||
// note that the length is 1 larger than the number of queries so the last
|
||||
// element can store the sum when using exclusive scan
|
||||
@@ -278,17 +283,17 @@ SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
|
||||
// compute the number of collisions to determine the size for allocation and
|
||||
// offset, this avoids the need for atomic
|
||||
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
|
||||
FindCollisions<T, true>(
|
||||
FindCollisions<T, true, selfCollision>(
|
||||
{thrust::pair<int*, int*>(nullptr, nullptr), counts.ptrD(),
|
||||
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
|
||||
// compute start index for each query and total count
|
||||
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin());
|
||||
SparseIndices queryTri(counts.back());
|
||||
// actually recording collisions
|
||||
for_each_n(
|
||||
policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
|
||||
FindCollisions<T, false>({queryTri.ptrDpq(), counts.ptrD(),
|
||||
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
|
||||
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
|
||||
FindCollisions<T, false, selfCollision>(
|
||||
{queryTri.ptrDpq(), counts.ptrD(), nodeBBox_.ptrD(),
|
||||
internalChildren_.ptrD()}));
|
||||
return queryTri;
|
||||
}
|
||||
|
||||
@@ -308,8 +313,8 @@ void Collider::UpdateBoxes(const VecDH<Box>& leafBB) {
|
||||
// kernel over leaves to save internal Boxes
|
||||
for_each_n(
|
||||
policy, countAt(0), NumLeaves(),
|
||||
BuildInternalBoxes({nodeBBox_.ptrD(), counter.ptrD(), nodeParent_.ptrD(),
|
||||
internalChildren_.ptrD()}));
|
||||
BuildInternalBoxes({nodeBBox_.ptrD(), counter.ptrD(), nodeParent_.cptrD(),
|
||||
internalChildren_.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,9 +337,12 @@ bool Collider::Transform(glm::mat4x3 transform) {
|
||||
return axisAligned;
|
||||
}
|
||||
|
||||
template SparseIndices Collider::Collisions<Box>(const VecDH<Box>&) const;
|
||||
template SparseIndices Collider::Collisions<true, Box>(const VecDH<Box>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<glm::vec3>(
|
||||
template SparseIndices Collider::Collisions<false, Box>(
|
||||
const VecDH<Box>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<false, glm::vec3>(
|
||||
const VecDH<glm::vec3>&) const;
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -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,238 @@
|
||||
// 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);
|
||||
CrossSection(const Rect& rect);
|
||||
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,
|
||||
int circularSegments = 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&);
|
||||
///@}
|
||||
|
||||
/** @name Topological
|
||||
*/
|
||||
///@{
|
||||
static CrossSection Compose(std::vector<CrossSection>&);
|
||||
std::vector<CrossSection> Decompose() const;
|
||||
///@}
|
||||
|
||||
/** @name Convex Hulling
|
||||
*/
|
||||
///@{
|
||||
CrossSection Hull() const;
|
||||
static CrossSection Hull(const std::vector<CrossSection>& crossSections);
|
||||
static CrossSection Hull(const SimplePolygon poly);
|
||||
static CrossSection Hull(const Polygons polys);
|
||||
///@}
|
||||
///
|
||||
/** @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 Area() 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,868 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
bool V2Lesser(glm::vec2 a, glm::vec2 b) {
|
||||
if (a.x == b.x) return a.y < b.y;
|
||||
return a.x < b.x;
|
||||
}
|
||||
|
||||
double Cross(glm::vec2 a, glm::vec2 b) { return a.x * b.y - a.y * b.x; }
|
||||
|
||||
double SquaredDistance(glm::vec2 a, glm::vec2 b) {
|
||||
auto d = a - b;
|
||||
return glm::dot(d, d);
|
||||
}
|
||||
|
||||
bool IsCw(glm::vec2 a, glm::vec2 b, glm::vec2 c) {
|
||||
double lhs = Cross(a - c, b - c);
|
||||
double rhs = 1e-18 * SquaredDistance(a, c) * SquaredDistance(b, c);
|
||||
return lhs * std::abs(lhs) <= rhs;
|
||||
}
|
||||
|
||||
void HullBacktrack(const SimplePolygon& pts, const int idx,
|
||||
std::vector<int>& keep, const int hold) {
|
||||
const int stop = keep.size() - hold;
|
||||
int i = 0;
|
||||
while (i < stop && !IsCw(pts[idx], pts[keep[keep.size() - 1]],
|
||||
pts[keep[keep.size() - 2]])) {
|
||||
keep.pop_back();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Based on method described here:
|
||||
// https://www.hackerearth.com/practice/math/geometry/line-sweep-technique/tutorial/
|
||||
C2::PathD HullImpl(SimplePolygon& pts) {
|
||||
int len = pts.size();
|
||||
if (len < 3) return C2::PathD(); // not enough points to create a polygon
|
||||
std::sort(pts.begin(), pts.end(), V2Lesser);
|
||||
auto keep = std::vector<int>{0, 1};
|
||||
for (int i = 2; i < len; i++) {
|
||||
HullBacktrack(pts, i, keep, 1);
|
||||
keep.push_back(i);
|
||||
}
|
||||
int nLower = keep.size();
|
||||
for (int i = 0; i < len - 1; i++) {
|
||||
int idx = len - 2 - i;
|
||||
HullBacktrack(pts, idx, keep, nLower);
|
||||
if (idx > 0) keep.push_back(idx);
|
||||
}
|
||||
auto path = C2::PathD(keep.size());
|
||||
for (int i = 0; i < keep.size(); i++) {
|
||||
path[i] = v2_to_pd(pts[keep[i]]);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
} // 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_));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from an axis-aligned rectangle (bounding box).
|
||||
*
|
||||
* @param rect An axis-aligned rectangular bounding box.
|
||||
*/
|
||||
CrossSection::CrossSection(const Rect& rect) {
|
||||
C2::PathD p(4);
|
||||
p[0] = C2::PointD(rect.min.x, rect.min.y);
|
||||
p[1] = C2::PointD(rect.max.x, rect.min.y);
|
||||
p[2] = C2::PointD(rect.max.x, rect.max.y);
|
||||
p[3] = C2::PointD(rect.min.x, rect.max.y);
|
||||
paths_ = shared_paths(C2::PathsD{p});
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 circularSegments Number of segments per 360 degrees of
|
||||
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
|
||||
* will be added to each contour). Default is calculated by the static Quality
|
||||
* defaults according to the radius.
|
||||
*/
|
||||
CrossSection CrossSection::Offset(double delta, JoinType jointype,
|
||||
double miter_limit,
|
||||
int circularSegments) const {
|
||||
double arc_tol = 0.;
|
||||
if (jointype == JoinType::Round) {
|
||||
int n = circularSegments > 2 ? circularSegments
|
||||
: Quality::GetCircularSegments(delta);
|
||||
// This calculates tolerance as a function of circular segments and delta
|
||||
// (radius) in order to get back the same number of segments in Clipper2:
|
||||
// steps_per_360 = PI / acos(1 - arc_tol / abs_delta)
|
||||
const double abs_delta = std::fabs(delta);
|
||||
const double scaled_delta = abs_delta * std::pow(10, precision_);
|
||||
arc_tol = (std::cos(Clipper2Lib::PI / n) - 1) * -scaled_delta;
|
||||
}
|
||||
auto ps =
|
||||
C2::InflatePaths(GetPaths(), delta, jt(jointype), C2::EndType::Polygon,
|
||||
miter_limit, precision_, arc_tol);
|
||||
return CrossSection(ps);
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Hull(
|
||||
const std::vector<CrossSection>& crossSections) {
|
||||
int n = 0;
|
||||
for (auto cs : crossSections) n += cs.NumVert();
|
||||
SimplePolygon pts;
|
||||
pts.reserve(n);
|
||||
for (auto cs : crossSections) {
|
||||
auto paths = cs.GetPaths();
|
||||
for (auto path : paths) {
|
||||
for (auto p : path) {
|
||||
pts.push_back(v2_of_pd(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
return CrossSection(C2::PathsD{HullImpl(pts)});
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Hull() const {
|
||||
return Hull(std::vector<CrossSection>{*this});
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Hull(SimplePolygon pts) {
|
||||
return CrossSection(C2::PathsD{HullImpl(pts)});
|
||||
}
|
||||
|
||||
CrossSection CrossSection::Hull(const Polygons polys) {
|
||||
SimplePolygon pts;
|
||||
for (auto poly : polys) {
|
||||
for (auto p : poly) {
|
||||
pts.push_back(p);
|
||||
}
|
||||
}
|
||||
return Hull(pts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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; }
|
||||
|
||||
/**
|
||||
* Return the area of the rectangle.
|
||||
*/
|
||||
float Rect::Area() const {
|
||||
auto sz = Size();
|
||||
return sz.x * sz.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 on border) 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 { return CrossSection(*this); }
|
||||
|
||||
} // 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 quickhull
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
@@ -32,6 +33,80 @@ ExecutionParams& ManifoldParams();
|
||||
class CsgNode;
|
||||
class CsgLeafNode;
|
||||
|
||||
/** @ingroup Connections
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly. This may not be manifold since the verts are duplicated
|
||||
* along property boundaries that do not match. The additional merge vectors
|
||||
* store this missing information, allowing the manifold to be reconstructed.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of property vertices
|
||||
uint32_t NumVert() const { return vertProperties.size() / numProp; };
|
||||
/// Number of triangles
|
||||
uint32_t NumTri() const { return triVerts.size() / 3; };
|
||||
|
||||
/// Number of properties per vertex, always >= 3.
|
||||
uint32_t numProp = 3;
|
||||
/// Flat, GL-style interleaved list of all vertex properties: propVal =
|
||||
/// vertProperties[vert * numProp + propIdx]. The first three properties are
|
||||
/// always the position x, y, z.
|
||||
std::vector<float> vertProperties;
|
||||
/// The vertex indices of the three triangle corners in CCW (from the outside)
|
||||
/// order, for each triangle.
|
||||
std::vector<uint32_t> triVerts;
|
||||
/// Optional: A list of only the vertex indicies that need to be merged to
|
||||
/// reconstruct the manifold.
|
||||
std::vector<uint32_t> mergeFromVert;
|
||||
/// Optional: The same length as mergeFromVert, and the corresponding value
|
||||
/// contains the vertex to merge with. It will have an identical position, but
|
||||
/// the other properties may differ.
|
||||
std::vector<uint32_t> mergeToVert;
|
||||
/// Optional: Indicates runs of triangles that correspond to a particular
|
||||
/// input mesh instance. The runs encompass all of triVerts and are sorted
|
||||
/// by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at
|
||||
/// triVerts[runIndex[i+1]]. All runIndex values are divisible by 3.
|
||||
std::vector<uint32_t> runIndex;
|
||||
/// Optional: The OriginalID of the mesh this triangle run came from. This ID
|
||||
/// is ideal for reapplying materials to the output mesh. Multiple runs may
|
||||
/// have the same ID, e.g. representing different copies of the same input
|
||||
/// mesh. If you create an input MeshGL that you want to be able to reference
|
||||
/// as one or more originals, be sure to set unique values from ReserveIDs().
|
||||
std::vector<uint32_t> runOriginalID;
|
||||
/// Optional: For each run, a 3x4 transform is stored representing how the
|
||||
/// corresponding original mesh was transformed to create this triangle run.
|
||||
/// This matrix is stored in column-major order and the length of the overall
|
||||
/// vector is 12 * runOriginalID.size().
|
||||
std::vector<float> runTransform;
|
||||
/// Optional: Length NumTri, contains an ID of the source face this triangle
|
||||
/// comes from. When auto-generated, this ID will be a triangle index into the
|
||||
/// original mesh. All neighboring coplanar triangles from that input mesh
|
||||
/// will refer to a single triangle of that group as the faceID. When
|
||||
/// supplying faceIDs, ensure that triangles with the same ID are in fact
|
||||
/// coplanar and have consistent properties (within some tolerance) or the
|
||||
/// output will be surprising.
|
||||
std::vector<uint32_t> faceID;
|
||||
/// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If
|
||||
/// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
|
||||
/// as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value
|
||||
/// Mesh.triVerts[tri][i] along the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<float> halfedgeTangent;
|
||||
/// The absolute precision of the vertex positions, based on accrued rounding
|
||||
/// errors. When creating a Manifold, the precision used will be the maximum
|
||||
/// of this and a baseline precision from the size of the bounding box. Any
|
||||
/// edge shorter than precision may be collapsed.
|
||||
float precision = 0;
|
||||
|
||||
MeshGL() = default;
|
||||
MeshGL(const Mesh& mesh);
|
||||
|
||||
bool Merge();
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @defgroup Core
|
||||
* @brief The central classes of the library
|
||||
* @{
|
||||
@@ -62,11 +137,12 @@ 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,
|
||||
int circularSegments = 0);
|
||||
static Manifold Revolve(const CrossSection& crossSection,
|
||||
int circularSegments = 0,
|
||||
float revolveDegrees = 360.0f);
|
||||
///@}
|
||||
|
||||
/** @name Topological
|
||||
@@ -74,27 +150,9 @@ 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
|
||||
*/
|
||||
@@ -114,6 +172,7 @@ class Manifold {
|
||||
TransformWrongLength,
|
||||
RunIndexWrongLength,
|
||||
FaceIDWrongLength,
|
||||
InvalidConstruction,
|
||||
};
|
||||
Error Status() const;
|
||||
int NumVert() const;
|
||||
@@ -125,7 +184,6 @@ class Manifold {
|
||||
float Precision() const;
|
||||
int Genus() const;
|
||||
Properties GetProperties() const;
|
||||
Curvature GetCurvature() const;
|
||||
///@}
|
||||
|
||||
/** @name Mesh ID
|
||||
@@ -146,7 +204,11 @@ 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 SetProperties(
|
||||
int, std::function<void(float*, glm::vec3, const float*)>) const;
|
||||
Manifold CalculateCurvature(int gaussianIdx, int meanIdx) const;
|
||||
Manifold Refine(int) const;
|
||||
// Manifold RefineToLength(float);
|
||||
// Manifold RefineToPrecision(float);
|
||||
@@ -156,10 +218,6 @@ 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);
|
||||
@@ -176,11 +234,14 @@ class Manifold {
|
||||
Manifold TrimByPlane(glm::vec3 normal, float originOffset) const;
|
||||
///@}
|
||||
|
||||
Manifold Hull() const;
|
||||
static Manifold Hull(const std::vector<Manifold>& manifolds);
|
||||
static Manifold Hull(const std::vector<glm::vec3>& pts);
|
||||
|
||||
/** @name Testing hooks
|
||||
* These are just for internal testing.
|
||||
*/
|
||||
///@{
|
||||
bool IsManifold() const;
|
||||
bool MatchesTriNormals() const;
|
||||
int NumDegenerateTris() const;
|
||||
int NumOverlaps(const Manifold& second) const;
|
||||
@@ -191,14 +252,10 @@ 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_;
|
||||
|
||||
@@ -481,7 +481,10 @@ void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
outR.halfedge_.cptrD(), outR.precision_}));
|
||||
|
||||
std::map<std::tuple<int, int, int, int>, int> propIdx;
|
||||
using Entry = std::pair<glm::ivec3, int>;
|
||||
int idMissProp = outR.NumVert();
|
||||
std::vector<std::vector<Entry>> propIdx(outR.NumVert() + 1);
|
||||
outR.meshRelation_.properties.reserve(outR.NumVert() * numProp);
|
||||
int idx = 0;
|
||||
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
@@ -490,47 +493,61 @@ void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
|
||||
const int triPQ = refPQ[tri].tri;
|
||||
const bool PQ = refPQ[tri].meshID == 0;
|
||||
const int oldNumProp = PQ ? numPropP : numPropQ;
|
||||
const auto &properties =
|
||||
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties;
|
||||
const auto &triProp = PQ ? inP.meshRelation_.triProperties[triPQ]
|
||||
: inQ.meshRelation_.triProperties[triPQ];
|
||||
const glm::ivec3 &triProp = oldNumProp == 0 ? glm::ivec3(-1)
|
||||
: PQ ? inP.meshRelation_.triProperties[triPQ]
|
||||
: inQ.meshRelation_.triProperties[triPQ];
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = outR.halfedge_[3 * tri + i].startVert;
|
||||
const glm::vec3 uvw = bary[3 * tri + i];
|
||||
const glm::vec3 &uvw = bary[3 * tri + i];
|
||||
|
||||
auto key = std::make_tuple(PQ, vert, -1, -1);
|
||||
int edge = -1;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (uvw[j] == 1) {
|
||||
// On a retained vert, the propVert must also match
|
||||
std::get<2>(key) = triProp[j];
|
||||
edge = -1;
|
||||
glm::ivec4 key(PQ, idMissProp, -1, -1);
|
||||
if (oldNumProp > 0) {
|
||||
key[1] = vert;
|
||||
int edge = -1;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (uvw[j] == 1) {
|
||||
// On a retained vert, the propVert must also match
|
||||
key[2] = triProp[j];
|
||||
edge = -1;
|
||||
break;
|
||||
}
|
||||
if (uvw[j] == 0) edge = j;
|
||||
}
|
||||
if (edge >= 0) {
|
||||
// On an edge, both propVerts must match
|
||||
const int p0 = triProp[Next3(edge)];
|
||||
const int p1 = triProp[Prev3(edge)];
|
||||
key[2] = glm::min(p0, p1);
|
||||
key[3] = glm::max(p0, p1);
|
||||
}
|
||||
}
|
||||
|
||||
auto &bin = propIdx[key.y];
|
||||
bool bFound = false;
|
||||
for (int k = 0; k < bin.size(); ++k) {
|
||||
if (bin[k].first == glm::ivec3(key.x, key.z, key.w)) {
|
||||
bFound = true;
|
||||
outR.meshRelation_.triProperties[tri][i] = bin[k].second;
|
||||
break;
|
||||
}
|
||||
if (uvw[j] == 0) edge = j;
|
||||
}
|
||||
if (edge >= 0) {
|
||||
// On an edge, both propVerts must match
|
||||
const int p0 = triProp[Next3(edge)];
|
||||
const int p1 = triProp[Prev3(edge)];
|
||||
std::get<2>(key) = glm::min(p0, p1);
|
||||
std::get<3>(key) = glm::max(p0, p1);
|
||||
}
|
||||
|
||||
const auto it = propIdx.find(key);
|
||||
if (it != propIdx.end()) {
|
||||
outR.meshRelation_.triProperties[tri][i] = it->second;
|
||||
continue;
|
||||
}
|
||||
outR.meshRelation_.triProperties[tri][i] = idx;
|
||||
propIdx.insert({key, idx++});
|
||||
if (bFound) continue;
|
||||
bin.push_back(std::make_pair(glm::ivec3(key.x, key.z, key.w), idx));
|
||||
outR.meshRelation_.triProperties[tri][i] = idx++;
|
||||
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
glm::vec3 oldProps;
|
||||
for (const int j : {0, 1, 2})
|
||||
oldProps[j] = properties[numProp * triProp[j] + p];
|
||||
outR.meshRelation_.properties.push_back(glm::dot(uvw, oldProps));
|
||||
if (p < oldNumProp) {
|
||||
glm::vec3 oldProps;
|
||||
for (const int j : {0, 1, 2})
|
||||
oldProps[j] = properties[oldNumProp * triProp[j] + p];
|
||||
outR.meshRelation_.properties.push_back(glm::dot(uvw, oldProps));
|
||||
} else {
|
||||
outR.meshRelation_.properties.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -539,31 +556,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 (inP_.IsEmpty()) {
|
||||
if (!inQ_.IsEmpty() && op == OpType::Add) {
|
||||
return inQ_;
|
||||
}
|
||||
return Manifold::Impl();
|
||||
} else if (w30_.size() == 0) {
|
||||
if (op == Manifold::OpType::Intersect) {
|
||||
} else if (inQ_.IsEmpty()) {
|
||||
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());
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <thrust/sequence.h>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "csg_tree.h"
|
||||
#include "graph.h"
|
||||
#include "impl.h"
|
||||
@@ -142,12 +143,17 @@ Manifold Manifold::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) {
|
||||
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);
|
||||
@@ -169,14 +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)));
|
||||
}
|
||||
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)
|
||||
@@ -196,8 +203,11 @@ 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;
|
||||
: 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(),
|
||||
@@ -223,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);
|
||||
|
||||
@@ -236,15 +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;
|
||||
PolygonsIdx crossSectionIndexed;
|
||||
for (auto& poly : crossSection) {
|
||||
PolygonsIdx polygonsIndexed;
|
||||
for (auto& poly : polygons) {
|
||||
nCrossSection += poly.size();
|
||||
SimplePolygonIdx simpleIndexed;
|
||||
for (const glm::vec2& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.x, polyVert.y, 0.0f});
|
||||
simpleIndexed.push_back({polyVert, idx++});
|
||||
}
|
||||
crossSectionIndexed.push_back(simpleIndexed);
|
||||
polygonsIndexed.push_back(simpleIndexed);
|
||||
}
|
||||
for (int i = 1; i < nDivisions + 1; ++i) {
|
||||
float alpha = i / float(nDivisions);
|
||||
@@ -254,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;
|
||||
@@ -275,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(crossSectionIndexed);
|
||||
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);
|
||||
@@ -301,86 +317,122 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
* @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.
|
||||
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
|
||||
*/
|
||||
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
Manifold Manifold::Revolve(const CrossSection& crossSection,
|
||||
int circularSegments, float revolveDegrees) {
|
||||
Polygons polygons = crossSection.ToPolygons();
|
||||
|
||||
if (polygons.size() == 0) {
|
||||
return Invalid();
|
||||
}
|
||||
|
||||
const Rect bounds = crossSection.Bounds();
|
||||
|
||||
if (bounds.max.x <= 0) {
|
||||
return Invalid();
|
||||
} else if (bounds.min.x < 0) {
|
||||
// Take the x>=0 slice.
|
||||
glm::vec2 min = bounds.min;
|
||||
glm::vec2 max = bounds.max;
|
||||
CrossSection posBoundingBox = CrossSection(
|
||||
{{0.0, min.y}, {max.x, min.y}, {max.x, max.y}, {0.0, max.y}});
|
||||
|
||||
polygons = (crossSection ^ posBoundingBox).ToPolygons();
|
||||
}
|
||||
|
||||
float radius = 0.0f;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& poly : polygons) {
|
||||
for (const auto& vert : poly) {
|
||||
radius = fmax(radius, vert.x);
|
||||
}
|
||||
}
|
||||
int nDivisions =
|
||||
circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
|
||||
if (revolveDegrees > 360.0f) {
|
||||
revolveDegrees = 360.0f;
|
||||
}
|
||||
const bool isFullRevolution = revolveDegrees == 360.0f;
|
||||
|
||||
const 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) {
|
||||
int start = -1;
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
if (poly[polyVert].x <= 0) {
|
||||
start = polyVert;
|
||||
break;
|
||||
|
||||
std::vector<int> startPoses;
|
||||
std::vector<int> endPoses;
|
||||
|
||||
const float dPhi = revolveDegrees / nDivisions;
|
||||
// first and last slice are distinguished if not a full revolution.
|
||||
const int nSlices = isFullRevolution ? nDivisions : nDivisions + 1;
|
||||
|
||||
for (const auto& poly : polygons) {
|
||||
std::size_t nPosVerts = 0;
|
||||
std::size_t nRevolveAxisVerts = 0;
|
||||
for (auto& pt : poly) {
|
||||
if (pt.x > 0) {
|
||||
nPosVerts++;
|
||||
} else {
|
||||
nRevolveAxisVerts++;
|
||||
}
|
||||
}
|
||||
if (start == -1) { // poly all positive
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
int startVert = vertPos.size();
|
||||
int lastStart =
|
||||
startVert +
|
||||
(polyVert == 0 ? nDivisions * (poly.size() - 1) : -nDivisions);
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
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});
|
||||
triVerts.push_back(
|
||||
{lastStart + lastSlice, lastStart + slice, startVert + slice});
|
||||
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
const int startPosIndex = vertPos.size();
|
||||
|
||||
if (!isFullRevolution) startPoses.push_back(startPosIndex);
|
||||
|
||||
const int prevStartPosIndex =
|
||||
startPosIndex +
|
||||
(polyVert == 0 ? nRevolveAxisVerts + (nSlices * nPosVerts) - nSlices
|
||||
: poly[polyVert - 1].x == 0.0 ? -1
|
||||
: -nSlices);
|
||||
glm::vec2 currPolyVertex = poly[polyVert];
|
||||
glm::vec2 prevPolyVertex =
|
||||
poly[polyVert == 0 ? poly.size() - 1 : polyVert - 1];
|
||||
|
||||
for (int slice = 0; slice < nSlices; ++slice) {
|
||||
const float phi = slice * dPhi;
|
||||
if (slice == 0 || currPolyVertex.x > 0) {
|
||||
vertPos.push_back({currPolyVertex.x * cosd(phi),
|
||||
currPolyVertex.x * sind(phi), currPolyVertex.y});
|
||||
}
|
||||
|
||||
if (isFullRevolution || slice > 0) {
|
||||
const int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
if (currPolyVertex.x > 0.0) {
|
||||
triVerts.push_back(
|
||||
{startPosIndex + slice, startPosIndex + lastSlice,
|
||||
// "Reuse" vertex of first slice if it lies on the revolve axis
|
||||
(prevPolyVertex.x == 0.0 ? prevStartPosIndex
|
||||
: prevStartPosIndex + lastSlice)});
|
||||
}
|
||||
|
||||
if (prevPolyVertex.x > 0.0) {
|
||||
triVerts.push_back(
|
||||
{prevStartPosIndex + lastSlice, prevStartPosIndex + slice,
|
||||
(currPolyVertex.x == 0.0 ? startPosIndex
|
||||
: startPosIndex + slice)});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // poly crosses zero
|
||||
int polyVert = start;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
do {
|
||||
glm::vec2 lastPos = pos;
|
||||
polyVert = (polyVert + 1) % poly.size();
|
||||
pos = poly[polyVert];
|
||||
if (pos.x > 0) {
|
||||
if (lastPos.x <= 0) {
|
||||
float a = pos.x / (pos.x - lastPos.x);
|
||||
vertPos.push_back({0.0f, 0.0f, glm::mix(pos.y, lastPos.y, a)});
|
||||
}
|
||||
int startVert = vertPos.size();
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
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,
|
||||
startVert - nDivisions + lastSlice});
|
||||
triVerts.push_back({startVert - nDivisions + lastSlice,
|
||||
startVert - nDivisions + slice,
|
||||
startVert + slice});
|
||||
} else {
|
||||
triVerts.push_back(
|
||||
{startVert - 1, startVert + slice, startVert + lastSlice});
|
||||
}
|
||||
}
|
||||
} else if (lastPos.x > 0) {
|
||||
int startVert = vertPos.size();
|
||||
float a = pos.x / (pos.x - lastPos.x);
|
||||
vertPos.push_back({0.0f, 0.0f, glm::mix(pos.y, lastPos.y, a)});
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
triVerts.push_back({startVert, startVert - nDivisions + lastSlice,
|
||||
startVert - nDivisions + slice});
|
||||
}
|
||||
}
|
||||
} while (polyVert != start);
|
||||
if (!isFullRevolution) endPoses.push_back(vertPos.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Add front and back triangles if not a full revolution.
|
||||
if (!isFullRevolution) {
|
||||
std::vector<glm::ivec3> frontTriangles =
|
||||
Triangulate(polygons, pImpl_->precision_);
|
||||
for (auto& t : frontTriangles) {
|
||||
triVerts.push_back({startPoses[t.x], startPoses[t.y], startPoses[t.z]});
|
||||
}
|
||||
|
||||
for (auto& t : frontTriangles) {
|
||||
triVerts.push_back({endPoses[t.z], endPoses[t.y], endPoses[t.x]});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +459,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) {
|
||||
@@ -417,23 +474,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_;
|
||||
@@ -468,13 +520,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
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "boolean3.h"
|
||||
#include "impl.h"
|
||||
#include "mesh_fixes.h"
|
||||
#include "par.h"
|
||||
|
||||
namespace {
|
||||
@@ -30,16 +31,6 @@ struct Transform4x3 {
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformNormals {
|
||||
const glm::mat3 transform;
|
||||
|
||||
__host__ __device__ glm::vec3 operator()(glm::vec3 normal) {
|
||||
normal = glm::normalize(transform * normal);
|
||||
if (isnan(normal.x)) normal = glm::vec3(0.0f);
|
||||
return normal;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateHalfedge {
|
||||
const int nextVert;
|
||||
const int nextEdge;
|
||||
@@ -54,6 +45,15 @@ struct UpdateHalfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateTriProp {
|
||||
const int nextProp;
|
||||
|
||||
__host__ __device__ glm::ivec3 operator()(glm::ivec3 tri) {
|
||||
tri += nextProp;
|
||||
return tri;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshIDs {
|
||||
const int offset;
|
||||
|
||||
@@ -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;
|
||||
@@ -138,6 +151,12 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 0;
|
||||
int numPropVert = 0;
|
||||
std::vector<int> vertIndices;
|
||||
std::vector<int> edgeIndices;
|
||||
std::vector<int> triIndices;
|
||||
std::vector<int> propVertIndices;
|
||||
int numPropOut = 0;
|
||||
for (auto &node : nodes) {
|
||||
float nodeOldScale = node->pImpl_->bBox_.Scale();
|
||||
float nodeNewScale =
|
||||
@@ -148,9 +167,18 @@ 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);
|
||||
propVertIndices.push_back(numPropVert);
|
||||
numVert += node->pImpl_->NumVert();
|
||||
numEdge += node->pImpl_->NumEdge();
|
||||
numTri += node->pImpl_->NumTri();
|
||||
const int numProp = node->pImpl_->NumProp();
|
||||
numPropOut = glm::max(numPropOut, numProp);
|
||||
numPropVert +=
|
||||
numProp == 0 ? 1
|
||||
: node->pImpl_->meshRelation_.properties.size() / numProp;
|
||||
}
|
||||
|
||||
Manifold::Impl combined;
|
||||
@@ -160,56 +188,115 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
combined.faceNormal_.resize(numTri);
|
||||
combined.halfedgeTangent_.resize(2 * numEdge);
|
||||
combined.meshRelation_.triRef.resize(numTri);
|
||||
if (numPropOut > 0) {
|
||||
combined.meshRelation_.numProp = numPropOut;
|
||||
combined.meshRelation_.properties.resize(numPropOut * numPropVert, 0);
|
||||
combined.meshRelation_.triProperties.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;
|
||||
|
||||
for_each_n_host(
|
||||
nodes.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
|
||||
countAt(0), nodes.size(),
|
||||
[&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices,
|
||||
numPropOut, &combined, policy](int i) {
|
||||
auto &node = nodes[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]}));
|
||||
|
||||
if (numPropOut > 0) {
|
||||
auto start =
|
||||
combined.meshRelation_.triProperties.begin() + triIndices[i];
|
||||
if (node->pImpl_->NumProp() > 0) {
|
||||
auto &triProp = node->pImpl_->meshRelation_.triProperties;
|
||||
transform(policy, triProp.begin(), triProp.end(), start,
|
||||
UpdateTriProp({propVertIndices[i]}));
|
||||
|
||||
const int numProp = node->pImpl_->NumProp();
|
||||
auto &oldProp = node->pImpl_->meshRelation_.properties;
|
||||
auto &newProp = combined.meshRelation_.properties;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
strided_range<VecDH<float>::IterC> oldRange(
|
||||
oldProp.begin() + p, oldProp.end(), numProp);
|
||||
strided_range<VecDH<float>::Iter> newRange(
|
||||
newProp.begin() + numPropOut * propVertIndices[i] + p,
|
||||
newProp.end(), numPropOut);
|
||||
copy(policy, oldRange.begin(), oldRange.end(), newRange.begin());
|
||||
}
|
||||
} else {
|
||||
// point all triangles at single new property of zeros.
|
||||
fill(policy, start, start + node->pImpl_->NumTri(),
|
||||
glm::ivec3(propVertIndices[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]);
|
||||
|
||||
const bool invert = glm::determinant(glm::mat3(node->transform_)) < 0;
|
||||
for_each_n(policy,
|
||||
zip(combined.halfedgeTangent_.begin() + edgeIndices[i],
|
||||
countAt(0)),
|
||||
node->pImpl_->halfedgeTangent_.size(),
|
||||
TransformTangents{glm::mat3(node->transform_), invert,
|
||||
node->pImpl_->halfedgeTangent_.cptrD(),
|
||||
node->pImpl_->halfedge_.cptrD()});
|
||||
if (invert)
|
||||
for_each_n(policy,
|
||||
zip(combined.meshRelation_.triRef.begin(),
|
||||
countAt(triIndices[i])),
|
||||
node->pImpl_->NumTri(),
|
||||
FlipTris({combined.halfedge_.ptrD()}));
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
nextVert += node->pImpl_->NumVert();
|
||||
nextEdge += 2 * node->pImpl_->NumEdge();
|
||||
nextTri += node->pImpl_->NumTri();
|
||||
}
|
||||
|
||||
// required to remove parts that are smaller than the precision
|
||||
@@ -222,38 +309,81 @@ 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_) {
|
||||
switch (op_) {
|
||||
case CsgNodeType::Union:
|
||||
BatchUnion();
|
||||
break;
|
||||
@@ -263,9 +393,9 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
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: {
|
||||
@@ -276,16 +406,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:
|
||||
// unreachable
|
||||
break;
|
||||
}
|
||||
} else if (children_.size() == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
// children_ must contain only one CsgLeafNode now, and its Transform will
|
||||
// give CsgLeafNode as well
|
||||
@@ -298,10 +429,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) {
|
||||
@@ -322,10 +453,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>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,7 +479,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)
|
||||
@@ -371,25 +510,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());
|
||||
@@ -398,60 +543,56 @@ void CsgOpNode::BatchUnion() const {
|
||||
|
||||
/**
|
||||
* Flatten the children to a list of leaf nodes and return them.
|
||||
* If finalize is true, the list will be guaranteed to be a list of leaf nodes
|
||||
* (i.e. no ops). Otherwise, the list may contain ops.
|
||||
* Note that this function will not apply the transform to children, as they may
|
||||
* be shared with other nodes.
|
||||
* If forceToLeafNodes is true, the list will be guaranteed to be a list of leaf
|
||||
* nodes (i.e. no ops). Otherwise, the list may contain ops. Note that this
|
||||
* function will not apply the transform to children, as they may be shared with
|
||||
* other nodes.
|
||||
*/
|
||||
std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
|
||||
bool finalize) const {
|
||||
auto &children_ = impl_->children_;
|
||||
if (children_.empty() || (impl_->simplified_ && !finalize) ||
|
||||
impl_->flattened_)
|
||||
return children_;
|
||||
impl_->simplified_ = true;
|
||||
impl_->flattened_ = finalize;
|
||||
std::vector<std::shared_ptr<CsgNode>> newChildren;
|
||||
bool forceToLeafNodes) const {
|
||||
auto impl = impl_.GetGuard();
|
||||
|
||||
CsgNodeType op = impl_->op_;
|
||||
for (auto &child : children_) {
|
||||
if (child->GetNodeType() == op && child.use_count() == 1 &&
|
||||
std::dynamic_pointer_cast<CsgOpNode>(child)->impl_.use_count() == 1) {
|
||||
auto grandchildren =
|
||||
std::dynamic_pointer_cast<CsgOpNode>(child)->GetChildren(finalize);
|
||||
int start = children_.size();
|
||||
for (auto &grandchild : grandchildren) {
|
||||
newChildren.push_back(grandchild->Transform(child->GetTransform()));
|
||||
}
|
||||
} else {
|
||||
if (!finalize || child->GetNodeType() == CsgNodeType::Leaf) {
|
||||
newChildren.push_back(child);
|
||||
} else {
|
||||
newChildren.push_back(child->ToLeafNode());
|
||||
}
|
||||
}
|
||||
// special handling for difference: we treat it as first - (second + third +
|
||||
// ...) so op = Union after the first node
|
||||
if (op == CsgNodeType::Difference) op = CsgNodeType::Union;
|
||||
if (forceToLeafNodes && !impl->forcedToLeafNodes_) {
|
||||
impl->forcedToLeafNodes_ = true;
|
||||
for_each_host(impl->children_.size() > 1 ? ExecutionPolicy::Par
|
||||
: ExecutionPolicy::Seq,
|
||||
impl->children_.begin(), impl->children_.end(),
|
||||
[](auto &child) {
|
||||
if (child->GetNodeType() != CsgNodeType::Leaf) {
|
||||
child = child->ToLeafNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
children_ = newChildren;
|
||||
return children_;
|
||||
return impl->children_;
|
||||
}
|
||||
|
||||
void CsgOpNode::SetOp(Manifold::OpType op) {
|
||||
void CsgOpNode::SetOp(OpType op) {
|
||||
switch (op) {
|
||||
case Manifold::OpType::Add:
|
||||
impl_->op_ = CsgNodeType::Union;
|
||||
case OpType::Add:
|
||||
op_ = CsgNodeType::Union;
|
||||
break;
|
||||
case Manifold::OpType::Subtract:
|
||||
impl_->op_ = CsgNodeType::Difference;
|
||||
case OpType::Subtract:
|
||||
op_ = CsgNodeType::Difference;
|
||||
break;
|
||||
case Manifold::OpType::Intersect:
|
||||
impl_->op_ = CsgNodeType::Intersection;
|
||||
case OpType::Intersect:
|
||||
op_ = CsgNodeType::Intersection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool CsgOpNode::IsOp(OpType op) {
|
||||
switch (op) {
|
||||
case OpType::Add:
|
||||
return op_ == CsgNodeType::Union;
|
||||
case OpType::Subtract:
|
||||
return op_ == CsgNodeType::Difference;
|
||||
case OpType::Intersect:
|
||||
return op_ == CsgNodeType::Intersection;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
glm::mat4x3 CsgOpNode::GetTransform() const { return transform_; }
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#pragma once
|
||||
#include "manifold.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
@@ -21,13 +22,16 @@ 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
|
||||
|
||||
@@ -49,7 +49,7 @@ struct ShortEdge {
|
||||
const glm::vec3* vertPos;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
__host__ __device__ bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag short edges
|
||||
const glm::vec3 delta =
|
||||
@@ -62,7 +62,7 @@ struct FlagEdge {
|
||||
const Halfedge* halfedge;
|
||||
const TriRef* triRef;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
__host__ __device__ bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag redundant edges - those where the startVert is surrounded by only
|
||||
// two original triangles.
|
||||
@@ -87,7 +87,7 @@ struct SwappableEdge {
|
||||
const glm::vec3* triNormal;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
__host__ __device__ bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
|
||||
int tri = halfedge[edge].face;
|
||||
@@ -110,6 +110,15 @@ struct SwappableEdge {
|
||||
Is01Longest(v[0], v[1], v[2]);
|
||||
}
|
||||
};
|
||||
|
||||
struct SortEntry {
|
||||
int start;
|
||||
int end;
|
||||
int index;
|
||||
inline bool operator<(const SortEntry& other) const {
|
||||
return start == other.start ? end < other.end : start < other.start;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
@@ -136,56 +145,96 @@ namespace manifold {
|
||||
void Manifold::Impl::SimplifyTopology() {
|
||||
if (!halfedge_.size()) return;
|
||||
|
||||
int nbEdges = halfedge_.size();
|
||||
int numFlagged = 0;
|
||||
VecDH<uint8_t> bflags(nbEdges);
|
||||
uint8_t* bflagsPtr = bflags.ptrD();
|
||||
|
||||
VecDH<SortEntry> entries(nbEdges);
|
||||
SortEntry* entriesPtr = entries.ptrD();
|
||||
|
||||
auto policy = autoPolicy(halfedge_.size());
|
||||
for_each_n(policy, countAt(0), nbEdges, [=] __host__ __device__(int i) {
|
||||
entriesPtr[i].start = halfedge_[i].startVert;
|
||||
entriesPtr[i].end = halfedge_[i].endVert;
|
||||
entriesPtr[i].index = i;
|
||||
});
|
||||
|
||||
VecDH<Halfedge> halfedge(halfedge_);
|
||||
VecDH<int> idx(halfedge_.size());
|
||||
sequence(policy, idx.begin(), idx.end());
|
||||
sort_by_key(policy, halfedge.begin(), halfedge.end(), idx.begin());
|
||||
|
||||
VecDH<int> flaggedEdges(halfedge_.size());
|
||||
|
||||
int numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, idx.begin(), idx.end() - 1, countAt(0), flaggedEdges.begin(),
|
||||
DuplicateEdge({halfedge.cptrD()})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges) DedupeEdge(edge);
|
||||
|
||||
flaggedEdges.resize(halfedge_.size());
|
||||
numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
|
||||
ShortEdge({halfedge_.cptrD(), vertPos_.cptrD(), precision_})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges) CollapseEdge(edge);
|
||||
|
||||
flaggedEdges.resize(halfedge_.size());
|
||||
numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triRef.cptrD()})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges) CollapseEdge(edge);
|
||||
|
||||
flaggedEdges.resize(halfedge_.size());
|
||||
numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
|
||||
SwappableEdge({halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
faceNormal_.cptrD(), precision_})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges) {
|
||||
RecursiveEdgeSwap(edge);
|
||||
sort(policy, entries.begin(), entries.end());
|
||||
for (int i = 0; i < nbEdges - 1; ++i) {
|
||||
if (entries[i].start == entries[i + 1].start &&
|
||||
entries[i].end == entries[i + 1].end) {
|
||||
DedupeEdge(entries[i].index);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " duplicate edges to split"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
numFlagged = 0;
|
||||
ShortEdge se{halfedge_.cptrD(), vertPos_.cptrD(), precision_};
|
||||
for_each_n(policy, countAt(0), nbEdges,
|
||||
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
|
||||
for (int i = 0; i < nbEdges; ++i) {
|
||||
if (bflags[i]) {
|
||||
CollapseEdge(i);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " short edges to collapse"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
numFlagged = 0;
|
||||
FlagEdge se{halfedge_.cptrD(), meshRelation_.triRef.cptrD()};
|
||||
for_each_n(policy, countAt(0), nbEdges,
|
||||
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
|
||||
for (int i = 0; i < nbEdges; ++i) {
|
||||
if (bflags[i]) {
|
||||
CollapseEdge(i);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " colinear edges to collapse"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
numFlagged = 0;
|
||||
SwappableEdge se{halfedge_.cptrD(), vertPos_.cptrD(), faceNormal_.cptrD(),
|
||||
precision_};
|
||||
for_each_n(policy, countAt(0), nbEdges,
|
||||
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
|
||||
for (int i = 0; i < nbEdges; ++i) {
|
||||
if (bflags[i]) {
|
||||
RecursiveEdgeSwap(i);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (ManifoldParams().verbose && numFlagged > 0) {
|
||||
std::cout << "found " << numFlagged << " edges to swap" << std::endl;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
@@ -253,11 +302,13 @@ void Manifold::Impl::PairUp(int edge0, int edge1) {
|
||||
// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point
|
||||
// to vert instead.
|
||||
void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {
|
||||
while (startEdge != endEdge) {
|
||||
halfedge_[startEdge].endVert = vert;
|
||||
startEdge = NextHalfedge(startEdge);
|
||||
halfedge_[startEdge].startVert = vert;
|
||||
startEdge = halfedge_[startEdge].pairedHalfedge;
|
||||
int current = startEdge;
|
||||
while (current != endEdge) {
|
||||
halfedge_[current].endVert = vert;
|
||||
current = NextHalfedge(current);
|
||||
halfedge_[current].startVert = vert;
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
ASSERT(current != startEdge, logicErr, "infinite loop in decimator!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,8 +349,23 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
const glm::ivec3 tri0edge = TriOf(edge);
|
||||
const glm::ivec3 tri1edge = TriOf(halfedge_[edge].pairedHalfedge);
|
||||
if (halfedge_[tri0edge[1]].endVert == halfedge_[tri1edge[1]].endVert) {
|
||||
if (halfedge_[tri0edge[1]].pairedHalfedge == tri1edge[2]) {
|
||||
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
|
||||
for (int i : {0, 1, 2})
|
||||
vertPos_[halfedge_[tri0edge[i]].startVert] = glm::vec3(NAN);
|
||||
} else {
|
||||
vertPos_[halfedge_[tri0edge[1]].startVert] = glm::vec3(NAN);
|
||||
}
|
||||
} else {
|
||||
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
|
||||
vertPos_[halfedge_[tri1edge[1]].startVert] = glm::vec3(NAN);
|
||||
}
|
||||
}
|
||||
PairUp(halfedge_[tri0edge[1]].pairedHalfedge,
|
||||
halfedge_[tri1edge[2]].pairedHalfedge);
|
||||
PairUp(halfedge_[tri0edge[2]].pairedHalfedge,
|
||||
halfedge_[tri1edge[1]].pairedHalfedge);
|
||||
for (int i : {0, 1, 2}) {
|
||||
vertPos_[halfedge_[tri0edge[i]].startVert] = glm::vec3(NAN);
|
||||
halfedge_[tri0edge[i]] = {-1, -1, -1, -1};
|
||||
halfedge_[tri1edge[i]] = {-1, -1, -1, -1};
|
||||
}
|
||||
@@ -519,4 +585,27 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
RecursiveEdgeSwap(halfedge_[tri0edge[1]].pairedHalfedge);
|
||||
RecursiveEdgeSwap(halfedge_[tri1edge[0]].pairedHalfedge);
|
||||
}
|
||||
|
||||
void Manifold::Impl::SplitPinchedVerts() {
|
||||
std::vector<bool> vertProcessed(NumVert(), false);
|
||||
std::vector<bool> halfedgeProcessed(halfedge_.size(), false);
|
||||
for (int i = 0; i < halfedge_.size(); ++i) {
|
||||
if (halfedgeProcessed[i]) continue;
|
||||
int vert = halfedge_[i].startVert;
|
||||
if (vertProcessed[vert]) {
|
||||
vertPos_.push_back(vertPos_[vert]);
|
||||
vert = NumVert() - 1;
|
||||
} else {
|
||||
vertProcessed[vert] = true;
|
||||
}
|
||||
int current = i;
|
||||
do {
|
||||
halfedgeProcessed[current] = true;
|
||||
halfedge_[current].startVert = vert;
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
halfedge_[current].endVert = vert;
|
||||
current = NextHalfedge(current);
|
||||
} while (current != i);
|
||||
}
|
||||
}
|
||||
} // namespace manifold
|
||||
|
||||
@@ -121,7 +121,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& 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);
|
||||
|
||||