Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6801c82ea | ||
|
|
a67aa13133 | ||
|
|
d43709715d | ||
|
|
2ddbd0a1a0 | ||
|
|
dc4afa950b | ||
|
|
c03a5ac3ad | ||
|
|
cae5a01af1 | ||
|
|
d59b9b93bd | ||
|
|
f3c5ada9f0 | ||
|
|
4395ab622c | ||
|
|
994956d7c3 | ||
|
|
79f735c59a | ||
|
|
dd67e992bd | ||
|
|
5bdb8bc233 | ||
|
|
c9ae400647 | ||
|
|
9c1517e54e | ||
|
|
3b8282e1d5 | ||
|
|
1635864cbc | ||
|
|
65b2ef8198 | ||
|
|
a9055a8abf | ||
|
|
0b7ee87489 | ||
|
|
9690305b8d | ||
|
|
aa88959899 | ||
|
|
dbb0ca76b4 | ||
|
|
9018b2ac45 | ||
|
|
f407e43e5b | ||
|
|
d7cbcb8c2e | ||
|
|
e27c9c100f | ||
|
|
8ad5f8e427 | ||
|
|
afc64f248a | ||
|
|
c0fb3766e4 | ||
|
|
887a5021af | ||
|
|
e83b9bd158 | ||
|
|
2d0c705839 | ||
|
|
b36ec2ce0e | ||
|
|
b8d143dd72 | ||
|
|
be022a58e4 | ||
|
|
30e0af942a | ||
|
|
8c77a6aa05 | ||
|
|
54fa822408 | ||
|
|
7d564cfbc1 | ||
|
|
bc0bded294 | ||
|
|
d52decc4f1 | ||
|
|
8060376e12 | ||
|
|
eba866d266 | ||
|
|
cc4966f062 | ||
|
|
3216a24be8 | ||
|
|
5e566c8c36 | ||
|
|
80edfee978 | ||
|
|
0e0d1c4f69 | ||
|
|
e15f4cf375 | ||
|
|
c9322ca63a | ||
|
|
45826d6c47 | ||
|
|
dd781cdbdf | ||
|
|
638badaf9b | ||
|
|
0149e482bc | ||
|
|
f683e8c9c0 | ||
|
|
1f8e8037f1 | ||
|
|
10a15c1dd4 | ||
|
|
6e1629ddce | ||
|
|
e273f6b09d | ||
|
|
67c468211d | ||
|
|
01c38e42ca | ||
|
|
1991232eb7 | ||
|
|
4551288f95 | ||
|
|
2e29784449 | ||
|
|
2a2cb6f88e | ||
|
|
1a91d8db2a | ||
|
|
82b98d9353 | ||
|
|
7273c9bf0f | ||
|
|
4ff3f7cf68 | ||
|
|
fafe4792da | ||
|
|
b439603eb9 | ||
|
|
b6eea01021 | ||
|
|
b9f7228064 | ||
|
|
0ef5f70434 | ||
|
|
bb6acf1f6e | ||
|
|
5930b5c09d | ||
|
|
a842de97c6 | ||
|
|
2942229b49 | ||
|
|
0856539bfc | ||
|
|
621835683f | ||
|
|
bec8164120 | ||
|
|
062399c1c7 | ||
|
|
d51ee709c2 | ||
|
|
206871ac11 | ||
|
|
2055b9c412 | ||
|
|
5e103931f1 | ||
|
|
77051ead35 | ||
|
|
83b78f8356 | ||
|
|
6c6193c25b | ||
|
|
e79d343bec | ||
|
|
1d6a89ac87 | ||
|
|
8651bf8bd5 | ||
|
|
1514c15184 | ||
|
|
cc83e1b7aa | ||
|
|
111350f1ef | ||
|
|
b4f583fcae | ||
|
|
8011fb6072 | ||
|
|
9290d420b0 | ||
|
|
fca66380f5 | ||
|
|
8497e44915 | ||
|
|
a7eac92087 | ||
|
|
15399474fa | ||
|
|
2d513446f7 | ||
|
|
abff7e8ed3 | ||
|
|
7c454dc7a4 | ||
|
|
ffdd97d4d3 | ||
|
|
7facd0ecc0 | ||
|
|
59760b3369 | ||
|
|
5f9b484b6c | ||
|
|
18edefc6c4 | ||
|
|
93e06a2de8 | ||
|
|
047159e2c4 | ||
|
|
a8626f907c | ||
|
|
2f22b92352 | ||
|
|
a3d7991563 | ||
|
|
2aef6e4728 | ||
|
|
c167a48d36 | ||
|
|
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 | ||
|
|
7c8ce3c935 | ||
|
|
4d434e4f8c | ||
|
|
9e9a22d66b | ||
|
|
893a4f8ac3 | ||
|
|
78c03f2611 | ||
|
|
2915f7aa90 | ||
|
|
95daa54591 | ||
|
|
2c597d1eef | ||
|
|
e2a8e94906 | ||
|
|
eae9653745 | ||
|
|
feb7fb6edd | ||
|
|
32f887d321 | ||
|
|
3a962cfa53 | ||
|
|
36ca7acf6c | ||
|
|
0704f62eaa | ||
|
|
5041c38424 | ||
|
|
c4bf392f66 | ||
|
|
3f42e5e6d7 | ||
|
|
376a7c676f | ||
|
|
e6bb87b2bb | ||
|
|
9477deaa9c | ||
|
|
02affd5247 | ||
|
|
171fe5466e | ||
|
|
5331b68485 | ||
|
|
20d7ef96fb | ||
|
|
3560bbf679 | ||
|
|
4415cfce46 | ||
|
|
bbb1fabcac | ||
|
|
f43dded234 | ||
|
|
05f9ceb999 | ||
|
|
422c7f26d9 | ||
|
|
a701d2be8e | ||
|
|
5b5f56674a | ||
|
|
1b7b365c3a | ||
|
|
812b056179 | ||
|
|
bd46bfd1cf | ||
|
|
a7b457db74 | ||
|
|
87059508e7 |
@@ -0,0 +1,57 @@
|
||||
name: build_wheels
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build_wheels:
|
||||
name: Build wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, windows-latest, macos-latest]
|
||||
steps:
|
||||
- run: |
|
||||
git config --global submodule.fetchJobs 8
|
||||
git config --global core.longpaths true
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.16.5
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
make_sdist:
|
||||
name: Make SDist
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build SDist
|
||||
run: pipx run build --sdist
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: dist/*.tar.gz
|
||||
|
||||
pypi-publish:
|
||||
name: Upload release to PyPI
|
||||
needs: [build_wheels, make_sdist]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/manifold3d
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: artifact
|
||||
path: dist
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
@@ -11,11 +11,14 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DoozyX/clang-format-lint-action@v0.14
|
||||
with:
|
||||
source: '.'
|
||||
exclude: '*/third_party'
|
||||
extensions: 'h,cpp'
|
||||
clangFormatVersion: 12
|
||||
|
||||
extensions: 'h,cpp,js,ts,html'
|
||||
clangFormatVersion: 11
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
options: "--check --verbose"
|
||||
src: "./bindings/python/examples"
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Doxygen Action
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
@@ -20,12 +20,12 @@ jobs:
|
||||
mkdir public
|
||||
mv ./samples ./public
|
||||
mv ./docs ./public
|
||||
mv ./bindings/wasm/examples/* ./public
|
||||
|
||||
- name: Download WASM files
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
name: wasm
|
||||
path: ./public
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
@@ -12,47 +12,46 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-20.04
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
pip install trimesh
|
||||
- uses: actions/checkout@v3
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libglm-dev libgtest-dev libassimp-dev git libtbb-dev libthrust-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
pip install trimesh pytest
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build ${{matrix.backend}}
|
||||
if: matrix.parallel_backend != 'NONE'
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
- 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
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
if: matrix.parallel_backend != 'NONE'
|
||||
run: |
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
python3 bindings/python/examples/run_all.py
|
||||
python3 bindings/python/examples/run_all.py -e
|
||||
python3 -m pytest
|
||||
- name: Coverage Report
|
||||
# only do code coverage for default sequential backend, it seems that TBB
|
||||
# backend will cause failure
|
||||
# perhaps issue related to invalid memory access?
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
if: matrix.parallel_backend == 'NONE'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
@@ -60,36 +59,66 @@ jobs:
|
||||
lcov --capture --directory . --output-file ./code_coverage_test.info
|
||||
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
|
||||
lcov --remove ./code_coverage_total.info '/usr/*' '*/third_party/*' '*/test/*' '*/extras/*' '*/bindings/*' --output-file ./code_coverage.info
|
||||
- uses: codecov/codecov-action@v2
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
cd ../
|
||||
- uses: codecov/codecov-action@v4
|
||||
if: matrix.parallel_backend == 'NONE'
|
||||
with:
|
||||
files: build/code_coverage.info
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
|
||||
name: ${{matrix.parallel_backend}}
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./test-cmake.sh
|
||||
|
||||
build_wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
build_cbind:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
|
||||
- uses: actions/checkout@v3
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev libglm-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build C bindings with TBB
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=TBB .. && make
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test --gtest_filter=CBIND.*
|
||||
|
||||
build_wasm:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs libglm-dev
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Setup WASM
|
||||
run: |
|
||||
# setup emscripten
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
./emsdk install 3.1.45
|
||||
./emsdk activate 3.1.45
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- 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
|
||||
@@ -97,98 +126,132 @@ 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
|
||||
cp ../manifold.* ./dist/
|
||||
- name: Upload WASM files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: wasm
|
||||
path: |
|
||||
build/bindings/wasm/manifold.js
|
||||
build/bindings/wasm/manifold.wasm
|
||||
build/bindings/wasm/manifold.d.ts
|
||||
retention-days: 7
|
||||
path: bindings/wasm/examples/dist/
|
||||
retention-days: 90
|
||||
|
||||
build_windows:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
cuda_support: [OFF]
|
||||
parallel_backend: [NONE, TBB]
|
||||
max-parallel: 1
|
||||
runs-on: windows-2019
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Install CUDA
|
||||
if: matrix.backend == 'CUDA'
|
||||
env:
|
||||
cuda: "11.3.0"
|
||||
visual_studio: "Visual Studio 16 2019"
|
||||
shell: powershell
|
||||
run: |
|
||||
Invoke-RestMethod https://raw.githubusercontent.com/ptheywood/cuda-cmake-github-actions/21811d232fc775fa8d44dddf7f8c5ce2cf8d2b81/scripts/actions/install_cuda_windows.ps1 -OutFile install_cuda_windows.ps1
|
||||
.\install_cuda_windows.ps1
|
||||
- name: nvcc check
|
||||
if: matrix.backend == 'CUDA'
|
||||
shell: powershell
|
||||
run: |
|
||||
nvcc -V
|
||||
ls $env:CUDA_PATH
|
||||
ls $env:CUDA_PATH\bin
|
||||
ls $env:CUDA_PATH\include
|
||||
- name: Build ${{matrix.backend}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64 -B build
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/bin/Release
|
||||
./manifold_test.exe
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
cmake --install .
|
||||
cd ..
|
||||
./test-cmake.sh
|
||||
|
||||
build_mxe:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
max-parallel: 1
|
||||
runs-on: ubuntu-latest
|
||||
container: openscad/mxe-x86_64-gui:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./Release/manifold_test.exe
|
||||
./manifold_test
|
||||
|
||||
build_mac:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install OpenMP
|
||||
if: matrix.parallel_backend == 'OMP'
|
||||
run: brew install libomp
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install pkg-config googletest assimp
|
||||
pip install trimesh pytest
|
||||
- name: Install TBB
|
||||
if: matrix.parallel_backend == 'TBB'
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v2
|
||||
- name: Build
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
- name: test cmake consumer
|
||||
run: |
|
||||
cd build
|
||||
sudo cmake --install .
|
||||
cd ..
|
||||
./test-cmake.sh
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
|
||||
variant: [none, tbb, js]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
build_nix_python:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- run: nix build -L '.?submodules=1#manifold3d'
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: publish_npm
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish_npm:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
name: wasm
|
||||
path: ./bindings/wasm/
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
cd ./bindings/wasm/
|
||||
npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
@@ -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
|
||||
@@ -1,13 +1,6 @@
|
||||
[submodule "third_party/thrust"]
|
||||
path = src/third_party/thrust
|
||||
url = https://github.com/NVIDIA/thrust
|
||||
[submodule "third_party/glm"]
|
||||
path = src/third_party/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "third_party/google_test"]
|
||||
path = test/third_party/google_test
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third_party/pybind11"]
|
||||
path = bindings/python/third_party/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
[submodule "src/third_party/quickhull"]
|
||||
path = src/third_party/quickhull
|
||||
url = https://github.com/akuukka/quickhull
|
||||
[submodule "bindings/python/third_party/nanobind"]
|
||||
path = bindings/python/third_party/nanobind
|
||||
url = https://github.com/wjakob/nanobind
|
||||
|
||||
@@ -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,16 +98,31 @@
|
||||
"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",
|
||||
"source_location": "cpp",
|
||||
"format": "cpp",
|
||||
"numbers": "cpp",
|
||||
"ranges": "cpp",
|
||||
"stop_token": "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",
|
||||
"-DMANIFOLD_PAR=TBB",
|
||||
"-DCODE_COVERAGE=OFF",
|
||||
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
|
||||
],
|
||||
@@ -118,4 +133,20 @@
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"Coplanarity",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"python.formatting.provider": "none",
|
||||
"clang-format.executable": "clang-format",
|
||||
}
|
||||
@@ -6,4 +6,5 @@
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Google LLC
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
|
||||
@@ -16,60 +16,136 @@ cmake_minimum_required(VERSION 3.18)
|
||||
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)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags")
|
||||
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
# fuzztest is a rather large dependency
|
||||
option(MANIFOLD_FUZZ "Enable fuzzing tests" OFF)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(MANIFOLD_PYBIND "Build python bindings" OFF)
|
||||
option(MANIFOLD_CBIND "Build C (FFI) bindings" ON)
|
||||
option(MANIFOLD_JSBIND "Build js binding" ${EMSCRIPTEN})
|
||||
option(BUILD_SHARED_LIBS "Build shared library" ON)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
if(MANIFOLD_FUZZ)
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR "fuzztest only supports clang" )
|
||||
endif()
|
||||
# we should enable debug checks
|
||||
set(MANIFOLD_DEBUG ON)
|
||||
# enable fuzztest fuzzing mode
|
||||
set(FUZZTEST_FUZZING_MODE ON)
|
||||
# address sanitizer required
|
||||
set(CMAKE_CXX_FLAGS "$CMAKE_CXX_FLAGS -fsanitize=address")
|
||||
endif()
|
||||
|
||||
option(TRACY_ENABLE "Use tracy profiling" OFF)
|
||||
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF)
|
||||
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
|
||||
if(TRACY_ENABLE)
|
||||
option(CMAKE_BUILD_TYPE "Build type" RelWithDebInfo)
|
||||
if(TRACY_MEMORY_USAGE)
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} -DTRACY_MEMORY_USAGE)
|
||||
endif()
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
endif()
|
||||
else()
|
||||
option(CMAKE_BUILD_TYPE "Build type" Release)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
message("Building for Emscripten")
|
||||
set(MANIFOLD_FLAGS -fexceptions)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
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 -fexceptions -sDISABLE_EXCEPTION_CATCHING=0")
|
||||
set(MANIFOLD_PYBIND OFF)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
endif()
|
||||
|
||||
option(PYBIND11_FINDPYTHON on)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/manifoldDeps.cmake)
|
||||
|
||||
option(BUILD_TEST_CGAL off)
|
||||
|
||||
option(BUILD_SHARED_LIBS off)
|
||||
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
enable_language(CUDA)
|
||||
find_package(CUDA REQUIRED)
|
||||
|
||||
# we cannot set THRUST_INC_DIR when building with CUDA, otherwise the
|
||||
# compiler will not use the system CUDA headers which causes incompatibility
|
||||
# clear THRUST_INC_DIR, we use the one from nvcc
|
||||
set(THRUST_INC_DIR "")
|
||||
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3 -lineinfo)
|
||||
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
|
||||
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda --expt-relaxed-constexpr
|
||||
"$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>"
|
||||
"$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>")
|
||||
if(MANIFOLD_PYBIND)
|
||||
if(Python_VERSION VERSION_LESS 3.12)
|
||||
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter Development.SABIModule REQUIRED)
|
||||
endif()
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>"
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=${WARNING_FLAGS}>")
|
||||
if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN)
|
||||
# for nixos
|
||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} /DNOMINMAX)
|
||||
else()
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused -Wno-array-bounds
|
||||
-Wno-stringop-overflow -Wno-alloc-size-larger-than)
|
||||
else()
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
|
||||
endif()
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${WARNING_FLAGS} -O3)
|
||||
endif()
|
||||
|
||||
if(CODE_COVERAGE AND NOT MSVC)
|
||||
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${COVERAGE_FLAGS})
|
||||
add_link_options("-coverage")
|
||||
endif()
|
||||
|
||||
# installation rpath
|
||||
set(CMAKE_SKIP_BUILD_RPATH FALSE)
|
||||
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
|
||||
if("${isSystemDir}" STREQUAL "-1")
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
endif("${isSystemDir}" STREQUAL "-1")
|
||||
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
add_subdirectory(bindings)
|
||||
add_subdirectory(bindings)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_subdirectory(meshIO)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_TEST)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
endif()
|
||||
|
||||
# installation related
|
||||
|
||||
set(MANIFOLD_VERSION_MAJOR 2)
|
||||
set(MANIFOLD_VERSION_MINOR 4)
|
||||
set(MANIFOLD_VERSION_PATCH 2)
|
||||
set(MANIFOLD_VERSION "${MANIFOLD_VERSION_MAJOR}.${MANIFOLD_VERSION_MINOR}.${MANIFOLD_VERSION_PATCH}")
|
||||
set_property(TARGET manifold PROPERTY VERSION "${MANIFOLD_VERSION}")
|
||||
set_property(TARGET manifold PROPERTY SOVERSION 2)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_BINARY_DIR}/cmake/manifoldConfigVersion.cmake
|
||||
VERSION ${MANIFOLD_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
install(EXPORT manifoldTargets DESTINATION ${CMAKE_INSTALL_DATADIR}/manifold)
|
||||
configure_file(manifoldConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake @ONLY)
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifoldDeps.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/cmake/manifoldConfigVersion.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifoldConfig.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/manifold
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1,55 +1,97 @@
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://badge.fury.io/py/manifold3d)
|
||||
[](https://badge.fury.io/js/manifold-3d)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
## Users
|
||||
|
||||
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.
|
||||
[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.
|
||||
|
||||
## Manifold Frontend Sandboxes
|
||||
|
||||
### [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app where you script in JS/TS. This uses our npm package, [manifold-3d](https://www.npmjs.com/package/manifold-3d), built via WASM. It's not quite as fast as our raw C++, but it's hard to beat for interoperability.
|
||||
|
||||
*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
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
may solve the problem.*
|
||||
|
||||
### [Python Colab Example](https://colab.research.google.com/drive/1VxrFYHPSHZgUbl9TeWzCeovlpXrPQ5J5?usp=sharing)
|
||||
|
||||
If you prefer Python to JS/TS, make your own copy of the example notebook above. It demonstrates interop between our [`manifold3d`](https://pypi.org/project/manifold3d/) PyPI library and the popular [`trimesh`](https://pypi.org/project/trimesh/) library, including showing the interactive model right in the notebook and saving 3D model output.
|
||||
|
||||

|
||||
|
||||
# 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/topics.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 ([manifold3d](https://pypi.org/project/manifold3d/)), and C to make this library more portable and easy to use.
|
||||
|
||||
System Dependencies (note that we will automatically download the dependency if there is no such package on the system):
|
||||
- [`GLM`](https://github.com/g-truc/glm/): A compact header-only vector library.
|
||||
- [`Thrust`](https://github.com/NVIDIA/thrust): NVIDIA's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
|
||||
- [`tbb`](https://github.com/oneapi-src/oneTBB/): Intel's thread building blocks library. (only when `MANIFOLD_PAR=TBB` is enabled)
|
||||
- [`gtest`](https://github.com/google/googletest/): Google test library (only when test is enabled, i.e. `MANIFOLD_TEST=ON`)
|
||||
|
||||
Other dependencies:
|
||||
- [`Clipper2`](https://github.com/AngusJohnson/Clipper2): provides our 2D subsystem
|
||||
- [`quickhull`](https://github.com/akuukka/quickhull): 3D convex hull algorithm.
|
||||
|
||||
## 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 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 with parallel backend, the code will still fall back to the serial version of the algorithms if the problem size is small. The WASM build is serial-only for now, but still fast.
|
||||
|
||||
> Note: OMP and CUDA backends are now removed
|
||||
|
||||
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
|
||||
test/manifold_test
|
||||
```
|
||||
|
||||
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
|
||||
- `MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
|
||||
- `MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
|
||||
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
|
||||
- `MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding when using emscripten.
|
||||
- `MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
|
||||
- `MANIFOLD_PYBIND=[OFF, <ON>]`: Build python binding.
|
||||
- `MANIFOLD_PAR=[<NONE>, TBB]`: Provides multi-thread parallelization, requires `libtbb-dev` if `TBB` backend is selected.
|
||||
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
|
||||
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
|
||||
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unittests.
|
||||
- `TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
|
||||
See profiling section below.
|
||||
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
|
||||
|
||||
Offline building:
|
||||
- `FETCHCONTENT_SOURCE_DIR_GLM`: path to glm source.
|
||||
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source.
|
||||
- `FETCHCONTENT_SOURCE_DIR_THRUST`: path to NVIDIA thrust source.
|
||||
|
||||
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
|
||||
|
||||
### WASM
|
||||
|
||||
> Note that we have only tested emscripten version 3.1.45. It is known that
|
||||
3.1.48 has some issues compiling manifold.
|
||||
|
||||
To build the JS WASM library, first install NodeJS and set up emscripten:
|
||||
|
||||
(on Mac):
|
||||
@@ -77,13 +119,13 @@ node test/manifold_test.js
|
||||
|
||||
### Python
|
||||
|
||||
The CMake script will build the python binding `pymanifold` automatically. To
|
||||
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
|
||||
The CMake script will build the python binding `manifold3d` automatically. To
|
||||
use the extension, please add `$BUILD_DIR/bindings/python` 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:
|
||||
```
|
||||
sudo apt install pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
pip install trimesh
|
||||
pip install trimesh pytest
|
||||
python3 run_all.py -e
|
||||
```
|
||||
|
||||
@@ -91,16 +133,52 @@ 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.
|
||||
|
||||
### Windows Shenanigans
|
||||
|
||||
Windows users should build with `-DBUILD_SHARED_LIBS=OFF`, as enabling shared
|
||||
libraries in general makes things very complicated.
|
||||
|
||||
The DLL file for manifoldc (C FFI bindings) when built with msvc is in `${CMAKE_BINARY_DIR}/bin/${BUILD_TYPE}/manifoldc.dll`.
|
||||
For example, for the following command, the path relative to the project root directory is `build/bin/Release/manifoldc.dll`.
|
||||
```sh
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64 -B build
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! A lower barrier contribution is to simply make a PR that adds a test, especially if it repros an issue you've found. Simply name it prepended with DISABLED_, so that it passes the CI. That will be a very strong signal to me to fix your issue. However, if you know how to fix it yourself, then including the fix in your PR would be much appreciated!
|
||||
|
||||
### Formatting
|
||||
|
||||
There is a formatting script `format.sh` that automatically formats everything.
|
||||
It requires clang-format 11 and black formatter for python.
|
||||
|
||||
If you have clang-format installed but without clang-11, you can specify the
|
||||
clang-format executable by setting the `CLANG_FORMAT` environment variable.
|
||||
|
||||
### Profiling
|
||||
|
||||
There is now basic support for the [Tracy profiler](https://github.com/wolfpld/tracy) for our tests.
|
||||
To enable tracing, compile with `-DTRACY_ENABLE=on` cmake option, and run the test with Tracy server running.
|
||||
To enable memory profiling in addition to tracing, compile with `-DTRACY_MEMORY_USAGE=ON` in addition to `-DTRACY_ENABLE=ON`.
|
||||
|
||||
### Fuzzing Support
|
||||
|
||||
We use https://github.com/google/fuzztest for fuzzing the triangulator.
|
||||
|
||||
To enable fuzzing, make sure that you are using clang compiler (`-DCMAKE_CXX_COMPILER=clang -DCMAKE_C_COMPILER=clang`), running Linux, and enable fuzzing support by setting `-DMANIFOLD_FUZZ=ON`.
|
||||
|
||||
To run the fuzzer and minimize testcase, do
|
||||
```
|
||||
../minimizer.sh ./test/polygon_fuzz --fuzz=PolygonFuzz.TriangulationNoCrash
|
||||
```
|
||||
|
||||
## About the author
|
||||
|
||||
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
1. Go through Github issues to verify bugs have been fixed and closed.
|
||||
1. Verify [ManifoldCAD.org](https://manifoldcad.org) - check a few examples, run them, download a GLB and a 3MF.
|
||||
1. Verify our three.js [example](https://manifoldcad.org/three) is functional.
|
||||
1. Verify our model-viewer [example](https://manifoldcad.org/model-viewer) is functional - select Union and Intersection.
|
||||
1. Verify [make-manifold](https://manifoldcad.org/make-manifold) is functional. Try dropping [DragonAttenuation.glb](https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/DragonAttenuation/glTF-Binary/DragonAttenuation.glb) in and verify you can select "View Manifold GLB" and that the dragon is still present while the backdrop is removed. Download the GLB.
|
||||
1. Make a new branch called the version, e.g. v2.3.0.
|
||||
1. Use VSCode to search and replace the old version with the new - so far in test-cmake.sh, flake.nix, pyproject.toml, and package.json.
|
||||
1. Also update CMakeLists.txt version by searching for "set(MANIFOLD_VERSION_".
|
||||
1. Commit, push, open a PR, verify tests pass, merge.
|
||||
1. On Github, draft a new release, make a new tag with the version number, add release notes, and publish.
|
||||
1. Check the Actions and verify that both PyPI and npm publishing actions ran successfully.
|
||||
1. Verify the npm [package](https://www.npmjs.com/package/manifold-3d?activeTab=code) looks good - unpacked size should be close to 1MB.
|
||||
1. Verify PyPI [package](https://pypi.org/project/manifold3d/#files) looks good - a bunch of built distributions ranging from ~600kB to ~1.1MB.
|
||||
@@ -12,10 +12,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
if(EMSCRIPTEN AND MANIFOLD_JSBIND)
|
||||
add_subdirectory(wasm)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
project(manifoldc)
|
||||
|
||||
# this is a shared library for FFI bindings
|
||||
add_library(${PROJECT_NAME} SHARED manifoldc.cpp conv.cpp)
|
||||
|
||||
if(MSVC)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY
|
||||
${CMAKE_BINARY_DIR}/bin )
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
${CMAKE_BINARY_DIR}/bin )
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE meshIO)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE manifold sdf cross_section
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<INSTALL_INTERFACE:include>
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT manifoldTargets)
|
||||
install(FILES include/manifoldc.h include/types.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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 "conv.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2) {
|
||||
auto p1 = glm::vec3(x1, y1, z1);
|
||||
auto p2 = glm::vec3(x2, y2, z2);
|
||||
auto box = new (mem) Box(p1, p2);
|
||||
return to_c(box);
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
|
||||
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
|
||||
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
|
||||
auto v = from_c(b)->Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
auto v = from_c(b)->Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
|
||||
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return from_c(b)->Contains(p);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
auto box = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(b)->Transform(mat);
|
||||
return to_c(new (mem) Box(transformed));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto scaled = (*from_c(b)) * p;
|
||||
return to_c(new (mem) Box(scaled));
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return from_c(b)->DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return from_c(a)->DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
|
||||
@@ -0,0 +1,250 @@
|
||||
// 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 "conv.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
return reinterpret_cast<ManifoldManifold *>(m);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms) {
|
||||
return reinterpret_cast<ManifoldManifoldVec *>(ms);
|
||||
}
|
||||
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs) {
|
||||
return reinterpret_cast<ManifoldCrossSection *>(cs);
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv) {
|
||||
return reinterpret_cast<ManifoldCrossSectionVec *>(csv);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon *>(m);
|
||||
}
|
||||
|
||||
ManifoldPolygons *to_c(manifold::Polygons *m) {
|
||||
return reinterpret_cast<ManifoldPolygons *>(m);
|
||||
}
|
||||
|
||||
ManifoldMesh *to_c(manifold::Mesh *m) {
|
||||
return reinterpret_cast<ManifoldMesh *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
ManifoldOpType to_c(manifold::OpType optype) {
|
||||
ManifoldOpType op = MANIFOLD_ADD;
|
||||
switch (optype) {
|
||||
case manifold::OpType::Add:
|
||||
break;
|
||||
case manifold::OpType::Subtract:
|
||||
op = MANIFOLD_SUBTRACT;
|
||||
break;
|
||||
case manifold::OpType::Intersect:
|
||||
op = MANIFOLD_INTERSECT;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
ManifoldError e = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
break;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
break;
|
||||
case Manifold::Error::NotManifold:
|
||||
e = MANIFOLD_NOT_MANIFOLD;
|
||||
break;
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
|
||||
break;
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
break;
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
e = MANIFOLD_INVALID_CONSTRUCTION;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
|
||||
ManifoldBox *to_c(manifold::Box *m) {
|
||||
return reinterpret_cast<ManifoldBox *>(m);
|
||||
}
|
||||
|
||||
ManifoldRect *to_c(manifold::Rect *m) {
|
||||
return reinterpret_cast<ManifoldRect *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
|
||||
|
||||
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldProperties to_c(manifold::Properties p) {
|
||||
return {p.surfaceArea, p.volume};
|
||||
}
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m) {
|
||||
return reinterpret_cast<manifold::Manifold const *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms) {
|
||||
return reinterpret_cast<ManifoldVec *>(ms);
|
||||
}
|
||||
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs) {
|
||||
return reinterpret_cast<manifold::CrossSection *>(cs);
|
||||
}
|
||||
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv) {
|
||||
return reinterpret_cast<CrossSectionVec *>(csv);
|
||||
}
|
||||
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m) {
|
||||
return reinterpret_cast<manifold::Polygons const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m) {
|
||||
return reinterpret_cast<manifold::Mesh const *>(m);
|
||||
}
|
||||
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
|
||||
return reinterpret_cast<manifold::MeshGL const *>(m);
|
||||
}
|
||||
|
||||
OpType from_c(ManifoldOpType optype) {
|
||||
auto op = OpType::Add;
|
||||
switch (optype) {
|
||||
case MANIFOLD_ADD:
|
||||
break;
|
||||
case MANIFOLD_SUBTRACT:
|
||||
op = OpType::Subtract;
|
||||
break;
|
||||
case MANIFOLD_INTERSECT:
|
||||
op = OpType::Intersect;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
|
||||
auto fr = CrossSection::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case MANIFOLD_FILL_RULE_EVEN_ODD:
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NON_ZERO:
|
||||
fr = CrossSection::FillRule::NonZero;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_POSITIVE:
|
||||
fr = CrossSection::FillRule::Positive;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NEGATIVE:
|
||||
fr = CrossSection::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
|
||||
auto jt = CrossSection::JoinType::Square;
|
||||
switch (join_type) {
|
||||
case MANIFOLD_JOIN_TYPE_SQUARE:
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_ROUND:
|
||||
jt = CrossSection::JoinType::Round;
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_MITER:
|
||||
jt = CrossSection::JoinType::Miter;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
const manifold::Box *from_c(ManifoldBox *m) {
|
||||
return reinterpret_cast<manifold::Box const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Rect *from_c(ManifoldRect *m) {
|
||||
return reinterpret_cast<manifold::Rect const *>(m);
|
||||
}
|
||||
|
||||
glm::vec2 from_c(ManifoldVec2 v) { return glm::vec2(v.x, v.y); }
|
||||
|
||||
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::ivec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec4>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
// 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 <vector>
|
||||
|
||||
#include "conv.h"
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.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_hull(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto hulled = from_c(cs)->Hull();
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_batch_hull(
|
||||
void *mem, ManifoldCrossSectionVec *css) {
|
||||
auto hulled = CrossSection::Hull(*from_c(css));
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
|
||||
void *mem, ManifoldSimplePolygon *ps) {
|
||||
auto hulled = CrossSection::Hull(*from_c(ps));
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_hull_polygons(
|
||||
void *mem, ManifoldPolygons *ps) {
|
||||
auto hulled = CrossSection::Hull(*from_c(ps));
|
||||
return to_c(new (mem) CrossSection(hulled));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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 <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace manifold;
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m);
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms);
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs);
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldRect *to_c(manifold::Rect *m);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldVec2 to_c(glm::vec2 v);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms);
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
OpType from_c(ManifoldOpType op);
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
|
||||
CrossSection::JoinType from_c(ManifoldJoinType jt);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Rect *from_c(ManifoldRect *r);
|
||||
glm::vec2 from_c(ManifoldVec2 v);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector_of_array(T *ts, size_t length) {
|
||||
auto vec = std::vector<T>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *copy_data(void *mem, std::vector<T> v) {
|
||||
T *ts = reinterpret_cast<T *>(mem);
|
||||
memcpy(ts, v.data(), sizeof(T) * v.size());
|
||||
return ts;
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
// 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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Polygons
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length);
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length);
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p);
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps);
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx);
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx);
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx);
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx);
|
||||
|
||||
// Mesh Construction
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris);
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent);
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// SDF
|
||||
// By default, the execution policy (sequential or parallel) of
|
||||
// manifold_level_set will be chosen automatically depending on the size of the
|
||||
// job and whether Manifold has been compiled with a PAR backend. If you are
|
||||
// using these bindings from a language that has a runtime lock preventing the
|
||||
// parallel execution of closures, then you should use manifold_level_set_seq to
|
||||
// force sequential execution.
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
// 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,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
|
||||
// 3D to 2D
|
||||
|
||||
ManifoldCrossSection *manifold_slice(void *mem, ManifoldManifold *m,
|
||||
float height);
|
||||
ManifoldCrossSection *manifold_project(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Convex Hulls
|
||||
|
||||
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms);
|
||||
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps, size_t length);
|
||||
|
||||
// Manifold Transformations
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz);
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float));
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
|
||||
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
|
||||
float length);
|
||||
|
||||
// Manifold Shapes / Constructors
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem);
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem);
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center);
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center);
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_idxs);
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Manifold Info
|
||||
|
||||
int manifold_is_empty(ManifoldManifold *m);
|
||||
ManifoldError manifold_status(ManifoldManifold *m);
|
||||
int manifold_num_vert(ManifoldManifold *m);
|
||||
int manifold_num_edge(ManifoldManifold *m);
|
||||
int manifold_num_tri(ManifoldManifold *m);
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
|
||||
float manifold_precision(ManifoldManifold *m);
|
||||
int manifold_genus(ManifoldManifold *m);
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
|
||||
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 Convex Hulls
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
ManifoldCrossSection *manifold_cross_section_batch_hull(
|
||||
void *mem, ManifoldCrossSectionVec *css);
|
||||
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
|
||||
void *mem, ManifoldSimplePolygon *ps);
|
||||
ManifoldCrossSection *manifold_cross_section_hull_polygons(
|
||||
void *mem, ManifoldPolygons *ps);
|
||||
|
||||
// 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);
|
||||
|
||||
// Bounding Box
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2);
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b);
|
||||
float manifold_box_scale(ManifoldBox *b);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
|
||||
int manifold_box_is_finite(ManifoldBox *b);
|
||||
|
||||
// Static Quality Globals
|
||||
|
||||
void manifold_set_min_circular_angle(float degrees);
|
||||
void manifold_set_min_circular_edge_length(float length);
|
||||
void manifold_set_circular_segments(int number);
|
||||
|
||||
// Manifold Mesh Extraction
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// 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);
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert);
|
||||
ManifoldExportOptions *manifold_export_options(void *mem);
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted);
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat);
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options);
|
||||
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
|
||||
int force_cleanup);
|
||||
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
void manifold_destruct_material(ManifoldMaterial *m);
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *options);
|
||||
|
||||
void manifold_delete_material(ManifoldMaterial *m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions *options);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,106 @@
|
||||
// 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 <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 ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldRect ManifoldRect;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
#endif
|
||||
|
||||
// structs
|
||||
|
||||
typedef struct ManifoldManifoldPair {
|
||||
ManifoldManifold* first;
|
||||
ManifoldManifold* second;
|
||||
} ManifoldManifoldPair;
|
||||
|
||||
typedef struct ManifoldVec2 {
|
||||
float x;
|
||||
float y;
|
||||
} ManifoldVec2;
|
||||
|
||||
typedef struct ManifoldVec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} ManifoldVec3;
|
||||
|
||||
typedef struct ManifoldIVec3 {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} ManifoldIVec3;
|
||||
|
||||
typedef struct ManifoldVec4 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
} ManifoldVec4;
|
||||
|
||||
typedef struct ManifoldProperties {
|
||||
float surface_area;
|
||||
float volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
// enums
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
MANIFOLD_ADD,
|
||||
MANIFOLD_SUBTRACT,
|
||||
MANIFOLD_INTERSECT
|
||||
} ManifoldOpType;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
MANIFOLD_NO_ERROR,
|
||||
MANIFOLD_NON_FINITE_VERTEX,
|
||||
MANIFOLD_NOT_MANIFOLD,
|
||||
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_PROPERTIES_WRONG_LENGTH,
|
||||
MANIFOLD_MISSING_POSITION_PROPERTIES,
|
||||
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_TRANSFORM_WRONG_LENGTH,
|
||||
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
|
||||
MANIFOLD_FACE_ID_WRONG_LENGTH,
|
||||
MANIFOLD_INVALID_CONSTRUCTION,
|
||||
} ManifoldError;
|
||||
|
||||
typedef enum ManifoldFillRule {
|
||||
MANIFOLD_FILL_RULE_EVEN_ODD,
|
||||
MANIFOLD_FILL_RULE_NON_ZERO,
|
||||
MANIFOLD_FILL_RULE_POSITIVE,
|
||||
MANIFOLD_FILL_RULE_NEGATIVE
|
||||
} ManifoldFillRule;
|
||||
|
||||
typedef enum ManifoldJoinType {
|
||||
MANIFOLD_JOIN_TYPE_SQUARE,
|
||||
MANIFOLD_JOIN_TYPE_ROUND,
|
||||
MANIFOLD_JOIN_TYPE_MITER,
|
||||
} ManifoldJoinType;
|
||||
@@ -0,0 +1,618 @@
|
||||
// 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 <conv.h>
|
||||
#include <cross_section.h>
|
||||
#include <manifold.h>
|
||||
#include <manifoldc.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "box.cpp"
|
||||
#include "cross.cpp"
|
||||
#include "include/conv.h"
|
||||
#include "include/types.h"
|
||||
#include "rect.cpp"
|
||||
#include "types.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshio.cpp"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length, float level,
|
||||
bool seq) {
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
|
||||
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 mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<glm::vec2>;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back({ps[i].x, ps[i].y});
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<SimplePolygon>;
|
||||
auto polys = reinterpret_cast<SimplePolygon **>(ps);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back(*polys[i]);
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p) {
|
||||
return from_c(p)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps) {
|
||||
return from_c(ps)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx) {
|
||||
return (*from_c(ps))[idx].size();
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx) {
|
||||
return to_c((*from_c(p))[idx]);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx) {
|
||||
auto sp = (*from_c(ps))[idx];
|
||||
return to_c(new (mem) SimplePolygon(sp));
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx) {
|
||||
return to_c((*from_c(ps))[simple_idx][pt_idx]);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem) {
|
||||
return to_c(new (mem) ManifoldVec());
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz) {
|
||||
return to_c(new (mem) ManifoldVec(sz));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz) {
|
||||
from_c(ms)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms) {
|
||||
return from_c(ms)->size();
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx) {
|
||||
auto m = (*from_c(ms))[idx];
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
ManifoldManifold *m) {
|
||||
(*from_c(ms))[idx] = *from_c(m);
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
|
||||
ManifoldManifold *m) {
|
||||
return from_c(ms)->push_back(*from_c(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b, ManifoldOpType op) {
|
||||
auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
|
||||
ManifoldOpType op) {
|
||||
auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b) {
|
||||
auto pair = from_c(a)->Split(*from_c(b));
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto pair = from_c(m)->SplitByPlane(normal, offset);
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
|
||||
return to_c(new (mem) Manifold(trimmed));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_slice(void *mem, ManifoldManifold *m,
|
||||
float height) {
|
||||
auto poly = from_c(m)->Slice(height);
|
||||
return to_c(new (mem) CrossSection(poly));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_project(void *mem, ManifoldManifold *m) {
|
||||
auto poly = from_c(m)->Project();
|
||||
return to_c(new (mem) CrossSection(poly));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m) {
|
||||
auto hulled = from_c(m)->Hull();
|
||||
return to_c(new (mem) Manifold(hulled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms) {
|
||||
auto hulled = Manifold::Hull(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(hulled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps,
|
||||
size_t length) {
|
||||
std::vector<glm::vec3> vec(length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec[i] = {ps[i].x, ps[i].y, ps[i].z};
|
||||
}
|
||||
auto hulled = Manifold::Hull(vec);
|
||||
return to_c(new (mem) Manifold(hulled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto v = glm::vec3(x, y, z);
|
||||
auto translated = from_c(m)->Translate(v);
|
||||
return to_c(new (mem) Manifold(translated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto rotated = from_c(m)->Rotate(x, y, z);
|
||||
return to_c(new (mem) Manifold(rotated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto s = glm::vec3(x, y, z);
|
||||
auto scaled = from_c(m)->Scale(s);
|
||||
return to_c(new (mem) Manifold(scaled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(m)->Transform(mat);
|
||||
return to_c(new (mem) Manifold(transformed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz) {
|
||||
auto mirrored = from_c(m)->Mirror({nx, ny, nz});
|
||||
return to_c(new (mem) Manifold(mirrored));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float)) {
|
||||
std::function<void(glm::vec3 & v)> warp = [fun](glm::vec3 &v) {
|
||||
v = from_c(fun(v.x, v.y, v.z));
|
||||
};
|
||||
auto warped = from_c(m)->Warp(warp);
|
||||
return to_c(new (mem) Manifold(warped));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, false);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, true);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine_to_length(void *mem, ManifoldManifold *m,
|
||||
float length) {
|
||||
auto refined = from_c(m)->RefineToLength(length);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem) {
|
||||
return to_c(new (mem) Manifold());
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m) {
|
||||
return to_c(new (mem) Manifold(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem) {
|
||||
auto m = Manifold::Tetrahedron();
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center) {
|
||||
auto size = glm::vec3(x, y, z);
|
||||
auto m = Manifold::Cube(size, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center) {
|
||||
auto m = Manifold::Cylinder(height, radius_low, radius_high,
|
||||
circular_segments, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Sphere(radius, circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (int i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
|
||||
auto m = Manifold(*from_c(mesh));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y) {
|
||||
auto scale = glm::vec2(scale_x, scale_y);
|
||||
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms) {
|
||||
auto composed = Manifold::Compose(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m) {
|
||||
auto comps = from_c(m)->Decompose();
|
||||
return to_c(new (mem) std::vector<Manifold>(comps));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m) {
|
||||
auto mesh = from_c(m)->GetMeshGL();
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
|
||||
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->triVerts.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->mergeFromVert.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runOriginalID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runTransform.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->faceID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->halfedgeTangent.size();
|
||||
}
|
||||
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->vertProperties);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->triVerts);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeFromVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeToVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runOriginalID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runTransform);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->faceID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
|
||||
auto orig = from_c(m)->AsOriginal();
|
||||
return to_c(new (mem) Manifold(orig));
|
||||
}
|
||||
|
||||
int manifold_original_id(ManifoldManifold *m) {
|
||||
return from_c(m)->OriginalID();
|
||||
}
|
||||
|
||||
int manifold_is_empty(ManifoldManifold *m) { return from_c(m)->IsEmpty(); }
|
||||
|
||||
ManifoldError manifold_status(ManifoldManifold *m) {
|
||||
auto error = from_c(m)->Status();
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
|
||||
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
|
||||
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
|
||||
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
|
||||
return to_c(from_c(m)->GetProperties());
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
|
||||
auto box = from_c(m)->BoundingBox();
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
|
||||
|
||||
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
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) {
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
return Quality::GetCircularSegments(radius);
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
|
||||
size_t manifold_cross_section_vec_size() {
|
||||
return sizeof(std::vector<CrossSection>);
|
||||
}
|
||||
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
|
||||
size_t manifold_polygons_size() { return sizeof(Polygons); }
|
||||
size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
|
||||
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
|
||||
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
|
||||
size_t manifold_box_size() { return sizeof(Box); }
|
||||
size_t manifold_rect_size() { return sizeof(Rect); }
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *c) {
|
||||
delete from_c(c);
|
||||
}
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv) {
|
||||
delete from_c(csv);
|
||||
}
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
delete from_c(p);
|
||||
}
|
||||
void manifold_delete_polygons(ManifoldPolygons *p) { delete from_c(p); }
|
||||
void manifold_delete_manifold(ManifoldManifold *m) { delete from_c(m); }
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
delete from_c(ms);
|
||||
}
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
|
||||
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
|
||||
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *cs) {
|
||||
from_c(cs)->~CrossSection();
|
||||
}
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv) {
|
||||
from_c(csv)->~CrossSectionVec();
|
||||
}
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
from_c(p)->~SimplePolygon();
|
||||
}
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p) { from_c(p)->~Polygons(); }
|
||||
void manifold_destruct_manifold(ManifoldManifold *m) { from_c(m)->~Manifold(); }
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
from_c(ms)->~ManifoldVec();
|
||||
}
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
|
||||
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
|
||||
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,117 @@
|
||||
// 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 "meshIO.h"
|
||||
|
||||
#include "conv.h"
|
||||
#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());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert) {
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
// export options
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
// 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); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions *m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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 "conv.h"
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.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(); }
|
||||
@@ -15,10 +15,44 @@
|
||||
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_14)
|
||||
target_include_directories(pymanifold
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
nanobind_add_module(
|
||||
manifold3d
|
||||
NB_STATIC STABLE_ABI LTO
|
||||
autogen_docstrings.inl
|
||||
manifold3d.cpp)
|
||||
target_link_libraries(manifold3d PRIVATE manifold sdf polygon)
|
||||
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d)
|
||||
target_compile_features(manifold3d PUBLIC cxx_std_17)
|
||||
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")
|
||||
|
||||
message(Python_EXECUTABLE = ${Python_EXECUTABLE})
|
||||
# ideally we should generate a dependency file from python...
|
||||
set(DOCSTRING_DEPS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/manifold/src/manifold.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/manifold/src/constructors.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/manifold/src/sort.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/cross_section/src/cross_section.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/polygon/src/polygon.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/utilities/include/public.h
|
||||
)
|
||||
add_custom_command(
|
||||
OUTPUT autogen_docstrings.inl
|
||||
DEPENDS ${DOCSTRING_DEPS}
|
||||
COMMAND ${Python_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/gen_docs.py
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
target_include_directories(manifold3d PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
if(SKBUILD)
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
else()
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
LIBRARY DESTINATION ${Python_SITEARCH}
|
||||
COMPONENT bindings
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Python Bindings
|
||||
|
||||
## Autogenerated Doc-Strings
|
||||
|
||||
Doc-strings for the python API wrapper are generated by gen_docs.py
|
||||
The script is run automatically during the build to keep python
|
||||
doc strings fully up-to-date with c++ sources.
|
||||
|
||||
It scrapes documentation comments and c++ function signatures
|
||||
from the manifold c++ sources, in order to generate a c++ header file
|
||||
exposing the comments as string variables named by function names.
|
||||
This allows python bindings to re-use the c++ comments directly.
|
||||
|
||||
Some snake-casing of params is applied for python use case.
|
||||
|
||||
---
|
||||
|
||||
When modifying the Manifold C++ sources, you may need to update
|
||||
gen_docs.py. For example, top-level free functions are white-listed,
|
||||
so if you add a new one, you will need to add it in gen_docs.py.
|
||||
|
||||
Similarly, the list of source files to parse is also white listed,
|
||||
so if you define functions in new files that need python wrappers,
|
||||
you will also need to up gen_docs.py.
|
||||
|
||||
To verify that python docs are correct after changes, you can
|
||||
run the following commends from the manifold repo root:
|
||||
```
|
||||
pip install .
|
||||
python -c 'import manifold3d; help(manifold3d)'
|
||||
```
|
||||
|
||||
Alternateively you could generate stubs with roughly the same info
|
||||
```
|
||||
pip install nanobind-stubgen
|
||||
pip install .
|
||||
nanobind-stubgen manifold3d
|
||||
```
|
||||
It will emit some warnings and write a file `manifold3d.pyi`
|
||||
which will show all the function signatures and docstrings.
|
||||
@@ -0,0 +1,122 @@
|
||||
from manifold3d import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
def all_root_level():
|
||||
set_min_circular_angle(10)
|
||||
set_min_circular_edge_length(1)
|
||||
set_circular_segments(22)
|
||||
n = get_circular_segments(1)
|
||||
assert n == 22
|
||||
poly = [[0, 0], [1, 0], [1, 1]]
|
||||
tris = triangulate([poly])
|
||||
tris = triangulate([np.array(poly)])
|
||||
|
||||
|
||||
def all_cross_section():
|
||||
poly = [[0, 0], [1, 0], [1, 1]]
|
||||
c = CrossSection([np.array(poly)])
|
||||
c = CrossSection([poly])
|
||||
c = CrossSection() + c
|
||||
a = c.area()
|
||||
c = CrossSection.batch_boolean(
|
||||
[
|
||||
CrossSection.circle(1),
|
||||
CrossSection.square((3, 3)),
|
||||
CrossSection.circle(3).translate((1, 1)),
|
||||
],
|
||||
OpType.Add,
|
||||
)
|
||||
c = CrossSection.batch_hull([c, c.translate((1, 0))])
|
||||
b = c.bounds()
|
||||
c = CrossSection.circle(1)
|
||||
c = CrossSection.compose([c, c.translate((1, 0))])
|
||||
cs = c.decompose()
|
||||
m = c.extrude(1)
|
||||
c = c.hull()
|
||||
c = CrossSection.hull_points(poly)
|
||||
c = CrossSection.hull_points(np.array(poly))
|
||||
e = c.is_empty()
|
||||
c = c.mirror((0, 1))
|
||||
n = c.num_contour()
|
||||
n = c.num_vert()
|
||||
c = c.offset(1, JoinType.Round)
|
||||
m = c.revolve()
|
||||
c = c.rotate(90)
|
||||
c = c.scale((2, 2))
|
||||
c = c.simplify()
|
||||
c = CrossSection.square((1, 1))
|
||||
p = c.to_polygons()
|
||||
c = c.transform([[1, 0, 0], [0, 1, 0]])
|
||||
c = c.translate((1, 1))
|
||||
c = c.warp(lambda p: (p[0] + 1, p[1] / 2))
|
||||
c = c.warp_batch(lambda ps: ps * [1, 0.5] + [1, 0])
|
||||
|
||||
|
||||
def all_manifold():
|
||||
mesh = Manifold.sphere(1).to_mesh()
|
||||
m = Manifold(mesh)
|
||||
m = Manifold() + m
|
||||
m = m.as_original()
|
||||
m = Manifold.batch_boolean(
|
||||
[
|
||||
Manifold.cylinder(4, 1),
|
||||
Manifold.cube((3, 2, 1)),
|
||||
Manifold.cylinder(5, 3).translate((1, 1, 1)),
|
||||
],
|
||||
OpType.Add,
|
||||
)
|
||||
m = Manifold.batch_hull([m, m.translate((0, 0, 1))])
|
||||
b = m.bounding_box()
|
||||
m = m.calculate_curvature(4, 5)
|
||||
m = Manifold.compose([m, m.translate((5, 0, 0))])
|
||||
m = Manifold.cube((1, 1, 1))
|
||||
m = Manifold.cylinder(1, 1)
|
||||
ms = m.decompose()
|
||||
m = Manifold.extrude(CrossSection.circle(1), 1)
|
||||
m = Manifold.revolve(CrossSection.circle(1))
|
||||
g = m.genus()
|
||||
a = m.surface_area()
|
||||
v = m.volume()
|
||||
m = m.hull()
|
||||
m = m.hull_points(mesh.vert_properties)
|
||||
e = m.is_empty()
|
||||
m = m.mirror((0, 0, 1))
|
||||
n = m.num_edge()
|
||||
n = m.num_prop()
|
||||
n = m.num_prop_vert()
|
||||
n = m.num_tri()
|
||||
n = m.num_vert()
|
||||
i = m.original_id()
|
||||
p = m.precision()
|
||||
c = m.project()
|
||||
m = m.refine(2)
|
||||
m = m.refine_to_length(0.1)
|
||||
i = Manifold.reserve_ids(1)
|
||||
m = m.scale((1, 2, 3))
|
||||
m = m.set_properties(3, lambda pos, prop: pos)
|
||||
c = m.slice(0.5)
|
||||
m = Manifold.smooth(mesh, [0], [0.5])
|
||||
m = Manifold.sphere(1)
|
||||
m, n = m.split(m.translate((1, 0, 0)))
|
||||
m, n = m.split_by_plane((0, 0, 1), 0)
|
||||
e = m.status()
|
||||
m = Manifold.tetrahedron()
|
||||
mesh = m.to_mesh()
|
||||
ok = mesh.merge()
|
||||
m = m.transform([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
|
||||
m = m.translate((0, 0, 0))
|
||||
m = m.trim_by_plane((0, 0, 1), 0)
|
||||
m = m.warp(lambda p: (p[0] + 1, p[1] / 2, p[2] * 2))
|
||||
m = m.warp_batch(lambda ps: ps * [1, 0.5, 2] + [1, 0, 0])
|
||||
|
||||
|
||||
def run():
|
||||
all_root_level()
|
||||
all_cross_section()
|
||||
all_manifold()
|
||||
return Manifold()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -14,66 +14,88 @@
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
from pymanifold import Manifold
|
||||
from manifold3d import Manifold
|
||||
|
||||
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
|
||||
|
||||
INCHES = 25.4
|
||||
|
||||
brick_depth = (3 + 5/8) * INCHES
|
||||
brick_height = (2 + 1/4) * INCHES
|
||||
brick_length = (7 + 5/8) * INCHES
|
||||
brick_depth = (3 + 5 / 8) * INCHES
|
||||
brick_height = (2 + 1 / 4) * INCHES
|
||||
brick_length = (7 + 5 / 8) * INCHES
|
||||
|
||||
mortar_gap = (3/8) * INCHES
|
||||
mortar_gap = (3 / 8) * INCHES
|
||||
|
||||
|
||||
def brick():
|
||||
return Manifold.cube(brick_length, brick_depth, brick_height)
|
||||
return Manifold.cube([brick_length, brick_depth, brick_height])
|
||||
|
||||
|
||||
def halfbrick():
|
||||
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
|
||||
return Manifold.cube([(brick_length - mortar_gap) / 2, brick_depth, brick_height])
|
||||
|
||||
|
||||
def row(length):
|
||||
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
|
||||
for x in range(length)]
|
||||
return Manifold(bricks)
|
||||
bricks = [
|
||||
brick().translate([(brick_length + mortar_gap) * x, 0, 0])
|
||||
for x in range(length)
|
||||
]
|
||||
return sum(bricks, Manifold())
|
||||
|
||||
|
||||
def wall(length, height, alternate=0):
|
||||
bricks = [row(length).translate(
|
||||
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
|
||||
(brick_height+mortar_gap)*z) for z in range(height)]
|
||||
return Manifold(bricks)
|
||||
bricks = [
|
||||
row(length).translate(
|
||||
[
|
||||
((z + alternate) % 2) * (brick_depth + mortar_gap),
|
||||
0,
|
||||
(brick_height + mortar_gap) * z,
|
||||
]
|
||||
)
|
||||
for z in range(height)
|
||||
]
|
||||
return sum(bricks, Manifold())
|
||||
|
||||
|
||||
def walls(length, width, height):
|
||||
return Manifold([
|
||||
wall(length, height),
|
||||
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
|
||||
wall(length, height, 1).translate(
|
||||
0, (width)*(brick_length+mortar_gap), 0),
|
||||
wall(width, height).rotate(0, 0, 90)
|
||||
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
|
||||
])
|
||||
return sum(
|
||||
[
|
||||
wall(length, height),
|
||||
wall(width, height, 1).rotate([0, 0, 90]).translate([brick_depth, 0, 0]),
|
||||
wall(length, height, 1).translate(
|
||||
[0, (width) * (brick_length + mortar_gap), 0]
|
||||
),
|
||||
wall(width, height)
|
||||
.rotate([0, 0, 90])
|
||||
.translate(
|
||||
[(length + 0.5) * (brick_length + mortar_gap) - mortar_gap, 0, 0]
|
||||
),
|
||||
],
|
||||
Manifold(),
|
||||
)
|
||||
|
||||
|
||||
def floor(length, width):
|
||||
results = [walls(length, width, 1)]
|
||||
if length > 1 and width > 1:
|
||||
results.append(floor(length-1, width-1).translate(brick_depth +
|
||||
mortar_gap, brick_depth+mortar_gap, 0))
|
||||
results.append(
|
||||
floor(length - 1, width - 1).translate(
|
||||
[brick_depth + mortar_gap, brick_depth + mortar_gap, 0]
|
||||
)
|
||||
)
|
||||
if length == 1 and width > 1:
|
||||
results.append(row(width-1).rotate(0, 0, 90))
|
||||
results.append(row(width - 1).rotate(0, 0, 90))
|
||||
if width == 1 and length > 1:
|
||||
results.append(row(length-1).translate(2*(brick_depth +
|
||||
mortar_gap), brick_depth+mortar_gap, 0))
|
||||
results.append(halfbrick().translate(
|
||||
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
|
||||
return Manifold(results)
|
||||
results.append(
|
||||
row(length - 1).translate(
|
||||
[2 * (brick_depth + mortar_gap), brick_depth + mortar_gap, 0]
|
||||
)
|
||||
)
|
||||
results.append(
|
||||
halfbrick().translate([brick_depth + mortar_gap, brick_depth + mortar_gap, 0])
|
||||
)
|
||||
return sum(results, Manifold())
|
||||
|
||||
|
||||
def run(width=10, length=10, height=10):
|
||||
return walls(length, width, height) + floor(length, width)
|
||||
|
||||
|
||||
@@ -13,18 +13,21 @@
|
||||
See the License for the specific language governing permissions and
|
||||
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
|
||||
|
||||
|
||||
def run(n=5, overlap=True):
|
||||
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
|
||||
a = Manifold.cube([n, n, 0.5]).translate([-0.5, -0.5, -0.5])
|
||||
|
||||
spheres = [Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
|
||||
for i in range(n) for j in range(n)]
|
||||
spheres = reduce(lambda a, b: a + b, spheres)
|
||||
|
||||
return a - spheres
|
||||
spheres = [
|
||||
Manifold.sphere(0.45 if overlap else 0.55, 50).translate([i, j, 0])
|
||||
for i in range(n)
|
||||
for j in range(n)
|
||||
]
|
||||
# spheres = reduce(lambda a, b: a + b, spheres)
|
||||
|
||||
return a - sum(spheres, Manifold())
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from manifold3d import CrossSection, Manifold
|
||||
|
||||
|
||||
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([tuple(p) for p in polygon]) != set(polygon_points):
|
||||
raise Exception(
|
||||
f"polygon={polygon} differs from polygon_points={polygon_points}"
|
||||
)
|
||||
|
||||
# extrude a polygon to create a manifold
|
||||
extruded_polygon = Manifold.extrude(cross_section, 10.0)
|
||||
eps = 0.001
|
||||
observed_volume = extruded_polygon.volume()
|
||||
expected_volume = 10.0
|
||||
if abs(observed_volume - expected_volume) > eps:
|
||||
raise Exception(
|
||||
f"observed_volume={observed_volume} differs from expected_volume={expected_volume}"
|
||||
)
|
||||
observed_surface_area = extruded_polygon.surface_area()
|
||||
expected_surface_area = 42.0
|
||||
if abs(observed_surface_area - expected_surface_area) > eps:
|
||||
raise Exception(
|
||||
f"observed_surface_area={observed_surface_area} differs from expected_surface_area={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={observed_bbox} differs from expected_bbox={expected_bbox}"
|
||||
)
|
||||
|
||||
return extruded_polygon
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
from manifold3d import Mesh, Manifold
|
||||
|
||||
|
||||
def gyroid(x, y, z):
|
||||
xi = x - math.pi / 4.0
|
||||
yi = y - math.pi / 4.0
|
||||
zi = z - math.pi / 4.0
|
||||
return (
|
||||
math.cos(xi) * math.sin(yi)
|
||||
+ math.cos(yi) * math.sin(zi)
|
||||
+ math.cos(zi) * math.sin(xi)
|
||||
)
|
||||
|
||||
|
||||
def gyroid_levelset(level, period, size, n):
|
||||
return Manifold(
|
||||
Mesh.level_set(
|
||||
gyroid,
|
||||
[-period, -period, -period, period, period, period],
|
||||
period / n,
|
||||
level,
|
||||
)
|
||||
).scale([size / period] * 3)
|
||||
|
||||
|
||||
def rhombic_dodecahedron(size):
|
||||
box = Manifold.cube(size * math.sqrt(2.0) * np.array([1, 1, 2]), True)
|
||||
result = box.rotate([90, 45, 0]) ^ box.rotate([90, 45, 90])
|
||||
return result ^ box.rotate([0, 0, 45])
|
||||
|
||||
|
||||
def gyroid_module(size=20, n=15):
|
||||
period = math.pi * 2.0
|
||||
result = (
|
||||
gyroid_levelset(-0.4, period, size, n) ^ rhombic_dodecahedron(size)
|
||||
) - gyroid_levelset(0.4, period, size, n)
|
||||
return result.rotate([-45, 0, 90]).translate([0, 0, size / math.sqrt(2.0)])
|
||||
|
||||
|
||||
def run(size=20, n=15):
|
||||
return gyroid_module(size, n)
|
||||
@@ -18,15 +18,15 @@ import pathlib
|
||||
import sys
|
||||
import importlib
|
||||
import trimesh
|
||||
import numpy as np
|
||||
from time import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
current_file = pathlib.Path(__file__)
|
||||
current_dir = current_file.parent
|
||||
files = [f.parts[-1][:-3]
|
||||
for f in current_dir.glob('*.py') if f != current_file]
|
||||
files = [f.parts[-1][:-3] for f in current_dir.glob("*.py") if f != current_file]
|
||||
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == "-e"
|
||||
|
||||
for f in files:
|
||||
module = importlib.import_module(f)
|
||||
@@ -34,9 +34,16 @@ if __name__ == "__main__":
|
||||
model = module.run()
|
||||
mesh = model.to_mesh()
|
||||
if export_models:
|
||||
if mesh.vert_properties.shape[1] > 3:
|
||||
vertices = mesh.vert_properties[:, :3]
|
||||
colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8)
|
||||
else:
|
||||
vertices = mesh.vert_properties
|
||||
colors = None
|
||||
meshOut = trimesh.Trimesh(
|
||||
vertices=mesh.vert_pos, faces=mesh.tri_verts)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
|
||||
print(f'Exported model to {f}.glb')
|
||||
vertices=vertices, faces=mesh.tri_verts, vertex_colors=colors
|
||||
)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f"{f}.glb", "glb")
|
||||
print(f"Exported model to {f}.glb")
|
||||
t1 = time()
|
||||
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
|
||||
print(f"Took {(t1-t0)*1000:.1f}ms for {f}")
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
from manifold3d import Manifold, Mesh
|
||||
import numpy as np
|
||||
|
||||
|
||||
def run():
|
||||
# A smoothed manifold demonstrating selective edge sharpening with
|
||||
# smooth() and refine(), see more details at:
|
||||
# https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
height = 10
|
||||
radius = 30
|
||||
offset = 20
|
||||
wiggles = 12
|
||||
sharpness = 0.8
|
||||
n = 50
|
||||
|
||||
triangles = []
|
||||
positions = [[-offset, 0, height], [-offset, 0, -height]]
|
||||
sharpenedEdges = []
|
||||
|
||||
delta = np.pi / wiggles
|
||||
for i in range(2 * wiggles):
|
||||
theta = (i - wiggles) * delta
|
||||
amp = 0.5 * height * max(np.cos(0.8 * theta), 0)
|
||||
|
||||
positions.append(
|
||||
[
|
||||
radius * np.cos(theta),
|
||||
radius * np.sin(theta),
|
||||
amp * (1 if i % 2 == 0 else -1),
|
||||
]
|
||||
)
|
||||
|
||||
j = i + 1
|
||||
if j == 2 * wiggles:
|
||||
j = 0
|
||||
|
||||
smoothness = 1 - sharpness * np.cos((theta + delta / 2) / 2)
|
||||
halfedge = 3 * len(triangles) + 1
|
||||
sharpenedEdges.append((halfedge, smoothness))
|
||||
triangles.append([0, 2 + i, 2 + j])
|
||||
|
||||
halfedge = 3 * len(triangles) + 1
|
||||
sharpenedEdges.append((halfedge, smoothness))
|
||||
triangles.append([1, 2 + j, 2 + i])
|
||||
|
||||
scallop = Mesh(
|
||||
tri_verts=np.array(triangles, np.int32),
|
||||
vert_properties=np.array(positions, np.float32),
|
||||
)
|
||||
|
||||
def colorCurvature(_pos, oldProp):
|
||||
a = max(0, min(1, oldProp[0] / 3 + 0.5))
|
||||
b = a * a * (3 - 2 * a)
|
||||
red = [1, 0, 0]
|
||||
blue = [0, 0, 1]
|
||||
return [(1 - b) * blue[i] + b * red[i] for i in range(3)]
|
||||
|
||||
edges, smoothing = zip(*sharpenedEdges)
|
||||
return (
|
||||
Manifold.smooth(scallop, edges, smoothing)
|
||||
.refine(n)
|
||||
.calculate_curvature(-1, 0)
|
||||
.set_properties(3, colorCurvature)
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
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
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def run():
|
||||
a = Manifold.cube([1.0, 1.0, 1.0])
|
||||
b = Manifold.cube([1.0, 1.0, 1.0]).rotate([45.0, 45.0, 45.0])
|
||||
return a.split(b)[0]
|
||||
@@ -0,0 +1,43 @@
|
||||
from manifold3d import Manifold
|
||||
import numpy as np
|
||||
|
||||
|
||||
def fractal(holes, hole, w, position, depth, maxDepth):
|
||||
w /= 3
|
||||
holes.append(hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]))
|
||||
if depth == maxDepth:
|
||||
return
|
||||
offsets = np.array(
|
||||
[
|
||||
[-w, -w],
|
||||
[-w, 0.0],
|
||||
[-w, w],
|
||||
[0.0, w],
|
||||
[w, w],
|
||||
[w, 0.0],
|
||||
[w, -w],
|
||||
[0.0, -w],
|
||||
]
|
||||
)
|
||||
for offset in offsets:
|
||||
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
|
||||
|
||||
|
||||
def posColors(pos, _):
|
||||
return [-p + 0.5 for p in pos] + [1.0]
|
||||
|
||||
|
||||
def run(n=1):
|
||||
result = Manifold.cube([1, 1, 1], True)
|
||||
holes = []
|
||||
fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n)
|
||||
|
||||
hole = Manifold.compose(holes)
|
||||
|
||||
result -= hole
|
||||
result -= hole.rotate([90, 0, 0])
|
||||
result -= hole.rotate([0, 90, 0])
|
||||
|
||||
return (
|
||||
result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale([100] * 3)
|
||||
)
|
||||
@@ -0,0 +1,98 @@
|
||||
from manifold3d import *
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
# Creates a classic torus knot, defined as a string wrapping periodically
|
||||
# around the surface of an imaginary donut. If p and q have a common
|
||||
# factor then you will get multiple separate, interwoven knots. This is
|
||||
# an example of using the warp() method, thus avoiding any direct
|
||||
# handling of triangles.
|
||||
|
||||
|
||||
def run(warp_single=False):
|
||||
# The number of times the thread passes through the donut hole.
|
||||
p = 1
|
||||
# The number of times the thread circles the donut.
|
||||
q = 3
|
||||
# Radius of the interior of the imaginary donut.
|
||||
majorRadius = 25
|
||||
# Radius of the small cross-section of the imaginary donut.
|
||||
minorRadius = 10
|
||||
# Radius of the small cross-section of the actual object.
|
||||
threadRadius = 3.75
|
||||
# Number of linear segments making up the threadRadius circle. Default is
|
||||
# getCircularSegments(threadRadius).
|
||||
circularSegments = -1
|
||||
# Number of segments along the length of the knot. Default makes roughly
|
||||
# square facets.
|
||||
linearSegments = -1
|
||||
|
||||
# These default values recreate Matlab Knot by Emmett Lalish:
|
||||
# https://www.thingiverse.com/thing:7080
|
||||
|
||||
kLoops = np.gcd(p, q)
|
||||
pk = p / kLoops
|
||||
qk = q / kLoops
|
||||
n = (
|
||||
circularSegments
|
||||
if circularSegments > 2
|
||||
else get_circular_segments(threadRadius)
|
||||
)
|
||||
m = linearSegments if linearSegments > 2 else n * qk * majorRadius / threadRadius
|
||||
|
||||
offset = 2
|
||||
circle = CrossSection.circle(1, n).translate([offset, 0])
|
||||
|
||||
def ax_rotate(x, theta):
|
||||
a, b = (x + 1) % 3, (x + 2) % 3
|
||||
s, c = np.sin(theta), np.cos(theta)
|
||||
m = np.zeros((len(theta), 4, 4), dtype=np.float32)
|
||||
m[:, a, a], m[:, a, b] = c, s
|
||||
m[:, b, a], m[:, b, b] = -s, c
|
||||
m[:, x, x], m[:, 3, 3] = 1, 1
|
||||
return m
|
||||
|
||||
def func(pts):
|
||||
npts = pts.shape[0]
|
||||
x, y, z = pts[:, 0], pts[:, 1], pts[:, 2]
|
||||
psi = qk * np.arctan2(x, y)
|
||||
theta = psi * pk / qk
|
||||
x1 = np.sqrt(x * x + y * y)
|
||||
phi = np.arctan2(x1 - offset, z)
|
||||
|
||||
v = np.zeros((npts, 4), dtype=np.float32)
|
||||
v[:, 0] = threadRadius * np.cos(phi)
|
||||
v[:, 2] = threadRadius * np.sin(phi)
|
||||
v[:, 3] = 1
|
||||
r = majorRadius + minorRadius * np.cos(theta)
|
||||
|
||||
m1 = ax_rotate(0, -np.arctan2(pk * minorRadius, qk * r))
|
||||
m1[:, 3, 0] = minorRadius
|
||||
m2 = ax_rotate(1, theta)
|
||||
m2[:, 3, 0] = majorRadius
|
||||
m3 = ax_rotate(2, psi)
|
||||
|
||||
v = v[:, None, :] @ m1 @ m2 @ m3
|
||||
pts[:] = v[:, 0, :3]
|
||||
|
||||
def func_single(v):
|
||||
pts = np.array([v])
|
||||
func(pts)
|
||||
return pts[0]
|
||||
|
||||
if warp_single:
|
||||
return Manifold.revolve(circle, int(m)).warp(func_single)
|
||||
else:
|
||||
return Manifold.revolve(circle, int(m)).warp_batch(func)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("warp_single", [True, False])
|
||||
def test_warp(warp_single):
|
||||
m = run(warp_single=warp_single)
|
||||
assert m.volume() == pytest.approx(20785.76)
|
||||
assert m.surface_area() == pytest.approx(11176.8)
|
||||
assert m.genus() == 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,8 @@
|
||||
from manifold3d import Manifold
|
||||
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold.cube().rotate([0, 0, 45 + 180])
|
||||
return obj
|
||||
@@ -0,0 +1,109 @@
|
||||
from os.path import dirname
|
||||
import re
|
||||
|
||||
base = dirname(dirname(dirname(__file__)))
|
||||
|
||||
def snake_case(name):
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
||||
|
||||
|
||||
def python_param_modifier(comment):
|
||||
# p = f":{snake_case(m[0][1:])}:"
|
||||
comment = re.sub(r"@(param \w+)", lambda m: f':{snake_case(m[1])}:', comment)
|
||||
# python API renames `MeshGL` to `Mesh`
|
||||
comment = re.sub('mesh_gl', 'mesh', comment)
|
||||
comment = re.sub('MeshGL', 'Mesh', comment)
|
||||
return comment
|
||||
|
||||
|
||||
def method_key(name):
|
||||
name = re.sub(r"\+", "_plus", name)
|
||||
name = re.sub(r"\-", "_minus", name)
|
||||
name = re.sub(r"\^", "_xor", name)
|
||||
name = re.sub(r"\=", "_eq", name)
|
||||
name = re.sub(r"\:", "_", name)
|
||||
name = re.sub(r"\~", "destroy_", name)
|
||||
return name
|
||||
|
||||
|
||||
parens_re = re.compile(r"[^(]+\(([^(]*(\(.*\))*[^(]*\))", flags=re.DOTALL)
|
||||
args_re = re.compile(
|
||||
r"^[^,^\(^\)]*(\(.*\))*[^,^\(^\)]*[\s\&\*]([0-9\w]+)\s*[,\)]", flags=re.DOTALL
|
||||
)
|
||||
|
||||
|
||||
def parse_args(s):
|
||||
par = parens_re.match(s)
|
||||
if not par:
|
||||
return None
|
||||
out = []
|
||||
arg_str = par[1]
|
||||
while m := re.search(args_re, arg_str):
|
||||
out += [snake_case(m[2])]
|
||||
arg_str = arg_str[m.span()[1] :]
|
||||
return out
|
||||
|
||||
|
||||
def collect(fname, matcher, param_modifier=python_param_modifier):
|
||||
comment = ""
|
||||
with open(fname) as f:
|
||||
for line in f:
|
||||
line = line.lstrip()
|
||||
if line.startswith("/**"):
|
||||
comment = ""
|
||||
elif line.startswith("*/"):
|
||||
pass
|
||||
elif line.startswith("*") and comment is not None:
|
||||
comment += line[1:].lstrip()
|
||||
elif comment and (m := matcher(line)):
|
||||
while (args := parse_args(line)) is None:
|
||||
line += next(f)
|
||||
if len(line) > 500:
|
||||
break
|
||||
|
||||
method = method_key(snake_case(m[1]))
|
||||
# comment = re.sub(param_re, param_modifier, comment)
|
||||
comment = param_modifier(comment)
|
||||
method = "__".join([method, *args])
|
||||
assert method not in comments
|
||||
comments[method] = comment
|
||||
comment = ""
|
||||
|
||||
|
||||
comments = {}
|
||||
|
||||
method_re = re.compile(r"(\w+::[\w\-\+\^\=\:]+)\(")
|
||||
function_re = re.compile(r"([\w\-\+\^\=\:]+)\(")
|
||||
|
||||
|
||||
# we don't handle inline functions in classes properly
|
||||
# so instead just white-list functions we want
|
||||
def select_functions(s):
|
||||
m = function_re.search(s)
|
||||
if m and "Triangulate" in m[0]:
|
||||
return m
|
||||
if m and "Circular" in m[0]:
|
||||
return m
|
||||
return None
|
||||
|
||||
|
||||
collect(f"{base}/src/manifold/src/manifold.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/manifold/src/constructors.cpp", lambda s: method_re.search(s))
|
||||
collect(f"{base}/src/manifold/src/sort.cpp", lambda s: method_re.search(s))
|
||||
collect(
|
||||
f"{base}/src/cross_section/src/cross_section.cpp", lambda s: method_re.search(s)
|
||||
)
|
||||
collect(f"{base}/src/polygon/src/polygon.cpp", select_functions)
|
||||
collect(f"{base}/src/utilities/include/public.h", select_functions)
|
||||
|
||||
comments = dict(sorted(comments.items()))
|
||||
|
||||
gen_h = "autogen_docstrings.inl"
|
||||
with open(gen_h, "w") as f:
|
||||
f.write("#pragma once\n\n")
|
||||
f.write("// --- AUTO GENERATED ---\n")
|
||||
f.write("// gen_docs.py is run by cmake build\n\n")
|
||||
f.write("namespace manifold_docstrings {\n")
|
||||
for key, doc in comments.items():
|
||||
f.write(f'const char* {key} = R"___({doc.strip()})___";\n')
|
||||
f.write("} // namespace manifold_docs")
|
||||
@@ -0,0 +1,693 @@
|
||||
// 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 <optional>
|
||||
#include <string>
|
||||
|
||||
#include "autogen_docstrings.inl" // generated in build folder
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "nanobind/nanobind.h"
|
||||
#include "nanobind/ndarray.h"
|
||||
#include "nanobind/operators.h"
|
||||
#include "nanobind/stl/function.h"
|
||||
#include "nanobind/stl/optional.h"
|
||||
#include "nanobind/stl/pair.h"
|
||||
#include "nanobind/stl/tuple.h"
|
||||
#include "nanobind/stl/vector.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
|
||||
namespace nb = nanobind;
|
||||
using namespace manifold;
|
||||
|
||||
template <class T>
|
||||
struct glm_name {};
|
||||
template <>
|
||||
struct glm_name<glm::vec3> {
|
||||
static constexpr char const name[] = "Floatx3";
|
||||
static constexpr char const multi_name[] = "FloatNx3";
|
||||
};
|
||||
template <>
|
||||
struct glm_name<glm::vec2> {
|
||||
static constexpr char const name[] = "Floatx2";
|
||||
static constexpr char const multi_name[] = "FloatNx2";
|
||||
};
|
||||
template <>
|
||||
struct glm_name<glm::ivec3> {
|
||||
static constexpr char const name[] = "Intx3";
|
||||
static constexpr char const multi_name[] = "IntNx3";
|
||||
};
|
||||
template <>
|
||||
struct glm_name<glm::mat4x3> {
|
||||
static constexpr char const name[] = "Float3x4";
|
||||
};
|
||||
template <>
|
||||
struct glm_name<glm::mat3x2> {
|
||||
static constexpr char const name[] = "Float2x3";
|
||||
};
|
||||
|
||||
// handle glm::vecN
|
||||
template <class T, int N, glm::qualifier Q>
|
||||
struct nb::detail::type_caster<glm::vec<N, T, Q>> {
|
||||
using glm_type = glm::vec<N, T, Q>;
|
||||
NB_TYPE_CASTER(glm_type, const_name(glm_name<glm_type>::name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
|
||||
int size = PyObject_Size(src.ptr()); // negative on failure
|
||||
if (size != N) return false;
|
||||
make_caster<T> t_cast;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (!t_cast.from_python(src[i], flags, cleanup)) return false;
|
||||
value[i] = t_cast.value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(glm_type vec, rv_policy policy,
|
||||
cleanup_list *cleanup) noexcept {
|
||||
nb::list out;
|
||||
for (int i = 0; i < N; i++) out.append(vec[i]);
|
||||
return out.release();
|
||||
}
|
||||
};
|
||||
|
||||
// handle glm::matMxN
|
||||
template <class T, int C, int R, glm::qualifier Q>
|
||||
struct nb::detail::type_caster<glm::mat<C, R, T, Q>> {
|
||||
using glm_type = glm::mat<C, R, T, Q>;
|
||||
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<R, C>>;
|
||||
NB_TYPE_CASTER(glm_type, const_name(glm_name<glm_type>::name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
|
||||
int rows = PyObject_Size(src.ptr()); // negative on failure
|
||||
if (rows != R) return false;
|
||||
for (size_t i = 0; i < R; i++) {
|
||||
const nb::object &slice = src[i];
|
||||
int cols = PyObject_Size(slice.ptr()); // negative on failure
|
||||
if (cols != C) return false;
|
||||
for (size_t j = 0; j < C; j++) {
|
||||
make_caster<T> t_cast;
|
||||
if (!t_cast.from_python(slice[j], flags, cleanup)) return false;
|
||||
value[j][i] = t_cast.value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(glm_type mat, rv_policy policy,
|
||||
cleanup_list *cleanup) noexcept {
|
||||
T *buffer = new T[R * C];
|
||||
nb::capsule mem_mgr(buffer, [](void *p) noexcept { delete[](T *) p; });
|
||||
for (int i = 0; i < R; i++) {
|
||||
for (int j = 0; j < C; j++) {
|
||||
// py is (Rows, Cols), glm is (Cols, Rows)
|
||||
buffer[i * C + j] = mat[j][i];
|
||||
}
|
||||
}
|
||||
numpy_type arr{buffer, {R, C}, std::move(mem_mgr)};
|
||||
return ndarray_wrap(arr.handle(), int(ndarray_framework::numpy), policy,
|
||||
cleanup);
|
||||
}
|
||||
};
|
||||
|
||||
// handle std::vector<glm::vecN>
|
||||
template <class T, int N, glm::qualifier Q>
|
||||
struct nb::detail::type_caster<std::vector<glm::vec<N, T, Q>>> {
|
||||
using glm_type = glm::vec<N, T, Q>;
|
||||
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>>;
|
||||
NB_TYPE_CASTER(std::vector<glm_type>,
|
||||
const_name(glm_name<glm_type>::multi_name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
|
||||
make_caster<numpy_type> arr_cast;
|
||||
if (arr_cast.from_python(src, flags, cleanup)) {
|
||||
int num_vec = arr_cast.value.shape(0);
|
||||
value.resize(num_vec);
|
||||
for (int i = 0; i < num_vec; i++) {
|
||||
for (int j = 0; j < N; j++) {
|
||||
value[i][j] = arr_cast.value(i, j);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int num_vec = PyObject_Size(src.ptr()); // negative on failure
|
||||
if (num_vec < 0) return false;
|
||||
value.resize(num_vec);
|
||||
for (int i = 0; i < num_vec; i++) {
|
||||
make_caster<glm_type> vec_cast;
|
||||
if (!vec_cast.from_python(src[i], flags, cleanup)) return false;
|
||||
value[i] = vec_cast.value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(Value vec, rv_policy policy,
|
||||
cleanup_list *cleanup) noexcept {
|
||||
size_t num_vec = vec.size();
|
||||
T *buffer = new T[num_vec * N];
|
||||
nb::capsule mem_mgr(buffer, [](void *p) noexcept { delete[](T *) p; });
|
||||
for (int i = 0; i < num_vec; i++) {
|
||||
for (int j = 0; j < N; j++) {
|
||||
buffer[i * N + j] = vec[i][j];
|
||||
}
|
||||
}
|
||||
numpy_type arr{buffer, {num_vec, N}, std::move(mem_mgr)};
|
||||
return ndarray_wrap(arr.handle(), int(ndarray_framework::numpy), policy,
|
||||
cleanup);
|
||||
}
|
||||
};
|
||||
|
||||
// handle VecView<glm::vec*>
|
||||
template <class T, int N, glm::qualifier Q>
|
||||
struct nb::detail::type_caster<manifold::VecView<glm::vec<N, T, Q>>> {
|
||||
using glm_type = glm::vec<N, T, Q>;
|
||||
using numpy_type = nb::ndarray<nb::numpy, T, nb::shape<nb::any, N>>;
|
||||
NB_TYPE_CASTER(manifold::VecView<glm_type>,
|
||||
const_name(glm_name<glm_type>::multi_name));
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
|
||||
make_caster<numpy_type> arr_cast;
|
||||
if (!arr_cast.from_python(src, flags, cleanup)) return false;
|
||||
// TODO try 2d iterators if numpy cast fails
|
||||
int num_vec = arr_cast.value.shape(0);
|
||||
if (num_vec != value.size()) return false;
|
||||
for (int i = 0; i < num_vec; i++) {
|
||||
for (int j = 0; j < N; j++) {
|
||||
value[i][j] = arr_cast.value(i, j);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static handle from_cpp(Value vec, rv_policy policy,
|
||||
cleanup_list *cleanup) noexcept {
|
||||
size_t num_vec = vec.size();
|
||||
static_assert(sizeof(vec[0]) == (N * sizeof(T)),
|
||||
"VecView -> numpy requires packed structs");
|
||||
numpy_type arr{&vec[0], {num_vec, N}};
|
||||
return ndarray_wrap(arr.handle(), int(ndarray_framework::numpy), policy,
|
||||
cleanup);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> toVector(const T *arr, size_t size) {
|
||||
return std::vector<T>(arr, arr + size);
|
||||
}
|
||||
|
||||
using namespace manifold_docstrings;
|
||||
|
||||
// strip original :params: and replace with ours
|
||||
const std::string manifold__rotate_xyz =
|
||||
manifold__rotate__x_degrees__y_degrees__z_degrees;
|
||||
const std::string manifold__rotate__v =
|
||||
manifold__rotate_xyz.substr(0, manifold__rotate_xyz.find(":param")) +
|
||||
":param v: [X, Y, Z] rotation in degrees.";
|
||||
|
||||
NB_MODULE(manifold3d, m) {
|
||||
m.doc() = "Python binding for the Manifold library.";
|
||||
|
||||
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
|
||||
nb::arg("angle"), set_min_circular_angle__angle);
|
||||
|
||||
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
|
||||
nb::arg("length"), set_min_circular_edge_length__length);
|
||||
|
||||
m.def("set_circular_segments", Quality::SetCircularSegments,
|
||||
nb::arg("number"), set_circular_segments__number);
|
||||
|
||||
m.def("get_circular_segments", Quality::GetCircularSegments,
|
||||
nb::arg("radius"), get_circular_segments__radius);
|
||||
|
||||
m.def("triangulate", &Triangulate, nb::arg("polygons"),
|
||||
nb::arg("precision") = -1, // TODO document
|
||||
triangulate__polygons__precision);
|
||||
|
||||
nb::class_<Manifold>(m, "Manifold")
|
||||
.def(nb::init<>(), manifold__manifold)
|
||||
.def(nb::init<const MeshGL &, const std::vector<float> &>(),
|
||||
nb::arg("mesh"), nb::arg("property_tolerance") = nb::list(),
|
||||
manifold__manifold__mesh_gl__property_tolerance)
|
||||
.def(nb::self + nb::self, manifold__operator_plus__q)
|
||||
.def(nb::self - nb::self, manifold__operator_minus__q)
|
||||
.def(nb::self ^ nb::self, manifold__operator_xor__q)
|
||||
.def(
|
||||
"hull", [](const Manifold &self) { return self.Hull(); },
|
||||
manifold__hull)
|
||||
.def_static(
|
||||
"batch_hull",
|
||||
[](std::vector<Manifold> ms) { return Manifold::Hull(ms); },
|
||||
nb::arg("manifolds"), manifold__hull__manifolds)
|
||||
.def_static(
|
||||
"hull_points",
|
||||
[](std::vector<glm::vec3> pts) { return Manifold::Hull(pts); },
|
||||
nb::arg("pts"), manifold__hull__pts)
|
||||
.def("transform", &Manifold::Transform, nb::arg("m"),
|
||||
manifold__transform__m)
|
||||
.def("translate", &Manifold::Translate, nb::arg("t"),
|
||||
manifold__translate__v)
|
||||
.def("scale", &Manifold::Scale, nb::arg("v"), manifold__scale__v)
|
||||
.def(
|
||||
"scale",
|
||||
[](const Manifold &m, float s) {
|
||||
m.Scale({s, s, s});
|
||||
},
|
||||
nb::arg("s"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily.\n\n"
|
||||
":param s: The scalar to multiply every vertex by component.")
|
||||
.def("mirror", &Manifold::Mirror, nb::arg("v"), manifold__mirror__normal)
|
||||
.def(
|
||||
"rotate",
|
||||
[](const Manifold &self, glm::vec3 v) {
|
||||
return self.Rotate(v.x, v.y, v.z);
|
||||
},
|
||||
nb::arg("v"), manifold__rotate__v.c_str())
|
||||
.def(
|
||||
"warp",
|
||||
[](const Manifold &self,
|
||||
std::function<glm::vec3(glm::vec3)> warp_func) {
|
||||
// need a wrapper because python cant modify a reference in-place
|
||||
return self.Warp([&warp_func](glm::vec3 &v) { v = warp_func(v); });
|
||||
},
|
||||
nb::arg("warp_func"), manifold__warp__warp_func)
|
||||
.def("warp_batch", &Manifold::WarpBatch, nb::arg("warp_func"),
|
||||
manifold__warp_batch__warp_func)
|
||||
.def(
|
||||
"set_properties",
|
||||
[](const Manifold &self, int newNumProp,
|
||||
const std::function<nb::object(
|
||||
glm::vec3, const nb::ndarray<nb::numpy, const float,
|
||||
nb::c_contig> &)> &f) {
|
||||
const int oldNumProp = self.NumProp();
|
||||
return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f](
|
||||
float *newProps,
|
||||
glm::vec3 v,
|
||||
const float *oldProps) {
|
||||
auto result =
|
||||
f(v, nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
oldProps, {static_cast<unsigned long>(oldNumProp)}));
|
||||
nb::ndarray<float, nb::shape<nb::any>> array;
|
||||
std::vector<float> vec;
|
||||
if (nb::try_cast(result, array)) {
|
||||
if (array.ndim() != 1 || array.shape(0) != newNumProp)
|
||||
throw std::runtime_error("Invalid vector shape, expected (" +
|
||||
std::to_string(newNumProp) + ")");
|
||||
for (int i = 0; i < newNumProp; i++) newProps[i] = array(i);
|
||||
} else if (nb::try_cast(result, vec)) {
|
||||
for (int i = 0; i < newNumProp; i++) newProps[i] = vec[i];
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
"Callback in set_properties should return an array");
|
||||
}
|
||||
});
|
||||
},
|
||||
nb::arg("new_num_prop"), nb::arg("f"),
|
||||
manifold__set_properties__num_prop__prop_func)
|
||||
.def("calculate_curvature", &Manifold::CalculateCurvature,
|
||||
nb::arg("gaussian_idx"), nb::arg("mean_idx"),
|
||||
manifold__calculate_curvature__gaussian_idx__mean_idx)
|
||||
.def("refine", &Manifold::Refine, nb::arg("n"), manifold__refine__n)
|
||||
.def("refine_to_length", &Manifold::RefineToLength, nb::arg("length"),
|
||||
manifold__refine_to_length__length)
|
||||
.def("to_mesh", &Manifold::GetMeshGL,
|
||||
nb::arg("normal_idx") = glm::ivec3(0),
|
||||
manifold__get_mesh_gl__normal_idx)
|
||||
.def("num_vert", &Manifold::NumVert, manifold__num_vert)
|
||||
.def("num_edge", &Manifold::NumEdge, manifold__num_edge)
|
||||
.def("num_tri", &Manifold::NumTri, manifold__num_tri)
|
||||
.def("num_prop", &Manifold::NumProp, manifold__num_prop)
|
||||
.def("num_prop_vert", &Manifold::NumPropVert, manifold__num_prop_vert)
|
||||
.def("precision", &Manifold::Precision, manifold__precision)
|
||||
.def("genus", &Manifold::Genus, manifold__genus)
|
||||
.def(
|
||||
"volume",
|
||||
[](const 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(
|
||||
"surface_area",
|
||||
[](const 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, manifold__original_id)
|
||||
.def("as_original", &Manifold::AsOriginal, manifold__as_original)
|
||||
.def("is_empty", &Manifold::IsEmpty, manifold__is_empty)
|
||||
.def("decompose", &Manifold::Decompose, manifold__decompose)
|
||||
.def("split", &Manifold::Split, nb::arg("cutter"),
|
||||
manifold__split__cutter)
|
||||
.def("split_by_plane", &Manifold::SplitByPlane, nb::arg("normal"),
|
||||
nb::arg("origin_offset"),
|
||||
manifold__split_by_plane__normal__origin_offset)
|
||||
.def("trim_by_plane", &Manifold::TrimByPlane, nb::arg("normal"),
|
||||
nb::arg("origin_offset"),
|
||||
manifold__trim_by_plane__normal__origin_offset)
|
||||
.def("slice", &Manifold::Slice, nb::arg("height"),
|
||||
manifold__slice__height)
|
||||
.def("project", &Manifold::Project, manifold__project)
|
||||
.def("status", &Manifold::Status, manifold__status)
|
||||
.def(
|
||||
"bounding_box",
|
||||
[](const Manifold &self) {
|
||||
Box b = self.BoundingBox();
|
||||
return nb::make_tuple(b.min[0], b.min[1], b.min[2], b.max[0],
|
||||
b.max[1], b.max[2]);
|
||||
},
|
||||
"Gets the manifold bounding box as a tuple "
|
||||
"(xmin, ymin, zmin, xmax, ymax, zmax).")
|
||||
.def_static(
|
||||
"smooth",
|
||||
[](const MeshGL &mesh, std::vector<int> sharpened_edges,
|
||||
std::vector<float> edge_smoothness) {
|
||||
if (sharpened_edges.size() != edge_smoothness.size()) {
|
||||
throw std::runtime_error(
|
||||
"sharpened_edges.size() != edge_smoothness.size()");
|
||||
}
|
||||
std::vector<Smoothness> vec(sharpened_edges.size());
|
||||
for (int i = 0; i < vec.size(); i++) {
|
||||
vec[i] = {sharpened_edges[i], edge_smoothness[i]};
|
||||
}
|
||||
return Manifold::Smooth(mesh, vec);
|
||||
},
|
||||
nb::arg("mesh"), nb::arg("sharpened_edges") = nb::list(),
|
||||
nb::arg("edge_smoothness") = nb::list(),
|
||||
// todo params slightly diff
|
||||
manifold__smooth__mesh_gl__sharpened_edges)
|
||||
.def_static("batch_boolean", &Manifold::BatchBoolean,
|
||||
nb::arg("manifolds"), nb::arg("op"),
|
||||
manifold__batch_boolean__manifolds__op)
|
||||
.def_static("compose", &Manifold::Compose, nb::arg("manifolds"),
|
||||
manifold__compose__manifolds)
|
||||
.def_static("tetrahedron", &Manifold::Tetrahedron, manifold__tetrahedron)
|
||||
.def_static("cube", &Manifold::Cube, nb::arg("size") = glm::vec3{1, 1, 1},
|
||||
nb::arg("center") = false, manifold__cube__size__center)
|
||||
.def_static(
|
||||
"extrude", &Manifold::Extrude, nb::arg("crossSection"),
|
||||
nb::arg("height"), nb::arg("n_divisions") = 0,
|
||||
nb::arg("twist_degrees") = 0.0f,
|
||||
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
manifold__extrude__cross_section__height__n_divisions__twist_degrees__scale_top)
|
||||
.def_static(
|
||||
"revolve", &Manifold::Revolve, nb::arg("crossSection"),
|
||||
nb::arg("circular_segments") = 0, nb::arg("revolve_degrees") = 360.0,
|
||||
manifold__revolve__cross_section__circular_segments__revolve_degrees)
|
||||
.def_static(
|
||||
"cylinder", &Manifold::Cylinder, nb::arg("height"),
|
||||
nb::arg("radius_low"), nb::arg("radius_high") = -1.0f,
|
||||
nb::arg("circular_segments") = 0, nb::arg("center") = false,
|
||||
manifold__cylinder__height__radius_low__radius_high__circular_segments__center)
|
||||
.def_static("sphere", &Manifold::Sphere, nb::arg("radius"),
|
||||
nb::arg("circular_segments") = 0,
|
||||
manifold__sphere__radius__circular_segments)
|
||||
.def_static("reserve_ids", Manifold::ReserveIDs, nb::arg("n"),
|
||||
manifold__reserve_ids__n);
|
||||
|
||||
nb::class_<MeshGL>(m, "Mesh")
|
||||
.def(
|
||||
// note that reshape requires mutable ndarray, but this will not
|
||||
// affect the original array passed into the function
|
||||
"__init__",
|
||||
[](MeshGL *self,
|
||||
nb::ndarray<float, nb::shape<nb::any, nb::any>, nb::c_contig>
|
||||
&vertProp,
|
||||
nb::ndarray<uint32_t, nb::shape<nb::any, 3>, nb::c_contig>
|
||||
&triVerts,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &mergeFromVert,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &mergeToVert,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &runIndex,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &runOriginalID,
|
||||
std::optional<nb::ndarray<float, nb::shape<nb::any, 4, 3>,
|
||||
nb::c_contig>> &runTransform,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &faceID,
|
||||
const std::optional<nb::ndarray<float, nb::shape<nb::any, 3, 4>,
|
||||
nb::c_contig>> &halfedgeTangent,
|
||||
float precision) {
|
||||
new (self) MeshGL();
|
||||
MeshGL &out = *self;
|
||||
out.numProp = vertProp.shape(1);
|
||||
out.vertProperties = toVector<float>(vertProp.data(), vertProp.size());
|
||||
|
||||
out.triVerts = toVector<uint32_t>(triVerts.data(), triVerts.size());
|
||||
|
||||
if (mergeFromVert.has_value())
|
||||
out.mergeFromVert =
|
||||
toVector<uint32_t>(mergeFromVert->data(), mergeFromVert->size());
|
||||
|
||||
if (mergeToVert.has_value())
|
||||
out.mergeToVert =
|
||||
toVector<uint32_t>(mergeToVert->data(), mergeToVert->size());
|
||||
|
||||
if (runIndex.has_value())
|
||||
out.runIndex = toVector<uint32_t>(runIndex->data(), runIndex->size());
|
||||
|
||||
if (runOriginalID.has_value())
|
||||
out.runOriginalID =
|
||||
toVector<uint32_t>(runOriginalID->data(), runOriginalID->size());
|
||||
|
||||
if (runTransform.has_value()) {
|
||||
out.runTransform =
|
||||
toVector<float>(runTransform->data(), runTransform->size());
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector<uint32_t>(faceID->data(), faceID->size());
|
||||
|
||||
if (halfedgeTangent.has_value()) {
|
||||
out.halfedgeTangent =
|
||||
toVector<float>(halfedgeTangent->data(), halfedgeTangent->size());
|
||||
}
|
||||
},
|
||||
nb::arg("vert_properties"), nb::arg("tri_verts"),
|
||||
nb::arg("merge_from_vert") = nb::none(),
|
||||
nb::arg("merge_to_vert") = nb::none(),
|
||||
nb::arg("run_index") = nb::none(),
|
||||
nb::arg("run_original_id") = nb::none(),
|
||||
nb::arg("run_transform") = nb::none(),
|
||||
nb::arg("face_id") = nb::none(),
|
||||
nb::arg("halfedge_tangent") = nb::none(), nb::arg("precision") = 0)
|
||||
.def_prop_ro("vert_properties",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.vertProperties.data(),
|
||||
{self.vertProperties.size() / self.numProp, self.numProp});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_prop_ro("tri_verts",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const int, nb::c_contig>(
|
||||
self.triVerts.data(), {self.triVerts.size() / 3, 3});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_prop_ro("run_transform",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.runTransform.data(), {self.runTransform.size() / 12, 4, 3});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_prop_ro("halfedge_tangent",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.halfedgeTangent.data(), {self.halfedgeTangent.size() / 12, 3, 4});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_ro("merge_from_vert", &MeshGL::mergeFromVert)
|
||||
.def_ro("merge_to_vert", &MeshGL::mergeToVert)
|
||||
.def_ro("run_index", &MeshGL::runIndex)
|
||||
.def_ro("run_original_id", &MeshGL::runOriginalID)
|
||||
.def_ro("face_id", &MeshGL::faceID)
|
||||
.def_static(
|
||||
"level_set",
|
||||
[](const std::function<float(float, float, float)> &f,
|
||||
std::vector<float> bounds, float edgeLength, float level = 0.0) {
|
||||
// Same format as Manifold.bounding_box
|
||||
Box bound = {glm::vec3(bounds[0], bounds[1], bounds[2]),
|
||||
glm::vec3(bounds[3], bounds[4], bounds[5])};
|
||||
|
||||
std::function<float(glm::vec3)> cppToPython = [&f](glm::vec3 v) {
|
||||
return f(v.x, v.y, v.z);
|
||||
};
|
||||
Mesh result = LevelSet(cppToPython, bound, edgeLength, level, false);
|
||||
return MeshGL(result);
|
||||
},
|
||||
nb::arg("f"), nb::arg("bounds"), nb::arg("edgeLength"),
|
||||
nb::arg("level") = 0.0,
|
||||
"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."
|
||||
"\n\n"
|
||||
":param f: The signed-distance functor, containing this function "
|
||||
"signature: `def sdf(xyz : tuple) -> float:`, 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."
|
||||
":return Mesh: This mesh is guaranteed to be manifold."
|
||||
"Use Manifold.from_mesh(mesh) to create a Manifold")
|
||||
.def("merge", &MeshGL::Merge, mesh_gl__merge);
|
||||
|
||||
nb::enum_<Manifold::Error>(m, "Error")
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
nb::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.");
|
||||
|
||||
nb::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::Round,
|
||||
"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.");
|
||||
|
||||
nb::enum_<OpType>(m, "OpType", "Operation types for batch_boolean")
|
||||
.value("Add", OpType::Add)
|
||||
.value("Subtract", OpType::Subtract)
|
||||
.value("Intersect", OpType::Intersect);
|
||||
|
||||
nb::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(nb::init<>(), cross_section__cross_section)
|
||||
.def(nb::init<std::vector<std::vector<glm::vec2>>,
|
||||
CrossSection::FillRule>(),
|
||||
nb::arg("contours"),
|
||||
nb::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
cross_section__cross_section__contours__fillrule)
|
||||
.def("area", &CrossSection::Area, cross_section__area)
|
||||
.def("num_vert", &CrossSection::NumVert, cross_section__num_vert)
|
||||
.def("num_contour", &CrossSection::NumContour, cross_section__num_contour)
|
||||
.def("is_empty", &CrossSection::IsEmpty, cross_section__is_empty)
|
||||
.def(
|
||||
"bounds",
|
||||
[](const CrossSection &self) {
|
||||
Rect r = self.Bounds();
|
||||
return nb::make_tuple(r.min[0], r.min[1], r.max[0], r.max[1]);
|
||||
},
|
||||
"Return bounding box of CrossSection as tuple("
|
||||
"min_x, min_y, max_x, max_y)")
|
||||
.def("translate", &CrossSection::Translate, nb::arg("v"),
|
||||
cross_section__translate__v)
|
||||
.def("rotate", &CrossSection::Rotate, nb::arg("degrees"),
|
||||
cross_section__rotate__degrees)
|
||||
.def("scale", &CrossSection::Scale, nb::arg("scale"),
|
||||
cross_section__scale__scale)
|
||||
.def(
|
||||
"scale",
|
||||
[](const CrossSection &self, float s) {
|
||||
self.Scale({s, s});
|
||||
},
|
||||
nb::arg("s"),
|
||||
"Scale this CrossSection in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param s: The scalar to multiply every vertex by per component.")
|
||||
.def("mirror", &CrossSection::Mirror, nb::arg("ax"),
|
||||
cross_section__mirror__ax)
|
||||
.def("transform", &CrossSection::Transform, nb::arg("m"),
|
||||
cross_section__transform__m)
|
||||
.def(
|
||||
"warp",
|
||||
[](const CrossSection &self,
|
||||
std::function<glm::vec2(glm::vec2)> warp_func) {
|
||||
// need a wrapper because python cant modify a reference in-place
|
||||
return self.Warp([&warp_func](glm::vec2 &v) { v = warp_func(v); });
|
||||
},
|
||||
nb::arg("warp_func"), cross_section__warp__warp_func)
|
||||
.def("warp_batch", &CrossSection::WarpBatch, nb::arg("warp_func"),
|
||||
cross_section__warp_batch__warp_func)
|
||||
.def("simplify", &CrossSection::Simplify, nb::arg("epsilon") = 1e-6,
|
||||
cross_section__simplify__epsilon)
|
||||
.def(
|
||||
"offset", &CrossSection::Offset, nb::arg("delta"),
|
||||
nb::arg("join_type"), nb::arg("miter_limit") = 2.0,
|
||||
nb::arg("circular_segments") = 0,
|
||||
cross_section__offset__delta__jointype__miter_limit__circular_segments)
|
||||
.def(nb::self + nb::self, cross_section__operator_plus__q)
|
||||
.def(nb::self - nb::self, cross_section__operator_minus__q)
|
||||
.def(nb::self ^ nb::self, cross_section__operator_xor__q)
|
||||
.def(
|
||||
"hull", [](const CrossSection &self) { return self.Hull(); },
|
||||
cross_section__hull)
|
||||
.def_static(
|
||||
"batch_hull",
|
||||
[](std::vector<CrossSection> cs) { return CrossSection::Hull(cs); },
|
||||
nb::arg("cross_sections"), cross_section__hull__cross_sections)
|
||||
.def_static(
|
||||
"hull_points",
|
||||
[](std::vector<glm::vec2> pts) { return CrossSection::Hull(pts); },
|
||||
nb::arg("pts"), cross_section__hull__pts)
|
||||
.def("decompose", &CrossSection::Decompose, cross_section__decompose)
|
||||
.def_static("batch_boolean", &CrossSection::BatchBoolean,
|
||||
nb::arg("cross_sections"), nb::arg("op"),
|
||||
cross_section__batch_boolean__cross_sections__op)
|
||||
.def_static("compose", &CrossSection::Compose, nb::arg("cross_sections"),
|
||||
cross_section__compose__cross_sections)
|
||||
.def("to_polygons", &CrossSection::ToPolygons, cross_section__to_polygons)
|
||||
.def(
|
||||
"extrude", &Manifold::Extrude, nb::arg("height"),
|
||||
nb::arg("n_divisions") = 0, nb::arg("twist_degrees") = 0.0f,
|
||||
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
manifold__extrude__cross_section__height__n_divisions__twist_degrees__scale_top)
|
||||
.def("revolve", &Manifold::Revolve, nb::arg("circular_segments") = 0,
|
||||
nb::arg("revolve_degrees") = 360.0,
|
||||
manifold__revolve__cross_section__circular_segments__revolve_degrees)
|
||||
|
||||
.def_static("square", &CrossSection::Square, nb::arg("size"),
|
||||
nb::arg("center") = false,
|
||||
cross_section__square__size__center)
|
||||
.def_static("circle", &CrossSection::Circle, nb::arg("radius"),
|
||||
nb::arg("circular_segments") = 0,
|
||||
cross_section__circle__radius__circular_segments);
|
||||
}
|
||||
@@ -1,504 +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;
|
||||
for (Manifold &manifold : manifolds) result += manifold;
|
||||
return result;
|
||||
}),
|
||||
"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", Manifold::Smooth,
|
||||
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_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<PolyVert> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {
|
||||
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
|
||||
j};
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
add_subdirectory(pybind11)
|
||||
add_subdirectory(nanobind)
|
||||
|
||||
@@ -16,23 +16,39 @@ 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)
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_14)
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
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)
|
||||
|
||||
# copy WASM build back here for publishing to npm
|
||||
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}/examples/)
|
||||
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# copy WASM build and TS declarations for Vite to package into ManifoldCAD.org
|
||||
add_custom_command(
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
|
||||
|
||||
# copy TS declarations to public so they can be accessed by our editor
|
||||
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_mesh_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
|
||||
## About the author
|
||||
|
||||
|
||||
@@ -15,120 +15,17 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "helpers.cpp"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
|
||||
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<PolyVert> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {polygons[i][j], j};
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return simplePolygons;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold) {
|
||||
MeshGL mesh = manifold.GetMeshGL();
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertPos",
|
||||
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
|
||||
mesh.vertNormal.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
|
||||
if (mesh["vertNormal"] != val::undefined()) {
|
||||
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
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, std::vector<float>& mat) {
|
||||
glm::mat4x3 matrix;
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
|
||||
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)
|
||||
@@ -151,18 +48,33 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("w", &glm::vec4::w);
|
||||
|
||||
enum_<Manifold::Error>("status")
|
||||
.value("NO_ERROR", Manifold::Error::NO_ERROR)
|
||||
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
|
||||
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
|
||||
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
|
||||
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
|
||||
.value("PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
|
||||
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
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")
|
||||
@@ -173,77 +85,113 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
value_object<BaryRef>("baryRef")
|
||||
.field("meshID", &BaryRef::meshID)
|
||||
.field("originalID", &BaryRef::originalID)
|
||||
.field("tri", &BaryRef::tri)
|
||||
.field("vertBary", &BaryRef::vertBary);
|
||||
|
||||
value_object<MeshRelation>("meshRelation")
|
||||
.field("barycentric", &MeshRelation::barycentric)
|
||||
.field("triBary", &MeshRelation::triBary);
|
||||
|
||||
value_object<Curvature>("curvature")
|
||||
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
|
||||
.field("minMeanCurvature", &Curvature::minMeanCurvature)
|
||||
.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<BaryRef>("Vector_baryRef");
|
||||
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);
|
||||
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
|
||||
function("_crossSectionHullPoints",
|
||||
select_overload<CrossSection(std::vector<glm::vec2>)>(
|
||||
&CrossSection::Hull));
|
||||
|
||||
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("slice", &Manifold::Slice)
|
||||
.function("project", &Manifold::Project)
|
||||
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
|
||||
.function("_GetMeshJS", &js::GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("_Warp", &Warp)
|
||||
.function("_Transform", &Transform)
|
||||
.function("refineToLength", &Manifold::RefineToLength)
|
||||
.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("_Decompose", &Manifold::Decompose)
|
||||
.function("_Mirror", &Manifold::Mirror)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
.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)
|
||||
.function("_getMeshRelation", &Manifold::GetMeshRelation);
|
||||
.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("_manifoldCollectVertices", &man_js::CollectVertices);
|
||||
function("_manifoldHullPoints",
|
||||
select_overload<Manifold(const std::vector<glm::vec3>&)>(
|
||||
&Manifold::Hull));
|
||||
|
||||
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);
|
||||
// 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,24 +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;
|
||||
};
|
||||
|
||||
// note that the matrix is using column major (same as glm)
|
||||
Module.Manifold.prototype.transform = function(mat) {
|
||||
console.assert(mat.length == 4, 'expects a 3x4 matrix');
|
||||
const vec = new Module.Vector_f32();
|
||||
for (let col of mat) {
|
||||
console.assert(col.length == 3, 'expects a 3x4 matrix');
|
||||
for (let x of col) mat.push_back(x);
|
||||
}
|
||||
const result = this._Transform(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
Module.Manifold.prototype.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) {
|
||||
@@ -97,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() {
|
||||
@@ -110,82 +289,6 @@ 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;
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
triVerts = new Uint32Array(),
|
||||
vertPos = new Float32Array(),
|
||||
vertNormal,
|
||||
halfedgeTangent
|
||||
} = {}) {
|
||||
this.triVerts = triVerts;
|
||||
this.vertPos = vertPos;
|
||||
this.vertNormal = vertNormal;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
return this.triVerts.length / 3;
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertPos.length / 3;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
|
||||
}
|
||||
|
||||
normal(vert) {
|
||||
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function() {
|
||||
return new Mesh(this._GetMeshJS());
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getMeshRelation = function() {
|
||||
const result = this._getMeshRelation();
|
||||
const oldBarycentric = result.barycentric;
|
||||
const oldTriBary = result.triBary;
|
||||
const conversion1 = v => [v.x, v.y, v.z];
|
||||
const conversion2 = v => [v[0], v[1], v[2]];
|
||||
const conversion3 = v => {
|
||||
return {
|
||||
meshID: v.meshID,
|
||||
originalID: v.originalID,
|
||||
tri: v.tri,
|
||||
vertBary: conversion2(v.vertBary)
|
||||
};
|
||||
};
|
||||
result.barycentric = fromVec(oldBarycentric, conversion1);
|
||||
result.triBary = fromVec(oldTriBary, conversion3);
|
||||
oldBarycentric.delete();
|
||||
oldTriBary.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
const result = this._boundingBox();
|
||||
return {
|
||||
@@ -194,26 +297,120 @@ Module.setup = function() {
|
||||
};
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
triVerts = new Uint32Array(),
|
||||
vertProperties = new Float32Array(),
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent,
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
this.vertProperties = vertProperties;
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.runOriginalID = runOriginalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
return this.triVerts.length / 3;
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
get numRun() {
|
||||
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));
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
|
||||
}
|
||||
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
numProp * vert + 3, numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
|
||||
transform(run) {
|
||||
const mat4 = new Array(16);
|
||||
for (const col of [0, 1, 2, 3]) {
|
||||
for (const row of [0, 1, 2]) {
|
||||
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
|
||||
}
|
||||
}
|
||||
mat4[15] = 1;
|
||||
return mat4;
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
|
||||
if (normalIdx instanceof Array)
|
||||
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
case Module.status.NON_FINITE_VERTEX.value:
|
||||
case Module.status.NonFiniteVertex.value:
|
||||
message = 'Non-finite vertex';
|
||||
break;
|
||||
case Module.status.NOT_MANIFOLD.value:
|
||||
case Module.status.NotManifold.value:
|
||||
message = 'Not manifold';
|
||||
break;
|
||||
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
|
||||
case Module.status.VertexOutOfBounds.value:
|
||||
message = 'Vertex index out of bounds';
|
||||
break;
|
||||
case Module.status.PROPERTIES_WRONG_LENGTH.value:
|
||||
case Module.status.PropertiesWrongLength.value:
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Tri properties have wrong length';
|
||||
case Module.status.MissingPositionProperties.value:
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
|
||||
message = 'Tri properties out of bounds';
|
||||
case Module.status.MergeVectorsDifferentLengths.value:
|
||||
message = 'Merge vectors have different lengths';
|
||||
break;
|
||||
case Module.status.MergeIndexOutOfBounds.value:
|
||||
message = 'Merge index out of bounds';
|
||||
break;
|
||||
case Module.status.TransformWrongLength.value:
|
||||
message = 'Transform vector has wrong length';
|
||||
break;
|
||||
case Module.status.RunIndexWrongLength.value:
|
||||
message = 'Run index vector has wrong length';
|
||||
break;
|
||||
case Module.status.FaceIDWrongLength.value:
|
||||
message = 'Face ID vector has wrong length';
|
||||
case Module.status.InvalidConstruction.value:
|
||||
message = 'Manifold constructed with invalid parameters';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -228,6 +425,95 @@ 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');
|
||||
|
||||
function pushVec2(vec, ps) {
|
||||
toVec(vec, ps, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1]};
|
||||
return p;
|
||||
})
|
||||
}
|
||||
|
||||
Module.CrossSection.hull = function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
let pts = new Module.Vector_vec2();
|
||||
for (const cs of args) {
|
||||
if (cs instanceof CrossSectionCtor) {
|
||||
Module._crossSectionCollectVertices(pts, cs);
|
||||
} else if (
|
||||
cs instanceof Array && cs.length == 2 && typeof cs[0] == 'number') {
|
||||
pts.push_back({x: cs[0], y: cs[1]});
|
||||
} else if (cs.x) {
|
||||
pts.push_back(cs);
|
||||
} else {
|
||||
const wrap =
|
||||
((cs[0].length == 2 && typeof cs[0][0] == 'number') || cs[0].x);
|
||||
const polys = wrap ? [cs] : cs;
|
||||
for (const poly of polys) pushVec2(pts, poly);
|
||||
}
|
||||
}
|
||||
const result = Module._crossSectionHullPoints(pts);
|
||||
pts.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
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);
|
||||
@@ -240,32 +526,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);
|
||||
@@ -273,33 +565,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]},
|
||||
@@ -316,18 +626,50 @@ 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;
|
||||
}
|
||||
function pushVec3(vec, ps) {
|
||||
toVec(vec, ps, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
|
||||
return p;
|
||||
})
|
||||
}
|
||||
|
||||
Module.union = batchbool('union');
|
||||
Module.difference = batchbool('difference');
|
||||
Module.intersection = batchbool('intersection');
|
||||
Module.Manifold.hull = function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
let pts = new Module.Vector_vec3();
|
||||
for (const m of args) {
|
||||
if (m instanceof ManifoldCtor) {
|
||||
Module._manifoldCollectVertices(pts, m);
|
||||
} else if (
|
||||
m instanceof Array && m.length == 3 && typeof m[0] == 'number') {
|
||||
pts.push_back({x: m[0], y: m[1], z: m[2]});
|
||||
} else if (m.x) {
|
||||
pts.push_back(m);
|
||||
} else {
|
||||
pushVec3(pts, m);
|
||||
}
|
||||
}
|
||||
const result = Module._manifoldHullPoints(pts);
|
||||
pts.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.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.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 @@
|
||||
manifold*
|
||||
@@ -0,0 +1 @@
|
||||
declare module '@jscadui/3mf-export';
|
||||
@@ -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.
|
||||
@@ -65,7 +65,7 @@ button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
input {
|
||||
.name {
|
||||
border: none;
|
||||
height: 36px;
|
||||
background-color: transparent;
|
||||
@@ -77,12 +77,12 @@ input {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
.name:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em;
|
||||
margin: 18px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -128,20 +128,16 @@ model-viewer {
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.margin {
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#file {
|
||||
width: 250px;
|
||||
margin: 18px 8px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 250px;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -150,6 +146,10 @@ model-viewer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.uparrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
@@ -168,28 +168,89 @@ model-viewer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-edit {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 36px;
|
||||
top: 18px;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.top-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 36px;
|
||||
background-size: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.squish {
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
width: 36px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.undo {
|
||||
background-image: url(/icons/undo.png);
|
||||
}
|
||||
|
||||
.redo {
|
||||
background-image: url(/icons/redo.png);
|
||||
}
|
||||
|
||||
.star {
|
||||
background-image: url(/icons/star.png);
|
||||
}
|
||||
|
||||
.share {
|
||||
background-image: url(/icons/share.png);
|
||||
}
|
||||
|
||||
.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);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
background-image: url(/icons/trash.png);
|
||||
}
|
||||
|
||||
.play {
|
||||
background-image: url(/icons/play.png);
|
||||
}
|
||||
|
||||
.pause {
|
||||
background-image: url(/icons/pause.png);
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.top-icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: rgb(148, 221, 255);
|
||||
}
|
||||
@@ -202,7 +263,61 @@ model-viewer {
|
||||
background-color: rgb(255, 177, 177);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
#file-margin {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#save {
|
||||
width: 125px;
|
||||
}
|
||||
|
||||
#animation {
|
||||
display: none;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#play {
|
||||
position: relative;
|
||||
background-color: #ccc;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: relative;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
margin-right: 16px;
|
||||
background: #ffffff55;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
background: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 820px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
@@ -210,5 +325,31 @@ model-viewer {
|
||||
|
||||
.col {
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
#file-margin {
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: .2em 1em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header .margin {
|
||||
margin: 0.2em 16px;
|
||||
}
|
||||
|
||||
#button-container {
|
||||
flex-direction: row;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
width: calc(100% - 16px);
|
||||
}
|
||||
|
||||
#button-container .margin {
|
||||
width: calc(50% - 16px);
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
@@ -12,26 +12,68 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const exampleFunctions = examples.functionBodies;
|
||||
import ManifoldWorker from './worker?worker';
|
||||
|
||||
const CODE_START = '<code>';
|
||||
// Loaded globally by examples.js
|
||||
const exampleFunctions = self.examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register(
|
||||
'/service-worker.js', {scope: './index.html'});
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// Edit UI ------------------------------------------------------------
|
||||
|
||||
const undoButton = document.querySelector('#undo');
|
||||
const redoButton = document.querySelector('#redo');
|
||||
const formatButton = document.querySelector('#format');
|
||||
const shareButton = document.querySelector('#share');
|
||||
|
||||
undoButton.onclick = () => editor.trigger('ignored', 'undo');
|
||||
redoButton.onclick = () => editor.trigger('ignored', 'redo');
|
||||
formatButton.onclick = () =>
|
||||
editor.trigger('ignored', 'editor.action.formatDocument');
|
||||
shareButton.onclick = () => {
|
||||
const url = new URL(window.location.toString());
|
||||
url.hash =
|
||||
'#' +
|
||||
encodeURIComponent(
|
||||
currentFileElement.textContent + CODE_START + editor.getValue());
|
||||
navigator.clipboard.writeText(url.toString());
|
||||
console.log('Shareable link copied to clipboard!');
|
||||
console.log('Consider shortening this URL using tinyURL.com');
|
||||
};
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
const fileButton = document.querySelector('#file');
|
||||
const currentElement = document.querySelector('#current');
|
||||
const arrow = document.querySelector('.uparrow');
|
||||
const dropdown = document.querySelector('.dropdown');
|
||||
const currentFileElement = document.querySelector('#current');
|
||||
const fileArrow = document.querySelector('#file .uparrow');
|
||||
const fileDropdown = document.querySelector('#fileDropdown');
|
||||
const saveContainer = document.querySelector('#save');
|
||||
const saveDropdown = document.querySelector('#saveDropdown');
|
||||
const saveArrow = document.querySelector('#save .uparrow');
|
||||
|
||||
const hideDropdown = function() {
|
||||
dropdown.classList.remove('show');
|
||||
arrow.classList.remove('down');
|
||||
fileDropdown.classList.remove('show');
|
||||
saveDropdown.classList.remove('show');
|
||||
fileArrow.classList.remove('down');
|
||||
saveArrow.classList.remove('down');
|
||||
};
|
||||
const toggleDropdown = function(event) {
|
||||
const toggleFileDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
dropdown.classList.toggle('show');
|
||||
arrow.classList.toggle('down');
|
||||
fileDropdown.classList.toggle('show');
|
||||
fileArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleDropdown;
|
||||
const toggleSaveDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
saveDropdown.classList.toggle('show');
|
||||
saveArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleFileDropdown;
|
||||
saveArrow.parentElement.onclick = toggleSaveDropdown;
|
||||
document.body.onclick = hideDropdown;
|
||||
|
||||
const prefix = 'ManifoldCAD';
|
||||
@@ -54,7 +96,7 @@ function nthKey(n) {
|
||||
|
||||
function saveCurrent() {
|
||||
if (editor) {
|
||||
const currentName = currentElement.textContent;
|
||||
const currentName = currentFileElement.textContent;
|
||||
if (!exampleFunctions.get(currentName)) {
|
||||
setScript(currentName, editor.getValue());
|
||||
}
|
||||
@@ -69,11 +111,12 @@ let isExample = true;
|
||||
function switchTo(scriptName) {
|
||||
if (editor) {
|
||||
switching = true;
|
||||
currentElement.textContent = scriptName;
|
||||
currentFileElement.textContent = scriptName;
|
||||
setScript('currentName', scriptName);
|
||||
const code =
|
||||
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
|
||||
isExample = exampleFunctions.get(scriptName) != null;
|
||||
const code = isExample ? exampleFunctions.get(scriptName).substring(1) :
|
||||
getScript(scriptName) ?? '';
|
||||
window.location.hash = '#' + scriptName;
|
||||
editor.setValue(code);
|
||||
}
|
||||
}
|
||||
@@ -93,6 +136,10 @@ function createDropdownItem(name) {
|
||||
saveCurrent();
|
||||
switchTo(label.textContent);
|
||||
};
|
||||
// Stop text input spaces from triggering the button
|
||||
button.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -115,8 +162,7 @@ function uniqueName(name) {
|
||||
function addEdit(button) {
|
||||
const label = button.firstChild;
|
||||
const edit = addIcon(button);
|
||||
edit.style.backgroundImage = 'url(icons/pencil.png)';
|
||||
edit.style.right = '30px';
|
||||
edit.classList.add('edit');
|
||||
|
||||
edit.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
@@ -124,6 +170,7 @@ function addEdit(button) {
|
||||
const code = getScript(oldName);
|
||||
const form = document.createElement('form');
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.classList.add('name');
|
||||
inputElement.value = oldName;
|
||||
label.textContent = '';
|
||||
button.appendChild(form);
|
||||
@@ -137,8 +184,8 @@ function addEdit(button) {
|
||||
if (!input) return;
|
||||
const newName = uniqueName(input);
|
||||
label.textContent = newName;
|
||||
if (currentElement.textContent == oldName) {
|
||||
currentElement.textContent = newName;
|
||||
if (currentFileElement.textContent == oldName) {
|
||||
currentFileElement.textContent = newName;
|
||||
}
|
||||
removeScript(oldName);
|
||||
setScript(newName, code);
|
||||
@@ -156,8 +203,7 @@ function addEdit(button) {
|
||||
};
|
||||
|
||||
const trash = addIcon(button);
|
||||
trash.style.backgroundImage = 'url(icons/trash.png)';
|
||||
trash.style.right = '0px';
|
||||
trash.classList.add('trash');
|
||||
let lastClick = 0;
|
||||
|
||||
trash.onclick = function(event) {
|
||||
@@ -172,7 +218,7 @@ function addEdit(button) {
|
||||
}, {once: true});
|
||||
} else if (performance.now() - lastClick > 500) {
|
||||
removeScript(label.textContent);
|
||||
if (currentElement.textContent == label.textContent) {
|
||||
if (currentFileElement.textContent == label.textContent) {
|
||||
switchTo('Intro');
|
||||
}
|
||||
const container = button.parentElement;
|
||||
@@ -182,16 +228,16 @@ function addEdit(button) {
|
||||
}
|
||||
|
||||
const newButton = document.querySelector('#new');
|
||||
function newItem(code) {
|
||||
const name = uniqueName('New Script');
|
||||
function newItem(code, scriptName = undefined) {
|
||||
const name = uniqueName(scriptName ?? 'New Script');
|
||||
setScript(name, code);
|
||||
const nextButton = createDropdownItem(name);
|
||||
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
|
||||
addEdit(nextButton);
|
||||
nextButton.click();
|
||||
return {button: nextButton, name};
|
||||
};
|
||||
newButton.onclick = function() {
|
||||
newItem('');
|
||||
newItem('').button.click();
|
||||
};
|
||||
|
||||
const runButton = document.querySelector('#compile');
|
||||
@@ -204,20 +250,52 @@ function initializeRun() {
|
||||
if (autoExecute) {
|
||||
runButton.click();
|
||||
} else {
|
||||
poster.textContent = 'Auto-run disabled due to prior failure';
|
||||
poster.textContent = 'Auto-run disabled';
|
||||
}
|
||||
}
|
||||
|
||||
// Editor ------------------------------------------------------------
|
||||
let tsWorker = undefined;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('/manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('/manifold-encapsulated-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
return `
|
||||
${global.replaceAll('export', '')}
|
||||
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
|
||||
declare interface 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: 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() {
|
||||
const content =
|
||||
await fetch('manifold.d.ts').then(response => response.text());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getManifoldDTS());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getEditorDTS());
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
@@ -227,10 +305,10 @@ require(['vs/editor/editor.main'], async function() {
|
||||
|
||||
for (const [name] of exampleFunctions) {
|
||||
const button = createDropdownItem(name);
|
||||
dropdown.appendChild(button.parentElement);
|
||||
fileDropdown.appendChild(button.parentElement);
|
||||
}
|
||||
|
||||
let currentName = currentElement.textContent;
|
||||
let currentName = currentFileElement.textContent;
|
||||
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = nthKey(i);
|
||||
@@ -245,7 +323,24 @@ require(['vs/editor/editor.main'], async function() {
|
||||
addEdit(button);
|
||||
}
|
||||
}
|
||||
switchTo(currentName);
|
||||
|
||||
if (window.location.hash.length > 0) {
|
||||
const fragment = decodeURIComponent(window.location.hash.substring(1));
|
||||
const codeIdx = fragment.indexOf(CODE_START);
|
||||
if (codeIdx != -1) {
|
||||
autoExecute = true;
|
||||
const name = fragment.substring(0, codeIdx);
|
||||
switchTo(
|
||||
newItem(fragment.substring(codeIdx + CODE_START.length), name).name);
|
||||
} else {
|
||||
if (fragment != currentName) {
|
||||
autoExecute = true;
|
||||
}
|
||||
switchTo(fragment);
|
||||
}
|
||||
} else {
|
||||
switchTo(currentName);
|
||||
}
|
||||
|
||||
if (manifoldInitialized) {
|
||||
initializeRun();
|
||||
@@ -255,11 +350,12 @@ require(['vs/editor/editor.main'], async function() {
|
||||
runButton.disabled = false;
|
||||
if (switching) {
|
||||
switching = false;
|
||||
editor.setScrollTop(0);
|
||||
return;
|
||||
}
|
||||
if (isExample) {
|
||||
const cursor = editor.getPosition();
|
||||
newItem(editor.getValue());
|
||||
newItem(editor.getValue()).button.click();
|
||||
editor.setPosition(cursor);
|
||||
}
|
||||
});
|
||||
@@ -269,6 +365,51 @@ require(['vs/editor/editor.main'], async function() {
|
||||
};
|
||||
});
|
||||
|
||||
// Animation ------------------------------------------------------------
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const animationContainer = document.querySelector('#animation');
|
||||
const playButton = document.querySelector('#play');
|
||||
const scrubber = document.querySelector('#scrubber');
|
||||
let paused = false;
|
||||
|
||||
mv.addEventListener('load', () => {
|
||||
const hasAnimation = mv.availableAnimations.length > 0;
|
||||
animationContainer.style.display = hasAnimation ? 'flex' : 'none';
|
||||
if (hasAnimation) {
|
||||
play();
|
||||
}
|
||||
});
|
||||
|
||||
function play() {
|
||||
mv.play();
|
||||
playButton.classList.remove('play');
|
||||
playButton.classList.add('pause');
|
||||
paused = false;
|
||||
scrubber.classList.add('hide');
|
||||
}
|
||||
|
||||
function pause() {
|
||||
mv.pause();
|
||||
playButton.classList.remove('pause');
|
||||
playButton.classList.add('play');
|
||||
paused = true;
|
||||
scrubber.max = mv.duration;
|
||||
scrubber.value = mv.currentTime;
|
||||
scrubber.classList.remove('hide');
|
||||
}
|
||||
|
||||
playButton.onclick = function() {
|
||||
if (paused) {
|
||||
play();
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
};
|
||||
|
||||
scrubber.oninput = function() {
|
||||
mv.currentTime = scrubber.value;
|
||||
};
|
||||
|
||||
// Execution ------------------------------------------------------------
|
||||
const consoleElement = document.querySelector('#console');
|
||||
const oldLog = console.log;
|
||||
@@ -304,12 +445,14 @@ function finishRun() {
|
||||
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
|
||||
}
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const output = {
|
||||
glbURL: null,
|
||||
threeMFURL: null
|
||||
};
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new Worker('worker.js');
|
||||
manifoldWorker = new ManifoldWorker();
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
@@ -327,14 +470,22 @@ function createWorker() {
|
||||
|
||||
finishRun();
|
||||
runButton.disabled = true;
|
||||
setScript('safe', 'true');
|
||||
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
mv.src = objectURL;
|
||||
if (objectURL == null) {
|
||||
if (output.threeMFURL != null) {
|
||||
URL.revokeObjectURL(output.threeMFURL);
|
||||
output.threeMFURL = null;
|
||||
}
|
||||
URL.revokeObjectURL(output.glbURL);
|
||||
output.glbURL = e.data.glbURL;
|
||||
output.threeMFURL = e.data.threeMFURL;
|
||||
threemfButton.disabled = output.threeMFURL == null;
|
||||
mv.src = output.glbURL;
|
||||
if (output.glbURL == null) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
createWorker();
|
||||
} else {
|
||||
setScript('safe', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,10 +518,24 @@ runButton.onclick = function() {
|
||||
}
|
||||
};
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
function clickSave(saveButton, filename, outputName) {
|
||||
const container = saveButton.parentElement;
|
||||
return () => {
|
||||
const oldSave = saveContainer.firstElementChild;
|
||||
if (oldSave !== container) {
|
||||
saveDropdown.insertBefore(oldSave, saveDropdown.firstElementChild);
|
||||
saveContainer.insertBefore(container, saveDropdown);
|
||||
container.appendChild(saveArrow.parentElement);
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
link.download = filename;
|
||||
link.href = output[outputName];
|
||||
link.click();
|
||||
};
|
||||
}
|
||||
|
||||
const glbButton = document.querySelector('#glb');
|
||||
glbButton.onclick = clickSave(glbButton, 'manifold.glb', 'glbURL');
|
||||
|
||||
const threemfButton = document.querySelector('#threemf');
|
||||
threemfButton.onclick = clickSave(threemfButton, 'manifold.3mf', 'threeMFURL');
|
||||
|
||||
@@ -1,440 +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.
|
||||
|
||||
(function(exports) {
|
||||
|
||||
exports.functions = {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the API
|
||||
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
|
||||
// static API - these functions can also be used bare. Use console.log() to
|
||||
// print output (lower-right). This editor defines Z as up and units of mm.
|
||||
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This app is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// To share your code with another browser/device, simply copy the text to a
|
||||
// file.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples.
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as a
|
||||
// puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions =
|
||||
50; // Number of divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = tetrahedron().scale(scale);
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
let edge1 = union(corner, edge);
|
||||
edge1 = edge1.rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
let edge2 = edge1.rotate([0, 0, 180]);
|
||||
edge2 = union(edge2, edge1);
|
||||
edge2 =
|
||||
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
let edge4 = edge2.rotate([0, 0, 90]);
|
||||
edge4 = union(edge4, edge2);
|
||||
|
||||
let frame = edge4.translate([0, 0, -edgeLength / 2]);
|
||||
frame = union(frame, frame.rotate([180, 0, 0]));
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function. This
|
||||
// example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with smooth()
|
||||
// and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
vertPos.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const scallop = new Mesh();
|
||||
scallop.triVerts = Uint32Array.from(triVerts);
|
||||
scallop.vertPos = Float32Array.from(vertPos);
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common factor
|
||||
// then you will get multiple separate, interwoven knots. This is an example
|
||||
// of using the warp() method, thus avoiding any direct handling of
|
||||
// triangles.
|
||||
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
const 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 point = new THREE.Vector3();
|
||||
const axis = new THREE.Vector3();
|
||||
|
||||
const func =
|
||||
(v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
point.set(Math.cos(phi), 0, Math.sin(phi))
|
||||
.multiplyScalar(threadRadius);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
point.applyAxisAngle(
|
||||
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
|
||||
point.x += minorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 1, 0), theta);
|
||||
point.x += majorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 0, 1), psi);
|
||||
v[0] = point.x;
|
||||
v[1] = point.y;
|
||||
v[2] = point.z;
|
||||
}
|
||||
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
|
||||
[0.0, -w]
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
function rotate(v, theta) {
|
||||
return [
|
||||
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
|
||||
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
|
||||
];
|
||||
}
|
||||
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor =
|
||||
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
|
||||
0, radius, 0
|
||||
]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const p0 = [outerRadius, 0];
|
||||
const p1 = [innerRadius, -cut];
|
||||
const p2 = [innerRadius, cut];
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
stretch.push(rotate(p1, dPhiRad * i));
|
||||
stretch.push(rotate(p2, dPhiRad * i));
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
}
|
||||
b = intersection(extrude(stretch, width), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
|
||||
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
|
||||
|
||||
const size = 20;
|
||||
const n = 20;
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: [-period, -period, -period],
|
||||
max: [period, period, period]
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
exports.functionBodies = new Map();
|
||||
|
||||
for (const [func, code] of Object.entries(exports.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first four leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
exports.functionBodies.set(name, body);
|
||||
};
|
||||
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
|
||||
@@ -0,0 +1,424 @@
|
||||
// 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, Document, Material, Mesh, Primitive, Texture, WebIO} from '@gltf-transform/core';
|
||||
|
||||
import {EXTManifold, ManifoldPrimitive} from './manifold-gltf';
|
||||
import {Mesh as ManifoldMesh, MeshOptions} from './public/manifold-encapsulated-types';
|
||||
|
||||
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 type Attribute = keyof(typeof attributeDefs);
|
||||
|
||||
export interface Properties {
|
||||
material: Material;
|
||||
attributes: Attribute[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this first to register the manifold extension so that readMesh and
|
||||
* writeMesh will work.
|
||||
*/
|
||||
export function setupIO(io: WebIO) {
|
||||
return io.registerExtensions([EXTManifold]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an input mesh into Manifold-compatible data structures, whether it
|
||||
* contains the EXT_mesh_manifold extension or not.
|
||||
*
|
||||
* @param mesh The Mesh to read.
|
||||
* @param attributes An array of attributes representing the order of desired
|
||||
* properties returned in the vertProperties array of the output mesh. If
|
||||
* omitted, this will be populated with the union of all attributes defined
|
||||
* in the primitives of the input mesh. If present, the first entry must be
|
||||
* 'POSITION', and any attributes in the primitives that are not included in
|
||||
* this list will be ignored, while those in the list but not defined in a
|
||||
* primitive will be populated with zeros.
|
||||
* @returns The returned mesh is suitable for initializing a Manifold or Mesh of
|
||||
* the Manifold library if desired. See Manifold documentation if you prefer
|
||||
* to use these GL arrays in a different library. The runProperties array
|
||||
* gives the Material and attributes list associated with each triangle run,
|
||||
* which in turn corresponds to a primitive of the input mesh. These
|
||||
* attributes are the intersection of the attributes present on the
|
||||
* primitive and those requested in the attributes input.
|
||||
*/
|
||||
export function readMesh(mesh: Mesh, attributes: Attribute[] = []):
|
||||
{mesh: MeshOptions, runProperties: Properties[]}|null {
|
||||
const primitives = mesh.listPrimitives();
|
||||
if (primitives.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (attributes.length === 0) {
|
||||
const attributeSet = new Set<Attribute>();
|
||||
for (const primitive of primitives) {
|
||||
const semantics = primitive.listSemantics() as Attribute[];
|
||||
for (const semantic of semantics) {
|
||||
attributeSet.add(semantic);
|
||||
}
|
||||
}
|
||||
let semantic: Attribute;
|
||||
for (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".');
|
||||
|
||||
let numProp = 0;
|
||||
const attributeOffsets = attributes.map((numProp = 0, def => {
|
||||
const last = numProp;
|
||||
numProp += attributeDefs[def].components;
|
||||
return last;
|
||||
}));
|
||||
|
||||
const manifoldPrimitive =
|
||||
mesh.getExtension('EXT_mesh_manifold') as ManifoldPrimitive;
|
||||
|
||||
let vertPropArray: number[] = [];
|
||||
let triVertArray: number[] = [];
|
||||
const runIndexArray = [0];
|
||||
const mergeFromVertArray = [];
|
||||
const mergeToVertArray = [];
|
||||
const runProperties: Properties[] = [];
|
||||
if (manifoldPrimitive != null) {
|
||||
const numVert = primitives[0].getAttribute('POSITION')!.getCount();
|
||||
const foundAttribute = attributes.map((a) => attributeDefs[a].type == null);
|
||||
vertPropArray = new Array<number>(numProp * numVert);
|
||||
|
||||
for (const primitive of primitives) {
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
const attributesIn = primitive.listSemantics() as Attribute[];
|
||||
|
||||
attributes.forEach((attributeOut, idx) => {
|
||||
if (foundAttribute[idx]) {
|
||||
return;
|
||||
}
|
||||
for (const attributeIn of attributesIn) {
|
||||
if (attributeIn === attributeOut) {
|
||||
foundAttribute[idx] = true;
|
||||
const accessor = primitive.getAttribute(attributeIn)!;
|
||||
writeProperties(
|
||||
vertPropArray, accessor, numProp, attributeOffsets[idx]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
triVertArray = [...triVertArray, ...indices.getArray()!];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
runProperties.push({
|
||||
material: primitive.getMaterial()!,
|
||||
attributes: attributesIn.filter(b => attributes.some(a => a == b))
|
||||
});
|
||||
}
|
||||
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()) {
|
||||
mergeFromVertArray.push(from);
|
||||
mergeToVertArray.push(to);
|
||||
}
|
||||
} else {
|
||||
for (const primitive of primitives) {
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
const numVert = vertPropArray.length / numProp;
|
||||
vertPropArray =
|
||||
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
|
||||
triVertArray =
|
||||
[...triVertArray, ...indices.getArray()!.map((i) => i + numVert)];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
|
||||
const attributesIn = primitive.listSemantics() as Attribute[];
|
||||
runProperties.push({
|
||||
material: primitive.getMaterial()!,
|
||||
attributes: attributesIn.filter(b => attributes.some(a => a == b))
|
||||
});
|
||||
}
|
||||
}
|
||||
const vertProperties = new Float32Array(vertPropArray);
|
||||
const triVerts = new Uint32Array(triVertArray);
|
||||
const runIndex = new Uint32Array(runIndexArray);
|
||||
const mergeFromVert = new Uint32Array(mergeFromVertArray);
|
||||
const mergeToVert = new Uint32Array(mergeToVertArray);
|
||||
|
||||
const meshOut =
|
||||
{numProp, triVerts, vertProperties, runIndex, mergeFromVert, mergeToVert};
|
||||
|
||||
return {mesh: meshOut, runProperties};
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a Manifold Mesh into a glTF Mesh object, using the EXT_mesh_manifold
|
||||
* extension to allow for lossless roundtrip of the manifold mesh through the
|
||||
* glTF file.
|
||||
*
|
||||
* @param doc The glTF Document to which this Mesh will be added.
|
||||
* @param manifoldMesh The Manifold Mesh to convert to glTF.
|
||||
* @param id2properties A map from originalID to Properties that include the
|
||||
* glTF Material and the set of attributes to output. All triangle runs with
|
||||
* the same originalID will be combined into a single output primitive. Any
|
||||
* originalIDs not found in the map will have the glTF default material and
|
||||
* no attributes beyond 'POSITION'. Each attributes array must correspond to
|
||||
* the manifoldMesh vertProperties, thus the first attribute must always be
|
||||
* 'POSITION'. Any properties that should not be output for a given
|
||||
* primitive must use the 'SKIP_*' attributes.
|
||||
* @returns The glTF Mesh to add to the Document.
|
||||
*/
|
||||
export function writeMesh(
|
||||
doc: Document, manifoldMesh: ManifoldMesh,
|
||||
id2properties: Map<number, Properties>): Mesh {
|
||||
if (doc.getRoot().listBuffers().length === 0) {
|
||||
doc.createBuffer();
|
||||
}
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
|
||||
const mesh = doc.createMesh();
|
||||
const runIndex = [];
|
||||
const attributeUnion: Attribute[] = [];
|
||||
const primitive2attributes = new Map<Primitive, Attribute[]>();
|
||||
const numRun = manifoldMesh.runIndex.length - 1;
|
||||
let lastID = -1;
|
||||
for (let run = 0; run < numRun; ++run) {
|
||||
const id = manifoldMesh.runOriginalID[run];
|
||||
if (id == lastID) {
|
||||
continue;
|
||||
}
|
||||
lastID = id;
|
||||
runIndex.push(manifoldMesh.runIndex[run]);
|
||||
|
||||
const indices = doc.createAccessor('primitive indices of ID ' + id)
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(1));
|
||||
const primitive = doc.createPrimitive().setIndices(indices);
|
||||
|
||||
const properties = id2properties.get(id);
|
||||
if (properties) {
|
||||
const {material, attributes} = properties;
|
||||
if (attributes.length < 1 || attributes[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
primitive.setMaterial(material);
|
||||
primitive2attributes.set(primitive, attributes);
|
||||
|
||||
properties.attributes.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;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
primitive2attributes.set(primitive, ['POSITION']);
|
||||
}
|
||||
|
||||
mesh.addPrimitive(primitive);
|
||||
}
|
||||
runIndex.push(manifoldMesh.runIndex[numRun]);
|
||||
|
||||
const numVert = manifoldMesh.numVert;
|
||||
const numProp = manifoldMesh.numProp;
|
||||
let offset = 0;
|
||||
attributeUnion.forEach((attribute, aIdx) => {
|
||||
const def = attributeDefs[attribute];
|
||||
if (def == null)
|
||||
throw new Error(attribute + ' is not a recognized attribute.');
|
||||
|
||||
if (def.type == null) {
|
||||
++offset;
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
let x = manifoldMesh.vertProperties[numProp * v + offset + i];
|
||||
if (attribute == 'COLOR_0') {
|
||||
x = Math.max(0, Math.min(1, x));
|
||||
}
|
||||
array[n * v + i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
const accessor = doc.createAccessor(attribute)
|
||||
.setBuffer(buffer)
|
||||
.setType(def.type)
|
||||
.setArray(array);
|
||||
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
const attributes = primitive2attributes.get(primitive)!;
|
||||
if (attributes.length > aIdx &&
|
||||
attributeDefs[attributes[aIdx]].type != null) {
|
||||
primitive.setAttribute(attribute, accessor);
|
||||
}
|
||||
}
|
||||
offset += n;
|
||||
});
|
||||
|
||||
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
|
||||
mesh.setExtension('EXT_mesh_manifold', manifoldPrimitive);
|
||||
|
||||
const indices = doc.createAccessor('manifold indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts);
|
||||
manifoldPrimitive.setIndices(indices);
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
|
||||
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
|
||||
const ind = [];
|
||||
const val = [];
|
||||
if (manifoldMesh.mergeFromVert && manifoldMesh.mergeToVert) {
|
||||
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
|
||||
vert2merge[from] = manifoldMesh.mergeToVert[i];
|
||||
}
|
||||
|
||||
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
|
||||
const newVert = vert2merge[vert];
|
||||
if (vert !== newVert) {
|
||||
ind.push(i);
|
||||
val.push(newVert);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ind.length > 0) {
|
||||
const indicesAccessor = doc.createAccessor('merge from')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(ind));
|
||||
const valuesAccessor = doc.createAccessor('merge to')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(val));
|
||||
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to dispose of a Mesh, useful when replacing an existing Mesh
|
||||
* with one from writeMesh.
|
||||
*/
|
||||
export function disposeMesh(mesh: Mesh) {
|
||||
if (!mesh) return;
|
||||
const primitives = mesh.listPrimitives();
|
||||
for (const primitive of primitives) {
|
||||
primitive.getIndices()?.dispose();
|
||||
for (const accessor of primitive.listAttributes()) {
|
||||
accessor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
const manifoldPrimitive =
|
||||
mesh.getExtension('EXT_mesh_manifold') as ManifoldPrimitive;
|
||||
if (manifoldPrimitive) {
|
||||
manifoldPrimitive.getIndices()?.dispose();
|
||||
manifoldPrimitive.getMergeIndices()?.dispose();
|
||||
manifoldPrimitive.getMergeValues()?.dispose();
|
||||
}
|
||||
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to download an image and apply it to the given texture.
|
||||
*
|
||||
* @param texture The texture to update
|
||||
* @param uri The location of the image to download
|
||||
*/
|
||||
export async function loadTexture(texture: Texture, uri: string) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
|
||||
function writeProperties(
|
||||
vertProperties: number[], accessor: Accessor, numProp: number,
|
||||
offset: number) {
|
||||
const array = accessor.getArray()!;
|
||||
const size = accessor.getElementSize();
|
||||
const numVert = accessor.getCount();
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = array[i * size + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readPrimitive(
|
||||
primitive: Primitive, numProp: number, attributes: Attribute[]) {
|
||||
const vertProperties: number[] = [];
|
||||
let offset = 0;
|
||||
for (const attribute of attributes) {
|
||||
const size = attributeDefs[attribute].components;
|
||||
if (attributeDefs[attribute].type == null) {
|
||||
offset += size;
|
||||
continue;
|
||||
}
|
||||
const accessor = primitive.getAttribute(attribute);
|
||||
if (accessor) {
|
||||
writeProperties(vertProperties, accessor, numProp, offset);
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
return vertProperties;
|
||||
}
|
||||
@@ -5,34 +5,81 @@
|
||||
<title>ManifoldCAD</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
<link type="text/css" href="editor.css" rel="stylesheet" />
|
||||
<meta name="description"
|
||||
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
|
||||
<meta name="theme-color" content="#A288B3">
|
||||
<meta property="og:title" content="ManifoldCAD">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://manifoldcad.org/" />
|
||||
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
|
||||
<meta property="og:image:width" content="850">
|
||||
<meta property="og:image:height" content="850">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link type="text/css" href="editor.css" rel="stylesheet">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "ManifoldCAD",
|
||||
"applicationCategory": "DesignApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
self.ModelViewerElement = self.ModelViewerElement || {};
|
||||
self.ModelViewerElement.modelCacheSize = 0;
|
||||
</script>
|
||||
<script type="module" src="/examples.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="background-color: rgb(162 136 179);">
|
||||
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
<span class="top-edit blue">
|
||||
<button type="button" id="undo" class="top-icon undo squish"></button>
|
||||
<button type="button" id="redo" class="top-icon redo squish"></button>
|
||||
<button type="button" id="format" class="top-icon star squish"></button>
|
||||
<button type="button" id="share" class="top-icon share"></button>
|
||||
</span>
|
||||
</p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
<div class="margin" id="file-margin">
|
||||
<button type="button" id="file" class="blue item"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
<div class="dropdown">
|
||||
<div class="dropdown" id="fileDropdown">
|
||||
<button type="button" class="green item" id="new">New</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
|
||||
<button type="button" id="download" class="blue margin">Save GLB</button>
|
||||
<div class="container" id="button-container">
|
||||
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
|
||||
<div id="save" class="margin" style="position: relative; ">
|
||||
<div class="item">
|
||||
<button type="button" id="glb" class="blue item">Save GLB</button>
|
||||
<button type="button" class="icon"><b class="uparrow" style="right: 2px;"></b></button>
|
||||
</div>
|
||||
<div class="dropdown" id="saveDropdown">
|
||||
<div class="item">
|
||||
<button type="button" id="threemf" class="blue item">Save 3MF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container" style="flex: 2;">
|
||||
<div id="editor" class="col"></div>
|
||||
<div class="col container" style="flex-direction: column;">
|
||||
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
|
||||
shadow-intensity="1" tone-mapping="commerce" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;">
|
||||
Loading...
|
||||
</p>
|
||||
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
|
||||
</div>
|
||||
<div id="animation">
|
||||
<button type="button" id="play" class="icon pause"></button>
|
||||
<input type="range" min="0" max="1" step="0.01" value="0" class="slider" id="scrubber">
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="console"></div>
|
||||
@@ -40,17 +87,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
|
||||
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script src="examples.js"></script>
|
||||
<script src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.4.0/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,188 @@
|
||||
<!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_mesh_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" tone-mapping="commerce" 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/3.4.0/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, disposeMesh } from './gltf-io';
|
||||
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 mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tmp = readMesh(mesh);
|
||||
|
||||
tmp.mesh.runOriginalID = [];
|
||||
const id2properties = new Map();
|
||||
const numID = tmp.runProperties.length;
|
||||
const firstID = Manifold.reserveIDs(numID);
|
||||
for (let i = 0; i < numID; ++i) {
|
||||
tmp.mesh.runOriginalID.push(firstID + i);
|
||||
id2properties.set(firstID + i, tmp.runProperties[i]);
|
||||
}
|
||||
const manifoldMesh = new Mesh(tmp.mesh);
|
||||
disposeMesh(mesh);
|
||||
|
||||
manifoldMesh.merge();
|
||||
|
||||
try {
|
||||
// Test manifoldness - will throw if not.
|
||||
const manifold = Manifold(manifoldMesh);
|
||||
node.setMesh(writeMesh(docIn, manifold.getMesh(), id2properties));
|
||||
manifold.delete();
|
||||
anyManifold = true;
|
||||
} catch (e) {
|
||||
console.log(mesh.getName(), e);
|
||||
allManifold = false;
|
||||
}
|
||||
}
|
||||
|
||||
await docIn.transform(prune());
|
||||
|
||||
return docIn;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,224 @@
|
||||
// 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, Extension, ExtensionProperty, GLTF, IProperty, PropertyType, ReaderContext, WriterContext} from '@gltf-transform/core';
|
||||
|
||||
const NAME = 'EXT_mesh_manifold';
|
||||
const MERGE = 'MERGE';
|
||||
|
||||
interface IManifoldPrimitive extends IProperty {
|
||||
mergeIndices: Accessor;
|
||||
mergeValues: Accessor;
|
||||
indices: Accessor;
|
||||
runIndex: number[]|Uint32Array;
|
||||
}
|
||||
|
||||
interface ManifoldDef {
|
||||
manifoldPrimitive: GLTF.IMeshPrimitive;
|
||||
mergeIndices: number;
|
||||
mergeValues: number;
|
||||
}
|
||||
|
||||
export class ManifoldPrimitive extends ExtensionProperty<IManifoldPrimitive> {
|
||||
static EXTENSION_NAME = NAME;
|
||||
public declare extensionName: typeof NAME;
|
||||
public declare propertyType: 'ManifoldPrimitive';
|
||||
public declare parentTypes: [PropertyType.MESH];
|
||||
|
||||
init() {
|
||||
ManifoldPrimitive.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: Accessor, valuesAccessor: Accessor) {
|
||||
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: number[]|Uint32Array) {
|
||||
return this.set('runIndex', runIndex);
|
||||
}
|
||||
|
||||
setIndices(indices: Accessor) {
|
||||
return this.setRef('indices', indices);
|
||||
}
|
||||
|
||||
getIndices() {
|
||||
return this.getRef('indices')!;
|
||||
}
|
||||
}
|
||||
|
||||
export class EXTManifold extends Extension {
|
||||
extensionName = NAME;
|
||||
prewriteTypes = [PropertyType.ACCESSOR];
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
createManifoldPrimitive() {
|
||||
return new ManifoldPrimitive(this.document.getGraph());
|
||||
}
|
||||
|
||||
read(context: ReaderContext) {
|
||||
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] as ManifoldDef;
|
||||
|
||||
if (manifoldDef.manifoldPrimitive) {
|
||||
let count = 0;
|
||||
const runIndex = [];
|
||||
runIndex.push(count);
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
const indices = primitive.getIndices();
|
||||
if (!indices) {
|
||||
console.log('Skipping non-indexed primitive ', primitive.getName());
|
||||
continue;
|
||||
}
|
||||
count += indices.getCount();
|
||||
runIndex.push(count);
|
||||
}
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
manifoldPrimitive.setIndices(
|
||||
context.accessors[manifoldDef.manifoldPrimitive.indices!]);
|
||||
}
|
||||
|
||||
if (manifoldDef.mergeIndices != null && manifoldDef.mergeValues != null) {
|
||||
manifoldPrimitive.setMerge(
|
||||
context.accessors[manifoldDef.mergeIndices],
|
||||
context.accessors[manifoldDef.mergeValues]);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
prewrite(context: WriterContext) {
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const indices = manifoldPrimitive.getIndices();
|
||||
context.addAccessorToUsageGroup(
|
||||
indices, WriterContext.BufferViewUsage.ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
const mergeFrom = manifoldPrimitive.getMergeIndices();
|
||||
const mergeTo = manifoldPrimitive.getMergeValues();
|
||||
if (!mergeFrom || !mergeTo) return;
|
||||
|
||||
context.addAccessorToUsageGroup(mergeFrom, MERGE);
|
||||
context.addAccessorToUsageGroup(mergeTo, MERGE);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context: WriterContext) {
|
||||
const {json} = context.jsonDoc;
|
||||
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME) as ManifoldPrimitive;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -37,90 +36,112 @@
|
||||
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
href="https://modelviewer.dev/"><model-viewer></a>
|
||||
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
|
||||
</p>
|
||||
href="https://modelviewer.dev/"><model-viewer></a> via <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
|
||||
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
|
||||
three.js example is <a href="three.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
<option value="union">Union</option>
|
||||
</select>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
<button id="download">Download manifold GLB</button>
|
||||
<model-viewer camera-controls shadow-intensity="1" tone-mapping="commerce"
|
||||
alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.4.0/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { Document, WebIO } from '@gltf-transform/core';
|
||||
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
|
||||
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
|
||||
color: 'yellow',
|
||||
metalness: 1,
|
||||
roughness: 0.2
|
||||
}));
|
||||
const io = setupIO(new WebIO());
|
||||
const doc = new Document();
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
push2MV(mesh);
|
||||
};
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
const id2properties = new Map();
|
||||
|
||||
csg('difference');
|
||||
});
|
||||
async function readGLB(url) {
|
||||
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 tmp = readMesh(node.getMesh());
|
||||
tmp.mesh.runOriginalID = [];
|
||||
const numID = tmp.runProperties.length;
|
||||
const firstID = Manifold.reserveIDs(numID);
|
||||
for (let i = 0; i < numID; ++i) {
|
||||
tmp.mesh.runOriginalID.push(firstID + i);
|
||||
ids.push(firstID + i);
|
||||
id2properties.set(firstID + i, tmp.runProperties[i]);
|
||||
}
|
||||
manifolds.push(Manifold(new Mesh(tmp.mesh)));
|
||||
}
|
||||
// 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());
|
||||
doc.getRoot().listAccessors().forEach((s) => s.dispose());
|
||||
for (const [i, id] of ids.entries()) {
|
||||
const material = doc.getRoot().listMaterials()[startIdx + i];
|
||||
id2properties.get(id).material = material;
|
||||
}
|
||||
|
||||
return Manifold.union(manifolds);
|
||||
}
|
||||
|
||||
const space = await readGLB('/models/space.glb');
|
||||
const moon = await readGLB('/models/moon.glb');
|
||||
|
||||
const node = doc.createNode();
|
||||
doc.createScene().addChild(node);
|
||||
|
||||
function csg(operation) {
|
||||
push2MV(Manifold[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
|
||||
function push2MV(mesh) {
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
},
|
||||
() => console.log('GLTF export failed!'),
|
||||
{ binary: true }
|
||||
);
|
||||
}
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
}
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
disposeMesh(node.getMesh());
|
||||
const mesh = writeMesh(doc, manifoldMesh, id2properties);
|
||||
node.setMesh(mesh);
|
||||
await doc.transform(prune());
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"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 run",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:dev": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.8.0",
|
||||
"@gltf-transform/extensions": "^3.8.0",
|
||||
"@gltf-transform/functions": "^3.8.0",
|
||||
"@jscadui/3mf-export": "^0.5.0",
|
||||
"fflate": "^0.8.0",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"simple-dropzone": "0.8.3",
|
||||
"three": "0.158.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.31.1",
|
||||
"@vitest/web-worker": "^0.31.1",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
// 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.
|
||||
|
||||
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
|
||||
// "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", 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
|
||||
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
|
||||
// only() to ghost out everything else, akin to * in OpenSCAD.
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This PWA is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// Consider installing it (icon in the search bar) for easy access.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples. The
|
||||
// gl-matrix package from npm is automatically imported for convenience -
|
||||
// its API is available in the top-level glMatrix object.
|
||||
|
||||
// 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;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as
|
||||
// a puzzle. This demonstrates how redundant points along a polygon can be
|
||||
// used to make twisted extrusions smoother. It also showcases animated
|
||||
// assemblies. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions = 50; // Divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = Manifold.tetrahedron().intersect(
|
||||
Manifold.tetrahedron().rotate([0, 0, 90]).scale(2.5));
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const cyan = [0, 1, 1];
|
||||
const magenta = [1, 0, 1];
|
||||
const fade = (color, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
const x = pos[2] / 2;
|
||||
color[i] = cyan[i] * x + magenta[i] * (1 - x);
|
||||
}
|
||||
};
|
||||
|
||||
// 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, 2, nDivisions, 270).setProperties(3, fade),
|
||||
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
|
||||
|
||||
const result =
|
||||
tet.intersect(screw.rotate([0, 0, -45]).translate([0, 0, -1]))
|
||||
.scale(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. The default material
|
||||
// properties, as well as animation parameters can be set via
|
||||
// globalDefaults.
|
||||
|
||||
const layFlat = new GLTFNode();
|
||||
layFlat.rotation =
|
||||
[45, -Math.atan(1 / Math.sqrt(2)) * 180 / Math.PI, 120];
|
||||
layFlat.translation = [0, 0, scale * Math.sqrt(3) / 3];
|
||||
|
||||
const fixed = new GLTFNode(layFlat);
|
||||
fixed.manifold = result;
|
||||
fixed.rotation = [0, 0, 180];
|
||||
|
||||
// For 3MF export, only top-level objects are independently arrangeable.
|
||||
const layFlat2 = layFlat.clone();
|
||||
|
||||
const moving = new GLTFNode(layFlat2);
|
||||
moving.manifold = result;
|
||||
// Use functions to create animation, which runs from t=0 to t=1.
|
||||
moving.translation = (t) => {
|
||||
const a = 1 - t;
|
||||
const x = a > 0.5 ? scale * 2 * (0.5 - a) : 0;
|
||||
return [x, x, -2 * x + scale * 4 * (0.5 - Math.abs(0.5 - a))];
|
||||
};
|
||||
moving.rotation = (t) => [0, 0, 270 * 2 * (1 - t)];
|
||||
|
||||
globalDefaults.animationLength = 10; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
const {sphere, cylinder, union} = Manifold;
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
// It also demonstrates the use of setMorph to animate a warping function.
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df =
|
||||
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = Manifold.sphere(1, 200);
|
||||
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const scale = 100 / (box.max[0] - box.min[0]);
|
||||
|
||||
setMorphStart(ball, func);
|
||||
const node = new GLTFNode();
|
||||
node.manifold = ball;
|
||||
node.scale = [scale, scale, scale];
|
||||
|
||||
globalDefaults.animationLength = 5; // seconds
|
||||
globalDefaults.animationMode = 'ping-pong';
|
||||
|
||||
const result = heart.scale(scale);
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with
|
||||
// smooth() and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
positions.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const triVerts = Uint32Array.from(triangles);
|
||||
const vertProperties = Float32Array.from(positions);
|
||||
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common
|
||||
// factor then you will get multiple separate, interwoven knots. This is
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
// The number of times the thread passes through the donut hole.
|
||||
const p = 1;
|
||||
// The number of times the thread circles the donut.
|
||||
const q = 3;
|
||||
// Radius of the interior of the imaginary donut.
|
||||
const majorRadius = 25;
|
||||
// Radius of the small cross-section of the imaginary donut.
|
||||
const minorRadius = 10;
|
||||
// Radius of the small cross-section of the actual object.
|
||||
const threadRadius = 3.75;
|
||||
// Number of linear segments making up the threadRadius circle. Default is
|
||||
// getCircularSegments(threadRadius).
|
||||
const circularSegments = -1;
|
||||
// Number of segments along the length of the knot. Default makes roughly
|
||||
// square facets.
|
||||
const linearSegments = -1;
|
||||
|
||||
// These default values recreate Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
const pk = p / kLoops;
|
||||
const qk = q / kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * qk * majorRadius / threadRadius;
|
||||
|
||||
const offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = qk * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * pk / qk;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(pk * minorRadius, qk * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
const result = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.rotation = [0, 0, 360 * (k / kLoops) * (qk / pk)];
|
||||
node.translation = [0, 0, result.boundingBox().min[2]];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
|
||||
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
|
||||
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
|
||||
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = Manifold.cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = Manifold.compose(holes);
|
||||
|
||||
result = Manifold.difference([
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = Manifold.cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = Manifold.extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const o = vec2.fromValues(0, 0);
|
||||
const p0 = vec2.fromValues(outerRadius, 0);
|
||||
const p1 = vec2.fromValues(innerRadius, -cut);
|
||||
const p2 = vec2.fromValues(innerRadius, cut);
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
}
|
||||
const result =
|
||||
Manifold.intersection(Manifold.extrude(stretch, width), b);
|
||||
return result;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return Manifold.difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
|
||||
// use of a Signed Distance Function (SDF) to create smooth, complex
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
// 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) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return Manifold.levelSet(gyroid, box, period / n, level)
|
||||
.scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
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]));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
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 |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 375 B |
|
After Width: | Height: | Size: 326 B |
|
After Width: | Height: | Size: 492 B |
|
After Width: | Height: | Size: 619 B |
|
After Width: | Height: | Size: 675 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 471 B |
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ManifoldCAD",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/mengerSponge192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/icons/mengerSponge512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"background_color": "#FFFFFF",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"scope": "/",
|
||||
"theme_color": "#A288B3",
|
||||
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
|
||||
"categories": [
|
||||
"design",
|
||||
"developer",
|
||||
"education",
|
||||
"graphics",
|
||||
"productivity"
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/icons/manifoldCAD.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
},
|
||||
{
|
||||
"src": "/icons/manifoldCADonly.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Increment version when updating CDN URLs to clean up cache.
|
||||
const cacheName = 'manifoldCAD-cache-v1';
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(caches.keys().then((keyList) => {
|
||||
return Promise.all(keyList.map((key) => {
|
||||
if (key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
// Serves from cache, then updates the cache in the background from the network,
|
||||
// if available. Update available on refresh.
|
||||
self.addEventListener(
|
||||
'fetch',
|
||||
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
|
||||
const networkFetch = fetch(e.request).then(response => {
|
||||
const clone = response.clone();
|
||||
caches.open(cacheName).then(cache => {
|
||||
cache.put(e.request, clone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
return cachedResponse || networkFetch;
|
||||
}))});
|
||||
@@ -1,119 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Manifold - three.js texture example</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 {
|
||||
margin: 0;
|
||||
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;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>.
|
||||
</p>
|
||||
<input type="checkbox"/><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
Module().then(function (wasm) {
|
||||
wasm.setup();
|
||||
|
||||
const cube = new wasm.Mesh();
|
||||
cube.vertPos = new Float32Array(24);
|
||||
cube.triVerts = new Uint32Array(36);
|
||||
cube.vertPos.set([
|
||||
-1, -1, -1,
|
||||
1, -1, -1,
|
||||
1, 1, -1,
|
||||
-1, 1, -1,
|
||||
-1, -1, 1,
|
||||
1, -1, 1,
|
||||
1, 1, 1,
|
||||
-1, 1, 1]);
|
||||
cube.triVerts.set([
|
||||
0, 2, 1, 0, 3, 2,
|
||||
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 manifold = wasm.Manifold(cube);
|
||||
|
||||
const result = manifold.add(manifold.rotate([90, 0, 90]).translate([1, 1, 0]));
|
||||
|
||||
// we have a manifold, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 1, 100);
|
||||
camera.position.z = 10;
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(mesh2geometry(result.getMesh()),
|
||||
new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
|
||||
const wireframe = new THREE.WireframeGeometry(mesh.geometry);
|
||||
const lines = new THREE.LineSegments(wireframe);
|
||||
|
||||
scene.add(mesh);
|
||||
scene.add(lines);
|
||||
lines.visible = false;
|
||||
|
||||
const checkbox = document.querySelector('input');
|
||||
checkbox.onchange = function (event) {
|
||||
mesh.visible = !checkbox.checked;
|
||||
lines.visible = checkbox.checked;
|
||||
};
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
scene.rotation.x = time / 2000;
|
||||
scene.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
});
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -37,8 +36,7 @@
|
||||
|
||||
<body>
|
||||
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>.
|
||||
</p>
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
@@ -46,64 +44,62 @@
|
||||
</select><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
<script type="module">
|
||||
import * as THREE from 'three';
|
||||
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
wasm.setup();
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
const manifold_1 = Manifold.cube([0.2, 0.2, 0.2], true);
|
||||
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
csg('difference');
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(Manifold[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
return new Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
@@ -112,7 +108,7 @@
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
const simplified = mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
|
||||
@@ -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,154 +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.
|
||||
|
||||
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
|
||||
importScripts(
|
||||
'manifold.js', threePath + 'build/three.js',
|
||||
threePath + 'examples/js/exporters/GLTFExporter.js');
|
||||
|
||||
// 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);
|
||||
|
||||
let wasm;
|
||||
Module().then(function(tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
||||
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 =
|
||||
(e) => {
|
||||
const content = e.data + '\nexportGLB(result);\n';
|
||||
try {
|
||||
const f = new Function(...exposedFunctions, content);
|
||||
f(...exposedFunctions.map(name => wasm[name]));
|
||||
} catch (error) {
|
||||
console.log(error.toString());
|
||||
postMessage({objectURL: null});
|
||||
} finally {
|
||||
wasm.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
// Export & Rendering
|
||||
// ------------------------------------------------------------
|
||||
const mesh = new THREE.Mesh(
|
||||
undefined,
|
||||
new THREE.MeshStandardMaterial(
|
||||
{color: 'yellow', metalness: 1, roughness: 0.2}));
|
||||
const rotation = new THREE.Matrix4();
|
||||
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
|
||||
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
|
||||
mesh.scale.setScalar(0.001); // mm -> m
|
||||
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
|
||||
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`);
|
||||
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(manifold.getMesh());
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], {type: 'application/octet-stream'});
|
||||
postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
},
|
||||
() => {
|
||||
console.log('glTF export failed!');
|
||||
postMessage({objectURL: null});
|
||||
},
|
||||
{binary: true});
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// 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';
|
||||
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 glbURL = 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(glbURL);
|
||||
glbURL = e.data.glbURL;
|
||||
if (glbURL == null) {
|
||||
reject('no glbURL)');
|
||||
}
|
||||
const docIn = await io.read(glbURL);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const docMesh = node.getMesh();
|
||||
if (!docMesh) {
|
||||
continue;
|
||||
}
|
||||
const {mesh} = readMesh(docMesh);
|
||||
const manifold = wasm.Manifold(mesh);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
manifold.delete();
|
||||
// Return properties of first mesh encountered.
|
||||
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(7240, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3235, 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(3.342, 0.001, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11.51, 0.01, '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,785 @@
|
||||
// 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 {Accessor, Animation, AnimationSampler, Document, mat4, Material, Mesh as GLTFMesh, Node, WebIO} from '@gltf-transform/core';
|
||||
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
import {fileForContentTypes, FileForRelThumbnail, to3dmodel} from '@jscadui/3mf-export';
|
||||
import {strToU8, Zippable, zipSync} from 'fflate'
|
||||
import * as glMatrix from 'gl-matrix';
|
||||
|
||||
import Module from './built/manifold';
|
||||
import {Properties, setupIO, writeMesh} from './gltf-io';
|
||||
import {GLTFMaterial, Quat} from './public/editor';
|
||||
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
|
||||
|
||||
interface GlobalDefaults {
|
||||
roughness: number;
|
||||
metallic: number;
|
||||
baseColorFactor: [number, number, number];
|
||||
alpha: number;
|
||||
unlit: boolean;
|
||||
animationLength: number;
|
||||
animationMode: 'loop'|'ping-pong';
|
||||
}
|
||||
|
||||
interface WorkerStatic extends ManifoldToplevel {
|
||||
GLTFNode: typeof GLTFNode;
|
||||
show(manifold: Manifold): Manifold;
|
||||
only(manifold: Manifold): Manifold;
|
||||
setMaterial(manifold: Manifold, material: GLTFMaterial): Manifold;
|
||||
setMorphStart(manifold: Manifold, func: (v: Vec3) => void): void;
|
||||
setMorphEnd(manifold: Manifold, func: (v: Vec3) => void): void;
|
||||
cleanup(): void;
|
||||
}
|
||||
|
||||
const module = await Module() 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', 'hull'
|
||||
];
|
||||
// manifold member functions (that return a new manifold)
|
||||
const manifoldMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'decompose', 'warp',
|
||||
'transform', 'translate', 'rotate', 'scale', 'mirror',
|
||||
'refine', 'refineToLength', 'setProperties', 'asOriginal', 'trimByPlane',
|
||||
'split', 'splitByPlane', 'slice', 'project', 'hull'
|
||||
];
|
||||
// CrossSection static methods (that return a new cross-section)
|
||||
const crossSectionStaticFunctions = [
|
||||
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
|
||||
'ofPolygons', 'hull'
|
||||
];
|
||||
// CrossSection member functions (that return a new cross-section)
|
||||
const crossSectionMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
|
||||
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset', 'hull'
|
||||
];
|
||||
// top level functions that construct a new manifold/mesh
|
||||
const toplevelConstructors = ['show', 'only', 'setMaterial'];
|
||||
const toplevel = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
'getCircularSegments', 'Mesh', 'GLTFNode', 'Manifold', 'CrossSection',
|
||||
'setMorphStart', 'setMorphEnd'
|
||||
];
|
||||
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) {
|
||||
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 FPS = 30;
|
||||
|
||||
const GLOBAL_DEFAULTS = {
|
||||
roughness: 0.2,
|
||||
metallic: 1,
|
||||
baseColorFactor: [1, 1, 0] as [number, number, number],
|
||||
alpha: 1,
|
||||
unlit: false,
|
||||
animationLength: 1,
|
||||
animationMode: 'loop'
|
||||
};
|
||||
|
||||
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>();
|
||||
const object2globalID = new Map<GLTFNode|Manifold, number>();
|
||||
const manifold2morph = new Map<Manifold, Morph>();
|
||||
// lib3mf doesn't like objectid=0
|
||||
let nextGlobalID = 1;
|
||||
let animation: Animation;
|
||||
let timesAccessor: Accessor;
|
||||
let weightsAccessor: Accessor;
|
||||
let weightsSampler: AnimationSampler;
|
||||
let hasAnimation: boolean;
|
||||
|
||||
function cleanup() {
|
||||
ghost = false;
|
||||
shown.clear();
|
||||
singles.clear();
|
||||
nodes.length = 0;
|
||||
id2material.clear();
|
||||
materialCache.clear();
|
||||
object2globalID.clear();
|
||||
manifold2morph.clear();
|
||||
nextGlobalID = 1;
|
||||
}
|
||||
|
||||
interface Morph {
|
||||
start?: (v: Vec3) => void;
|
||||
end?: (v: Vec3) => void;
|
||||
}
|
||||
|
||||
interface Mesh3MF {
|
||||
id: string;
|
||||
vertices: Float32Array;
|
||||
indices: Uint32Array;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface Child3MF {
|
||||
objectID: string;
|
||||
transform?: mat4;
|
||||
}
|
||||
|
||||
interface Component3MF {
|
||||
id: string;
|
||||
children: Array<Child3MF>;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface Header {
|
||||
unit?: 'micron'|'millimeter'|'centimeter'|'inch'|'foot'|'meter';
|
||||
title?: string;
|
||||
author?: string;
|
||||
description?: string;
|
||||
application?: string;
|
||||
creationDate?: string;
|
||||
license?: string;
|
||||
modificationDate?: string;
|
||||
}
|
||||
|
||||
interface To3MF {
|
||||
meshes: Array<Mesh3MF>;
|
||||
components: Array<Component3MF>;
|
||||
items: Array<Child3MF>;
|
||||
precision: number;
|
||||
header: Header;
|
||||
}
|
||||
|
||||
class GLTFNode {
|
||||
private _parent?: 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) {
|
||||
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;
|
||||
|
||||
const globalDefaults = {...GLOBAL_DEFAULTS};
|
||||
|
||||
module.setMaterial = (manifold: Manifold, material: GLTFMaterial): Manifold => {
|
||||
const out = manifold.asOriginal();
|
||||
id2material.set(out.originalID(), material);
|
||||
return out;
|
||||
};
|
||||
|
||||
module.setMorphStart = (manifold: Manifold, func: (v: Vec3) => void): void => {
|
||||
const morph = manifold2morph.get(manifold);
|
||||
if (morph != null) {
|
||||
morph.start = func;
|
||||
} else {
|
||||
manifold2morph.set(manifold, {start: func});
|
||||
}
|
||||
};
|
||||
|
||||
module.setMorphEnd = (manifold: Manifold, func: (v: Vec3) => void): void => {
|
||||
const morph = manifold2morph.get(manifold);
|
||||
if (morph != null) {
|
||||
morph.end = func;
|
||||
} else {
|
||||
manifold2morph.set(manifold, {end: func});
|
||||
}
|
||||
};
|
||||
|
||||
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 = 'const globalDefaults = {};\n' + e.data +
|
||||
'\nreturn exportModels(globalDefaults, typeof result === "undefined" ? undefined : result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportModels', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportModels, 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 euler2quat(rotation: Vec3): Quat {
|
||||
const {quat} = glMatrix;
|
||||
const deg2rad = Math.PI / 180;
|
||||
const q = quat.create() as Quat;
|
||||
quat.rotateZ(q, q, deg2rad * rotation[2]);
|
||||
quat.rotateY(q, q, deg2rad * rotation[1]);
|
||||
quat.rotateX(q, q, deg2rad * rotation[0]);
|
||||
return q;
|
||||
}
|
||||
|
||||
function addMotion(
|
||||
doc: Document, type: 'translation'|'rotation'|'scale', node: GLTFNode,
|
||||
out: Node): Vec3|null {
|
||||
const motion = node[type];
|
||||
if (motion == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof motion !== 'function') {
|
||||
return motion;
|
||||
}
|
||||
|
||||
const nFrames = timesAccessor.getCount();
|
||||
const nEl = type == 'rotation' ? 4 : 3;
|
||||
const frames = new Float32Array(nEl * nFrames);
|
||||
for (let i = 0; i < nFrames; ++i) {
|
||||
const x = i / (nFrames - 1);
|
||||
const m = motion(
|
||||
globalDefaults.animationMode !== 'ping-pong' ?
|
||||
x :
|
||||
(1 - Math.cos(x * 2 * Math.PI)) / 2);
|
||||
frames.set(nEl === 4 ? euler2quat(m) : m, nEl * i);
|
||||
}
|
||||
|
||||
const framesAccessor =
|
||||
doc.createAccessor(node.name + ' ' + type + ' frames')
|
||||
.setBuffer(doc.getRoot().listBuffers()[0])
|
||||
.setArray(frames)
|
||||
.setType(nEl === 4 ? Accessor.Type.VEC4 : Accessor.Type.VEC3);
|
||||
const sampler = doc.createAnimationSampler()
|
||||
.setInput(timesAccessor)
|
||||
.setOutput(framesAccessor)
|
||||
.setInterpolation('LINEAR');
|
||||
const channel = doc.createAnimationChannel()
|
||||
.setTargetPath(type)
|
||||
.setTargetNode(out)
|
||||
.setSampler(sampler);
|
||||
animation.addSampler(sampler);
|
||||
animation.addChannel(channel);
|
||||
hasAnimation = true;
|
||||
return motion(0);
|
||||
}
|
||||
|
||||
function setMorph(doc: Document, node: Node, manifold: Manifold) {
|
||||
if (manifold2morph.has(manifold)) {
|
||||
const channel = doc.createAnimationChannel()
|
||||
.setTargetPath('weights')
|
||||
.setTargetNode(node)
|
||||
.setSampler(weightsSampler);
|
||||
animation.addChannel(channel);
|
||||
hasAnimation = true;
|
||||
}
|
||||
}
|
||||
|
||||
function morphStart(manifoldMesh: Mesh, morph?: Morph): number[] {
|
||||
const inputPositions: number[] = [];
|
||||
if (morph == null) {
|
||||
return inputPositions;
|
||||
}
|
||||
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
for (let j = 0; j < 3; ++j)
|
||||
inputPositions[i * 3 + j] =
|
||||
manifoldMesh.vertProperties[i * manifoldMesh.numProp + j];
|
||||
}
|
||||
if (morph.start) {
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
const vertProp = manifoldMesh.vertProperties;
|
||||
const offset = i * manifoldMesh.numProp;
|
||||
const pos = inputPositions.slice(offset, offset + 3) as Vec3;
|
||||
morph.start(pos);
|
||||
for (let j = 0; j < 3; ++j) vertProp[offset + j] = pos[j];
|
||||
}
|
||||
}
|
||||
return inputPositions;
|
||||
}
|
||||
|
||||
function morphEnd(
|
||||
doc: Document, manifoldMesh: Mesh, mesh: GLTFMesh, inputPositions: number[],
|
||||
morph?: Morph) {
|
||||
if (morph == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.setWeights([0]);
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, i) => {
|
||||
if (morph.end) {
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
const pos = inputPositions.slice(3 * i, 3 * (i + 1)) as Vec3;
|
||||
morph.end(pos);
|
||||
inputPositions.splice(3 * i, 3, ...pos);
|
||||
}
|
||||
}
|
||||
|
||||
const startPosition = primitive.getAttribute('POSITION')!.getArray()!;
|
||||
const array = new Float32Array(startPosition.length);
|
||||
|
||||
const offset = manifoldMesh.runIndex[i];
|
||||
for (let j = 0; j < array.length; ++j) {
|
||||
array[j] = inputPositions[offset + j] - startPosition[j];
|
||||
}
|
||||
|
||||
const morphAccessor = doc.createAccessor(mesh.getName() + ' morph target')
|
||||
.setBuffer(doc.getRoot().listBuffers()[0])
|
||||
.setArray(array)
|
||||
.setType(Accessor.Type.VEC3);
|
||||
const morphTarget =
|
||||
doc.createPrimitiveTarget().setAttribute('POSITION', morphAccessor);
|
||||
primitive.addTarget(morphTarget);
|
||||
});
|
||||
}
|
||||
|
||||
function createGLTFnode(doc: Document, node: GLTFNode): Node {
|
||||
const out = doc.createNode(node.name);
|
||||
|
||||
const pos = addMotion(doc, 'translation', node, out);
|
||||
if (pos != null) {
|
||||
out.setTranslation(pos);
|
||||
}
|
||||
|
||||
const rot = addMotion(doc, 'rotation', node, out);
|
||||
if (rot != null) {
|
||||
out.setRotation(euler2quat(rot));
|
||||
}
|
||||
|
||||
const scale = addMotion(doc, 'scale', node, out);
|
||||
if (scale != null) {
|
||||
out.setScale(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, matIn: GLTFMaterial = {}): Material {
|
||||
const defaults = {...globalDefaults};
|
||||
Object.assign(defaults, matIn);
|
||||
const {roughness, metallic, baseColorFactor, alpha, unlit} = defaults;
|
||||
|
||||
const material = doc.createMaterial(matIn.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, to3mf: To3MF, 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 id2properties = new Map<number, Properties>();
|
||||
for (const id of manifoldMesh.runOriginalID!) {
|
||||
const material = id2material.get(id) || backupMaterial;
|
||||
id2properties.set(id, {
|
||||
material: getCachedMaterial(doc, ghost ? GHOST : material),
|
||||
attributes: ['POSITION', ...material.attributes ?? []]
|
||||
});
|
||||
}
|
||||
|
||||
const morph = manifold2morph.get(manifold);
|
||||
const inputPositions = morphStart(manifoldMesh, morph);
|
||||
|
||||
const mesh = writeMesh(doc, manifoldMesh, id2properties);
|
||||
mesh.setName(node.getName());
|
||||
node.setMesh(mesh);
|
||||
|
||||
morphEnd(doc, manifoldMesh, mesh, inputPositions, morph);
|
||||
|
||||
const vertices = manifoldMesh.numProp === 3 ?
|
||||
manifoldMesh.vertProperties :
|
||||
new Float32Array(manifoldMesh.numVert * 3);
|
||||
if (manifoldMesh.numProp > 3) {
|
||||
for (let i = 0; i < manifoldMesh.numVert; ++i) {
|
||||
for (let j = 0; j < 3; ++j)
|
||||
vertices[i * 3 + j] =
|
||||
manifoldMesh.vertProperties[i * manifoldMesh.numProp + j];
|
||||
}
|
||||
}
|
||||
object2globalID.set(manifold, nextGlobalID);
|
||||
to3mf.meshes.push(
|
||||
{vertices, indices: manifoldMesh.triVerts, id: `${nextGlobalID++}`});
|
||||
|
||||
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
|
||||
const show = shown.has(id);
|
||||
const inMesh = show ? shown.get(id) : singles.get(id);
|
||||
if (inMesh == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
id2properties.get(id)!.material = getCachedMaterial(
|
||||
doc, show ? SHOW : (id2material.get(id) || backupMaterial));
|
||||
|
||||
const debugNode = doc.createNode('debug')
|
||||
.setMesh(writeMesh(doc, inMesh, id2properties))
|
||||
.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, to3MF: To3MF, nodeDef: GLTFNode,
|
||||
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
|
||||
const node = createGLTFnode(doc, nodeDef);
|
||||
const {manifold} = nodeDef;
|
||||
if (manifold != null) {
|
||||
setMorph(doc, node, manifold);
|
||||
const backupMaterial = getBackupMaterial(nodeDef);
|
||||
const cachedNodes = manifold2node.get(manifold);
|
||||
if (cachedNodes == null) {
|
||||
addMesh(doc, to3MF, node, manifold, backupMaterial);
|
||||
const cache = new Map<GLTFMaterial, Node>();
|
||||
cache.set(backupMaterial, node);
|
||||
manifold2node.set(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object2globalID.set(nodeDef, nextGlobalID);
|
||||
to3MF.components.push({
|
||||
id: `${nextGlobalID++}`,
|
||||
name: nodeDef.name,
|
||||
children:
|
||||
manifold == null ? [] : [{objectID: `${object2globalID.get(manifold)}`}]
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
async function exportModels(defaults: GlobalDefaults, manifold?: Manifold) {
|
||||
Object.assign(globalDefaults, GLOBAL_DEFAULTS);
|
||||
Object.assign(globalDefaults, defaults);
|
||||
|
||||
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);
|
||||
|
||||
animation = doc.createAnimation('');
|
||||
hasAnimation = false;
|
||||
const nFrames = Math.round(globalDefaults.animationLength * FPS) + 1;
|
||||
const times = new Float32Array(nFrames);
|
||||
const weights = new Float32Array(nFrames);
|
||||
for (let i = 0; i < nFrames; ++i) {
|
||||
const x = i / (nFrames - 1);
|
||||
times[i] = x * globalDefaults.animationLength;
|
||||
weights[i] = globalDefaults.animationMode !== 'ping-pong' ?
|
||||
x :
|
||||
(1 - Math.cos(x * 2 * Math.PI)) / 2;
|
||||
}
|
||||
timesAccessor = doc.createAccessor('animation times')
|
||||
.setBuffer(doc.createBuffer())
|
||||
.setArray(times)
|
||||
.setType(Accessor.Type.SCALAR);
|
||||
weightsAccessor = doc.createAccessor('animation weights')
|
||||
.setBuffer(doc.getRoot().listBuffers()[0])
|
||||
.setArray(weights)
|
||||
.setType(Accessor.Type.SCALAR);
|
||||
weightsSampler = doc.createAnimationSampler()
|
||||
.setInput(timesAccessor)
|
||||
.setOutput(weightsAccessor)
|
||||
.setInterpolation('LINEAR');
|
||||
animation.addSampler(weightsSampler);
|
||||
|
||||
const to3mf = {
|
||||
meshes: [],
|
||||
components: [],
|
||||
items: [],
|
||||
precision: 7,
|
||||
header: {
|
||||
unit: 'millimeter',
|
||||
title: 'ManifoldCAD.org model',
|
||||
description: 'ManifoldCAD.org model',
|
||||
application: 'ManifoldCAD.org',
|
||||
}
|
||||
} as To3MF;
|
||||
|
||||
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, to3mf, nodeDef, manifold2node));
|
||||
if (nodeDef.manifold) {
|
||||
++leafNodes;
|
||||
}
|
||||
}
|
||||
|
||||
for (const nodeDef of nodes) {
|
||||
const gltfNode = node2gltf.get(nodeDef)!;
|
||||
const child = {
|
||||
objectID: `${object2globalID.get(nodeDef)}`,
|
||||
transform: gltfNode.getMatrix()
|
||||
};
|
||||
const {parent} = nodeDef;
|
||||
if (parent == null) {
|
||||
wrapper.addChild(gltfNode);
|
||||
to3mf.items.push(child);
|
||||
} else {
|
||||
node2gltf.get(parent)!.addChild(gltfNode);
|
||||
const parent3mf = to3mf.components.find(
|
||||
(comp) => comp.id == `${object2globalID.get(parent)}`)!;
|
||||
parent3mf.children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
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, to3mf, node, manifold);
|
||||
wrapper.addChild(node);
|
||||
to3mf.items.push({objectID: `${object2globalID.get(manifold)}`});
|
||||
}
|
||||
|
||||
if (!hasAnimation) {
|
||||
timesAccessor.dispose();
|
||||
weightsAccessor.dispose();
|
||||
weightsSampler.dispose();
|
||||
animation.dispose();
|
||||
}
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
const blobGLB = new Blob([glb], {type: 'application/octet-stream'});
|
||||
|
||||
const fileForRelThumbnail = new FileForRelThumbnail();
|
||||
fileForRelThumbnail.add3dModel('3D/3dmodel.model')
|
||||
|
||||
const model = to3dmodel(to3mf);
|
||||
const files: Zippable = {};
|
||||
files['3D/3dmodel.model'] = strToU8(model);
|
||||
files[fileForContentTypes.name] = strToU8(fileForContentTypes.content);
|
||||
files[fileForRelThumbnail.name] = strToU8(fileForRelThumbnail.content);
|
||||
const zipFile = zipSync(files);
|
||||
const blob3MF = new Blob(
|
||||
[zipFile],
|
||||
{type: 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'});
|
||||
|
||||
self.postMessage({
|
||||
glbURL: URL.createObjectURL(blobGLB),
|
||||
threeMFURL: URL.createObjectURL(blob3MF)
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.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);
|
||||
}
|
||||
|
||||
void CollectVertices(std::vector<glm::vec2>& verts, const CrossSection& cs) {
|
||||
auto polys = cs.ToPolygons();
|
||||
for (auto poly : polys) verts.insert(verts.end(), poly.begin(), poly.end());
|
||||
}
|
||||
} // 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};
|
||||
}
|
||||
|
||||
void CollectVertices(std::vector<glm::vec3>& verts, const Manifold& manifold) {
|
||||
Mesh mesh = manifold.GetMesh();
|
||||
verts.insert(verts.end(), mesh.vertPos.begin(), mesh.vertPos.end());
|
||||
}
|
||||
} // namespace man_js
|
||||
@@ -0,0 +1,903 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Box, FillRule, JoinType, Mat3, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
|
||||
|
||||
/**
|
||||
* Triangulates a set of /epsilon-valid polygons.
|
||||
*
|
||||
* @param polygons The set of polygons, wound CCW and representing multiple
|
||||
* polygons and/or holes.
|
||||
* @param precision The value of epsilon, bounding the uncertainty of the input
|
||||
* @return The triangles, referencing the original polygon points in order.
|
||||
*/
|
||||
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
export function setMinCircularAngle(angle: number): void;
|
||||
export function setMinCircularEdgeLength(length: number): void;
|
||||
export function setCircularSegments(segments: number): void;
|
||||
export function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
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;
|
||||
translate(x: number, y?: number): 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Convex Hulls
|
||||
|
||||
/**
|
||||
* Compute the convex hull of the contours in this CrossSection.
|
||||
*/
|
||||
hull(): CrossSection;
|
||||
|
||||
/**
|
||||
* Compute the convex hull of all points in a list of polygons/cross-sections.
|
||||
*/
|
||||
static hull(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 {
|
||||
/**
|
||||
* Convert a Mesh into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will throw an error if the
|
||||
* result is not an oriented 2-manifold. Will collapse degenerate triangles
|
||||
* and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless
|
||||
* round-trip of data from getMesh(). For multi-material input, use
|
||||
* reserveIDs() to set a unique originalID for each material, and sort the
|
||||
* materials into triangle runs.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
|
||||
// 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.
|
||||
*
|
||||
* @param m The affine transformation matrix to apply to all the vertices. The
|
||||
* last row is ignored.
|
||||
*/
|
||||
transform(m: Mat4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
translate(x: number, y?: number, z?: number): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
rotate(x: number, y?: number, z?: number): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Mirror this Manifold over the plane described by the unit form of the given
|
||||
* normal vector. If the length of the normal is zero, an empty Manifold is
|
||||
* returned. This operation can be chained. Transforms are combined and
|
||||
* applied lazily.
|
||||
*
|
||||
* @param normal The normal vector of the plane to be mirrored over
|
||||
*/
|
||||
mirror(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting each edge into pieces of
|
||||
* roughly the input length. Interior verts are added to keep the rest of the
|
||||
* triangulation edges also of roughly the same length. If halfedgeTangents
|
||||
* are present (e.g. from the Smooth() constructor), the new vertices will be
|
||||
* moved to the interpolated surface according to their barycentric
|
||||
* coordinates.
|
||||
*
|
||||
* @param length The length that edges will be broken down to.
|
||||
*/
|
||||
refineToLength(length: 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
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union of the manifolds a and b
|
||||
*/
|
||||
static union(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference of the manifold b from the manifold a
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Returns the cross section of this object parallel to the X-Y plane at the
|
||||
* specified height. Using a height equal to the bottom
|
||||
* of the bounding box will return the bottom faces, while using a height
|
||||
* equal to the top of the bounding box will return empty.
|
||||
*
|
||||
* @param height Z-level of slice.
|
||||
*/
|
||||
slice(height: number): CrossSection;
|
||||
|
||||
/**
|
||||
* Returns a cross section representing the projected outline of this object
|
||||
* onto the X-Y plane.
|
||||
*/
|
||||
project(): CrossSection;
|
||||
|
||||
// Convex Hulls
|
||||
|
||||
/**
|
||||
* Compute the convex hull of all points in this Manifold.
|
||||
*/
|
||||
hull(): Manifold;
|
||||
|
||||
/**
|
||||
* Compute the convex hull of all points contained within a set of Manifolds
|
||||
* and point vectors.
|
||||
*/
|
||||
static hull(points: (Manifold|Vec3)[]): 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
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
// Property Access
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
// Export
|
||||
|
||||
/**
|
||||
* Returns a Mesh that is designed to easily push into a renderer, including
|
||||
* all interleaved vertex properties that may have been input. It also
|
||||
* includes relations to all the input meshes that form a part of this result
|
||||
* and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original Mesh inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output Mesh to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* Meshes must use the same channels for their normals.
|
||||
*/
|
||||
getMesh(normalIdx?: Vec3): Mesh;
|
||||
|
||||
// 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,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its ID that can be referenced
|
||||
* by product manifolds. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
|
||||
/**
|
||||
* 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 interface MeshOptions {
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
mergeFromVert?: Uint32Array;
|
||||
mergeToVert?: Uint32Array;
|
||||
runIndex?: Uint32Array;
|
||||
runOriginalID?: Uint32Array;
|
||||
runTransform?: Float32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
constructor(options: MeshOptions);
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
mergeFromVert: Uint32Array;
|
||||
mergeToVert: Uint32Array;
|
||||
runIndex: Uint32Array;
|
||||
runOriginalID: Uint32Array;
|
||||
runTransform: Float32Array;
|
||||
faceID: Uint32Array;
|
||||
halfedgeTangent: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
get numRun(): number;
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export interface SealedUint32Array<N extends number> extends Uint32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export interface SealedFloat32Array<N extends number> extends Float32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export type Vec2 = [number, number];
|
||||
export type Vec3 = [number, number, number];
|
||||
// 3x3 matrix stored in column-major order
|
||||
export type Mat3 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
// 4x4 matrix stored in column-major order
|
||||
export type Mat4 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
export type SimplePolygon = Vec2[];
|
||||
export type Polygons = SimplePolygon|SimplePolygon[];
|
||||
export type Rect = {
|
||||
min: Vec2,
|
||||
max: Vec2
|
||||
};
|
||||
export type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
export type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
export type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative'
|
||||
export type JoinType = 'Square'|'Round'|'Miter'
|
||||
@@ -12,441 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
type Vec2 = [number, number];
|
||||
type Vec3 = [number, number, number];
|
||||
type Vec4 = [number, number, number, number];
|
||||
type Matrix3x4 = [Vec3, Vec3, Vec3, Vec3];
|
||||
type SimplePolygon = Vec2[];
|
||||
type Polygons = SimplePolygon|SimplePolygon[];
|
||||
type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
type BaryRef = {
|
||||
meshID: number,
|
||||
originalID: number,
|
||||
tri: number,
|
||||
vertBary: Vec3
|
||||
};
|
||||
type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
type MeshRelation = {
|
||||
barycentric: Vec3[],
|
||||
triBary: BaryRef[],
|
||||
};
|
||||
import * as T from './manifold-encapsulated-types';
|
||||
export * from './manifold-global-types';
|
||||
|
||||
declare class Mesh {
|
||||
vertPos: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
vertNormal?: Float32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
verts(tri: number): Uint32Array<3>;
|
||||
position(vert: number): Float32Array<3>;
|
||||
normal(vert: number): Float32Array<3>;
|
||||
tangent(halfedge: number): Float32Array<4>;
|
||||
}
|
||||
export type CrossSection = T.CrossSection;
|
||||
export type Manifold = T.Manifold;
|
||||
export type Mesh = T.Mesh;
|
||||
|
||||
declare class Manifold {
|
||||
/**
|
||||
* Create a Manifold from a Mesh object.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
/**
|
||||
* Transform this Manifold in space. The first three columns form a 3x3 matrix
|
||||
* transform and the last is a translation vector. This operation can be
|
||||
* chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transform matrix to apply to all the vertices.
|
||||
*/
|
||||
transform(m: Matrix3x4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
* will all be coplanar (and will not be immediately collapsed) unless the
|
||||
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
|
||||
* constructor), in which case the new vertices will be moved to the
|
||||
* interpolated surface according to their barycentric coordinates.
|
||||
*
|
||||
* @param n The number of pieces to split every edge into. Must be > 1.
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
* function that warps a geometrically valid object into one which overlaps,
|
||||
* but that is not checked here, so it is up to the user to choose their
|
||||
* function with discretion.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. This approximates them for every vertex (returned
|
||||
* as vectors in the structure) and also returns their minimum and maximum
|
||||
* values.
|
||||
*/
|
||||
getCurvature(): Curvature;
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable
|
||||
* for saving or other operations outside of the context of this library.
|
||||
*/
|
||||
getMesh(): Mesh;
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous meshes, for the purpose of assigning
|
||||
* properties like texture coordinates. The triBary vector is the same length
|
||||
* as Mesh.triVerts: BaryRef.originalID indicates the source mesh and
|
||||
* BaryRef.tri is that mesh's triangle index to which these barycentric
|
||||
* coordinates refer. BaryRef.vertBary gives an index for each vertex into the
|
||||
* barycentric vector if that index is >= 0, indicating it is a new vertex. If
|
||||
* the index is < 0, this indicates it is an original vertex, the index + 3
|
||||
* vert of the referenced triangle.
|
||||
*
|
||||
* BaryRef.meshID is a unique ID to the particular instance of a given mesh.
|
||||
* For instance, if you want to convert the triangle mesh to a polygon mesh,
|
||||
* all the triangles from a given face will have the same .meshID and .tri
|
||||
* values.
|
||||
*/
|
||||
getMeshRelation(): MeshRelation;
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
declare function cylinder(
|
||||
height: number, radiusLow: number, radiusHigh?: number,
|
||||
circularSegments?: number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
declare function sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param mesh input Mesh.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
declare function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
declare function tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
declare function extrude(
|
||||
crossSection: Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by revolving this cross-section
|
||||
* around its Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
declare function revolve(
|
||||
crossSection: Polygons, circularSegments?: number): Manifold;
|
||||
|
||||
declare function union(a: Manifold, b: Manifold): Manifold;
|
||||
declare function difference(a: Manifold, b: Manifold): Manifold;
|
||||
declare function intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
declare function union(manifolds: Manifold[]): Manifold;
|
||||
declare function difference(manifolds: Manifold[]): Manifold;
|
||||
declare function intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a list of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A list of Manifolds to lazy-union together.
|
||||
*/
|
||||
declare function compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance function which returns the signed distance of
|
||||
* a given point in R^3. Positive values are inside, negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
*/
|
||||
declare function levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
declare function setMinCircularAngle(angle: number): void;
|
||||
declare function setMinCircularEdgeLength(length: number): void;
|
||||
declare function setCircularSegments(segments: number): void;
|
||||
declare function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
/**
|
||||
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
|
||||
* constructor, this method does not dispose the Mesh after using it.
|
||||
*
|
||||
* @param meshVec The serialized Mesh object to convert into a Manifold.
|
||||
*/
|
||||
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
|
||||
|
||||
declare interface ManifoldStatic {
|
||||
cube: typeof cube;
|
||||
cylinder: typeof cylinder;
|
||||
sphere: typeof sphere;
|
||||
smooth: typeof smooth;
|
||||
tetrahedron: typeof tetrahedron;
|
||||
extrude: typeof extrude;
|
||||
revolve: typeof revolve;
|
||||
union: typeof union;
|
||||
difference: typeof difference;
|
||||
intersection: typeof intersection;
|
||||
compose: typeof compose;
|
||||
levelSet: typeof levelSet;
|
||||
setMinCircularAngle: typeof setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof setCircularSegments;
|
||||
getCircularSegments: typeof getCircularSegments;
|
||||
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
|
||||
Manifold: typeof Manifold;
|
||||
export interface 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 function Module(): Promise<ManifoldStatic>;
|
||||
export default function Module(config?: {locateFile: () => string}):
|
||||
Promise<ManifoldToplevel>;
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.4.2",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"files": [
|
||||
"manifold.js",
|
||||
"manifold.wasm",
|
||||
"manifold.d.ts",
|
||||
"examples/examples.js",
|
||||
"test/test.js"
|
||||
"manifold-encapsulated-types.d.ts",
|
||||
"manifold-global-types.d.ts"
|
||||
],
|
||||
"typings": "manifold.d.ts",
|
||||
"types": "manifold.d.ts",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/elalish/manifold.git"
|
||||
@@ -34,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": "*",
|
||||
"three": "^0.146.0"
|
||||
}
|
||||
"homepage": "https://github.com/elalish/manifold#readme"
|
||||
}
|
||||
@@ -1,153 +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.
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const wasm = require('../manifold.js');
|
||||
const examples = require('../examples/examples');
|
||||
const THREE = require('three');
|
||||
|
||||
// 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);
|
||||
|
||||
wasm().then(function(Module) {
|
||||
Module.setup();
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = Module.Manifold.prototype[name];
|
||||
Module.Manifold.prototype['_' + name] = originalFn;
|
||||
Module.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
||||
function runExample(name) {
|
||||
try {
|
||||
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
|
||||
;
|
||||
const f = new Function(...exposedFunctions, 'THREE', content);
|
||||
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
return {...prop, genus};
|
||||
} finally {
|
||||
Module.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
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, 1, '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, 1, '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, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', () => {
|
||||
const result = runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Gyroid Module', () => {
|
||||
const result = runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -16,18 +16,34 @@ project(extras)
|
||||
|
||||
add_executable(perfTest perf_test.cpp)
|
||||
target_link_libraries(perfTest manifold)
|
||||
|
||||
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTest PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTest PUBLIC cxx_std_17)
|
||||
|
||||
add_executable(largeSceneTest large_scene_test.cpp)
|
||||
target_link_libraries(largeSceneTest manifold)
|
||||
target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(largeSceneTest PUBLIC cxx_std_17)
|
||||
|
||||
if(MANIFOLD_DEBUG)
|
||||
add_executable(minimizeTestcase minimize_testcase.cpp)
|
||||
target_link_libraries(minimizeTestcase polygon)
|
||||
target_compile_options(minimizeTestcase PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(minimizeTestcase PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
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)
|
||||
find_package(Boost REQUIRED COMPONENTS thread)
|
||||
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_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core Boost::thread)
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
|
||||
#include <execution>
|
||||
#endif
|
||||
|
||||
#include "polygon.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
inline float cross(glm::vec2 p, glm::vec2 q) { return p.x * q.y - p.y * q.x; }
|
||||
|
||||
// return true if p intersects with q
|
||||
// note that we don't care about collinear, intersection in the ends etc.
|
||||
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
||||
inline bool intersect(glm::vec2 p0, glm::vec2 p1, glm::vec2 q0, glm::vec2 q1,
|
||||
float precision) {
|
||||
glm::vec2 r = p1 - p0;
|
||||
glm::vec2 s = q1 - q0;
|
||||
// allow some error in the intersection point
|
||||
float epsilon_r = 2.0 * precision / glm::length(r);
|
||||
float epsilon_s = 2.0 * precision / glm::length(s);
|
||||
float rxs = cross(r, s);
|
||||
// in case they are nearly collinear, ignore them...
|
||||
// this is to avoid treating degenerate triangles as intersecting
|
||||
//
|
||||
// note that this does not correspond to a fixed angle,
|
||||
// but seems to work well enough
|
||||
if (rxs < kTolerance && rxs > -kTolerance) {
|
||||
return false;
|
||||
}
|
||||
float u = cross(q0 - p0, r) / rxs;
|
||||
float t = cross(q0 - p0, s) / rxs;
|
||||
// we only care about intersection in the middle of both lines, excluding
|
||||
// their ends
|
||||
if (epsilon_r <= u && u <= (1 - epsilon_r) && //
|
||||
epsilon_s <= t && t <= (1 - epsilon_s)) {
|
||||
// there can be cases which r and s are close to collinear,
|
||||
// so even though the lines are not intersecting,
|
||||
// perturbation along r/s is not enough.
|
||||
//
|
||||
// in that case, apply perturbation perpendicular to r
|
||||
glm::vec2 r_orth = glm::normalize(glm::vec2(-r.y, r.x)) * precision;
|
||||
float u1 = cross(q0 - p0 + r_orth, r) / rxs;
|
||||
float u2 = cross(q0 - p0 - r_orth, r) / rxs;
|
||||
float t1 = cross(q0 - p0 + r_orth, s) / rxs;
|
||||
float t2 = cross(q0 - p0 - r_orth, s) / rxs;
|
||||
return 0 <= u1 && u1 <= 1 && //
|
||||
0 <= t1 && t1 <= 1 && //
|
||||
0 <= u2 && u2 <= 1 && //
|
||||
0 <= t2 && t2 <= 1;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if removing point j in polygon i will introduce self-intersection
|
||||
// this checks if the new edge j-1 <-> j+1 intersects with any other edges,
|
||||
// assuming the original polygon does not contain self-intersection
|
||||
bool safeToRemove(const Polygons &polys, int i, int j, float precision) {
|
||||
if (polys[i].size() == 3) return false;
|
||||
glm::vec2 prev = polys[i][j == 0 ? polys[i].size() - 1 : (j - 1)];
|
||||
glm::vec2 next = polys[i][j == (polys[i].size() - 1) ? 0 : (j + 1)];
|
||||
for (int k = 0; k < polys.size(); k++) {
|
||||
auto ll = [&](size_t l) {
|
||||
return l == (polys[k].size() - 1) ? 0 : (l + 1);
|
||||
};
|
||||
const glm::vec2 *polysk = polys[k].data();
|
||||
if (!std::all_of(
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
|
||||
std::execution::par,
|
||||
#endif
|
||||
countAt((size_t)0), countAt(polys[k].size()), [=](size_t l) {
|
||||
if (i == k && (l == j || ll(l) == j)) return true;
|
||||
return !intersect(prev, next, polysk[l], polysk[ll(l)],
|
||||
precision);
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<int, int> findIndex(const Polygons &polys, int i) {
|
||||
int outer = 0;
|
||||
while (i >= polys[outer].size()) i -= polys[outer++].size();
|
||||
return std::make_pair(outer, i);
|
||||
}
|
||||
|
||||
void Dump(const Polygons &polys) {
|
||||
for (const SimplePolygon &poly : polys) {
|
||||
std::cout << "polys.push_back({" << std::setprecision(9) << std::endl;
|
||||
for (const glm::vec2 v : poly) {
|
||||
std::cout << " {" << v.x << ", " << v.y << "}, //" << std::endl;
|
||||
}
|
||||
std::cout << "});" << std::endl;
|
||||
}
|
||||
for (const SimplePolygon &poly : polys) {
|
||||
std::cout << "show(array([" << std::endl;
|
||||
for (const glm::vec2 v : poly) {
|
||||
std::cout << " [" << v.x << ", " << v.y << "]," << std::endl;
|
||||
}
|
||||
std::cout << "]))" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void DumpTriangulation(const Polygons &polys, float precision) {
|
||||
ExecutionParams oldParams = PolygonParams();
|
||||
manifold::PolygonParams().processOverlaps = true;
|
||||
std::vector<glm::ivec3> result = Triangulate(polys);
|
||||
PolygonParams() = oldParams;
|
||||
for (const glm::ivec3 &tri : result) {
|
||||
std::pair<int, int> xid = findIndex(polys, tri.x);
|
||||
std::pair<int, int> yid = findIndex(polys, tri.y);
|
||||
std::pair<int, int> zid = findIndex(polys, tri.z);
|
||||
glm::vec2 x = polys[xid.first][xid.second];
|
||||
glm::vec2 y = polys[yid.first][yid.second];
|
||||
glm::vec2 z = polys[zid.first][zid.second];
|
||||
printf("show(array([[%.7f, %.7f], [%.7f, %.7f], [%.7f, %.7f]]))\n", x.x,
|
||||
x.y, y.x, y.y, z.x, z.y);
|
||||
if (CCW(x, y, z, precision) == -1) {
|
||||
printf("^ not CCW\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// x direction ray
|
||||
// idk which direction, but it is a fixed direction...
|
||||
// https://stackoverflow.com/questions/11716268/point-in-polygon-algorithm
|
||||
bool rayHit(glm::vec2 point, glm::vec2 q0, glm::vec2 q1) {
|
||||
return ((q0.y >= point.y) != (q1.y >= point.y)) &&
|
||||
(point.x <= (q1.x - q0.x) * (point.y - q0.y) / (q1.y - q0.y) + q0.x);
|
||||
}
|
||||
|
||||
// get the list of polygons inside polygon i
|
||||
// this enumerates over the first point in all other polygons,
|
||||
// and check if they are inside the current polygon
|
||||
// this algorithm assumes that polygons are non-intersecting
|
||||
std::vector<int> getChildren(const Polygons &polys, int i) {
|
||||
std::vector<int> results;
|
||||
for (int j = 0; j < polys.size(); j++) {
|
||||
if (i == j) continue;
|
||||
glm::vec2 point = polys[j][0];
|
||||
auto k1 = [&](size_t k) {
|
||||
return k == (polys[i].size() - 1) ? 0 : (k + 1);
|
||||
};
|
||||
int count = std::count_if(
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
|
||||
std::execution::par,
|
||||
#endif
|
||||
countAt((size_t)0), countAt(polys[i].size()),
|
||||
[&](size_t k) { return rayHit(point, polys[i][k], polys[i][k1(k)]); });
|
||||
if (count % 2) results.push_back(j);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// assuming polys is valid
|
||||
// we try to remove vertices from polys such that
|
||||
// 1. the updated polys is still valid (no overlapping edges, correct winding
|
||||
// direction)
|
||||
// 2. same error in triangulation (either geometryErr or overlapping triangles)
|
||||
void simplify(Polygons &polys, float precision = -1) {
|
||||
bool removedSomething = true;
|
||||
std::string msg;
|
||||
while (removedSomething) {
|
||||
int points = 0;
|
||||
for (const SimplePolygon &poly : polys) points += poly.size();
|
||||
std::cout << "trying to simplify " << points << " points" << std::endl;
|
||||
|
||||
removedSomething = false;
|
||||
// try to remove simple polygons
|
||||
for (int i = 0; i < polys.size(); i++) {
|
||||
std::vector<int> children = getChildren(polys, i);
|
||||
// if there are children, we can't remove it or we will mess up with
|
||||
// winding direction...
|
||||
if (!children.empty()) continue;
|
||||
SimplePolygon poly = std::move(polys[i]);
|
||||
polys.erase(polys.begin() + i);
|
||||
try {
|
||||
std::vector<glm::ivec3> result = Triangulate(polys, precision);
|
||||
polys.insert(polys.begin() + i, std::move(poly));
|
||||
} catch (geometryErr &e) {
|
||||
if (msg.size() > 0 && msg.compare(e.what()) != 0) {
|
||||
polys.insert(polys.begin() + i, std::move(poly));
|
||||
} else {
|
||||
removedSomething = true;
|
||||
msg = e.what();
|
||||
}
|
||||
} catch (topologyErr &e) {
|
||||
polys.insert(polys.begin() + i, std::move(poly));
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < polys.size(); i++) {
|
||||
std::vector<int> children = getChildren(polys, i);
|
||||
for (int j = 0; j < polys[i].size(); j++) {
|
||||
// removed vertex cannot change inclusion relation
|
||||
// we just check if the vertex
|
||||
// x: intersects with j0, j (original edge 1)
|
||||
// y: intersects with j, j1 (original edge 2)
|
||||
// z: intersects with j0, j1 (new edge)
|
||||
// if (x ^ y ^ z) is true, it means that the count % 2 is changed,
|
||||
// and we changed inclusion relation, so vertex j cannot be removed
|
||||
int j0 = j == 0 ? polys[i].size() - 1 : j - 1;
|
||||
int j1 = j == polys[i].size() - 1 ? 0 : j + 1;
|
||||
if (std::any_of(children.begin(), children.end(), [&](int k) {
|
||||
return rayHit(polys[k][0], polys[i][j0], polys[i][j]) ^
|
||||
rayHit(polys[k][0], polys[i][j], polys[i][j1]) ^
|
||||
rayHit(polys[k][0], polys[i][j0], polys[i][j1]);
|
||||
}))
|
||||
continue;
|
||||
|
||||
// removed vertex cannot introduce intersection
|
||||
if (!safeToRemove(polys, i, j, precision)) continue;
|
||||
glm::vec2 removed = polys[i][j];
|
||||
polys[i].erase(polys[i].begin() + j);
|
||||
try {
|
||||
std::vector<glm::ivec3> result = Triangulate(polys, precision);
|
||||
polys[i].insert(polys[i].begin() + j, removed);
|
||||
} catch (geometryErr &e) {
|
||||
if (msg.size() > 0 && msg.compare(e.what()) != 0) {
|
||||
polys[i].insert(polys[i].begin() + j, removed);
|
||||
} else {
|
||||
removedSomething = true;
|
||||
msg = e.what();
|
||||
}
|
||||
} catch (topologyErr &e) {
|
||||
polys[i].insert(polys[i].begin() + j, removed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "Error message for triangulation:" << std::endl
|
||||
<< msg.c_str() << std::endl;
|
||||
}
|
||||
|
||||
struct Edge {
|
||||
glm::vec2 west; // -x side vertex
|
||||
glm::vec2 east; // +x side vertex
|
||||
size_t i, j; // indices of origin vertex
|
||||
bool operator<(const Edge &other) const {
|
||||
return west.x < other.west.x ||
|
||||
(west.x == other.west.x && east.x < other.east.x);
|
||||
}
|
||||
Edge(const Polygons &polys, size_t i, size_t j) : i(i), j(j) {
|
||||
size_t j1 = j == polys[i].size() - 1 ? 0 : j + 1;
|
||||
east = polys[i][j];
|
||||
west = polys[i][j1];
|
||||
if (west.x > east.x) std::swap(east, west);
|
||||
}
|
||||
};
|
||||
|
||||
int isValid(const Polygons &polys, float precision = -1) {
|
||||
size_t numEdge = 0;
|
||||
for (const SimplePolygon &poly : polys) numEdge += poly.size();
|
||||
std::vector<Edge> edges;
|
||||
edges.reserve(numEdge);
|
||||
for (size_t i = 0; i < polys.size(); i++)
|
||||
for (size_t j = 0; j < polys[i].size(); j++)
|
||||
edges.push_back(Edge(polys, i, j));
|
||||
std::sort(edges.begin(), edges.end());
|
||||
// check intersection
|
||||
std::mutex mutex;
|
||||
std::vector<std::pair<Edge, Edge>> overlappingPairs;
|
||||
|
||||
std::for_each( //
|
||||
countAt((size_t)0), countAt(edges.size()), [&](size_t i) {
|
||||
// check all subsequent edges e' until e'.west.x > e.east.x
|
||||
// this ensures we checked all edges e' where
|
||||
// e.west.x < e'.west.x < e.east.x
|
||||
Edge e = edges[i];
|
||||
for (size_t j = i + 1; j < edges.size() && edges[j].west.x < e.east.x;
|
||||
j++) {
|
||||
if (intersect(e.west, e.east, edges[j].west, edges[j].east,
|
||||
precision)) {
|
||||
mutex.lock();
|
||||
overlappingPairs.push_back(std::make_pair(e, edges[j]));
|
||||
mutex.unlock();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!overlappingPairs.empty()) {
|
||||
std::cout << "found " << overlappingPairs.size() << " self-intersection"
|
||||
<< std::endl;
|
||||
for (const std::pair<Edge, Edge> &pairs : overlappingPairs) {
|
||||
std::cout << pairs.first.i << " " << pairs.first.j << "<->" //
|
||||
<< pairs.second.i << " " << pairs.second.j << std::endl;
|
||||
printf("array([[%.7f, %.7f], [%.7f, %.7f]])\n", pairs.first.west.x,
|
||||
pairs.first.west.y, pairs.first.east.x, pairs.first.east.y);
|
||||
printf("array([[%.7f, %.7f], [%.7f, %.7f]])\n", pairs.second.west.x,
|
||||
pairs.second.west.y, pairs.second.east.x, pairs.second.east.y);
|
||||
}
|
||||
} else {
|
||||
std::cout << "No self-intersection" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
PolygonParams().intermediateChecks = true;
|
||||
PolygonParams().processOverlaps = false;
|
||||
PolygonParams().suppressErrors = true;
|
||||
|
||||
if (argc < 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <input file>" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
std::ifstream fin(argv[1]);
|
||||
std::string line;
|
||||
float precision = -1;
|
||||
Polygons polys;
|
||||
SimplePolygon poly;
|
||||
// search for precision first
|
||||
while (std::getline(fin, line)) {
|
||||
int index = line.find("Precision = ");
|
||||
if (index != std::string::npos) {
|
||||
std::istringstream iss(line.substr(index + 12));
|
||||
if (!(iss >> precision)) {
|
||||
std::cerr << "Error parsing precision" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::cout << "Precision = " << precision << std::endl;
|
||||
// fill polygons
|
||||
while (std::getline(fin, line)) {
|
||||
if (line.length() > 5 && line.substr(0, 5).compare(" {") == 0) {
|
||||
std::istringstream iss(line.substr(5));
|
||||
float x, y;
|
||||
if (!(iss >> x)) {
|
||||
std::cerr << "Error parsing coordinate:" << std::endl;
|
||||
std::cerr << "Line: " << line << std::endl;
|
||||
return 1;
|
||||
}
|
||||
iss.ignore(2);
|
||||
if (!(iss >> y)) {
|
||||
std::cerr << "Error parsing coordinate:" << std::endl;
|
||||
std::cerr << "Line: " << line << std::endl;
|
||||
return 1;
|
||||
}
|
||||
poly.push_back(glm::vec2(x, y));
|
||||
} else if (line.length() >= 3 && line.substr(0, 3).compare("});") == 0) {
|
||||
if (poly.size() < 3) {
|
||||
std::cerr << "Error: empty/invalid polygon" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
polys.push_back(std::move(poly));
|
||||
poly.clear();
|
||||
} else if (line.length() >= 5 && line.substr(0, 5).compare("array") == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (polys.size() == 0) {
|
||||
std::cerr << "Error: empty polygon" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (precision == -1) {
|
||||
float bound = 0;
|
||||
for (const SimplePolygon &poly : polys) {
|
||||
for (const glm::vec2 &pt : poly) {
|
||||
bound = glm::max(bound, glm::abs(pt.x));
|
||||
bound = glm::max(bound, glm::abs(pt.y));
|
||||
}
|
||||
}
|
||||
precision = bound * kTolerance;
|
||||
}
|
||||
|
||||
std::cout << "------------" << std::endl;
|
||||
|
||||
isValid(polys, precision);
|
||||
|
||||
simplify(polys, precision);
|
||||
|
||||
std::cout << "------------" << std::endl;
|
||||
std::cout << "Final polygon:" << std::endl;
|
||||
Dump(polys);
|
||||
|
||||
std::cout << "------------" << std::endl;
|
||||
std::cout << "Erroneous triangulation:" << std::endl;
|
||||
DumpTriangulation(polys, precision);
|
||||
return 0;
|
||||
}
|
||||
@@ -57,10 +57,9 @@ void manifoldToCGALSurfaceMesh(Manifold &manifold, TriangleMesh &cgalMesh) {
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Manifold sphere = Manifold::Sphere(1, (8 << i) * 4);
|
||||
Manifold sphere2 = sphere;
|
||||
sphere2.Translate(glm::vec3(0.5));
|
||||
Manifold sphere2 = sphere.Translate(glm::vec3(0.5));
|
||||
|
||||
TriangleMesh cgalSphere, cgalSphere2, cgalOut;
|
||||
TriangleMesh cgalSphere, cgalSphere2;
|
||||
manifoldToCGALSurfaceMesh(sphere, cgalSphere);
|
||||
manifoldToCGALSurfaceMesh(sphere2, cgalSphere2);
|
||||
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"clipper2-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1649676176,
|
||||
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||
"lastModified": 1701175684,
|
||||
"narHash": "sha256-3TKhb48cABl0QcbeG12xlA1taQ/8/RdUUHSp0Qh85eE=",
|
||||
"owner": "AngusJohnson",
|
||||
"repo": "Clipper2",
|
||||
"rev": "98db5662e8dd1808a5a7b50c5605a2289bb390e8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "AngusJohnson",
|
||||
"ref": "Clipper2_1.3.0",
|
||||
"repo": "Clipper2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -15,25 +35,78 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gtest-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1690989893,
|
||||
"narHash": "sha256-t0RchAHTJbuI5YW4uyBPykTvcjy90JW9AOPNjIhwh6U=",
|
||||
"owner": "google",
|
||||
"repo": "googletest",
|
||||
"rev": "f8d7d77c06936315286eb55f8de22cd23c188571",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "google",
|
||||
"ref": "v1.14.0",
|
||||
"repo": "googletest",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1657296039,
|
||||
"narHash": "sha256-Ghh39+aS+pw5sTP/ZO8VIKE6sBhMadDaQZtf+3yu4Vc=",
|
||||
"lastModified": 1704722960,
|
||||
"narHash": "sha256-mKGJ3sPsT6//s+Knglai5YflJUF2DGj7Ai6Ynopz0kI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "71d7a4c037dc4f3e98d5c4a81b941933cf5bf675",
|
||||
"rev": "317484b1ead87b9c1b8ac5261a8d2dd748a0492d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-22.05",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"clipper2-src": "clipper2-src",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
"gtest-src": "gtest-src",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"thrust-src": "thrust-src"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"thrust-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696873248,
|
||||
"narHash": "sha256-mUhMXGPbO2t83EvI8YNDRLngvLiwfzs4EgRrcxKfhHs=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "756c5afc0750f1413da05bd2b6505180e84c53d4",
|
||||
"revCount": 4758,
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://github.com/NVIDIA/thrust.git"
|
||||
},
|
||||
"original": {
|
||||
"submodules": true,
|
||||
"type": "git",
|
||||
"url": "https://github.com/NVIDIA/thrust.git"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||