Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
311d25a8cf | ||
|
|
6fa0e7becb | ||
|
|
e425761d8e | ||
|
|
a4cc9ee54b | ||
|
|
2a7f9cf48f | ||
|
|
99740dafcb | ||
|
|
9157350a11 | ||
|
|
a8626f907c | ||
|
|
2f22b92352 | ||
|
|
a3d7991563 | ||
|
|
2aef6e4728 | ||
|
|
c167a48d36 | ||
|
|
f6cf8d23d3 | ||
|
|
da3f323113 | ||
|
|
8b7bf4231b | ||
|
|
ac2cfd857f | ||
|
|
285bb0754b | ||
|
|
ba5fead6d5 | ||
|
|
96c57c0591 | ||
|
|
4b65ff3afe | ||
|
|
709021d9d2 | ||
|
|
6cec951aed | ||
|
|
dd3445ddb1 | ||
|
|
37db0d0182 | ||
|
|
1b9243df2a | ||
|
|
8b4c79c485 | ||
|
|
7a197af83d | ||
|
|
a65fedb2f9 | ||
|
|
6ae9dee640 | ||
|
|
79daba1200 | ||
|
|
2f8e3174f7 | ||
|
|
cce35aa5a0 | ||
|
|
e727b115c0 | ||
|
|
f6431112a8 | ||
|
|
2643c291ed | ||
|
|
e024edf103 | ||
|
|
cbfeed1672 | ||
|
|
9dac47a76a | ||
|
|
eb41b9d36a | ||
|
|
72935213e8 | ||
|
|
e1ebc66425 | ||
|
|
ef52b0cce2 | ||
|
|
812ca5c30f | ||
|
|
f064b2a5fd | ||
|
|
18abd3ffcf | ||
|
|
1615b72e83 | ||
|
|
58fb7e3300 | ||
|
|
1959a30560 | ||
|
|
4bdb761d5e | ||
|
|
ba042ee8db | ||
|
|
97317888bc | ||
|
|
a9bf836574 | ||
|
|
29dc753513 | ||
|
|
3a517a490e | ||
|
|
aeba26760b | ||
|
|
85d74b4ccf | ||
|
|
a5f92e2522 | ||
|
|
fbe4cc6a86 | ||
|
|
f25d9601cb | ||
|
|
07513bd951 | ||
|
|
9a795162f4 | ||
|
|
cdce9e7618 | ||
|
|
628edc8e26 | ||
|
|
d209ead1c2 | ||
|
|
112f9ba501 | ||
|
|
2a63971983 | ||
|
|
4962ebf879 | ||
|
|
d713986f62 | ||
|
|
3b72852b0f | ||
|
|
e17eb038bf | ||
|
|
7b0758eddc | ||
|
|
20178cb209 | ||
|
|
61d87c503c | ||
|
|
7c2007771e | ||
|
|
d82cf061f8 | ||
|
|
dca9cce529 | ||
|
|
754a4811a5 | ||
|
|
83131c672e | ||
|
|
bd8da6ce1e | ||
|
|
3c736f9030 | ||
|
|
bb24367220 | ||
|
|
a97c666358 | ||
|
|
7c8ce3c935 | ||
|
|
4d434e4f8c | ||
|
|
9e9a22d66b | ||
|
|
893a4f8ac3 | ||
|
|
78c03f2611 | ||
|
|
2915f7aa90 | ||
|
|
95daa54591 | ||
|
|
2c597d1eef | ||
|
|
e2a8e94906 | ||
|
|
eae9653745 | ||
|
|
feb7fb6edd | ||
|
|
32f887d321 | ||
|
|
3a962cfa53 | ||
|
|
36ca7acf6c | ||
|
|
0704f62eaa | ||
|
|
5041c38424 | ||
|
|
c4bf392f66 | ||
|
|
3f42e5e6d7 | ||
|
|
376a7c676f | ||
|
|
e6bb87b2bb | ||
|
|
9477deaa9c | ||
|
|
02affd5247 | ||
|
|
171fe5466e | ||
|
|
5331b68485 | ||
|
|
20d7ef96fb | ||
|
|
3560bbf679 | ||
|
|
4415cfce46 | ||
|
|
bbb1fabcac | ||
|
|
f43dded234 | ||
|
|
05f9ceb999 | ||
|
|
422c7f26d9 | ||
|
|
a701d2be8e | ||
|
|
5b5f56674a | ||
|
|
1b7b365c3a | ||
|
|
812b056179 | ||
|
|
bd46bfd1cf | ||
|
|
a7b457db74 | ||
|
|
87059508e7 | ||
|
|
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,js,ts,html'
|
||||
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
|
||||
|
||||
- name: Download built examples
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
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
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
@@ -10,35 +10,222 @@ 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
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-22.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:12.0.1-devel-ubuntu22.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: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
|
||||
run: |
|
||||
mkdir buildCUDA
|
||||
cd buildCUDA
|
||||
cmake -DCMAKE_BUILD_TYPE=Release .. && make
|
||||
- name: Build CPP
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
|
||||
run: |
|
||||
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: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
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_cbind:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build C bindings with OMP and CUDA
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=OMP -DMANIFOLD_USE_CUDA=ON .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test --gtest_filter=CBIND.*
|
||||
|
||||
build_wasm:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Setup WASM
|
||||
run: |
|
||||
# setup emscripten
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build WASM
|
||||
run: |
|
||||
source ./emsdk/emsdk_env.sh
|
||||
mkdir build
|
||||
cd build
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
|
||||
- name: Test WASM
|
||||
run: |
|
||||
cd build/test
|
||||
node ./manifold_test.js
|
||||
- name: Test examples
|
||||
run: |
|
||||
cd bindings/wasm/examples
|
||||
npm ci
|
||||
npm run build
|
||||
npm test
|
||||
- name: Upload WASM files
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wasm
|
||||
path: bindings/wasm/examples/dist/
|
||||
retention-days: 90
|
||||
|
||||
build_windows:
|
||||
timeout-minutes: 30
|
||||
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: recursive
|
||||
- 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 -DPYBIND11_FINDPYTHON=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/test
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mac:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install pkg-config assimp
|
||||
pip install trimesh
|
||||
- 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: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
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
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
@@ -106,3 +106,17 @@ Temporary Items
|
||||
# End of https://www.gitignore.io/api/osx,c++,cuda,linux,cmake
|
||||
|
||||
build
|
||||
buildWASM
|
||||
docs/html
|
||||
node_modules/
|
||||
bindings/wasm/examples/built
|
||||
bindings/wasm/examples/dist
|
||||
bindings/wasm/examples/public/manifold*
|
||||
__pycache__
|
||||
.vscode/c_cpp_properties.json
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.cache
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
@@ -0,0 +1,19 @@
|
||||
[submodule "third_party/thrust"]
|
||||
path = src/third_party/thrust
|
||||
url = https://github.com/NVIDIA/thrust
|
||||
[submodule "third_party/glm"]
|
||||
path = src/third_party/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "third_party/google_test"]
|
||||
path = test/third_party/google_test
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third_party/pybind11"]
|
||||
path = bindings/python/third_party/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
[submodule "src/third_party/clipper2"]
|
||||
path = src/third_party/clipper2
|
||||
url = https://github.com/AngusJohnson/clipper2
|
||||
[submodule "src/third_party/quickhull"]
|
||||
path = src/third_party/quickhull
|
||||
url = https://github.com/akuukka/quickhull
|
||||
|
||||
@@ -1,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,26 +1,26 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_break_on_failure",
|
||||
"--gtest_filter=Manifold.GetMesh"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
"environment": [
|
||||
{
|
||||
"name": "manifold test",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/build/test/manifold_test",
|
||||
"args": [
|
||||
"--gtest_break_on_failure",
|
||||
"--gtest_filter=Manifold.GetMesh"
|
||||
],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}/build/test",
|
||||
"environment": [
|
||||
{
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
"name": "MALLOC_CHECK_",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,98 +1,143 @@
|
||||
{
|
||||
"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",
|
||||
"__std_stream": "cpp",
|
||||
"__verbose_abort": "cpp",
|
||||
"any": "cpp",
|
||||
"barrier": "cpp",
|
||||
"charconv": "cpp",
|
||||
"coroutine": "cpp",
|
||||
"latch": "cpp",
|
||||
"semaphore": "cpp",
|
||||
"span": "cpp",
|
||||
"__sso_allocator": "cpp"
|
||||
},
|
||||
"C_Cpp.clang_format_fallbackStyle": "google",
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DBUILD_SHARED_LIBS=ON",
|
||||
"-DMANIFOLD_CBIND=ON",
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-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,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"Coplanarity",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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>
|
||||
Geoff deRosenroll <geoffder>
|
||||
Google LLC
|
||||
@@ -1,49 +1,92 @@
|
||||
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 "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(MANIFOLD_USE_CUDA "Enable GPU support via CUDA" OFF)
|
||||
option(MANIFOLD_PYBIND "Build python bindings" ON)
|
||||
option(MANIFOLD_CBIND "Build C (FFI) bindings" OFF)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS -O3)
|
||||
|
||||
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 -D_LIBCUDACXX_HAS_THREAD_API_EXTERNAL -D_LIBCUDACXX_HAS_THREAD_API_CUDA)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
endif()
|
||||
|
||||
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 "Enable PyBind to perform FindPython for you" 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 "Build CGAL performance comparisons" OFF)
|
||||
|
||||
add_subdirectory(utilities)
|
||||
add_subdirectory(collider)
|
||||
add_subdirectory(polygon)
|
||||
add_subdirectory(manifold)
|
||||
add_subdirectory(third_party)
|
||||
add_subdirectory(meshIO)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(tools)
|
||||
option(BUILD_SHARED_LIBS "Build dynamic/shared libraries" OFF)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
|
||||
# Allow shared libraries to be relocatable (add Place Independent Code flag).
|
||||
# Also include when statically linking C bindings to avoid issues with bundling
|
||||
# artefacts in host languages using them for FFI.
|
||||
add_compile_options(-fPIC)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
enable_language(CUDA)
|
||||
find_package(CUDA REQUIRED)
|
||||
|
||||
# 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(bindings)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_subdirectory(meshIO)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_TEST)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
endif()
|
||||
|
||||
@@ -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,122 @@
|
||||
[](https://codecov.io/github/elalish/manifold)
|
||||
[](https://twitter.com/intent/follow?screen_name=manifoldcad)
|
||||
|
||||
## Users
|
||||
|
||||
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
|
||||
|
||||
For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:
|
||||
|
||||
<img src="https://elalish.github.io/manifold/samples/models/perfSpheres.png" width="350px"/>
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not GPU-accelerated, but it's still quite fast and a good way to test out our Manifold library.
|
||||
|
||||

|
||||
|
||||
### Note for Firefox users
|
||||
|
||||
If you find the editor is stuck on **Loading...**, setting
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository may solve the problem.
|
||||
|
||||
# Manifold
|
||||
|
||||
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).
|
||||
[**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
|
||||
|
||||
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
|
||||
|
||||
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
|
||||
|
||||
We have four core dependencies, making use of submodules to ensure compatibility:
|
||||
- `graphlite`: connected components algorithm
|
||||
- `Clipper2`: provides our 2D subsystem
|
||||
- `GLM`: a compact vector library
|
||||
- `Thrust`: Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
|
||||
|
||||
## What's here
|
||||
|
||||
This library is 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, TBB, and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
|
||||
|
||||
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible, 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
|
||||
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 `manifold3d` automatically. To
|
||||
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
|
||||
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
|
||||
can be found in `bindings/python/examples`. To see exported samples, run:
|
||||
```
|
||||
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 manifold3d
|
||||
>>> help(manifold3d)
|
||||
```
|
||||
|
||||
For more detailed documentation, please refer to the C++ API.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -22,4 +124,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,25 @@
|
||||
# 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 AND MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
add_subdirectory(wasm)
|
||||
endif()
|
||||
@@ -0,0 +1,22 @@
|
||||
project(manifoldc)
|
||||
|
||||
add_library(${PROJECT_NAME} manifoldc.cpp conv.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE meshIO)
|
||||
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE manifold sdf graphlite cross_section
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
@@ -0,0 +1,85 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2) {
|
||||
auto p1 = glm::vec3(x1, y1, z1);
|
||||
auto p2 = glm::vec3(x2, y2, z2);
|
||||
auto box = new (mem) Box(p1, p2);
|
||||
return to_c(box);
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
|
||||
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
|
||||
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
|
||||
auto v = from_c(b)->Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
auto v = from_c(b)->Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
|
||||
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return from_c(b)->Contains(p);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
auto box = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(b)->Transform(mat);
|
||||
return to_c(new (mem) Box(transformed));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto scaled = (*from_c(b)) * p;
|
||||
return to_c(new (mem) Box(scaled));
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return from_c(b)->DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return from_c(a)->DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
|
||||
@@ -0,0 +1,237 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "include/types.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
return reinterpret_cast<ManifoldManifold *>(m);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms) {
|
||||
return reinterpret_cast<ManifoldManifoldVec *>(ms);
|
||||
}
|
||||
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs) {
|
||||
return reinterpret_cast<ManifoldCrossSection *>(cs);
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv) {
|
||||
return reinterpret_cast<ManifoldCrossSectionVec *>(csv);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon *>(m);
|
||||
}
|
||||
|
||||
ManifoldPolygons *to_c(manifold::Polygons *m) {
|
||||
return reinterpret_cast<ManifoldPolygons *>(m);
|
||||
}
|
||||
|
||||
ManifoldMesh *to_c(manifold::Mesh *m) {
|
||||
return reinterpret_cast<ManifoldMesh *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
ManifoldOpType to_c(manifold::OpType optype) {
|
||||
ManifoldOpType op = MANIFOLD_ADD;
|
||||
switch (optype) {
|
||||
case manifold::OpType::Add:
|
||||
break;
|
||||
case manifold::OpType::Subtract:
|
||||
op = MANIFOLD_SUBTRACT;
|
||||
break;
|
||||
case manifold::OpType::Intersect:
|
||||
op = MANIFOLD_INTERSECT;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
ManifoldError e = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
break;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
break;
|
||||
case Manifold::Error::NotManifold:
|
||||
e = MANIFOLD_NOT_MANIFOLD;
|
||||
break;
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
|
||||
break;
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
break;
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::InvalidConstruction:
|
||||
e = MANIFOLD_INVALID_CONSTRUCTION;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
|
||||
ManifoldBox *to_c(manifold::Box *m) {
|
||||
return reinterpret_cast<ManifoldBox *>(m);
|
||||
}
|
||||
|
||||
ManifoldRect *to_c(manifold::Rect *m) {
|
||||
return reinterpret_cast<ManifoldRect *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
|
||||
|
||||
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldProperties to_c(manifold::Properties p) {
|
||||
return {p.surfaceArea, p.volume};
|
||||
}
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m) {
|
||||
return reinterpret_cast<manifold::Manifold const *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms) {
|
||||
return reinterpret_cast<ManifoldVec *>(ms);
|
||||
}
|
||||
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs) {
|
||||
return reinterpret_cast<manifold::CrossSection *>(cs);
|
||||
}
|
||||
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv) {
|
||||
return reinterpret_cast<CrossSectionVec *>(csv);
|
||||
}
|
||||
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m) {
|
||||
return reinterpret_cast<manifold::Polygons const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m) {
|
||||
return reinterpret_cast<manifold::Mesh const *>(m);
|
||||
}
|
||||
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
|
||||
return reinterpret_cast<manifold::MeshGL const *>(m);
|
||||
}
|
||||
|
||||
OpType from_c(ManifoldOpType optype) {
|
||||
auto op = OpType::Add;
|
||||
switch (optype) {
|
||||
case MANIFOLD_ADD:
|
||||
break;
|
||||
case MANIFOLD_SUBTRACT:
|
||||
op = OpType::Subtract;
|
||||
break;
|
||||
case MANIFOLD_INTERSECT:
|
||||
op = OpType::Intersect;
|
||||
break;
|
||||
};
|
||||
return op;
|
||||
}
|
||||
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
|
||||
auto fr = CrossSection::FillRule::EvenOdd;
|
||||
switch (fillrule) {
|
||||
case MANIFOLD_FILL_RULE_EVEN_ODD:
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NON_ZERO:
|
||||
fr = CrossSection::FillRule::NonZero;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_POSITIVE:
|
||||
fr = CrossSection::FillRule::Positive;
|
||||
break;
|
||||
case MANIFOLD_FILL_RULE_NEGATIVE:
|
||||
fr = CrossSection::FillRule::Negative;
|
||||
break;
|
||||
};
|
||||
return fr;
|
||||
}
|
||||
|
||||
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
|
||||
auto jt = CrossSection::JoinType::Square;
|
||||
switch (join_type) {
|
||||
case MANIFOLD_JOIN_TYPE_SQUARE:
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_ROUND:
|
||||
jt = CrossSection::JoinType::Round;
|
||||
break;
|
||||
case MANIFOLD_JOIN_TYPE_MITER:
|
||||
jt = CrossSection::JoinType::Miter;
|
||||
break;
|
||||
};
|
||||
return jt;
|
||||
}
|
||||
|
||||
const manifold::Box *from_c(ManifoldBox *m) {
|
||||
return reinterpret_cast<manifold::Box const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Rect *from_c(ManifoldRect *m) {
|
||||
return reinterpret_cast<manifold::Rect const *>(m);
|
||||
}
|
||||
|
||||
glm::vec2 from_c(ManifoldVec2 v) { return glm::vec2(v.x, v.y); }
|
||||
|
||||
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::ivec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec4>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem) {
|
||||
return to_c(new (mem) CrossSection());
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto cross = *from_c(cs);
|
||||
return to_c(new (mem) CrossSection(cross));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
|
||||
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
|
||||
ManifoldPolygons *p,
|
||||
ManifoldFillRule fr) {
|
||||
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem) {
|
||||
return to_c(new (mem) CrossSectionVec());
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz) {
|
||||
return to_c(new (mem) CrossSectionVec(sz));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
|
||||
size_t sz) {
|
||||
from_c(csv)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv) {
|
||||
return from_c(csv)->size();
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_vec_get(
|
||||
void *mem, ManifoldCrossSectionVec *csv, int idx) {
|
||||
auto cs = (*from_c(csv))[idx];
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
|
||||
ManifoldCrossSection *cs) {
|
||||
(*from_c(csv))[idx] = *from_c(cs);
|
||||
}
|
||||
|
||||
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
|
||||
ManifoldCrossSection *cs) {
|
||||
return from_c(csv)->push_back(*from_c(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
|
||||
int center) {
|
||||
auto cs = CrossSection::Square(glm::vec2(x, y), center);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
|
||||
int circular_segments) {
|
||||
auto cs = CrossSection::Circle(radius, circular_segments);
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b,
|
||||
ManifoldOpType op) {
|
||||
auto cs = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_batch_boolean(
|
||||
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op) {
|
||||
auto cs = CrossSection::BatchBoolean(*from_c(csv), from_c(op));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_union(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b) {
|
||||
auto cs = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_difference(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
|
||||
auto cs = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_intersection(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
|
||||
auto cs = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y) {
|
||||
auto translated = from_c(cs)->Translate(glm::vec2(x, y));
|
||||
return to_c(new (mem) CrossSection(translated));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float deg) {
|
||||
auto rotated = from_c(cs)->Rotate(deg);
|
||||
return to_c(new (mem) CrossSection(rotated));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y) {
|
||||
auto scaled = from_c(cs)->Scale(glm::vec2(x, y));
|
||||
return to_c(new (mem) CrossSection(scaled));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float ax_x, float ax_y) {
|
||||
auto mirrored = from_c(cs)->Mirror(glm::vec2(ax_x, ax_y));
|
||||
return to_c(new (mem) CrossSection(mirrored));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3) {
|
||||
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
|
||||
auto transformed = from_c(cs)->Transform(mat);
|
||||
return to_c(new (mem) CrossSection(transformed));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_warp(
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float)) {
|
||||
std::function<void(glm::vec2 & v)> warp = [fun](glm::vec2 &v) {
|
||||
v = from_c(fun(v.x, v.y));
|
||||
};
|
||||
auto warped = from_c(cs)->Warp(warp);
|
||||
return to_c(new (mem) CrossSection(warped));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
double epsilon) {
|
||||
auto simplified = from_c(cs)->Simplify(epsilon);
|
||||
return to_c(new (mem) CrossSection(simplified));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_offset(
|
||||
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, int circular_segments) {
|
||||
auto offset =
|
||||
from_c(cs)->Offset(delta, from_c(jt), miter_limit, circular_segments);
|
||||
return to_c(new (mem) CrossSection(offset));
|
||||
}
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->Area();
|
||||
}
|
||||
|
||||
int manifold_cross_section_num_vert(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->NumVert();
|
||||
}
|
||||
|
||||
int manifold_cross_section_num_contour(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->NumContour();
|
||||
}
|
||||
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection *cs) {
|
||||
return from_c(cs)->IsEmpty();
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_cross_section_bounds(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto rect = from_c(cs)->Bounds();
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
|
||||
ManifoldCrossSection *cs) {
|
||||
auto ps = from_c(cs)->ToPolygons();
|
||||
return to_c(new (mem) Polygons(ps));
|
||||
}
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_compose(
|
||||
void *mem, ManifoldCrossSectionVec *csv) {
|
||||
auto cs = CrossSection::Compose(*from_c(csv));
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_decompose(
|
||||
void *mem, ManifoldCrossSection *cs) {
|
||||
auto comps = from_c(cs)->Decompose();
|
||||
return to_c(new (mem) CrossSectionVec(comps));
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include <types.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m);
|
||||
ManifoldManifoldVec *to_c(ManifoldVec *ms);
|
||||
ManifoldCrossSection *to_c(manifold::CrossSection *cs);
|
||||
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldRect *to_c(manifold::Rect *m);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldVec2 to_c(glm::vec2 v);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
ManifoldVec *from_c(ManifoldManifoldVec *ms);
|
||||
const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
|
||||
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
OpType from_c(ManifoldOpType op);
|
||||
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
|
||||
CrossSection::JoinType from_c(ManifoldJoinType jt);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Rect *from_c(ManifoldRect *r);
|
||||
glm::vec2 from_c(ManifoldVec2 v);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector_of_array(T *ts, size_t length) {
|
||||
auto vec = std::vector<T>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *copy_data(void *mem, std::vector<T> v) {
|
||||
T *ts = reinterpret_cast<T *>(mem);
|
||||
memcpy(ts, v.data(), sizeof(T) * v.size());
|
||||
return ts;
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Polygons
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length);
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length);
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p);
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps);
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx);
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx);
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx);
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx);
|
||||
|
||||
// Mesh Construction
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris);
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent);
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// SDF
|
||||
// By default, the execution policy (sequential or parallel) of
|
||||
// manifold_level_set will be chosen automatically depending on the size of the
|
||||
// job and whether Manifold has been compiled with a PAR backend. If you are
|
||||
// using these bindings from a language that has a runtime lock preventing the
|
||||
// parallel execution of closures, then you should use manifold_level_set_seq to
|
||||
// force sequential execution.
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
// The _context variants of manifold_level_set allow a pointer to be passed
|
||||
// back to each invocation of the sdf function pointer, for languages that
|
||||
// need additional data.
|
||||
ManifoldMeshGL *manifold_level_set_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx);
|
||||
ManifoldMeshGL *manifold_level_set_seq_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx);
|
||||
|
||||
// Manifold Vectors
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem);
|
||||
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz);
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz);
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms);
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx);
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
ManifoldManifold *m);
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
|
||||
ManifoldManifold *m);
|
||||
|
||||
// Manifold Booleans
|
||||
|
||||
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b, ManifoldOpType op);
|
||||
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
|
||||
ManifoldOpType op);
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
|
||||
// Manifold Transformations
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz);
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float));
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
|
||||
|
||||
// Manifold Shapes / Constructors
|
||||
ManifoldManifold *manifold_empty(void *mem);
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem);
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center);
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center);
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_idxs);
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Manifold Info
|
||||
int manifold_is_empty(ManifoldManifold *m);
|
||||
ManifoldError manifold_status(ManifoldManifold *m);
|
||||
int manifold_num_vert(ManifoldManifold *m);
|
||||
int manifold_num_edge(ManifoldManifold *m);
|
||||
int manifold_num_tri(ManifoldManifold *m);
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
|
||||
float manifold_precision(ManifoldManifold *m);
|
||||
int manifold_genus(ManifoldManifold *m);
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
|
||||
int manifold_get_circular_segments(float radius);
|
||||
int manifold_original_id(ManifoldManifold *m);
|
||||
uint32_t manifold_reserve_ids(uint32_t n);
|
||||
ManifoldManifold *manifold_set_properties(
|
||||
void *mem, ManifoldManifold *m, int num_prop,
|
||||
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop));
|
||||
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
|
||||
int gaussian_idx, int mean_idx);
|
||||
|
||||
// CrossSection Shapes/Constructors
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem);
|
||||
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
|
||||
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr);
|
||||
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
|
||||
ManifoldPolygons *p,
|
||||
ManifoldFillRule fr);
|
||||
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
|
||||
int center);
|
||||
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
|
||||
int circular_segments);
|
||||
ManifoldCrossSection *manifold_cross_section_compose(
|
||||
void *mem, ManifoldCrossSectionVec *csv);
|
||||
ManifoldCrossSectionVec *manifold_cross_section_decompose(
|
||||
void *mem, ManifoldCrossSection *cs);
|
||||
|
||||
// CrossSection Vectors
|
||||
|
||||
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem);
|
||||
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz);
|
||||
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
|
||||
size_t sz);
|
||||
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv);
|
||||
ManifoldCrossSection *manifold_cross_section_vec_get(
|
||||
void *mem, ManifoldCrossSectionVec *csv, int idx);
|
||||
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
|
||||
ManifoldCrossSection *cs);
|
||||
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
|
||||
ManifoldCrossSection *cs);
|
||||
|
||||
// CrossSection Booleans
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b,
|
||||
ManifoldOpType op);
|
||||
ManifoldCrossSection *manifold_cross_section_batch_boolean(
|
||||
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op);
|
||||
ManifoldCrossSection *manifold_cross_section_union(void *mem,
|
||||
ManifoldCrossSection *a,
|
||||
ManifoldCrossSection *b);
|
||||
ManifoldCrossSection *manifold_cross_section_difference(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
|
||||
ManifoldCrossSection *manifold_cross_section_intersection(
|
||||
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
|
||||
|
||||
// CrossSection Transformation
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y);
|
||||
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float deg);
|
||||
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x, float y);
|
||||
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float ax_x, float ax_y);
|
||||
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
float x1, float y1,
|
||||
float x2, float y2,
|
||||
float x3, float y3);
|
||||
ManifoldCrossSection *manifold_cross_section_warp(
|
||||
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float));
|
||||
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
|
||||
ManifoldCrossSection *cs,
|
||||
double epsilon);
|
||||
ManifoldCrossSection *manifold_cross_section_offset(
|
||||
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
|
||||
double miter_limit, int circular_segments);
|
||||
|
||||
// CrossSection Info
|
||||
|
||||
double manifold_cross_section_area(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_num_vert(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_num_contour(ManifoldCrossSection *cs);
|
||||
int manifold_cross_section_is_empty(ManifoldCrossSection *cs);
|
||||
ManifoldRect *manifold_cross_section_bounds(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
|
||||
ManifoldCrossSection *cs);
|
||||
|
||||
// Rectangle
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2);
|
||||
ManifoldVec2 manifold_rect_min(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_max(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r);
|
||||
ManifoldVec2 manifold_rect_center(ManifoldRect *r);
|
||||
float manifold_rect_scale(ManifoldRect *r);
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y);
|
||||
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b);
|
||||
void manifold_rect_include_pt(ManifoldRect *r, float x, float y);
|
||||
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b);
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
|
||||
float y1, float x2, float y2, float x3,
|
||||
float y3);
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
|
||||
float y);
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y);
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r);
|
||||
int manifold_rect_is_empty(ManifoldRect *r);
|
||||
int manifold_rect_is_finite(ManifoldRect *r);
|
||||
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
|
||||
ManifoldRect *r);
|
||||
|
||||
// Bounding Box
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2);
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b);
|
||||
float manifold_box_scale(ManifoldBox *b);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
|
||||
int manifold_box_is_finite(ManifoldBox *b);
|
||||
|
||||
// Static Quality Globals
|
||||
void manifold_set_min_circular_angle(float degrees);
|
||||
void manifold_set_min_circular_edge_length(float length);
|
||||
void manifold_set_circular_segments(int number);
|
||||
|
||||
// Manifold Mesh Extraction
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// memory size
|
||||
size_t manifold_manifold_size();
|
||||
size_t manifold_manifold_vec_size();
|
||||
size_t manifold_cross_section_size();
|
||||
size_t manifold_cross_section_vec_size();
|
||||
size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
size_t manifold_meshgl_size();
|
||||
size_t manifold_box_size();
|
||||
size_t manifold_rect_size();
|
||||
size_t manifold_curvature_size();
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_manifold(ManifoldManifold *m);
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms);
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *m);
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_destruct_box(ManifoldBox *b);
|
||||
void manifold_destruct_rect(ManifoldRect *b);
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_manifold(ManifoldManifold *m);
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms);
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *cs);
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv);
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_delete_polygons(ManifoldPolygons *p);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_delete_box(ManifoldBox *b);
|
||||
void manifold_delete_rect(ManifoldRect *b);
|
||||
|
||||
// MeshIO / Export
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ManifoldMaterial *manifold_material(void *mem);
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert);
|
||||
ManifoldExportOptions *manifold_export_options(void *mem);
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted);
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat);
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options);
|
||||
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
|
||||
int force_cleanup);
|
||||
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
void manifold_destruct_material(ManifoldMaterial *m);
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *options);
|
||||
|
||||
void manifold_delete_material(ManifoldMaterial *m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions *options);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
// opaque pointers
|
||||
|
||||
typedef struct ManifoldManifold ManifoldManifold;
|
||||
typedef struct ManifoldManifoldVec ManifoldManifoldVec;
|
||||
typedef struct ManifoldCrossSection ManifoldCrossSection;
|
||||
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
|
||||
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
|
||||
typedef struct ManifoldPolygons ManifoldPolygons;
|
||||
typedef struct ManifoldMesh ManifoldMesh;
|
||||
typedef struct ManifoldMeshGL ManifoldMeshGL;
|
||||
typedef struct ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldRect ManifoldRect;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
#endif
|
||||
|
||||
// structs
|
||||
|
||||
typedef struct ManifoldManifoldPair {
|
||||
ManifoldManifold* first;
|
||||
ManifoldManifold* second;
|
||||
} ManifoldManifoldPair;
|
||||
|
||||
typedef struct ManifoldVec2 {
|
||||
float x;
|
||||
float y;
|
||||
} ManifoldVec2;
|
||||
|
||||
typedef struct ManifoldVec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} ManifoldVec3;
|
||||
|
||||
typedef struct ManifoldIVec3 {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} ManifoldIVec3;
|
||||
|
||||
typedef struct ManifoldVec4 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
} ManifoldVec4;
|
||||
|
||||
typedef struct ManifoldProperties {
|
||||
float surface_area;
|
||||
float volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
// enums
|
||||
|
||||
typedef enum ManifoldOpType {
|
||||
MANIFOLD_ADD,
|
||||
MANIFOLD_SUBTRACT,
|
||||
MANIFOLD_INTERSECT
|
||||
} ManifoldOpType;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
MANIFOLD_NO_ERROR,
|
||||
MANIFOLD_NON_FINITE_VERTEX,
|
||||
MANIFOLD_NOT_MANIFOLD,
|
||||
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_PROPERTIES_WRONG_LENGTH,
|
||||
MANIFOLD_MISSING_POSITION_PROPERTIES,
|
||||
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_TRANSFORM_WRONG_LENGTH,
|
||||
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
|
||||
MANIFOLD_FACE_ID_WRONG_LENGTH,
|
||||
MANIFOLD_INVALID_CONSTRUCTION,
|
||||
} ManifoldError;
|
||||
|
||||
typedef enum ManifoldFillRule {
|
||||
MANIFOLD_FILL_RULE_EVEN_ODD,
|
||||
MANIFOLD_FILL_RULE_NON_ZERO,
|
||||
MANIFOLD_FILL_RULE_POSITIVE,
|
||||
MANIFOLD_FILL_RULE_NEGATIVE
|
||||
} ManifoldFillRule;
|
||||
|
||||
typedef enum ManifoldJoinType {
|
||||
MANIFOLD_JOIN_TYPE_SQUARE,
|
||||
MANIFOLD_JOIN_TYPE_ROUND,
|
||||
MANIFOLD_JOIN_TYPE_MITER,
|
||||
} ManifoldJoinType;
|
||||
@@ -0,0 +1,571 @@
|
||||
#include <conv.h>
|
||||
#include <cross_section.h>
|
||||
#include <manifold.h>
|
||||
#include <manifoldc.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "box.cpp"
|
||||
#include "cross.cpp"
|
||||
#include "include/conv.h"
|
||||
#include "include/types.h"
|
||||
#include "rect.cpp"
|
||||
#include "types.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshio.cpp"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length, float level,
|
||||
bool seq) {
|
||||
// typing with std::function rather than auto compiles when CUDA is on,
|
||||
// passing it into GPU (and crashing) is avoided dynamically in `sdf.h`
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
ManifoldMeshGL *level_set_context(
|
||||
void *mem, float (*sdf_context)(float, float, float, void *),
|
||||
ManifoldBox *bounds, float edge_length, float level, bool seq, void *ctx) {
|
||||
// Bind function with context argument to one without
|
||||
using namespace std::placeholders;
|
||||
std::function<float(float, float, float)> sdf =
|
||||
std::bind(sdf_context, _1, _2, _3, ctx);
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<glm::vec2>;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back({ps[i].x, ps[i].y});
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<SimplePolygon>;
|
||||
auto polys = reinterpret_cast<SimplePolygon **>(ps);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back(*polys[i]);
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p) {
|
||||
return from_c(p)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_length(ManifoldPolygons *ps) {
|
||||
return from_c(ps)->size();
|
||||
}
|
||||
|
||||
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx) {
|
||||
return (*from_c(ps))[idx].size();
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
|
||||
int idx) {
|
||||
return to_c((*from_c(p))[idx]);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
|
||||
ManifoldPolygons *ps,
|
||||
int idx) {
|
||||
auto sp = (*from_c(ps))[idx];
|
||||
return to_c(new (mem) SimplePolygon(sp));
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
|
||||
int pt_idx) {
|
||||
return to_c((*from_c(ps))[simple_idx][pt_idx]);
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem) {
|
||||
return to_c(new (mem) ManifoldVec());
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz) {
|
||||
return to_c(new (mem) ManifoldVec(sz));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz) {
|
||||
from_c(ms)->reserve(sz);
|
||||
}
|
||||
|
||||
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms) {
|
||||
return from_c(ms)->size();
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
|
||||
int idx) {
|
||||
auto m = (*from_c(ms))[idx];
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
|
||||
ManifoldManifold *m) {
|
||||
(*from_c(ms))[idx] = *from_c(m);
|
||||
}
|
||||
|
||||
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
|
||||
ManifoldManifold *m) {
|
||||
return from_c(ms)->push_back(*from_c(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b, ManifoldOpType op) {
|
||||
auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
|
||||
ManifoldOpType op) {
|
||||
auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b) {
|
||||
auto pair = from_c(a)->Split(*from_c(b));
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto pair = from_c(m)->SplitByPlane(normal, offset);
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
|
||||
return to_c(new (mem) Manifold(trimmed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto v = glm::vec3(x, y, z);
|
||||
auto translated = from_c(m)->Translate(v);
|
||||
return to_c(new (mem) Manifold(translated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto rotated = from_c(m)->Rotate(x, y, z);
|
||||
return to_c(new (mem) Manifold(rotated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto s = glm::vec3(x, y, z);
|
||||
auto scaled = from_c(m)->Scale(s);
|
||||
return to_c(new (mem) Manifold(scaled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(m)->Transform(mat);
|
||||
return to_c(new (mem) Manifold(transformed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
|
||||
float ny, float nz) {
|
||||
auto mirrored = from_c(m)->Mirror({nx, ny, nz});
|
||||
return to_c(new (mem) Manifold(mirrored));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float)) {
|
||||
std::function<void(glm::vec3 & v)> warp = [fun](glm::vec3 &v) {
|
||||
v = from_c(fun(v.x, v.y, v.z));
|
||||
};
|
||||
auto warped = from_c(m)->Warp(warp);
|
||||
return to_c(new (mem) Manifold(warped));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, false);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, true);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx) {
|
||||
return level_set_context(mem, sdf, bounds, edge_length, level, false, ctx);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq_context(
|
||||
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
|
||||
float edge_length, float level, void *ctx) {
|
||||
return level_set_context(mem, sdf, bounds, edge_length, level, true, ctx);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
|
||||
auto refined = from_c(m)->Refine(refine);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem) {
|
||||
return to_c(new (mem) Manifold());
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m) {
|
||||
return to_c(new (mem) Manifold(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem) {
|
||||
auto m = Manifold::Tetrahedron();
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center) {
|
||||
auto size = glm::vec3(x, y, z);
|
||||
auto m = Manifold::Cube(size, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center) {
|
||||
auto m = Manifold::Cylinder(height, radius_low, radius_high,
|
||||
circular_segments, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Sphere(radius, circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (int i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
|
||||
auto m = Manifold(*from_c(mesh));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y) {
|
||||
auto scale = glm::vec2(scale_x, scale_y);
|
||||
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
|
||||
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms) {
|
||||
auto composed = Manifold::Compose(*from_c(ms));
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m) {
|
||||
auto comps = from_c(m)->Decompose();
|
||||
return to_c(new (mem) std::vector<Manifold>(comps));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m) {
|
||||
auto mesh = from_c(m)->GetMeshGL();
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
|
||||
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->triVerts.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->mergeFromVert.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runOriginalID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runTransform.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->faceID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->halfedgeTangent.size();
|
||||
}
|
||||
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->vertProperties);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->triVerts);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeFromVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeToVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runOriginalID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runTransform);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->faceID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
|
||||
auto orig = from_c(m)->AsOriginal();
|
||||
return to_c(new (mem) Manifold(orig));
|
||||
}
|
||||
|
||||
int manifold_original_id(ManifoldManifold *m) {
|
||||
return from_c(m)->OriginalID();
|
||||
}
|
||||
|
||||
int manifold_is_empty(ManifoldManifold *m) { return from_c(m)->IsEmpty(); }
|
||||
|
||||
ManifoldError manifold_status(ManifoldManifold *m) {
|
||||
auto error = from_c(m)->Status();
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
|
||||
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
|
||||
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
|
||||
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
|
||||
return to_c(from_c(m)->GetProperties());
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
|
||||
auto box = from_c(m)->BoundingBox();
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
|
||||
|
||||
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
|
||||
|
||||
ManifoldManifold *manifold_set_properties(void *mem, ManifoldManifold *m,
|
||||
int num_prop,
|
||||
void (*fun)(float *new_prop,
|
||||
ManifoldVec3 position,
|
||||
const float *old_prop)) {
|
||||
std::function<void(float *, glm::vec3, const float *)> f =
|
||||
[fun](float *new_prop, glm::vec3 v, const float *old_prop) {
|
||||
fun(new_prop, to_c(v), old_prop);
|
||||
};
|
||||
auto man = from_c(m)->SetProperties(num_prop, f);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
};
|
||||
|
||||
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
|
||||
int gaussian_idx, int mean_idx) {
|
||||
auto man = from_c(m)->CalculateCurvature(gaussian_idx, mean_idx);
|
||||
return to_c(new (mem) Manifold(man));
|
||||
}
|
||||
|
||||
// Static Quality Globals
|
||||
|
||||
void manifold_set_min_circular_angle(float degrees) {
|
||||
Quality::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
Quality::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Quality::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
return Quality::GetCircularSegments(radius);
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
|
||||
size_t manifold_cross_section_vec_size() {
|
||||
return sizeof(std::vector<CrossSection>);
|
||||
}
|
||||
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
|
||||
size_t manifold_polygons_size() { return sizeof(Polygons); }
|
||||
size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
|
||||
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
|
||||
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
|
||||
size_t manifold_box_size() { return sizeof(Box); }
|
||||
size_t manifold_rect_size() { return sizeof(Rect); }
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_cross_section(ManifoldCrossSection *c) {
|
||||
delete from_c(c);
|
||||
}
|
||||
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv) {
|
||||
delete from_c(csv);
|
||||
}
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
delete from_c(p);
|
||||
}
|
||||
void manifold_delete_polygons(ManifoldPolygons *p) { delete from_c(p); }
|
||||
void manifold_delete_manifold(ManifoldManifold *m) { delete from_c(m); }
|
||||
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
delete from_c(ms);
|
||||
}
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
|
||||
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
|
||||
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_cross_section(ManifoldCrossSection *cs) {
|
||||
from_c(cs)->~CrossSection();
|
||||
}
|
||||
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv) {
|
||||
from_c(csv)->~CrossSectionVec();
|
||||
}
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
from_c(p)->~SimplePolygon();
|
||||
}
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p) { from_c(p)->~Polygons(); }
|
||||
void manifold_destruct_manifold(ManifoldManifold *m) { from_c(m)->~Manifold(); }
|
||||
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms) {
|
||||
from_c(ms)->~ManifoldVec();
|
||||
}
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
|
||||
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
|
||||
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "meshIO.h"
|
||||
|
||||
#include <conv.h>
|
||||
|
||||
#include "include/types.h"
|
||||
|
||||
// C <-> C++ conversions
|
||||
|
||||
ManifoldMaterial *to_c(manifold::Material *m) {
|
||||
return reinterpret_cast<ManifoldMaterial *>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
|
||||
return reinterpret_cast<ManifoldExportOptions *>(m);
|
||||
}
|
||||
|
||||
manifold::Material *from_c(ManifoldMaterial *mat) {
|
||||
return reinterpret_cast<manifold::Material *>(mat);
|
||||
}
|
||||
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options) {
|
||||
return reinterpret_cast<manifold::ExportOptions *>(options);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// material
|
||||
|
||||
ManifoldMaterial *manifold_material(void *mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert) {
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
// export options
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
// mesh IO
|
||||
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
|
||||
int force_cleanup) {
|
||||
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
|
||||
return to_c(new (mem) manifold::MeshGL(m));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions *m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,93 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2) {
|
||||
auto p1 = glm::vec2(x1, y1);
|
||||
auto p2 = glm::vec2(x2, y2);
|
||||
auto rect = new (mem) Rect(p1, p2);
|
||||
return to_c(rect);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_min(ManifoldRect *r) {
|
||||
return to_c((*from_c(r)).min);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_max(ManifoldRect *r) {
|
||||
return to_c((*from_c(r)).max);
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r) {
|
||||
auto v = from_c(r)->Size();
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
ManifoldVec2 manifold_rect_center(ManifoldRect *r) {
|
||||
auto v = from_c(r)->Center();
|
||||
return {v.x, v.y};
|
||||
}
|
||||
|
||||
float manifold_rect_scale(ManifoldRect *r) { return from_c(r)->Scale(); }
|
||||
|
||||
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
return rect.Contains(p);
|
||||
}
|
||||
|
||||
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_rect_include_pt(ManifoldRect *r, float x, float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
rect.Union(p);
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b) {
|
||||
auto rect = from_c(a)->Union(*from_c(b));
|
||||
return to_c(new (mem) Rect(rect));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
|
||||
float y1, float x2, float y2, float x3,
|
||||
float y3) {
|
||||
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
|
||||
auto transformed = from_c(r)->Transform(mat);
|
||||
return to_c(new (mem) Rect(transformed));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
|
||||
float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto translated = (*from_c(r)) + p;
|
||||
return to_c(new (mem) Rect(translated));
|
||||
}
|
||||
|
||||
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y) {
|
||||
auto rect = *from_c(r);
|
||||
auto p = glm::vec2(x, y);
|
||||
auto scaled = (*from_c(r)) * p;
|
||||
return to_c(new (mem) Rect(scaled));
|
||||
}
|
||||
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r) {
|
||||
return from_c(a)->DoesOverlap(*from_c(r));
|
||||
}
|
||||
|
||||
int manifold_rect_is_empty(ManifoldRect *r) { return from_c(r)->IsEmpty(); }
|
||||
|
||||
int manifold_rect_is_finite(ManifoldRect *r) { return from_c(r)->IsFinite(); }
|
||||
|
||||
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
|
||||
ManifoldRect *r) {
|
||||
auto cs = from_c(r)->AsCrossSection();
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
@@ -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(manifold3d manifold3d.cpp)
|
||||
target_link_libraries(manifold3d PRIVATE manifold)
|
||||
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(manifold3d PUBLIC cxx_std_17)
|
||||
target_include_directories(manifold3d
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
@@ -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 manifold3d import Manifold
|
||||
|
||||
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
|
||||
|
||||
INCHES = 25.4
|
||||
|
||||
brick_depth = (3 + 5/8) * INCHES
|
||||
brick_height = (2 + 1/4) * INCHES
|
||||
brick_length = (7 + 5/8) * INCHES
|
||||
|
||||
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 manifold3d import Manifold
|
||||
from functools import reduce
|
||||
|
||||
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
|
||||
|
||||
def run(n=5, overlap=True):
|
||||
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
|
||||
|
||||
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,38 @@
|
||||
from manifold3d import CrossSection, FillRule
|
||||
|
||||
|
||||
def run():
|
||||
# create a polygon
|
||||
polygon_points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
||||
polygons_points = [polygon_points]
|
||||
|
||||
# create a cross-section
|
||||
cross_section = CrossSection(polygons_points)
|
||||
polygons = cross_section.to_polygons()
|
||||
polygon = polygons[0]
|
||||
if set(polygon) != set(polygon_points):
|
||||
raise Exception(f"{polygon=} differs from {polygon_points=}")
|
||||
|
||||
# extrude a polygon to create a manifold
|
||||
extruded_polygon = cross_section.extrude(10.0)
|
||||
eps = 0.001
|
||||
observed_volume = extruded_polygon.get_volume()
|
||||
expected_volume = 10.0
|
||||
if abs(observed_volume - expected_volume) > eps:
|
||||
raise Exception(f"{observed_volume=} differs from {expected_volume=}")
|
||||
observed_surface_area = extruded_polygon.get_surface_area()
|
||||
expected_surface_area = 42.0
|
||||
if abs(observed_surface_area - expected_surface_area) > eps:
|
||||
raise Exception(f"{observed_surface_area=} differs from {expected_surface_area=}")
|
||||
|
||||
# get bounding box from manifold
|
||||
observed_bbox = extruded_polygon.bounding_box
|
||||
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
|
||||
if observed_bbox != expected_bbox:
|
||||
raise Exception(f"{observed_bbox=} differs from {expected_bbox=}")
|
||||
|
||||
return extruded_polygon
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
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
|
||||
import numpy as np
|
||||
from time import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
current_file = pathlib.Path(__file__)
|
||||
current_dir = current_file.parent
|
||||
files = [f.parts[-1][:-3]
|
||||
for f in current_dir.glob('*.py') if f != current_file]
|
||||
|
||||
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:
|
||||
if mesh.vert_properties.shape[1] > 3:
|
||||
vertices = np.hsplit(mesh.vert_properties, 3)[0]
|
||||
else:
|
||||
vertices = mesh.vert_properties
|
||||
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
|
||||
print(f'Exported model to {f}.glb')
|
||||
t1 = time()
|
||||
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
|
||||
@@ -0,0 +1,7 @@
|
||||
from manifold3d import Manifold
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
|
||||
return obj
|
||||
@@ -0,0 +1,876 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "pybind11/functional.h"
|
||||
#include "pybind11/numpy.h"
|
||||
#include "pybind11/operators.h"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/stl.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
typedef std::tuple<float, float> Float2;
|
||||
typedef std::tuple<float, float, float> Float3;
|
||||
|
||||
constexpr auto pyArrayFlags = py::array::c_style | py::array::forcecast;
|
||||
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<int>);
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<uint32_t>);
|
||||
PYBIND11_MAKE_OPAQUE(std::vector<float>);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> toVector(const py::array_t<T> &arr) {
|
||||
return std::vector<T>(arr.data(), arr.data() + arr.size());
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(manifold3d, m) {
|
||||
m.doc() = "Python binding for the Manifold library.";
|
||||
|
||||
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
|
||||
"Sets an angle constraint the default number of circular segments for "
|
||||
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
|
||||
"and Manifold::Revolve() constructors. The number of segments will be "
|
||||
"rounded up to the nearest factor of four."
|
||||
"\n\n"
|
||||
":param angle: The minimum angle in degrees between consecutive "
|
||||
"segments. The angle will increase if the the segments hit the minimum "
|
||||
"edge length.\n"
|
||||
"Default is 10 degrees.");
|
||||
|
||||
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
|
||||
"Sets a length constraint the default number of circular segments for "
|
||||
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
|
||||
"and Manifold::Revolve() constructors. The number of segments will be "
|
||||
"rounded up to the nearest factor of four."
|
||||
"\n\n"
|
||||
":param length: The minimum length of segments. The length will "
|
||||
"increase if the the segments hit the minimum angle. Default is 1.0.");
|
||||
|
||||
m.def("set_circular_segments", Quality::SetCircularSegments,
|
||||
"Sets the default number of circular segments for the "
|
||||
"CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and "
|
||||
"Manifold::Revolve() constructors. Overrides the edge length and angle "
|
||||
"constraints and sets the number of segments to exactly this value."
|
||||
"\n\n"
|
||||
":param number: Number of circular segments. Default is 0, meaning no "
|
||||
"constraint is applied.");
|
||||
|
||||
m.def("get_circular_segments", Quality::GetCircularSegments,
|
||||
"Determine the result of the SetMinCircularAngle(), "
|
||||
"SetMinCircularEdgeLength(), and SetCircularSegments() defaults."
|
||||
"\n\n"
|
||||
":param radius: For a given radius of circle, determine how many "
|
||||
"default");
|
||||
|
||||
py::class_<Manifold>(m, "Manifold")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
return first;
|
||||
} else {
|
||||
return Manifold();
|
||||
}
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def(
|
||||
"transform",
|
||||
[](Manifold &self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat.ndim() != 2 || mat.shape(0) != 3 || mat.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid matrix shape, expected (3, 4)");
|
||||
glm::mat4x3 mat_glm;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
},
|
||||
py::arg("m"),
|
||||
"Transform this Manifold in space. The first three columns form a "
|
||||
"3x3 matrix transform and the last is a translation vector. This "
|
||||
"operation can be chained. Transforms are combined and applied "
|
||||
"lazily.\n"
|
||||
"\n\n"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold &self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
|
||||
return self.Translate(glm::vec3(x, y, z));
|
||||
},
|
||||
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param x: X axis translation. (default 0.0).\n"
|
||||
":param y: Y axis translation. (default 0.0).\n"
|
||||
":param z: Z axis translation. (default 0.0).")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold &self, py::array_t<float> &t) {
|
||||
auto t_view = t.unchecked<1>();
|
||||
if (t.ndim() != 1 || t.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
|
||||
},
|
||||
py::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold &self, float scale) {
|
||||
return self.Scale(glm::vec3(scale));
|
||||
},
|
||||
py::arg("scale"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param scale: The scalar multiplier for each component of every "
|
||||
"vertices.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold &self, py::array_t<float> &scale) {
|
||||
auto scale_view = scale.unchecked<1>();
|
||||
if (scale.ndim() != 1 || scale.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
|
||||
return self.Scale(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def(
|
||||
"mirror",
|
||||
[](Manifold &self, py::array_t<float> &mirror) {
|
||||
auto v_view = mirror.unchecked<1>();
|
||||
if (mirror.ndim() != 1 || mirror.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
glm::vec3 v(v_view(0), v_view(1), v_view(2));
|
||||
return self.Mirror(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Mirror this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param mirror: The vector defining the axis of mirroring.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold &self, py::array_t<float> &v) {
|
||||
auto v_view = v.unchecked<1>();
|
||||
if (v.ndim() != 1 || v.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
return self.Rotate(v_view(0), v_view(1), v_view(2));
|
||||
},
|
||||
py::arg("v"),
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then Y, then Z, in degrees. We use degrees so that we can "
|
||||
"minimize rounding error, and eliminate it completely for any "
|
||||
"multiples of 90 degrees. Additionally, more efficient code paths "
|
||||
"are used to update the manifold when the transforms only rotate by "
|
||||
"multiples of 90 degrees. This operation can be chained. Transforms "
|
||||
"are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: [X, Y, Z] rotation in degrees.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold &self, float xDegrees = 0.0f, float yDegrees = 0.0f,
|
||||
float zDegrees = 0.0f) {
|
||||
return self.Rotate(xDegrees, yDegrees, zDegrees);
|
||||
},
|
||||
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
|
||||
py::arg("z_degrees") = 0.0f,
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then Y, then Z, in degrees. We use degrees so that we can "
|
||||
"minimize rounding error, and eliminate it completely for any "
|
||||
"multiples of 90 degrees. Additionally, more efficient code paths "
|
||||
"are used to update the manifold when the transforms only rotate by "
|
||||
"multiples of 90 degrees. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param x: X rotation in degrees. (default 0.0).\n"
|
||||
":param y: Y rotation in degrees. (default 0.0).\n"
|
||||
":param z: Z rotation in degrees. (default 0.0).")
|
||||
.def(
|
||||
"warp",
|
||||
[](Manifold &self, const std::function<Float3(Float3)> &f) {
|
||||
return self.Warp([&f](glm::vec3 &v) {
|
||||
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
|
||||
v.x = std::get<0>(fv);
|
||||
v.y = std::get<1>(fv);
|
||||
v.z = std::get<2>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"),
|
||||
"This function does not change the topology, but allows the vertices "
|
||||
"to be moved according to any arbitrary input function. It is easy "
|
||||
"to create a function that warps a geometrically valid object into "
|
||||
"one which overlaps, but that is not checked here, so it is up to "
|
||||
"the user to choose their function with discretion."
|
||||
"\n\n"
|
||||
":param warpFunc: A function that modifies a given vertex position.")
|
||||
.def(
|
||||
"refine", [](Manifold &self, int n) { return self.Refine(n); },
|
||||
py::arg("n"),
|
||||
"Increase the density of the mesh by splitting every edge into n "
|
||||
"pieces. For instance, with n = 2, each triangle will be split into "
|
||||
"4 triangles. These will all be coplanar (and will not be "
|
||||
"immediately collapsed) unless the Mesh/Manifold has "
|
||||
"halfedgeTangents specified (e.g. from the Smooth() constructor), "
|
||||
"in which case the new vertices will be moved to the interpolated "
|
||||
"surface according to their barycentric coordinates.\n"
|
||||
"\n"
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def(
|
||||
"to_mesh",
|
||||
[](Manifold &self, std::optional<py::array_t<uint32_t>> &normalIdx) {
|
||||
glm::ivec3 v(0);
|
||||
if (normalIdx.has_value()) {
|
||||
if (normalIdx.value().ndim() != 1 ||
|
||||
normalIdx.value().shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
auto normalIdx_view = normalIdx.value().unchecked<1>();
|
||||
v = glm::ivec3(normalIdx_view(0), normalIdx_view(1),
|
||||
normalIdx_view(2));
|
||||
}
|
||||
return self.GetMeshGL(v);
|
||||
},
|
||||
"The most complete output of this library, returning a MeshGL that "
|
||||
"is designed to easily push into a renderer, including all "
|
||||
"interleaved vertex properties that may have been input. It also "
|
||||
"includes relations to all the input meshes that form a part of "
|
||||
"this result and the transforms applied to each."
|
||||
"\n\n"
|
||||
":param normalIdx: If the original MeshGL inputs that formed this "
|
||||
"manifold had properties corresponding to normal vectors, you can "
|
||||
"specify which property channels these are (x, y, z), which will "
|
||||
"cause this output MeshGL to automatically update these normals "
|
||||
"according to the applied transforms and front/back side. Each "
|
||||
"channel must be >= 3 and < numProp, and all original MeshGLs must "
|
||||
"use the same channels for their normals.",
|
||||
py::arg("normalIdx") = py::none())
|
||||
.def("num_vert", &Manifold::NumVert,
|
||||
"The number of vertices in the Manifold.")
|
||||
.def("num_edge", &Manifold::NumEdge,
|
||||
"The number of edges in the Manifold.")
|
||||
.def("num_tri", &Manifold::NumTri,
|
||||
"The number of triangles in the Manifold.")
|
||||
.def("num_prop", &Manifold::NumProp,
|
||||
"The number of properties per vertex in the Manifold")
|
||||
.def("num_prop_vert", &Manifold::NumPropVert,
|
||||
"The number of property vertices in the Manifold. This will always "
|
||||
"be >= NumVert, as some physical vertices may be duplicated to "
|
||||
"account for different properties on different neighboring "
|
||||
"triangles.")
|
||||
.def("precision", &Manifold::Precision,
|
||||
"Returns the precision of this Manifold's vertices, which tracks "
|
||||
"the approximate rounding error over all the transforms and "
|
||||
"operations that have led to this state. Any triangles that are "
|
||||
"colinear within this precision are considered degenerate and "
|
||||
"removed. This is the value of ε defining "
|
||||
"[ε-valid](https://github.com/elalish/manifold/wiki/"
|
||||
"Manifold-Library#definition-of-%CE%B5-valid).")
|
||||
.def("genus", &Manifold::Genus,
|
||||
"The genus is a topological property of the manifold, representing "
|
||||
"the number of \"handles\". A sphere is 0, torus 1, etc. It is only "
|
||||
"meaningful for a single mesh, so it is best to call Decompose() "
|
||||
"first.")
|
||||
.def(
|
||||
"get_volume",
|
||||
[](Manifold &self) { return self.GetProperties().volume; },
|
||||
"Get the volume of the manifold\n This is clamped to zero for a "
|
||||
"given face if they are within the Precision().")
|
||||
.def(
|
||||
"get_surface_area",
|
||||
[](Manifold &self) { return self.GetProperties().surfaceArea; },
|
||||
"Get the surface area of the manifold\n This is clamped to zero for "
|
||||
"a given face if they are within the Precision().")
|
||||
.def("original_id", &Manifold::OriginalID,
|
||||
"If this mesh is an original, this returns its meshID that can be "
|
||||
"referenced by product manifolds' MeshRelation. If this manifold is "
|
||||
"a product, this returns -1.")
|
||||
.def("as_original", &Manifold::AsOriginal,
|
||||
"This function condenses all coplanar faces in the relation, and "
|
||||
"collapses those edges. In the process the relation to ancestor "
|
||||
"meshes is lost and this new Manifold is marked an original. "
|
||||
"Properties are preserved, so if they do not match across an edge, "
|
||||
"that edge will be kept.")
|
||||
.def("is_empty", &Manifold::IsEmpty,
|
||||
"Does the Manifold have any triangles?")
|
||||
.def(
|
||||
"decompose", [](Manifold &self) { return self.Decompose(); },
|
||||
"This operation returns a vector of Manifolds that are "
|
||||
"topologically disconnected. If everything is connected, the vector "
|
||||
"is length one, containing a copy of the original. It is the inverse "
|
||||
"operation of Compose().")
|
||||
.def("split", &Manifold::Split,
|
||||
"Split cuts this manifold in two using the cutter manifold. The "
|
||||
"first result is the intersection, second is the difference. This "
|
||||
"is more efficient than doing them separately.")
|
||||
.def(
|
||||
"split_by_plane",
|
||||
[](Manifold &self, Float3 normal, float originOffset) {
|
||||
return self.SplitByPlane(
|
||||
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
|
||||
originOffset);
|
||||
},
|
||||
py::arg("normal"), py::arg("origin_offset"),
|
||||
"Convenient version of Split() for a half-space."
|
||||
"\n\n"
|
||||
":param normal: This vector is normal to the cutting plane and its "
|
||||
"length does not matter. The first result is in the direction of "
|
||||
"this vector, the second result is on the opposite side.\n"
|
||||
":param originOffset: The distance of the plane from the origin in "
|
||||
"the direction of the normal vector.")
|
||||
.def(
|
||||
"trim_by_plane",
|
||||
[](Manifold &self, Float3 normal, float originOffset) {
|
||||
return self.TrimByPlane(
|
||||
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
|
||||
originOffset);
|
||||
},
|
||||
py::arg("normal"), py::arg("origin_offset"),
|
||||
"Identical to SplitByPlane(), but calculating and returning only the "
|
||||
"first result."
|
||||
"\n\n"
|
||||
":param normal: This vector is normal to the cutting plane and its "
|
||||
"length does not matter. The result is in the direction of this "
|
||||
"vector from the plane.\n"
|
||||
":param originOffset: The distance of the plane from the origin in "
|
||||
"the direction of the normal vector.")
|
||||
.def_property_readonly(
|
||||
"bounding_box",
|
||||
[](Manifold &self) {
|
||||
auto b = self.BoundingBox();
|
||||
py::tuple box = py::make_tuple(b.min[0], b.min[1], b.min[2],
|
||||
b.max[0], b.max[1], b.max[2]);
|
||||
return box;
|
||||
},
|
||||
"Gets the manifold bounding box as a tuple "
|
||||
"(xmin, ymin, zmin, xmax, ymax, zmax).")
|
||||
.def_static(
|
||||
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
|
||||
"Constructs a smooth version of the input mesh by creating tangents; "
|
||||
"this method will throw if you have supplied tangents with your "
|
||||
"mesh already. The actual triangle resolution is unchanged; use the "
|
||||
"Refine() method to interpolate to a higher-resolution curve."
|
||||
"\n\n"
|
||||
"By default, every edge is calculated for maximum smoothness (very "
|
||||
"much approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude. No higher-order derivatives are considered, "
|
||||
"as the interpolation is independent per triangle, only sharing "
|
||||
"constraints on their boundaries."
|
||||
"\n\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector of "
|
||||
"sharpened halfedges, which should in general be a small subset of "
|
||||
"all halfedges. Order of entries doesn't matter, as each one "
|
||||
"specifies the desired smoothness (between zero and one, with one "
|
||||
"the default for all unspecified halfedges) and the halfedge index "
|
||||
"(3 * triangle index + [0,1,2] where 0 is the edge between triVert 0 "
|
||||
"and 1, etc)."
|
||||
"\n\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is interpolated along each edge, so the specified value "
|
||||
"should be thought of as an average. Where exactly two sharpened "
|
||||
"edges meet at a vertex, their tangents are rotated to be colinear "
|
||||
"so that the sharpened edge can be continuous. Vertices with only "
|
||||
"one sharpened edge are completely smooth, allowing sharpened edges "
|
||||
"to smoothly vanish at termination. A single vertex can be sharpened "
|
||||
"by sharping all edges that are incident on it, allowing cones to be "
|
||||
"formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const MeshGL &mesh) { return Manifold(mesh); },
|
||||
py::arg("mesh"))
|
||||
.def_static(
|
||||
"tetrahedron", []() { return Manifold::Tetrahedron(); },
|
||||
"Constructs a tetrahedron centered at the origin with one vertex at "
|
||||
"(1,1,1) and the rest at similarly symmetric points.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](Float3 size, bool center = false) {
|
||||
return Manifold::Cube(
|
||||
glm::vec3(std::get<0>(size), std::get<1>(size),
|
||||
std::get<2>(size)),
|
||||
center);
|
||||
},
|
||||
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
|
||||
py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](py::array_t<float> &size, bool center = false) {
|
||||
auto size_view = size.unchecked<1>();
|
||||
if (size.ndim() != 1 || size.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
return Manifold::Cube(
|
||||
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
|
||||
},
|
||||
py::arg("size"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](float x, float y, float z, bool center = false) {
|
||||
return Manifold::Cube(glm::vec3(x, y, z), center);
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param x: The X dimensions of the box.\n"
|
||||
":param y: The Y dimensions of the box.\n"
|
||||
":param z: The Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cylinder",
|
||||
[](float height, float radiusLow, float radiusHigh = -1.0f,
|
||||
int circularSegments = 0, bool center = false) {
|
||||
return Manifold::Cylinder(height, radiusLow, radiusHigh,
|
||||
circularSegments, center);
|
||||
},
|
||||
py::arg("height"), py::arg("radius_low"),
|
||||
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
|
||||
py::arg("center") = false,
|
||||
"A convenience constructor for the common case of extruding a "
|
||||
"circle. Can also form cones if both radii are specified."
|
||||
"\n\n"
|
||||
":param height: Z-extent\n"
|
||||
":param radiusLow: Radius of bottom circle. Must be positive.\n"
|
||||
":param radiusHigh: Radius of top circle. Can equal zero. Default "
|
||||
"(-1) is equal to radiusLow.\n"
|
||||
":param circularSegments: How many line segments to use around the "
|
||||
"circle. Default (-1) is calculated by the static Defaults.\n"
|
||||
":param center: Set to true to shift the center to the origin. "
|
||||
"Default is origin at the bottom.")
|
||||
.def_static(
|
||||
"sphere",
|
||||
[](float radius, int circularSegments = 0) {
|
||||
return Manifold::Sphere(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circular_segments") = 0,
|
||||
"Constructs a geodesic sphere of a given radius.\n"
|
||||
"\n"
|
||||
":param radius: Radius of the sphere. Must be positive.\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"This number will always be rounded up to the nearest factor of "
|
||||
"four, as this sphere is constructed by refining an octahedron. This "
|
||||
"means there are a circle of vertices on all three of the axis "
|
||||
"planes. Default is calculated by the static Defaults.")
|
||||
.def_static("reserve_ids", Manifold::ReserveIDs, py::arg("n"),
|
||||
"Returns the first of n sequential new unique mesh IDs for "
|
||||
"marking sets of triangles that can be looked up after "
|
||||
"further operations. Assign to MeshGL.runOriginalID vector");
|
||||
|
||||
py::class_<MeshGL>(m, "Mesh")
|
||||
.def(
|
||||
// note that reshape requires mutable array_t, but this will not
|
||||
// affect the original array passed into the function
|
||||
py::init(
|
||||
[](py::array_t<float, pyArrayFlags> &vertProp,
|
||||
py::array_t<int, pyArrayFlags> &triVerts,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&mergeFromVert,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&mergeToVert,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&runIndex,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&runOriginalID,
|
||||
std::optional<py::array_t<float, pyArrayFlags>> &runTransform,
|
||||
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
|
||||
&faceID,
|
||||
const std::optional<py::array_t<float, pyArrayFlags>>
|
||||
&halfedgeTangent,
|
||||
float precision) {
|
||||
MeshGL out;
|
||||
|
||||
out.numProp = vertProp.shape(1);
|
||||
vertProp = vertProp.reshape({-1});
|
||||
out.vertProperties = toVector<float>(vertProp);
|
||||
|
||||
if (triVerts.ndim() != 2 || triVerts.shape(1) != 3)
|
||||
throw std::runtime_error(
|
||||
"Invalid tri_verts shape, expected (-1, 3)");
|
||||
triVerts = triVerts.reshape({-1});
|
||||
out.triVerts = toVector<uint32_t>(triVerts);
|
||||
|
||||
if (mergeFromVert.has_value())
|
||||
out.mergeFromVert = toVector<uint32_t>(mergeFromVert.value());
|
||||
|
||||
if (mergeToVert.has_value())
|
||||
out.mergeToVert = toVector<uint32_t>(mergeToVert.value());
|
||||
|
||||
if (runIndex.has_value())
|
||||
out.runIndex = toVector<uint32_t>(runIndex.value());
|
||||
|
||||
if (runOriginalID.has_value())
|
||||
out.runOriginalID = toVector<uint32_t>(runOriginalID.value());
|
||||
|
||||
if (runTransform.has_value()) {
|
||||
auto runTransform1 = runTransform.value();
|
||||
if (runTransform1.ndim() != 3 ||
|
||||
runTransform1.shape(1) != 4 ||
|
||||
runTransform1.shape(2) != 3)
|
||||
throw std::runtime_error(
|
||||
"Invalid run_transform shape, expected (-1, 4, 3)");
|
||||
runTransform1 = runTransform1.reshape({-1});
|
||||
out.runTransform = toVector<float>(runTransform1);
|
||||
}
|
||||
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector<uint32_t>(faceID.value());
|
||||
|
||||
if (halfedgeTangent.has_value()) {
|
||||
auto halfedgeTangent1 = halfedgeTangent.value();
|
||||
if (halfedgeTangent1.ndim() != 3 ||
|
||||
halfedgeTangent1.shape(1) != 3 ||
|
||||
halfedgeTangent1.shape(2) != 4)
|
||||
throw std::runtime_error(
|
||||
"Invalid halfedge_tangent shape, expected (-1, 3, 4)");
|
||||
halfedgeTangent1 = halfedgeTangent1.reshape({-1});
|
||||
out.halfedgeTangent = toVector<float>(halfedgeTangent1);
|
||||
}
|
||||
|
||||
return out;
|
||||
}),
|
||||
py::arg("vert_properties"), py::arg("tri_verts"),
|
||||
py::arg("merge_from_vert") = py::none(),
|
||||
py::arg("merge_to_vert") = py::none(),
|
||||
py::arg("run_index") = py::none(),
|
||||
py::arg("run_original_id") = py::none(),
|
||||
py::arg("run_transform") = py::none(),
|
||||
py::arg("face_id") = py::none(),
|
||||
py::arg("halfedge_tangent") = py::none(), py::arg("precision") = 0)
|
||||
.def_property_readonly(
|
||||
"vert_properties",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.vertProperties.size(),
|
||||
self.vertProperties.data())
|
||||
.reshape(std::array<int, 2>{-1, (int)self.numProp});
|
||||
})
|
||||
.def_property_readonly("tri_verts",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.triVerts.size(),
|
||||
self.triVerts.data())
|
||||
.reshape(std::array<int, 2>{-1, 3});
|
||||
})
|
||||
.def_property_readonly("run_transform",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.runTransform.size(),
|
||||
self.runTransform.data())
|
||||
.reshape(std::array<int, 3>{-1, 4, 3});
|
||||
})
|
||||
.def_property_readonly("halfedge_tangent",
|
||||
[](const MeshGL &self) {
|
||||
return py::array(self.halfedgeTangent.size(),
|
||||
self.halfedgeTangent.data())
|
||||
.reshape(std::array<int, 3>{-1, 3, 4});
|
||||
})
|
||||
.def_readonly("merge_from_vert", &MeshGL::mergeFromVert)
|
||||
.def_readonly("merge_to_vert", &MeshGL::mergeToVert)
|
||||
.def_readonly("run_index", &MeshGL::runIndex)
|
||||
.def_readonly("run_original_id", &MeshGL::runOriginalID)
|
||||
.def_readonly("face_id", &MeshGL::faceID);
|
||||
|
||||
py::enum_<CrossSection::FillRule>(m, "FillRule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
|
||||
"Only odd numbered sub-regions are filled.")
|
||||
.value("NonZero", CrossSection::FillRule::NonZero,
|
||||
"Only non-zero sub-regions are filled.")
|
||||
.value("Positive", CrossSection::FillRule::Positive,
|
||||
"Only sub-regions with winding counts > 0 are filled.")
|
||||
.value("Negative", CrossSection::FillRule::Negative,
|
||||
"Only sub-regions with winding counts < 0 are filled.");
|
||||
|
||||
py::enum_<CrossSection::JoinType>(m, "JoinType")
|
||||
.value("Square", CrossSection::JoinType::Square,
|
||||
"Squaring is applied uniformly at all joins where the internal "
|
||||
"join angle is less that 90 degrees. The squared edge will be at "
|
||||
"exactly the offset distance from the join vertex.")
|
||||
.value(
|
||||
"Round", CrossSection::JoinType::Square,
|
||||
"Rounding is applied to all joins that have convex external angles, "
|
||||
"and it maintains the exact offset distance from the join vertex.")
|
||||
.value(
|
||||
"Miter", CrossSection::JoinType::Miter,
|
||||
"There's a necessary limit to mitered joins (to avoid narrow angled "
|
||||
"joins producing excessively long and narrow "
|
||||
"[spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/"
|
||||
"Classes/ClipperOffset/Properties/MiterLimit.htm)). So where mitered "
|
||||
"joins would exceed a given maximum miter distance (relative to the "
|
||||
"offset distance), these are 'squared' instead.");
|
||||
|
||||
py::class_<CrossSection>(
|
||||
m, "CrossSection",
|
||||
"Two-dimensional cross sections guaranteed to be without "
|
||||
"self-intersections, or overlaps between polygons (from construction "
|
||||
"onwards). This class makes use of the "
|
||||
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
|
||||
"for polygon clipping (boolean) and offsetting operations.")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<std::vector<Float2>> &polygons,
|
||||
CrossSection::FillRule fillrule) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
simplePolygons[i] = std::vector<glm::vec2>(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
simplePolygons[i][j] = {std::get<0>(polygons[i][j]),
|
||||
std::get<1>(polygons[i][j])};
|
||||
}
|
||||
}
|
||||
return CrossSection(simplePolygons, fillrule);
|
||||
}),
|
||||
py::arg("polygons"),
|
||||
py::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
"Create a 2d cross-section from a set of contours (complex "
|
||||
"polygons). A boolean union operation (with Positive filling rule "
|
||||
"by default) performed to combine overlapping polygons and ensure "
|
||||
"the resulting CrossSection is free of intersections."
|
||||
"\n\n"
|
||||
":param contours: A set of closed paths describing zero or more "
|
||||
"complex polygons.\n"
|
||||
":param fillrule: The filling rule used to interpret polygon "
|
||||
"sub-regions in contours.")
|
||||
.def("area", &CrossSection::Area,
|
||||
"Return the total area covered by complex polygons making up the "
|
||||
"CrossSection.")
|
||||
.def("num_vert", &CrossSection::NumVert,
|
||||
"Return the number of vertices in the CrossSection.")
|
||||
.def("num_contour", &CrossSection::NumContour,
|
||||
"Return the number of contours (both outer and inner paths) in the "
|
||||
"CrossSection.")
|
||||
.def("is_empty", &CrossSection::IsEmpty,
|
||||
"Does the CrossSection contain any contours?")
|
||||
.def(
|
||||
"translate",
|
||||
[](CrossSection self, Float2 v) {
|
||||
return self.Translate({std::get<0>(v), std::get<1>(v)});
|
||||
},
|
||||
"Move this CrossSection in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def("rotate", &CrossSection::Rotate,
|
||||
"Applies a (Z-axis) rotation to the CrossSection, in degrees. This "
|
||||
"operation can be chained. Transforms are combined and applied "
|
||||
"lazily."
|
||||
"\n\n"
|
||||
":param degrees: degrees about the Z-axis to rotate.")
|
||||
.def(
|
||||
"scale",
|
||||
[](CrossSection self, Float2 s) {
|
||||
return self.Scale({std::get<0>(s), std::get<1>(s)});
|
||||
},
|
||||
"Scale this CrossSection in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by per component.")
|
||||
.def(
|
||||
"mirror",
|
||||
[](CrossSection self, Float2 ax) {
|
||||
return self.Mirror({std::get<0>(ax), std::get<1>(ax)});
|
||||
},
|
||||
"Mirror this CrossSection over the arbitrary axis described by the "
|
||||
"unit form of the given vector. If the length of the vector is zero, "
|
||||
"an empty CrossSection is returned. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param ax: the axis to be mirrored over")
|
||||
.def(
|
||||
"transform",
|
||||
[](CrossSection self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat.ndim() != 2 || mat.shape(0) != 2 || mat.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid matrix shape, expected (2, 3)");
|
||||
glm::mat3x2 mat_glm;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
},
|
||||
"Transform this CrossSection in space. The first two columns form a "
|
||||
"2x2 matrix transform and the last is a translation vector. This "
|
||||
"operation can be chained. Transforms are combined and applied "
|
||||
"lazily."
|
||||
"\n\n"
|
||||
":param m: The affine transform matrix to apply to all the vertices.")
|
||||
.def(
|
||||
"warp",
|
||||
[](CrossSection self, const std::function<Float2(Float2)> &f) {
|
||||
return self.Warp([&f](glm::vec2 &v) {
|
||||
Float2 fv = f(std::make_tuple(v.x, v.y));
|
||||
v.x = std::get<0>(fv);
|
||||
v.y = std::get<1>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"),
|
||||
"Move the vertices of this CrossSection (creating a new one) "
|
||||
"according to any arbitrary input function, followed by a union "
|
||||
"operation (with a Positive fill rule) that ensures any introduced "
|
||||
"intersections are not included in the result."
|
||||
"\n\n"
|
||||
":param warpFunc: A function that modifies a given vertex position.")
|
||||
.def("simplify", &CrossSection::Simplify,
|
||||
"Remove vertices from the contours in this CrossSection that are "
|
||||
"less than the specified distance epsilon from an imaginary line "
|
||||
"that passes through its two adjacent vertices. Near duplicate "
|
||||
"vertices and collinear points will be removed at lower epsilons, "
|
||||
"with elimination of line segments becoming increasingly aggressive "
|
||||
"with larger epsilons."
|
||||
"\n\n"
|
||||
"It is recommended to apply this function following Offset, in "
|
||||
"order to clean up any spurious tiny line segments introduced that "
|
||||
"do not improve quality in any meaningful way. This is particularly "
|
||||
"important if further offseting operations are to be performed, "
|
||||
"which would compound the issue.")
|
||||
.def("offset", &CrossSection::Offset, py::arg("delta"),
|
||||
py::arg("join_type"), py::arg("miter_limit") = 2.0,
|
||||
py::arg("arc_tolerance") = 0.0,
|
||||
"Inflate the contours in CrossSection by the specified delta, "
|
||||
"handling corners according to the given JoinType."
|
||||
"\n\n"
|
||||
":param delta: Positive deltas will cause the expansion of "
|
||||
"outlining contours to expand, and retraction of inner (hole) "
|
||||
"contours. Negative deltas will have the opposite effect.\n"
|
||||
":param jt: The join type specifying the treatment of contour joins "
|
||||
"(corners).\n"
|
||||
":param miter_limit: The maximum distance in multiples of delta "
|
||||
"that vertices can be offset from their original positions with "
|
||||
"before squaring is applied, <B>when the join type is Miter</B> "
|
||||
"(default is 2, which is the minimum allowed). See the [Clipper2 "
|
||||
"MiterLimit](http://www.angusj.com/clipper2/Docs/Units/"
|
||||
"Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm) "
|
||||
"page for a visual example.\n"
|
||||
":param circularSegments: Number of segments per 360 degrees of "
|
||||
"<B>JoinType::Round</B> corners (roughly, the number of vertices "
|
||||
"that will be added to each contour). Default is calculated by the "
|
||||
"static Quality defaults according to the radius.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def("decompose", &CrossSection::Decompose,
|
||||
"This operation returns a vector of CrossSections that are "
|
||||
"topologically disconnected, each containing one outline contour "
|
||||
"with zero or more holes.")
|
||||
.def(
|
||||
"to_polygons",
|
||||
[](CrossSection self) {
|
||||
const Polygons &data = self.ToPolygons();
|
||||
py::list polygon_list;
|
||||
for (int i = 0; i < data.size(); ++i) {
|
||||
py::list polygon;
|
||||
for (int j = 0; j < data[i].size(); ++j) {
|
||||
auto f = data[i][j];
|
||||
py::tuple vertex = py::make_tuple(f[0], f[1]);
|
||||
polygon.append(vertex);
|
||||
}
|
||||
polygon_list.append(polygon);
|
||||
}
|
||||
return polygon_list;
|
||||
},
|
||||
"Returns the vertices of the cross-section's polygons as a "
|
||||
"List[List[Tuple[float, float]]].")
|
||||
.def(
|
||||
"extrude",
|
||||
[](CrossSection self, float height, int nDivisions = 0,
|
||||
float twistDegrees = 0.0f,
|
||||
Float2 scaleTop = std::make_tuple(1.0f, 1.0f)) {
|
||||
glm::vec2 scaleTopVec(std::get<0>(scaleTop), std::get<1>(scaleTop));
|
||||
return Manifold::Extrude(self, height, nDivisions, twistDegrees,
|
||||
scaleTopVec);
|
||||
},
|
||||
py::arg("height"), py::arg("n_divisions") = 0,
|
||||
py::arg("twist_degrees") = 0.0f,
|
||||
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
"Constructs a manifold from the set of polygons by extruding them "
|
||||
"along the Z-axis.\n"
|
||||
"\n"
|
||||
":param height: Z-extent of extrusion.\n"
|
||||
":param nDivisions: Number of extra copies of the crossSection to "
|
||||
"insert into the shape vertically; especially useful in combination "
|
||||
"with twistDegrees to avoid interpolation artifacts. Default is "
|
||||
"none.\n"
|
||||
":param twistDegrees: Amount to twist the top crossSection relative "
|
||||
"to the bottom, interpolated linearly for the divisions in between.\n"
|
||||
":param scaleTop: Amount to scale the top (independently in X and "
|
||||
"Y). If the scale is (0, 0), a pure cone is formed with only a "
|
||||
"single vertex at the top. Default (1, 1).")
|
||||
.def(
|
||||
"revolve",
|
||||
[](CrossSection self, int circularSegments = 0) {
|
||||
return Manifold::Revolve(self, circularSegments);
|
||||
},
|
||||
py::arg("circular_segments") = 0,
|
||||
"Constructs a manifold from the set of polygons by revolving this "
|
||||
"cross-section around its Y-axis and then setting this as the Z-axis "
|
||||
"of the resulting manifold. If the polygons cross the Y-axis, only "
|
||||
"the part on the positive X side is used. Geometrically valid input "
|
||||
"will result in geometrically valid output.\n"
|
||||
"\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is calculated by the static Defaults.")
|
||||
.def_static(
|
||||
"square",
|
||||
[](Float2 dims, bool center) {
|
||||
return CrossSection::Square({std::get<0>(dims), std::get<1>(dims)},
|
||||
center);
|
||||
},
|
||||
py::arg("dims"), py::arg("center") = false,
|
||||
"Constructs a square with the given XY dimensions. By default it is "
|
||||
"positioned in the first quadrant, touching the origin. If any "
|
||||
"dimensions in size are negative, or if all are zero, an empty "
|
||||
"Manifold will be returned."
|
||||
"\n\n"
|
||||
":param size: The X, and Y dimensions of the square.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"circle",
|
||||
[](float radius, int circularSegments) {
|
||||
return CrossSection::Circle(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circularSegments") = 0,
|
||||
"Constructs a circle of a given radius."
|
||||
"\n\n"
|
||||
":param radius: Radius of the circle. Must be positive.\n"
|
||||
":param circularSegments: Number of segments along its diameter. "
|
||||
"Default is calculated by the static Quality defaults according to "
|
||||
"the radius.");
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
add_subdirectory(pybind11)
|
||||
@@ -0,0 +1,46 @@
|
||||
# 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)
|
||||
|
||||
set_source_files_properties(bindings.cpp PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
|
||||
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
|
||||
|
||||
# ensure that interface files are copied over when modified
|
||||
add_custom_target(js_deps ALL
|
||||
DEPENDS manifoldjs
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)
|
||||
|
||||
add_custom_command(
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
|
||||
|
||||
add_custom_command(
|
||||
TARGET js_deps POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
|
||||
@@ -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,31 @@
|
||||
[](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 [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
|
||||
|
||||
## Examples
|
||||
|
||||
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
|
||||
|
||||
## About the author
|
||||
|
||||
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,185 @@
|
||||
// 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>
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "helpers.cpp"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
|
||||
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("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
enum_<CrossSection::FillRule>("fillrule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
|
||||
.value("NonZero", CrossSection::FillRule::NonZero)
|
||||
.value("Positive", CrossSection::FillRule::Positive)
|
||||
.value("Negative", CrossSection::FillRule::Negative);
|
||||
|
||||
enum_<CrossSection::JoinType>("jointype")
|
||||
.value("Square", CrossSection::JoinType::Square)
|
||||
.value("Round", CrossSection::JoinType::Round)
|
||||
.value("Miter", CrossSection::JoinType::Miter);
|
||||
|
||||
value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
|
||||
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
value_object<Smoothness>("smoothness")
|
||||
.field("halfedge", &Smoothness::halfedge)
|
||||
.field("smoothness", &Smoothness::smoothness);
|
||||
|
||||
value_object<Properties>("properties")
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
register_vector<glm::ivec3>("Vector_ivec3");
|
||||
register_vector<glm::vec3>("Vector_vec3");
|
||||
register_vector<glm::vec2>("Vector_vec2");
|
||||
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<CrossSection>("Vector_crossSection");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
|
||||
class_<CrossSection>("CrossSection")
|
||||
.constructor(&cross_js::OfPolygons)
|
||||
.function("_add", &cross_js::Union)
|
||||
.function("_subtract", &cross_js::Difference)
|
||||
.function("_intersect", &cross_js::Intersection)
|
||||
.function("_Warp", &cross_js::Warp)
|
||||
.function("transform", &cross_js::Transform)
|
||||
.function("_Translate", &CrossSection::Translate)
|
||||
.function("rotate", &CrossSection::Rotate)
|
||||
.function("_Scale", &CrossSection::Scale)
|
||||
.function("_Mirror", &CrossSection::Mirror)
|
||||
.function("_Decompose", &CrossSection::Decompose)
|
||||
.function("isEmpty", &CrossSection::IsEmpty)
|
||||
.function("area", &CrossSection::Area)
|
||||
.function("numVert", &CrossSection::NumVert)
|
||||
.function("numContour", &CrossSection::NumContour)
|
||||
.function("_Bounds", &CrossSection::Bounds)
|
||||
.function("simplify", &CrossSection::Simplify)
|
||||
.function("_Offset", &cross_js::Offset)
|
||||
.function("_ToPolygons", &CrossSection::ToPolygons)
|
||||
.function("hull",
|
||||
select_overload<CrossSection() const>(&CrossSection::Hull));
|
||||
|
||||
// CrossSection Static Methods
|
||||
function("_Square", &CrossSection::Square);
|
||||
function("_Circle", &CrossSection::Circle);
|
||||
function("_crossSectionCompose", &CrossSection::Compose);
|
||||
function("_crossSectionUnionN", &cross_js::UnionN);
|
||||
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
|
||||
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
.constructor(&man_js::FromMeshJS)
|
||||
.function("add", &man_js::Union)
|
||||
.function("subtract", &man_js::Difference)
|
||||
.function("intersect", &man_js::Intersection)
|
||||
.function("_Split", &man_js::Split)
|
||||
.function("_SplitByPlane", &man_js::SplitByPlane)
|
||||
.function("_TrimByPlane", &Manifold::TrimByPlane)
|
||||
.function("_GetMeshJS", &js::GetMeshJS)
|
||||
.function("refine", &Manifold::Refine)
|
||||
.function("_Warp", &man_js::Warp)
|
||||
.function("_SetProperties", &man_js::SetProperties)
|
||||
.function("transform", &man_js::Transform)
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
.function("_Rotate", &Manifold::Rotate)
|
||||
.function("_Scale", &Manifold::Scale)
|
||||
.function("_Mirror", &Manifold::Mirror)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
.function("numEdge", &Manifold::NumEdge)
|
||||
.function("numTri", &Manifold::NumTri)
|
||||
.function("numProp", &Manifold::NumProp)
|
||||
.function("numPropVert", &Manifold::NumPropVert)
|
||||
.function("_boundingBox", &Manifold::BoundingBox)
|
||||
.function("precision", &Manifold::Precision)
|
||||
.function("genus", &Manifold::Genus)
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("calculateCurvature", &Manifold::CalculateCurvature)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
// Manifold Static Methods
|
||||
function("_Cube", &Manifold::Cube);
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
function("_Sphere", &Manifold::Sphere);
|
||||
function("_Tetrahedron", &Manifold::Tetrahedron);
|
||||
function("_Smooth", &js::Smooth);
|
||||
function("_Extrude", &Manifold::Extrude);
|
||||
function("_Triangulate", &Triangulate);
|
||||
function("_Revolve", &Manifold::Revolve);
|
||||
function("_LevelSet", &man_js::LevelSet);
|
||||
function("_Merge", &js::Merge);
|
||||
function("_ReserveIDs", &Manifold::ReserveIDs);
|
||||
function("_manifoldCompose", &Manifold::Compose);
|
||||
function("_manifoldUnionN", &man_js::UnionN);
|
||||
function("_manifoldDifferenceN", &man_js::DifferenceN);
|
||||
function("_manifoldIntersectionN", &man_js::IntersectionN);
|
||||
|
||||
// Quality Globals
|
||||
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
|
||||
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Quality::SetCircularSegments);
|
||||
function("getCircularSegments", &Quality::GetCircularSegments);
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
// 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;
|
||||
|
||||
// conversion utilities
|
||||
|
||||
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 vec2polygons(vec, f = x => x) {
|
||||
const result = [];
|
||||
const nPoly = vec.size();
|
||||
for (let i = 0; i < nPoly; i++) {
|
||||
const v = vec.get(i);
|
||||
const nPts = v.size();
|
||||
const poly = [];
|
||||
for (let j = 0; j < nPts; j++) {
|
||||
poly.push(f(v.get(j)));
|
||||
}
|
||||
result.push(poly);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function polygons2vec(polygons) {
|
||||
if (polygons[0].length < 3) {
|
||||
polygons = [polygons];
|
||||
}
|
||||
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 vararg2vec2(vec) {
|
||||
if (vec[0] instanceof Array) return {x: vec[0][0], y: vec[0][1]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
// default to 0
|
||||
return {x: vec[0] || 0, y: vec[1] || 0};
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
function vararg2vec3(vec) {
|
||||
if (vec[0] instanceof Array)
|
||||
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
|
||||
if (typeof (vec[0]) == 'number')
|
||||
// default to 0
|
||||
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
|
||||
return vec[0];
|
||||
}
|
||||
|
||||
function fillRuleToInt(fillRule) {
|
||||
return fillRule == 'EvenOdd' ? 0 :
|
||||
fillRule == 'NonZero' ? 1 :
|
||||
fillRule == 'Negative' ? 3 :
|
||||
/* Positive */ 2;
|
||||
}
|
||||
|
||||
function joinTypeToInt(joinType) {
|
||||
return joinType == 'Round' ? 1 : joinType == 'Miter' ? 2 : /* Square */ 0;
|
||||
}
|
||||
|
||||
// CrossSection methods
|
||||
|
||||
const CrossSectionCtor = Module.CrossSection;
|
||||
|
||||
function cross(polygons, fillRule = 'Positive') {
|
||||
if (polygons instanceof CrossSectionCtor) {
|
||||
return polygons;
|
||||
} else {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
}
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec2(vec));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.scale = function(vec) {
|
||||
// if only one factor provided, scale both x and y with it
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec2([vec]));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec2([vec]));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec2Ptr) {
|
||||
const x = getValue(vec2Ptr, 'float');
|
||||
const y = getValue(vec2Ptr + 4, 'float');
|
||||
const vert = [x, y];
|
||||
func(vert);
|
||||
setValue(vec2Ptr, vert[0], 'float');
|
||||
setValue(vec2Ptr + 4, vert[1], 'float');
|
||||
}, 'vi');
|
||||
const out = this._Warp(wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.bounds = function() {
|
||||
const result = this._Bounds();
|
||||
return {
|
||||
min: ['x', 'y'].map(f => result.min[f]),
|
||||
max: ['x', 'y'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.offset = function(
|
||||
delta, joinType = 'Square', miterLimit = 2.0, circularSegments = 0) {
|
||||
return this._Offset(
|
||||
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.extrude = function(
|
||||
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
|
||||
center = false) {
|
||||
scaleTop = vararg2vec2([scaleTop]);
|
||||
const man =
|
||||
Module._Extrude(this, height, nDivisions, twistDegrees, scaleTop);
|
||||
return (center ? man.translate([0., 0., -height / 2.]) : man);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.revolve = function(
|
||||
circularSegments = 0, revolveDegrees = 360.0) {
|
||||
return Module._Revolve(this, circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.add = function(other) {
|
||||
return this._add(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.subtract = function(other) {
|
||||
return this._subtract(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.intersect = function(other) {
|
||||
return this._intersect(cross(other));
|
||||
};
|
||||
|
||||
Module.CrossSection.prototype.toPolygons = function() {
|
||||
const vec = this._ToPolygons();
|
||||
const result = vec2polygons(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
// Manifold methods
|
||||
|
||||
Module.Manifold.prototype.warp = function(func) {
|
||||
const wasmFuncPtr = addFunction(function(vec3Ptr) {
|
||||
const x = getValue(vec3Ptr, 'float');
|
||||
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);
|
||||
|
||||
const status = out.status();
|
||||
if (status.value !== 0) {
|
||||
throw new Module.ManifoldError(status.value);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.setProperties = function(numProp, func) {
|
||||
const oldNumProp = this.numProp();
|
||||
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
|
||||
const newProp = [];
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
newProp[i] = getValue(newPtr + 4 * i, 'float');
|
||||
}
|
||||
const pos = [];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
pos[i] = getValue(vec3Ptr + 4 * i, 'float');
|
||||
}
|
||||
const oldProp = [];
|
||||
for (let i = 0; i < oldNumProp; ++i) {
|
||||
oldProp[i] = getValue(oldPtr + 4 * i, 'float');
|
||||
}
|
||||
|
||||
func(newProp, pos, oldProp);
|
||||
|
||||
for (let i = 0; i < numProp; ++i) {
|
||||
setValue(newPtr + 4 * i, newProp[i], 'float');
|
||||
}
|
||||
}, 'viii');
|
||||
const out = this._SetProperties(numProp, wasmFuncPtr);
|
||||
removeFunction(wasmFuncPtr);
|
||||
return out;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec3(vec));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.rotate = function(vec) {
|
||||
return this._Rotate(...vec);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.scale = function(vec) {
|
||||
// if only one factor provided, scale all three dimensions (xyz) with it
|
||||
if (typeof vec == 'number') {
|
||||
return this._Scale({x: vec, y: vec, z: vec});
|
||||
}
|
||||
return this._Scale(vararg2vec3([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.mirror = function(vec) {
|
||||
return this._Mirror(vararg2vec3([vec]));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.trimByPlane = function(normal, offset = 0.) {
|
||||
return this._TrimByPlane(vararg2vec3([normal]), offset);
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.split = function(manifold) {
|
||||
const vec = this._split(manifold);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
|
||||
const vec = this._splitByPlane(vararg2vec3([normal]), offset);
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.decompose = function() {
|
||||
const vec = this._Decompose();
|
||||
const result = fromVec(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
const result = this._boundingBox();
|
||||
return {
|
||||
min: ['x', 'y', 'z'].map(f => result.min[f]),
|
||||
max: ['x', 'y', 'z'].map(f => result.max[f]),
|
||||
};
|
||||
};
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
triVerts = new Uint32Array(),
|
||||
vertProperties = new Float32Array(),
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent,
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
this.vertProperties = vertProperties;
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.runOriginalID = runOriginalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
return this.triVerts.length / 3;
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
get numRun() {
|
||||
return this.runOriginalID.length;
|
||||
}
|
||||
|
||||
merge() {
|
||||
const {changed, mesh} = Module._Merge(this);
|
||||
Object.assign(this, {...mesh});
|
||||
return changed;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
|
||||
}
|
||||
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
numProp * vert + 3, numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
|
||||
transform(run) {
|
||||
const mat4 = new Array(16);
|
||||
for (const col of [0, 1, 2, 3]) {
|
||||
for (const row of [0, 1, 2]) {
|
||||
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
|
||||
}
|
||||
}
|
||||
mat4[15] = 1;
|
||||
return mat4;
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
|
||||
if (normalIdx instanceof Array)
|
||||
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
case Module.status.NonFiniteVertex.value:
|
||||
message = 'Non-finite vertex';
|
||||
break;
|
||||
case Module.status.NotManifold.value:
|
||||
message = 'Not manifold';
|
||||
break;
|
||||
case Module.status.VertexOutOfBounds.value:
|
||||
message = 'Vertex index out of bounds';
|
||||
break;
|
||||
case Module.status.PropertiesWrongLength.value:
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.MissingPositionProperties.value:
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.MergeVectorsDifferentLengths.value:
|
||||
message = 'Merge vectors have different lengths';
|
||||
break;
|
||||
case Module.status.MergeIndexOutOfBounds.value:
|
||||
message = 'Merge index out of bounds';
|
||||
break;
|
||||
case Module.status.TransformWrongLength.value:
|
||||
message = 'Transform vector has wrong length';
|
||||
break;
|
||||
case Module.status.RunIndexWrongLength.value:
|
||||
message = 'Run index vector has wrong length';
|
||||
break;
|
||||
case Module.status.FaceIDWrongLength.value:
|
||||
message = 'Face ID vector has wrong length';
|
||||
case Module.status.InvalidConstruction.value:
|
||||
message = 'Manifold constructed with invalid parameters';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
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}
|
||||
});
|
||||
|
||||
// CrossSection Constructors
|
||||
|
||||
Module.CrossSection = function(polygons, fillRule = 'Positive') {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
|
||||
disposePolygons(polygonsVec);
|
||||
return cs;
|
||||
};
|
||||
|
||||
Module.CrossSection.ofPolygons = function(polygons, fillRule = 'Positive') {
|
||||
return new Module.CrossSection(polygons, fillRule);
|
||||
};
|
||||
|
||||
Module.CrossSection.square = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = {x: 1, y: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = {x: args[0], y: args[0]};
|
||||
else
|
||||
size = vararg2vec2(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Square(size, center);
|
||||
};
|
||||
|
||||
Module.CrossSection.circle = function(radius, circularSegments = 0) {
|
||||
return Module._Circle(radius, circularSegments);
|
||||
};
|
||||
|
||||
// allows args to be either CrossSection or polygons (constructed with
|
||||
// Positive fill)
|
||||
function crossSectionBatchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_crossSection();
|
||||
for (const cs of args) v.push_back(cross(cs));
|
||||
const result = Module['_crossSection' + name](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Module.CrossSection.compose = crossSectionBatchbool('Compose');
|
||||
Module.CrossSection.union = crossSectionBatchbool('UnionN');
|
||||
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
|
||||
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
|
||||
|
||||
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
|
||||
|
||||
// Because the constructor and prototype are being replaced, instanceof will
|
||||
// not work as desired unless we refer back to the original like this
|
||||
Object.defineProperty(Module.CrossSection, Symbol.hasInstance, {
|
||||
get: () => (t) => {
|
||||
return (t instanceof CrossSectionCtor);
|
||||
}
|
||||
});
|
||||
|
||||
// Manifold Constructors
|
||||
|
||||
const ManifoldCtor = Module.Manifold;
|
||||
Module.Manifold = function(mesh) {
|
||||
const manifold = new ManifoldCtor(mesh);
|
||||
|
||||
const status = manifold.status();
|
||||
if (status.value !== 0) {
|
||||
throw new Module.ManifoldError(status.value);
|
||||
}
|
||||
|
||||
return manifold;
|
||||
};
|
||||
|
||||
Module.Manifold.ofMesh = function(mesh) {
|
||||
return new Module.Manifold(mesh);
|
||||
};
|
||||
|
||||
Module.Manifold.tetrahedron = function() {
|
||||
return Module._Tetrahedron();
|
||||
};
|
||||
|
||||
Module.Manifold.cube = function(...args) {
|
||||
let size = undefined;
|
||||
if (args.length == 0)
|
||||
size = {x: 1, y: 1, z: 1};
|
||||
else if (typeof args[0] == 'number')
|
||||
size = {x: args[0], y: args[0], z: args[0]};
|
||||
else
|
||||
size = vararg2vec3(args);
|
||||
const center = args[1] || false;
|
||||
return Module._Cube(size, center);
|
||||
};
|
||||
|
||||
Module.Manifold.cylinder = function(
|
||||
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
|
||||
center = false) {
|
||||
return Module._Cylinder(
|
||||
height, radiusLow, radiusHigh, circularSegments, center);
|
||||
};
|
||||
|
||||
Module.Manifold.sphere = function(radius, circularSegments = 0) {
|
||||
return Module._Sphere(radius, circularSegments);
|
||||
};
|
||||
|
||||
Module.Manifold.smooth = function(mesh, sharpenedEdges = []) {
|
||||
const sharp = new Module.Vector_smoothness();
|
||||
toVec(sharp, sharpenedEdges);
|
||||
const result = Module._Smooth(mesh, sharp);
|
||||
sharp.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.extrude = function(
|
||||
polygons, height, nDivisions = 0, twistDegrees = 0.0,
|
||||
scaleTop = [1.0, 1.0], center = false) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
|
||||
};
|
||||
|
||||
Module.Manifold.revolve = function(
|
||||
polygons, circularSegments = 0, revolveDegrees = 360.0) {
|
||||
const cs = (polygons instanceof CrossSectionCtor) ?
|
||||
polygons :
|
||||
Module.CrossSection(polygons, 'Positive');
|
||||
return cs.revolve(circularSegments, revolveDegrees);
|
||||
};
|
||||
|
||||
Module.Manifold.reserveIDs = function(n) {
|
||||
return Module._ReserveIDs(n);
|
||||
};
|
||||
|
||||
Module.Manifold.compose = function(manifolds) {
|
||||
const vec = new Module.Vector_manifold();
|
||||
toVec(vec, manifolds);
|
||||
const result = Module._manifoldCompose(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
function manifoldBatchbool(name) {
|
||||
return function(...args) {
|
||||
if (args.length == 1) args = args[0];
|
||||
const v = new Module.Vector_manifold();
|
||||
for (const m of args) v.push_back(m);
|
||||
const result = Module['_manifold' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
Module.Manifold.union = manifoldBatchbool('Union');
|
||||
Module.Manifold.difference = manifoldBatchbool('Difference');
|
||||
Module.Manifold.intersection = manifoldBatchbool('Intersection');
|
||||
|
||||
Module.Manifold.levelSet = function(sdf, bounds, edgeLength, level = 0) {
|
||||
const bounds2 = {
|
||||
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
|
||||
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
|
||||
|
||||
// Because the constructor and prototype are being replaced, instanceof will
|
||||
// not work as desired unless we refer back to the original like this
|
||||
Object.defineProperty(Module.Manifold, Symbol.hasInstance, {
|
||||
get: () => (t) => {
|
||||
return (t instanceof ManifoldCtor);
|
||||
}
|
||||
});
|
||||
|
||||
// Top-level functions
|
||||
|
||||
Module.triangulate = function(polygons, precision = -1) {
|
||||
const polygonsVec = polygons2vec(polygons);
|
||||
const result = fromVec(
|
||||
Module._Triangulate(polygonsVec, precision), (x) => [x[0], x[1], x[2]]);
|
||||
disposePolygons(polygonsVec);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
manifold*
|
||||
@@ -0,0 +1,45 @@
|
||||
# WASM/JS Examples
|
||||
|
||||
This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, `<model-viewer>`, and `glTF`. Included are `manifold-gltf.js` and `gltf-io.js`, which are intended to be fairly general-purpose libraries for interfacing between manifolds and glTF. We should probably make them into their own npm package at some point.
|
||||
|
||||
## Local development
|
||||
|
||||
First, follow the directions in the root README to get your C++ build environment set up and working for WASM. From this directory (`bindings/wasm/examples/`) you can test the JS bindings by running:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
To develop the manifoldCAD.org editor as well as our other example pages, run
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
See `package.json` for other useful scripts.
|
||||
|
||||
Note that the `emcmake` command automatically copies your WASM build into `built/`, (here, not just under the `buildWASM` directory) which is then packaged by Vite into `dist/assets/`.
|
||||
|
||||
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
|
||||
```
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
|
||||
```
|
||||
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
|
||||
|
||||
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
|
||||
deployed version) note that it uses a service worker for faster loading. This
|
||||
means you need to open the page twice to see updates (the first time loads the
|
||||
old version and caches the new one, the second time loads the new version from
|
||||
cache). To see changes on each reload, open Chrome dev tools, go to the
|
||||
Application tab and check "update on reload".
|
||||
|
||||
### Note for firefox users
|
||||
|
||||
To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set
|
||||
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
|
||||
discussion of the
|
||||
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
|
||||
of this repository.
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
.header .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;
|
||||
}
|
||||
|
||||
.edit {
|
||||
background-image: url(/icons/pencil.png);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
background-image: url(/icons/trash.png);
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.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,420 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import ManifoldWorker from './worker?worker';
|
||||
|
||||
// Loaded globally by examples.js
|
||||
const exampleFunctions = self.examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register(
|
||||
'/service-worker.js', {scope: './index.html'});
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// 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);
|
||||
};
|
||||
// Stop text input spaces from triggering the button
|
||||
button.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
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.classList.add('edit');
|
||||
|
||||
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.classList.add('trash');
|
||||
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;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('/manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('/manifold-encapsulated-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
return `
|
||||
${global.replaceAll('export', '')}
|
||||
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
|
||||
declare interface ManifoldToplevel {
|
||||
CrossSection: typeof T.CrossSection;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
triangulate: typeof T.triangulate;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
setup: () => void;
|
||||
}
|
||||
declare const module: ManifoldToplevel;
|
||||
`;
|
||||
}
|
||||
|
||||
async function getEditorDTS() {
|
||||
const global = await fetch('/editor.d.ts').then(response => response.text());
|
||||
return `${global.replace(/^import.*$/gm, '')}`;
|
||||
}
|
||||
|
||||
require.config({
|
||||
paths:
|
||||
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
|
||||
});
|
||||
require(['vs/editor/editor.main'], async function() {
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getManifoldDTS());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getEditorDTS());
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
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 ManifoldWorker();
|
||||
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;
|
||||
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
mv.src = objectURL;
|
||||
if (objectURL == null) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
createWorker();
|
||||
} else {
|
||||
setScript('safe', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,285 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Accessor} from '@gltf-transform/core';
|
||||
|
||||
import {EXTManifold} from './manifold-gltf.js';
|
||||
|
||||
export const attributeDefs = {
|
||||
'POSITION': {type: Accessor.Type.VEC3, components: 3},
|
||||
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
|
||||
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
|
||||
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
|
||||
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
|
||||
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
|
||||
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
|
||||
'SKIP_1': {type: null, components: 1},
|
||||
'SKIP_2': {type: null, components: 2},
|
||||
'SKIP_3': {type: null, components: 3},
|
||||
'SKIP_4': {type: null, components: 4},
|
||||
};
|
||||
|
||||
export function setupIO(io) {
|
||||
return io.registerExtensions([EXTManifold]);
|
||||
}
|
||||
|
||||
function readPrimitive(primitive, numProp, attributes) {
|
||||
const position = primitive.getAttribute('POSITION');
|
||||
const numVert = position.getCount();
|
||||
const vertProperties = [];
|
||||
let offset = 0;
|
||||
for (const attribute of attributes) {
|
||||
const size = attributeDefs[attribute].components;
|
||||
if (attribute === 'SKIP') {
|
||||
offset += size;
|
||||
continue;
|
||||
}
|
||||
const accessor = primitive.getAttribute(attribute);
|
||||
if (accessor) {
|
||||
const array = accessor.getArray();
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = array[i * size + j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
for (let j = 0; j < size; ++j) {
|
||||
vertProperties[numProp * i + offset + j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += size;
|
||||
}
|
||||
return vertProperties;
|
||||
}
|
||||
|
||||
export function readMesh(mesh, attributes, materials) {
|
||||
const primitives = mesh.listPrimitives();
|
||||
if (primitives.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (attributes.length === 0) {
|
||||
const attributeSet = new Set();
|
||||
for (const primitive of primitives) {
|
||||
const semantics = primitive.listSemantics();
|
||||
for (const semantic of semantics) {
|
||||
attributeSet.add(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic in attributeDefs) {
|
||||
if (attributeSet.has(semantic)) {
|
||||
attributes.push(semantic);
|
||||
attributeSet.delete(semantic);
|
||||
}
|
||||
}
|
||||
for (const semantic of attributeSet.keys()) {
|
||||
attributes.push(semantic);
|
||||
}
|
||||
}
|
||||
|
||||
if (attributes.length < 1 || attributes[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
const numProp = attributes.map((def) => attributeDefs[def].components)
|
||||
.reduce((a, b) => a + b);
|
||||
|
||||
const manifoldPrimitive = mesh.getExtension('EXT_manifold');
|
||||
|
||||
let vertPropArray = [];
|
||||
let triVertArray = [];
|
||||
const runIndexArray = [0];
|
||||
const mergeFromVert = [];
|
||||
const mergeToVert = [];
|
||||
if (manifoldPrimitive != null) {
|
||||
vertPropArray = readPrimitive(primitives[0], numProp, attributes);
|
||||
for (const primitive of primitives) {
|
||||
triVertArray = [...triVertArray, ...primitive.getIndices().getArray()];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
materials.push(primitive.getMaterial());
|
||||
}
|
||||
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
|
||||
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
|
||||
const vert2merge = new Map();
|
||||
for (const [i, idx] of mergeTriVert.entries()) {
|
||||
vert2merge.set(triVertArray[idx], mergeTo[i]);
|
||||
}
|
||||
for (const [from, to] of vert2merge.entries()) {
|
||||
mergeFromVert.push(from);
|
||||
mergeToVert.push(to);
|
||||
}
|
||||
} else {
|
||||
for (const primitive of primitives) {
|
||||
const numVert = vertPropArray.length / numProp;
|
||||
vertPropArray =
|
||||
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
|
||||
triVertArray = [
|
||||
...triVertArray,
|
||||
...primitive.getIndices().getArray().map((i) => i + numVert)
|
||||
];
|
||||
runIndexArray.push(triVertArray.length);
|
||||
materials.push(primitive.getMaterial());
|
||||
}
|
||||
}
|
||||
const vertProperties = new Float32Array(vertPropArray);
|
||||
const triVerts = new Uint32Array(triVertArray);
|
||||
const runIndex = new Uint32Array(runIndexArray);
|
||||
|
||||
return {
|
||||
numProp,
|
||||
triVerts,
|
||||
vertProperties,
|
||||
runIndex,
|
||||
mergeFromVert,
|
||||
mergeToVert
|
||||
};
|
||||
}
|
||||
|
||||
export function writeMesh(doc, manifoldMesh, attributes, materials) {
|
||||
if (doc.getRoot().listBuffers().length === 0) {
|
||||
doc.createBuffer();
|
||||
}
|
||||
const buffer = doc.getRoot().listBuffers()[0];
|
||||
const manifoldExtension = doc.createExtension(EXTManifold);
|
||||
|
||||
const attributeUnion = [];
|
||||
for (const matAttributes of attributes) {
|
||||
matAttributes.forEach((attribute, i) => {
|
||||
if (i >= attributeUnion.length) {
|
||||
attributeUnion.push(attribute);
|
||||
} else {
|
||||
const size = attributeDefs[attribute].components;
|
||||
const unionSize = attributeDefs[attributeUnion[i]].components;
|
||||
if (size != unionSize) {
|
||||
throw new Error(
|
||||
'Attribute sizes do not correspond: ', attribute, ' and ',
|
||||
attributeUnion[i]);
|
||||
}
|
||||
if (attributeDefs[attributeUnion[i]].type == null) {
|
||||
attributeUnion[i] = attribute;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (attributeUnion.length < 1 || attributeUnion[0] !== 'POSITION')
|
||||
throw new Error('First attribute must be "POSITION".');
|
||||
|
||||
const mesh = doc.createMesh();
|
||||
const numPrimitive = manifoldMesh.runIndex.length - 1;
|
||||
for (let run = 0; run < numPrimitive; ++run) {
|
||||
const indices = doc.createAccessor('indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array());
|
||||
const primitive = doc.createPrimitive().setIndices(indices);
|
||||
const material = materials[run];
|
||||
if (material) {
|
||||
primitive.setMaterial(material);
|
||||
}
|
||||
mesh.addPrimitive(primitive);
|
||||
}
|
||||
|
||||
const numVert = manifoldMesh.numVert;
|
||||
const numProp = manifoldMesh.numProp;
|
||||
let offset = 0;
|
||||
attributeUnion.forEach((attribute, aIdx) => {
|
||||
if (attribute === 'SKIP') {
|
||||
++offset;
|
||||
return;
|
||||
}
|
||||
const def = attributeDefs[attribute];
|
||||
if (def == null)
|
||||
throw new Error(attribute + ' is not a recognized attribute.');
|
||||
|
||||
const n = def.components;
|
||||
if (offset + n > numProp) throw new Error('Too many attribute channels.');
|
||||
|
||||
const array = new Float32Array(n * numVert);
|
||||
for (let v = 0; v < numVert; ++v) {
|
||||
for (let i = 0; i < n; ++i) {
|
||||
array[n * v + i] =
|
||||
manifoldMesh.vertProperties[numProp * v + offset + i];
|
||||
}
|
||||
}
|
||||
|
||||
const accessor =
|
||||
doc.createAccessor().setBuffer(buffer).setType(def.type).setArray(
|
||||
array);
|
||||
|
||||
mesh.listPrimitives().forEach((primitive, pIdx) => {
|
||||
if (attributes[pIdx].length > aIdx &&
|
||||
attributeDefs[attributes[pIdx][aIdx]].type != null) {
|
||||
primitive.setAttribute(attribute, accessor);
|
||||
}
|
||||
});
|
||||
offset += n;
|
||||
});
|
||||
|
||||
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
|
||||
mesh.setExtension('EXT_manifold', manifoldPrimitive);
|
||||
|
||||
const indices = doc.createAccessor('indices')
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(manifoldMesh.triVerts);
|
||||
manifoldPrimitive.setIndices(indices);
|
||||
manifoldPrimitive.setRunIndex(manifoldMesh.runIndex);
|
||||
|
||||
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
|
||||
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
|
||||
vert2merge[from] = manifoldMesh.mergeToVert[i];
|
||||
}
|
||||
const ind = [];
|
||||
const val = [];
|
||||
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
|
||||
const newVert = vert2merge[vert];
|
||||
if (vert !== newVert) {
|
||||
ind.push(i);
|
||||
val.push(newVert);
|
||||
}
|
||||
}
|
||||
const indicesAccessor = doc.createAccessor()
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(ind));
|
||||
const valuesAccessor = doc.createAccessor()
|
||||
.setBuffer(buffer)
|
||||
.setType(Accessor.Type.SCALAR)
|
||||
.setArray(new Uint32Array(val));
|
||||
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
export function disposeMesh(mesh) {
|
||||
if (!mesh) return;
|
||||
const primitives = mesh.listPrimitives();
|
||||
for (const primitive of primitives) {
|
||||
primitive.getIndices()?.dispose();
|
||||
for (const accessor of primitive.listAttributes()) {
|
||||
accessor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
export async function loadTexture(texture, uri) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>ManifoldCAD</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description"
|
||||
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
|
||||
<meta name="theme-color" content="#A288B3">
|
||||
<meta property="og:title" content="ManifoldCAD">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://manifoldcad.org/" />
|
||||
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
|
||||
<meta property="og:image:width" content="850">
|
||||
<meta property="og:image:height" content="850">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link type="text/css" href="editor.css" rel="stylesheet">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "ManifoldCAD",
|
||||
"applicationCategory": "DesignApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
self.ModelViewerElement = self.ModelViewerElement || {};
|
||||
self.ModelViewerElement.modelCacheSize = 0;
|
||||
</script>
|
||||
<script type="module" src="/examples.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
<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 type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,189 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>Make manifold glTF</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
<style>
|
||||
body {
|
||||
background-color: #f7f7f7;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(0, 0, 0, .87);
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 700px;
|
||||
margin: 1em;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
model-viewer {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vw;
|
||||
max-width: 600px;
|
||||
max-height: 600px;
|
||||
border: 3px solid black;
|
||||
border-radius: 10px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#poster {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p>Load a glTF/GLB model and our <a href="https://github.com/elalish/manifold">Manifold</a> library will attempt to
|
||||
merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have
|
||||
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_manifold</a> extension, thus preserving the
|
||||
manifold data without losing any mesh properties.</p>
|
||||
<p> If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a
|
||||
solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the
|
||||
download button is still disabled, it means there were no manifold meshes found. </p>
|
||||
<input type="file" id="input">
|
||||
<button id="download" disabled>Download manifold GLB</button>
|
||||
<input type="checkbox" id="viewFinal" disabled>
|
||||
<label for="viewFinal">View Manifold GLB</label>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Loaded GLB model">
|
||||
<span id="poster" slot="poster">Drop a GLB here</span>
|
||||
</model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { WebIO } from '@gltf-transform/core';
|
||||
import { prune } from '@gltf-transform/functions';
|
||||
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
|
||||
import { SimpleDropzone } from 'simple-dropzone';
|
||||
import { readMesh, writeMesh, setupIO } from './gltf-io.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
const inputEl = document.querySelector('#input');
|
||||
const downloadButton = document.querySelector('#download');
|
||||
const checkbox = document.querySelector('#viewFinal');
|
||||
const dropCtrl = new SimpleDropzone(mv, inputEl);
|
||||
|
||||
let inputGLBurl = null;
|
||||
let outputGLBurl = null;
|
||||
let allManifold = true;
|
||||
let anyManifold = false;
|
||||
|
||||
dropCtrl.on('drop', async ({ files }) => {
|
||||
for (const [path, file] of files) {
|
||||
const filename = file.name.toLowerCase();
|
||||
if (filename.match(/\.(gltf|glb)$/)) {
|
||||
URL.revokeObjectURL(inputGLBurl);
|
||||
inputGLBurl = URL.createObjectURL(file);
|
||||
await writeGLB(await readGLB(inputGLBurl));
|
||||
updateUI();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateUI() {
|
||||
if (allManifold) {
|
||||
checkbox.checked = true;
|
||||
checkbox.disabled = true;
|
||||
} else if (anyManifold) {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = false;
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
onClick();
|
||||
}
|
||||
|
||||
checkbox.onclick = onClick;
|
||||
|
||||
function onClick() {
|
||||
mv.src = checkbox.checked ? outputGLBurl : inputGLBurl;
|
||||
downloadButton.disabled = !checkbox.checked;
|
||||
};
|
||||
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = outputGLBurl;
|
||||
link.click();
|
||||
};
|
||||
|
||||
async function writeGLB(doc) {
|
||||
URL.revokeObjectURL(outputGLBurl);
|
||||
if (!anyManifold) {
|
||||
return;
|
||||
}
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
outputGLBurl = URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
async function readGLB(url) {
|
||||
allManifold = false;
|
||||
anyManifold = false;
|
||||
updateUI();
|
||||
allManifold = true;
|
||||
const docIn = await io.read(url);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const attributes = [];
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const materials = [];
|
||||
const tmpMesh = readMesh(mesh, attributes, materials);
|
||||
tmpMesh.runOriginalID = [];
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
for (let i = 0; i < materials.length; ++i) {
|
||||
tmpMesh.runOriginalID.push(firstID + i);
|
||||
}
|
||||
const manifoldMesh = new Mesh(tmpMesh);
|
||||
mesh.dispose();
|
||||
|
||||
const matAttributes = [];
|
||||
materials.forEach(() => matAttributes.push(attributes));
|
||||
|
||||
manifoldMesh.merge();
|
||||
|
||||
try {
|
||||
// Test manifoldness - will throw if not.
|
||||
const manifold = Manifold(manifoldMesh);
|
||||
node.setMesh(writeMesh(docIn, manifold.getMesh(), matAttributes, materials));
|
||||
manifold.delete();
|
||||
anyManifold = true;
|
||||
} catch (e) {
|
||||
console.log(mesh.getName(), e);
|
||||
allManifold = false;
|
||||
}
|
||||
}
|
||||
|
||||
await docIn.transform(prune());
|
||||
|
||||
return docIn;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,180 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Extension, ExtensionProperty, PropertyType} from '@gltf-transform/core';
|
||||
|
||||
const NAME = 'EXT_manifold';
|
||||
|
||||
export class ManifoldPrimitive extends ExtensionProperty {
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
init() {
|
||||
this.EXTENSION_NAME = NAME;
|
||||
this.propertyType = 'ManifoldPrimitive';
|
||||
this.parentTypes = [PropertyType.MESH];
|
||||
}
|
||||
|
||||
getDefaults() {
|
||||
return Object.assign(
|
||||
super.getDefaults(),
|
||||
{manifoldPrimitive: null, mergeIndices: null, mergeValues: null});
|
||||
}
|
||||
|
||||
getMergeIndices() {
|
||||
return this.getRef('mergeIndices');
|
||||
}
|
||||
|
||||
getMergeValues() {
|
||||
return this.getRef('mergeValues');
|
||||
}
|
||||
|
||||
setMerge(indicesAccessor, valuesAccessor) {
|
||||
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
|
||||
throw new Error('merge vectors must be the same length.');
|
||||
this.setRef('mergeIndices', indicesAccessor);
|
||||
return this.setRef('mergeValues', valuesAccessor);
|
||||
}
|
||||
|
||||
getRunIndex() {
|
||||
return this.get('runIndex');
|
||||
}
|
||||
|
||||
setRunIndex(runIndex) {
|
||||
return this.set('runIndex', runIndex);
|
||||
}
|
||||
|
||||
setIndices(indices) {
|
||||
return this.setRef('indices', indices);
|
||||
}
|
||||
|
||||
getIndices() {
|
||||
return this.getRef('indices');
|
||||
}
|
||||
}
|
||||
|
||||
export class EXTManifold extends Extension {
|
||||
extensionName = NAME;
|
||||
static EXTENSION_NAME = NAME;
|
||||
|
||||
createManifoldPrimitive() {
|
||||
return new ManifoldPrimitive(this.document.getGraph());
|
||||
}
|
||||
|
||||
read(context) {
|
||||
const {json} = context.jsonDoc;
|
||||
const meshDefs = json.meshes || [];
|
||||
|
||||
meshDefs.forEach((meshDef, meshIndex) => {
|
||||
if (!meshDef.extensions || !meshDef.extensions[NAME]) return;
|
||||
|
||||
const mesh = context.meshes[meshIndex];
|
||||
const manifoldPrimitive = this.createManifoldPrimitive();
|
||||
mesh.setExtension(NAME, manifoldPrimitive);
|
||||
|
||||
const manifoldDef = meshDef.extensions[NAME];
|
||||
|
||||
if (manifoldDef.manifoldPrimitive) {
|
||||
let count = 0;
|
||||
const runIndex = [];
|
||||
runIndex.push(count);
|
||||
for (const primitive of mesh.listPrimitives()) {
|
||||
count += primitive.getIndices().getCount();
|
||||
runIndex.push(count);
|
||||
}
|
||||
manifoldPrimitive.setRunIndex(runIndex);
|
||||
}
|
||||
|
||||
if (manifoldDef.mergeIndices && manifoldDef.mergeValues) {
|
||||
manifoldPrimitive.setMerge(
|
||||
context.accessors[manifoldDef.mergeIndices],
|
||||
context.accessors[manifoldDef.mergeValues]);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
write(context) {
|
||||
const {json} = context.jsonDoc;
|
||||
|
||||
this.document.getRoot().listMeshes().forEach((mesh) => {
|
||||
const manifoldPrimitive = mesh.getExtension(NAME);
|
||||
if (!manifoldPrimitive) return;
|
||||
|
||||
const meshIndex = context.meshIndexMap.get(mesh);
|
||||
const meshDef = json.meshes[meshIndex];
|
||||
|
||||
const runIndex = manifoldPrimitive.getRunIndex();
|
||||
const numPrimitive = runIndex.length - 1;
|
||||
|
||||
if (numPrimitive !== meshDef.primitives.length) {
|
||||
throw new Error(
|
||||
'The number of primitives must be exactly one less than the length of runIndex.');
|
||||
}
|
||||
|
||||
const mergeIndicesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices());
|
||||
const mergeValuesIndex =
|
||||
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues());
|
||||
const mergeIndices = json.accessors[mergeIndicesIndex];
|
||||
const mergeValues = json.accessors[mergeValuesIndex];
|
||||
|
||||
const existingPrimitive = meshDef.primitives[0];
|
||||
const primitive = {
|
||||
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices()),
|
||||
mode: existingPrimitive.mode,
|
||||
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
|
||||
};
|
||||
|
||||
const indices = json.accessors[primitive.indices];
|
||||
if (!indices) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mergeIndices && mergeValues) {
|
||||
indices.sparse = {
|
||||
count: mergeIndices.count,
|
||||
indices: {
|
||||
bufferView: mergeIndices.bufferView,
|
||||
byteOffset: mergeIndices.byteOffset,
|
||||
componentType: mergeIndices.componentType
|
||||
},
|
||||
values: {
|
||||
bufferView: mergeValues.bufferView,
|
||||
byteOffset: mergeValues.byteOffset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for (let i = 0; i < numPrimitive; ++i) {
|
||||
const accessor = json.accessors[meshDef.primitives[i].indices];
|
||||
accessor.bufferView = indices.bufferView;
|
||||
accessor.byteOffset = indices.byteOffset + 4 * runIndex[i];
|
||||
accessor.count = runIndex[i + 1] - runIndex[i];
|
||||
}
|
||||
|
||||
meshDef.extensions = meshDef.extensions || {};
|
||||
meshDef.extensions[NAME] = {
|
||||
manifoldPrimitive: primitive,
|
||||
mergeIndices: mergeIndicesIndex,
|
||||
mergeValues: mergeValuesIndex
|
||||
};
|
||||
|
||||
// Test the manifold primitive by replacing the material primitives
|
||||
// meshDef.primitives = [primitive];
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<!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 <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
|
||||
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
|
||||
three.js example is <a href="three.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
<option value="union">Union</option>
|
||||
</select>
|
||||
<button id="download">Download manifold GLB</button>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { Document, WebIO } from '@gltf-transform/core';
|
||||
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
|
||||
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
const doc = new Document();
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
const attributes = ['POSITION', 'TEXCOORD_0'];
|
||||
const materialMap = new Map();
|
||||
|
||||
async function readGLB(url, attributes, materialMap) {
|
||||
const manifolds = [];
|
||||
const docIn = await io.read(url);
|
||||
await docIn.transform(flatten());
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
const ids = [];
|
||||
for (const node of nodes) {
|
||||
clearNodeTransform(node);
|
||||
const materials = [];
|
||||
const manifoldMesh = readMesh(node.getMesh(), attributes, materials);
|
||||
manifoldMesh.runOriginalID = [];
|
||||
const firstID = Manifold.reserveIDs(materials.length);
|
||||
for (let i = 0; i < materials.length; ++i) {
|
||||
manifoldMesh.runOriginalID.push(firstID + i);
|
||||
ids.push(firstID + i);
|
||||
}
|
||||
manifolds.push(Manifold(new Mesh(manifoldMesh)));
|
||||
}
|
||||
// pull in materials, TODO: replace with transfer() when available
|
||||
const startIdx = doc.getRoot().listMaterials().length;
|
||||
doc.merge(docIn);
|
||||
doc.getRoot().listScenes().forEach((s) => s.dispose());
|
||||
doc.getRoot().listBuffers().forEach((s) => s.dispose());
|
||||
for (const [i, id] of ids.entries()) {
|
||||
const material = doc.getRoot().listMaterials()[startIdx + i];
|
||||
materialMap.set(id, material);
|
||||
}
|
||||
|
||||
return Manifold.union(manifolds);
|
||||
}
|
||||
|
||||
const space = await readGLB('/models/space.glb', attributes, materialMap);
|
||||
const moon = await readGLB('/models/moon.glb', attributes, materialMap);
|
||||
|
||||
const node = doc.createNode();
|
||||
doc.createScene().addChild(node);
|
||||
|
||||
function csg(operation) {
|
||||
push2MV(Manifold[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function () {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
disposeMesh(node.getMesh());
|
||||
const materials = [];
|
||||
const matAttributes = [];
|
||||
for (const id of manifoldMesh.runOriginalID) {
|
||||
materials.push(materialMap.get(id));
|
||||
matAttributes.push(attributes);
|
||||
}
|
||||
const mesh = writeMesh(doc, manifoldMesh, matAttributes, materials);
|
||||
node.setMesh(mesh);
|
||||
await doc.transform(prune());
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "manifold-examples",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"build:watch": "vite build --watch",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"test:ui": "vitest --ui",
|
||||
"test:run": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gltf-transform/core": "^3.2.1",
|
||||
"@gltf-transform/extensions": "^3.2.1",
|
||||
"@gltf-transform/functions": "^3.2.1",
|
||||
"gl-matrix": "^3.4.3",
|
||||
"simple-dropzone": "0.8.3",
|
||||
"three": "0.151.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitest/ui": "^0.31.1",
|
||||
"@vitest/web-worker": "^0.31.1",
|
||||
"typescript": "5.0.4",
|
||||
"vite": "^4.3.2",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export const examples = {
|
||||
functions: {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the
|
||||
// API docs. Type e.g. "box." to see the Manifold API. Type
|
||||
// "CrossSection." or "Manifold." to list the 2D and 3D constructors,
|
||||
// respectively. Type "module." to see the static API - these functions
|
||||
// can also be used bare. Use console.log() to print output (lower-right).
|
||||
// This editor defines Z as up and units of mm.
|
||||
const {cube, sphere} = Manifold;
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result", or create at least one
|
||||
// GLTFNode - see Menger Sponge and Gyroid Module examples.
|
||||
const result = box.subtract(ball);
|
||||
|
||||
// For visual debug, wrap any shape with show() and it and all of its
|
||||
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
|
||||
// only() to ghost out everything else, akin to * in OpenSCAD.
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This PWA is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// Consider installing it (icon in the search bar) for easy access.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples. The
|
||||
// gl-matrix package from npm is automatically imported for convenience -
|
||||
// its API is available in the top-level glMatrix object.
|
||||
|
||||
// Use GLTFNode for disjoint manifolds rather than compose(), as this will
|
||||
// keep them better organized in the GLB. This will also allow you to
|
||||
// specify material properties, and even vertex colors via
|
||||
// setProperties(). See Tetrahedron Puzzle example.
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as
|
||||
// a puzzle. This 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; // Divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = Manifold.tetrahedron().scale(scale);
|
||||
|
||||
const box = [];
|
||||
box.push([1, -1], [1, 1]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (4 * scale), 1 - i * 2 / nDivisions]);
|
||||
}
|
||||
|
||||
const cyan = [0, 1, 1];
|
||||
const magenta = [1, 0, 1];
|
||||
const fade = (color, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = cyan[i] * pos[2] + magenta[i] * (1 - pos[2]);
|
||||
}
|
||||
};
|
||||
|
||||
// setProperties(3, fade) creates three channels of vertex properties
|
||||
// according to the above fade function. setMaterial assigns these
|
||||
// channels as colors, and sets the factor to white, since our default is
|
||||
// yellow.
|
||||
const screw = setMaterial(
|
||||
Manifold.extrude(box, 1, nDivisions, 270).setProperties(3, fade),
|
||||
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
|
||||
|
||||
const result = tet.intersect(
|
||||
screw.rotate([0, 0, -45]).translate([0, 0, -0.5]).scale(2 * scale));
|
||||
|
||||
// Assigned materials are only applied to a GLTFNode. Note that material
|
||||
// definitions cascade, applying recursively to all child surfaces, but
|
||||
// overridden by any materials defined lower down. Our default material:
|
||||
// {
|
||||
// roughness = 0.2,
|
||||
// metallic = 1,
|
||||
// baseColorFactor = [1, 1, 0],
|
||||
// alpha = 1,
|
||||
// unlit = false,
|
||||
// name = ''
|
||||
// }
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
const {sphere, cylinder, union} = Manifold;
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df =
|
||||
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = Manifold.sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with
|
||||
// smooth() and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
positions.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const triVerts = Uint32Array.from(triangles);
|
||||
const vertProperties = Float32Array.from(positions);
|
||||
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
|
||||
|
||||
const colorCurvature = (color, pos, oldProp) => {
|
||||
const a = Math.max(0, Math.min(1, oldProp[0] / 3 + 0.5));
|
||||
const b = a * a * (3 - 2 * a);
|
||||
const red = [1, 0, 0];
|
||||
const blue = [0, 0, 1];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
color[i] = (1 - b) * blue[i] + b * red[i];
|
||||
}
|
||||
};
|
||||
const result = Manifold.smooth(scallop, sharpenedEdges)
|
||||
.refine(n)
|
||||
.calculateCurvature(-1, 0)
|
||||
.setProperties(3, colorCurvature);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {
|
||||
baseColorFactor: [1, 1, 1],
|
||||
metallic: 0,
|
||||
attributes: ['COLOR_0']
|
||||
};
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common
|
||||
// factor then you will get multiple separate, interwoven knots. This is
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
// @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) {
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
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 offset = 2
|
||||
const circle = CrossSection.circle(1, n).translate([offset, 0]);
|
||||
|
||||
const func = (v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(p * minorRadius, q * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
let knot = Manifold.revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = Manifold.compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
|
||||
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
|
||||
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
|
||||
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = Manifold.cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = Manifold.compose(holes);
|
||||
|
||||
result = Manifold.difference(
|
||||
result,
|
||||
hole,
|
||||
hole.rotate([90, 0, 0]),
|
||||
hole.rotate([0, 90, 0]),
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
const posColors = (newProp, pos) => {
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
newProp[i] = (1 - pos[i]) / 2;
|
||||
}
|
||||
};
|
||||
|
||||
const result = mengerSponge(3)
|
||||
.trimByPlane([1, 1, 1], 0)
|
||||
.setProperties(3, posColors)
|
||||
.scale(100);
|
||||
|
||||
const node = new GLTFNode();
|
||||
node.manifold = result;
|
||||
node.material = {baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']};
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = Manifold.cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = Manifold.extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const o = vec2.fromValues(0, 0);
|
||||
const p0 = vec2.fromValues(outerRadius, 0);
|
||||
const p1 = vec2.fromValues(innerRadius, -cut);
|
||||
const p2 = vec2.fromValues(innerRadius, cut);
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
}
|
||||
const result =
|
||||
Manifold.intersection(Manifold.extrude(stretch, width), b);
|
||||
return result;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return Manifold.difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
|
||||
// use of a Signed Distance Function (SDF) to create smooth, complex
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
// number of modules along pyramid edge (use 1 for print orientation)
|
||||
const m = 4;
|
||||
// module size
|
||||
const size = 20;
|
||||
// SDF resolution
|
||||
const n = 20;
|
||||
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return Manifold.levelSet(gyroid, box, period / n, level)
|
||||
.scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = Manifold.cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
const gyroidModule = rhombicDodecahedron()
|
||||
.intersect(gyroidOffset(-0.4))
|
||||
.subtract(gyroidOffset(0.4));
|
||||
|
||||
if (m > 1) {
|
||||
for (let i = 0; i < m; ++i) {
|
||||
for (let j = i; j < m; ++j) {
|
||||
for (let k = j; k < m; ++k) {
|
||||
const node = new GLTFNode();
|
||||
node.manifold = gyroidModule;
|
||||
node.translation =
|
||||
[(k + i - j) * size, (k - i) * size, (-j) * size];
|
||||
node.material = {
|
||||
baseColorFactor:
|
||||
[(k + i - j + 1) / m, (k - i + 1) / m, (j + 1) / m]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = gyroidModule.rotate([-45, 0, 90]).translate([
|
||||
0, 0, size / Math.sqrt(2)
|
||||
]);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
examples.functionBodies.set(name, body);
|
||||
};
|
||||
|
||||
if (typeof self !== 'undefined') {
|
||||
self.examples = examples;
|
||||
}
|
||||
|
After Width: | Height: | Size: 539 B |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 375 B |
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ManifoldCAD",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/mengerSponge192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/icons/mengerSponge512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"background_color": "#FFFFFF",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"scope": "/",
|
||||
"theme_color": "#A288B3",
|
||||
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
|
||||
"categories": [
|
||||
"design",
|
||||
"developer",
|
||||
"education",
|
||||
"graphics",
|
||||
"productivity"
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/icons/manifoldCAD.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
},
|
||||
{
|
||||
"src": "/icons/manifoldCADonly.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Increment version when updating CDN URLs to clean up cache.
|
||||
const cacheName = 'manifoldCAD-cache-v1';
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(caches.keys().then((keyList) => {
|
||||
return Promise.all(keyList.map((key) => {
|
||||
if (key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
// Serves from cache, then updates the cache in the background from the network,
|
||||
// if available. Update available on refresh.
|
||||
self.addEventListener(
|
||||
'fetch',
|
||||
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
|
||||
const networkFetch = fetch(e.request).then(response => {
|
||||
const clone = response.clone();
|
||||
caches.open(cacheName).then(cache => {
|
||||
cache.put(e.request, clone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
return cachedResponse || networkFetch;
|
||||
}))});
|
||||
@@ -0,0 +1,117 @@
|
||||
<!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 type="module">
|
||||
import * as THREE from 'three';
|
||||
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
|
||||
import Module from './built/manifold.js';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const { Manifold, Mesh } = wasm;
|
||||
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
|
||||
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 = Manifold.cube([0.2, 0.2, 0.2], true);
|
||||
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(Manifold[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 vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"lib": [
|
||||
"es2022",
|
||||
"dom"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
"include": [
|
||||
"./*.ts"
|
||||
],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// vite.config.js
|
||||
import {resolve} from 'path'
|
||||
import {defineConfig} from 'vite'
|
||||
|
||||
export default defineConfig({
|
||||
test: {testTimeout: 15000},
|
||||
worker: {
|
||||
format: 'es',
|
||||
},
|
||||
build: {
|
||||
target: 'esnext',
|
||||
rollupOptions: {
|
||||
input: {
|
||||
manifoldCAD: resolve(__dirname, 'index.html'),
|
||||
makeManifold: resolve(__dirname, 'make-manifold.html'),
|
||||
modelViewer: resolve(__dirname, 'model-viewer.html'),
|
||||
three: resolve(__dirname, 'three.html'),
|
||||
},
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import '@vitest/web-worker';
|
||||
|
||||
import {WebIO} from '@gltf-transform/core';
|
||||
import {expect, suite, test} from 'vitest';
|
||||
|
||||
import Module from './built/manifold.js';
|
||||
import {readMesh, setupIO} from './gltf-io.js';
|
||||
import {examples} from './public/examples.js';
|
||||
import ManifoldWorker from './worker?worker';
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
function initialized(worker) {
|
||||
return new Promise((resolve) => {
|
||||
worker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let objectURL = null;
|
||||
|
||||
async function runExample(name) {
|
||||
const worker = new ManifoldWorker();
|
||||
await initialized(worker);
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.onmessageerror = function({data}) {
|
||||
reject(data);
|
||||
};
|
||||
|
||||
worker.onerror = function(e) {
|
||||
reject(e);
|
||||
};
|
||||
|
||||
worker.onmessage = async function(e) {
|
||||
try {
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
if (objectURL == null) {
|
||||
reject('no objectURL');
|
||||
}
|
||||
const docIn = await io.read(objectURL);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const mesh = node.getMesh();
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
const attributes = [];
|
||||
const materials = [];
|
||||
const manifoldMesh = readMesh(mesh, attributes, materials);
|
||||
const manifold = wasm.Manifold(manifoldMesh);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
console.log(genus);
|
||||
manifold.delete();
|
||||
resolve({...prop, genus});
|
||||
}
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
|
||||
worker.postMessage(examples.functionBodies.get(name));
|
||||
});
|
||||
}
|
||||
|
||||
suite('Examples', () => {
|
||||
test('Intro', async () => {
|
||||
const result = await runExample('Intro');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Tetrahedron Puzzle', async () => {
|
||||
const result = await runExample('Tetrahedron Puzzle');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Rounded Frame', async () => {
|
||||
const result = await runExample('Rounded Frame');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Heart', async () => {
|
||||
const result = await runExample('Heart');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Scallop', async () => {
|
||||
const result = await runExample('Scallop');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Torus Knot', async () => {
|
||||
const result = await runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20786, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11177, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Menger Sponge', async () => {
|
||||
const result = await runExample('Menger Sponge');
|
||||
expect(result.genus).to.equal(729, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203222, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(130475, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', async () => {
|
||||
const result = await runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Gyroid Module', async () => {
|
||||
const result = await runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,464 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, Material, Node, WebIO} from '@gltf-transform/core';
|
||||
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
|
||||
import * as glMatrix from 'gl-matrix';
|
||||
|
||||
import Module from './built/manifold';
|
||||
//@ts-ignore
|
||||
import {setupIO, writeMesh} from './gltf-io';
|
||||
import type {GLTFMaterial, Quat} from './public/editor';
|
||||
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
|
||||
|
||||
interface WorkerStatic extends ManifoldToplevel {
|
||||
GLTFNode: typeof GLTFNode;
|
||||
show(manifold: Manifold): Manifold;
|
||||
only(manifold: Manifold): Manifold;
|
||||
setMaterial(manifold: Manifold, material: GLTFMaterial): void;
|
||||
cleanup(): void;
|
||||
}
|
||||
|
||||
const module = await Module() as unknown as WorkerStatic;
|
||||
module.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
glMatrix.glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
const io = setupIO(new WebIO());
|
||||
io.registerExtensions(KHRONOS_EXTENSIONS);
|
||||
|
||||
// manifold static methods (that return a new manifold)
|
||||
const manifoldStaticFunctions = [
|
||||
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'compose',
|
||||
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh'
|
||||
];
|
||||
// manifold member functions (that return a new manifold)
|
||||
const manifoldMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'decompose', 'warp', 'transform', 'translate',
|
||||
'rotate', 'scale', 'mirror', 'refine', 'setProperties', 'asOriginal',
|
||||
'trimByPlane', 'split', 'splitByPlane'
|
||||
];
|
||||
// CrossSection static methods (that return a new cross-section)
|
||||
const crossSectionStaticFunctions = [
|
||||
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
|
||||
'ofPolygons'
|
||||
];
|
||||
// CrossSection member functions (that return a new cross-section)
|
||||
const crossSectionMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
|
||||
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset'
|
||||
];
|
||||
// top level functions that construct a new manifold/mesh
|
||||
const toplevelConstructors = ['show', 'only', 'setMaterial'];
|
||||
const toplevel = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
'getCircularSegments', 'Mesh', 'GLTFNode', 'Manifold', 'CrossSection'
|
||||
];
|
||||
const exposedFunctions = toplevelConstructors.concat(toplevel);
|
||||
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
const memoryRegistry = new Array<Manifold|CrossSection>();
|
||||
|
||||
function addMembers(
|
||||
className: string, methodNames: Array<string>, areStatic: boolean) {
|
||||
//@ts-ignore
|
||||
const cls = module[className];
|
||||
const obj = areStatic ? cls : cls.prototype;
|
||||
for (const name of methodNames) {
|
||||
if (name != 'cylinder') {
|
||||
const originalFn = obj[name];
|
||||
obj[name] = function(...args: any) {
|
||||
//@ts-ignore
|
||||
const result = originalFn(...args);
|
||||
memoryRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addMembers('Manifold', manifoldMemberFunctions, false);
|
||||
addMembers('Manifold', manifoldStaticFunctions, true);
|
||||
addMembers('CrossSection', crossSectionMemberFunctions, false);
|
||||
addMembers('CrossSection', crossSectionStaticFunctions, true);
|
||||
|
||||
for (const name of toplevelConstructors) {
|
||||
//@ts-ignore
|
||||
const originalFn = module[name];
|
||||
//@ts-ignore
|
||||
module[name] = function(...args: any) {
|
||||
const result = originalFn(...args);
|
||||
memoryRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
module.cleanup = function() {
|
||||
for (const obj of memoryRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
memoryRegistry.length = 0;
|
||||
};
|
||||
|
||||
// Debug setup to show source meshes
|
||||
let ghost = false;
|
||||
const shown = new Map<number, Mesh>();
|
||||
const singles = new Map<number, Mesh>();
|
||||
|
||||
const SHOW = {
|
||||
baseColorFactor: [1, 0, 0],
|
||||
alpha: 0.25,
|
||||
roughness: 1,
|
||||
metallic: 0
|
||||
} as GLTFMaterial;
|
||||
|
||||
const GHOST = {
|
||||
baseColorFactor: [0.5, 0.5, 0.5],
|
||||
alpha: 0.25,
|
||||
roughness: 1,
|
||||
metallic: 0
|
||||
} as GLTFMaterial;
|
||||
|
||||
const nodes = new Array<GLTFNode>();
|
||||
const id2material = new Map<number, GLTFMaterial>();
|
||||
const materialCache = new Map<GLTFMaterial, Material>();
|
||||
|
||||
function cleanup() {
|
||||
ghost = false;
|
||||
shown.clear();
|
||||
singles.clear();
|
||||
nodes.length = 0;
|
||||
id2material.clear();
|
||||
materialCache.clear();
|
||||
}
|
||||
|
||||
class GLTFNode {
|
||||
private _parent?: GLTFNode;
|
||||
manifold?: Manifold;
|
||||
translation?: Vec3;
|
||||
rotation?: Vec3;
|
||||
scale?: Vec3;
|
||||
material?: GLTFMaterial;
|
||||
name?: string;
|
||||
|
||||
constructor(parent?: GLTFNode) {
|
||||
this._parent = parent;
|
||||
nodes.push(this);
|
||||
}
|
||||
clone(parent?: GLTFNode) {
|
||||
const copy = {...this};
|
||||
copy._parent = parent;
|
||||
nodes.push(copy);
|
||||
return copy;
|
||||
}
|
||||
get parent() {
|
||||
return this._parent;
|
||||
}
|
||||
}
|
||||
|
||||
module.GLTFNode = GLTFNode;
|
||||
|
||||
module.setMaterial = (manifold: Manifold, material: GLTFMaterial): Manifold => {
|
||||
const out = manifold.asOriginal();
|
||||
id2material.set(out.originalID(), material);
|
||||
return out;
|
||||
};
|
||||
|
||||
function debug(manifold: Manifold, map: Map<number, Mesh>) {
|
||||
let result = manifold.asOriginal();
|
||||
map.set(result.originalID(), result.getMesh());
|
||||
return result;
|
||||
};
|
||||
|
||||
module.show = (manifold) => {
|
||||
return debug(manifold, shown);
|
||||
};
|
||||
|
||||
module.only = (manifold) => {
|
||||
ghost = true;
|
||||
return debug(manifold, singles);
|
||||
};
|
||||
|
||||
// Setup complete
|
||||
self.postMessage(null);
|
||||
|
||||
if (self.console) {
|
||||
const oldLog = self.console.log;
|
||||
self.console.log = function(...args) {
|
||||
let message = '';
|
||||
for (const arg of args) {
|
||||
if (arg == null) {
|
||||
message += 'undefined';
|
||||
} else if (typeof arg == 'object') {
|
||||
message += JSON.stringify(arg, null, 4);
|
||||
} else {
|
||||
message += arg.toString();
|
||||
}
|
||||
}
|
||||
self.postMessage({log: message});
|
||||
oldLog(...args);
|
||||
};
|
||||
}
|
||||
|
||||
// Swallow informational logs in testing framework
|
||||
function log(...args: any[]) {
|
||||
if (self.console) {
|
||||
self.console.log(...args);
|
||||
}
|
||||
}
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
const content = e.data +
|
||||
'\nreturn exportGLB(typeof result === "undefined" ? undefined : result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportGLB, glMatrix, module, //@ts-ignore
|
||||
...exposedFunctions.map(name => module[name]));
|
||||
} catch (error: any) {
|
||||
console.log(error.toString());
|
||||
self.postMessage({objectURL: null});
|
||||
} finally {
|
||||
module.cleanup();
|
||||
cleanup();
|
||||
}
|
||||
};
|
||||
|
||||
function createGLTFnode(doc: Document, node: GLTFNode) {
|
||||
const out = doc.createNode(node.name);
|
||||
if (node.translation) {
|
||||
out.setTranslation(node.translation);
|
||||
}
|
||||
if (node.rotation) {
|
||||
const {quat} = glMatrix;
|
||||
const deg2rad = Math.PI / 180;
|
||||
const q = quat.create() as Quat;
|
||||
quat.rotateX(q, q, deg2rad * node.rotation[0]);
|
||||
quat.rotateY(q, q, deg2rad * node.rotation[1]);
|
||||
quat.rotateZ(q, q, deg2rad * node.rotation[2]);
|
||||
out.setRotation(q);
|
||||
}
|
||||
if (node.scale) {
|
||||
out.setScale(node.scale);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function getBackupMaterial(node?: GLTFNode): GLTFMaterial {
|
||||
if (node == null) {
|
||||
return {};
|
||||
}
|
||||
if (node.material == null) {
|
||||
node.material = getBackupMaterial(node.parent);
|
||||
}
|
||||
return node.material;
|
||||
}
|
||||
|
||||
function makeDefaultedMaterial(doc: Document, {
|
||||
roughness = 0.2,
|
||||
metallic = 1,
|
||||
baseColorFactor = [1, 1, 0],
|
||||
alpha = 1,
|
||||
unlit = false,
|
||||
name = ''
|
||||
}: GLTFMaterial = {}): Material {
|
||||
const material = doc.createMaterial(name);
|
||||
|
||||
if (unlit) {
|
||||
const unlit = doc.createExtension(KHRMaterialsUnlit).createUnlit();
|
||||
material.setExtension('KHR_materials_unlit', unlit);
|
||||
}
|
||||
|
||||
if (alpha < 1) {
|
||||
material.setAlphaMode(Material.AlphaMode.BLEND).setDoubleSided(true);
|
||||
}
|
||||
|
||||
return material.setRoughnessFactor(roughness)
|
||||
.setMetallicFactor(metallic)
|
||||
.setBaseColorFactor([...baseColorFactor, alpha]);
|
||||
}
|
||||
|
||||
function getCachedMaterial(doc: Document, matDef: GLTFMaterial): Material {
|
||||
if (!materialCache.has(matDef)) {
|
||||
materialCache.set(matDef, makeDefaultedMaterial(doc, matDef));
|
||||
}
|
||||
return materialCache.get(matDef)!;
|
||||
}
|
||||
|
||||
function addMesh(
|
||||
doc: Document, node: Node, manifold: Manifold,
|
||||
backupMaterial: GLTFMaterial = {}) {
|
||||
const numTri = manifold.numTri();
|
||||
if (numTri == 0) {
|
||||
log('Empty manifold, skipping.');
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Triangles: ${numTri.toLocaleString()}`);
|
||||
const box = manifold.boundingBox();
|
||||
const size = [0, 0, 0];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
|
||||
}
|
||||
log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
|
||||
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
|
||||
const volume = Math.round(manifold.getProperties().volume / 10);
|
||||
log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
|
||||
(volume / 100).toLocaleString()} cm^3`);
|
||||
|
||||
// From Z-up to Y-up (glTF)
|
||||
const manifoldMesh = manifold.getMesh();
|
||||
|
||||
const materials = new Array<GLTFMaterial>();
|
||||
const attributes = new Array<Array<string>>();
|
||||
for (const id of manifoldMesh.runOriginalID!) {
|
||||
const material = id2material.get(id) || backupMaterial;
|
||||
materials.push(material);
|
||||
attributes.push(['POSITION', ...material.attributes ?? []]);
|
||||
}
|
||||
|
||||
const gltfMaterials = materials.map((matDef) => {
|
||||
return getCachedMaterial(doc, ghost ? GHOST : matDef);
|
||||
});
|
||||
|
||||
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
|
||||
|
||||
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
|
||||
let inMesh = shown.get(id);
|
||||
let single = false;
|
||||
if (inMesh == null) {
|
||||
single = true;
|
||||
inMesh = singles.get(id);
|
||||
}
|
||||
if (inMesh == null) {
|
||||
continue;
|
||||
}
|
||||
const mat = single ? materials[run] : SHOW;
|
||||
const debugNode =
|
||||
doc.createNode('debug')
|
||||
.setMesh(writeMesh(
|
||||
doc, inMesh, attributes, [getCachedMaterial(doc, mat)]))
|
||||
.setMatrix(manifoldMesh.transform(run));
|
||||
node.addChild(debugNode);
|
||||
}
|
||||
}
|
||||
|
||||
function cloneNode(toNode: Node, fromNode: Node) {
|
||||
toNode.setMesh(fromNode.getMesh());
|
||||
fromNode.listChildren().forEach((child) => {
|
||||
const clone = child.clone();
|
||||
toNode.addChild(clone);
|
||||
});
|
||||
}
|
||||
|
||||
function cloneNodeNewMaterial(
|
||||
doc: Document, toNode: Node, fromNode: Node, backupMaterial: Material,
|
||||
oldBackupMaterial: Material) {
|
||||
cloneNode(toNode, fromNode);
|
||||
const mesh = doc.createMesh();
|
||||
toNode.setMesh(mesh);
|
||||
fromNode.getMesh()!.listPrimitives().forEach((primitive) => {
|
||||
const newPrimitive = primitive.clone();
|
||||
if (primitive.getMaterial() === oldBackupMaterial) {
|
||||
newPrimitive.setMaterial(backupMaterial);
|
||||
}
|
||||
mesh.addPrimitive(newPrimitive);
|
||||
});
|
||||
}
|
||||
|
||||
function createNodeFromCache(
|
||||
doc: Document, nodeDef: GLTFNode,
|
||||
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
|
||||
const node = createGLTFnode(doc, nodeDef);
|
||||
if (nodeDef.manifold != null) {
|
||||
const backupMaterial = getBackupMaterial(nodeDef);
|
||||
const cachedNodes = manifold2node.get(nodeDef.manifold);
|
||||
if (cachedNodes == null) {
|
||||
addMesh(doc, node, nodeDef.manifold, backupMaterial);
|
||||
const cache = new Map<GLTFMaterial, Node>();
|
||||
cache.set(backupMaterial, node);
|
||||
manifold2node.set(nodeDef.manifold, cache);
|
||||
} else {
|
||||
const cachedNode = cachedNodes.get(backupMaterial);
|
||||
if (cachedNode == null) {
|
||||
const [oldBackupMaterial, oldNode] = cachedNodes.entries().next().value;
|
||||
cloneNodeNewMaterial(
|
||||
doc, node, oldNode, getCachedMaterial(doc, backupMaterial),
|
||||
getCachedMaterial(doc, oldBackupMaterial));
|
||||
cachedNodes.set(backupMaterial, node);
|
||||
} else {
|
||||
cloneNode(node, cachedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
async function exportGLB(manifold?: Manifold) {
|
||||
const doc = new Document();
|
||||
const halfRoot2 = Math.sqrt(2) / 2;
|
||||
const mm2m = 1 / 1000;
|
||||
const wrapper = doc.createNode('wrapper')
|
||||
.setRotation([-halfRoot2, 0, 0, halfRoot2])
|
||||
.setScale([mm2m, mm2m, mm2m]);
|
||||
doc.createScene().addChild(wrapper);
|
||||
|
||||
if (nodes.length > 0) {
|
||||
const node2gltf = new Map<GLTFNode, Node>();
|
||||
const manifold2node = new Map<Manifold, Map<GLTFMaterial, Node>>();
|
||||
let leafNodes = 0;
|
||||
|
||||
for (const nodeDef of nodes) {
|
||||
node2gltf.set(nodeDef, createNodeFromCache(doc, nodeDef, manifold2node));
|
||||
if (nodeDef.manifold) {
|
||||
++leafNodes;
|
||||
}
|
||||
}
|
||||
|
||||
for (const nodeDef of nodes) {
|
||||
const gltfNode = node2gltf.get(nodeDef)!;
|
||||
if (nodeDef.parent == null) {
|
||||
wrapper.addChild(gltfNode);
|
||||
} else {
|
||||
node2gltf.get(nodeDef.parent)!.addChild(gltfNode);
|
||||
}
|
||||
}
|
||||
|
||||
log('Total glTF nodes: ', nodes.length,
|
||||
', Total mesh references: ', leafNodes);
|
||||
} else {
|
||||
if (manifold == null) {
|
||||
log('No output because "result" is undefined and no "GLTFNode"s were created.');
|
||||
return;
|
||||
}
|
||||
const node = doc.createNode();
|
||||
addMesh(doc, node, manifold);
|
||||
wrapper.addChild(node);
|
||||
}
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
self.postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
|
||||
namespace js {
|
||||
val MeshGL2JS(const MeshGL& mesh) {
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("numProp", mesh.numProp);
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertProperties",
|
||||
val(typed_memory_view(mesh.vertProperties.size(),
|
||||
mesh.vertProperties.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
|
||||
mesh.mergeFromVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
|
||||
mesh.mergeToVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runIndex",
|
||||
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
|
||||
mesh.runOriginalID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("faceID",
|
||||
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
|
||||
mesh.runTransform.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.numProp = mesh["numProp"].as<int>();
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertProperties =
|
||||
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
|
||||
if (mesh["mergeFromVert"] != val::undefined()) {
|
||||
out.mergeFromVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
|
||||
}
|
||||
if (mesh["mergeToVert"] != val::undefined()) {
|
||||
out.mergeToVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
|
||||
}
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["runOriginalID"] != val::undefined()) {
|
||||
out.runOriginalID =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
if (mesh["runTransform"] != val::undefined()) {
|
||||
out.runTransform =
|
||||
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
|
||||
MeshGL mesh = manifold.GetMeshGL(normalIdx);
|
||||
return MeshGL2JS(mesh);
|
||||
}
|
||||
|
||||
val Merge(const val& mesh) {
|
||||
val out = val::object();
|
||||
MeshGL meshGL = MeshJS2GL(mesh);
|
||||
bool changed = meshGL.Merge();
|
||||
out.set("changed", changed);
|
||||
out.set("mesh", changed ? MeshGL2JS(meshGL) : mesh);
|
||||
return out;
|
||||
}
|
||||
|
||||
Manifold Smooth(const val& mesh,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {}) {
|
||||
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
namespace cross_js {
|
||||
CrossSection OfPolygons(std::vector<std::vector<glm::vec2>> polygons,
|
||||
int fill_rule) {
|
||||
auto fr = fill_rule == 0 ? CrossSection::FillRule::EvenOdd
|
||||
: fill_rule == 1 ? CrossSection::FillRule::NonZero
|
||||
: fill_rule == 2 ? CrossSection::FillRule::Positive
|
||||
: CrossSection::FillRule::Negative;
|
||||
return CrossSection(polygons, fr);
|
||||
}
|
||||
|
||||
CrossSection Union(const CrossSection& a, const CrossSection& b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
CrossSection Difference(const CrossSection& a, const CrossSection& b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
CrossSection Intersection(const CrossSection& a, const CrossSection& b) {
|
||||
return a ^ b;
|
||||
}
|
||||
|
||||
CrossSection UnionN(const std::vector<CrossSection>& cross_sections) {
|
||||
return CrossSection::BatchBoolean(cross_sections, OpType::Add);
|
||||
}
|
||||
|
||||
CrossSection DifferenceN(const std::vector<CrossSection>& cross_sections) {
|
||||
return CrossSection::BatchBoolean(cross_sections, OpType::Subtract);
|
||||
}
|
||||
|
||||
CrossSection IntersectionN(const std::vector<CrossSection>& cross_sections) {
|
||||
return CrossSection::BatchBoolean(cross_sections, OpType::Intersect);
|
||||
}
|
||||
|
||||
CrossSection Transform(CrossSection& cross_section, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat3x2 matrix;
|
||||
for (const int col : {0, 1, 2})
|
||||
for (const int row : {0, 1}) matrix[col][row] = array[col * 3 + row];
|
||||
return cross_section.Transform(matrix);
|
||||
}
|
||||
|
||||
CrossSection Warp(CrossSection& cross_section, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec2&) = reinterpret_cast<void (*)(glm::vec2&)>(funcPtr);
|
||||
return cross_section.Warp(f);
|
||||
}
|
||||
|
||||
CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
|
||||
double miter_limit, double arc_tolerance) {
|
||||
auto jt = join_type == 0 ? CrossSection::JoinType::Square
|
||||
: join_type == 1 ? CrossSection::JoinType::Round
|
||||
: CrossSection::JoinType::Miter;
|
||||
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
|
||||
}
|
||||
} // namespace cross_js
|
||||
|
||||
namespace man_js {
|
||||
Manifold FromMeshJS(const val& mesh) { return Manifold(js::MeshJS2GL(mesh)); }
|
||||
|
||||
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
|
||||
|
||||
Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
|
||||
|
||||
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
|
||||
|
||||
Manifold UnionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Add);
|
||||
}
|
||||
|
||||
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Subtract);
|
||||
}
|
||||
|
||||
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, OpType::Intersect);
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat4x3 matrix;
|
||||
for (const int col : {0, 1, 2, 3})
|
||||
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
|
||||
return manifold.Transform(matrix);
|
||||
}
|
||||
|
||||
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
|
||||
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
|
||||
return manifold.Warp(f);
|
||||
}
|
||||
|
||||
Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) {
|
||||
void (*f)(float*, glm::vec3, const float*) =
|
||||
reinterpret_cast<void (*)(float*, glm::vec3, const float*)>(funcPtr);
|
||||
return manifold.SetProperties(numProp, f);
|
||||
}
|
||||
|
||||
Manifold LevelSet(uintptr_t funcPtr, Box bounds, float edgeLength,
|
||||
float level) {
|
||||
float (*f)(const glm::vec3&) =
|
||||
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
|
||||
Mesh m = LevelSet(f, bounds, edgeLength, level);
|
||||
return Manifold(m);
|
||||
}
|
||||
|
||||
std::vector<Manifold> Split(Manifold& a, Manifold& b) {
|
||||
auto [r1, r2] = a.Split(b);
|
||||
return {r1, r2};
|
||||
}
|
||||
|
||||
std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
|
||||
float originOffset) {
|
||||
auto [a, b] = m.SplitByPlane(normal, originOffset);
|
||||
return {a, b};
|
||||
}
|
||||
} // namespace man_js
|
||||
@@ -0,0 +1,850 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Box, FillRule, JoinType, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
|
||||
|
||||
/**
|
||||
* Triangulates a set of /epsilon-valid polygons.
|
||||
*
|
||||
* @param polygons The set of polygons, wound CCW and representing multiple
|
||||
* polygons and/or holes.
|
||||
* @param precision The value of epsilon, bounding the uncertainty of the input
|
||||
* @return The triangles, referencing the original polygon points in order.
|
||||
*/
|
||||
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
export function setMinCircularAngle(angle: number): void;
|
||||
export function setMinCircularEdgeLength(length: number): void;
|
||||
export function setCircularSegments(segments: number): void;
|
||||
export function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
export class CrossSection {
|
||||
/**
|
||||
* Create a 2d cross-section from a set of contours (complex polygons). A
|
||||
* boolean union operation (with Positive filling rule by default) is
|
||||
* performed to combine overlapping polygons and ensure the resulting
|
||||
* CrossSection is free of intersections.
|
||||
*
|
||||
* @param contours A set of closed paths describing zero or more complex
|
||||
* polygons.
|
||||
* @param fillRule The filling rule used to interpret polygon sub-regions in
|
||||
* contours.
|
||||
*/
|
||||
constructor(polygons: Polygons, fillRule?: FillRule);
|
||||
|
||||
// Shapes
|
||||
|
||||
static square(size?: Vec2|number, center?: boolean): CrossSection;
|
||||
|
||||
static circle(radius: number, circularSegments?: number): CrossSection;
|
||||
|
||||
// Extrusions (2d to 3d manifold)
|
||||
|
||||
/**
|
||||
* Constructs a manifold by extruding the cross-section along Z-axis.
|
||||
*
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the
|
||||
* top. Default {1, 1}.
|
||||
* @param center If true, the extrusion is centered on the z-axis through the
|
||||
* origin
|
||||
* as opposed to resting on the XY plane as is default.
|
||||
*/
|
||||
extrude(
|
||||
height: number, nDivisions?: number, twistDegrees?: number,
|
||||
scaleTop?: Vec2|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold by revolving this cross-section around its Y-axis and
|
||||
* then setting this as the Z-axis of the resulting manifold. If the contours
|
||||
* cross the Y-axis, only the part on the positive X side is used.
|
||||
* Geometrically valid input will result in geometrically valid output.
|
||||
*
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
revolve(circularSegments?: number, revolveDegrees?: number): Manifold;
|
||||
|
||||
// Transformations
|
||||
|
||||
/**
|
||||
* Transform this CrossSection in space. Stored in column-major order. This
|
||||
* operation can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transformation matrix to apply to all the vertices. The
|
||||
* last row is ignored.
|
||||
*/
|
||||
transform(m: Mat3): CrossSection;
|
||||
|
||||
/**
|
||||
* Move this CrossSection in space. This operation can be chained. Transforms
|
||||
* are combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec2): CrossSection;
|
||||
|
||||
/**
|
||||
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param degrees degrees about the Z-axis to rotate.
|
||||
*/
|
||||
rotate(v: number): CrossSection;
|
||||
|
||||
/**
|
||||
* Scale this CrossSection in space. This operation can be chained. Transforms
|
||||
* are combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec2|number): CrossSection;
|
||||
|
||||
|
||||
/**
|
||||
* Mirror this CrossSection over the arbitrary axis described by the unit form
|
||||
* of the given vector. If the length of the vector is zero, an empty
|
||||
* CrossSection is returned. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param ax the axis to be mirrored over
|
||||
*/
|
||||
mirror(v: Vec2): CrossSection;
|
||||
|
||||
/**
|
||||
* Move the vertices of this CrossSection (creating a new one) according to
|
||||
* any arbitrary input function, followed by a union operation (with a
|
||||
* Positive fill rule) that ensures any introduced intersections are not
|
||||
* included in the result.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec2) => void): CrossSection;
|
||||
|
||||
/**
|
||||
* Inflate the contours in CrossSection by the specified delta, handling
|
||||
* corners according to the given JoinType.
|
||||
*
|
||||
* @param delta Positive deltas will cause the expansion of outlining contours
|
||||
* to expand, and retraction of inner (hole) contours. Negative deltas will
|
||||
* have the opposite effect.
|
||||
* @param joinType The join type specifying the treatment of contour joins
|
||||
* (corners).
|
||||
* @param miterLimit The maximum distance in multiples of delta that vertices
|
||||
* can be offset from their original positions with before squaring is
|
||||
* applied, **when the join type is Miter** (default is 2, which is the
|
||||
* minimum allowed). See the [Clipper2
|
||||
* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)
|
||||
* page for a visual example.
|
||||
* @param circularSegments Number of segments per 360 degrees of
|
||||
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
|
||||
* will be added to each contour). Default is calculated by the static Quality
|
||||
* defaults according to the radius.
|
||||
*/
|
||||
offset(
|
||||
delta: number, joinType?: JoinType, miterLimit?: number,
|
||||
circularSegments?: number): CrossSection;
|
||||
|
||||
/**
|
||||
* Compute the convex hull of the contours in this CrossSection.
|
||||
*/
|
||||
hull(): CrossSection;
|
||||
|
||||
/**
|
||||
* Remove vertices from the contours in this CrossSection that are less than
|
||||
* the specified distance epsilon from an imaginary line that passes through
|
||||
* its two adjacent vertices. Near duplicate vertices and collinear points
|
||||
* will be removed at lower epsilons, with elimination of line segments
|
||||
* becoming increasingly aggressive with larger epsilons.
|
||||
*
|
||||
* It is recommended to apply this function following Offset, in order to
|
||||
* clean up any spurious tiny line segments introduced that do not improve
|
||||
* quality in any meaningful way. This is particularly important if further
|
||||
* offseting operations are to be performed, which would compound the issue.
|
||||
*
|
||||
* @param epsilon minimum distance vertices must diverge from the hypothetical
|
||||
* outline without them in order to be included in the output (default
|
||||
* 1e-6)
|
||||
*/
|
||||
simplify(epsilon?: number): CrossSection;
|
||||
|
||||
// Clipping Operations
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: CrossSection|Polygons): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: CrossSection|Polygons): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: CrossSection|Polygons): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean union of the cross-sections a and b
|
||||
*/
|
||||
static union(a: CrossSection|Polygons, b: CrossSection|Polygons):
|
||||
CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean difference of the cross-section b from the cross-section a
|
||||
*/
|
||||
static difference(a: CrossSection|Polygons, b: CrossSection|Polygons):
|
||||
CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean intersection of the cross-sections a and b
|
||||
*/
|
||||
static intersection(a: CrossSection|Polygons, b: CrossSection|Polygons):
|
||||
CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean union of a list of cross-sections
|
||||
*/
|
||||
static union(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean difference of the tail of a list of cross-sections from its head
|
||||
*/
|
||||
static difference(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
/**
|
||||
* Boolean intersection of a list of cross-sections
|
||||
*/
|
||||
static intersection(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
// Topological Operations
|
||||
|
||||
/**
|
||||
* Construct a CrossSection from a vector of other Polygons (batch
|
||||
* boolean union).
|
||||
*/
|
||||
static compose(polygons: (CrossSection|Polygons)[]): CrossSection;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of CrossSections that are topologically
|
||||
* disconnected, each containing one outline contour with zero or more
|
||||
* holes.
|
||||
*/
|
||||
decompose(): CrossSection[];
|
||||
|
||||
// Polygon Conversion
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from a set of contours (complex polygons). A
|
||||
* boolean union operation (with Positive filling rule by default) is
|
||||
* performed to combine overlapping polygons and ensure the resulting
|
||||
* CrossSection is free of intersections.
|
||||
*
|
||||
* @param contours A set of closed paths describing zero or more complex
|
||||
* polygons.
|
||||
* @param fillRule The filling rule used to interpret polygon sub-regions in
|
||||
* contours.
|
||||
*/
|
||||
static ofPolygons(polygons: Polygons, fillRule?: FillRule): CrossSection;
|
||||
|
||||
/**
|
||||
* Return the contours of this CrossSection as a list of simple polygons.
|
||||
*/
|
||||
toPolygons(): SimplePolygon[];
|
||||
|
||||
// Properties
|
||||
|
||||
/**
|
||||
* Return the total area covered by complex polygons making up the
|
||||
* CrossSection.
|
||||
*/
|
||||
area(): number;
|
||||
|
||||
/**
|
||||
* Does the CrossSection (not) have any contours?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the CrossSection.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of contours in the CrossSection.
|
||||
*/
|
||||
numContour(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding rectangle of all the CrossSection's
|
||||
* vertices.
|
||||
*/
|
||||
bounds(): Rect;
|
||||
|
||||
// Memory
|
||||
|
||||
/**
|
||||
* Frees the WASM memory of this CrossSection, since these cannot be
|
||||
* garbage-collected automatically.
|
||||
*/
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
export class Manifold {
|
||||
/**
|
||||
* Convert a Mesh into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will throw an error if the
|
||||
* result is not an oriented 2-manifold. Will collapse degenerate triangles
|
||||
* and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless
|
||||
* round-trip of data from getMesh(). For multi-material input, use
|
||||
* reserveIDs() to set a unique originalID for each material, and sort the
|
||||
* materials into triangle runs.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
|
||||
// Shapes
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
static tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
static cube(size?: Vec3|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can
|
||||
* also form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
static cylinder(
|
||||
height: number, radiusLow: number, radiusHigh?: number,
|
||||
circularSegments?: number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
static sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
// Extrusions from 2d shapes
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons/cross-section by extruding
|
||||
* them along the Z-axis.
|
||||
*
|
||||
* @param polygons A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the
|
||||
* top. Default {1, 1}.
|
||||
* @param center If true, the extrusion is centered on the z-axis through the
|
||||
* origin
|
||||
* as opposed to resting on the XY plane as is default.
|
||||
*/
|
||||
static extrude(
|
||||
polygons: CrossSection|Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2|number,
|
||||
center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons/cross-section by revolving
|
||||
* them around the Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param polygons A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
|
||||
*/
|
||||
static revolve(
|
||||
polygons: CrossSection|Polygons, circularSegments?: number,
|
||||
revolveDegrees?: number): Manifold;
|
||||
|
||||
// Mesh Conversion
|
||||
|
||||
/**
|
||||
* Convert a Mesh into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will throw an error if the
|
||||
* result is not an oriented 2-manifold. Will collapse degenerate triangles
|
||||
* and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless
|
||||
* round-trip of data from getMesh(). For multi-material input, use
|
||||
* reserveIDs() to set a unique originalID for each material, and sort the
|
||||
* materials into triangle runs.
|
||||
*/
|
||||
static ofMesh(mesh: Mesh): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature
|
||||
* magnitude. No higher-order derivatives are considered, as the interpolation
|
||||
* is independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param mesh input Mesh.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges.
|
||||
* Order of entries doesn't matter, as each one specifies the desired
|
||||
* smoothness (between zero and one, with one the default for all unspecified
|
||||
* halfedges) and the halfedge index (3 * triangle index + [0,1,2] where 0 is
|
||||
* the edge between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of
|
||||
* as an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
static smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
// Signed Distance Functions
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's
|
||||
* interior exceeds the given bounds, you will see a kind of egg-crate shape
|
||||
* closing off the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance function which returns the signed distance
|
||||
* of
|
||||
* a given point in R^3. Positive values are inside, negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
*/
|
||||
static levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
// Transformations
|
||||
|
||||
/**
|
||||
* Transform this Manifold in space. Stored in column-major order. This
|
||||
* operation can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transformation matrix to apply to all the vertices. The
|
||||
* last row is ignored.
|
||||
*/
|
||||
transform(m: Mat4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Mirror this Manifold over the plane described by the unit form of the given
|
||||
* normal vector. If the length of the normal is zero, an empty Manifold is
|
||||
* returned. This operation can be chained. Transforms are combined and
|
||||
* applied lazily.
|
||||
*
|
||||
* @param normal The normal vector of the plane to be mirrored over
|
||||
*/
|
||||
mirror(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
* function that warps a geometrically valid object into one which overlaps,
|
||||
* but that is not checked here, so it is up to the user to choose their
|
||||
* function with discretion.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
* will all be coplanar (and will not be immediately collapsed) unless the
|
||||
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
|
||||
* constructor), in which case the new vertices will be moved to the
|
||||
* interpolated surface according to their barycentric coordinates.
|
||||
*
|
||||
* @param n The number of pieces to split every edge into. Must be > 1.
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* Create a new copy of this manifold with updated vertex properties by
|
||||
* supplying a function that takes the existing position and properties as
|
||||
* input. You may specify any number of output properties, allowing creation
|
||||
* and removal of channels. Note: undefined behavior will result if you read
|
||||
* past the number of input properties or write past the number of output
|
||||
* properties.
|
||||
*
|
||||
* @param numProp The new number of properties per vertex.
|
||||
* @param propFunc A function that modifies the properties of a given vertex.
|
||||
*/
|
||||
setProperties(
|
||||
numProp: number,
|
||||
propFunc: (newProp: number[], position: Vec3, oldProp: number[]) => void):
|
||||
Manifold;
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. This approximates them for every vertex and assigns
|
||||
* them as vertex properties on the given channels.
|
||||
*
|
||||
* @param gaussianIdx The property channel index in which to store the
|
||||
* Gaussian curvature. An index < 0 will be ignored (stores nothing). The
|
||||
* property set will be automatically expanded to include the channel
|
||||
* index specified.
|
||||
* @param meanIdx The property channel index in which to store the mean
|
||||
* curvature. An index < 0 will be ignored (stores nothing). The property
|
||||
* set will be automatically expanded to include the channel index
|
||||
* specified.
|
||||
*/
|
||||
calculateCurvature(gaussianIdx: number, meanIdx: number): Manifold;
|
||||
|
||||
// Boolean Operations
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union of the manifolds a and b
|
||||
*/
|
||||
static union(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference of the manifold b from the manifold a
|
||||
*/
|
||||
static difference(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection of the manifolds a and b
|
||||
*/
|
||||
static intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union of a list of manifolds
|
||||
*/
|
||||
static union(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference of the tail of a list of manifolds from its head
|
||||
*/
|
||||
static difference(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection of a list of manifolds
|
||||
*/
|
||||
static intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Split cuts this manifold in two using the cutter manifold. The first result
|
||||
* is the intersection, second is the difference. This is more efficient than
|
||||
* doing them separately.
|
||||
*
|
||||
* @param cutter
|
||||
*/
|
||||
split(cutter: Manifold): Manifold[];
|
||||
|
||||
/**
|
||||
* Convenient version of Split() for a half-space.
|
||||
*
|
||||
* @param normal This vector is normal to the cutting plane and its length
|
||||
* does
|
||||
* not matter. The first result is in the direction of this vector, the second
|
||||
* result is on the opposite side.
|
||||
* @param originOffset The distance of the plane from the origin in the
|
||||
* direction of the normal vector.
|
||||
*/
|
||||
splitByPlane(normal: Vec3, originOffset: number): Manifold[];
|
||||
|
||||
/**
|
||||
* Removes everything behind the given half-space plane.
|
||||
*
|
||||
* @param normal This vector is normal to the cutting plane and its length
|
||||
* does not matter. The result is in the direction of this vector from the
|
||||
* plane.
|
||||
* @param originOffset The distance of the plane from the origin in the
|
||||
* direction of the normal vector.
|
||||
*/
|
||||
trimByPlane(normal: Vec3, originOffset: number): Manifold;
|
||||
|
||||
// Topological Operations
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a list of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A list of Manifolds to lazy-union together.
|
||||
*/
|
||||
static compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
// Property Access
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* The number of properties per vertex in the Manifold.
|
||||
*/
|
||||
numProp(): number;
|
||||
|
||||
/**
|
||||
* The number of property vertices in the Manifold. This will always be >=
|
||||
* numVert, as some physical vertices may be duplicated to account for
|
||||
* different properties on different neighboring triangles.
|
||||
*/
|
||||
numPropVert(): number
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
// Export
|
||||
|
||||
/**
|
||||
* Returns a Mesh that is designed to easily push into a renderer, including
|
||||
* all interleaved vertex properties that may have been input. It also
|
||||
* includes relations to all the input meshes that form a part of this result
|
||||
* and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original Mesh inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output Mesh to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* Meshes must use the same channels for their normals.
|
||||
*/
|
||||
getMesh(normalIdx?: Vec3): Mesh;
|
||||
|
||||
// ID Management
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its IDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its ID that can be referenced
|
||||
* by product manifolds. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
|
||||
/**
|
||||
* Returns the first of n sequential new unique mesh IDs for marking sets of
|
||||
* triangles that can be looked up after further operations. Assign to
|
||||
* Mesh.runOriginalID vector.
|
||||
*/
|
||||
static reserveIDs(count: number): number;
|
||||
|
||||
// Memory
|
||||
|
||||
/**
|
||||
* Frees the WASM memory of this Manifold, since these cannot be
|
||||
* garbage-collected automatically.
|
||||
*/
|
||||
delete(): void;
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
constructor(options: {
|
||||
numProp: number,
|
||||
vertProperties: Float32Array,
|
||||
triVerts: Uint32Array,
|
||||
mergeFromVert?: Uint32Array,
|
||||
mergeToVert?: Uint32Array,
|
||||
runIndex?: Uint32Array,
|
||||
runOriginalID?: Uint32Array,
|
||||
runTransform?: Float32Array,
|
||||
faceID?: Uint32Array,
|
||||
halfedgeTangent?: Float32Array
|
||||
});
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
mergeFromVert?: Uint32Array;
|
||||
mergeToVert?: Uint32Array;
|
||||
runIndex?: Uint32Array;
|
||||
runOriginalID?: Uint32Array;
|
||||
runTransform?: Float32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
get numRun(): number;
|
||||
merge(): boolean;
|
||||
verts(tri: number): SealedUint32Array<3>;
|
||||
position(vert: number): SealedFloat32Array<3>;
|
||||
extras(vert: number): Float32Array;
|
||||
tangent(halfedge: number): SealedFloat32Array<4>;
|
||||
transform(run: number): Mat4;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export interface SealedUint32Array<N extends number> extends Uint32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export interface SealedFloat32Array<N extends number> extends Float32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export type Vec2 = [number, number];
|
||||
export type Vec3 = [number, number, number];
|
||||
// 4x4 matrix stored in column-major order
|
||||
export type Mat4 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
export type SimplePolygon = Vec2[];
|
||||
export type Polygons = SimplePolygon|SimplePolygon[];
|
||||
export type Rect = {
|
||||
min: Vec2,
|
||||
max: Vec2
|
||||
};
|
||||
export type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
export type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
export type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative'
|
||||
export type JoinType = 'Square'|'Round'|'Miter'
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as T from './manifold-encapsulated-types';
|
||||
export * from './manifold-global-types';
|
||||
|
||||
export type CrossSection = T.CrossSection;
|
||||
export type Manifold = T.Manifold;
|
||||
export type Mesh = T.Mesh;
|
||||
|
||||
export interface ManifoldToplevel {
|
||||
CrossSection: typeof T.CrossSection;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
triangulate: typeof T.triangulate;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
setup: () => void;
|
||||
}
|
||||
|
||||
export default function Module(config?: {locateFile: () => string}):
|
||||
Promise<ManifoldToplevel>;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manifold-3d",
|
||||
"version": "2.1.0",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "2.1.0",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"files": [
|
||||
"manifold.js",
|
||||
"manifold.wasm",
|
||||
"manifold.d.ts",
|
||||
"manifold-encapsulated-types.d.ts",
|
||||
"manifold-global-types.d.ts"
|
||||
],
|
||||
"typings": "manifold.d.ts",
|
||||
"types": "manifold.d.ts",
|
||||
"type": "module",
|
||||
"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"
|
||||
}
|
||||
@@ -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,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.
|
||||
|
||||
project(extras)
|
||||
|
||||
add_executable(perfTest perf_test.cpp)
|
||||
target_link_libraries(perfTest manifold)
|
||||
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTest PUBLIC cxx_std_17)
|
||||
|
||||
add_executable(largeSceneTest large_scene_test.cpp)
|
||||
target_link_libraries(largeSceneTest manifold)
|
||||
target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(largeSceneTest PUBLIC cxx_std_17)
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_executable(convertFile convert_file.cpp)
|
||||
target_link_libraries(convertFile manifold meshIO)
|
||||
target_compile_options(convertFile PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(convertFile PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
if(BUILD_TEST_CGAL)
|
||||
add_executable(perfTestCGAL perf_test_cgal.cpp)
|
||||
find_package(CGAL REQUIRED COMPONENTS Core)
|
||||
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
|
||||
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
|
||||
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
|
||||
endif()
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "manifold.h"
|
||||
#include "meshIO.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
/*
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::cout << "Specify an input filename." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
manifold::ManifoldParams().verbose = true;
|
||||
|
||||
const std::string filename(argv[1]);
|
||||
|
||||
MeshGL input = ImportMesh(filename);
|
||||
std::cout << input.NumVert() << " vertices, " << input.NumTri()
|
||||
<< " triangles" << std::endl;
|
||||
|
||||
if (input.Merge())
|
||||
std::cout << filename << " is not manifold, attempting to fix."
|
||||
<< std::endl;
|
||||
|
||||
const Manifold manifold(input);
|
||||
if (manifold.Status() != Manifold::Error::NoError) {
|
||||
std::cout << "Could not make a valid manifold, error: "
|
||||
<< (int)manifold.Status() << std::endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
const std::vector<Manifold> parts = manifold.Decompose();
|
||||
std::cout << parts.size() << " objects:" << std::endl;
|
||||
for (const Manifold& part : parts) {
|
||||
auto prop = part.GetProperties();
|
||||
std::cout << part.NumVert() << " vertices, " << part.NumTri()
|
||||
<< " triangles, volume = " << prop.volume
|
||||
<< ", surface area = " << prop.surfaceArea << std::endl;
|
||||
}
|
||||
|
||||
if (argc == 3) {
|
||||
std::string outName = argv[2];
|
||||
|
||||
std::cout << "Writing " << outName << std::endl;
|
||||
|
||||
ExportMesh(outName, manifold.GetMeshGL(), {});
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
#include "manifold.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
/*
|
||||
Build & execute with the following command:
|
||||
|
||||
( mkdir -p build && cd build && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_PAR=TBB .. && \
|
||||
make -j && \
|
||||
time ./extras/largeSceneTest 50 )
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
int n = 20;
|
||||
if (argc == 2) n = atoi(argv[1]);
|
||||
|
||||
std::cout << "n = " << n << std::endl;
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
Manifold scene;
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int j = 0; j < n; ++j) {
|
||||
for (int k = 0; k < n; ++k) {
|
||||
if (i == 0 && j == 0 && k == 0) continue;
|
||||
|
||||
Manifold sphere = Manifold::Sphere(1).Translate(glm::vec3(i, j, k));
|
||||
scene = scene.Boolean(sphere, OpType::Add);
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.NumTri();
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double> elapsed = end - start;
|
||||
std::cout << "nTri = " << scene.NumTri() << ", time = " << elapsed.count()
|
||||
<< " sec" << std::endl;
|
||||
}
|
||||
@@ -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": 1689008574,
|
||||
"narHash": "sha256-VFMgyHDiqsGDkRg73alv6OdHJAqhybryWHv77bSCGIw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4a729ce4b1fe5ec4fffc71c67c96aa5184ebb462",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"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-unstable";
|
||||
|
||||
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;
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages (ps: with ps; [ trimesh ])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PYBIND=ON"
|
||||
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
|
||||
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
|
||||
];
|
||||
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/manifold3d* $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,121 +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 <memory>
|
||||
|
||||
#include "structs.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
class Manifold {
|
||||
public:
|
||||
// Creation
|
||||
Manifold();
|
||||
|
||||
Manifold(
|
||||
const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
|
||||
static Manifold Smooth(const Mesh&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Tetrahedron();
|
||||
static Manifold Cube(glm::vec3 size = glm::vec3(1.0f), bool center = false);
|
||||
static Manifold Cylinder(float height, float radiusLow,
|
||||
float radiusHigh = -1.0f, int circularSegments = 0,
|
||||
bool center = false);
|
||||
static Manifold Sphere(float radius, int circularSegments = 0);
|
||||
static Manifold Extrude(Polygons crossSection, float height,
|
||||
int nDivisions = 0, float twistDegrees = 0.0f,
|
||||
glm::vec2 scaleTop = glm::vec2(1.0f));
|
||||
static Manifold Revolve(const Polygons& crossSection,
|
||||
int circularSegments = 0);
|
||||
|
||||
// Topological
|
||||
static Manifold Compose(const std::vector<Manifold>&);
|
||||
std::vector<Manifold> Decompose() const;
|
||||
|
||||
// Defaults for construction
|
||||
static void SetMinCircularAngle(float degrees);
|
||||
static void SetMinCircularEdgeLength(float length);
|
||||
static void SetCircularSegments(int number);
|
||||
static int GetCircularSegments(float radius);
|
||||
|
||||
// Information
|
||||
Mesh GetMesh() const;
|
||||
bool IsEmpty() const;
|
||||
int NumVert() const;
|
||||
int NumEdge() const;
|
||||
int NumTri() const;
|
||||
Box BoundingBox() const;
|
||||
float Precision() const;
|
||||
int Genus() const;
|
||||
Properties GetProperties() const;
|
||||
Curvature GetCurvature() const;
|
||||
|
||||
// Relation
|
||||
MeshRelation GetMeshRelation() const;
|
||||
std::vector<int> GetMeshIDs() const;
|
||||
int SetAsOriginal();
|
||||
static std::vector<int> MeshID2Original();
|
||||
|
||||
// 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);
|
||||
// Manifold RefineToLength(float);
|
||||
// Manifold RefineToPrecision(float);
|
||||
|
||||
// Boolean
|
||||
enum class OpType { ADD, SUBTRACT, INTERSECT };
|
||||
Manifold Boolean(const Manifold& second, OpType op) const;
|
||||
// Boolean operation shorthand
|
||||
Manifold operator+(const Manifold&) const; // ADD (Union)
|
||||
Manifold& operator+=(const Manifold&);
|
||||
Manifold operator-(const Manifold&) const; // SUBTRACT (Difference)
|
||||
Manifold& operator-=(const Manifold&);
|
||||
Manifold operator^(const Manifold&) const; // INTERSECT
|
||||
Manifold& operator^=(const Manifold&);
|
||||
std::pair<Manifold, Manifold> Split(const Manifold&) const;
|
||||
std::pair<Manifold, Manifold> SplitByPlane(glm::vec3 normal,
|
||||
float originOffset) const;
|
||||
Manifold TrimByPlane(glm::vec3 normal, float originOffset) const;
|
||||
|
||||
// Testing hooks
|
||||
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_;
|
||||
static int circularSegments_;
|
||||
static float circularAngle_;
|
||||
static float circularEdgeLength_;
|
||||
};
|
||||
} // namespace manifold
|
||||
@@ -1,469 +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 <thrust/sequence.h>
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/connected_components.hpp>
|
||||
|
||||
#include "impl.cuh"
|
||||
#include "polygon.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
using namespace thrust::placeholders;
|
||||
|
||||
struct ToSphere {
|
||||
float length;
|
||||
__host__ __device__ void operator()(glm::vec3& v) {
|
||||
v = glm::cos(glm::half_pi<float>() * (1.0f - v));
|
||||
v = length * glm::normalize(v);
|
||||
if (isnan(v.x)) v = glm::vec3(0.0);
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateTriBary {
|
||||
const int nextBary;
|
||||
|
||||
__host__ __device__ BaryRef operator()(BaryRef ref) {
|
||||
for (int i : {0, 1, 2})
|
||||
if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateHalfedge {
|
||||
const int nextVert;
|
||||
const int nextEdge;
|
||||
const int nextFace;
|
||||
|
||||
__host__ __device__ Halfedge operator()(Halfedge edge) {
|
||||
edge.startVert += nextVert;
|
||||
edge.endVert += nextVert;
|
||||
edge.pairedHalfedge += nextEdge;
|
||||
edge.face += nextFace;
|
||||
return edge;
|
||||
}
|
||||
};
|
||||
|
||||
int ConnectedComponents(VecDH<int>& components, int numVert,
|
||||
const VecDH<Halfedge>& halfedges) {
|
||||
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> graph(
|
||||
numVert);
|
||||
for (int i = 0; i < halfedges.size(); ++i) {
|
||||
const Halfedge halfedge = halfedges.H()[i];
|
||||
if (halfedge.IsForward()) {
|
||||
boost::add_edge(halfedge.startVert, halfedge.endVert, graph);
|
||||
}
|
||||
}
|
||||
components.resize(numVert);
|
||||
int numComponent = boost::connected_components(graph, components.H().data());
|
||||
return numComponent;
|
||||
}
|
||||
|
||||
struct Equals {
|
||||
int val;
|
||||
__host__ __device__ bool operator()(int x) { return x == val; }
|
||||
};
|
||||
|
||||
struct RemoveFace {
|
||||
const Halfedge* halfedge;
|
||||
const int* vertLabel;
|
||||
const int keepLabel;
|
||||
|
||||
__host__ __device__ bool operator()(int face) {
|
||||
return vertLabel[halfedge[3 * face].startVert] != keepLabel;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
/*
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangnets 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.
|
||||
*
|
||||
* If desired, you can supply a vector of sharpened halfedges, which should in
|
||||
* general be a small subset of all halfedges. Order of entries doesn't matter,
|
||||
* as each one specifies the desired smoothness (between zero and one, with one
|
||||
* the default for all unspecified halfedges) and the halfedge index (3 *
|
||||
* triangle index + [0,1,2] where 0 is the edge between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
Manifold Manifold::Smooth(const Mesh& mesh,
|
||||
const std::vector<Smoothness>& sharpenedEdges) {
|
||||
ALWAYS_ASSERT(
|
||||
mesh.halfedgeTangent.empty(), std::runtime_error,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
Manifold manifold(mesh);
|
||||
manifold.pImpl_->CreateTangents(sharpenedEdges);
|
||||
return manifold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
Manifold Manifold::Tetrahedron() {
|
||||
Manifold tetrahedron;
|
||||
tetrahedron.pImpl_ = std::make_unique<Impl>(Impl::Shape::TETRAHEDRON);
|
||||
return tetrahedron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin. Set center to true to shift the center to the
|
||||
* origin.
|
||||
*/
|
||||
Manifold Manifold::Cube(glm::vec3 size, bool center) {
|
||||
Manifold cube;
|
||||
cube.pImpl_ = std::make_unique<Impl>(Impl::Shape::CUBE);
|
||||
cube.Scale(size);
|
||||
if (center) cube.Translate(-size / 2.0f);
|
||||
return cube;
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified. Set center to true to center the
|
||||
* manifold vertically on the origin (default places the bottom on the origin).
|
||||
*/
|
||||
Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
int circularSegments, bool center) {
|
||||
float scale = radiusHigh >= 0.0f ? radiusHigh / radiusLow : 1.0f;
|
||||
float radius = max(radiusLow, radiusHigh);
|
||||
int n = circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back(
|
||||
{radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)), 0});
|
||||
}
|
||||
Manifold cylinder =
|
||||
Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale));
|
||||
if (center) cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f));
|
||||
return cylinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a sphere of a given radius and 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.
|
||||
*/
|
||||
Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
int n = circularSegments > 0 ? (circularSegments + 3) / 4
|
||||
: GetCircularSegments(radius) / 4;
|
||||
Manifold sphere;
|
||||
sphere.pImpl_ = std::make_unique<Impl>(Impl::Shape::OCTAHEDRON);
|
||||
sphere.pImpl_->Subdivide(n);
|
||||
thrust::for_each_n(sphere.pImpl_->vertPos_.beginD(), sphere.NumVert(),
|
||||
ToSphere({radius}));
|
||||
sphere.pImpl_->Finish();
|
||||
// Ignore preceding octahedron.
|
||||
sphere.pImpl_->ReinitializeReference();
|
||||
return sphere;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis. The overall height and the scale at the top (X and Y independently)
|
||||
* can be specified, as can a twist, to be applied linearly. In the case of
|
||||
* twist, it can also be helpful to specify nDivisions, which specifies the
|
||||
* quantization of the triangles vertically. If the scale is {0,0}, a pure cone
|
||||
* is formed with only a single vertex at the top.
|
||||
*/
|
||||
Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
float twistDegrees, glm::vec2 scaleTop) {
|
||||
ALWAYS_ASSERT(scaleTop.x >= 0 && scaleTop.y >= 0, userErr,
|
||||
"scale values cannot be negative");
|
||||
Manifold extrusion;
|
||||
++nDivisions;
|
||||
auto& vertPos = extrusion.pImpl_->vertPos_.H();
|
||||
VecDH<glm::ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH.H();
|
||||
int nCrossSection = 0;
|
||||
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
|
||||
int idx = 0;
|
||||
for (auto& poly : crossSection) {
|
||||
nCrossSection += poly.size();
|
||||
for (PolyVert& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.pos.x, polyVert.pos.y, 0.0f});
|
||||
polyVert.idx = idx++;
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < nDivisions + 1; ++i) {
|
||||
float alpha = i / float(nDivisions);
|
||||
float phi = alpha * twistDegrees;
|
||||
glm::mat2 transform(cosd(phi), sind(phi), -sind(phi), cosd(phi));
|
||||
glm::vec2 scale = glm::mix(glm::vec2(1.0f), scaleTop, alpha);
|
||||
transform = transform * glm::mat2(scale.x, 0.0f, 0.0f, scale.y);
|
||||
int j = 0;
|
||||
int idx = 0;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (int vert = 0; vert < poly.size(); ++vert) {
|
||||
int offset = idx + nCrossSection * i;
|
||||
int thisVert = vert + offset;
|
||||
int lastVert = (vert == 0 ? poly.size() : vert) - 1 + offset;
|
||||
if (i == nDivisions && isCone) {
|
||||
triVerts.push_back({nCrossSection * i + j, lastVert - nCrossSection,
|
||||
thisVert - nCrossSection});
|
||||
} else {
|
||||
glm::vec2 pos = transform * poly[vert].pos;
|
||||
vertPos.push_back({pos.x, pos.y, height * alpha});
|
||||
triVerts.push_back({thisVert, lastVert, thisVert - nCrossSection});
|
||||
triVerts.push_back(
|
||||
{lastVert, lastVert - nCrossSection, thisVert - nCrossSection});
|
||||
}
|
||||
}
|
||||
++j;
|
||||
idx += poly.size();
|
||||
}
|
||||
}
|
||||
if (isCone)
|
||||
for (int j = 0; j < crossSection.size(); ++j) // Duplicate vertex for Genus
|
||||
vertPos.push_back({0.0f, 0.0f, height});
|
||||
std::vector<glm::ivec3> top = Triangulate(crossSection);
|
||||
for (const glm::ivec3& tri : top) {
|
||||
triVerts.push_back({tri[0], tri[2], tri[1]});
|
||||
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
|
||||
}
|
||||
|
||||
extrusion.pImpl_->CreateHalfedges(triVertsDH);
|
||||
extrusion.pImpl_->Finish();
|
||||
extrusion.pImpl_->InitializeNewReference();
|
||||
return extrusion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
float radius = 0.0f;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& vert : poly) {
|
||||
radius = max(radius, vert.pos.x);
|
||||
}
|
||||
}
|
||||
int nDivisions =
|
||||
circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
|
||||
Manifold revoloid;
|
||||
auto& vertPos = revoloid.pImpl_->vertPos_.H();
|
||||
VecDH<glm::ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH.H();
|
||||
float dPhi = 360.0f / nDivisions;
|
||||
for (const auto& poly : crossSection) {
|
||||
int start = -1;
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
if (poly[polyVert].pos.x <= 0) {
|
||||
start = polyVert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start == -1) { // poly all positive
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
int startVert = vertPos.size();
|
||||
int lastStart =
|
||||
startVert +
|
||||
(polyVert == 0 ? nDivisions * (poly.size() - 1) : -nDivisions);
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
|
||||
triVerts.push_back({startVert + slice, startVert + lastSlice,
|
||||
lastStart + lastSlice});
|
||||
triVerts.push_back(
|
||||
{lastStart + lastSlice, lastStart + slice, startVert + slice});
|
||||
}
|
||||
}
|
||||
} else { // poly crosses zero
|
||||
int polyVert = start;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
do {
|
||||
glm::vec2 lastPos = pos;
|
||||
polyVert = (polyVert + 1) % poly.size();
|
||||
pos = poly[polyVert].pos;
|
||||
if (pos.x > 0) {
|
||||
if (lastPos.x <= 0) {
|
||||
float a = pos.x / (pos.x - lastPos.x);
|
||||
vertPos.push_back({0.0f, 0.0f, glm::mix(pos.y, lastPos.y, a)});
|
||||
}
|
||||
int startVert = vertPos.size();
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
|
||||
if (lastPos.x > 0) {
|
||||
triVerts.push_back({startVert + slice, startVert + lastSlice,
|
||||
startVert - nDivisions + lastSlice});
|
||||
triVerts.push_back({startVert - nDivisions + lastSlice,
|
||||
startVert - nDivisions + slice,
|
||||
startVert + slice});
|
||||
} else {
|
||||
triVerts.push_back(
|
||||
{startVert - 1, startVert + slice, startVert + lastSlice});
|
||||
}
|
||||
}
|
||||
} else if (lastPos.x > 0) {
|
||||
int startVert = vertPos.size();
|
||||
float a = pos.x / (pos.x - lastPos.x);
|
||||
vertPos.push_back({0.0f, 0.0f, glm::mix(pos.y, lastPos.y, a)});
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
triVerts.push_back({startVert, startVert - nDivisions + lastSlice,
|
||||
startVert - nDivisions + slice});
|
||||
}
|
||||
}
|
||||
} while (polyVert != start);
|
||||
}
|
||||
}
|
||||
|
||||
revoloid.pImpl_->CreateHalfedges(triVertsDH);
|
||||
revoloid.pImpl_->Finish();
|
||||
revoloid.pImpl_->InitializeNewReference();
|
||||
return revoloid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a vector of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* geometrically-invalid results.
|
||||
*/
|
||||
Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 0;
|
||||
int numBary = 0;
|
||||
for (const Manifold& manifold : manifolds) {
|
||||
numVert += manifold.NumVert();
|
||||
numEdge += manifold.NumEdge();
|
||||
numTri += manifold.NumTri();
|
||||
numBary += manifold.pImpl_->meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
Manifold out;
|
||||
Impl& combined = *(out.pImpl_);
|
||||
combined.vertPos_.resize(numVert);
|
||||
combined.halfedge_.resize(2 * numEdge);
|
||||
combined.faceNormal_.resize(numTri);
|
||||
combined.halfedgeTangent_.resize(2 * numEdge);
|
||||
combined.meshRelation_.barycentric.resize(numBary);
|
||||
combined.meshRelation_.triBary.resize(numTri);
|
||||
|
||||
int nextVert = 0;
|
||||
int nextEdge = 0;
|
||||
int nextTri = 0;
|
||||
int nextBary = 0;
|
||||
for (const Manifold& manifold : manifolds) {
|
||||
const Impl& impl = *(manifold.pImpl_);
|
||||
impl.ApplyTransform();
|
||||
|
||||
thrust::copy(impl.vertPos_.beginD(), impl.vertPos_.endD(),
|
||||
combined.vertPos_.beginD() + nextVert);
|
||||
thrust::copy(impl.faceNormal_.beginD(), impl.faceNormal_.endD(),
|
||||
combined.faceNormal_.beginD() + nextTri);
|
||||
thrust::copy(impl.halfedgeTangent_.beginD(), impl.halfedgeTangent_.endD(),
|
||||
combined.halfedgeTangent_.beginD() + nextEdge);
|
||||
thrust::copy(impl.meshRelation_.barycentric.beginD(),
|
||||
impl.meshRelation_.barycentric.endD(),
|
||||
combined.meshRelation_.barycentric.beginD() + nextBary);
|
||||
thrust::transform(impl.meshRelation_.triBary.beginD(),
|
||||
impl.meshRelation_.triBary.endD(),
|
||||
combined.meshRelation_.triBary.beginD() + nextTri,
|
||||
UpdateTriBary({nextBary}));
|
||||
thrust::transform(impl.halfedge_.beginD(), impl.halfedge_.endD(),
|
||||
combined.halfedge_.beginD() + nextEdge,
|
||||
UpdateHalfedge({nextVert, nextEdge, nextTri}));
|
||||
|
||||
nextVert += manifold.NumVert();
|
||||
nextEdge += 2 * manifold.NumEdge();
|
||||
nextTri += manifold.NumTri();
|
||||
nextBary += impl.meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
combined.DuplicateMeshIDs();
|
||||
combined.Finish();
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a copy of this manifold, but as a vector of meshes
|
||||
* that are topologically disconnected.
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
VecDH<int> vertLabel;
|
||||
int numLabel = ConnectedComponents(vertLabel, NumVert(), pImpl_->halfedge_);
|
||||
|
||||
if (numLabel == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
|
||||
std::vector<Manifold> meshes(numLabel);
|
||||
for (int i = 0; i < numLabel; ++i) {
|
||||
meshes[i].pImpl_->vertPos_.resize(NumVert());
|
||||
VecDH<int> vertNew2Old(NumVert());
|
||||
int nVert =
|
||||
thrust::copy_if(
|
||||
zip(pImpl_->vertPos_.beginD(), countAt(0)),
|
||||
zip(pImpl_->vertPos_.endD(), countAt(NumVert())),
|
||||
vertLabel.beginD(),
|
||||
zip(meshes[i].pImpl_->vertPos_.beginD(), vertNew2Old.beginD()),
|
||||
Equals({i})) -
|
||||
zip(meshes[i].pImpl_->vertPos_.beginD(), countAt(0));
|
||||
meshes[i].pImpl_->vertPos_.resize(nVert);
|
||||
|
||||
VecDH<int> faceNew2Old(NumTri());
|
||||
thrust::sequence(faceNew2Old.beginD(), faceNew2Old.endD());
|
||||
|
||||
int nFace =
|
||||
thrust::remove_if(
|
||||
faceNew2Old.beginD(), faceNew2Old.endD(),
|
||||
RemoveFace({pImpl_->halfedge_.cptrD(), vertLabel.cptrD(), i})) -
|
||||
faceNew2Old.beginD();
|
||||
faceNew2Old.resize(nFace);
|
||||
|
||||
meshes[i].pImpl_->GatherFaces(*pImpl_, faceNew2Old);
|
||||
meshes[i].pImpl_->ReindexVerts(vertNew2Old, pImpl_->NumVert());
|
||||
|
||||
meshes[i].pImpl_->Finish();
|
||||
meshes[i].pImpl_->transform_ = pImpl_->transform_;
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -1,411 +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 "impl.cuh"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
__host__ __device__ glm::ivec3 TriOf(int edge) {
|
||||
glm::ivec3 triEdge;
|
||||
triEdge[0] = edge;
|
||||
triEdge[1] = NextHalfedge(triEdge[0]);
|
||||
triEdge[2] = NextHalfedge(triEdge[1]);
|
||||
return triEdge;
|
||||
}
|
||||
|
||||
__host__ __device__ bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
|
||||
const glm::vec2 e[3] = {v1 - v0, v2 - v1, v0 - v2};
|
||||
float l[3];
|
||||
for (int i : {0, 1, 2}) l[i] = glm::dot(e[i], e[i]);
|
||||
return l[0] > l[1] && l[0] > l[2];
|
||||
}
|
||||
|
||||
struct ShortEdge {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag short edges
|
||||
const glm::vec3 delta =
|
||||
vertPos[halfedge[edge].endVert] - vertPos[halfedge[edge].startVert];
|
||||
return glm::dot(delta, delta) < precision * precision;
|
||||
}
|
||||
};
|
||||
|
||||
struct FlagEdge {
|
||||
const Halfedge* halfedge;
|
||||
const BaryRef* triBary;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag redundant edges - those where the startVert is surrounded by only
|
||||
// two original triangles.
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
|
||||
const BaryRef ref1 = triBary[current / 3];
|
||||
while (current != edge) {
|
||||
current = NextHalfedge(halfedge[current].pairedHalfedge);
|
||||
int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct SwappableEdge {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const glm::vec3* triNormal;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
|
||||
int tri = halfedge[edge].face;
|
||||
glm::ivec3 triedge = TriOf(edge);
|
||||
glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[tri]);
|
||||
glm::vec2 v[3];
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedge[triedge[i]].startVert];
|
||||
// if (CCW(v[0], v[1], v[2], precision) < 0) printf("tri %d is CW!\n", tri);
|
||||
if (CCW(v[0], v[1], v[2], precision) > 0 || !Is01Longest(v[0], v[1], v[2]))
|
||||
return false;
|
||||
|
||||
// Switch to neighbor's projection.
|
||||
edge = halfedge[edge].pairedHalfedge;
|
||||
tri = halfedge[edge].face;
|
||||
triedge = TriOf(edge);
|
||||
projection = GetAxisAlignedProjection(triNormal[tri]);
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedge[triedge[i]].startVert];
|
||||
return CCW(v[0], v[1], v[2], precision) > 0 ||
|
||||
Is01Longest(v[0], v[1], v[2]);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Collapses degenerate triangles by removing edges shorter than precision_ and
|
||||
* any edge that is preceeded by an edge that joins the same two face relations.
|
||||
* It also performs edge swaps on the long edges of degenerate triangles, though
|
||||
* there are some configurations of degenerates that cannot be removed this way.
|
||||
*
|
||||
* Rather than actually removing the edges, this step merely marks them for
|
||||
* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.
|
||||
*/
|
||||
void Manifold::Impl::CollapseDegenerates() {
|
||||
VecDH<int> flaggedEdges(halfedge_.size());
|
||||
int numFlagged =
|
||||
thrust::copy_if(
|
||||
countAt(0), countAt(halfedge_.size()), flaggedEdges.beginD(),
|
||||
ShortEdge({halfedge_.cptrD(), vertPos_.cptrD(), precision_})) -
|
||||
flaggedEdges.beginD();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges.H()) CollapseEdge(edge);
|
||||
|
||||
flaggedEdges.resize(halfedge_.size());
|
||||
numFlagged =
|
||||
thrust::copy_if(
|
||||
countAt(0), countAt(halfedge_.size()), flaggedEdges.beginD(),
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triBary.cptrD()})) -
|
||||
flaggedEdges.beginD();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges.H()) CollapseEdge(edge);
|
||||
|
||||
flaggedEdges.resize(halfedge_.size());
|
||||
numFlagged = thrust::copy_if(
|
||||
countAt(0), countAt(halfedge_.size()), flaggedEdges.beginD(),
|
||||
SwappableEdge({halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
faceNormal_.cptrD(), precision_})) -
|
||||
flaggedEdges.beginD();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
for (const int edge : flaggedEdges.H()) {
|
||||
RecursiveEdgeSwap(edge);
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::PairUp(int edge0, int edge1) {
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
halfedge[edge0].pairedHalfedge = edge1;
|
||||
halfedge[edge1].pairedHalfedge = edge0;
|
||||
}
|
||||
|
||||
// Traverses CW around startEdge.endVert from startEdge to endEdge
|
||||
// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point
|
||||
// to vert instead.
|
||||
void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
while (startEdge != endEdge) {
|
||||
halfedge[startEdge].endVert = vert;
|
||||
startEdge = NextHalfedge(startEdge);
|
||||
halfedge[startEdge].startVert = vert;
|
||||
startEdge = halfedge[startEdge].pairedHalfedge;
|
||||
}
|
||||
}
|
||||
|
||||
// In the event that the edge collapse would create a non-manifold edge,
|
||||
// instead we duplicate the two verts and attach the manifolds the other way
|
||||
// across this edge.
|
||||
void Manifold::Impl::FormLoop(int current, int end) {
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
VecH<glm::vec3>& vertPos = vertPos_.H();
|
||||
|
||||
int startVert = vertPos.size();
|
||||
vertPos.push_back(vertPos[halfedge[current].startVert]);
|
||||
int endVert = vertPos.size();
|
||||
vertPos.push_back(vertPos[halfedge[current].endVert]);
|
||||
|
||||
int oldMatch = halfedge[current].pairedHalfedge;
|
||||
int newMatch = halfedge[end].pairedHalfedge;
|
||||
|
||||
UpdateVert(startVert, oldMatch, newMatch);
|
||||
UpdateVert(endVert, end, current);
|
||||
|
||||
halfedge[current].pairedHalfedge = newMatch;
|
||||
halfedge[newMatch].pairedHalfedge = current;
|
||||
halfedge[end].pairedHalfedge = oldMatch;
|
||||
halfedge[oldMatch].pairedHalfedge = end;
|
||||
|
||||
RemoveIfFolded(end);
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseTri(const glm::ivec3& triEdge) {
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
int pair1 = halfedge[triEdge[1]].pairedHalfedge;
|
||||
int pair2 = halfedge[triEdge[2]].pairedHalfedge;
|
||||
halfedge[pair1].pairedHalfedge = pair2;
|
||||
halfedge[pair2].pairedHalfedge = pair1;
|
||||
for (int i : {0, 1, 2}) {
|
||||
halfedge[triEdge[i]] = {-1, -1, -1, -1};
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
VecH<glm::vec3>& vertPos = vertPos_.H();
|
||||
const glm::ivec3 tri0edge = TriOf(edge);
|
||||
const glm::ivec3 tri1edge = TriOf(halfedge[edge].pairedHalfedge);
|
||||
if (halfedge[tri0edge[1]].endVert == halfedge[tri1edge[1]].endVert) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
vertPos[halfedge[tri0edge[i]].startVert] = glm::vec3(0.0f / 0.0f);
|
||||
halfedge[tri0edge[i]] = {-1, -1, -1, -1};
|
||||
halfedge[tri1edge[i]] = {-1, -1, -1, -1};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
VecH<glm::vec3>& vertPos = vertPos_.H();
|
||||
VecH<glm::vec3>& triNormal = faceNormal_.H();
|
||||
VecH<BaryRef>& triBary = meshRelation_.triBary.H();
|
||||
|
||||
const Halfedge toRemove = halfedge[edge];
|
||||
if (toRemove.pairedHalfedge < 0) return;
|
||||
|
||||
const int endVert = toRemove.endVert;
|
||||
const glm::ivec3 tri0edge = TriOf(edge);
|
||||
const glm::ivec3 tri1edge = TriOf(toRemove.pairedHalfedge);
|
||||
|
||||
const glm::vec3 pNew = vertPos[endVert];
|
||||
const glm::vec3 pOld = vertPos[toRemove.startVert];
|
||||
const glm::vec3 delta = pNew - pOld;
|
||||
const bool shortEdge = glm::dot(delta, delta) < precision_ * precision_;
|
||||
|
||||
std::vector<int> edges;
|
||||
// Orbit endVert
|
||||
int current = halfedge[tri0edge[1]].pairedHalfedge;
|
||||
while (current != tri1edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
edges.push_back(current);
|
||||
current = halfedge[current].pairedHalfedge;
|
||||
}
|
||||
|
||||
// Orbit startVert
|
||||
int start = halfedge[tri1edge[1]].pairedHalfedge;
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const BaryRef ref1 = triBary[toRemove.pairedHalfedge / 3];
|
||||
if (!shortEdge) {
|
||||
current = start;
|
||||
glm::vec3 pLast = vertPos[halfedge[tri1edge[1]].endVert];
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
glm::vec3 pNext = vertPos[halfedge[current].endVert];
|
||||
const int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
// Don't collapse if the edge is not redundant (this may have changed due
|
||||
// to the collapse of neighbors).
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return;
|
||||
|
||||
// Don't collapse edge if it would cause a triangle to invert.
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[tri]);
|
||||
if (CCW(projection * pNext, projection * pLast, projection * pNew,
|
||||
precision_) < 0)
|
||||
return;
|
||||
|
||||
pLast = pNext;
|
||||
current = halfedge[current].pairedHalfedge;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove toRemove.startVert and replace with endVert.
|
||||
vertPos[toRemove.startVert] = glm::vec3(0.0f / 0.0f);
|
||||
CollapseTri(tri1edge);
|
||||
|
||||
// Orbit startVert
|
||||
current = start;
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
|
||||
if (!shortEdge) {
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
triBary[tri].vertBary[vIdx] =
|
||||
(ref0.meshID == triBary[tri].meshID && ref0.tri == triBary[tri].tri)
|
||||
? ref0.vertBary[(edge + 1) % 3]
|
||||
: ref1.vertBary[toRemove.pairedHalfedge % 3];
|
||||
}
|
||||
|
||||
const int vert = halfedge[current].endVert;
|
||||
const int next = halfedge[current].pairedHalfedge;
|
||||
for (int i = 0; i < edges.size(); ++i) {
|
||||
if (vert == halfedge[edges[i]].endVert) {
|
||||
FormLoop(edges[i], current);
|
||||
start = next;
|
||||
edges.resize(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
|
||||
UpdateVert(endVert, start, tri0edge[2]);
|
||||
CollapseTri(tri0edge);
|
||||
RemoveIfFolded(start);
|
||||
}
|
||||
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
const VecH<glm::vec3>& vertPos = vertPos_.H();
|
||||
VecH<Halfedge>& halfedge = halfedge_.H();
|
||||
VecH<glm::vec3>& triNormal = faceNormal_.H();
|
||||
VecH<BaryRef>& triBary = meshRelation_.triBary.H();
|
||||
|
||||
if (halfedge[edge].pairedHalfedge < 0) return;
|
||||
|
||||
const int pair = halfedge[edge].pairedHalfedge;
|
||||
const glm::ivec3 tri0edge = TriOf(edge);
|
||||
const glm::ivec3 tri1edge = TriOf(pair);
|
||||
const glm::ivec3 perm0 = TriOf(edge % 3);
|
||||
const glm::ivec3 perm1 = TriOf(pair % 3);
|
||||
|
||||
glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[edge / 3]);
|
||||
glm::vec2 v[4];
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedge[tri0edge[i]].startVert];
|
||||
// Only operate on the long edge of a degenerate triangle.
|
||||
if (CCW(v[0], v[1], v[2], precision_) > 0 || !Is01Longest(v[0], v[1], v[2]))
|
||||
return;
|
||||
|
||||
// Switch to neighbor's projection.
|
||||
projection = GetAxisAlignedProjection(triNormal[halfedge[pair].face]);
|
||||
for (int i : {0, 1, 2})
|
||||
v[i] = projection * vertPos[halfedge[tri0edge[i]].startVert];
|
||||
v[3] = projection * vertPos[halfedge[tri1edge[2]].startVert];
|
||||
|
||||
auto SwapEdge = [&]() {
|
||||
// The 0-verts are swapped to the opposite 2-verts.
|
||||
const int v0 = halfedge[tri0edge[2]].startVert;
|
||||
const int v1 = halfedge[tri1edge[2]].startVert;
|
||||
halfedge[tri0edge[0]].startVert = v1;
|
||||
halfedge[tri0edge[2]].endVert = v1;
|
||||
halfedge[tri1edge[0]].startVert = v0;
|
||||
halfedge[tri1edge[2]].endVert = v0;
|
||||
PairUp(tri0edge[0], halfedge[tri1edge[2]].pairedHalfedge);
|
||||
PairUp(tri1edge[0], halfedge[tri0edge[2]].pairedHalfedge);
|
||||
PairUp(tri0edge[2], tri1edge[2]);
|
||||
// Both triangles are now subsets of the neighboring triangle.
|
||||
const int tri0 = halfedge[tri0edge[0]].face;
|
||||
const int tri1 = halfedge[tri1edge[0]].face;
|
||||
triNormal[tri0] = triNormal[tri1];
|
||||
triBary[tri0] = triBary[tri1];
|
||||
triBary[tri0].vertBary[perm0[1]] = triBary[tri1].vertBary[perm1[0]];
|
||||
triBary[tri0].vertBary[perm0[0]] = triBary[tri1].vertBary[perm1[2]];
|
||||
// Calculate a new barycentric coordinate for the split triangle.
|
||||
const glm::vec3 uvw0 = UVW(triBary[tri1].vertBary[perm1[0]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
const glm::vec3 uvw1 = UVW(triBary[tri1].vertBary[perm1[1]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
const float l01 = glm::length(v[1] - v[0]);
|
||||
const float l02 = glm::length(v[2] - v[0]);
|
||||
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
|
||||
const glm::vec3 uvw2 = a * uvw0 + (1 - a) * uvw1;
|
||||
// And assign it.
|
||||
const int newBary = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.H().push_back(uvw2);
|
||||
triBary[tri1].vertBary[perm1[0]] = newBary;
|
||||
triBary[tri0].vertBary[perm0[2]] = newBary;
|
||||
|
||||
// if the new edge already exists, duplicate the verts and split the mesh.
|
||||
int current = halfedge[tri1edge[0]].pairedHalfedge;
|
||||
const int endVert = halfedge[tri1edge[1]].endVert;
|
||||
while (current != tri0edge[1]) {
|
||||
current = NextHalfedge(current);
|
||||
if (halfedge[current].endVert == endVert) {
|
||||
FormLoop(tri0edge[2], current);
|
||||
RemoveIfFolded(tri0edge[2]);
|
||||
return;
|
||||
}
|
||||
current = halfedge[current].pairedHalfedge;
|
||||
}
|
||||
};
|
||||
|
||||
// Only operate if the other triangles are not degenerate.
|
||||
if (CCW(v[1], v[0], v[3], precision_) <= 0) {
|
||||
if (!Is01Longest(v[1], v[0], v[3])) return;
|
||||
// Two facing, long-edge degenerates can swap.
|
||||
SwapEdge();
|
||||
const glm::vec2 e23 = v[3] - v[2];
|
||||
if (glm::dot(e23, e23) < precision_ * precision_) {
|
||||
CollapseEdge(tri0edge[2]);
|
||||
} else {
|
||||
RecursiveEdgeSwap(tri0edge[0]);
|
||||
RecursiveEdgeSwap(tri0edge[1]);
|
||||
RecursiveEdgeSwap(tri1edge[0]);
|
||||
RecursiveEdgeSwap(tri1edge[1]);
|
||||
}
|
||||
return;
|
||||
} else if (CCW(v[0], v[3], v[2], precision_) <= 0 ||
|
||||
CCW(v[1], v[2], v[3], precision_) <= 0) {
|
||||
return;
|
||||
}
|
||||
// Normal path
|
||||
SwapEdge();
|
||||
RecursiveEdgeSwap(halfedge[tri0edge[1]].pairedHalfedge);
|
||||
RecursiveEdgeSwap(halfedge[tri1edge[0]].pairedHalfedge);
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -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,641 +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 <thrust/execution_policy.h>
|
||||
#include <thrust/logical.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/graph/adjacency_list.hpp>
|
||||
#include <boost/graph/connected_components.hpp>
|
||||
#include <map>
|
||||
|
||||
#include "impl.cuh"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
__host__ __device__ void AtomicAddVec3(glm::vec3& target,
|
||||
const glm::vec3& add) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
atomicAdd(&target[i], add[i]);
|
||||
#else
|
||||
#pragma omp atomic
|
||||
target[i] += add[i];
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
struct Normalize {
|
||||
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
|
||||
};
|
||||
|
||||
struct Transform4x3 {
|
||||
const glm::mat4x3 transform;
|
||||
|
||||
__host__ __device__ void operator()(glm::vec3& position) {
|
||||
position = transform * glm::vec4(position, 1.0f);
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformNormals {
|
||||
const glm::mat3 transform;
|
||||
|
||||
__host__ __device__ void operator()(glm::vec3& normal) {
|
||||
normal = glm::normalize(transform * normal);
|
||||
if (isnan(normal.x)) normal = glm::vec3(0.0f);
|
||||
}
|
||||
};
|
||||
|
||||
struct AssignNormals {
|
||||
glm::vec3* vertNormal;
|
||||
const glm::vec3* vertPos;
|
||||
const Halfedge* halfedges;
|
||||
const float precision;
|
||||
const bool calculateTriNormal;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::vec3&, int> in) {
|
||||
glm::vec3& triNormal = thrust::get<0>(in);
|
||||
const int face = thrust::get<1>(in);
|
||||
|
||||
glm::ivec3 triVerts;
|
||||
for (int i : {0, 1, 2}) triVerts[i] = halfedges[3 * face + i].startVert;
|
||||
|
||||
glm::vec3 edge[3];
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int j = (i + 1) % 3;
|
||||
edge[i] = glm::normalize(vertPos[triVerts[j]] - vertPos[triVerts[i]]);
|
||||
}
|
||||
|
||||
if (calculateTriNormal) {
|
||||
triNormal = glm::normalize(glm::cross(edge[0], edge[1]));
|
||||
if (isnan(triNormal.x)) triNormal = glm::vec3(0, 0, 1);
|
||||
}
|
||||
|
||||
// corner angles
|
||||
glm::vec3 phi;
|
||||
float dot = -glm::dot(edge[2], edge[0]);
|
||||
phi[0] = dot >= 1 ? 0 : (dot <= -1 ? glm::pi<float>() : glm::acos(dot));
|
||||
dot = -glm::dot(edge[0], edge[1]);
|
||||
phi[1] = dot >= 1 ? 0 : (dot <= -1 ? glm::pi<float>() : glm::acos(dot));
|
||||
phi[2] = glm::pi<float>() - phi[0] - phi[1];
|
||||
|
||||
// assign weighted sum
|
||||
for (int i : {0, 1, 2}) {
|
||||
AtomicAddVec3(vertNormal[triVerts[i]], phi[i] * triNormal);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Tri2Halfedges {
|
||||
Halfedge* halfedges;
|
||||
TmpEdge* edges;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<int, const glm::ivec3&> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const glm::ivec3& triVerts = thrust::get<1>(in);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int j = (i + 1) % 3;
|
||||
const int edge = 3 * tri + i;
|
||||
halfedges[edge] = {triVerts[i], triVerts[j], -1, tri};
|
||||
edges[edge] = TmpEdge(triVerts[i], triVerts[j], edge);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct LinkHalfedges {
|
||||
Halfedge* halfedges;
|
||||
const TmpEdge* edges;
|
||||
|
||||
__host__ __device__ void operator()(int k) {
|
||||
const int i = 2 * k;
|
||||
const int j = i + 1;
|
||||
const int pair0 = edges[i].halfedgeIdx;
|
||||
const int pair1 = edges[j].halfedgeIdx;
|
||||
if (halfedges[pair0].startVert != halfedges[pair1].endVert ||
|
||||
halfedges[pair0].endVert != halfedges[pair1].startVert ||
|
||||
halfedges[pair0].face == halfedges[pair1].face)
|
||||
printf("Not manifold!\n");
|
||||
halfedges[pair0].pairedHalfedge = pair1;
|
||||
halfedges[pair1].pairedHalfedge = pair0;
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapHalfedges {
|
||||
Halfedge* halfedges;
|
||||
const TmpEdge* edges;
|
||||
|
||||
__host__ void operator()(int k) {
|
||||
const int i = 2 * k;
|
||||
const int j = i - 2;
|
||||
const TmpEdge thisEdge = edges[i];
|
||||
const TmpEdge lastEdge = edges[j];
|
||||
if (thisEdge.first == lastEdge.first &&
|
||||
thisEdge.second == lastEdge.second) {
|
||||
const int swap0idx = thisEdge.halfedgeIdx;
|
||||
Halfedge& swap0 = halfedges[swap0idx];
|
||||
const int swap1idx = swap0.pairedHalfedge;
|
||||
Halfedge& swap1 = halfedges[swap1idx];
|
||||
|
||||
const int next0idx = swap0idx + ((swap0idx + 1) % 3 == 0 ? -2 : 1);
|
||||
const int next1idx = swap1idx + ((swap1idx + 1) % 3 == 0 ? -2 : 1);
|
||||
Halfedge& next0 = halfedges[next0idx];
|
||||
Halfedge& next1 = halfedges[next1idx];
|
||||
|
||||
next0.startVert = swap0.endVert = next1.endVert;
|
||||
swap0.pairedHalfedge = next1.pairedHalfedge;
|
||||
halfedges[swap0.pairedHalfedge].pairedHalfedge = swap0idx;
|
||||
|
||||
next1.startVert = swap1.endVert = next0.endVert;
|
||||
swap1.pairedHalfedge = next0.pairedHalfedge;
|
||||
halfedges[swap1.pairedHalfedge].pairedHalfedge = swap1idx;
|
||||
|
||||
next0.pairedHalfedge = next1idx;
|
||||
next1.pairedHalfedge = next0idx;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InitializeBaryRef {
|
||||
const int meshID;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef&, int> inOut) {
|
||||
BaryRef& baryRef = thrust::get<0>(inOut);
|
||||
int tri = thrust::get<1>(inOut);
|
||||
|
||||
// Leave existing meshID if input is negative
|
||||
if (meshID >= 0) baryRef.meshID = meshID;
|
||||
baryRef.tri = tri;
|
||||
baryRef.vertBary = {-3, -2, -1};
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckProperties {
|
||||
const int numSets;
|
||||
|
||||
__host__ __device__ bool operator()(glm::ivec3 triProp) {
|
||||
bool good = true;
|
||||
for (int i : {0, 1, 2}) good &= (triProp[i] >= 0 && triProp[i] < numSets);
|
||||
return good;
|
||||
}
|
||||
};
|
||||
|
||||
struct CoplanarEdge {
|
||||
float* triArea;
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const glm::ivec3* triProp;
|
||||
const float* prop;
|
||||
const float* propTol;
|
||||
const int numProp;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<thrust::pair<int, int>&, int> inOut) {
|
||||
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
|
||||
const int edgeIdx = thrust::get<1>(inOut);
|
||||
|
||||
const Halfedge edge = halfedge[edgeIdx];
|
||||
if (!edge.IsForward()) return;
|
||||
const Halfedge pair = halfedge[edge.pairedHalfedge];
|
||||
const glm::vec3 base = vertPos[edge.startVert];
|
||||
|
||||
const int baseNum = edgeIdx - 3 * edge.face;
|
||||
const int jointNum = edge.pairedHalfedge - 3 * pair.face;
|
||||
const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
|
||||
const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
|
||||
|
||||
const glm::vec3 jointVec = vertPos[pair.startVert] - base;
|
||||
const glm::vec3 edgeVec =
|
||||
vertPos[halfedge[3 * edge.face + edgeNum].startVert] - base;
|
||||
const glm::vec3 pairVec =
|
||||
vertPos[halfedge[3 * pair.face + pairNum].startVert] - base;
|
||||
|
||||
const float length = glm::max(glm::length(jointVec), glm::length(edgeVec));
|
||||
const float lengthPair =
|
||||
glm::max(glm::length(jointVec), glm::length(pairVec));
|
||||
glm::vec3 normal = glm::cross(jointVec, edgeVec);
|
||||
const float area = glm::length(normal);
|
||||
const float areaPair = glm::length(glm::cross(pairVec, jointVec));
|
||||
// Don't link degenerate triangles
|
||||
if (area < length * precision || areaPair < lengthPair * precision) return;
|
||||
|
||||
const float volume = glm::abs(glm::dot(normal, pairVec));
|
||||
// Only operate on coplanar triangles
|
||||
if (volume > glm::max(area, areaPair) * precision) return;
|
||||
|
||||
// Check property linearity
|
||||
if (area > 0) {
|
||||
normal /= area;
|
||||
for (int i = 0; i < numProp; ++i) {
|
||||
const float scale = precision / propTol[i];
|
||||
|
||||
const float baseProp = prop[numProp * triProp[edge.face][baseNum] + i];
|
||||
const float jointProp =
|
||||
prop[numProp * triProp[pair.face][jointNum] + i];
|
||||
const float edgeProp = prop[numProp * triProp[edge.face][edgeNum] + i];
|
||||
const float pairProp = prop[numProp * triProp[pair.face][pairNum] + i];
|
||||
|
||||
const glm::vec3 iJointVec =
|
||||
jointVec + normal * scale * (jointProp - baseProp);
|
||||
const glm::vec3 iEdgeVec =
|
||||
edgeVec + normal * scale * (edgeProp - baseProp);
|
||||
const glm::vec3 iPairVec =
|
||||
pairVec + normal * scale * (pairProp - baseProp);
|
||||
|
||||
glm::vec3 cross = glm::cross(iJointVec, iEdgeVec);
|
||||
const float area = glm::max(
|
||||
glm::length(cross), glm::length(glm::cross(iPairVec, iJointVec)));
|
||||
const float volume = glm::abs(glm::dot(cross, iPairVec));
|
||||
// Only operate on consistent triangles
|
||||
if (volume > area * precision) return;
|
||||
}
|
||||
}
|
||||
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
face2face.first = edge.face;
|
||||
face2face.second = pair.face;
|
||||
}
|
||||
};
|
||||
|
||||
struct EdgeBox {
|
||||
const glm::vec3* vertPos;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<Box&, const TmpEdge&> inout) {
|
||||
const TmpEdge& edge = thrust::get<1>(inout);
|
||||
thrust::get<0>(inout) = Box(vertPos[edge.first], vertPos[edge.second]);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
std::vector<int> Manifold::Impl::meshID2Original_;
|
||||
|
||||
/**
|
||||
* Create a manifold from an input triangle Mesh. Will throw if the Mesh is not
|
||||
* manifold. TODO: update halfedgeTangent during CollapseDegenerates.
|
||||
*/
|
||||
Manifold::Impl::Impl(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: vertPos_(mesh.vertPos), halfedgeTangent_(mesh.halfedgeTangent) {
|
||||
CheckDevice();
|
||||
CalculateBBox();
|
||||
SetPrecision();
|
||||
CreateAndFixHalfedges(mesh.triVerts);
|
||||
CalculateNormals();
|
||||
InitializeNewReference(triProperties, properties, propertyTolerance);
|
||||
CollapseDegenerates();
|
||||
Finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create eiter a unit tetrahedron, cube or octahedron. The cube is in the first
|
||||
* octant, while the others are symmetric about the origin.
|
||||
*/
|
||||
Manifold::Impl::Impl(Shape shape) {
|
||||
std::vector<glm::vec3> vertPos;
|
||||
std::vector<glm::ivec3> triVerts;
|
||||
switch (shape) {
|
||||
case Shape::TETRAHEDRON:
|
||||
vertPos = {{-1.0f, -1.0f, 1.0f},
|
||||
{-1.0f, 1.0f, -1.0f},
|
||||
{1.0f, -1.0f, -1.0f},
|
||||
{1.0f, 1.0f, 1.0f}};
|
||||
triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
break;
|
||||
case Shape::CUBE:
|
||||
vertPos = {{0.0f, 0.0f, 0.0f}, //
|
||||
{1.0f, 0.0f, 0.0f}, //
|
||||
{1.0f, 1.0f, 0.0f}, //
|
||||
{0.0f, 1.0f, 0.0f}, //
|
||||
{0.0f, 0.0f, 1.0f}, //
|
||||
{1.0f, 0.0f, 1.0f}, //
|
||||
{1.0f, 1.0f, 1.0f}, //
|
||||
{0.0f, 1.0f, 1.0f}};
|
||||
triVerts = {{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}};
|
||||
break;
|
||||
case Shape::OCTAHEDRON:
|
||||
vertPos = {{1.0f, 0.0f, 0.0f}, //
|
||||
{-1.0f, 0.0f, 0.0f}, //
|
||||
{0.0f, 1.0f, 0.0f}, //
|
||||
{0.0f, -1.0f, 0.0f}, //
|
||||
{0.0f, 0.0f, 1.0f}, //
|
||||
{0.0f, 0.0f, -1.0f}};
|
||||
triVerts = {{0, 2, 4}, {1, 5, 3}, //
|
||||
{2, 1, 4}, {3, 5, 0}, //
|
||||
{1, 3, 4}, {0, 5, 2}, //
|
||||
{3, 0, 4}, {2, 5, 1}};
|
||||
break;
|
||||
default:
|
||||
throw userErr("Unrecognized shape!");
|
||||
}
|
||||
vertPos_ = vertPos;
|
||||
CreateHalfedges(triVerts);
|
||||
Finish();
|
||||
InitializeNewReference();
|
||||
}
|
||||
|
||||
/**
|
||||
* When a manifold is copied, it is given a new unique set of mesh relation IDs,
|
||||
* identifying a particular instance of a copied input mesh. The original mesh
|
||||
* ID can be found using the meshID2Original mapping.
|
||||
*/
|
||||
void Manifold::Impl::DuplicateMeshIDs() {
|
||||
std::map<int, int> old2new;
|
||||
for (BaryRef& ref : meshRelation_.triBary) {
|
||||
if (old2new.find(ref.meshID) == old2new.end()) {
|
||||
old2new[ref.meshID] = meshID2Original_.size();
|
||||
meshID2Original_.push_back(meshID2Original_[ref.meshID]);
|
||||
}
|
||||
ref.meshID = old2new[ref.meshID];
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::ReinitializeReference(int meshID) {
|
||||
thrust::for_each_n(zip(meshRelation_.triBary.beginD(), countAt(0)), NumTri(),
|
||||
InitializeBaryRef({meshID, halfedge_.cptrD()}));
|
||||
}
|
||||
|
||||
int Manifold::Impl::InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance) {
|
||||
meshRelation_.triBary.resize(NumTri());
|
||||
const int nextMeshID = meshID2Original_.size();
|
||||
meshID2Original_.push_back(nextMeshID);
|
||||
ReinitializeReference(nextMeshID);
|
||||
|
||||
const int numProps = propertyTolerance.size();
|
||||
|
||||
VecDH<glm::ivec3> triPropertiesD(triProperties);
|
||||
VecDH<float> propertiesD(properties);
|
||||
VecDH<float> propertyToleranceD(propertyTolerance);
|
||||
|
||||
if (numProps > 0) {
|
||||
ALWAYS_ASSERT(
|
||||
triProperties.size() == NumTri() || triProperties.size() == 0, userErr,
|
||||
"If specified, triProperties vector length must match NumTri().");
|
||||
ALWAYS_ASSERT(properties.size() % numProps == 0, userErr,
|
||||
"properties vector must be a multiple of the size of "
|
||||
"propertyTolerance.");
|
||||
|
||||
const int numSets = properties.size() / numProps;
|
||||
ALWAYS_ASSERT(thrust::all_of(triPropertiesD.beginD(), triPropertiesD.endD(),
|
||||
CheckProperties({numSets})),
|
||||
userErr,
|
||||
"triProperties value is outside the properties range.");
|
||||
}
|
||||
|
||||
VecDH<thrust::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
||||
VecDH<float> triArea(NumTri());
|
||||
thrust::for_each_n(
|
||||
zip(face2face.beginD(), countAt(0)), halfedge_.size(),
|
||||
CoplanarEdge({triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
triPropertiesD.cptrD(), propertiesD.cptrD(),
|
||||
propertyToleranceD.cptrD(), numProps, precision_}));
|
||||
|
||||
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> graph(
|
||||
NumTri());
|
||||
for (int i = 0; i < face2face.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = face2face.H()[i];
|
||||
if (edge.first < 0) continue;
|
||||
boost::add_edge(edge.first, edge.second, graph);
|
||||
}
|
||||
std::vector<int> components(NumTri());
|
||||
const int numComponent =
|
||||
boost::connected_components(graph, components.data());
|
||||
|
||||
std::vector<int> comp2tri(numComponent, -1);
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int comp = components[tri];
|
||||
const int current = comp2tri[comp];
|
||||
if (current < 0 || triArea.H()[tri] > triArea.H()[current]) {
|
||||
comp2tri[comp] = tri;
|
||||
triArea.H()[comp] = triArea.H()[tri];
|
||||
}
|
||||
}
|
||||
|
||||
VecH<BaryRef>& triBary = meshRelation_.triBary.H();
|
||||
std::map<std::pair<int, int>, int> triVert2bary;
|
||||
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int refTri = comp2tri[components[tri]];
|
||||
if (refTri == tri) continue;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_.H()[3 * refTri + i].startVert;
|
||||
triPos[i] = vertPos_.H()[vert];
|
||||
triVert2bary[{refTri, vert}] = i - 3;
|
||||
}
|
||||
|
||||
glm::ivec3 vertBary;
|
||||
bool coplanar = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_.H()[3 * tri + i].startVert;
|
||||
if (triVert2bary.find({refTri, vert}) == triVert2bary.end()) {
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPos_.H()[vert], triPos, precision_);
|
||||
if (isnan(uvw[0])) {
|
||||
coplanar = false;
|
||||
triVert2bary[{refTri, vert}] = -4;
|
||||
break;
|
||||
}
|
||||
triVert2bary[{refTri, vert}] = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.H().push_back(uvw);
|
||||
}
|
||||
const int bary = triVert2bary[{refTri, vert}];
|
||||
if (bary < -3) {
|
||||
coplanar = false;
|
||||
break;
|
||||
}
|
||||
vertBary[i] = bary;
|
||||
}
|
||||
|
||||
if (coplanar) {
|
||||
BaryRef& ref = triBary[tri];
|
||||
ref.tri = refTri;
|
||||
ref.vertBary = vertBary;
|
||||
}
|
||||
}
|
||||
|
||||
return nextMeshID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the halfedge_ data structure from an input triVerts array like Mesh.
|
||||
*/
|
||||
void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
const int numTri = triVerts.size();
|
||||
halfedge_.resize(3 * numTri);
|
||||
VecDH<TmpEdge> edge(3 * numTri);
|
||||
thrust::for_each_n(zip(countAt(0), triVerts.beginD()), numTri,
|
||||
Tri2Halfedges({halfedge_.ptrD(), edge.ptrD()}));
|
||||
thrust::sort(edge.beginD(), edge.endD());
|
||||
thrust::for_each_n(countAt(0), halfedge_.size() / 2,
|
||||
LinkHalfedges({halfedge_.ptrD(), edge.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the halfedge_ data structure from an input triVerts array like Mesh.
|
||||
* Check that the input is an even-manifold, and if it is not 2-manifold,
|
||||
* perform edge swaps until it is. This is a host function.
|
||||
*/
|
||||
void Manifold::Impl::CreateAndFixHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
const int numTri = triVerts.size();
|
||||
halfedge_.resize(3 * numTri);
|
||||
VecDH<TmpEdge> edge(3 * numTri);
|
||||
thrust::for_each_n(zip(countAt(0), triVerts.begin()), numTri,
|
||||
Tri2Halfedges({halfedge_.ptrH(), edge.ptrH()}));
|
||||
// Stable sort is required here so that halfedges from the same face are
|
||||
// paired together (the triangles were created in face order). In some
|
||||
// degenerate situations the triangulator can add the same internal edge in
|
||||
// two different faces, causing this edge to not be 2-manifold. We detect this
|
||||
// and fix it by swapping one of the identical edges, so it is important that
|
||||
// we have the edges paired according to their face.
|
||||
std::stable_sort(edge.begin(), edge.end());
|
||||
thrust::for_each_n(thrust::host, countAt(0), halfedge_.size() / 2,
|
||||
LinkHalfedges({halfedge_.ptrH(), edge.cptrH()}));
|
||||
thrust::for_each(thrust::host, countAt(1), countAt(halfedge_.size() / 2),
|
||||
SwapHalfedges({halfedge_.ptrH(), edge.cptrH()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full recalculation of the face bounding boxes, including updating the
|
||||
* collider, but does not resort the faces.
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
VecDH<Box> faceBox;
|
||||
VecDH<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
collider_.UpdateBoxes(faceBox);
|
||||
}
|
||||
|
||||
void Manifold::Impl::ApplyTransform() const {
|
||||
// This const_cast is here because these operations cancel out, leaving the
|
||||
// state conceptually unchanged. This enables lazy transformation evaluation.
|
||||
const_cast<Impl*>(this)->ApplyTransform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bake the manifold's transform into its vertices. This function allows lazy
|
||||
* evaluation, which is important because often several transforms are applied
|
||||
* between operations.
|
||||
*/
|
||||
void Manifold::Impl::ApplyTransform() {
|
||||
if (transform_ == glm::mat4x3(1.0f)) return;
|
||||
thrust::for_each(vertPos_.beginD(), vertPos_.endD(),
|
||||
Transform4x3({transform_}));
|
||||
|
||||
glm::mat3 normalTransform =
|
||||
glm::inverse(glm::transpose(glm::mat3(transform_)));
|
||||
thrust::for_each(faceNormal_.beginD(), faceNormal_.endD(),
|
||||
TransformNormals({normalTransform}));
|
||||
thrust::for_each(vertNormal_.beginD(), vertNormal_.endD(),
|
||||
TransformNormals({normalTransform}));
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
// axis-aligned.
|
||||
if (!collider_.Transform(transform_)) Update();
|
||||
|
||||
const float oldScale = bBox_.Scale();
|
||||
transform_ = glm::mat4x3(1.0f);
|
||||
CalculateBBox();
|
||||
|
||||
const float newScale = bBox_.Scale();
|
||||
precision_ *= glm::max(1.0f, newScale / oldScale) *
|
||||
glm::max(glm::length(transform_[0]),
|
||||
glm::max(glm::length(transform_[1]),
|
||||
glm::length(transform_[2])));
|
||||
|
||||
// Maximum of inherited precision loss and translational precision loss.
|
||||
SetPrecision(precision_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the precision based on the bounding box, and limits its minimum value by
|
||||
* the optional input.
|
||||
*/
|
||||
void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
precision_ = glm::max(minPrecision, kTolerance * bBox_.Scale());
|
||||
if (!glm::isfinite(precision_)) precision_ = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* If face normals are already present, this function uses them to compute
|
||||
* vertex normals (angle-weighted pseudo-normals); otherwise it also computes
|
||||
* the face normals. Face normals are only calculated when needed because nearly
|
||||
* degenerate faces will accrue rounding error, while the Boolean can retain
|
||||
* their original normal, which is more accurate and can help with merging
|
||||
* coplanar faces.
|
||||
*
|
||||
* If the face normals have been invalidated by an operation like Warp(), ensure
|
||||
* you do faceNormal_.resize(0) before calling this function to force
|
||||
* recalculation.
|
||||
*/
|
||||
void Manifold::Impl::CalculateNormals() {
|
||||
vertNormal_.resize(NumVert());
|
||||
thrust::fill(vertNormal_.beginD(), vertNormal_.endD(), glm::vec3(0));
|
||||
bool calculateTriNormal = false;
|
||||
if (faceNormal_.size() != NumTri()) {
|
||||
faceNormal_.resize(NumTri());
|
||||
calculateTriNormal = true;
|
||||
}
|
||||
thrust::for_each_n(
|
||||
zip(faceNormal_.beginD(), countAt(0)), NumTri(),
|
||||
AssignNormals({vertNormal_.ptrD(), vertPos_.cptrD(), halfedge_.cptrD(),
|
||||
precision_, calculateTriNormal}));
|
||||
thrust::for_each(vertNormal_.beginD(), vertNormal_.endD(), Normalize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sparse array of the bounding box overlaps between the edges of the
|
||||
* input manifold, Q and the faces of this manifold. Returned indices only
|
||||
* point to forward halfedges.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q) const {
|
||||
VecDH<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
|
||||
const int numEdge = edges.size();
|
||||
VecDH<Box> QedgeBB(numEdge);
|
||||
thrust::for_each_n(zip(QedgeBB.beginD(), edges.cbeginD()), numEdge,
|
||||
EdgeBox({Q.vertPos_.cptrD()}));
|
||||
|
||||
SparseIndices q1p2 = collider_.Collisions(QedgeBB);
|
||||
|
||||
thrust::for_each(q1p2.beginD(0), q1p2.endD(0), ReindexEdge({edges.cptrD()}));
|
||||
return q1p2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sparse array of the input vertices that project inside the XY
|
||||
* bounding boxes of the faces of this manifold.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::VertexCollisionsZ(
|
||||
const VecDH<glm::vec3>& vertsIn) const {
|
||||
return collider_.Collisions(vertsIn);
|
||||
}
|
||||
} // 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,338 +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 <thrust/sequence.h>
|
||||
|
||||
#include "impl.cuh"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
constexpr uint32_t kNoCode = 0xFFFFFFFFu;
|
||||
|
||||
struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
|
||||
__host__ __device__ void MakeForward(Halfedge& a) {
|
||||
if (!a.IsForward()) {
|
||||
int tmp = a.startVert;
|
||||
a.startVert = a.endVert;
|
||||
a.endVert = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ int MaxOrMinus(int a, int b) {
|
||||
return glm::min(a, b) < 0 ? -1 : glm::max(a, b);
|
||||
}
|
||||
|
||||
__host__ __device__ Halfedge operator()(Halfedge a, Halfedge b) {
|
||||
MakeForward(a);
|
||||
MakeForward(b);
|
||||
a.startVert = glm::min(a.startVert, b.startVert);
|
||||
a.endVert = glm::max(a.endVert, b.endVert);
|
||||
a.face = MaxOrMinus(a.face, b.face);
|
||||
a.pairedHalfedge = MaxOrMinus(a.pairedHalfedge, b.pairedHalfedge);
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
__host__ __device__ uint32_t SpreadBits3(uint32_t v) {
|
||||
v = 0xFF0000FFu & (v * 0x00010001u);
|
||||
v = 0x0F00F00Fu & (v * 0x00000101u);
|
||||
v = 0xC30C30C3u & (v * 0x00000011u);
|
||||
v = 0x49249249u & (v * 0x00000005u);
|
||||
return v;
|
||||
}
|
||||
|
||||
__host__ __device__ uint32_t MortonCode(glm::vec3 position, Box bBox) {
|
||||
// Unreferenced vertices are marked NaN, and this will sort them to the end
|
||||
// (the Morton code only uses the first 30 of 32 bits).
|
||||
if (isnan(position.x)) return kNoCode;
|
||||
|
||||
glm::vec3 xyz = (position - bBox.min) / (bBox.max - bBox.min);
|
||||
xyz = glm::min(glm::vec3(1023.0f), glm::max(glm::vec3(0.0f), 1024.0f * xyz));
|
||||
uint32_t x = SpreadBits3(static_cast<uint32_t>(xyz.x));
|
||||
uint32_t y = SpreadBits3(static_cast<uint32_t>(xyz.y));
|
||||
uint32_t z = SpreadBits3(static_cast<uint32_t>(xyz.z));
|
||||
return x * 4 + y * 2 + z;
|
||||
}
|
||||
|
||||
struct Morton {
|
||||
const Box bBox;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<uint32_t&, const glm::vec3&> inout) {
|
||||
glm::vec3 position = thrust::get<1>(inout);
|
||||
thrust::get<0>(inout) = MortonCode(position, bBox);
|
||||
}
|
||||
};
|
||||
|
||||
struct FaceMortonBox {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const Box bBox;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<uint32_t&, Box&, int> inout) {
|
||||
uint32_t& mortonCode = thrust::get<0>(inout);
|
||||
Box& faceBox = thrust::get<1>(inout);
|
||||
int face = thrust::get<2>(inout);
|
||||
|
||||
// Removed tris are marked by all halfedges having pairedHalfedge = -1, and
|
||||
// this will sort them to the end (the Morton code only uses the first 30 of
|
||||
// 32 bits).
|
||||
if (halfedge[3 * face].pairedHalfedge < 0) {
|
||||
mortonCode = kNoCode;
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 center(0.0f);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const glm::vec3 pos = vertPos[halfedge[3 * face + i].startVert];
|
||||
center += pos;
|
||||
faceBox.Union(pos);
|
||||
}
|
||||
center /= 3;
|
||||
|
||||
mortonCode = MortonCode(center, bBox);
|
||||
}
|
||||
};
|
||||
|
||||
struct Reindex {
|
||||
const int* indexInv;
|
||||
|
||||
__host__ __device__ void operator()(Halfedge& edge) {
|
||||
if (edge.startVert < 0) return;
|
||||
edge.startVert = indexInv[edge.startVert];
|
||||
edge.endVert = indexInv[edge.endVert];
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
VecDH<T> tmp(inOut);
|
||||
inOut.resize(new2Old.size());
|
||||
thrust::gather(new2Old.beginD(), new2Old.endD(), tmp.beginD(),
|
||||
inOut.beginD());
|
||||
}
|
||||
|
||||
template void Permute<BaryRef>(VecDH<BaryRef>&, const VecDH<int>&);
|
||||
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
|
||||
|
||||
struct ReindexFace {
|
||||
Halfedge* halfedge;
|
||||
glm::vec4* halfedgeTangent;
|
||||
const Halfedge* oldHalfedge;
|
||||
const glm::vec4* oldHalfedgeTangent;
|
||||
const int* faceNew2Old;
|
||||
const int* faceOld2New;
|
||||
|
||||
__host__ __device__ void operator()(int newFace) {
|
||||
const int oldFace = faceNew2Old[newFace];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int oldEdge = 3 * oldFace + i;
|
||||
Halfedge edge = oldHalfedge[oldEdge];
|
||||
edge.face = newFace;
|
||||
const int pairedFace = edge.pairedHalfedge / 3;
|
||||
const int offset = edge.pairedHalfedge - 3 * pairedFace;
|
||||
edge.pairedHalfedge = 3 * faceOld2New[pairedFace] + offset;
|
||||
const int newEdge = 3 * newFace + i;
|
||||
halfedge[newEdge] = edge;
|
||||
if (oldHalfedgeTangent != nullptr) {
|
||||
halfedgeTangent[newEdge] = oldHalfedgeTangent[oldEdge];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
* Once halfedge_ has been filled in, this function can be called to create the
|
||||
* rest of the internal data structures. This function also removes the verts
|
||||
* and halfedges flagged for removal (NaN verts and -1 halfedges).
|
||||
*/
|
||||
void Manifold::Impl::Finish() {
|
||||
if (halfedge_.size() == 0) return;
|
||||
|
||||
CalculateBBox();
|
||||
SetPrecision(precision_);
|
||||
if (!bBox_.isFinite()) {
|
||||
vertPos_.resize(0);
|
||||
halfedge_.resize(0);
|
||||
faceNormal_.resize(0);
|
||||
return;
|
||||
}
|
||||
|
||||
SortVerts();
|
||||
VecDH<Box> faceBox;
|
||||
VecDH<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
|
||||
ALWAYS_ASSERT(halfedge_.size() % 6 == 0, topologyErr,
|
||||
"Not an even number of faces after sorting faces!");
|
||||
Halfedge extrema = {0, 0, 0, 0};
|
||||
extrema =
|
||||
thrust::reduce(halfedge_.beginD(), halfedge_.endD(), extrema, Extrema());
|
||||
|
||||
ALWAYS_ASSERT(extrema.startVert >= 0, topologyErr,
|
||||
"Vertex index is negative!");
|
||||
ALWAYS_ASSERT(extrema.endVert < NumVert(), topologyErr,
|
||||
"Vertex index exceeds number of verts!");
|
||||
ALWAYS_ASSERT(extrema.face >= 0, topologyErr, "Face index is negative!");
|
||||
ALWAYS_ASSERT(extrema.face < NumTri(), topologyErr,
|
||||
"Face index exceeds number of faces!");
|
||||
ALWAYS_ASSERT(extrema.pairedHalfedge >= 0, topologyErr,
|
||||
"Halfedge index is negative!");
|
||||
ALWAYS_ASSERT(extrema.pairedHalfedge < 2 * NumEdge(), topologyErr,
|
||||
"Halfedge index exceeds number of halfedges!");
|
||||
|
||||
CalculateNormals();
|
||||
collider_ = Collider(faceBox, faceMorton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the vertices according to their Morton code.
|
||||
*/
|
||||
void Manifold::Impl::SortVerts() {
|
||||
VecDH<uint32_t> vertMorton(NumVert());
|
||||
thrust::for_each_n(zip(vertMorton.beginD(), vertPos_.cbeginD()), NumVert(),
|
||||
Morton({bBox_}));
|
||||
|
||||
VecDH<int> vertNew2Old(NumVert());
|
||||
thrust::sequence(vertNew2Old.beginD(), vertNew2Old.endD());
|
||||
thrust::sort_by_key(vertMorton.beginD(), vertMorton.endD(),
|
||||
zip(vertPos_.beginD(), vertNew2Old.beginD()));
|
||||
|
||||
ReindexVerts(vertNew2Old, NumVert());
|
||||
|
||||
// Verts were flagged for removal with NaNs and assigned kNoCode to sort them
|
||||
// to the end, which allows them to be removed.
|
||||
const int newNumVert =
|
||||
thrust::find(vertMorton.beginD(), vertMorton.endD(), kNoCode) -
|
||||
vertMorton.beginD();
|
||||
vertPos_.resize(newNumVert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the halfedges to point to new vert indices based on a mapping,
|
||||
* vertNew2Old. This may be a subset, so the total number of original verts is
|
||||
* also given.
|
||||
*/
|
||||
void Manifold::Impl::ReindexVerts(const VecDH<int>& vertNew2Old,
|
||||
int oldNumVert) {
|
||||
VecDH<int> vertOld2New(oldNumVert);
|
||||
thrust::scatter(countAt(0), countAt(NumVert()), vertNew2Old.beginD(),
|
||||
vertOld2New.beginD());
|
||||
thrust::for_each(halfedge_.beginD(), halfedge_.endD(),
|
||||
Reindex({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the faceBox and faceMorton input with the bounding boxes and Morton
|
||||
* codes of the faces, respectively. The Morton code is based on the center of
|
||||
* the bounding box.
|
||||
*/
|
||||
void Manifold::Impl::GetFaceBoxMorton(VecDH<Box>& faceBox,
|
||||
VecDH<uint32_t>& faceMorton) const {
|
||||
faceBox.resize(NumTri());
|
||||
faceMorton.resize(NumTri());
|
||||
thrust::for_each_n(
|
||||
zip(faceMorton.beginD(), faceBox.beginD(), countAt(0)), NumTri(),
|
||||
FaceMortonBox({halfedge_.cptrD(), vertPos_.cptrD(), bBox_}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the faces of this manifold according to their input Morton code. The
|
||||
* bounding box and Morton code arrays are also sorted accordingly.
|
||||
*/
|
||||
void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
|
||||
VecDH<uint32_t>& faceMorton) {
|
||||
VecDH<int> faceNew2Old(NumTri());
|
||||
thrust::sequence(faceNew2Old.beginD(), faceNew2Old.endD());
|
||||
|
||||
thrust::sort_by_key(faceMorton.beginD(), faceMorton.endD(),
|
||||
zip(faceBox.beginD(), faceNew2Old.beginD()));
|
||||
|
||||
// Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode
|
||||
// to sort them to the end, which allows them to be removed.
|
||||
const int newNumTri =
|
||||
thrust::find(faceMorton.beginD(), faceMorton.endD(), kNoCode) -
|
||||
faceMorton.beginD();
|
||||
faceBox.resize(newNumTri);
|
||||
faceMorton.resize(newNumTri);
|
||||
faceNew2Old.resize(newNumTri);
|
||||
|
||||
GatherFaces(faceNew2Old);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the halfedge_ vector for this manifold by copying a set of faces from
|
||||
* another manifold, given by oldHalfedge. Input faceNew2Old defines the old
|
||||
* faces to gather into this.
|
||||
*/
|
||||
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
if (meshRelation_.triBary.size() == NumTri())
|
||||
Permute(meshRelation_.triBary, faceNew2Old);
|
||||
|
||||
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
|
||||
|
||||
VecDH<Halfedge> oldHalfedge(halfedge_);
|
||||
VecDH<glm::vec4> oldHalfedgeTangent(halfedgeTangent_);
|
||||
VecDH<int> faceOld2New(oldHalfedge.size() / 3);
|
||||
thrust::scatter(countAt(0), countAt(numTri), faceNew2Old.beginD(),
|
||||
faceOld2New.beginD());
|
||||
|
||||
halfedge_.resize(3 * numTri);
|
||||
if (oldHalfedgeTangent.size() != 0) halfedgeTangent_.resize(3 * numTri);
|
||||
thrust::for_each_n(
|
||||
countAt(0), numTri,
|
||||
ReindexFace({halfedge_.ptrD(), halfedgeTangent_.ptrD(),
|
||||
oldHalfedge.cptrD(), oldHalfedgeTangent.cptrD(),
|
||||
faceNew2Old.cptrD(), faceOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::GatherFaces(const Impl& old,
|
||||
const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
meshRelation_.triBary.resize(numTri);
|
||||
thrust::gather(faceNew2Old.beginD(), faceNew2Old.endD(),
|
||||
old.meshRelation_.triBary.beginD(),
|
||||
meshRelation_.triBary.beginD());
|
||||
meshRelation_.barycentric = old.meshRelation_.barycentric;
|
||||
DuplicateMeshIDs();
|
||||
|
||||
if (old.faceNormal_.size() == old.NumTri()) {
|
||||
faceNormal_.resize(numTri);
|
||||
thrust::gather(faceNew2Old.beginD(), faceNew2Old.endD(),
|
||||
old.faceNormal_.beginD(), faceNormal_.beginD());
|
||||
}
|
||||
|
||||
VecDH<int> faceOld2New(old.NumTri());
|
||||
thrust::scatter(countAt(0), countAt(numTri), faceNew2Old.beginD(),
|
||||
faceOld2New.beginD());
|
||||
|
||||
halfedge_.resize(3 * numTri);
|
||||
if (old.halfedgeTangent_.size() != 0) halfedgeTangent_.resize(3 * numTri);
|
||||
thrust::for_each_n(
|
||||
countAt(0), numTri,
|
||||
ReindexFace({halfedge_.ptrD(), halfedgeTangent_.ptrD(),
|
||||
old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
|
||||
faceNew2Old.cptrD(), faceOld2New.cptrD()}));
|
||||
}
|
||||
} // namespace manifold
|
||||