Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a5b7140d | ||
|
|
98a61df840 | ||
|
|
a9dc1550ba | ||
|
|
fd8c381179 | ||
|
|
af9377d6e7 | ||
|
|
50fc5d87de | ||
|
|
ca81951355 | ||
|
|
c59130c754 | ||
|
|
491d7d8045 | ||
|
|
0fb47693b3 | ||
|
|
066aec0137 | ||
|
|
40624329e6 | ||
|
|
3eae433537 | ||
|
|
1fced6a33f | ||
|
|
89578ccb7a | ||
|
|
d5b5f044f2 | ||
|
|
4ea17bced1 | ||
|
|
5be306bb7d | ||
|
|
b26ae27916 | ||
|
|
667ae7fe75 | ||
|
|
2c750753a5 | ||
|
|
d15b8726c3 | ||
|
|
bc83f65ba7 | ||
|
|
3b00e9206a | ||
|
|
f2e3956770 | ||
|
|
d49de483ad | ||
|
|
4d01e46bd3 | ||
|
|
69a53f522a | ||
|
|
022995a697 | ||
|
|
add3a04fae | ||
|
|
a860854143 | ||
|
|
60697cc48a | ||
|
|
3e61c1a156 | ||
|
|
3f33c919df | ||
|
|
8538cf5ee3 | ||
|
|
ae2ba3e323 | ||
|
|
975a6e6b4b | ||
|
|
b1b35395fa | ||
|
|
31589d255c | ||
|
|
414c6e06be | ||
|
|
17f3751ae4 | ||
|
|
0e1ed721ba | ||
|
|
f6839a36e6 | ||
|
|
c38c2b6022 | ||
|
|
3464a9ca4f | ||
|
|
692dc69092 | ||
|
|
cc62c2c9dc | ||
|
|
fd1d42e96b | ||
|
|
2835e15a0e | ||
|
|
73a53e0ff8 | ||
|
|
405dd6373a | ||
|
|
11e58ea4d2 | ||
|
|
08598c045a | ||
|
|
747dcd94bc | ||
|
|
591b9e648d | ||
|
|
ae19d081f4 | ||
|
|
a08a9e7e51 | ||
|
|
bf2a9a84c1 | ||
|
|
c0c00345e0 | ||
|
|
e307662691 | ||
|
|
6fd20df42e | ||
|
|
b0a821efc0 | ||
|
|
f8a47865ec | ||
|
|
17fe0f7b7e | ||
|
|
0030964eb8 | ||
|
|
3003160b9e | ||
|
|
5cd70ff695 | ||
|
|
ce5d305491 | ||
|
|
986dbd529d | ||
|
|
449916bf70 | ||
|
|
bdd9214aea | ||
|
|
7430e43f60 | ||
|
|
e3a791a44d | ||
|
|
d7ecdd5fab | ||
|
|
2033bbf4d0 | ||
|
|
882468a0de | ||
|
|
a1382f247e | ||
|
|
e441e2e80b | ||
|
|
b51b0cb0af | ||
|
|
f7dec8c653 | ||
|
|
2db8c716ee | ||
|
|
541a99d290 | ||
|
|
0d9f4dadfb | ||
|
|
feedcd82d4 | ||
|
|
e9fc4018d5 | ||
|
|
70e911e2f2 | ||
|
|
ca6daf0ecc | ||
|
|
debe6b2c7f | ||
|
|
a7584bc7d3 | ||
|
|
4add644d76 | ||
|
|
45d217b37a | ||
|
|
311ca75a21 | ||
|
|
dd889d2b77 | ||
|
|
63164bed95 | ||
|
|
6cafc06970 | ||
|
|
372f5170ec | ||
|
|
2098bc9d1f | ||
|
|
d404b26d7a | ||
|
|
5b182a243c | ||
|
|
92bb83c9fd | ||
|
|
3b39bf2377 | ||
|
|
a149732fc7 | ||
|
|
fc79b076f3 | ||
|
|
cea3d22e09 | ||
|
|
97bef24f2b | ||
|
|
b7a4502866 | ||
|
|
d23e5512f0 | ||
|
|
e63e9b32e7 | ||
|
|
1a7b603420 | ||
|
|
eceb966804 | ||
|
|
a6f714b2e6 | ||
|
|
35ffc7a704 | ||
|
|
fce9adc5ff | ||
|
|
6247784ca4 | ||
|
|
873d8c6dc8 | ||
|
|
801b94a982 | ||
|
|
1e1fcf2dad | ||
|
|
15467456c8 | ||
|
|
bee3d08310 | ||
|
|
3a17c94262 | ||
|
|
98f87f005e | ||
|
|
4e98c1adf5 | ||
|
|
431656d676 | ||
|
|
f6cf8f87dd | ||
|
|
d12d090b2b | ||
|
|
efecc846ef | ||
|
|
f9a563bde5 | ||
|
|
b953396afc | ||
|
|
c1998773f1 | ||
|
|
c40adb1eff | ||
|
|
738d0fa050 | ||
|
|
9868a0e0c0 | ||
|
|
389f4ba8f9 | ||
|
|
08a59fdca0 | ||
|
|
aa5badd1ee | ||
|
|
c13ad5657e | ||
|
|
3e5ab8eb0c | ||
|
|
870b94e172 | ||
|
|
3da4dfbde0 | ||
|
|
14e0dcbd89 | ||
|
|
65b6057d2d | ||
|
|
8bb7aab137 | ||
|
|
02ae191ade | ||
|
|
efe2665352 | ||
|
|
fa5e1fe5db | ||
|
|
999a035b69 | ||
|
|
819739825b | ||
|
|
90a4b2205e | ||
|
|
e115f5e519 | ||
|
|
ecd93294af | ||
|
|
ecb6b97d38 | ||
|
|
c07f2b44a5 | ||
|
|
ed272b05b0 | ||
|
|
7023d2b1b7 | ||
|
|
1da7f6a75b | ||
|
|
b7a5d532e2 | ||
|
|
b567441192 | ||
|
|
3baf39cb24 | ||
|
|
5bf95d6402 | ||
|
|
b36d08b3ca | ||
|
|
2b601ea831 | ||
|
|
573adb62d5 | ||
|
|
16b11838f2 | ||
|
|
c7de23412c | ||
|
|
9c55fb932e | ||
|
|
817e34b187 | ||
|
|
a40fe42023 | ||
|
|
4a21b58aa0 | ||
|
|
cfae8694d2 | ||
|
|
0601a0e93d | ||
|
|
97b0b9f7d0 | ||
|
|
fd250cde87 | ||
|
|
d76f0ae3da | ||
|
|
0fc2151806 | ||
|
|
0292ed5c32 | ||
|
|
359774115a | ||
|
|
d1e71eb076 | ||
|
|
25c45cf99e | ||
|
|
6f89c5974c | ||
|
|
452cca961a | ||
|
|
be6dd1805b | ||
|
|
acb3cfe675 | ||
|
|
fcbb364b43 | ||
|
|
d4ec0485c2 | ||
|
|
a5d8b0c74f | ||
|
|
b1862224e8 | ||
|
|
5d25c9c8c5 | ||
|
|
bc7f8dc992 | ||
|
|
7d19d389fe | ||
|
|
93135d94fc | ||
|
|
1d7f22dc57 | ||
|
|
c6f4d9d181 | ||
|
|
dd931fcb72 | ||
|
|
5ef76f684c | ||
|
|
d4be350e5c | ||
|
|
bd48946ce1 | ||
|
|
2c1b52c58e | ||
|
|
f7f4d09f1a | ||
|
|
a7791d09b6 | ||
|
|
9648d32182 | ||
|
|
3545a49e0f | ||
|
|
c33bf99096 | ||
|
|
bfc8e5c30f | ||
|
|
d229f5f2f6 | ||
|
|
8c73a647cb | ||
|
|
9c32052a37 | ||
|
|
5f822a6533 | ||
|
|
2a861984b8 | ||
|
|
dbbcdbbd49 | ||
|
|
8f21bc5810 | ||
|
|
56878f9925 | ||
|
|
b4749542d3 | ||
|
|
4077aced41 | ||
|
|
c31683618e | ||
|
|
03f670783c | ||
|
|
3856fc6aba | ||
|
|
6db6a19e6c | ||
|
|
8eb63ee39c | ||
|
|
90dc3547f6 | ||
|
|
5b940b8229 | ||
|
|
fb0ec3493d | ||
|
|
fadad776b9 | ||
|
|
ddf70afcc1 | ||
|
|
c3688de36e | ||
|
|
5076f4009c | ||
|
|
c4a36d0d57 | ||
|
|
b43e8c2e17 | ||
|
|
17c4dc44c9 | ||
|
|
3443ded1de | ||
|
|
48d51b62b4 | ||
|
|
25e7bf77f3 | ||
|
|
f940fe9471 | ||
|
|
c5c533510d | ||
|
|
0c22c562a6 | ||
|
|
42b46f0d08 | ||
|
|
02ce2b49d4 | ||
|
|
1f1b608da2 | ||
|
|
ce963d3748 | ||
|
|
a39f4fd8aa | ||
|
|
88ed9d406c | ||
|
|
20c83d03f2 | ||
|
|
5d7f29c836 | ||
|
|
798aeb806a | ||
|
|
e16ef19ce4 | ||
|
|
14d6a6d917 | ||
|
|
5471fc19aa | ||
|
|
4d84ebf172 | ||
|
|
e8010cac2d | ||
|
|
09f4105007 | ||
|
|
8929136f30 | ||
|
|
d210dba8eb | ||
|
|
97aa92940f | ||
|
|
58432afcb7 | ||
|
|
76d07120b1 | ||
|
|
ec8d3d14a3 | ||
|
|
4708cc8744 | ||
|
|
4cd06cae1e | ||
|
|
0453418371 | ||
|
|
c8e5b28651 | ||
|
|
fd72eb9f12 | ||
|
|
68f9f14cf9 | ||
|
|
8e17e6c8ba | ||
|
|
5b55be9b5e | ||
|
|
5ff0a177e8 | ||
|
|
dc1d849e32 | ||
|
|
138801688c | ||
|
|
390560ea5c | ||
|
|
25964648bc | ||
|
|
569149f248 | ||
|
|
07e3acc522 | ||
|
|
bf76b21190 | ||
|
|
e04cc9ac01 | ||
|
|
e05f6ffbf3 | ||
|
|
44321662a4 | ||
|
|
a5c90055dc | ||
|
|
33a2d5a607 | ||
|
|
92bb669888 | ||
|
|
f09390a10d | ||
|
|
3ff256241a | ||
|
|
aebba6ab17 | ||
|
|
b65b445f43 | ||
|
|
4dc825761a | ||
|
|
d257f848ef | ||
|
|
2de7344fb7 | ||
|
|
c0f92acdf8 | ||
|
|
88dd84890b | ||
|
|
f55be52d93 | ||
|
|
3dc6c692c7 | ||
|
|
e3b9dd2402 | ||
|
|
0588106ed1 | ||
|
|
1a27e56fd4 | ||
|
|
3e92f47c89 | ||
|
|
eb3776343e | ||
|
|
7c3ee99161 | ||
|
|
9d7c31ae68 | ||
|
|
f1fad2f60d | ||
|
|
474073f58a | ||
|
|
24de66974b | ||
|
|
849c101356 | ||
|
|
7e1a09638b | ||
|
|
68471278d7 | ||
|
|
2166fbbde8 | ||
|
|
cc54247510 | ||
|
|
447bb8e208 | ||
|
|
e0f9daeb29 | ||
|
|
c112f4ede8 | ||
|
|
9c84e57d24 | ||
|
|
bb20b8254a | ||
|
|
826162068f | ||
|
|
a3ad27f3d8 | ||
|
|
6ebdc756ef | ||
|
|
656bd3b9d9 | ||
|
|
ac87d42eba | ||
|
|
db4688d22f | ||
|
|
2ac674540d | ||
|
|
f6620c592c | ||
|
|
3348cdec92 | ||
|
|
36a16ba051 | ||
|
|
eeaca5c17e | ||
|
|
7080173f30 | ||
|
|
bab5282969 | ||
|
|
a4b5d7a389 | ||
|
|
d3367d7a24 | ||
|
|
588fc12d7c | ||
|
|
fe8871122f | ||
|
|
3a1877e69f | ||
|
|
50a27f1186 | ||
|
|
3641b07806 | ||
|
|
8a1ca5e389 | ||
|
|
f2731443cd | ||
|
|
7db12dc944 | ||
|
|
52ed27a876 | ||
|
|
7275f15456 | ||
|
|
5c3e6028de |
@@ -0,0 +1 @@
|
||||
BasedOnStyle: Google
|
||||
@@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
@@ -1,27 +0,0 @@
|
||||
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-existing-dockerfile
|
||||
{
|
||||
"name": "Existing Dockerfile",
|
||||
// Sets the run context to one level up instead of the .devcontainer folder.
|
||||
"context": "..",
|
||||
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
||||
"dockerFile": "../Dockerfile",
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": null
|
||||
},
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [],
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Uncomment the next line to run commands after the container is created - for example installing git.
|
||||
// "postCreateCommand": "apt-get update && apt-get install -y git",
|
||||
// Uncomment when using a ptrace-based debugger like C++, Go, and Rust
|
||||
"runArgs": [
|
||||
"--runtime=nvidia"
|
||||
],
|
||||
// Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker.
|
||||
// "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
|
||||
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
|
||||
// "remoteUser": "vscode"
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
README.md
|
||||
@@ -0,0 +1,21 @@
|
||||
name: Check code formatting
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
check_format:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: DoozyX/clang-format-lint-action@v0.14
|
||||
with:
|
||||
source: '.'
|
||||
exclude: '*/third_party'
|
||||
extensions: 'h,cpp'
|
||||
clangFormatVersion: 12
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
name: Deploy documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
deploy_github_pages:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Doxygen Action
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
|
||||
- name: Move deployables
|
||||
run: |
|
||||
mkdir public
|
||||
mv ./samples ./public
|
||||
mv ./docs ./public
|
||||
mv ./bindings/wasm/examples/* ./public
|
||||
|
||||
- name: Download WASM files
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
name: wasm
|
||||
path: ./public
|
||||
|
||||
- name: Deploy to Github Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_branch: gh-pages
|
||||
force_orphan: true
|
||||
cname: manifoldcad.org
|
||||
publish_dir: ./public
|
||||
@@ -10,35 +10,185 @@ on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:11.1-devel-ubuntu20.04
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y cmake libglm-dev libassimp-dev libboost-graph-dev
|
||||
- name: Build CUDA
|
||||
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
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
run: |
|
||||
mkdir buildCUDA
|
||||
cd buildCUDA
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .. && make
|
||||
- name: Build CPP
|
||||
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
|
||||
run: |
|
||||
mkdir buildCPP
|
||||
cd buildCPP
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_USE_CPP=ON .. && make
|
||||
- name: Test CPP
|
||||
run: |
|
||||
cd buildCPP/test
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
cd build/test
|
||||
./manifold_test
|
||||
|
||||
cd ../../
|
||||
python3 bindings/python/examples/run_all.py
|
||||
- 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'
|
||||
run: |
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
cd ../
|
||||
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'
|
||||
with:
|
||||
files: build/code_coverage.info
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
|
||||
|
||||
build_wasm:
|
||||
runs-on: ubuntu-20.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
|
||||
with:
|
||||
submodules: true
|
||||
- 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
|
||||
- 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
|
||||
- name: Test WASM
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
cd ../bindings/wasm
|
||||
npm install
|
||||
npm test
|
||||
- name: Upload WASM files
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wasm
|
||||
path: |
|
||||
build/bindings/wasm/manifold.js
|
||||
build/bindings/wasm/manifold.wasm
|
||||
build/bindings/wasm/manifold.d.ts
|
||||
retention-days: 7
|
||||
|
||||
build_windows:
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
cuda_support: [OFF]
|
||||
max-parallel: 1
|
||||
runs-on: windows-2019
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- 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
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/test
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mac:
|
||||
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 TBB
|
||||
if: matrix.parallel_backend == 'TBB'
|
||||
run: brew install tbb
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
|
||||
build_nix:
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
|
||||
@@ -106,3 +106,11 @@ Temporary Items
|
||||
# End of https://www.gitignore.io/api/osx,c++,cuda,linux,cmake
|
||||
|
||||
build
|
||||
buildWASM
|
||||
docs/html
|
||||
node_modules/
|
||||
__pycache__
|
||||
.vscode/c_cpp_properties.json
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.cache
|
||||
@@ -0,0 +1,13 @@
|
||||
[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
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Mac",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**"
|
||||
],
|
||||
"defines": [],
|
||||
"macFrameworkPath": [
|
||||
"/System/Library/Frameworks",
|
||||
"/Library/Frameworks"
|
||||
],
|
||||
"compilerPath": "/usr/bin/clang",
|
||||
"cStandard": "c11",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "clang-x64",
|
||||
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||
"configurationProvider": "vector-of-bool.cmake-tools"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
@@ -1,98 +1,121 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"*.cu": "cpp",
|
||||
"*.cuh": "cpp",
|
||||
"*.m": "matlab",
|
||||
"deque": "cpp",
|
||||
"vector": "cpp",
|
||||
"cctype": "cpp",
|
||||
"cmath": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"strstream": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"exception": "cpp",
|
||||
"slist": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"memory": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"numeric": "cpp",
|
||||
"ratio": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"utility": "cpp",
|
||||
"csignal": "cpp",
|
||||
"bitset": "cpp",
|
||||
"complex": "cpp",
|
||||
"list": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"cfenv": "cpp",
|
||||
"*.ipp": "cpp",
|
||||
"string": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"valarray": "cpp",
|
||||
"__bit_reference": "cpp",
|
||||
"__config": "cpp",
|
||||
"__debug": "cpp",
|
||||
"__errc": "cpp",
|
||||
"__functional_base": "cpp",
|
||||
"__locale": "cpp",
|
||||
"__mutex_base": "cpp",
|
||||
"__node_handle": "cpp",
|
||||
"__nullptr": "cpp",
|
||||
"__split_buffer": "cpp",
|
||||
"__string": "cpp",
|
||||
"__threading_support": "cpp",
|
||||
"__tree": "cpp",
|
||||
"__tuple": "cpp",
|
||||
"bit": "cpp",
|
||||
"ios": "cpp",
|
||||
"iterator": "cpp",
|
||||
"locale": "cpp",
|
||||
"map": "cpp",
|
||||
"mutex": "cpp",
|
||||
"optional": "cpp",
|
||||
"string_view": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"set": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"random": "cpp",
|
||||
"variant": "cpp"
|
||||
},
|
||||
"C_Cpp.clang_format_style": "google",
|
||||
"C_Cpp.clang_format_fallbackStyle": "google",
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DMANIFOLD_USE_CPP=ON",
|
||||
// "-DMANIFOLD_USE_OMP=ON"
|
||||
],
|
||||
"files.associations": {
|
||||
"*.cu": "cpp",
|
||||
"*.cuh": "cpp",
|
||||
"*.m": "matlab",
|
||||
"deque": "cpp",
|
||||
"vector": "cpp",
|
||||
"cctype": "cpp",
|
||||
"cmath": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"strstream": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"exception": "cpp",
|
||||
"slist": "cpp",
|
||||
"fstream": "cpp",
|
||||
"functional": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"iostream": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"memory": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"numeric": "cpp",
|
||||
"ratio": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"utility": "cpp",
|
||||
"csignal": "cpp",
|
||||
"bitset": "cpp",
|
||||
"complex": "cpp",
|
||||
"list": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"cfenv": "cpp",
|
||||
"*.ipp": "cpp",
|
||||
"string": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"valarray": "cpp",
|
||||
"__bit_reference": "cpp",
|
||||
"__config": "cpp",
|
||||
"__debug": "cpp",
|
||||
"__errc": "cpp",
|
||||
"__functional_base": "cpp",
|
||||
"__locale": "cpp",
|
||||
"__mutex_base": "cpp",
|
||||
"__node_handle": "cpp",
|
||||
"__nullptr": "cpp",
|
||||
"__split_buffer": "cpp",
|
||||
"__string": "cpp",
|
||||
"__threading_support": "cpp",
|
||||
"__tree": "cpp",
|
||||
"__tuple": "cpp",
|
||||
"bit": "cpp",
|
||||
"ios": "cpp",
|
||||
"iterator": "cpp",
|
||||
"locale": "cpp",
|
||||
"map": "cpp",
|
||||
"mutex": "cpp",
|
||||
"optional": "cpp",
|
||||
"string_view": "cpp",
|
||||
"condition_variable": "cpp",
|
||||
"set": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"random": "cpp",
|
||||
"variant": "cpp",
|
||||
"hash_map": "cpp",
|
||||
"hash_set": "cpp",
|
||||
"codecvt": "cpp",
|
||||
"regex": "cpp",
|
||||
"future": "cpp",
|
||||
"shared_mutex": "cpp",
|
||||
"__bits": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"queue": "cpp",
|
||||
"stack": "cpp",
|
||||
"__hash_table": "cpp"
|
||||
},
|
||||
"C_Cpp.clang_format_fallbackStyle": "google",
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DBUILD_SHARED_LIBS=ON",
|
||||
"-DMANIFOLD_EXPORT=OFF",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=NONE",
|
||||
"-DCODE_COVERAGE=OFF",
|
||||
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
|
||||
],
|
||||
"search.exclude": {
|
||||
"**/third_party/*": true,
|
||||
"build*": true,
|
||||
"docs/*": true
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# This is the list of Manifold's significant contributors.
|
||||
#
|
||||
# This does not necessarily list everyone who has contributed code,
|
||||
# especially since many employees of one corporation may be contributing.
|
||||
# To see the full list of contributors, see the revision history in
|
||||
# source control.
|
||||
Emmett Lalish <elalish>
|
||||
Chun Kit LAM <pca006132>
|
||||
Google LLC
|
||||
@@ -1,49 +1,75 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
if (NOT CMAKE_CUDA_COMPILER)
|
||||
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")
|
||||
endif()
|
||||
project(manifold LANGUAGES CXX CUDA)
|
||||
# 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
|
||||
#
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(manifold LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
# OPTION(DEBUG_CMAKE_TARGETS "enable debug output for cmake target properties" OFF)
|
||||
# if(DEBUG_CMAKE_TARGETS)
|
||||
# set(CMAKE_DEBUG_TARGET_PROPERTIES
|
||||
# INCLUDE_DIRECTORIES
|
||||
# )
|
||||
# endif()
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
OPTION( MANIFOLD_USE_OMP
|
||||
"Use OpenMP as the Thrust backend instead of CUDA."
|
||||
OFF
|
||||
)
|
||||
OPTION( MANIFOLD_USE_CPP
|
||||
"Use C++ (single-threaded) as the Thrust backend instead of CUDA."
|
||||
OFF
|
||||
)
|
||||
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(MAINFOLD_FLAGS -Werror -Wall -Wno-sign-compare)
|
||||
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda)
|
||||
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3)
|
||||
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
|
||||
if(EMSCRIPTEN)
|
||||
message("Building for Emscripten")
|
||||
set(MANIFOLD_FLAGS -fexceptions)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
endif()
|
||||
|
||||
IF(MANIFOLD_USE_OMP)
|
||||
message("------------------------- Using OpenMP instead of CUDA.")
|
||||
find_package(OpenMP REQUIRED)
|
||||
set(MANIFOLD_OMP_INCLUDE OpenMP::OpenMP_CXX)
|
||||
set(MANIFOLD_NVCC_FLAGS ${MANIFOLD_NVCC_FLAGS} -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_OMP -Xcompiler=-fopenmp)
|
||||
ENDIF(MANIFOLD_USE_OMP)
|
||||
option(PYBIND11_FINDPYTHON on)
|
||||
|
||||
IF(MANIFOLD_USE_CPP)
|
||||
message("------------------------- Using C++ instead of CUDA.")
|
||||
set(MANIFOLD_NVCC_FLAGS ${MANIFOLD_NVCC_FLAGS} -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_CPP)
|
||||
ENDIF(MANIFOLD_USE_CPP)
|
||||
option(BUILD_TEST_CGAL off)
|
||||
|
||||
add_subdirectory(utilities)
|
||||
add_subdirectory(collider)
|
||||
add_subdirectory(polygon)
|
||||
add_subdirectory(manifold)
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(meshIO)
|
||||
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}>")
|
||||
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}>")
|
||||
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>")
|
||||
add_link_options("-coverage")
|
||||
endif()
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(tools)
|
||||
add_subdirectory(extras)
|
||||
add_subdirectory(bindings)
|
||||
@@ -0,0 +1,29 @@
|
||||
# How to Contribute
|
||||
|
||||
We'd love to accept your patches and contributions to this project. There are
|
||||
just a few small guidelines you need to follow.
|
||||
|
||||
## Contributor License Agreement
|
||||
|
||||
Contributions to this project must be accompanied by a Contributor License
|
||||
Agreement (CLA). You (or your employer) retain the copyright to your
|
||||
contribution; this simply gives us permission to use and redistribute your
|
||||
contributions as part of the project. Head over to
|
||||
<https://cla.developers.google.com/> to see your current agreements on file or
|
||||
to sign a new one.
|
||||
|
||||
You generally only need to submit a CLA once, so if you've already submitted one
|
||||
(even if it was for a different project), you probably don't need to do it
|
||||
again.
|
||||
|
||||
## Code Reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
## Community Guidelines
|
||||
|
||||
This project follows
|
||||
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
|
||||
@@ -1,17 +0,0 @@
|
||||
FROM nvidia/cuda:11.0-devel-ubuntu20.04
|
||||
LABEL Name=manifold Version=0.0.2
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get -y update && apt-get -y install \
|
||||
cmake \
|
||||
libglm-dev \
|
||||
libassimp-dev \
|
||||
libboost-graph-dev
|
||||
# RUN DEBIAN_FRONTEND=noninteractive apt-get -y install cuda-drivers
|
||||
COPY . /usr/src
|
||||
WORKDIR /usr/src
|
||||
RUN mkdir buildCPP && cd buildCPP && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_USE_CPP=ON .. && make
|
||||
RUN mkdir buildCUDA && cd buildCUDA && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .. && make
|
||||
CMD cd buildCPP/test && \
|
||||
./manifold_test
|
||||
@@ -1,20 +1,101 @@
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app. The operations are not GPU-accelerated, but it's still pretty fast and a good way to test out our Manifold library.
|
||||
|
||||

|
||||
|
||||
# Manifold
|
||||
|
||||
This is a geometry library dedicated to creating and operating on manifold 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).
|
||||
[**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)
|
||||
|
||||
[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.
|
||||
|
||||
## What's here
|
||||
|
||||
This library is intended to be fast with guaranteed manifold output. As such you need manifold meshes as input, which can be hard to come by since most 3D graphics meshes are not. This library can create simple primitive meshes but also links in Assimp, which will import many kinds of 3D files, but you'll get an error 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 anyone knows of another, please tell me. 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
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, though it could be improved even more with JS or Python bindings to avoid the syntax and compiling of C++.
|
||||
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
|
||||
|
||||
The canonical build instructions are in the [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml) file, as that is what this project's continuous integration server uses to build and test. I have only built under Ubuntu Linux, and the CI uses Nvidia's Cuda 11 Docker image. Part of my [road map](https://github.com/elalish/manifold/wiki/Manifold-Library#road-map) is to migrate from Thrust to C++20 parallel algorithms, which will alleviate the need to install the Cuda Developer Kit to build.
|
||||
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`.
|
||||
- `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.
|
||||
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
|
||||
|
||||
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
|
||||
To build the JS WASM library, first install NodeJS and set up emscripten:
|
||||
|
||||
(on Mac):
|
||||
```
|
||||
brew install nodejs
|
||||
brew install emscripten
|
||||
```
|
||||
(on Linux):
|
||||
```
|
||||
sudo apt install nodejs
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
source ./emsdk/emsdk_env.sh
|
||||
```
|
||||
Then build:
|
||||
```
|
||||
cd manifold
|
||||
mkdir buildWASM
|
||||
cd buildWASM
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
|
||||
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
|
||||
`$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
|
||||
python3 run_all.py -e
|
||||
```
|
||||
|
||||
Run the following code in the interpreter for
|
||||
python binding documentation:
|
||||
|
||||
```
|
||||
>>> import pymanifold
|
||||
>>> help(pymanifold)
|
||||
```
|
||||
|
||||
For more detailed documentation, please refer to the C++ API.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -22,4 +103,4 @@ Contributions are welcome! A lower barrier contribution is to simply make a PR t
|
||||
|
||||
## About the author
|
||||
|
||||
This library is by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee, but this is not a 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).
|
||||
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,21 @@
|
||||
# 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.
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
add_subdirectory(wasm)
|
||||
endif()
|
||||
@@ -0,0 +1,24 @@
|
||||
# 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.
|
||||
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
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 pymanifold 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
|
||||
|
||||
mortar_gap = (3/8) * INCHES
|
||||
|
||||
|
||||
def brick():
|
||||
return Manifold.cube(brick_length, brick_depth, brick_height)
|
||||
|
||||
|
||||
def halfbrick():
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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),
|
||||
])
|
||||
|
||||
|
||||
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))
|
||||
if length == 1 and width > 1:
|
||||
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)
|
||||
|
||||
|
||||
def run(width=10, length=10, height=10):
|
||||
return walls(length, width, height) + floor(length, width)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
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 pymanifold 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)
|
||||
|
||||
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
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
import sys
|
||||
import importlib
|
||||
import trimesh
|
||||
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]
|
||||
|
||||
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
|
||||
|
||||
for f in files:
|
||||
module = importlib.import_module(f)
|
||||
t0 = time()
|
||||
model = module.run()
|
||||
mesh = model.to_mesh()
|
||||
if export_models:
|
||||
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')
|
||||
t1 = time()
|
||||
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
|
||||
@@ -0,0 +1,504 @@
|
||||
// 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;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
add_subdirectory(pybind11)
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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.
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_14)
|
||||
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)
|
||||
|
||||
add_custom_command(
|
||||
TARGET manifoldjs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_BINARY_DIR}/examples/)
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,27 @@
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app based on this package.
|
||||
|
||||

|
||||
|
||||
# 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)
|
||||
|
||||
[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).
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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,249 @@
|
||||
// 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 <emscripten/bind.h>
|
||||
#include <emscripten/val.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)
|
||||
.field("y", &glm::vec2::y);
|
||||
|
||||
value_object<glm::ivec3>("ivec3")
|
||||
.field("0", &glm::ivec3::x)
|
||||
.field("1", &glm::ivec3::y)
|
||||
.field("2", &glm::ivec3::z);
|
||||
|
||||
value_object<glm::vec3>("vec3")
|
||||
.field("x", &glm::vec3::x)
|
||||
.field("y", &glm::vec3::y)
|
||||
.field("z", &glm::vec3::z);
|
||||
|
||||
value_object<glm::vec4>("vec4")
|
||||
.field("x", &glm::vec4::x)
|
||||
.field("y", &glm::vec4::y)
|
||||
.field("z", &glm::vec4::z)
|
||||
.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_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
value_object<Smoothness>("smoothness")
|
||||
.field("halfedge", &Smoothness::halfedge)
|
||||
.field("smoothness", &Smoothness::smoothness);
|
||||
|
||||
value_object<Properties>("properties")
|
||||
.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<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<BaryRef>("Vector_baryRef");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
.constructor(&FromMeshJS)
|
||||
.function("add", &Union)
|
||||
.function("subtract", &Difference)
|
||||
.function("intersect", &Intersection)
|
||||
.function("_GetMeshJS", &GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("_Warp", &Warp)
|
||||
.function("_Transform", &Transform)
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
.function("_Rotate", &Manifold::Rotate)
|
||||
.function("_Scale", &Manifold::Scale)
|
||||
.function("_Decompose", &Manifold::Decompose)
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
.function("numEdge", &Manifold::NumEdge)
|
||||
.function("numTri", &Manifold::NumTri)
|
||||
.function("_boundingBox", &Manifold::BoundingBox)
|
||||
.function("precision", &Manifold::Precision)
|
||||
.function("genus", &Manifold::Genus)
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("_getCurvature", &Manifold::GetCurvature)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal)
|
||||
.function("_getMeshRelation", &Manifold::GetMeshRelation);
|
||||
|
||||
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("_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);
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// 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.
|
||||
|
||||
var _ManifoldInitialized = false;
|
||||
Module.setup = function() {
|
||||
if (_ManifoldInitialized) return;
|
||||
_ManifoldInitialized = true;
|
||||
|
||||
function toVec(vec, list, f = x => x) {
|
||||
if (list) {
|
||||
for (let x of list) {
|
||||
vec.push_back(f(x));
|
||||
}
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
function fromVec(vec, f = x => x) {
|
||||
const result = [];
|
||||
const size = vec.size();
|
||||
for (let i = 0; i < size; i++) result.push(f(vec.get(i)));
|
||||
return result;
|
||||
}
|
||||
|
||||
function polygons2vec(polygons) {
|
||||
if (polygons[0].length < 3) {
|
||||
polygons = [polygons];
|
||||
}
|
||||
return toVec(
|
||||
new Module.Vector2_vec2(), polygons,
|
||||
poly => toVec(new Module.Vector_vec2(), poly, p => {
|
||||
if (p instanceof Array) return {x: p[0], y: p[1]};
|
||||
return p;
|
||||
}));
|
||||
}
|
||||
|
||||
function disposePolygons(polygonsVec) {
|
||||
for (let i = 0; i < polygonsVec.size(); i++) polygonsVec.get(i).delete();
|
||||
polygonsVec.delete();
|
||||
}
|
||||
|
||||
function vararg2vec(vec) {
|
||||
if (vec[0] instanceof Array)
|
||||
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
// default to 0
|
||||
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
const y = getValue(vec3Ptr + 4, 'float');
|
||||
const z = getValue(vec3Ptr + 8, 'float');
|
||||
const vert = [x, y, z];
|
||||
func(vert);
|
||||
setValue(vec3Ptr, vert[0], 'float');
|
||||
setValue(vec3Ptr + 4, vert[1], 'float');
|
||||
setValue(vec3Ptr + 8, vert[2], 'float');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
// note that the matrix is using column major (same as glm)
|
||||
Module.Manifold.prototype.transform = function(mat) {
|
||||
console.assert(mat.length == 4, 'expects a 3x4 matrix');
|
||||
const vec = new Module.Vector_f32();
|
||||
for (let col of mat) {
|
||||
console.assert(col.length == 3, 'expects a 3x4 matrix');
|
||||
for (let x of col) mat.push_back(x);
|
||||
}
|
||||
const result = this._Transform(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function(vec) {
|
||||
return this._Rotate(...vec);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec, z: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
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 {
|
||||
min: ['x', 'y', 'z'].map(f => result.min[f]),
|
||||
max: ['x', 'y', 'z'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
case Module.status.NON_FINITE_VERTEX.value:
|
||||
message = 'Non-finite vertex';
|
||||
break;
|
||||
case Module.status.NOT_MANIFOLD.value:
|
||||
message = 'Not manifold';
|
||||
break;
|
||||
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
|
||||
message = 'Vertex index out of bounds';
|
||||
break;
|
||||
case Module.status.PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Tri properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
|
||||
message = 'Tri properties out of bounds';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
base.name = this.name = 'ManifoldError';
|
||||
this.message = base.message;
|
||||
this.stack = base.stack;
|
||||
this.code = code;
|
||||
};
|
||||
|
||||
Module.ManifoldError.prototype = Object.create(Error.prototype, {
|
||||
constructor:
|
||||
{value: Module.ManifoldError, writable: true, configurable: true}
|
||||
});
|
||||
|
||||
const ManifoldCtor = Module.Manifold;
|
||||
Module.Manifold = function(mesh) {
|
||||
const manifold = new ManifoldCtor(mesh);
|
||||
|
||||
const status = manifold.status();
|
||||
if (status.value !== 0) {
|
||||
throw new Module.ManifoldError(status.value);
|
||||
}
|
||||
|
||||
return manifold;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
|
||||
Module.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);
|
||||
const center = args[1] || false;
|
||||
return Module._Cube(size, center);
|
||||
};
|
||||
|
||||
Module.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) {
|
||||
return Module._Sphere(radius, circularSegments);
|
||||
};
|
||||
|
||||
Module.smooth = function(mesh, sharpenedEdges = []) {
|
||||
const sharp = new Module.Vector_smoothness();
|
||||
toVec(sharp, sharpenedEdges);
|
||||
const result = Module._Smooth(mesh, sharp);
|
||||
sharp.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.extrude = function(
|
||||
polygons, height, nDivisions = 0, twistDegrees = 0.0,
|
||||
scaleTop = [1.0, 1.0]) {
|
||||
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Extrude(
|
||||
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.revolve = function(polygons, circularSegments = 0) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = Module._Revolve(polygonsVec, circularSegments);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.compose = function(manifolds) {
|
||||
const vec = new Module.Vector_manifold();
|
||||
toVec(vec, manifolds);
|
||||
const result = Module._Compose(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
|
||||
const bounds2 = {
|
||||
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
|
||||
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
|
||||
};
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
const y = getValue(vec3Ptr + 4, 'float');
|
||||
const z = getValue(vec3Ptr + 8, 'float');
|
||||
const vert = [x, y, z];
|
||||
return sdf(vert);
|
||||
}, 'fi');
|
||||
const out = Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
function batchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_manifold();
|
||||
for (const m of args) v.push_back(m);
|
||||
const result = Module['_' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Module.union = batchbool('union');
|
||||
Module.difference = batchbool('difference');
|
||||
Module.intersection = batchbool('intersection');
|
||||
};
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
font-weight: 800;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
padding: 0 1em;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 18px;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 800;
|
||||
text-align: left;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:active {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
color: black;
|
||||
background-color: #eee !important;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
height: 36px;
|
||||
background-color: transparent;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 800;
|
||||
text-align: left;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
model-viewer {
|
||||
position: relative;
|
||||
flex: 3;
|
||||
width: 100%;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#console {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
max-height: 25vh;
|
||||
overflow-y: scroll;
|
||||
background-color: #eee;
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
font-weight: 100;
|
||||
line-height: 20px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.col {
|
||||
position: relative;
|
||||
width: 50vw;
|
||||
}
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#file {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 250px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.uparrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
border: solid black;
|
||||
border-width: 0 3px 3px 0;
|
||||
padding: 3px;
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
|
||||
.down {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
position: absolute;
|
||||
width: 36px;
|
||||
top: 0px;
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.cancel {
|
||||
background-image: url(icons/close.png);
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.blue {
|
||||
background-color: rgb(148, 221, 255);
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: rgb(131, 255, 131);
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: rgb(255, 177, 177);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.col {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
// 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 exampleFunctions = examples.functionBodies;
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
const fileButton = document.querySelector('#file');
|
||||
const currentElement = document.querySelector('#current');
|
||||
const arrow = document.querySelector('.uparrow');
|
||||
const dropdown = document.querySelector('.dropdown');
|
||||
|
||||
const hideDropdown = function() {
|
||||
dropdown.classList.remove('show');
|
||||
arrow.classList.remove('down');
|
||||
};
|
||||
const toggleDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
dropdown.classList.toggle('show');
|
||||
arrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleDropdown;
|
||||
document.body.onclick = hideDropdown;
|
||||
|
||||
const prefix = 'ManifoldCAD';
|
||||
function getScript(name) {
|
||||
return window.localStorage.getItem(prefix + name);
|
||||
}
|
||||
function setScript(name, code) {
|
||||
window.localStorage.setItem(prefix + name, code);
|
||||
}
|
||||
function removeScript(name) {
|
||||
window.localStorage.removeItem(prefix + name);
|
||||
}
|
||||
function nthKey(n) {
|
||||
if (n >= window.localStorage.length) return;
|
||||
const key = window.localStorage.key(n);
|
||||
if (key.startsWith(prefix)) {
|
||||
return key.slice(prefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
function saveCurrent() {
|
||||
if (editor) {
|
||||
const currentName = currentElement.textContent;
|
||||
if (!exampleFunctions.get(currentName)) {
|
||||
setScript(currentName, editor.getValue());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.onpagehide = saveCurrent;
|
||||
window.beforeunload = saveCurrent;
|
||||
|
||||
let switching = false;
|
||||
let isExample = true;
|
||||
function switchTo(scriptName) {
|
||||
if (editor) {
|
||||
switching = true;
|
||||
currentElement.textContent = scriptName;
|
||||
setScript('currentName', scriptName);
|
||||
const code =
|
||||
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
|
||||
isExample = exampleFunctions.get(scriptName) != null;
|
||||
editor.setValue(code);
|
||||
}
|
||||
}
|
||||
|
||||
function createDropdownItem(name) {
|
||||
const container = document.createElement('div');
|
||||
container.classList.add('item');
|
||||
const button = document.createElement('button');
|
||||
container.appendChild(button);
|
||||
button.type = 'button';
|
||||
button.classList.add('blue', 'item');
|
||||
const label = document.createElement('span');
|
||||
button.appendChild(label);
|
||||
label.textContent = name;
|
||||
|
||||
button.onclick = function() {
|
||||
saveCurrent();
|
||||
switchTo(label.textContent);
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
function addIcon(button) {
|
||||
const icon = document.createElement('button');
|
||||
icon.classList.add('icon');
|
||||
button.parentElement.appendChild(icon);
|
||||
return icon;
|
||||
}
|
||||
|
||||
function uniqueName(name) {
|
||||
let num = 1;
|
||||
let newName = name;
|
||||
while (getScript(newName) != null || exampleFunctions.get(newName) != null) {
|
||||
newName = name + ' ' + num++;
|
||||
}
|
||||
return newName;
|
||||
}
|
||||
|
||||
function addEdit(button) {
|
||||
const label = button.firstChild;
|
||||
const edit = addIcon(button);
|
||||
edit.style.backgroundImage = 'url(icons/pencil.png)';
|
||||
edit.style.right = '30px';
|
||||
|
||||
edit.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
const oldName = label.textContent;
|
||||
const code = getScript(oldName);
|
||||
const form = document.createElement('form');
|
||||
const inputElement = document.createElement('input');
|
||||
inputElement.value = oldName;
|
||||
label.textContent = '';
|
||||
button.appendChild(form);
|
||||
form.appendChild(inputElement);
|
||||
inputElement.focus();
|
||||
inputElement.setSelectionRange(0, oldName.length);
|
||||
|
||||
function rename() {
|
||||
const input = inputElement.value;
|
||||
inputElement.blur();
|
||||
if (!input) return;
|
||||
const newName = uniqueName(input);
|
||||
label.textContent = newName;
|
||||
if (currentElement.textContent == oldName) {
|
||||
currentElement.textContent = newName;
|
||||
}
|
||||
removeScript(oldName);
|
||||
setScript(newName, code);
|
||||
}
|
||||
|
||||
form.onsubmit = rename;
|
||||
inputElement.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
inputElement.onblur = function() {
|
||||
button.removeChild(form);
|
||||
label.textContent = oldName;
|
||||
};
|
||||
};
|
||||
|
||||
const trash = addIcon(button);
|
||||
trash.style.backgroundImage = 'url(icons/trash.png)';
|
||||
trash.style.right = '0px';
|
||||
let lastClick = 0;
|
||||
|
||||
trash.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
if (button.classList.contains('blue')) {
|
||||
lastClick = performance.now();
|
||||
button.classList.remove('blue');
|
||||
button.classList.add('red');
|
||||
document.body.addEventListener('click', function() {
|
||||
button.classList.add('blue');
|
||||
button.classList.remove('red');
|
||||
}, {once: true});
|
||||
} else if (performance.now() - lastClick > 500) {
|
||||
removeScript(label.textContent);
|
||||
if (currentElement.textContent == label.textContent) {
|
||||
switchTo('Intro');
|
||||
}
|
||||
const container = button.parentElement;
|
||||
container.parentElement.removeChild(container);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const newButton = document.querySelector('#new');
|
||||
function newItem(code) {
|
||||
const name = uniqueName('New Script');
|
||||
setScript(name, code);
|
||||
const nextButton = createDropdownItem(name);
|
||||
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
|
||||
addEdit(nextButton);
|
||||
nextButton.click();
|
||||
};
|
||||
newButton.onclick = function() {
|
||||
newItem('');
|
||||
};
|
||||
|
||||
const runButton = document.querySelector('#compile');
|
||||
const poster = document.querySelector('#poster');
|
||||
let manifoldInitialized = false;
|
||||
let autoExecute = true;
|
||||
|
||||
function initializeRun() {
|
||||
runButton.disabled = false;
|
||||
if (autoExecute) {
|
||||
runButton.click();
|
||||
} else {
|
||||
poster.textContent = 'Auto-run disabled due to prior failure';
|
||||
}
|
||||
}
|
||||
|
||||
// Editor ------------------------------------------------------------
|
||||
let tsWorker = undefined;
|
||||
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);
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
const w = await monaco.languages.typescript.getTypeScriptWorker();
|
||||
tsWorker = await w(editor.getModel().uri);
|
||||
editor.getModel().updateOptions({tabSize: 2});
|
||||
|
||||
for (const [name] of exampleFunctions) {
|
||||
const button = createDropdownItem(name);
|
||||
dropdown.appendChild(button.parentElement);
|
||||
}
|
||||
|
||||
let currentName = currentElement.textContent;
|
||||
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = nthKey(i);
|
||||
if (!key) continue;
|
||||
if (key === 'currentName') {
|
||||
currentName = getScript(key);
|
||||
} else if (key === 'safe') {
|
||||
autoExecute = getScript(key) !== 'false';
|
||||
} else {
|
||||
const button = createDropdownItem(key);
|
||||
newButton.insertAdjacentElement('afterend', button.parentElement);
|
||||
addEdit(button);
|
||||
}
|
||||
}
|
||||
switchTo(currentName);
|
||||
|
||||
if (manifoldInitialized) {
|
||||
initializeRun();
|
||||
}
|
||||
|
||||
editor.onDidChangeModelContent(e => {
|
||||
runButton.disabled = false;
|
||||
if (switching) {
|
||||
switching = false;
|
||||
return;
|
||||
}
|
||||
if (isExample) {
|
||||
const cursor = editor.getPosition();
|
||||
newItem(editor.getValue());
|
||||
editor.setPosition(cursor);
|
||||
}
|
||||
});
|
||||
|
||||
window.onresize = () => {
|
||||
editor.layout({});
|
||||
};
|
||||
});
|
||||
|
||||
// Execution ------------------------------------------------------------
|
||||
const consoleElement = document.querySelector('#console');
|
||||
const oldLog = console.log;
|
||||
console.log = function(message) {
|
||||
consoleElement.textContent += message + '\r\n';
|
||||
consoleElement.scrollTop = consoleElement.scrollHeight;
|
||||
oldLog(message);
|
||||
};
|
||||
|
||||
function clearConsole() {
|
||||
consoleElement.textContent = '';
|
||||
}
|
||||
|
||||
function enableCancel() {
|
||||
runButton.firstChild.style.visibility = 'hidden';
|
||||
runButton.classList.add('red', 'cancel');
|
||||
}
|
||||
|
||||
function disableCancel() {
|
||||
runButton.firstChild.style.visibility = 'visible';
|
||||
runButton.classList.remove('red', 'cancel');
|
||||
}
|
||||
|
||||
let t0 = performance.now();
|
||||
|
||||
function finishRun() {
|
||||
disableCancel();
|
||||
const t1 = performance.now();
|
||||
const log = consoleElement.textContent;
|
||||
// Remove "Running..."
|
||||
consoleElement.textContent = log.substring(log.indexOf('\n') + 1);
|
||||
console.log(
|
||||
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
|
||||
}
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new Worker('worker.js');
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
initializeRun();
|
||||
}
|
||||
manifoldInitialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.data.log != null) {
|
||||
consoleElement.textContent += e.data.log + '\r\n';
|
||||
consoleElement.scrollTop = consoleElement.scrollHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
finishRun();
|
||||
runButton.disabled = true;
|
||||
setScript('safe', 'true');
|
||||
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
mv.src = objectURL;
|
||||
if (objectURL == null) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createWorker();
|
||||
|
||||
async function run() {
|
||||
saveCurrent();
|
||||
setScript('safe', 'false');
|
||||
enableCancel();
|
||||
clearConsole();
|
||||
console.log('Running...');
|
||||
const output = await tsWorker.getEmitOutput(editor.getModel().uri.toString());
|
||||
manifoldWorker.postMessage(output.outputFiles[0].text);
|
||||
t0 = performance.now();
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
manifoldWorker.terminate();
|
||||
createWorker();
|
||||
finishRun();
|
||||
console.log('Run canceled');
|
||||
}
|
||||
|
||||
runButton.onclick = function() {
|
||||
if (runButton.classList.contains('cancel')) {
|
||||
cancel();
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
};
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
@@ -0,0 +1,440 @@
|
||||
// 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);
|
||||
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 375 B |
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,56 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<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" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="background-color: rgb(162 136 179);">
|
||||
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
</p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
<div class="dropdown">
|
||||
<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>
|
||||
<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">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;">
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="console"></div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,127 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Manifold - <code></code><model-viewer></code> 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;
|
||||
}
|
||||
|
||||
model-viewer {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
</body>
|
||||
|
||||
<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 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 csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
push2MV(mesh);
|
||||
};
|
||||
|
||||
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 }
|
||||
);
|
||||
}
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,119 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,121 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Manifold - three.js 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>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
<option value="union">Union</option>
|
||||
</select><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
|
||||
<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;
|
||||
|
||||
wasm.setup();
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
|
||||
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 csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
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 triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, 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.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
// 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,452 @@
|
||||
// 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.
|
||||
|
||||
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[],
|
||||
};
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
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;
|
||||
setup: () => void;
|
||||
}
|
||||
|
||||
declare function Module(): Promise<ManifoldStatic>;
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"files": [
|
||||
"manifold.js",
|
||||
"manifold.wasm",
|
||||
"manifold.d.ts",
|
||||
"examples/examples.js",
|
||||
"test/test.js"
|
||||
],
|
||||
"typings": "manifold.d.ts",
|
||||
"types": "manifold.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/elalish/manifold.git"
|
||||
},
|
||||
"keywords": [
|
||||
"3D",
|
||||
"mesh",
|
||||
"manifold",
|
||||
"geometry",
|
||||
"solid",
|
||||
"CAD",
|
||||
"triangle",
|
||||
"SDF"
|
||||
],
|
||||
"contributors": [
|
||||
"Emmett Lalish <elalish>",
|
||||
"Chun Kit LAM <pca006132>"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
project (collider)
|
||||
|
||||
add_library(${PROJECT_NAME} src/collider.cu)
|
||||
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
|
||||
target_include_directories( ${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE ${MANIFOLD_OMP_INCLUDE}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE ${MANIFOLD_NVCC_FLAGS}
|
||||
)
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE "$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>" "$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>"
|
||||
)
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
manifold:
|
||||
image: manifold
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
@@ -1,6 +0,0 @@
|
||||
version: '3.4'
|
||||
|
||||
services:
|
||||
manifold:
|
||||
image: manifold
|
||||
build: .
|
||||
@@ -0,0 +1,33 @@
|
||||
# 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.
|
||||
|
||||
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)
|
||||
|
||||
if(BUILD_TEST_CGAL)
|
||||
add_executable(perfTestCGAL perf_test_cgal.cpp)
|
||||
find_package(CGAL REQUIRED COMPONENTS Core)
|
||||
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
|
||||
|
||||
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
|
||||
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
|
||||
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
|
||||
endif()
|
||||
@@ -0,0 +1,22 @@
|
||||
2022-02-22
|
||||
CPU:
|
||||
nTri = 512, time = 0.0176101 sec
|
||||
nTri = 2048, time = 0.0196583 sec
|
||||
nTri = 8192, time = 0.0355035 sec
|
||||
nTri = 32768, time = 0.0999017 sec
|
||||
nTri = 131072, time = 0.334987 sec
|
||||
nTri = 524288, time = 1.25136 sec
|
||||
nTri = 2097152, time = 5.81453 sec
|
||||
nTri = 8388608, time = 26.987 sec
|
||||
|
||||
GPU:
|
||||
nTri = 512, time = 0.00846689 sec
|
||||
nTri = 2048, time = 0.0115478 sec
|
||||
nTri = 8192, time = 0.0222773 sec
|
||||
nTri = 32768, time = 0.0560136 sec
|
||||
nTri = 131072, time = 0.176548 sec
|
||||
nTri = 524288, time = 0.670403 sec
|
||||
nTri = 2097152, time = 2.60109 sec
|
||||
nTri = 8388608, time = 10.5697 sec
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Emmett Lalish
|
||||
// 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.
|
||||
@@ -22,10 +22,10 @@ using namespace manifold;
|
||||
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));
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Manifold diff = sphere - sphere2;
|
||||
diff.NumTri();
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> elapsed = end - start;
|
||||
std::cout << "nTri = " << sphere.NumTri() << ", time = " << elapsed.count()
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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 <CGAL/Exact_predicates_exact_constructions_kernel.h>
|
||||
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
|
||||
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "manifold.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
// Epick = Exact predicates Inexact constructions. Seems fair to use to compare
|
||||
// to Manifold, which seems to store double coordinates.
|
||||
typedef CGAL::Epick Kernel;
|
||||
|
||||
// Epeck = Exact predicates Exact constructions. What OpenSCAD uses to guarantee
|
||||
// geometry ends up where it should even after many operations. typedef
|
||||
// CGAL::Epeck Kernel;
|
||||
|
||||
typedef CGAL::Point_3<Kernel> Point;
|
||||
typedef CGAL::Surface_mesh<Point> TriangleMesh;
|
||||
typedef CGAL::SM_Vertex_index Vertex;
|
||||
|
||||
void manifoldToCGALSurfaceMesh(Manifold &manifold, TriangleMesh &cgalMesh) {
|
||||
auto maniMesh = manifold.GetMesh();
|
||||
|
||||
const int n = maniMesh.vertPos.size();
|
||||
std::vector<Vertex> vertices(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
auto &vert = maniMesh.vertPos[i];
|
||||
vertices[i] = cgalMesh.add_vertex(Point(vert.x, vert.y, vert.z));
|
||||
}
|
||||
|
||||
for (auto &triVert : maniMesh.triVerts) {
|
||||
std::vector<Vertex> polygon{vertices[triVert[0]], vertices[triVert[1]],
|
||||
vertices[triVert[2]]};
|
||||
cgalMesh.add_face(polygon);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
TriangleMesh cgalSphere, cgalSphere2, cgalOut;
|
||||
manifoldToCGALSurfaceMesh(sphere, cgalSphere);
|
||||
manifoldToCGALSurfaceMesh(sphere2, cgalSphere2);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto result =
|
||||
CGAL::Polygon_mesh_processing::corefine_and_compute_difference(
|
||||
// CGAL::Polygon_mesh_processing::corefine_and_compute_union(
|
||||
// CGAL::Polygon_mesh_processing::corefine_and_compute_intersection(
|
||||
cgalSphere, cgalSphere2, cgalSphere);
|
||||
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> elapsed = end - start;
|
||||
std::cout << "nTri = " << sphere.NumTri() << ", time = " << elapsed.count()
|
||||
<< " sec" << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1649676176,
|
||||
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1657296039,
|
||||
"narHash": "sha256-Ghh39+aS+pw5sTP/ZO8VIKE6sBhMadDaQZtf+3yu4Vc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "71d7a4c037dc4f3e98d5c4a81b941933cf5bf675",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-22.05",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
{
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
manifold =
|
||||
{ parallel-backend ? "none"
|
||||
, cuda-support ? false
|
||||
, doCheck ? true
|
||||
, build-tools ? [ ]
|
||||
, ...
|
||||
}: pkgs.stdenv.mkDerivation {
|
||||
inherit doCheck;
|
||||
pname =
|
||||
if cuda-support then
|
||||
"manifold-${parallel-backend}-cuda"
|
||||
else
|
||||
"manifold-${parallel-backend}";
|
||||
version = "beta";
|
||||
src = self;
|
||||
patches = [ ./thrust.diff ];
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages(ps: with ps; [trimesh])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
|
||||
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
|
||||
];
|
||||
checkPhase = ''
|
||||
cd test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python python3 bindings/python/examples/run_all.py
|
||||
cd build
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp src/manifold/libmanifold.a $out/
|
||||
cp extras/perfTest $out
|
||||
cp bindings/python/pymanifold* $out
|
||||
'';
|
||||
};
|
||||
parallelBackends = [
|
||||
{ parallel-backend = "none"; }
|
||||
{
|
||||
parallel-backend = "omp";
|
||||
build-tools = [ pkgs.llvmPackages_13.openmp ];
|
||||
}
|
||||
{
|
||||
parallel-backend = "tbb";
|
||||
build-tools = with pkgs; [ tbb pkg-config ];
|
||||
}
|
||||
];
|
||||
buildMatrix = with pkgs; with lib; lists.flatten (map
|
||||
(env: map
|
||||
(x: x // env)
|
||||
parallelBackends) [
|
||||
{ cuda-support = false; }
|
||||
{
|
||||
cuda-support = true;
|
||||
}
|
||||
]);
|
||||
devShell = { additional ? [ ] }: pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cmake
|
||||
llvmPackages_13.openmp
|
||||
clang-tools
|
||||
clang_13
|
||||
emscripten
|
||||
tbb
|
||||
lcov
|
||||
] ++ additional;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = (builtins.listToAttrs
|
||||
(map
|
||||
(x: {
|
||||
name = "manifold-" + x.parallel-backend + (if
|
||||
x.cuda-support then "-cuda" else "");
|
||||
value = manifold x;
|
||||
})
|
||||
buildMatrix)) // {
|
||||
manifold-js = pkgs.buildEmscriptenPackage {
|
||||
name = "manifold-js";
|
||||
version = "beta";
|
||||
src = self;
|
||||
nativeBuildInputs = (with pkgs; [ cmake python39 ]);
|
||||
buildInputs = [ pkgs.nodejs ];
|
||||
configurePhase = ''
|
||||
mkdir -p .emscriptencache
|
||||
export EM_CACHE=$(pwd)/.emscriptencache
|
||||
mkdir build
|
||||
cd build
|
||||
mkdir cache
|
||||
export EM_CACHE=$(pwd)/cache
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
'';
|
||||
buildPhase = ''
|
||||
emmake make -j''${NIX_BUILD_CORES}
|
||||
'';
|
||||
checkPhase = ''
|
||||
cd test
|
||||
node manifold_test.js
|
||||
cd ../
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp {extras,wasm}/*.js $out/
|
||||
cp {extras,wasm}/*.wasm $out/
|
||||
'';
|
||||
};
|
||||
};
|
||||
devShell = devShell { };
|
||||
devShells.cuda = devShell {
|
||||
additional = with pkgs.cudaPackages; [
|
||||
cuda_nvcc
|
||||
cuda_cudart
|
||||
cuda_cccl
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
project (manifold)
|
||||
|
||||
find_package(Boost COMPONENTS graph REQUIRED)
|
||||
|
||||
add_library(${PROJECT_NAME} src/manifold.cu src/constructors.cu src/impl.cu src/properties.cu src/sort.cu src/edge_op.cu src/face_op.cu src/smoothing.cu src/boolean3.cu src/boolean_result.cu)
|
||||
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
|
||||
target_include_directories( ${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE collider polygon ${MANIFOLD_OMP_INCLUDE} Boost::graph
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE ${MANIFOLD_NVCC_FLAGS}
|
||||
)
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PRIVATE "$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>" "$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>"
|
||||
)
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_14
|
||||
)
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
//
|
||||
// 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 <map>
|
||||
|
||||
#include "impl.cuh"
|
||||
#include "polygon.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Triangulates the faces. In this case, the halfedge_ vector is not yet a set
|
||||
* of triangles as required by this data structure, but is instead a set of
|
||||
* general faces with the input faceEdge vector having length of the number of
|
||||
* faces + 1. The values are indicies into the halfedge_ vector for the first
|
||||
* edge of each face, with the final value being the length of the halfedge_
|
||||
* vector itself. Upon return, halfedge_ has been lengthened and properly
|
||||
* represents the mesh as a set of triangles as usual. In this process the
|
||||
* faceNormal_ values are retained, repeated as necessary.
|
||||
*/
|
||||
void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
const VecDH<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary) {
|
||||
VecDH<glm::ivec3> triVertsOut;
|
||||
VecDH<glm::vec3> triNormalOut;
|
||||
|
||||
VecH<glm::ivec3>& triVerts = triVertsOut.H();
|
||||
VecH<glm::vec3>& triNormal = triNormalOut.H();
|
||||
const VecH<glm::vec3>& vertPos = vertPos_.H();
|
||||
const VecH<int>& faceEdgeH = faceEdge.H();
|
||||
const VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
const VecH<glm::vec3>& faceNormal = faceNormal_.H();
|
||||
meshRelation_.triBary.resize(0);
|
||||
|
||||
for (int face = 0; face < faceEdgeH.size() - 1; ++face) {
|
||||
const int firstEdge = faceEdgeH[face];
|
||||
const int lastEdge = faceEdgeH[face + 1];
|
||||
const int numEdge = lastEdge - firstEdge;
|
||||
ALWAYS_ASSERT(numEdge >= 3, topologyErr, "face has less than three edges.");
|
||||
const glm::vec3 normal = faceNormal[face];
|
||||
|
||||
std::map<int, int> vertBary;
|
||||
for (int j = firstEdge; j < lastEdge; ++j)
|
||||
vertBary[halfedge[j].startVert] = halfedgeBary.H()[j];
|
||||
const int startTri = triVerts.size();
|
||||
|
||||
if (numEdge == 3) { // Single triangle
|
||||
glm::ivec3 tri(halfedge[firstEdge].startVert,
|
||||
halfedge[firstEdge + 1].startVert,
|
||||
halfedge[firstEdge + 2].startVert);
|
||||
glm::ivec3 ends(halfedge[firstEdge].endVert,
|
||||
halfedge[firstEdge + 1].endVert,
|
||||
halfedge[firstEdge + 2].endVert);
|
||||
if (ends[0] == tri[2]) {
|
||||
std::swap(tri[1], tri[2]);
|
||||
std::swap(ends[1], ends[2]);
|
||||
}
|
||||
ALWAYS_ASSERT(ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0],
|
||||
topologyErr, "These 3 edges do not form a triangle!");
|
||||
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
} else if (numEdge == 4) { // Pair of triangles
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
|
||||
auto triCCW = [&projection, &vertPos, this](const glm::ivec3 tri) {
|
||||
return CCW(projection * vertPos[tri[0]], projection * vertPos[tri[1]],
|
||||
projection * vertPos[tri[2]], precision_) >= 0;
|
||||
};
|
||||
|
||||
glm::ivec3 tri0(halfedge[firstEdge].startVert,
|
||||
halfedge[firstEdge].endVert, -1);
|
||||
glm::ivec3 tri1(-1, -1, tri0[0]);
|
||||
for (const int i : {1, 2, 3}) {
|
||||
if (halfedge[firstEdge + i].startVert == tri0[1]) {
|
||||
tri0[2] = halfedge[firstEdge + i].endVert;
|
||||
tri1[0] = tri0[2];
|
||||
}
|
||||
if (halfedge[firstEdge + i].endVert == tri0[0]) {
|
||||
tri1[1] = halfedge[firstEdge + i].startVert;
|
||||
}
|
||||
}
|
||||
ALWAYS_ASSERT(glm::all(glm::greaterThanEqual(tri0, glm::ivec3(0))) &&
|
||||
glm::all(glm::greaterThanEqual(tri1, glm::ivec3(0))),
|
||||
topologyErr, "non-manifold quad!");
|
||||
bool firstValid = triCCW(tri0) && triCCW(tri1);
|
||||
tri0[2] = tri1[1];
|
||||
tri1[2] = tri0[1];
|
||||
bool secondValid = triCCW(tri0) && triCCW(tri1);
|
||||
|
||||
if (!secondValid) {
|
||||
tri0[2] = tri1[0];
|
||||
tri1[2] = tri0[0];
|
||||
} else if (firstValid) {
|
||||
glm::vec3 firstCross = vertPos[tri0[0]] - vertPos[tri1[0]];
|
||||
glm::vec3 secondCross = vertPos[tri0[1]] - vertPos[tri1[1]];
|
||||
if (glm::dot(firstCross, firstCross) <
|
||||
glm::dot(secondCross, secondCross)) {
|
||||
tri0[2] = tri1[0];
|
||||
tri1[2] = tri0[0];
|
||||
}
|
||||
}
|
||||
|
||||
triVerts.push_back(tri0);
|
||||
triNormal.push_back(normal);
|
||||
triVerts.push_back(tri1);
|
||||
triNormal.push_back(normal);
|
||||
} else { // General triangulation
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
|
||||
|
||||
Polygons polys;
|
||||
try {
|
||||
polys = Face2Polygons(face, projection, faceEdgeH);
|
||||
} catch (const std::exception& e) {
|
||||
std::cout << e.what() << std::endl;
|
||||
for (int edge = faceEdgeH[face]; edge < faceEdgeH[face + 1]; ++edge)
|
||||
std::cout << "halfedge: " << edge << ", " << halfedge[edge]
|
||||
<< std::endl;
|
||||
throw;
|
||||
}
|
||||
|
||||
std::vector<glm::ivec3> newTris = Triangulate(polys, precision_);
|
||||
|
||||
for (auto tri : newTris) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
}
|
||||
}
|
||||
|
||||
for (int tri = startTri; tri < triVerts.size(); ++tri) {
|
||||
meshRelation_.triBary.H().push_back(faceRef.H()[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
meshRelation_.triBary.H().back().vertBary[k] =
|
||||
vertBary[triVerts[tri][k]];
|
||||
}
|
||||
}
|
||||
}
|
||||
faceNormal_ = triNormalOut;
|
||||
CreateAndFixHalfedges(triVertsOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the input face index, return a set of 2D polygons formed by the input
|
||||
* projection of the vertices.
|
||||
*/
|
||||
Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecH<int>& faceEdge) const {
|
||||
const VecH<glm::vec3>& vertPos = vertPos_.H();
|
||||
const VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
const int firstEdge = faceEdge[face];
|
||||
const int lastEdge = faceEdge[face + 1];
|
||||
|
||||
std::map<int, int> vert_edge;
|
||||
for (int edge = firstEdge; edge < lastEdge; ++edge) {
|
||||
ALWAYS_ASSERT(
|
||||
vert_edge.emplace(std::make_pair(halfedge[edge].startVert, edge))
|
||||
.second,
|
||||
topologyErr, "face has duplicate vertices.");
|
||||
}
|
||||
|
||||
Polygons polys;
|
||||
int startEdge = 0;
|
||||
int thisEdge = startEdge;
|
||||
while (1) {
|
||||
if (thisEdge == startEdge) {
|
||||
if (vert_edge.empty()) break;
|
||||
startEdge = vert_edge.begin()->second;
|
||||
thisEdge = startEdge;
|
||||
polys.push_back({});
|
||||
}
|
||||
int vert = halfedge[thisEdge].startVert;
|
||||
polys.back().push_back({projection * vertPos[vert], vert});
|
||||
const auto result = vert_edge.find(halfedge[thisEdge].endVert);
|
||||
ALWAYS_ASSERT(result != vert_edge.end(), topologyErr, "nonmanifold edge");
|
||||
thisEdge = result->second;
|
||||
vert_edge.erase(result);
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -1,401 +0,0 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
//
|
||||
// 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 "boolean3.cuh"
|
||||
#include "impl.cuh"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
using namespace thrust::placeholders;
|
||||
|
||||
struct MakeTri {
|
||||
const Halfedge* halfedges;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
|
||||
glm::ivec3& tri = thrust::get<0>(inOut);
|
||||
const int face = 3 * thrust::get<1>(inOut);
|
||||
|
||||
for (int i : {0, 1, 2}) {
|
||||
tri[i] = halfedges[face + i].startVert;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GetMeshID {
|
||||
__host__ __device__ void operator()(thrust::tuple<int&, BaryRef> inOut) {
|
||||
thrust::get<0>(inOut) = thrust::get<1>(inOut).meshID;
|
||||
}
|
||||
};
|
||||
|
||||
Manifold Halfspace(Box bBox, glm::vec3 normal, float originOffset) {
|
||||
normal = glm::normalize(normal);
|
||||
Manifold cutter =
|
||||
Manifold::Cube(glm::vec3(2.0f), true).Translate({1.0f, 0.0f, 0.0f});
|
||||
float size = glm::length(bBox.Center() - normal * originOffset) +
|
||||
0.5f * glm::length(bBox.Size());
|
||||
cutter.Scale(glm::vec3(size)).Translate({originOffset, 0.0f, 0.0f});
|
||||
float yDeg = glm::degrees(-glm::asin(normal.z));
|
||||
float zDeg = glm::degrees(glm::atan(normal.y, normal.x));
|
||||
return cutter.Rotate(0.0f, yDeg, zDeg);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
Manifold::Manifold() : pImpl_{std::make_unique<Impl>()} {}
|
||||
Manifold::Manifold(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: pImpl_{std::make_unique<Impl>(mesh, triProperties, properties,
|
||||
propertyTolerance)} {}
|
||||
Manifold::~Manifold() = default;
|
||||
Manifold::Manifold(Manifold&&) noexcept = default;
|
||||
Manifold& Manifold::operator=(Manifold&&) noexcept = default;
|
||||
|
||||
Manifold::Manifold(const Manifold& other) : pImpl_(new Impl(*other.pImpl_)) {
|
||||
pImpl_->DuplicateMeshIDs();
|
||||
}
|
||||
|
||||
Manifold& Manifold::operator=(const Manifold& other) {
|
||||
if (this != &other) {
|
||||
pImpl_.reset(new Impl(*other.pImpl_));
|
||||
pImpl_->DuplicateMeshIDs();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable for
|
||||
* saving or other operations outside of the context of this library.
|
||||
*/
|
||||
Mesh Manifold::GetMesh() const {
|
||||
pImpl_->ApplyTransform();
|
||||
|
||||
Mesh result;
|
||||
result.vertPos.insert(result.vertPos.end(), pImpl_->vertPos_.begin(),
|
||||
pImpl_->vertPos_.end());
|
||||
result.vertNormal.insert(result.vertNormal.end(), pImpl_->vertNormal_.begin(),
|
||||
pImpl_->vertNormal_.end());
|
||||
result.halfedgeTangent.insert(result.halfedgeTangent.end(),
|
||||
pImpl_->halfedgeTangent_.begin(),
|
||||
pImpl_->halfedgeTangent_.end());
|
||||
|
||||
result.triVerts.resize(NumTri());
|
||||
thrust::for_each_n(zip(result.triVerts.begin(), countAt(0)), NumTri(),
|
||||
MakeTri({pImpl_->halfedge_.cptrH()}));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, circularSegements must be specified.
|
||||
*/
|
||||
int Manifold::circularSegments_ = 0;
|
||||
float Manifold::circularAngle_ = 10.0f;
|
||||
float Manifold::circularEdgeLength_ = 1.0f;
|
||||
|
||||
void Manifold::SetMinCircularAngle(float angle) {
|
||||
ALWAYS_ASSERT(angle > 0.0f, userErr, "angle must be positive!");
|
||||
Manifold::circularAngle_ = angle;
|
||||
}
|
||||
|
||||
void Manifold::SetMinCircularEdgeLength(float length) {
|
||||
ALWAYS_ASSERT(length > 0.0f, userErr, "length must be positive!");
|
||||
Manifold::circularEdgeLength_ = length;
|
||||
}
|
||||
|
||||
void Manifold::SetCircularSegments(int number) {
|
||||
ALWAYS_ASSERT(number > 2 || number == 0, userErr,
|
||||
"must have at least three segments in circle!");
|
||||
Manifold::circularSegments_ = number;
|
||||
}
|
||||
|
||||
int Manifold::GetCircularSegments(float radius) {
|
||||
if (Manifold::circularSegments_ > 0) return Manifold::circularSegments_;
|
||||
int nSegA = 360.0f / Manifold::circularAngle_;
|
||||
int nSegL = 2.0f * radius * glm::pi<float>() / Manifold::circularEdgeLength_;
|
||||
int nSeg = min(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return nSeg;
|
||||
}
|
||||
|
||||
bool Manifold::IsEmpty() const { return pImpl_->IsEmpty(); }
|
||||
int Manifold::NumVert() const { return pImpl_->NumVert(); }
|
||||
int Manifold::NumEdge() const { return pImpl_->NumEdge(); }
|
||||
int Manifold::NumTri() const { return pImpl_->NumTri(); }
|
||||
|
||||
Box Manifold::BoundingBox() const {
|
||||
return pImpl_->bBox_.Transform(pImpl_->transform_);
|
||||
}
|
||||
|
||||
float Manifold::Precision() const {
|
||||
pImpl_->ApplyTransform();
|
||||
return pImpl_->precision_;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
int Manifold::Genus() const {
|
||||
int chi = NumVert() - NumEdge() + NumTri();
|
||||
return 1 - chi / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold in a Properties
|
||||
* structure. These properties are clamped to zero for a given face if they are
|
||||
* within rounding tolerance. This means degenerate manifolds can by identified
|
||||
* by testing these properties as == 0.
|
||||
*/
|
||||
Properties Manifold::GetProperties() const { return pImpl_->GetProperties(); }
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Curvature Manifold::GetCurvature() const { return pImpl_->GetCurvature(); }
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous mesh, for the purpose of assinging
|
||||
* properties like texture coordinates. The triBary vector is the same length as
|
||||
* Mesh.triVerts and .meshID indicates the mesh instance and .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.
|
||||
*
|
||||
* Every time a manifold is copied or combined to form a new manifold it gets a
|
||||
* new meshID to indicate that particular instance of the mesh. In order to look
|
||||
* up which input mesh a given instance came from, simply use the
|
||||
* MeshID2Original static vector.
|
||||
*/
|
||||
MeshRelation Manifold::GetMeshRelation() const {
|
||||
MeshRelation out;
|
||||
const auto& relation = pImpl_->meshRelation_;
|
||||
out.triBary.insert(out.triBary.end(), relation.triBary.begin(),
|
||||
relation.triBary.end());
|
||||
out.barycentric.insert(out.barycentric.end(), relation.barycentric.begin(),
|
||||
relation.barycentric.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a vector of unique meshIDs that are referenced by this manifold's
|
||||
* meshRelation. If this manifold has been newly constructed then there will
|
||||
* only be a single meshID, which can be associated with the input mesh for
|
||||
* future reference.
|
||||
*/
|
||||
std::vector<int> Manifold::GetMeshIDs() const {
|
||||
VecDH<int> meshIDs(NumTri());
|
||||
thrust::for_each_n(
|
||||
zip(meshIDs.beginD(), pImpl_->meshRelation_.triBary.beginD()), NumTri(),
|
||||
GetMeshID());
|
||||
|
||||
thrust::sort(meshIDs.beginD(), meshIDs.endD());
|
||||
int n = thrust::unique(meshIDs.beginD(), meshIDs.endD()) - meshIDs.beginD();
|
||||
meshIDs.resize(n);
|
||||
|
||||
std::vector<int> out;
|
||||
out.insert(out.end(), meshIDs.begin(), meshIDs.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 meshID as an original,
|
||||
* meaning it will now be referenced by its descendents instead of the meshes it
|
||||
* was built from, allowing you to differentiate the copies when applying your
|
||||
* properties to the final result. Its new meshID is returned.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, allowing
|
||||
* these edges to be collapsed. If you plan 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.
|
||||
*/
|
||||
int Manifold::SetAsOriginal() {
|
||||
int meshID = pImpl_->InitializeNewReference();
|
||||
return meshID;
|
||||
}
|
||||
|
||||
std::vector<int> Manifold::MeshID2Original() {
|
||||
return Manifold::Impl::meshID2Original_;
|
||||
}
|
||||
|
||||
bool Manifold::IsManifold() const { return pImpl_->IsManifold(); }
|
||||
|
||||
bool Manifold::MatchesTriNormals() const { return pImpl_->MatchesTriNormals(); }
|
||||
|
||||
int Manifold::NumDegenerateTris() const { return pImpl_->NumDegenerateTris(); }
|
||||
|
||||
Manifold& Manifold::Translate(glm::vec3 v) {
|
||||
pImpl_->transform_[3] += v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Manifold& Manifold::Scale(glm::vec3 v) {
|
||||
glm::mat3 s(1.0f);
|
||||
for (int i : {0, 1, 2}) s[i] *= v;
|
||||
pImpl_->transform_ = s * pImpl_->transform_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applys 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 elimiate it completely for any multiples of 90 degrees. Addtionally, more
|
||||
* efficient code paths are used to update the manifold when the transforms only
|
||||
* rotate by multiples of 90 degrees.
|
||||
*/
|
||||
Manifold& Manifold::Rotate(float xDegrees, float yDegrees, float zDegrees) {
|
||||
glm::mat3 rX(1.0f, 0.0f, 0.0f, //
|
||||
0.0f, cosd(xDegrees), sind(xDegrees), //
|
||||
0.0f, -sind(xDegrees), cosd(xDegrees));
|
||||
glm::mat3 rY(cosd(yDegrees), 0.0f, -sind(yDegrees), //
|
||||
0.0f, 1.0f, 0.0f, //
|
||||
sind(yDegrees), 0.0f, cosd(yDegrees));
|
||||
glm::mat3 rZ(cosd(zDegrees), sind(zDegrees), 0.0f, //
|
||||
-sind(zDegrees), cosd(zDegrees), 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
pImpl_->transform_ = rZ * rY * rX * pImpl_->transform_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Manifold& Manifold::Transform(const glm::mat4x3& m) {
|
||||
glm::mat4 old(pImpl_->transform_);
|
||||
pImpl_->transform_ = m * old;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 with is not, but
|
||||
* that is not checked here, so it is up to the user to choose their function
|
||||
* with discretion.
|
||||
*/
|
||||
Manifold& Manifold::Warp(std::function<void(glm::vec3&)> warpFunc) {
|
||||
pImpl_->ApplyTransform();
|
||||
thrust::for_each_n(pImpl_->vertPos_.begin(), NumVert(), warpFunc);
|
||||
pImpl_->Update();
|
||||
pImpl_->faceNormal_.resize(0); // force recalculation of triNormal
|
||||
pImpl_->CalculateNormals();
|
||||
pImpl_->SetPrecision();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Manifold& Manifold::Refine(int n) {
|
||||
pImpl_->Refine(n);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a checksum-style verification of the collider, simply returning the
|
||||
* total number of edge-face bounding box overlaps between this and other.
|
||||
*/
|
||||
int Manifold::NumOverlaps(const Manifold& other) const {
|
||||
pImpl_->ApplyTransform();
|
||||
other.pImpl_->ApplyTransform();
|
||||
|
||||
SparseIndices overlaps = pImpl_->EdgeCollisions(*other.pImpl_);
|
||||
int num_overlaps = overlaps.size();
|
||||
|
||||
overlaps = other.pImpl_->EdgeCollisions(*pImpl_);
|
||||
return num_overlaps += overlaps.size();
|
||||
}
|
||||
|
||||
Manifold Manifold::Boolean(const Manifold& second, OpType op) const {
|
||||
pImpl_->ApplyTransform();
|
||||
second.pImpl_->ApplyTransform();
|
||||
Boolean3 boolean(*pImpl_, *second.pImpl_, op);
|
||||
Manifold result;
|
||||
result.pImpl_ = std::make_unique<Impl>(boolean.Result(op));
|
||||
return result;
|
||||
}
|
||||
|
||||
Manifold Manifold::operator+(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::ADD);
|
||||
}
|
||||
|
||||
Manifold& Manifold::operator+=(const Manifold& Q) {
|
||||
*this = *this + Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Manifold Manifold::operator-(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::SUBTRACT);
|
||||
}
|
||||
|
||||
Manifold& Manifold::operator-=(const Manifold& Q) {
|
||||
*this = *this - Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Manifold Manifold::operator^(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::INTERSECT);
|
||||
}
|
||||
|
||||
Manifold& Manifold::operator^=(const Manifold& Q) {
|
||||
*this = *this ^ Q;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split cuts this manifold in two using the input manifold. The first result is
|
||||
* the intersection, second is the difference. This is more efficient than doing
|
||||
* them separately.
|
||||
*/
|
||||
std::pair<Manifold, Manifold> Manifold::Split(const Manifold& cutter) const {
|
||||
pImpl_->ApplyTransform();
|
||||
cutter.pImpl_->ApplyTransform();
|
||||
Boolean3 boolean(*pImpl_, *cutter.pImpl_, OpType::SUBTRACT);
|
||||
std::pair<Manifold, Manifold> result;
|
||||
result.first.pImpl_ =
|
||||
std::make_unique<Impl>(boolean.Result(OpType::INTERSECT));
|
||||
result.second.pImpl_ =
|
||||
std::make_unique<Impl>(boolean.Result(OpType::SUBTRACT));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convient version of Split for a half-space. The first result is in the
|
||||
* direction of the normal, second is opposite. Origin offset is the distance of
|
||||
* the plane from the origin in the direction of the normal vector. The length
|
||||
* of the normal is not important, as it is normalized internally.
|
||||
*/
|
||||
std::pair<Manifold, Manifold> Manifold::SplitByPlane(glm::vec3 normal,
|
||||
float originOffset) const {
|
||||
return Split(Halfspace(BoundingBox(), normal, originOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Identical to SplitbyPlane, but calculating and returning only the first
|
||||
* result.
|
||||
*/
|
||||
Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const {
|
||||
pImpl_->ApplyTransform();
|
||||
return *this ^ Halfspace(BoundingBox(), normal, originOffset);
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -1,15 +0,0 @@
|
||||
project (meshIO)
|
||||
|
||||
add_library(${PROJECT_NAME} src/meshIO.cpp)
|
||||
|
||||
target_include_directories( ${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE assimp
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
@@ -1,13 +0,0 @@
|
||||
project (polygon)
|
||||
|
||||
add_library(${PROJECT_NAME} src/polygon.cpp)
|
||||
|
||||
target_include_directories( ${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
//
|
||||
// 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 <functional>
|
||||
|
||||
#include "structs.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
std::vector<glm::ivec3> Triangulate(const Polygons &polys,
|
||||
float precision = -1);
|
||||
|
||||
std::vector<Halfedge> Polygons2Edges(const Polygons &polys);
|
||||
std::vector<Halfedge> Triangles2Edges(const std::vector<glm::ivec3> &triangles);
|
||||
void CheckTopology(const std::vector<Halfedge> &halfedges);
|
||||
void CheckTopology(const std::vector<glm::ivec3> &triangles,
|
||||
const Polygons &polys);
|
||||
void CheckGeometry(const std::vector<glm::ivec3> &triangles,
|
||||
const Polygons &polys, float precision);
|
||||
void Dump(const Polygons &polys);
|
||||
ExecutionParams &PolygonParams();
|
||||
|
||||
} // namespace manifold
|
||||
@@ -1,14 +1,51 @@
|
||||
project (samples)
|
||||
# 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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
add_library(${PROJECT_NAME} src/knot.cpp src/bracelet.cpp src/mengerSponge.cpp src/roundedFrame.cpp src/tet_puzzle.cpp src/scallop.cpp)
|
||||
project(samples)
|
||||
|
||||
target_include_directories( ${PROJECT_NAME}
|
||||
add_library(samples src/bracelet.cpp src/knot.cpp src/menger_sponge.cpp src/rounded_frame.cpp src/scallop.cpp src/tet_puzzle.cpp)
|
||||
|
||||
target_include_directories(samples
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries( ${PROJECT_NAME}
|
||||
target_link_libraries(samples
|
||||
PUBLIC manifold
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samples PUBLIC cxx_std_14)
|
||||
|
||||
project(samplesGPU)
|
||||
|
||||
add_library(samplesGPU src/gyroid_module.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(src/gyroid_module.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET samplesGPU PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_include_directories(samplesGPU
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(samplesGPU
|
||||
PUBLIC manifold sdf
|
||||
)
|
||||
|
||||
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samplesGPU
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,51 +15,41 @@
|
||||
#pragma once
|
||||
#include "manifold.h"
|
||||
|
||||
// These are mostly 3D-printable designs I've invented over the years,
|
||||
// translated from their original OpenSCAD to C++ to demonstrate the usage of
|
||||
// this library. You can find the originals here:
|
||||
// http://www.thingiverse.com/emmett
|
||||
// These also each have tests you can find in test/samples_test.cpp, which have
|
||||
// nice parameter choices for making some of the specific designs I print. While
|
||||
// the source code is under the Apache License above, I license all of my
|
||||
// designs (the output of those tests if you uncomment the export lines) under
|
||||
// CC-BY-SA: https://creativecommons.org/licenses/by-sa/2.0/, which means you're
|
||||
// welcome to print and sell them, so long as you attribute the design to
|
||||
// Emmett Lalish and share any derivative works under the same license.
|
||||
|
||||
namespace manifold {
|
||||
|
||||
// p is the number of times the thread passes through the donut hole and q is
|
||||
// the number of times the thread circles the 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 on a manifold, thus avoiding any
|
||||
// handling of triangles. It also demonstrates copying meshes, since that is not
|
||||
// automatic.
|
||||
/** @defgroup Samples
|
||||
* @brief Examples of usage and interesting designs
|
||||
*
|
||||
* These are mostly 3D-printable designs I've invented over the years,
|
||||
* translated from their original OpenSCAD to C++ to demonstrate the usage of
|
||||
* this library. You can find the originals here:
|
||||
* http://www.thingiverse.com/emmett These also each have tests you can find in
|
||||
* test/samples_test.cpp, which have nice parameter choices for making some of
|
||||
* the specific designs I print. While the source code is under the Apache
|
||||
* License above, I license all of my designs (the output of those tests if you
|
||||
* uncomment the export lines) under CC-BY-SA:
|
||||
* https://creativecommons.org/licenses/by-sa/2.0/, which means you're welcome
|
||||
* to print and sell them, so long as you attribute the design to Emmett Lalish
|
||||
* and share any derivative works under the same license.
|
||||
* @{
|
||||
*/
|
||||
|
||||
Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
|
||||
float threadRadius, int circularSegments = 0,
|
||||
int linearSegments = 0);
|
||||
|
||||
// The overall size is given by radius; the radius left for your wrist is
|
||||
// roughly radius - height. Its length along your arm (the height of the print)
|
||||
// is given by width. The thickness parameter is the width of the material,
|
||||
// which should be equal to your printer's nozzle diameter. The number of twisty
|
||||
// shapes around the outside is nDecor, while the number of cuts that enable
|
||||
// stretching is nCut. The resolution is given by nDivision, the number of
|
||||
// divisions along the width.
|
||||
Manifold StretchyBracelet(float radius = 30.0f, float height = 8.0f,
|
||||
float width = 15.0f, float thickness = 0.4f,
|
||||
int nDecor = 20, int nCut = 27, int nDivision = 30);
|
||||
|
||||
// The classic cubic fractal, going down recursively n times.
|
||||
Manifold MengerSponge(int n = 3);
|
||||
|
||||
// A cubic frame with cylinders for edges and spheres at the corners.
|
||||
Manifold RoundedFrame(float edgeLength, float radius);
|
||||
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments = 0);
|
||||
|
||||
// A tetrahedron cut into two halves that can screw together.
|
||||
Manifold TetPuzzle(float edgeLength, float gap, int nDivisions);
|
||||
|
||||
// A smoothed manifold demonstrating selective edge sharpening. Refine before
|
||||
// export to see the curvature.
|
||||
Manifold Scallop();
|
||||
|
||||
Manifold GyroidModule(float size = 20, int n = 20);
|
||||
/** @} */ // end of Samples
|
||||
} // namespace manifold
|
||||
|
After Width: | Height: | Size: 760 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 422 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 598 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 663 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 49 KiB |
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -31,12 +31,12 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
decorRadius * sind(dPhiDeg * i)),
|
||||
0});
|
||||
}
|
||||
Manifold decor = std::move(Manifold::Extrude(circle, width, nDivision, 180)
|
||||
.Scale({1.0f, 0.5f, 1.0f})
|
||||
.Translate({0.0f, radius, 0.0f}));
|
||||
Manifold decor = Manifold::Extrude(circle, width, nDivision, 180)
|
||||
.Scale({1.0f, 0.5f, 1.0f})
|
||||
.Translate({0.0f, radius, 0.0f});
|
||||
|
||||
for (int i = 0; i < nDecor; ++i) {
|
||||
base += decor.Rotate(0, 0, 360.0f / nDecor);
|
||||
base += decor.Rotate(0, 0, (360.0f / nDecor) * i);
|
||||
}
|
||||
|
||||
Polygons stretch(1);
|
||||
@@ -52,7 +52,7 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
}
|
||||
|
||||
base = Manifold::Extrude(stretch, width) ^ base;
|
||||
base.SetAsOriginal();
|
||||
base = base.AsOriginal();
|
||||
|
||||
return base;
|
||||
}
|
||||
@@ -60,6 +60,22 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* My Stretchy Bracelet: this is one of my most popular designs, largely because
|
||||
* it's quick and easy to 3D print. The defaults are picked to work well; change
|
||||
* the radius to fit your wrist. Changing the other values too much may break
|
||||
* the design.
|
||||
*
|
||||
* @param radius The overall size; the radius left for your wrist is roughly
|
||||
* radius - height.
|
||||
* @param height Thickness of the bracelet around your wrist.
|
||||
* @param width The length along your arm (the height of the print).
|
||||
* @param thickness The width of the material, which should be equal to your
|
||||
* printer's nozzle diameter.
|
||||
* @param nDecor The number of twisty shapes around the outside.
|
||||
* @param nCut The number of cuts that enable stretching.
|
||||
* @param nDivision the number of divisions along the width.
|
||||
*/
|
||||
Manifold StretchyBracelet(float radius, float height, float width,
|
||||
float thickness, int nDecor, int nCut,
|
||||
int nDivision) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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 "samples.h"
|
||||
#include "sdf.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct Gyroid {
|
||||
__host__ __device__ float operator()(glm::vec3 p) const {
|
||||
p -= glm::pi<float>() / 4;
|
||||
return cos(p.x) * sin(p.y) + cos(p.y) * sin(p.z) + cos(p.z) * sin(p.x);
|
||||
}
|
||||
};
|
||||
|
||||
Manifold RhombicDodecahedron(float size) {
|
||||
Manifold box =
|
||||
Manifold::Cube(size * glm::sqrt(2.0f) * glm::vec3(1, 1, 2), true);
|
||||
Manifold result = box.Rotate(90, 45) ^ box.Rotate(90, 45, 90);
|
||||
return result ^ box.Rotate(0, 0, 45);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Creates a rhombic dodecahedral module of a gyroid manifold, which can be
|
||||
* assembled together to tile space continuously. This one is designed to be
|
||||
* 3D-printable, as it is oriented with minimal overhangs. This sample
|
||||
* demonstrates the use of a Signed Distance Function (SDF) to create smooth,
|
||||
* complex manifolds.
|
||||
*/
|
||||
Manifold GyroidModule(float size, int n) {
|
||||
auto gyroid = [&](float level) {
|
||||
const float period = glm::two_pi<float>();
|
||||
return Manifold(LevelSet(Gyroid(), {glm::vec3(-period), glm::vec3(period)},
|
||||
period / n, level))
|
||||
.Scale(glm::vec3(size / period));
|
||||
};
|
||||
|
||||
Manifold result = (RhombicDodecahedron(size) ^ gyroid(-0.4)) - gyroid(0.4);
|
||||
|
||||
return result.Rotate(-45, 0, 90).Translate({0, 0, size / glm::sqrt(2.0f)});
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,8 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <glm/gtx/rotate_vector.hpp>
|
||||
|
||||
#include "samples.h"
|
||||
|
||||
namespace {
|
||||
@@ -23,6 +21,22 @@ int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* 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 Manifold.Warp() method, thus avoiding any 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 Manifold.GetCircularSegments().
|
||||
* @param linearSegments Number of segments along the length of the knot.
|
||||
* Default makes roughly square facets.
|
||||
*/
|
||||
Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
|
||||
float threadRadius, int circularSegments,
|
||||
int linearSegments) {
|
||||
@@ -42,31 +56,32 @@ Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
|
||||
|
||||
Manifold knot = Manifold::Revolve(circle, m);
|
||||
|
||||
knot.Warp([p, q, majorRadius, minorRadius, threadRadius](glm::vec3& v) {
|
||||
float psi = q * atan2(v.x, v.y);
|
||||
float theta = psi * p / q;
|
||||
glm::vec2 xy = glm::vec2(v);
|
||||
float x1 = sqrt(glm::dot(xy, xy));
|
||||
float phi = atan2(x1 - 2, v.z);
|
||||
v = glm::vec3(cos(phi), 0.0f, sin(phi));
|
||||
v *= threadRadius;
|
||||
float r = majorRadius + minorRadius * cos(theta);
|
||||
v = glm::rotateX(v, -float(atan2(p * minorRadius, q * r)));
|
||||
v.x += minorRadius;
|
||||
v = glm::rotateY(v, theta);
|
||||
v.x += majorRadius;
|
||||
v = glm::rotateZ(v, psi);
|
||||
});
|
||||
knot =
|
||||
knot.Warp([p, q, majorRadius, minorRadius, threadRadius](glm::vec3& v) {
|
||||
float psi = q * atan2(v.x, v.y);
|
||||
float theta = psi * p / q;
|
||||
glm::vec2 xy = glm::vec2(v);
|
||||
float x1 = sqrt(glm::dot(xy, xy));
|
||||
float phi = atan2(x1 - 2, v.z);
|
||||
v = glm::vec3(cos(phi), 0.0f, sin(phi));
|
||||
v *= threadRadius;
|
||||
float r = majorRadius + minorRadius * cos(theta);
|
||||
v = glm::rotateX(v, -float(atan2(p * minorRadius, q * r)));
|
||||
v.x += minorRadius;
|
||||
v = glm::rotateY(v, theta);
|
||||
v.x += majorRadius;
|
||||
v = glm::rotateZ(v, psi);
|
||||
});
|
||||
|
||||
if (kLoops > 1) {
|
||||
std::vector<Manifold> knots;
|
||||
for (float k = 0; k < kLoops; ++k) {
|
||||
knots.emplace_back(knot);
|
||||
knots.back().Rotate(0, 0, 360.0f * (k / kLoops) * (q / float(p)));
|
||||
knots.push_back(
|
||||
knot.Rotate(0, 0, 360.0f * (k / kLoops) * (q / float(p))));
|
||||
}
|
||||
knot = Manifold::Compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -21,8 +21,8 @@ using namespace manifold;
|
||||
void Fractal(std::vector<Manifold>& holes, Manifold& hole, float w,
|
||||
glm::vec2 position, int depth, int maxDepth) {
|
||||
w /= 3;
|
||||
holes.emplace_back(hole);
|
||||
holes.back().Scale({w, w, 1.0f}).Translate(glm::vec3(position, 0.0f));
|
||||
holes.push_back(
|
||||
hole.Scale({w, w, 1.0f}).Translate(glm::vec3(position, 0.0f)));
|
||||
if (depth == maxDepth) return;
|
||||
|
||||
glm::vec2 offsets[8] = {{-w, -w}, {-w, 0.0f}, {-w, w}, {0.0f, w},
|
||||
@@ -35,6 +35,12 @@ void Fractal(std::vector<Manifold>& holes, Manifold& hole, float w,
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* The classic cubic fractal.
|
||||
*
|
||||
* @param n Fractal depth. Warning: scales exponentially, n = 4 has almost
|
||||
* 400,000 triangles!
|
||||
*/
|
||||
Manifold MengerSponge(int n) {
|
||||
Manifold result = Manifold::Cube(glm::vec3(1.0f), true);
|
||||
|
||||
@@ -44,9 +50,16 @@ Manifold MengerSponge(int n) {
|
||||
Manifold hole = Manifold::Compose(holes);
|
||||
|
||||
result -= hole;
|
||||
result -= hole.Rotate(90);
|
||||
result -= hole.Rotate(0, 0, 90);
|
||||
result.SetAsOriginal();
|
||||
hole = hole.Rotate(90);
|
||||
result -= hole;
|
||||
hole = hole.Rotate(0, 0, 90);
|
||||
result -= hole;
|
||||
|
||||
// Alternative order causes degenerate triangles
|
||||
// Manifold tmp1 = hole.Rotate(90) + hole.Rotate(90).Rotate(0, 0, 90);
|
||||
// Manifold tmp2 = hole + tmp1;
|
||||
// result = result - tmp2;
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
//
|
||||
// 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 "samples.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
Manifold RoundedFrame(float edgeLength, float radius) {
|
||||
Manifold edge = Manifold::Cylinder(edgeLength, radius);
|
||||
Manifold corner = Manifold::Sphere(radius);
|
||||
|
||||
Manifold edge1 = corner + edge;
|
||||
edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0});
|
||||
|
||||
Manifold edge2 = edge1;
|
||||
edge2.Rotate(0, 0, 180);
|
||||
edge2 += edge1;
|
||||
edge2 += edge.Translate({-edgeLength / 2, -edgeLength / 2, 0});
|
||||
|
||||
Manifold edge4 = edge2;
|
||||
edge4.Rotate(0, 0, 90);
|
||||
edge4 += edge2;
|
||||
|
||||
Manifold frame = edge4.Translate({0, 0, -edgeLength / 2});
|
||||
frame += edge4.Rotate(180);
|
||||
|
||||
return frame;
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "samples.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* A cubic frame with cylinders for edges and spheres at the corners.
|
||||
* Demonstrates how at 90-degree intersections, the sphere and cylinder facets
|
||||
* match up perfectly.
|
||||
*
|
||||
* @param edgeLength Distance between the corners.
|
||||
* @param radius Radius of the frame members.
|
||||
* @param circularSegments Number of segments in the cylinders and spheres.
|
||||
* Defaults to Manifold.GetCircularSegments().
|
||||
*/
|
||||
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments) {
|
||||
Manifold edge = Manifold::Cylinder(edgeLength, radius, -1, circularSegments);
|
||||
Manifold corner = Manifold::Sphere(radius, circularSegments);
|
||||
|
||||
Manifold edge1 = corner + edge;
|
||||
edge1 = edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0});
|
||||
|
||||
Manifold edge2 = edge1.Rotate(0, 0, 180);
|
||||
edge2 += edge1;
|
||||
edge2 += edge.Translate({-edgeLength / 2, -edgeLength / 2, 0});
|
||||
|
||||
Manifold edge4 = edge2.Rotate(0, 0, 90);
|
||||
edge4 += edge2;
|
||||
|
||||
Manifold frame = edge4.Translate({0, 0, -edgeLength / 2});
|
||||
frame += frame.Rotate(180);
|
||||
|
||||
return frame;
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* A smoothed manifold demonstrating selective edge sharpening with
|
||||
* Manifold.Smooth(). Use Manifold.Refine() before export to see the curvature.
|
||||
*/
|
||||
Manifold Scallop() {
|
||||
constexpr float height = 1;
|
||||
constexpr float radius = 3;
|
||||
@@ -30,7 +34,7 @@ Manifold Scallop() {
|
||||
const float delta = glm::pi<float>() / wiggles;
|
||||
for (int i = 0; i < 2 * wiggles; ++i) {
|
||||
float theta = (i - wiggles) * delta;
|
||||
float amp = 0.5 * height * glm::cos(theta / 2);
|
||||
float amp = 0.5 * height * glm::max(glm::cos(0.8f * theta), 0.0f);
|
||||
|
||||
scallop.vertPos.push_back({radius * glm::cos(theta),
|
||||
radius * glm::sin(theta),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,15 @@
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param edgeLength Length of each edge of the overall tetrahedron.
|
||||
* @param gap Spacing between the two halves to allow sliding.
|
||||
* @param nDivisions Number of divisions (both ways) in the screw surface.
|
||||
*/
|
||||
Manifold TetPuzzle(float edgeLength, float gap, int nDivisions) {
|
||||
const glm::vec3 scale(edgeLength / (2 * sqrt(2)));
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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.
|
||||
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(utilities)
|
||||
add_subdirectory(collider)
|
||||
add_subdirectory(polygon)
|
||||
add_subdirectory(manifold)
|
||||
add_subdirectory(sdf)
|
||||
@@ -0,0 +1,32 @@
|
||||
# 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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project (collider)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -13,12 +13,13 @@
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "sparse.cuh"
|
||||
#include "structs.h"
|
||||
#include "vec_dh.cuh"
|
||||
#include "public.h"
|
||||
#include "sparse.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
class Collider {
|
||||
public:
|
||||
Collider() {}
|
||||
@@ -26,10 +27,10 @@ class Collider {
|
||||
// Aborts and returns false if transform is not axis aligned.
|
||||
bool Transform(glm::mat4x3);
|
||||
void UpdateBoxes(const VecDH<Box>& leafBB);
|
||||
// Collisions returns a sparse result, where i is the querry index and j is
|
||||
// Collisions returns a sparse result, where i is the query index and j is
|
||||
// the leaf index where their bounding boxes overlap.
|
||||
template <typename T>
|
||||
SparseIndices Collisions(const VecDH<T>& querriesIn) const;
|
||||
SparseIndices Collisions(const VecDH<T>& queriesIn) const;
|
||||
|
||||
private:
|
||||
VecDH<Box> nodeBBox_;
|
||||
@@ -41,4 +42,4 @@ class Collider {
|
||||
int NumLeaves() const { return NumInternal() + 1; };
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,8 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "collider.cuh"
|
||||
#include "utils.cuh"
|
||||
#include "collider.h"
|
||||
|
||||
#include "par.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
// Adjustable parameters
|
||||
constexpr int kInitialLength = 128;
|
||||
@@ -21,6 +27,35 @@ constexpr int kLengthMultiple = 4;
|
||||
// Fundamental constants
|
||||
constexpr int kRoot = 1;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
#ifndef _WINDEF_
|
||||
typedef unsigned long DWORD;
|
||||
#endif
|
||||
|
||||
uint32_t __inline ctz(uint32_t value) {
|
||||
DWORD trailing_zero = 0;
|
||||
|
||||
if (_BitScanForward(&trailing_zero, value)) {
|
||||
return trailing_zero;
|
||||
} else {
|
||||
// This is undefined, I better choose 32 than 0
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t __inline clz(uint32_t value) {
|
||||
DWORD leading_zero = 0;
|
||||
|
||||
if (_BitScanReverse(&leading_zero, value)) {
|
||||
return 31 - leading_zero;
|
||||
} else {
|
||||
// Same remarks as above
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
@@ -34,7 +69,7 @@ __host__ __device__ int Leaf2Node(int leaf) { return leaf * 2; }
|
||||
struct CreateRadixTree {
|
||||
int* nodeParent_;
|
||||
thrust::pair<int, int>* internalChildren_;
|
||||
const VecD<uint32_t> leafMorton_;
|
||||
const VecDc<uint32_t> leafMorton_;
|
||||
|
||||
__host__ __device__ int PrefixLength(uint32_t a, uint32_t b) const {
|
||||
// count-leading-zeros is used to find the number of identical highest-order
|
||||
@@ -42,7 +77,14 @@ struct CreateRadixTree {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return __clz(a ^ b);
|
||||
#else
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// return __lzcnt(a ^ b);
|
||||
return clz(a ^ b);
|
||||
#else
|
||||
return __builtin_clz(a ^ b);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -115,26 +157,28 @@ struct CreateRadixTree {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
template <typename T, const bool allocateOnly>
|
||||
struct FindCollisions {
|
||||
thrust::pair<int*, int*> querryTri_;
|
||||
int* numOverlaps_;
|
||||
const int maxOverlaps_;
|
||||
thrust::pair<int*, int*> queryTri_;
|
||||
int* counts;
|
||||
const Box* nodeBBox_;
|
||||
const thrust::pair<int, int>* internalChildren_;
|
||||
|
||||
__host__ __device__ int RecordCollision(int node,
|
||||
const thrust::tuple<T, int>& query) {
|
||||
thrust::tuple<T, int>& query) {
|
||||
const T& queryObj = thrust::get<0>(query);
|
||||
const int queryIdx = thrust::get<1>(query);
|
||||
int& count = counts[queryIdx];
|
||||
|
||||
bool overlaps = nodeBBox_[node].DoesOverlap(queryObj);
|
||||
if (overlaps && IsLeaf(node)) {
|
||||
int pos = AtomicAdd(*numOverlaps_, 1);
|
||||
if (pos >= maxOverlaps_)
|
||||
return -1; // Didn't allocate enough memory; bail out
|
||||
querryTri_.first[pos] = queryIdx;
|
||||
querryTri_.second[pos] = Node2Leaf(node);
|
||||
if (allocateOnly) {
|
||||
count++;
|
||||
} else {
|
||||
int pos = count++;
|
||||
queryTri_.first[pos] = queryIdx;
|
||||
queryTri_.second[pos] = Node2Leaf(node);
|
||||
}
|
||||
}
|
||||
return overlaps && IsInternal(node); // Should traverse into node
|
||||
}
|
||||
@@ -146,15 +190,16 @@ struct FindCollisions {
|
||||
int top = -1;
|
||||
// Depth-first search
|
||||
int node = kRoot;
|
||||
const int queryIdx = thrust::get<1>(query);
|
||||
// same implies that this query do not have any collision
|
||||
if (!allocateOnly && counts[queryIdx] == counts[queryIdx + 1]) return;
|
||||
while (1) {
|
||||
int internal = Node2Internal(node);
|
||||
int child1 = internalChildren_[internal].first;
|
||||
int child2 = internalChildren_[internal].second;
|
||||
|
||||
int traverse1 = RecordCollision(child1, query);
|
||||
if (traverse1 < 0) return;
|
||||
int traverse2 = RecordCollision(child2, query);
|
||||
if (traverse2 < 0) return;
|
||||
|
||||
if (!traverse1 && !traverse2) {
|
||||
if (top < 0) break; // done
|
||||
@@ -204,51 +249,47 @@ namespace manifold {
|
||||
*/
|
||||
Collider::Collider(const VecDH<Box>& leafBB,
|
||||
const VecDH<uint32_t>& leafMorton) {
|
||||
ALWAYS_ASSERT(leafBB.size() == leafMorton.size(), userErr,
|
||||
"vectors must be the same length");
|
||||
ASSERT(leafBB.size() == leafMorton.size(), userErr,
|
||||
"vectors must be the same length");
|
||||
int num_nodes = 2 * leafBB.size() - 1;
|
||||
// assign and allocate members
|
||||
nodeBBox_.resize(num_nodes);
|
||||
nodeParent_.resize(num_nodes, -1);
|
||||
internalChildren_.resize(leafBB.size() - 1, thrust::make_pair(-1, -1));
|
||||
// organize tree
|
||||
thrust::for_each_n(countAt(0), NumInternal(),
|
||||
CreateRadixTree({nodeParent_.ptrD(),
|
||||
internalChildren_.ptrD(), leafMorton}));
|
||||
for_each_n(autoPolicy(NumInternal()), countAt(0), NumInternal(),
|
||||
CreateRadixTree(
|
||||
{nodeParent_.ptrD(), internalChildren_.ptrD(), leafMorton}));
|
||||
UpdateBoxes(leafBB);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a vector of querry objects, this returns a sparse array of overlaps
|
||||
* between the querries and the bounding boxes of the collider. Querries are
|
||||
* For a vector of query objects, this returns a sparse array of overlaps
|
||||
* between the queries and the bounding boxes of the collider. Queries are
|
||||
* normally axis-aligned bounding boxes. Points can also be used, and this case
|
||||
* overlaps are defined as lying in the XY projection of the bounding box.
|
||||
*/
|
||||
template <typename T>
|
||||
SparseIndices Collider::Collisions(const VecDH<T>& querriesIn) const {
|
||||
int maxOverlaps = 1 << 20;
|
||||
SparseIndices querryTri(maxOverlaps);
|
||||
int nOverlaps = 0;
|
||||
while (1) {
|
||||
// scalar number of overlaps found
|
||||
VecDH<int> nOverlapsD(1, 0);
|
||||
// calculate Bounding Box overlaps
|
||||
thrust::for_each_n(
|
||||
zip(querriesIn.cbeginD(), countAt(0)), querriesIn.size(),
|
||||
FindCollisions<T>({querryTri.ptrDpq(), nOverlapsD.ptrD(), maxOverlaps,
|
||||
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
|
||||
nOverlaps = nOverlapsD.H()[0];
|
||||
if (nOverlaps <= maxOverlaps)
|
||||
break;
|
||||
else { // if not enough memory was allocated, guess how much will be needed
|
||||
int lastQuery = querryTri.Get(0).H().back();
|
||||
maxOverlaps *= 2 * static_cast<float>(querriesIn.size()) / lastQuery;
|
||||
querryTri.Resize(maxOverlaps);
|
||||
}
|
||||
}
|
||||
// remove unused part of array
|
||||
querryTri.Resize(nOverlaps);
|
||||
return querryTri;
|
||||
SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
|
||||
// note that the length is 1 larger than the number of queries so the last
|
||||
// element can store the sum when using exclusive scan
|
||||
VecDH<int> counts(queriesIn.size() + 1, 0);
|
||||
auto policy = autoPolicy(queriesIn.size());
|
||||
// compute the number of collisions to determine the size for allocation and
|
||||
// offset, this avoids the need for atomic
|
||||
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
|
||||
FindCollisions<T, true>(
|
||||
{thrust::pair<int*, int*>(nullptr, nullptr), counts.ptrD(),
|
||||
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
|
||||
// compute start index for each query and total count
|
||||
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin());
|
||||
SparseIndices queryTri(counts.back());
|
||||
// actually recording collisions
|
||||
for_each_n(
|
||||
policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
|
||||
FindCollisions<T, false>({queryTri.ptrDpq(), counts.ptrD(),
|
||||
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
|
||||
return queryTri;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,19 +297,18 @@ SparseIndices Collider::Collisions(const VecDH<T>& querriesIn) const {
|
||||
* hierarchy.
|
||||
*/
|
||||
void Collider::UpdateBoxes(const VecDH<Box>& leafBB) {
|
||||
ALWAYS_ASSERT(leafBB.size() == NumLeaves(), userErr,
|
||||
"must have the same number of updated boxes as original");
|
||||
// copy in leaf node Boxs
|
||||
strided_range<VecDH<Box>::IterD> leaves(nodeBBox_.beginD(), nodeBBox_.endD(),
|
||||
2);
|
||||
thrust::copy(leafBB.cbeginD(), leafBB.cendD(), leaves.begin());
|
||||
ASSERT(leafBB.size() == NumLeaves(), userErr,
|
||||
"must have the same number of updated boxes as original");
|
||||
// copy in leaf node Boxes
|
||||
strided_range<VecDH<Box>::Iter> leaves(nodeBBox_.begin(), nodeBBox_.end(), 2);
|
||||
auto policy = autoPolicy(NumInternal());
|
||||
copy(policy, leafBB.cbegin(), leafBB.cend(), leaves.begin());
|
||||
// create global counters
|
||||
VecDH<int> counter_(NumInternal());
|
||||
thrust::fill(counter_.beginD(), counter_.endD(), 0);
|
||||
// kernel over leaves to save internal Boxs
|
||||
thrust::for_each_n(
|
||||
countAt(0), NumLeaves(),
|
||||
BuildInternalBoxes({nodeBBox_.ptrD(), counter_.ptrD(), nodeParent_.ptrD(),
|
||||
VecDH<int> counter(NumInternal(), 0);
|
||||
// kernel over leaves to save internal Boxes
|
||||
for_each_n(
|
||||
policy, countAt(0), NumLeaves(),
|
||||
BuildInternalBoxes({nodeBBox_.ptrD(), counter.ptrD(), nodeParent_.ptrD(),
|
||||
internalChildren_.ptrD()}));
|
||||
}
|
||||
|
||||
@@ -286,8 +326,8 @@ bool Collider::Transform(glm::mat4x3 transform) {
|
||||
if (count != 2) axisAligned = false;
|
||||
}
|
||||
if (axisAligned) {
|
||||
thrust::for_each(nodeBBox_.beginD(), nodeBBox_.endD(),
|
||||
TransformBox({transform}));
|
||||
for_each(autoPolicy(nodeBBox_.size()), nodeBBox_.begin(), nodeBBox_.end(),
|
||||
TransformBox({transform}));
|
||||
}
|
||||
return axisAligned;
|
||||
}
|
||||
@@ -297,4 +337,4 @@ template SparseIndices Collider::Collisions<Box>(const VecDH<Box>&) const;
|
||||
template SparseIndices Collider::Collisions<glm::vec3>(
|
||||
const VecDH<glm::vec3>&) const;
|
||||
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
project (manifold)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties( ${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -16,14 +16,38 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "structs.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* @ingroup Debug
|
||||
*
|
||||
* Allows modification of the assertions checked in MANIFOLD_DEBUG mode.
|
||||
*
|
||||
* @return ExecutionParams&
|
||||
*/
|
||||
ExecutionParams& ManifoldParams();
|
||||
|
||||
class CsgNode;
|
||||
class CsgLeafNode;
|
||||
|
||||
/** @defgroup Core
|
||||
* @brief The central classes of the library
|
||||
* @{
|
||||
*/
|
||||
class Manifold {
|
||||
public:
|
||||
// Creation
|
||||
/** @name Creation
|
||||
* Constructors
|
||||
*/
|
||||
///@{
|
||||
Manifold();
|
||||
~Manifold();
|
||||
Manifold(const Manifold& other);
|
||||
Manifold& operator=(const Manifold& other);
|
||||
Manifold(Manifold&&) noexcept;
|
||||
Manifold& operator=(Manifold&&) noexcept;
|
||||
|
||||
Manifold(
|
||||
const Mesh&,
|
||||
@@ -44,20 +68,48 @@ class Manifold {
|
||||
glm::vec2 scaleTop = glm::vec2(1.0f));
|
||||
static Manifold Revolve(const Polygons& crossSection,
|
||||
int circularSegments = 0);
|
||||
///@}
|
||||
|
||||
// Topological
|
||||
/** @name Topological
|
||||
* No geometric calculations.
|
||||
*/
|
||||
///@{
|
||||
static Manifold Compose(const std::vector<Manifold>&);
|
||||
std::vector<Manifold> Decompose() const;
|
||||
///@}
|
||||
|
||||
// Defaults for construction
|
||||
/** @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
static void SetMinCircularAngle(float degrees);
|
||||
static void SetMinCircularEdgeLength(float length);
|
||||
static void SetCircularSegments(int number);
|
||||
static int GetCircularSegments(float radius);
|
||||
///@}
|
||||
|
||||
// Information
|
||||
/** @name Information
|
||||
* Details of the manifold
|
||||
*/
|
||||
///@{
|
||||
Mesh GetMesh() const;
|
||||
MeshGL GetMeshGL() const;
|
||||
bool IsEmpty() const;
|
||||
enum class Error {
|
||||
NO_ERROR,
|
||||
NON_FINITE_VERTEX,
|
||||
NOT_MANIFOLD,
|
||||
VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_OUT_OF_BOUNDS,
|
||||
};
|
||||
Error Status() const;
|
||||
int NumVert() const;
|
||||
int NumEdge() const;
|
||||
int NumTri() const;
|
||||
@@ -66,27 +118,43 @@ class Manifold {
|
||||
int Genus() const;
|
||||
Properties GetProperties() const;
|
||||
Curvature GetCurvature() const;
|
||||
///@}
|
||||
|
||||
// Relation
|
||||
/** @name Relation
|
||||
* Details of the manifold's relation to its input meshes, for the purposes
|
||||
* of reapplying mesh properties.
|
||||
*/
|
||||
///@{
|
||||
MeshRelation GetMeshRelation() const;
|
||||
std::vector<int> GetMeshIDs() const;
|
||||
int SetAsOriginal();
|
||||
static std::vector<int> MeshID2Original();
|
||||
int OriginalID() const;
|
||||
Manifold AsOriginal() const;
|
||||
///@}
|
||||
|
||||
// Modification
|
||||
Manifold& Translate(glm::vec3);
|
||||
Manifold& Scale(glm::vec3);
|
||||
Manifold& Rotate(float xDegrees, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f);
|
||||
Manifold& Transform(const glm::mat4x3&);
|
||||
Manifold& Warp(std::function<void(glm::vec3&)>);
|
||||
Manifold& Refine(int);
|
||||
/** @name Modification
|
||||
*/
|
||||
///@{
|
||||
Manifold Translate(glm::vec3) const;
|
||||
Manifold Scale(glm::vec3) const;
|
||||
Manifold Rotate(float xDegrees, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f) const;
|
||||
Manifold Transform(const glm::mat4x3&) const;
|
||||
Manifold Warp(std::function<void(glm::vec3&)>) const;
|
||||
Manifold Refine(int) const;
|
||||
// Manifold RefineToLength(float);
|
||||
// Manifold RefineToPrecision(float);
|
||||
///@}
|
||||
|
||||
// Boolean
|
||||
/** @name Boolean
|
||||
* Combine two manifolds
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* Boolean operation type: ADD (Union), SUBTRACT (Difference), and INTERSECT.
|
||||
*/
|
||||
enum class OpType { ADD, SUBTRACT, INTERSECT };
|
||||
Manifold Boolean(const Manifold& second, OpType op) const;
|
||||
static Manifold BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
OpType op);
|
||||
// Boolean operation shorthand
|
||||
Manifold operator+(const Manifold&) const; // ADD (Union)
|
||||
Manifold& operator+=(const Manifold&);
|
||||
@@ -98,24 +166,31 @@ class Manifold {
|
||||
std::pair<Manifold, Manifold> SplitByPlane(glm::vec3 normal,
|
||||
float originOffset) const;
|
||||
Manifold TrimByPlane(glm::vec3 normal, float originOffset) const;
|
||||
///@}
|
||||
|
||||
// Testing hooks
|
||||
/** @name Testing hooks
|
||||
* These are just for internal testing.
|
||||
*/
|
||||
///@{
|
||||
bool IsManifold() const;
|
||||
bool MatchesTriNormals() const;
|
||||
int NumDegenerateTris() const;
|
||||
int NumOverlaps(const Manifold& second) const;
|
||||
///@}
|
||||
|
||||
~Manifold();
|
||||
Manifold(const Manifold& other);
|
||||
Manifold& operator=(const Manifold& other);
|
||||
Manifold(Manifold&&) noexcept;
|
||||
Manifold& operator=(Manifold&&) noexcept;
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Impl> pImpl_;
|
||||
Manifold(std::shared_ptr<CsgNode> pNode_);
|
||||
Manifold(std::shared_ptr<Impl> pImpl_);
|
||||
|
||||
mutable std::shared_ptr<CsgNode> pNode_;
|
||||
|
||||
CsgLeafNode& GetCsgLeafNode() const;
|
||||
|
||||
static int circularSegments_;
|
||||
static float circularAngle_;
|
||||
static float circularEdgeLength_;
|
||||
};
|
||||
} // namespace manifold
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -12,10 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "boolean3.cuh"
|
||||
#include "boolean3.h"
|
||||
|
||||
// TODO: make this runtime configurable for quicker debug
|
||||
constexpr bool kVerbose = false;
|
||||
#include <limits>
|
||||
|
||||
#include "par.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
@@ -29,7 +30,9 @@ namespace {
|
||||
__host__ __device__ glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
|
||||
float dxL = x - pL.x;
|
||||
float dxR = x - pR.x;
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (dxL * dxR > 0) printf("Not in domain!\n");
|
||||
#endif
|
||||
bool useL = fabs(dxL) < fabs(dxR);
|
||||
float lambda = (useL ? dxL : dxR) / (pR.x - pL.x);
|
||||
if (!isfinite(lambda)) return glm::vec2(pL.y, pL.z);
|
||||
@@ -45,7 +48,9 @@ __host__ __device__ glm::vec4 Intersect(const glm::vec3 &pL,
|
||||
const glm::vec3 &qR) {
|
||||
float dyL = qL.y - pL.y;
|
||||
float dyR = qR.y - pR.y;
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (dyL * dyR > 0) printf("No intersection!\n");
|
||||
#endif
|
||||
bool useL = fabs(dyL) < fabs(dyR);
|
||||
float dx = pR.x - pL.x;
|
||||
float lambda = (useL ? dyL : dyR) / (dyL - dyR);
|
||||
@@ -82,18 +87,18 @@ struct CopyFaceEdges {
|
||||
};
|
||||
|
||||
SparseIndices Filter11(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
const SparseIndices &p1q2, const SparseIndices &p2q1) {
|
||||
const SparseIndices &p1q2, const SparseIndices &p2q1,
|
||||
ExecutionPolicy policy) {
|
||||
SparseIndices p1q1(3 * p1q2.size() + 3 * p2q1.size());
|
||||
thrust::for_each_n(zip(countAt(0), p1q2.beginD(0), p1q2.beginD(1)),
|
||||
p1q2.size(),
|
||||
CopyFaceEdges({p1q1.ptrDpq(), inQ.halfedge_.cptrD()}));
|
||||
for_each_n(policy, zip(countAt(0), p1q2.begin(0), p1q2.begin(1)), p1q2.size(),
|
||||
CopyFaceEdges({p1q1.ptrDpq(), inQ.halfedge_.cptrD()}));
|
||||
|
||||
p1q1.SwapPQ();
|
||||
thrust::for_each_n(zip(countAt(p1q2.size()), p2q1.beginD(1), p2q1.beginD(0)),
|
||||
p2q1.size(),
|
||||
CopyFaceEdges({p1q1.ptrDpq(), inP.halfedge_.cptrD()}));
|
||||
for_each_n(policy, zip(countAt(p1q2.size()), p2q1.begin(1), p2q1.begin(0)),
|
||||
p2q1.size(),
|
||||
CopyFaceEdges({p1q1.ptrDpq(), inP.halfedge_.cptrD()}));
|
||||
p1q1.SwapPQ();
|
||||
p1q1.Unique();
|
||||
p1q1.Unique(policy);
|
||||
return p1q1;
|
||||
}
|
||||
|
||||
@@ -101,6 +106,17 @@ __host__ __device__ bool Shadows(float p, float q, float dir) {
|
||||
return p == q ? dir < 0 : p < q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since this function is called from two different places, it is necessary that
|
||||
* it returns identical results for identical input to keep consistency.
|
||||
* Normally this is trivial as computers make floating-point errors, but are
|
||||
* at least deterministic. However, in the case of CUDA, these functions can be
|
||||
* compiled by two different compilers (for the CPU and GPU). We have found that
|
||||
* the different compilers can cause slightly different rounding errors, so it
|
||||
* is critical that the two places this function is called both use the same
|
||||
* compiled function (they must agree on CPU or GPU). This is now taken care of
|
||||
* by the shared policy_ member.
|
||||
*/
|
||||
__host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
|
||||
const int p0, const int q1, const glm::vec3 *vertPosP,
|
||||
const glm::vec3 *vertPosQ, const Halfedge *halfedgeQ, const float expandP,
|
||||
@@ -114,7 +130,7 @@ __host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
|
||||
Shadows(q1ex, p0x, expandP * normalP[q1e].x)
|
||||
: Shadows(p0x, q1ex, expandP * normalP[p0].x) -
|
||||
Shadows(p0x, q1sx, expandP * normalP[p0].x);
|
||||
glm::vec2 yz01(0.0f / 0.0f);
|
||||
glm::vec2 yz01(NAN);
|
||||
|
||||
if (s01 != 0) {
|
||||
yz01 = Interpolate(vertPosQ[q1s], vertPosQ[q1e], vertPosP[p0].x);
|
||||
@@ -175,7 +191,7 @@ struct Kernel11 {
|
||||
glm::vec3 pRL[2], qRL[2];
|
||||
// Either the left or right must shadow, but not both. This ensures the
|
||||
// intersection is between the left and right.
|
||||
bool shadows;
|
||||
bool shadows = false;
|
||||
s11 = 0;
|
||||
|
||||
const int p0[2] = {halfedgeP[p1].startVert, halfedgeP[p1].endVert};
|
||||
@@ -215,13 +231,14 @@ struct Kernel11 {
|
||||
}
|
||||
|
||||
if (s11 == 0) { // No intersection
|
||||
xyzz11 = glm::vec4(0.0f / 0.0f);
|
||||
xyzz11 = glm::vec4(NAN);
|
||||
} else {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
// Assert left and right were both found
|
||||
if (k != 2) {
|
||||
printf("k = %d\n", k);
|
||||
}
|
||||
|
||||
#endif
|
||||
xyzz11 = Intersect(pRL[0], pRL[1], qRL[0], qRL[1]);
|
||||
|
||||
const int p1s = halfedgeP[p1].startVert;
|
||||
@@ -240,16 +257,17 @@ struct Kernel11 {
|
||||
std::tuple<VecDH<int>, VecDH<glm::vec4>> Shadow11(SparseIndices &p1q1,
|
||||
const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
float expandP) {
|
||||
float expandP,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<int> s11(p1q1.size());
|
||||
VecDH<glm::vec4> xyzz11(p1q1.size());
|
||||
|
||||
thrust::for_each_n(
|
||||
zip(xyzz11.beginD(), s11.beginD(), p1q1.beginD(0), p1q1.beginD(1)),
|
||||
p1q1.size(),
|
||||
Kernel11({inP.vertPos_.cptrD(), inQ.vertPos_.cptrD(),
|
||||
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(), expandP,
|
||||
inP.vertNormal_.cptrD()}));
|
||||
for_each_n(policy,
|
||||
zip(xyzz11.begin(), s11.begin(), p1q1.begin(0), p1q1.begin(1)),
|
||||
p1q1.size(),
|
||||
Kernel11({inP.vertPos_.cptrD(), inQ.vertPos_.cptrD(),
|
||||
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(), expandP,
|
||||
inP.vertNormal_.cptrD()}));
|
||||
|
||||
p1q1.KeepFinite(xyzz11, s11);
|
||||
|
||||
@@ -276,9 +294,9 @@ struct Kernel02 {
|
||||
glm::vec3 yzzRL[2];
|
||||
// Either the left or right must shadow, but not both. This ensures the
|
||||
// intersection is between the left and right.
|
||||
bool shadows;
|
||||
int closestVert;
|
||||
float minMetric = 1.0f / 0.0f;
|
||||
bool shadows = false;
|
||||
int closestVert = -1;
|
||||
float minMetric = std::numeric_limits<float>::infinity();
|
||||
s02 = 0;
|
||||
|
||||
const glm::vec3 posP = vertPosP[p0];
|
||||
@@ -312,18 +330,20 @@ struct Kernel02 {
|
||||
}
|
||||
|
||||
if (s02 == 0) { // No intersection
|
||||
z02 = 0.0f / 0.0f;
|
||||
z02 = NAN;
|
||||
} else {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
// Assert left and right were both found
|
||||
if (k != 2) {
|
||||
printf("k = %d\n", k);
|
||||
}
|
||||
|
||||
#endif
|
||||
glm::vec3 vertPos = vertPosP[p0];
|
||||
z02 = Interpolate(yzzRL[0], yzzRL[1], vertPos.y)[1];
|
||||
if (forward) {
|
||||
if (!Shadows(vertPos.z, z02, expandP * vertNormalP[p0].z)) s02 = 0;
|
||||
} else {
|
||||
// ASSERT(closestVert != -1, topologyErr, "No closest vert");
|
||||
if (!Shadows(z02, vertPos.z, expandP * vertNormalP[closestVert].z))
|
||||
s02 = 0;
|
||||
}
|
||||
@@ -334,15 +354,16 @@ struct Kernel02 {
|
||||
std::tuple<VecDH<int>, VecDH<float>> Shadow02(const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
SparseIndices &p0q2, bool forward,
|
||||
float expandP) {
|
||||
float expandP,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<int> s02(p0q2.size());
|
||||
VecDH<float> z02(p0q2.size());
|
||||
|
||||
auto vertNormalP =
|
||||
forward ? inP.vertNormal_.cptrD() : inQ.vertNormal_.cptrD();
|
||||
thrust::for_each_n(
|
||||
zip(s02.beginD(), z02.beginD(), p0q2.beginD(!forward),
|
||||
p0q2.beginD(forward)),
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(s02.begin(), z02.begin(), p0q2.begin(!forward), p0q2.begin(forward)),
|
||||
p0q2.size(),
|
||||
Kernel02({inP.vertPos_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), forward, expandP, vertNormalP}));
|
||||
@@ -379,7 +400,7 @@ struct Kernel12 {
|
||||
glm::vec3 xzyLR1[2];
|
||||
// Either the left or right must shadow, but not both. This ensures the
|
||||
// intersection is between the left and right.
|
||||
bool shadows;
|
||||
bool shadows = false;
|
||||
x12 = 0;
|
||||
|
||||
const Halfedge edge = halfedgesP[p1];
|
||||
@@ -427,12 +448,14 @@ struct Kernel12 {
|
||||
}
|
||||
|
||||
if (x12 == 0) { // No intersection
|
||||
v12 = glm::vec3(0.0f / 0.0f);
|
||||
v12 = glm::vec3(NAN);
|
||||
} else {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
// Assert left and right were both found
|
||||
if (k != 2) {
|
||||
printf("k = %d\n", k);
|
||||
}
|
||||
#endif
|
||||
const glm::vec4 xzyy =
|
||||
Intersect(xzyLR0[0], xzyLR0[1], xzyLR1[0], xzyLR1[1]);
|
||||
v12.x = xzyy[0];
|
||||
@@ -446,13 +469,13 @@ std::tuple<VecDH<int>, VecDH<glm::vec3>> Intersect12(
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, const VecDH<int> &s02,
|
||||
const SparseIndices &p0q2, const VecDH<int> &s11, const SparseIndices &p1q1,
|
||||
const VecDH<float> &z02, const VecDH<glm::vec4> &xyzz11,
|
||||
SparseIndices &p1q2, bool forward) {
|
||||
SparseIndices &p1q2, bool forward, ExecutionPolicy policy) {
|
||||
VecDH<int> x12(p1q2.size());
|
||||
VecDH<glm::vec3> v12(p1q2.size());
|
||||
|
||||
thrust::for_each_n(
|
||||
zip(x12.beginD(), v12.beginD(), p1q2.beginD(!forward),
|
||||
p1q2.beginD(forward)),
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(x12.begin(), v12.begin(), p1q2.begin(!forward), p1q2.begin(forward)),
|
||||
p1q2.size(),
|
||||
Kernel12({p0q2.ptrDpq(), s02.ptrD(), z02.cptrD(), p0q2.size(),
|
||||
p1q1.ptrDpq(), s11.ptrD(), xyzz11.cptrD(), p1q1.size(),
|
||||
@@ -465,24 +488,24 @@ std::tuple<VecDH<int>, VecDH<glm::vec3>> Intersect12(
|
||||
};
|
||||
|
||||
VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
|
||||
VecDH<int> &s02, bool reverse) {
|
||||
VecDH<int> &s02, bool reverse, ExecutionPolicy policy) {
|
||||
// verts that are not shadowed (not in p0q2) have winding number zero.
|
||||
VecDH<int> w03(inP.NumVert(), 0);
|
||||
|
||||
if (!thrust::is_sorted(p0q2.beginD(reverse), p0q2.endD(reverse)))
|
||||
thrust::sort_by_key(p0q2.beginD(reverse), p0q2.endD(reverse), s02.beginD());
|
||||
if (!is_sorted(policy, p0q2.begin(reverse), p0q2.end(reverse)))
|
||||
sort_by_key(policy, p0q2.begin(reverse), p0q2.end(reverse), s02.begin());
|
||||
VecDH<int> w03val(w03.size());
|
||||
VecDH<int> w03vert(w03.size());
|
||||
// sum known s02 values into w03 (winding number)
|
||||
auto endPair =
|
||||
thrust::reduce_by_key(p0q2.beginD(reverse), p0q2.endD(reverse),
|
||||
s02.beginD(), w03vert.beginD(), w03val.beginD());
|
||||
thrust::scatter(w03val.beginD(), endPair.second, w03vert.beginD(),
|
||||
w03.beginD());
|
||||
auto endPair = reduce_by_key<
|
||||
thrust::pair<decltype(w03val.begin()), decltype(w03val.begin())>>(
|
||||
policy, p0q2.begin(reverse), p0q2.end(reverse), s02.begin(),
|
||||
w03vert.begin(), w03val.begin());
|
||||
scatter(policy, w03val.begin(), endPair.second, w03vert.begin(), w03.begin());
|
||||
|
||||
if (reverse)
|
||||
thrust::transform(w03.beginD(), w03.endD(), w03.beginD(),
|
||||
thrust::negate<int>());
|
||||
transform(policy, w03.begin(), w03.end(), w03.begin(),
|
||||
thrust::negate<int>());
|
||||
return w03;
|
||||
};
|
||||
} // namespace
|
||||
@@ -490,16 +513,21 @@ VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
|
||||
namespace manifold {
|
||||
Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
Manifold::OpType op)
|
||||
: inP_(inP), inQ_(inQ), expandP_(op == Manifold::OpType::ADD ? 1.0 : -1.0) {
|
||||
: inP_(inP),
|
||||
inQ_(inQ),
|
||||
expandP_(op == Manifold::OpType::ADD ? 1.0 : -1.0),
|
||||
policy_(autoPolicy(glm::max(inP.NumEdge(), inQ.NumEdge()))) {
|
||||
// Symbolic perturbation:
|
||||
// Union -> expand inP
|
||||
// Difference, Intersection -> contract inP
|
||||
|
||||
Timer filter;
|
||||
filter.Start();
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
Timer broad;
|
||||
broad.Start();
|
||||
#endif
|
||||
|
||||
if (inP.IsEmpty() || inQ.IsEmpty() || !inP.bBox_.DoesOverlap(inQ.bBox_)) {
|
||||
if (kVerbose) std::cout << "No overlap, early out" << std::endl;
|
||||
PRINT("No overlap, early out");
|
||||
w03_.resize(inP.NumVert(), 0);
|
||||
w30_.resize(inQ.NumVert(), 0);
|
||||
return;
|
||||
@@ -508,76 +536,82 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
// Level 3
|
||||
// Find edge-triangle overlaps (broad phase)
|
||||
p1q2_ = inQ_.EdgeCollisions(inP_);
|
||||
p1q2_.Sort();
|
||||
if (kVerbose) std::cout << "p1q2 size = " << p1q2_.size() << std::endl;
|
||||
|
||||
p2q1_ = inP_.EdgeCollisions(inQ_);
|
||||
|
||||
policy_ = autoPolicy(glm::max(p1q2_.size(), p2q1_.size()));
|
||||
p1q2_.Sort(policy_);
|
||||
PRINT("p1q2 size = " << p1q2_.size());
|
||||
|
||||
p2q1_.SwapPQ();
|
||||
p2q1_.Sort();
|
||||
if (kVerbose) std::cout << "p2q1 size = " << p2q1_.size() << std::endl;
|
||||
p2q1_.Sort(policy_);
|
||||
PRINT("p2q1 size = " << p2q1_.size());
|
||||
|
||||
// Level 2
|
||||
// Find vertices that overlap faces in XY-projection
|
||||
SparseIndices p0q2 = inQ.VertexCollisionsZ(inP.vertPos_);
|
||||
p0q2.Sort();
|
||||
if (kVerbose) std::cout << "p0q2 size = " << p0q2.size() << std::endl;
|
||||
p0q2.Sort(policy_);
|
||||
PRINT("p0q2 size = " << p0q2.size());
|
||||
|
||||
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_);
|
||||
p2q0.SwapPQ();
|
||||
p2q0.Sort();
|
||||
if (kVerbose) std::cout << "p2q0 size = " << p2q0.size() << std::endl;
|
||||
p2q0.Sort(policy_);
|
||||
PRINT("p2q0 size = " << p2q0.size());
|
||||
|
||||
// Find involved edge pairs from Level 3
|
||||
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_);
|
||||
if (kVerbose) std::cout << "p1q1 size = " << p1q1.size() << std::endl;
|
||||
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_, policy_);
|
||||
PRINT("p1q1 size = " << p1q1.size());
|
||||
|
||||
filter.Stop();
|
||||
Timer levels;
|
||||
levels.Start();
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
broad.Stop();
|
||||
Timer intersections;
|
||||
intersections.Start();
|
||||
#endif
|
||||
|
||||
// Level 2
|
||||
// Build up XY-projection intersection of two edges, including the z-value for
|
||||
// each edge, keeping only those whose intersection exists.
|
||||
VecDH<int> s11;
|
||||
VecDH<glm::vec4> xyzz11;
|
||||
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_);
|
||||
if (kVerbose) std::cout << "s11 size = " << s11.size() << std::endl;
|
||||
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_, policy_);
|
||||
PRINT("s11 size = " << s11.size());
|
||||
|
||||
// Build up Z-projection of vertices onto triangles, keeping only those that
|
||||
// fall inside the triangle.
|
||||
VecDH<int> s02;
|
||||
VecDH<float> z02;
|
||||
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_);
|
||||
if (kVerbose) std::cout << "s02 size = " << s02.size() << std::endl;
|
||||
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_, policy_);
|
||||
PRINT("s02 size = " << s02.size());
|
||||
|
||||
VecDH<int> s20;
|
||||
VecDH<float> z20;
|
||||
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_);
|
||||
if (kVerbose) std::cout << "s20 size = " << s20.size() << std::endl;
|
||||
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_, policy_);
|
||||
PRINT("s20 size = " << s20.size());
|
||||
|
||||
// Level 3
|
||||
// Build up the intersection of the edges and triangles, keeping only those
|
||||
// that intersect, and record the direction the edge is passing through the
|
||||
// triangle.
|
||||
std::tie(x12_, v12_) =
|
||||
Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02, xyzz11, p1q2_, true);
|
||||
if (kVerbose) std::cout << "x12 size = " << x12_.size() << std::endl;
|
||||
std::tie(x12_, v12_) = Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02,
|
||||
xyzz11, p1q2_, true, policy_);
|
||||
PRINT("x12 size = " << x12_.size());
|
||||
|
||||
std::tie(x21_, v21_) =
|
||||
Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20, xyzz11, p2q1_, false);
|
||||
if (kVerbose) std::cout << "x21 size = " << x21_.size() << std::endl;
|
||||
std::tie(x21_, v21_) = Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20,
|
||||
xyzz11, p2q1_, false, policy_);
|
||||
PRINT("x21 size = " << x21_.size());
|
||||
|
||||
// Sum up the winding numbers of all vertices.
|
||||
w03_ = Winding03(inP, p0q2, s02, false);
|
||||
w03_ = Winding03(inP, p0q2, s02, false, policy_);
|
||||
|
||||
w30_ = Winding03(inQ, p2q0, s20, true);
|
||||
w30_ = Winding03(inQ, p2q0, s20, true, policy_);
|
||||
|
||||
levels.Stop();
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
intersections.Stop();
|
||||
|
||||
if (kVerbose) {
|
||||
filter.Print("Filter");
|
||||
levels.Print("Levels 1-3");
|
||||
if (ManifoldParams().verbose) {
|
||||
broad.Print("Broad phase");
|
||||
intersections.Print("Intersections");
|
||||
MemUsage();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Emmett Lalish
|
||||
// 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.
|
||||
@@ -13,7 +13,14 @@
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include "impl.cuh"
|
||||
#include "impl.h"
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#define PRINT(msg) \
|
||||
if (ManifoldParams().verbose) std::cout << msg << std::endl;
|
||||
#else
|
||||
#define PRINT(msg)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The notation in these files is abbreviated due to the complexity of the
|
||||
@@ -31,12 +38,13 @@
|
||||
*
|
||||
* Note many functions are designed to work symmetrically, for instance for both
|
||||
* p2q1 and p1q2. Inside of these functions P and Q are marked as though the
|
||||
* funtion is forwards, but it may include a Boolean "reverse" that indicates P
|
||||
* function is forwards, but it may include a Boolean "reverse" that indicates P
|
||||
* and Q have been swapped.
|
||||
*/
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
class Boolean3 {
|
||||
public:
|
||||
Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
|
||||
@@ -49,5 +57,6 @@ class Boolean3 {
|
||||
SparseIndices p1q2_, p2q1_;
|
||||
VecDH<int> x12_, x21_, w03_, w30_;
|
||||
VecDH<glm::vec3> v12_, v21_;
|
||||
ExecutionPolicy policy_;
|
||||
};
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 Emmett Lalish
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,12 +15,10 @@
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include "boolean3.cuh"
|
||||
#include "boolean3.h"
|
||||
#include "par.h"
|
||||
#include "polygon.h"
|
||||
|
||||
// TODO: make this runtime configurable for quicker debug
|
||||
constexpr bool kVerbose = false;
|
||||
|
||||
using namespace manifold;
|
||||
using namespace thrust::placeholders;
|
||||
|
||||
@@ -78,52 +76,53 @@ std::tuple<VecDH<int>, VecDH<int>> SizeOutput(
|
||||
Manifold::Impl &outR, const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
const VecDH<int> &i03, const VecDH<int> &i30, const VecDH<int> &i12,
|
||||
const VecDH<int> &i21, const SparseIndices &p1q2, const SparseIndices &p2q1,
|
||||
bool invertQ) {
|
||||
VecDH<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri());
|
||||
bool invertQ, ExecutionPolicy policy) {
|
||||
VecDH<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);
|
||||
auto sidesPerFaceP = sidesPerFacePQ.ptrD();
|
||||
auto sidesPerFaceQ = sidesPerFacePQ.ptrD() + inP.NumTri();
|
||||
|
||||
thrust::for_each(inP.halfedge_.beginD(), inP.halfedge_.endD(),
|
||||
CountVerts({sidesPerFaceP, i03.cptrD()}));
|
||||
thrust::for_each(inQ.halfedge_.beginD(), inQ.halfedge_.endD(),
|
||||
CountVerts({sidesPerFaceQ, i30.cptrD()}));
|
||||
thrust::for_each_n(
|
||||
zip(p1q2.beginD(0), p1q2.beginD(1), i12.beginD()), i12.size(),
|
||||
for_each(policy, inP.halfedge_.begin(), inP.halfedge_.end(),
|
||||
CountVerts({sidesPerFaceP, i03.cptrD()}));
|
||||
for_each(policy, inQ.halfedge_.begin(), inQ.halfedge_.end(),
|
||||
CountVerts({sidesPerFaceQ, i30.cptrD()}));
|
||||
for_each_n(
|
||||
policy, zip(p1q2.begin(0), p1q2.begin(1), i12.begin()), i12.size(),
|
||||
CountNewVerts({sidesPerFaceP, sidesPerFaceQ, inP.halfedge_.cptrD()}));
|
||||
thrust::for_each_n(
|
||||
zip(p2q1.beginD(1), p2q1.beginD(0), i21.beginD()), i21.size(),
|
||||
for_each_n(
|
||||
policy, zip(p2q1.begin(1), p2q1.begin(0), i21.begin()), i21.size(),
|
||||
CountNewVerts({sidesPerFaceQ, sidesPerFaceP, inQ.halfedge_.cptrD()}));
|
||||
|
||||
VecDH<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1);
|
||||
VecDH<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);
|
||||
auto keepFace =
|
||||
thrust::make_transform_iterator(sidesPerFacePQ.beginD(), NotZero());
|
||||
thrust::inclusive_scan(keepFace, keepFace + sidesPerFacePQ.size(),
|
||||
facePQ2R.beginD() + 1);
|
||||
int numFaceR = facePQ2R.H().back();
|
||||
thrust::make_transform_iterator(sidesPerFacePQ.begin(), NotZero());
|
||||
inclusive_scan(policy, keepFace, keepFace + sidesPerFacePQ.size(),
|
||||
facePQ2R.begin() + 1);
|
||||
int numFaceR = facePQ2R.back();
|
||||
facePQ2R.resize(inP.NumTri() + inQ.NumTri());
|
||||
|
||||
outR.faceNormal_.resize(numFaceR);
|
||||
auto next = thrust::copy_if(inP.faceNormal_.beginD(), inP.faceNormal_.endD(),
|
||||
keepFace, outR.faceNormal_.beginD(),
|
||||
thrust::identity<bool>());
|
||||
auto next = copy_if<decltype(outR.faceNormal_.begin())>(
|
||||
policy, inP.faceNormal_.begin(), inP.faceNormal_.end(), keepFace,
|
||||
outR.faceNormal_.begin(), thrust::identity<bool>());
|
||||
if (invertQ) {
|
||||
auto start = thrust::make_transform_iterator(inQ.faceNormal_.beginD(),
|
||||
auto start = thrust::make_transform_iterator(inQ.faceNormal_.begin(),
|
||||
thrust::negate<glm::vec3>());
|
||||
auto end = thrust::make_transform_iterator(inQ.faceNormal_.endD(),
|
||||
auto end = thrust::make_transform_iterator(inQ.faceNormal_.end(),
|
||||
thrust::negate<glm::vec3>());
|
||||
thrust::copy_if(start, end, keepFace + inP.NumTri(), next,
|
||||
thrust::identity<bool>());
|
||||
copy_if<decltype(inQ.faceNormal_.begin())>(policy, start, end,
|
||||
keepFace + inP.NumTri(), next,
|
||||
thrust::identity<bool>());
|
||||
} else {
|
||||
thrust::copy_if(inQ.faceNormal_.beginD(), inQ.faceNormal_.endD(),
|
||||
keepFace + inP.NumTri(), next, thrust::identity<bool>());
|
||||
copy_if<decltype(inQ.faceNormal_.begin())>(
|
||||
policy, inQ.faceNormal_.begin(), inQ.faceNormal_.end(),
|
||||
keepFace + inP.NumTri(), next, thrust::identity<bool>());
|
||||
}
|
||||
|
||||
auto newEnd =
|
||||
thrust::remove(sidesPerFacePQ.beginD(), sidesPerFacePQ.endD(), 0);
|
||||
VecDH<int> faceEdge(newEnd - sidesPerFacePQ.beginD() + 1);
|
||||
thrust::inclusive_scan(sidesPerFacePQ.beginD(), newEnd,
|
||||
faceEdge.beginD() + 1);
|
||||
outR.halfedge_.resize(faceEdge.H().back());
|
||||
auto newEnd = remove<decltype(sidesPerFacePQ.begin())>(
|
||||
policy, sidesPerFacePQ.begin(), sidesPerFacePQ.end(), 0);
|
||||
VecDH<int> faceEdge(newEnd - sidesPerFacePQ.begin() + 1, 0);
|
||||
inclusive_scan(policy, sidesPerFacePQ.begin(), newEnd, faceEdge.begin() + 1);
|
||||
outR.halfedge_.resize(faceEdge.back());
|
||||
|
||||
return std::make_tuple(faceEdge, facePQ2R);
|
||||
}
|
||||
@@ -137,15 +136,15 @@ struct EdgePos {
|
||||
void AddNewEdgeVerts(
|
||||
std::map<int, std::vector<EdgePos>> &edgesP,
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
const SparseIndices &p1q2, const VecH<int> &i12, const VecH<int> &v12R,
|
||||
const VecH<Halfedge> &halfedgeP, bool forward) {
|
||||
const SparseIndices &p1q2, const VecDH<int> &i12, const VecDH<int> &v12R,
|
||||
const VecDH<Halfedge> &halfedgeP, bool forward) {
|
||||
// For each edge of P that intersects a face of Q (p1q2), add this vertex to
|
||||
// P's corresponding edge vector and to the two new edges, which are
|
||||
// intersections between the face of Q and the two faces of P attached to the
|
||||
// edge. The direction and duplicity are given by i12, while v12R remaps to
|
||||
// the output vert index. When forward is false, all is reversed.
|
||||
const VecH<int> &p1 = p1q2.Get(!forward).H();
|
||||
const VecH<int> &q2 = p1q2.Get(forward).H();
|
||||
const VecDH<int> &p1 = p1q2.Get(!forward);
|
||||
const VecDH<int> &q2 = p1q2.Get(forward);
|
||||
for (int i = 0; i < p1q2.size(); ++i) {
|
||||
const int edgeP = p1[i];
|
||||
const int faceQ = q2[i];
|
||||
@@ -183,13 +182,12 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
|
||||
// geometrically valid. If the order does not go start-end-start-end... then
|
||||
// the input and output are not geometrically valid and this algorithm becomes
|
||||
// a heuristic.
|
||||
ALWAYS_ASSERT(edgePos.size() % 2 == 0, topologyErr,
|
||||
"Non-manifold edge! Not an even number of points.");
|
||||
ASSERT(edgePos.size() % 2 == 0, topologyErr,
|
||||
"Non-manifold edge! Not an even number of points.");
|
||||
int nEdges = edgePos.size() / 2;
|
||||
auto middle = std::partition(edgePos.begin(), edgePos.end(),
|
||||
[](EdgePos x) { return x.isStart; });
|
||||
ALWAYS_ASSERT(middle - edgePos.begin() == nEdges, topologyErr,
|
||||
"Non-manifold edge!");
|
||||
ASSERT(middle - edgePos.begin() == nEdges, topologyErr, "Non-manifold edge!");
|
||||
auto cmp = [](EdgePos a, EdgePos b) { return a.edgePos < b.edgePos; };
|
||||
std::sort(edgePos.begin(), middle, cmp);
|
||||
std::sort(middle, edgePos.end(), cmp);
|
||||
@@ -206,21 +204,20 @@ struct Ref {
|
||||
int PQ, tri, vert;
|
||||
};
|
||||
|
||||
void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
|
||||
VecH<int> &facePtrR,
|
||||
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
VecDH<int> &facePtrR,
|
||||
std::map<int, std::vector<EdgePos>> &edgesP,
|
||||
VecH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecH<int> &i03, const VecH<int> &vP2R,
|
||||
const thrust::host_vector<int>::const_iterator faceP2R,
|
||||
bool forward) {
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<int> &i03, const VecDH<int> &vP2R,
|
||||
const VecDH<int>::IterC faceP2R, bool forward) {
|
||||
// Each edge in the map is partially retained; for each of these, look up
|
||||
// their original verts and include them based on their winding number (i03),
|
||||
// while remaping them to the output using vP2R. Use the verts position
|
||||
// while remapping them to the output using vP2R. Use the verts position
|
||||
// projected along the edge vector to pair them up, then distribute these
|
||||
// edges to their faces.
|
||||
VecH<Halfedge> &halfedgeR = outR.halfedge_.H();
|
||||
const VecH<glm::vec3> &vertPosP = inP.vertPos_.H();
|
||||
const VecH<Halfedge> &halfedgeP = inP.halfedge_.H();
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
const VecDH<glm::vec3> &vertPosP = inP.vertPos_;
|
||||
const VecDH<Halfedge> &halfedgeP = inP.halfedge_;
|
||||
|
||||
for (auto &value : edgesP) {
|
||||
const int edgeP = value.first;
|
||||
@@ -235,13 +232,13 @@ void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
|
||||
const glm::vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart];
|
||||
// Fill in the edge positions of the old points.
|
||||
for (EdgePos &edge : edgePosP) {
|
||||
edge.edgePos = glm::dot(outR.vertPos_.H()[edge.vert], edgeVec);
|
||||
edge.edgePos = glm::dot(outR.vertPos_[edge.vert], edgeVec);
|
||||
}
|
||||
|
||||
int inclusion = i03[vStart];
|
||||
bool reversed = inclusion < 0;
|
||||
EdgePos edgePos = {vP2R[vStart],
|
||||
glm::dot(outR.vertPos_.H()[vP2R[vStart]], edgeVec),
|
||||
glm::dot(outR.vertPos_[vP2R[vStart]], edgeVec),
|
||||
inclusion > 0};
|
||||
for (int j = 0; j < glm::abs(inclusion); ++j) {
|
||||
edgePosP.push_back(edgePos);
|
||||
@@ -250,7 +247,7 @@ void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
|
||||
|
||||
inclusion = i03[vEnd];
|
||||
reversed |= inclusion < 0;
|
||||
edgePos = {vP2R[vEnd], glm::dot(outR.vertPos_.H()[vP2R[vEnd]], edgeVec),
|
||||
edgePos = {vP2R[vEnd], glm::dot(outR.vertPos_[vP2R[vEnd]], edgeVec),
|
||||
inclusion < 0};
|
||||
for (int j = 0; j < glm::abs(inclusion); ++j) {
|
||||
edgePosP.push_back(edgePos);
|
||||
@@ -294,12 +291,12 @@ void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
|
||||
}
|
||||
|
||||
void AppendNewEdges(
|
||||
Manifold::Impl &outR, VecH<int> &facePtrR,
|
||||
Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
VecH<Ref> &halfedgeRef, const VecH<int> &facePQ2R, const int numFaceP) {
|
||||
VecDH<Ref> &halfedgeRef, const VecDH<int> &facePQ2R, const int numFaceP) {
|
||||
// Pair up each edge's verts and distribute to faces based on indices in key.
|
||||
VecH<Halfedge> &halfedgeR = outR.halfedge_.H();
|
||||
VecH<glm::vec3> &vertPosR = outR.vertPos_.H();
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
|
||||
|
||||
for (auto &value : edgesNew) {
|
||||
const int faceP = value.first.first;
|
||||
@@ -402,21 +399,23 @@ struct DuplicateHalfedges {
|
||||
|
||||
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<bool> wholeHalfedgeP, const VecDH<int> &i03,
|
||||
const VecDH<int> &vP2R, const int *faceP2R,
|
||||
bool forward) {
|
||||
thrust::for_each_n(
|
||||
zip(wholeHalfedgeP.beginD(), inP.halfedge_.beginD(), countAt(0)),
|
||||
inP.halfedge_.size(),
|
||||
DuplicateHalfedges({outR.halfedge_.ptrD(), halfedgeRef.ptrD(),
|
||||
facePtrR.ptrD(), inP.halfedge_.cptrD(), i03.cptrD(),
|
||||
vP2R.cptrD(), faceP2R, forward}));
|
||||
const VecDH<char> wholeHalfedgeP, const VecDH<int> &i03,
|
||||
const VecDH<int> &vP2R, const int *faceP2R, bool forward,
|
||||
ExecutionPolicy policy) {
|
||||
for_each_n(policy,
|
||||
zip(wholeHalfedgeP.begin(), inP.halfedge_.begin(), countAt(0)),
|
||||
inP.halfedge_.size(),
|
||||
DuplicateHalfedges({outR.halfedge_.ptrD(), halfedgeRef.ptrD(),
|
||||
facePtrR.ptrD(), inP.halfedge_.cptrD(),
|
||||
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
|
||||
}
|
||||
|
||||
struct CreateBarycentric {
|
||||
glm::vec3 *barycentricR;
|
||||
BaryRef *faceRef;
|
||||
int *idx;
|
||||
|
||||
const int offsetQ;
|
||||
const int firstNewVert;
|
||||
const glm::vec3 *vertPosR;
|
||||
const glm::vec3 *vertPosP;
|
||||
@@ -443,6 +442,11 @@ struct CreateBarycentric {
|
||||
|
||||
faceRef[halfedgeR.face] = oldRef;
|
||||
|
||||
if (halfedgeRef.PQ == 1) {
|
||||
// Mark the meshID as coming from Q
|
||||
faceRef[halfedgeR.face].meshID += offsetQ;
|
||||
}
|
||||
|
||||
if (halfedgeR.startVert < firstNewVert) { // retained vert
|
||||
int i = halfedgeRef.vert;
|
||||
const int bary = oldRef.vertBary[i];
|
||||
@@ -476,23 +480,26 @@ struct CreateBarycentric {
|
||||
std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
|
||||
Manifold::Impl &outR, const VecDH<Ref> &halfedgeRef,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, int firstNewVert,
|
||||
int numFaceR, bool invertQ) {
|
||||
int numFaceR, bool invertQ, ExecutionPolicy policy) {
|
||||
outR.meshRelation_.barycentric.resize(outR.halfedge_.size());
|
||||
VecDH<BaryRef> faceRef(numFaceR);
|
||||
VecDH<int> halfedgeBary(halfedgeRef.size());
|
||||
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
VecDH<int> idx(1, 0);
|
||||
thrust::for_each_n(
|
||||
zip(halfedgeBary.beginD(), halfedgeRef.beginD(),
|
||||
outR.halfedge_.cbeginD()),
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(halfedgeBary.begin(), halfedgeRef.begin(), outR.halfedge_.cbegin()),
|
||||
halfedgeRef.size(),
|
||||
CreateBarycentric(
|
||||
{outR.meshRelation_.barycentric.ptrD(), faceRef.ptrD(), idx.ptrD(),
|
||||
firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
|
||||
offsetQ, firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
inP.meshRelation_.triBary.cptrD(), inQ.meshRelation_.triBary.cptrD(),
|
||||
inP.meshRelation_.barycentric.cptrD(),
|
||||
inQ.meshRelation_.barycentric.cptrD(), invertQ, outR.precision_}));
|
||||
outR.meshRelation_.barycentric.resize(idx.H()[0]);
|
||||
outR.meshRelation_.barycentric.resize(idx[0]);
|
||||
|
||||
return std::make_pair(faceRef, halfedgeBary);
|
||||
}
|
||||
} // namespace
|
||||
@@ -500,36 +507,16 @@ std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
|
||||
namespace manifold {
|
||||
|
||||
Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
Timer assemble;
|
||||
assemble.Start();
|
||||
#endif
|
||||
|
||||
if ((expandP_ > 0) != (op == Manifold::OpType::ADD))
|
||||
std::cout << "Warning! Result op type not compatible with constructor op "
|
||||
"type: coplanar faces may have incorrect results."
|
||||
<< std::endl;
|
||||
int c1, c2, c3;
|
||||
switch (op) {
|
||||
case Manifold::OpType::ADD:
|
||||
c1 = 1;
|
||||
c2 = 1;
|
||||
c3 = -1;
|
||||
if (kVerbose) std::cout << "ADD" << std::endl;
|
||||
break;
|
||||
case Manifold::OpType::SUBTRACT:
|
||||
c1 = 1;
|
||||
c2 = 0;
|
||||
c3 = -1;
|
||||
if (kVerbose) std::cout << "SUBTRACT" << std::endl;
|
||||
break;
|
||||
case Manifold::OpType::INTERSECT:
|
||||
c1 = 0;
|
||||
c2 = 0;
|
||||
c3 = 1;
|
||||
if (kVerbose) std::cout << "INTERSECT" << std::endl;
|
||||
break;
|
||||
default:
|
||||
throw std::invalid_argument("invalid enum: OpType.");
|
||||
}
|
||||
ASSERT((expandP_ > 0) == (op == Manifold::OpType::ADD), logicErr,
|
||||
"Result op type not compatible with constructor op type.");
|
||||
const int c1 = op == Manifold::OpType::INTERSECT ? 0 : 1;
|
||||
const int c2 = op == Manifold::OpType::ADD ? 1 : 0;
|
||||
const int c3 = op == Manifold::OpType::INTERSECT ? 1 : -1;
|
||||
|
||||
if (w03_.size() == 0) {
|
||||
if (w30_.size() != 0 && op == Manifold::OpType::ADD) {
|
||||
@@ -550,35 +537,36 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
VecDH<int> i21(x21_.size());
|
||||
VecDH<int> i03(w03_.size());
|
||||
VecDH<int> i30(w30_.size());
|
||||
thrust::transform(x12_.beginD(), x12_.endD(), i12.beginD(), c3 * _1);
|
||||
thrust::transform(x21_.beginD(), x21_.endD(), i21.beginD(), c3 * _1);
|
||||
thrust::transform(w03_.beginD(), w03_.endD(), i03.beginD(), c1 + c3 * _1);
|
||||
thrust::transform(w30_.beginD(), w30_.endD(), i30.beginD(), c2 + c3 * _1);
|
||||
|
||||
transform(policy_, x12_.begin(), x12_.end(), i12.begin(), c3 * _1);
|
||||
transform(policy_, x21_.begin(), x21_.end(), i21.begin(), c3 * _1);
|
||||
transform(policy_, w03_.begin(), w03_.end(), i03.begin(), c1 + c3 * _1);
|
||||
transform(policy_, w30_.begin(), w30_.end(), i30.begin(), c2 + c3 * _1);
|
||||
|
||||
VecDH<int> vP2R(inP_.NumVert());
|
||||
thrust::exclusive_scan(i03.beginD(), i03.endD(), vP2R.beginD(), 0, AbsSum());
|
||||
int numVertR = AbsSum()(vP2R.H().back(), i03.H().back());
|
||||
exclusive_scan(policy_, i03.begin(), i03.end(), vP2R.begin(), 0, AbsSum());
|
||||
int numVertR = AbsSum()(vP2R.back(), i03.back());
|
||||
const int nPv = numVertR;
|
||||
|
||||
VecDH<int> vQ2R(inQ_.NumVert());
|
||||
thrust::exclusive_scan(i30.beginD(), i30.endD(), vQ2R.beginD(), numVertR,
|
||||
AbsSum());
|
||||
numVertR = AbsSum()(vQ2R.H().back(), i30.H().back());
|
||||
exclusive_scan(policy_, i30.begin(), i30.end(), vQ2R.begin(), numVertR,
|
||||
AbsSum());
|
||||
numVertR = AbsSum()(vQ2R.back(), i30.back());
|
||||
const int nQv = numVertR - nPv;
|
||||
|
||||
VecDH<int> v12R(v12_.size());
|
||||
if (v12_.size() > 0) {
|
||||
thrust::exclusive_scan(i12.beginD(), i12.endD(), v12R.beginD(), numVertR,
|
||||
AbsSum());
|
||||
numVertR = AbsSum()(v12R.H().back(), i12.H().back());
|
||||
exclusive_scan(policy_, i12.begin(), i12.end(), v12R.begin(), numVertR,
|
||||
AbsSum());
|
||||
numVertR = AbsSum()(v12R.back(), i12.back());
|
||||
}
|
||||
const int n12 = numVertR - nPv - nQv;
|
||||
|
||||
VecDH<int> v21R(v21_.size());
|
||||
if (v21_.size() > 0) {
|
||||
thrust::exclusive_scan(i21.beginD(), i21.endD(), v21R.beginD(), numVertR,
|
||||
AbsSum());
|
||||
numVertR = AbsSum()(v21R.H().back(), i21.H().back());
|
||||
exclusive_scan(policy_, i21.begin(), i21.end(), v21R.begin(), numVertR,
|
||||
AbsSum());
|
||||
numVertR = AbsSum()(v21R.back(), i21.back());
|
||||
}
|
||||
const int n21 = numVertR - nPv - nQv - n12;
|
||||
|
||||
@@ -592,22 +580,20 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
outR.vertPos_.resize(numVertR);
|
||||
// Add vertices, duplicating for inclusion numbers not in [-1, 1].
|
||||
// Retained vertices from P and Q:
|
||||
thrust::for_each_n(zip(i03.beginD(), vP2R.beginD(), inP_.vertPos_.beginD()),
|
||||
inP_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
thrust::for_each_n(zip(i30.beginD(), vQ2R.beginD(), inQ_.vertPos_.beginD()),
|
||||
inQ_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(policy_, zip(i03.begin(), vP2R.begin(), inP_.vertPos_.begin()),
|
||||
inP_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(policy_, zip(i30.begin(), vQ2R.begin(), inQ_.vertPos_.begin()),
|
||||
inQ_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
// New vertices created from intersections:
|
||||
thrust::for_each_n(zip(i12.beginD(), v12R.beginD(), v12_.beginD()),
|
||||
i12.size(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
thrust::for_each_n(zip(i21.beginD(), v21R.beginD(), v21_.beginD()),
|
||||
i21.size(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(policy_, zip(i12.begin(), v12R.begin(), v12_.begin()), i12.size(),
|
||||
DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(policy_, zip(i21.begin(), v21R.begin(), v21_.begin()), i21.size(),
|
||||
DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
|
||||
if (kVerbose) {
|
||||
std::cout << nPv << " verts from inP" << std::endl;
|
||||
std::cout << nQv << " verts from inQ" << std::endl;
|
||||
std::cout << n12 << " new verts from edgesP -> facesQ" << std::endl;
|
||||
std::cout << n21 << " new verts from facesP -> edgesQ" << std::endl;
|
||||
}
|
||||
PRINT(nPv << " verts from inP");
|
||||
PRINT(nQv << " verts from inQ");
|
||||
PRINT(n12 << " new verts from edgesP -> facesQ");
|
||||
PRINT(n21 << " new verts from facesP -> edgesQ");
|
||||
|
||||
// Build up new polygonal faces from triangle intersections. At this point the
|
||||
// calculation switches from parallel to serial.
|
||||
@@ -620,76 +606,94 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
// This key is the face index of <P, Q>
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
|
||||
|
||||
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12.H(), v12R.H(),
|
||||
inP_.halfedge_.H(), true);
|
||||
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21.H(), v21R.H(),
|
||||
inQ_.halfedge_.H(), false);
|
||||
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true);
|
||||
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false);
|
||||
|
||||
// Level 4
|
||||
VecDH<int> faceEdge;
|
||||
VecDH<int> facePQ2R;
|
||||
std::tie(faceEdge, facePQ2R) =
|
||||
SizeOutput(outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ);
|
||||
std::tie(faceEdge, facePQ2R) = SizeOutput(
|
||||
outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ, policy_);
|
||||
|
||||
const int numFaceR = faceEdge.size() - 1;
|
||||
// This gets incremented for each halfedge that's added to a face so that the
|
||||
// next one knows where to slot in.
|
||||
VecDH<int> facePtrR = faceEdge;
|
||||
// Intersected halfedges are marked false.
|
||||
VecDH<bool> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
VecDH<bool> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
VecDH<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
VecDH<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
// The halfedgeRef contains the data that will become triBary once the faces
|
||||
// are triangulated.
|
||||
VecDH<Ref> halfedgeRef(2 * outR.NumEdge());
|
||||
|
||||
AppendPartialEdges(outR, wholeHalfedgeP.H(), facePtrR.H(), edgesP,
|
||||
halfedgeRef.H(), inP_, i03.H(), vP2R.H(), facePQ2R.begin(),
|
||||
true);
|
||||
AppendPartialEdges(outR, wholeHalfedgeQ.H(), facePtrR.H(), edgesQ,
|
||||
halfedgeRef.H(), inQ_, i30.H(), vQ2R.H(),
|
||||
facePQ2R.begin() + inP_.NumTri(), false);
|
||||
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
|
||||
i03, vP2R, facePQ2R.begin(), true);
|
||||
AppendPartialEdges(outR, wholeHalfedgeQ, facePtrR, edgesQ, halfedgeRef, inQ_,
|
||||
i30, vQ2R, facePQ2R.begin() + inP_.NumTri(), false);
|
||||
|
||||
AppendNewEdges(outR, facePtrR.H(), edgesNew, halfedgeRef.H(), facePQ2R.H(),
|
||||
AppendNewEdges(outR, facePtrR, edgesNew, halfedgeRef, facePQ2R,
|
||||
inP_.NumTri());
|
||||
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inP_, wholeHalfedgeP, i03, vP2R,
|
||||
facePQ2R.cptrD(), true);
|
||||
facePQ2R.cptrD(), true, policy_);
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
|
||||
facePQ2R.cptrD() + inP_.NumTri(), false);
|
||||
facePQ2R.cptrD() + inP_.NumTri(), false, policy_);
|
||||
|
||||
VecDH<BaryRef> faceRef;
|
||||
VecDH<int> halfedgeBary;
|
||||
std::tie(faceRef, halfedgeBary) = CalculateMeshRelation(
|
||||
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ);
|
||||
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ, policy_);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
assemble.Stop();
|
||||
Timer triangulate;
|
||||
triangulate.Start();
|
||||
#endif
|
||||
|
||||
// Level 6
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
|
||||
|
||||
outR.Face2Tri(faceEdge, faceRef, halfedgeBary);
|
||||
|
||||
outR.DuplicateMeshIDs();
|
||||
|
||||
outR.CollapseDegenerates();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
triangulate.Stop();
|
||||
Timer finish;
|
||||
finish.Start();
|
||||
Timer simplify;
|
||||
simplify.Start();
|
||||
#endif
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
|
||||
|
||||
outR.IncrementMeshIDs(0, outR.NumTri());
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
simplify.Stop();
|
||||
Timer sort;
|
||||
sort.Start();
|
||||
#endif
|
||||
|
||||
outR.Finish();
|
||||
|
||||
finish.Stop();
|
||||
if (kVerbose) {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
sort.Stop();
|
||||
if (ManifoldParams().verbose) {
|
||||
assemble.Print("Assembly");
|
||||
triangulate.Print("Triangulation");
|
||||
finish.Print("Finishing the manifold");
|
||||
simplify.Print("Simplification");
|
||||
sort.Print("Sorting");
|
||||
std::cout << outR.NumVert() << " verts and " << outR.NumTri() << " tris"
|
||||
<< std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
return outR;
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||