Compare commits

...
334 Commits
Author SHA1 Message Date
Emmett Lalish 67a5b7140d basic example working 2022-11-16 00:01:08 +01:00
Emmett Lalish 98a61df840 Fix editor first run (#281)
* fixed first run bug, plus minor cleanup

* added examples pointer
2022-11-14 00:02:43 +01:00
Emmett Lalish a9dc1550ba new JS Mesh class (#272)
* new JS Mesh class

* fix Mesh

* switch MeshGL to vectors

* new Mesh constructor

* export Mesh and updated types

* got scallop working again

* optional normals

* fixed Mesh constructor

* added halfedgeTangent

* cleanup
2022-11-10 23:24:46 +01:00
Emmett Lalish fd8c381179 Fix spelling (#270)
* fixed spelling

* forgot one

* JS code formatting

* updated CMakeLists for easier testing

* revert small change
2022-11-08 10:40:15 +01:00
Emmett LalishandStewart Allen af9377d6e7 Add GetMeshGL (#269)
* figure out wasm shared memory access, manifold data structure storage

* checkpoint multi-mode mesh direct access

* clean this up for a PR

* use size_t instead of int. fix pointer math for free() call. tab indent 2 spaces in js to match other code

* apply clang formatting. default to mode 1 (faster)

* eliminate new getMeshDirect() api call and fold it into getMesh() as an optional parameter object
eliminate _FreeMeshDirect and use Module._free() instead

* added optional ability to get normals array
restore access to mode 0 largely for testing

* remove mode options, simplify code

* this is quite a bit neater. fixes shorter header length

* attempt to do this the right way

* meshGL working

* revert JS API update

* added docs

Co-authored-by: Stewart Allen <stewart.allen@gmail.com>
2022-11-05 17:04:38 +01:00
Rafael 50fc5d87de Updated JS bindings (#268)
* Updated JS binding type definitions

* Ignore sublime text projects

* Ignore clangd cache

* Expose isManifold method

* Added wrapper for Manifold constructor, updated Mesh interface (...)
- Optional Mesh fields no longer error out with a 'missing property' error when they are missing
- Manifold constructor now uses the JS style Mesh instead of the serialized Mesh interface (MeshVec)
  - The old constructor was kept as a new static function (ManifoldFromMeshVec)
  - Error checking added to the constructor. If the status is not 0, an error with the correct message and code is thrown
- Updated type definitions

* Fix typo
whoops...

* Fix broken MeshRelation deserialization

* Revert "Expose isManifold method"

This reverts commit cedde70e1a28d721f58d886cea3ebd02fe0565af.

* Dummy commit to trigger CI
2022-11-05 00:23:54 +01:00
Jiří Hon ca81951355 WASM bindings: add missing smooth function to the ManifoldStatic interface (#267)
Update manifold.d.ts

Add missing smooth function to the ManifoldStatic interface.
2022-11-03 22:27:43 +01:00
Emmett Lalish c59130c754 Adding NodeJS tests for WASM module (#262)
* modularize wasm and add mocha tests

* add npm tests to CI

* fix mv command

* revert to build directory

* added rest of example tests

* fixed three.js test

* update web examples for module
2022-10-27 10:01:08 -07:00
Emmett Lalish 491d7d8045 added package.json to publish to npm (#257)
* added package.json

* updated READMEs
2022-10-23 22:04:53 -07:00
Emmett Lalish 0fb47693b3 Fix web examples (#254)
* fixed three.js examples

* add link and fix spelling
2022-10-21 15:42:00 -07:00
Emmett Lalish 066aec0137 Add missing license headers (#253)
* added examples attribution

* added missing headers
2022-10-20 12:27:27 -07:00
Emmett Lalish 40624329e6 updated getMesh JS binding (#252) 2022-10-20 09:15:34 -07:00
Emmett Lalish 3eae433537 Yet more JS examples (#251)
* added tet puzzle

* added scallop, updated smooth

* add missing file

* moved saved scripts to top of dropdown
2022-10-18 21:14:28 -07:00
Emmett Lalish 1fced6a33f fix deploy order (#250)
fix order
2022-10-16 23:37:14 -07:00
Emmett Lalish 89578ccb7a Torus Knot example (#249)
* added torusKnot

* save state

* update deploy

* remove shell script
2022-10-16 23:02:12 -07:00
Emmett Lalish d5b5f044f2 update workflows (#248) 2022-10-16 21:40:19 -07:00
Emmett Lalish 4ea17bced1 fix deploy script (#247) 2022-10-16 16:18:48 -07:00
Emmett Lalish 5be306bb7d reorganize gh-pages (#246)
reorganized gh-pages
2022-10-16 15:51:47 -07:00
Emmett Lalish b26ae27916 More examples (#244)
* better error handling and allow single polygons

* improved console logging

* added gyroidModule example

* fixed delete script
2022-10-14 08:53:46 -07:00
Emmett Lalish 667ae7fe75 Editor output info (#240)
* add missing icon

* added more stats
2022-10-12 08:33:30 -07:00
Emmett Lalish 2c750753a5 Add worker and the ability to cancel (#239)
* moved computations to worker

* fixed console logs

* fixed run at startup

* buttons should not be children of buttons!

* enabled cancel

* allow top and bottom view

* fixed reinitialization
2022-10-11 16:21:36 -07:00
pca006132 d15b8726c3 optimize collision detection (#235)
collider: improve performance

Reduces the number of atomic instructions needed and only perform
allocation once. This improves the performance for complicated models by
~10%.
2022-10-09 22:56:12 -07:00
Emmett Lalish bc83f65ba7 Wasm namespace (#236)
Made our API more discoverable in the editor
2022-10-09 16:11:59 -07:00
pca006132 3b00e9206a fixes collider data race (#234)
added fence for AtomicAdd
2022-10-09 12:56:24 -07:00
Emmett Lalish f2e3956770 Replaced warp example with heart (#232)
* fixed console overflow

* added heart example

* better saving

* better uniqueness

* make file button fixed width
2022-10-08 08:43:57 -07:00
Emmett Lalish d49de483ad Editor examples (#231)
* moved examples

* added examples

* addressing feedback
2022-10-07 00:42:49 -07:00
Emmett Lalish 4d01e46bd3 Editor console (#230)
* added console output

* fixed bounding box

* updated intro

* catch compile errors

* update intro again
2022-10-06 10:58:47 -07:00
Emmett Lalish 69a53f522a added mac to CI (#229)
* added mac to CI

* add mac parallel backends

* disable OMP for now
2022-10-06 08:59:09 -07:00
Emmett Lalish 022995a697 updated build info (#228)
* updated build info

* added cgal
2022-10-06 08:25:30 -07:00
pca006132 add3a04fae fixed csg_tree bugs (#226)
1. Due to the shared cache between op nodes, the children list can have
   only 1 child and the original code will access a dangling pointer.
2. Users may supply empty/singleton lists to BatchBoolean, which
   CsgOpNode cannot handle the empty case correctly.

The new test is for bug 1.
2022-10-04 07:53:41 -07:00
pca006132 a860854143 add missing js bindings (#224)
* export addFunction explicitly

* add missing js APIs

* fixed sdf

* made requested changes
2022-10-03 21:41:04 -07:00
Emmett Lalish 60697cc48a Editor save (#222)
* split editor files

* added loading poster

* added dropdown menu

* switching examples works

* autosave working

* state retrieval working

* rearrange code

* added edit buttons

* made delete function

* rename works
2022-09-30 00:08:06 -07:00
Emmett Lalish 3e61c1a156 WASM warp (#221)
* builds, doesn't run

* runs!

* addressing feedback
2022-09-27 08:51:51 -07:00
pca006132 3f33c919df expose batch boolean API to wasm (#220)
* expose batch boolean API

It is common to perform boolean operations on a set of meshes. This
batch boolean API allows faster operation in that case by avoiding
building up a complicated csg tree.

* expose batch boolean interface for wasm

This brings a 10x improvement to browser in some cases.
See #215 for discussion.

* record performance statistics for editor

So we can compare against other libraries easily, and users are able to
verify it without having to compile the whole library. Ideally we should
put this information in a console GUI, but printing to browser console
works for now.
2022-09-25 23:06:56 -07:00
pca006132andEmmett Lalish 8538cf5ee3 use compose for disjoint union (#217)
* use compose for disjoint union

Last mystery in #114. It turns out that the problem is due to
misunderstanding of the bounding box API: Box::Transform is *not* a safe
over-approximation of the bounding box of the manifold after the
transform. In fact it might be smaller than that if the transform is not
axis aligned. The simple fix is to apply the transform first and then
compute the bounding box, this will also make sure that the bounding box
is tight.

* fix batch union

Co-authored-by: Emmett Lalish <elalish@gmail.com>
2022-09-24 23:35:05 -07:00
pca006132andEmmett Lalish ae2ba3e323 python binding changes (#214)
* python documentation

* fixed error in readme

Co-authored-by: Emmett Lalish <elalish@gmail.com>
2022-09-24 23:07:43 -07:00
pca006132 975a6e6b4b free memory for js editor (#213)
* fixed js rotate binding

Rotate accepts three float args instead of 1 vec3 arg.

* free memory for js editor
2022-09-24 22:09:14 -07:00
Emmett Lalish b1b35395fa Editor updates (#212)
* simplification

* more functional binding

* added TS docs

* more docs
2022-09-23 08:38:13 -07:00
Emmett Lalish 31589d255c fix deploy and add favicon (#211) 2022-09-22 09:17:44 -07:00
Emmett Lalish 414c6e06be Editor UX updates (#210)
* UX updates and TS fix

* small refactor

* enable/disable run button

* improved responsive layout
2022-09-22 00:05:05 -07:00
pca006132 17f3751ae4 added monaco editor to model-viewer example (#208)
* add monaco editor page

* changed typescript bindings

* use modern js
2022-09-21 09:32:11 -07:00
pca006132 0e1ed721ba cache intermediate results (#199) (#206) 2022-09-20 08:14:11 -07:00
Emmett Lalish f6839a36e6 updated python exporter from meshio to trimesh (#204) 2022-09-18 16:34:20 -07:00
Emmett Lalish c38c2b6022 updated wasm examples (#203) 2022-09-18 15:44:37 -07:00
pca006132 3464a9ca4f updated js binding (#201)
* JS API changes

* wasm cmake changes

add the js binding file and configure dependencies properly

* updated model-viewer example

* remove three.js specific APIs

* removed getGeometry
2022-09-11 09:06:44 -07:00
Emmett Lalish 692dc69092 Doxygen updates (#200)
* added paper

* reorganized doxygen

* added debug module

* rename structs.h to public.h

* cleanup
2022-09-10 15:22:20 -07:00
Emmett Lalish cc62c2c9dc model-viewer WASM example (#198)
* added what will become the model-viewer example

* model-viewer working

* cleanup
2022-09-08 16:04:31 -07:00
Emmett Lalish fd1d42e96b Improve codecov metric (#195)
assimp failing
2022-09-06 10:16:15 -07:00
Emmett Lalish 2835e15a0e remove assimp submodule (#194)
* remove assimp submodule

* optionally compile meshIO

* fix nix
2022-09-06 09:31:20 -07:00
Emmett Lalish 73a53e0ff8 Removing export from python binding (#190)
* try python meshio

* removed meshIO dependency

* fixed to_mesh

* fix nix

* more nix fix
2022-09-05 09:29:28 -07:00
Emmett Lalish 405dd6373a Make MeshIO optional for tests (#189)
* replaced file-based testing

* conditionally link meshIO in tests

* fix wasm build
2022-09-04 11:52:05 -07:00
Emmett Lalish 11e58ea4d2 Adding SDF LevelSet (#187)
* polymorphic device functions working

* starting to flesh out algorithm

* getting closer

* fleshed out vert computation

* more progress

* BuildTris

* fixed some syntax

* updating to cpp

* added VecDc

* SDF compiles

* SDF runs

* fixed some bugs

* manifold output

* fixed out of memory bug

* flattened sides

* back to egg-crate

* works with CUDA too

* moved SDF

* Revert "moved SDF"

This reverts commit ba2348f38ed752d49eebdf14a73b5e1fd7a4827b.

* added gyroid sample

* added sdf_test

* added SDF tests

* tests pass

* updated gyroid_module

* fixed gyroid module

* added docs

* addressing feedback

* fix cuda detection

* clean up detect cuda

* fixed CUDA failure

* consistent execution policy in Boolean

* remove SDF wrapper class

* simplified tet logic

* use NeighborInside()

* optimized SDF

* moved RemoveUnreferencedVerts

* put printf statements behind MANIFOLD_DEBUG

* enable hashtable resize

* added test for hashtable resizing
2022-09-01 09:04:51 -07:00
pca006132 08598c045a re-enabled reordering (#171)
* re-enabled reordering

* fix test failures
2022-08-13 22:28:16 -07:00
Emmett Lalish 747dcd94bc Changed MeshRelation API (#184)
* cause repro

* added originalID to BaryRef

* make coverage check informational

* unique meshIDs for compose

* make AsOriginal decimate

* remove vscode properties from git

* update docs

* remove vertNormal handling

* Revert "remove vertNormal handling"

This reverts commit c92d590e4d23795504db9afb62eb45adb43d5308.

* alternative fix
2022-08-13 17:20:43 -07:00
Emmett Lalish 591b9e648d added east and west skipping to the triangulator (#182)
added east and west skipping
2022-08-07 21:48:04 -07:00
Emmett Lalish ae19d081f4 fixed triangulation bug (#180) 2022-08-07 12:07:09 -07:00
Emmett Lalish a08a9e7e51 Fixed duplicate edge handling (#179)
* better manifold checks

* fixed duplicate edge handling
2022-08-06 09:26:42 -07:00
Emmett Lalish bf2a9a84c1 Revert "use std if possible" (#175)
* Revert "use std if possible"

This reverts commit 1e1fcf2dad.

* cleanup
2022-08-04 10:57:50 -07:00
Emmett Lalish c0c00345e0 Merge pull request #170 from elalish/noExceptions
Make exceptions optional
2022-08-02 22:13:32 -07:00
Emmett Lalish e307662691 fix NAN test 2022-08-02 12:07:41 -07:00
Emmett Lalish 6fd20df42e added properties tests 2022-08-02 11:29:35 -07:00
Emmett Lalish b0a821efc0 added invalid input tests 2022-08-02 10:36:48 -07:00
Emmett Lalish f8a47865ec removed input assertions 2022-07-31 11:54:34 -07:00
Emmett Lalish 17fe0f7b7e finished statuses 2022-07-31 11:40:20 -07:00
Emmett Lalish 0030964eb8 status working 2022-07-31 11:03:10 -07:00
Emmett Lalish 3003160b9e starting an error status API 2022-07-30 13:16:11 -07:00
Emmett Lalish 5cd70ff695 tweak test precision 2022-07-30 10:25:04 -07:00
Emmett Lalish ce5d305491 string include 2022-07-30 10:13:32 -07:00
Emmett Lalish 986dbd529d fixed ASSERT problem 2022-07-30 10:03:02 -07:00
Emmett Lalish 449916bf70 pull assert macro out of public API 2022-07-28 12:28:25 -07:00
Emmett Lalish bdd9214aea forgot include 2022-07-28 11:54:03 -07:00
Emmett Lalish 7430e43f60 Merge branch 'master' of github.com:elalish/manifold into noExceptions 2022-07-28 11:40:45 -07:00
Emmett Lalish e3a791a44d cleanup 2022-07-28 11:40:39 -07:00
Emmett Lalish d7ecdd5fab exceptions optional 2022-07-28 11:00:13 -07:00
Emmett Lalish 2033bbf4d0 Merge pull request #166 from elalish/xDocker
remove docker image
2022-07-26 12:50:44 -07:00
Emmett Lalish 882468a0de remove docker image 2022-07-26 11:42:24 -07:00
Emmett Lalish a1382f247e Merge pull request #165 from elalish/cyclebug
fixed seg fault
2022-07-26 11:07:59 -07:00
Emmett Lalish e441e2e80b tighten Close test 2022-07-26 10:37:18 -07:00
Emmett Lalish b51b0cb0af fixed seg fault 2022-07-26 10:19:05 -07:00
Emmett Lalish f7dec8c653 Merge pull request #164 from elalish/wasmUpload2
try a different download action
2022-07-24 22:43:18 -07:00
Emmett Lalish 2db8c716ee try a different download action 2022-07-24 22:27:17 -07:00
Emmett Lalish 541a99d290 Merge pull request #163 from elalish/wasmUpload
deploy WASM
2022-07-24 21:57:29 -07:00
Emmett Lalish 0d9f4dadfb cleanup 2022-07-23 23:09:36 -07:00
Emmett Lalish feedcd82d4 deploy WASM 2022-07-23 23:04:35 -07:00
Emmett Lalish e9fc4018d5 Merge pull request #160 from elalish/exceptions
Make triangulator exceptions optional
2022-07-21 13:07:28 -07:00
Emmett Lalish 70e911e2f2 add OVERLAP_ASSERT 2022-07-21 12:13:55 -07:00
Emmett Lalish ca6daf0ecc refactored asserts 2022-07-21 11:01:32 -07:00
Emmett Lalish debe6b2c7f Merge pull request #149 from elalish/copyright
Copyright updates
2022-07-20 15:15:57 -07:00
Emmett Lalish a7584bc7d3 updated for new files 2022-07-20 15:01:03 -07:00
Emmett Lalish 4add644d76 merging master 2022-07-20 14:54:45 -07:00
Emmett Lalish 45d217b37a Merge pull request #157 from elalish/organize2
Reorganize Directories
2022-07-19 22:44:58 -07:00
Emmett Lalish 311ca75a21 actions format check 2022-07-19 22:31:08 -07:00
Emmett Lalish dd889d2b77 more path fixes 2022-07-19 22:08:53 -07:00
Emmett Lalish 63164bed95 fixing paths 2022-07-19 22:03:11 -07:00
Emmett Lalish 6cafc06970 updated diff path 2022-07-19 21:49:03 -07:00
Emmett Lalish 372f5170ec formatted bindings 2022-07-19 21:47:50 -07:00
Emmett Lalish 2098bc9d1f fix workflows 2022-07-19 21:40:36 -07:00
Emmett Lalish d404b26d7a examples adjustments 2022-07-19 21:28:38 -07:00
Emmett Lalish 5b182a243c fix build 2022-07-19 21:17:09 -07:00
Emmett Lalish 92bb83c9fd fixed some CMake stuff 2022-07-18 22:25:59 -07:00
Emmett Lalish 3b39bf2377 new directory structure 2022-07-18 22:01:03 -07:00
Emmett Lalish a149732fc7 Merge pull request #155 from makc/wasm
wasm
2022-07-17 22:26:39 -07:00
Emmett Lalish fc79b076f3 Merge branch 'master' into wasm 2022-07-17 22:09:41 -07:00
Emmett Lalish cea3d22e09 Merge pull request #154 from elalish/triangulatorPrecision
Triangulator robustness
2022-07-17 22:09:13 -07:00
pca006132 97bef24f2b fix build_nix (js) 2022-07-18 05:38:20 +02:00
makc b7a4502866 flat shading in 3js demo
https://user-images.githubusercontent.com/242577/179437300-2658856f-a105-4cdd-9a7c-be3535c26070.png
2022-07-18 04:36:02 +02:00
makc d23e5512f0 wasm bindings 2022-07-18 03:58:11 +02:00
Emmett Lalish e63e9b32e7 Merge branch 'triangulatorPrecision' of github.com:elalish/manifold into triangulatorPrecision 2022-07-17 12:14:00 -07:00
Emmett Lalish 1a7b603420 added docs 2022-07-17 12:13:45 -07:00
Emmett Lalish eceb966804 Merge branch 'master' into triangulatorPrecision 2022-07-17 11:55:50 -07:00
Emmett Lalish a6f714b2e6 fixed degenerate polygon triangulation and added tests 2022-07-17 11:38:25 -07:00
Emmett Lalish 35ffc7a704 fixed meshRelation in Dedupe 2022-07-17 10:21:18 -07:00
Emmett Lalish fce9adc5ff Merge branch 'master' into copyright 2022-07-17 09:20:19 -07:00
Emmett Lalish 6247784ca4 Merge pull request #153 from pca006132/thrust
misc changes for thrust
2022-07-17 09:20:00 -07:00
pca006132 873d8c6dc8 patch https://github.com/NVIDIA/thrust/issues/1743 2022-07-17 14:41:48 +08:00
pca006132 801b94a982 enable use of host constexpr in cuda 2022-07-17 14:41:48 +08:00
pca006132 1e1fcf2dad use std if possible 2022-07-17 14:41:48 +08:00
Emmett Lalish 15467456c8 merging master 2022-07-16 09:42:02 -07:00
Emmett Lalish bee3d08310 fixed bug, found others 2022-07-15 21:44:25 -07:00
Emmett Lalish 3a17c94262 Merge branch 'master' of github.com:elalish/manifold into copyright 2022-07-15 21:30:03 -07:00
Emmett Lalish 98f87f005e Merge pull request #152 from pca006132/precision
Precision calculation
2022-07-14 22:11:14 -07:00
pca006132 4e98c1adf5 added new precision test 2022-07-14 14:13:19 +08:00
pca006132 431656d676 change precision calculation 2022-07-14 14:12:58 +08:00
Emmett Lalish f6cf8f87dd Merge pull request #150 from pca006132/opt
use radix sort instead of merge sort
2022-07-13 08:19:21 -07:00
pca006132 d12d090b2b fix EM_CACHE 2022-07-12 20:39:27 +08:00
pca006132 efecc846ef update nix flake
use 22.05 and minimized cuda dependency
2022-07-12 20:12:42 +08:00
pca006132 f9a563bde5 formatting changes 2022-07-12 20:12:30 +08:00
Emmett Lalish b953396afc updated README 2022-07-11 10:49:58 -07:00
pca006132 c1998773f1 tiny optimization 2022-07-09 17:13:19 +08:00
Emmett Lalish c40adb1eff missed a few 2022-07-06 12:46:13 -07:00
Emmett Lalish 738d0fa050 switch copyright to authors 2022-07-06 12:42:00 -07:00
Emmett Lalish 9868a0e0c0 Merge pull request #147 from elalish/precisionTest
Adding a test
2022-07-05 10:30:59 -07:00
Emmett Lalish 389f4ba8f9 fix seg fault 2022-07-05 10:08:16 -07:00
Emmett Lalish 08a59fdca0 added test 2022-07-05 09:55:20 -07:00
Emmett Lalish aa5badd1ee Merge pull request #142 from elalish/coverage
disable branch coverage
2022-06-20 17:48:26 -07:00
Emmett Lalish c13ad5657e speed up CI 2022-06-20 17:31:51 -07:00
Emmett Lalish 3e5ab8eb0c revert cmake change 2022-06-20 17:02:03 -07:00
Emmett Lalish 870b94e172 fixed coverage 2022-06-20 16:12:03 -07:00
Emmett Lalish 3da4dfbde0 remove third_party coverage 2022-06-19 23:15:16 -07:00
Emmett Lalish 14e0dcbd89 disable branch coverage 2022-06-19 22:27:46 -07:00
Emmett Lalish 65b6057d2d Merge pull request #140 from pca006132/coverage
build script update
2022-06-19 10:23:19 -07:00
pca006132 8bb7aab137 only run code coverage for cpp backend 2022-06-18 18:12:50 +08:00
pca006132 02ae191ade actually running codecov 2022-06-18 16:49:50 +08:00
pca006132 efe2665352 try coverage info 2022-06-18 16:42:18 +08:00
pca006132 fa5e1fe5db build script update
Simplified build script by exposing compiler options as public in
utilities, so we don't have to repeat them in collider/manifold.
Use file glob instead of adding source files one by one, so we don't
have to change the cmake script to add more source files later.
2022-06-18 16:17:13 +08:00
Emmett Lalish 999a035b69 Merge pull request #131 from pca006132/lazy-boolean
Lazy boolean
2022-06-12 21:49:08 -07:00
pca006132 819739825b cleanup 2022-06-12 21:07:45 +08:00
pca006132 90a4b2205e fixed compilation errors 2022-06-11 23:45:10 +08:00
pca006132 e115f5e519 fix cmake error 2022-06-11 22:23:32 +08:00
pca006132 ecd93294af csg_tree: disabled reordering 2022-06-11 22:02:05 +08:00
pca006132 ecb6b97d38 added license to csg_tree.cpp 2022-06-06 23:37:46 +08:00
pca006132 c07f2b44a5 Merge remote-tracking branch 'upstream/master' into lazy-boolean 2022-06-06 07:30:18 +08:00
Emmett Lalish ed272b05b0 Merge pull request #137 from elalish/infiniteLoop
Fixed edge collapse
2022-06-05 13:21:17 -07:00
Emmett Lalish 7023d2b1b7 revert early attempt 2022-06-05 13:03:09 -07:00
Emmett Lalish 1da7f6a75b added another test 2022-06-05 12:36:34 -07:00
Emmett Lalish b7a5d532e2 Added DedupeEdge 2022-06-05 12:20:25 -07:00
Emmett Lalish b567441192 merging master 2022-06-05 09:26:29 -07:00
pca006132 3baf39cb24 faster clang-format check
so it becomes not noticeable when running in precommit hook
2022-06-05 22:30:23 +08:00
pca006132 5bf95d6402 renamed SetAsOriginal to AsOriginal 2022-06-05 22:25:34 +08:00
pca006132 b36d08b3ca flatten the tree on the fly if possible 2022-06-05 22:17:42 +08:00
pca006132 2b601ea831 fixed perf_test error 2022-06-05 22:05:18 +08:00
pca006132 573adb62d5 fixed precision bug for compose/decompose 2022-06-05 22:05:18 +08:00
pca006132 16b11838f2 removed ApplyTransform 2022-06-05 22:05:18 +08:00
pca006132 c7de23412c disabled CI for draft PRs 2022-06-05 22:05:18 +08:00
pca006132 9c55fb932e test updates 2022-06-05 22:05:18 +08:00
pca006132 817e34b187 every manifold operation now returns a new manifold object 2022-06-05 21:59:37 +08:00
pca006132 a40fe42023 lazy boolean implementation 2022-06-05 21:57:26 +08:00
pca006132 4a21b58aa0 moved transform_ from Impl to Manifold
This allows lazy-boolean to reuse Imp for meshes with different
transformations.
2022-06-05 21:56:08 +08:00
Emmett Lalish cfae8694d2 Merge pull request #132 from pca006132/clang-format
clang-format
2022-06-04 21:58:47 -07:00
pca006132 0601a0e93d added clang-format CI check 2022-06-04 23:35:00 +08:00
pca006132 97b0b9f7d0 applied clang-format 2022-06-04 23:25:50 +08:00
pca006132 fd250cde87 added .clang-format 2022-06-04 23:25:39 +08:00
Emmett Lalish d76f0ae3da Merge pull request #113 from pca006132/pybind11
python binding
2022-06-03 08:48:08 -07:00
pca006132 0fc2151806 CI: use libpython3-dev as python3 dependency 2022-06-03 19:23:41 +08:00
pca006132 0292ed5c32 Merge remote-tracking branch 'upstream/master' into pybind11 2022-06-03 15:35:09 +08:00
Emmett Lalish 359774115a removed CreateAndFixHalfedges 2022-06-02 22:38:19 -07:00
Emmett Lalish d1e71eb076 Merge pull request #126 from pca006132/dynamic-backend
dynamic backend implementation
2022-06-02 22:20:08 -07:00
Emmett Lalish 25c45cf99e Merge branch 'master' of github.com:elalish/manifold into infiniteLoop 2022-06-01 22:28:02 -07:00
Emmett Lalish 6f89c5974c updated cylinder test 2022-06-01 22:26:33 -07:00
pca006132 452cca961a relaxed NumDegenerateTris for Boolean.Gyroid 2022-06-02 12:15:40 +08:00
pca006132 be6dd1805b clean up dynamic-backend 2022-06-02 12:15:06 +08:00
pca006132 acb3cfe675 removed use of __builtin_expect and __builtin_unreachable
as MSVC does not support them and we don't really need to use these.
2022-06-01 20:11:18 +08:00
pca006132 fcbb364b43 fix Windows CI 2022-06-01 14:29:54 +08:00
pca006132 d4ec0485c2 CI updates 2022-06-01 14:01:21 +08:00
pca006132 a5d8b0c74f parallel backend settings 2022-06-01 13:54:13 +08:00
pca006132 b1862224e8 Merge remote-tracking branch 'upstream/master' into dynamic-backend 2022-06-01 11:06:27 +08:00
Emmett Lalish 5d25c9c8c5 Merge pull request #125 from pca006132/thread-safety
Thread safety
2022-05-31 09:31:51 -07:00
pca006132 bc7f8dc992 build tbb on CI 2022-05-31 15:29:42 +08:00
pca006132 7d19d389fe fixed todo 2022-05-31 15:20:47 +08:00
pca006132 93135d94fc documentation update 2022-05-31 15:19:20 +08:00
pca006132 1d7f22dc57 added UpdateMeshIDs method to Manifold::Impl 2022-05-31 15:15:09 +08:00
pca006132 c6f4d9d181 cmake fix 2022-05-30 21:23:45 +08:00
pca006132 dd931fcb72 dynamic backend implementation 2022-05-30 20:31:03 +08:00
pca006132 5ef76f684c removed originalID from public API
... at the expense of the internal code becoming a bit less intuitive.
The meshID in the internal API is purely internal, usually starting from
0, which is used for differentiating between triangles coming from
different meshes. originalID can map this internal meshID to the
external meshID, and such mapping is automatically done when calling
`GetMeshRelation`, so users don't have to care about this mapping.

As a bonus, we don't need to deal with atomics when manipulating meshIDs
internally: we can use whatever IDs we want as long as the mapping is
preserved and distinct for triangles coming from different meshes.
I also made the meshID modification code run in the device, which should
be faster for larger meshes.
2022-05-28 23:17:28 +08:00
pca006132 d4be350e5c faster ASSIMP build
Disabled most of the importer/exporter formats by default, for faster
development and CI.

Note that you may need to CMakeCache.txt for this to take effect, as the
options might be cached.
2022-05-28 22:26:51 +08:00
pca006132 bd48946ce1 meshID update
Removed static vector for thread safety. Accessing the original mesh ID
can now be done by `originalID[meshID]`, where `originalID` is a field
of `MeshRelation`. Note that `meshID` will not be updated when a
manifold is cloned. Instead, it is only used to identify triangles
coming from different manifolds when doing boolean operations/compose.
2022-05-28 22:23:37 +08:00
pca006132 2c1b52c58e tbb 2022-05-19 09:55:52 +08:00
Emmett Lalish f7f4d09f1a Merge branch 'master' of github.com:elalish/manifold into infiniteLoop 2022-05-16 23:58:55 -07:00
Emmett Lalish a7791d09b6 Merge pull request #124 from elalish/overflow
Fix test overflow
2022-05-16 23:49:32 -07:00
Emmett Lalish 9648d32182 testing 2022-05-16 23:46:08 -07:00
Emmett Lalish 3545a49e0f nits from last PR 2022-05-16 23:30:48 -07:00
Emmett Lalish c33bf99096 Merge branch 'master' of github.com:elalish/manifold into overflow 2022-05-16 23:28:46 -07:00
Emmett Lalish bfc8e5c30f Merge pull request #121 from pca006132/universal-vector
universal vector
2022-05-16 23:27:28 -07:00
pca006132 d229f5f2f6 fixed removed cache 2022-05-17 13:47:00 +08:00
Emmett Lalish 8c73a647cb removed a mesh import 2022-05-16 22:34:44 -07:00
Emmett Lalish 9c32052a37 add conditional export 2022-05-16 22:20:31 -07:00
Emmett Lalish 5f822a6533 fixed underflow 2022-05-16 22:15:15 -07:00
pca006132 2a861984b8 Merge branch 'master' into universal-vector 2022-05-16 15:02:19 +08:00
pca006132 dbbcdbbd49 vec_dh cleanup 2022-05-16 14:34:26 +08:00
Emmett Lalish 8f21bc5810 Merge pull request #122 from elalish/tests
Test updates
2022-05-15 22:43:50 -07:00
Emmett Lalish 56878f9925 more precision space for CUDA 2022-05-15 22:01:31 -07:00
Emmett Lalish b4749542d3 added infinite loop test 2022-05-15 21:57:50 -07:00
Emmett Lalish 4077aced41 disabled determinism test 2022-05-15 21:42:55 -07:00
pca006132 c31683618e universal vector 2022-05-15 20:07:05 +08:00
pca006132 03f670783c python naming convention 2022-05-13 23:03:44 +08:00
pca006132 3856fc6aba fix and added new function bindings 2022-05-13 22:33:45 +08:00
pca006132 6db6a19e6c python binding: default parameters 2022-05-13 01:07:25 +08:00
pca006132 8eb63ee39c github action: added python3 dependency 2022-05-13 01:04:53 +08:00
pca006132 90dc3547f6 nix flakes test 2022-05-13 01:04:53 +08:00
pca006132 5b940b8229 added python binding tests
the model outputs are not yet tested
2022-05-13 01:04:53 +08:00
pca006132 fb0ec3493d added python binding 2022-05-13 00:44:46 +08:00
Emmett Lalish fadad776b9 Merge pull request #111 from pca006132/windows
windows compatibility
2022-05-12 09:26:57 -07:00
pca006132 ddf70afcc1 perhaps github action OOMed? 2022-05-12 23:45:51 +08:00
pca006132 c3688de36e disable windows CUDA build 2022-05-12 23:06:47 +08:00
pca006132 5076f4009c fix DISABLED_DETERMINISM 2022-05-12 23:06:17 +08:00
pca006132 c4a36d0d57 try nix flakes 2022-05-12 22:45:48 +08:00
pca006132 b43e8c2e17 try fixing windows build 2022-05-12 22:45:48 +08:00
pca006132 17c4dc44c9 try windows build again 2022-05-12 00:02:51 +08:00
pca006132 3443ded1de msvc compatibility
It seems that msvc does not like alternatife operators and getopt.
2022-05-11 23:59:56 +08:00
Emmett Lalish 48d51b62b4 Merge pull request #106 from pca006132/optimizations
Optimizations
2022-05-10 21:40:43 -07:00
pca006132 25e7bf77f3 reduce code duplication 2022-05-11 00:13:41 +08:00
pca006132 f940fe9471 collider: resize faster 2022-05-11 00:08:46 +08:00
Emmett Lalish c5c533510d Merge branch 'master' into optimizations 2022-05-10 08:35:00 -07:00
Emmett Lalish 0c22c562a6 Merge pull request #108 from elalish/gtest
updated submodule
2022-05-10 08:33:18 -07:00
pca006132 42b46f0d08 fixed sprase indices resize 2022-05-10 23:05:21 +08:00
pca006132 02ce2b49d4 collider: reduced initial max size 2022-05-10 22:56:38 +08:00
pca006132 1f1b608da2 avoid using std::map for 3/4 edge cases 2022-05-10 22:56:38 +08:00
Emmett Lalish ce963d3748 updated submodule 2022-05-09 22:38:33 -07:00
pca006132 a39f4fd8aa use std::move to avoid copying 2022-05-10 00:36:59 +08:00
Emmett Lalish 88ed9d406c Merge pull request #103 from pca006132/emscripten
build script refactoring
2022-05-09 08:40:08 -07:00
pca006132 20c83d03f2 comment out assertion in CUDA code 2022-05-09 13:57:25 +08:00
pca006132 5d7f29c836 disabled windows CI for now 2022-05-08 21:56:13 +08:00
pca006132 798aeb806a try fix windows 2022-05-08 21:56:13 +08:00
pca006132 e16ef19ce4 fixed warnings
For boolean3.cpp, I am not sure if those `shadow` variables should be
initialized to 0.
2022-05-08 20:45:13 +08:00
pca006132 14d6a6d917 updated github actions 2022-05-08 20:45:13 +08:00
pca006132 5471fc19aa build script updates 2022-05-08 15:49:51 +08:00
pca006132 4d84ebf172 fixed build failure 2022-05-08 14:53:14 +08:00
pca006132 e8010cac2d update action
use single thread build to reduce memory consumption for CI
2022-05-07 16:54:29 +08:00
pca006132 09f4105007 updated GitHub action 2022-05-07 15:40:18 +08:00
pca006132 8929136f30 build script cleanup 2022-05-07 15:40:03 +08:00
pca006132 d210dba8eb moved google_test to submodule 2022-05-07 15:03:56 +08:00
pca006132 97aa92940f disabled determinism test for emscripten 2022-05-05 17:13:16 +08:00
pca006132 58432afcb7 github action 2022-05-05 17:13:06 +08:00
pca006132 76d07120b1 refactored for emscripten 2022-05-05 17:12:55 +08:00
Emmett Lalish ec8d3d14a3 Merge pull request #97 from elalish/apollo
apollo model
2022-04-24 22:08:24 -07:00
Emmett Lalish 4708cc8744 another poster 2022-04-24 21:51:40 -07:00
Emmett Lalish 4cd06cae1e apollo model 2022-04-24 21:32:36 -07:00
Emmett Lalish 0453418371 Merge pull request #93 from fire/godot-patches
Godot patches
2022-04-21 09:48:22 -07:00
K. S. Ernest (iFire) Lee c8e5b28651 Remainder Graphlite changes for use in Godot Engine. 2022-04-21 09:25:15 -07:00
Emmett Lalish fd72eb9f12 Merge pull request #92 from elalish/graphlite
Replace boost::graph with graphlite
2022-04-20 23:56:30 -07:00
Emmett Lalish 68f9f14cf9 added graphlite commit hash 2022-04-20 23:43:19 -07:00
Emmett Lalish 8e17e6c8ba added header 2022-04-20 23:24:23 -07:00
Emmett Lalish 5b55be9b5e removed boost 2022-04-20 23:11:04 -07:00
Emmett Lalish 5ff0a177e8 simplified connected components 2022-04-20 22:50:37 -07:00
Emmett Lalish dc1d849e32 Merge branch 'master' of github.com:elalish/manifold into graphlite 2022-04-20 07:45:59 -07:00
Emmett Lalish 138801688c Merge pull request #94 from elalish/cpp17
updated cu internals to c++17
2022-04-20 07:44:59 -07:00
Emmett Lalish 390560ea5c try a different action 2022-04-19 23:35:24 -07:00
Emmett Lalish 25964648bc try an action 2022-04-19 23:31:33 -07:00
Emmett Lalish 569149f248 try some more 2022-04-19 23:25:35 -07:00
Emmett Lalish 07e3acc522 try again 2022-04-19 23:18:14 -07:00
Emmett Lalish bf76b21190 rearrange 2022-04-19 23:13:18 -07:00
Emmett Lalish e04cc9ac01 add pubkey 2022-04-19 23:02:20 -07:00
Emmett Lalish e05f6ffbf3 fix sudo 2022-04-19 22:57:51 -07:00
Emmett Lalish 44321662a4 update cmake version 2022-04-19 22:49:27 -07:00
Emmett Lalish a5c90055dc updated CI CUDA version 2022-04-19 22:30:16 -07:00
Emmett Lalish 33a2d5a607 updated cu internals to c++17 2022-04-19 22:21:34 -07:00
Emmett Lalish 92bb669888 Merge pull request #88 from yetigit/vs
windows compatibility support
2022-04-19 08:48:12 -07:00
Emmett Lalish f09390a10d Merge branch 'master' into vs 2022-04-19 08:35:50 -07:00
Emmett Lalish 3ff256241a minor update 2022-04-17 23:17:30 -07:00
Emmett Lalish aebba6ab17 added graphlite 2022-04-17 22:51:53 -07:00
yetigit b65b445f43 reset this file to its original state (no path problems anymore) 2022-04-14 13:22:44 +02:00
Emmett Lalish 4dc825761a Merge pull request #90 from elalish/scallop2
command-line arguments for tests
2022-03-21 22:27:30 -07:00
Emmett Lalish d257f848ef command-line arguments for tests 2022-03-21 22:08:21 -07:00
Emmett Lalish 2de7344fb7 Merge pull request #89 from elalish/scallop
updated and deployed scallop models
2022-03-20 00:01:34 -07:00
Emmett Lalish c0f92acdf8 cleanup 2022-03-19 23:45:30 -07:00
Emmett Lalish 88dd84890b add sample export flag to tests 2022-03-19 23:21:49 -07:00
Emmett Lalish f55be52d93 updated and deployed scallop models 2022-03-19 23:15:47 -07:00
yetigit 3dc6c692c7 windows compatibility support 2022-03-16 19:48:43 +01:00
Emmett Lalish e3b9dd2402 Merge pull request #84 from ochafik/perf_test_cgal
Add CGAL corefinement perf test
2022-03-07 23:20:37 -08:00
Olivier Chafik 0588106ed1 Added missing bits to compile perfTestCGAL 2022-03-08 01:35:23 +00:00
Olivier Chafik 1a27e56fd4 Add CGAL corefinement perf test
In C++ monothreaded mode and still on that limited microbenchmark, Manifold union seems 2.3x faster than corefinement for big meshes, but otherwise corefinement still has an edge on smaller meshes and on differences.
2022-03-08 00:54:56 +00:00
Emmett Lalish 3e92f47c89 Merge pull request #83 from elalish/samplesDocs
Lots more docs
2022-03-06 22:05:31 -08:00
Emmett Lalish eb3776343e update README 2022-03-06 21:20:08 -08:00
Emmett Lalish 7c3ee99161 forgot a few details 2022-03-06 17:11:22 -08:00
Emmett Lalish 9d7c31ae68 lots more docs 2022-03-06 16:55:37 -08:00
Emmett Lalish f1fad2f60d Merge pull request #82 from elalish/docsUpdate
added Manifold class docs
2022-03-06 12:54:27 -08:00
Emmett Lalish 474073f58a Merge branch 'master' into docsUpdate 2022-03-05 23:37:18 -08:00
Emmett Lalish 24de66974b Merge pull request #80 from virtualritz/patch-1
Add clarification of face arity
2022-03-05 23:37:02 -08:00
Emmett Lalish 849c101356 Merge branch 'master' into patch-1 2022-03-05 23:24:50 -08:00
Emmett Lalish 7e1a09638b added Manifold class docs 2022-03-05 23:24:12 -08:00
Emmett Lalish 68471278d7 Merge pull request #81 from elalish/improveIO
Improve I/O
2022-03-05 13:52:23 -08:00
Emmett Lalish 2166fbbde8 added docs 2022-03-05 13:37:59 -08:00
Emmett Lalish cc54247510 added perf posters 2022-03-05 13:08:31 -08:00
Emmett Lalish 447bb8e208 manifold throws for non-manifold input 2022-03-05 13:06:59 -08:00
Moritz Mœller e0f9daeb29 Update README.md 2022-03-02 19:17:51 +01:00
Moritz Mœller c112f4ede8 Add clarification of face arity. 2022-03-02 18:58:16 +01:00
Emmett Lalish 9c84e57d24 Merge pull request #72 from elalish/moreDocs
More docs
2022-02-27 16:46:21 -08:00
Emmett Lalish bb20b8254a added posters and box docs 2022-02-27 16:23:45 -08:00
Emmett Lalish 826162068f organizing 2022-02-26 11:54:37 -08:00
Emmett Lalish a3ad27f3d8 Merge pull request #71 from elalish/docsUpdates
Docs updates
2022-02-22 23:44:56 -08:00
Emmett Lalish 6ebdc756ef added models 2022-02-22 23:29:35 -08:00
Emmett Lalish 656bd3b9d9 link to doxygen 2022-02-22 22:26:02 -08:00
Emmett Lalish ac87d42eba Merge pull request #70 from elalish/deployFix4
switch deploy order
2022-02-21 23:51:47 -08:00
Emmett Lalish db4688d22f switch order 2022-02-21 23:37:54 -08:00
Emmett Lalish 2ac674540d Merge pull request #69 from elalish/deployFix3
deploy directory permissions
2022-02-21 23:29:58 -08:00
Emmett Lalish f6620c592c directory permissions 2022-02-21 23:16:54 -08:00
Emmett Lalish 3348cdec92 Merge pull request #68 from elalish/deployFix2
deploy to docs folder
2022-02-21 23:10:45 -08:00
Emmett Lalish 36a16ba051 deploy to docs folder 2022-02-21 22:58:33 -08:00
Emmett Lalish eeaca5c17e Merge pull request #67 from elalish/deployFix
Deploy fix
2022-02-21 22:41:46 -08:00
Emmett Lalish 7080173f30 fixed root path 2022-02-21 22:27:07 -08:00
Emmett Lalish bab5282969 make executable 2022-02-21 22:21:26 -08:00
Emmett Lalish a4b5d7a389 Merge pull request #66 from elalish/doxyFix
Fix Doxygen
2022-02-21 22:13:29 -08:00
Emmett Lalish d3367d7a24 relative paths 2022-02-21 21:59:37 -08:00
Emmett Lalish 588fc12d7c Merge pull request #65 from elalish/doxygen
Add Doxygen
2022-02-21 21:47:28 -08:00
Emmett Lalish fe8871122f fix comment placement 2022-02-21 21:32:50 -08:00
Emmett Lalish 3a1877e69f added deploy action 2022-02-21 21:24:04 -08:00
Emmett Lalish 50a27f1186 better grouping 2022-02-21 15:40:17 -08:00
Emmett Lalish 3641b07806 more modules 2022-02-21 14:44:15 -08:00
Emmett Lalish 8a1ca5e389 reverted extra namespaces in favor or doxy modules 2022-02-21 14:23:11 -08:00
Emmett Lalish f2731443cd better struct hierarchy 2022-02-21 12:17:18 -08:00
Emmett Lalish 7db12dc944 added internal namespace 2022-02-21 11:31:08 -08:00
Emmett Lalish 52ed27a876 added Doxyfile 2022-02-21 10:30:32 -08:00
Emmett Lalish 7275f15456 Merge pull request #64 from elalish/pages
adding sponge GLBs
2022-02-20 21:37:10 -08:00
Emmett Lalish 5c3e6028de adding sponge GLBs 2022-02-20 21:19:53 -08:00
158 changed files with 17114 additions and 45945 deletions
+1
View File
@@ -0,0 +1 @@
BasedOnStyle: Google
+8
View File
@@ -0,0 +1,8 @@
coverage:
status:
project:
default:
informational: true
patch:
default:
informational: true
-27
View File
@@ -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"
}
-24
View File
@@ -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
+21
View File
@@ -0,0 +1,21 @@
name: Check code formatting
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
check_format:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.14
with:
source: '.'
exclude: '*/third_party'
extensions: 'h,cpp'
clangFormatVersion: 12
+40
View File
@@ -0,0 +1,40 @@
name: Deploy documentation
on:
push:
branches:
- master
jobs:
deploy_github_pages:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Doxygen Action
uses: mattnotmitt/doxygen-action@v1
- name: Move deployables
run: |
mkdir public
mv ./samples ./public
mv ./docs ./public
mv ./bindings/wasm/examples/* ./public
- name: Download WASM files
uses: dawidd6/action-download-artifact@v2
with:
workflow: manifold.yml
pr: ${{github.event.pull_request.number}}
name: wasm
path: ./public
- name: Deploy to Github Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
force_orphan: true
cname: manifoldcad.org
publish_dir: ./public
+171 -21
View File
@@ -10,35 +10,185 @@ on:
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
strategy:
matrix:
cuda_support: [ON, OFF]
parallel_backend: [NONE, OMP, TBB]
runs-on: ubuntu-20.04
if: github.event.pull_request.draft == false
container:
image: docker://nvidia/cuda:11.1-devel-ubuntu20.04
# Steps represent a sequence of tasks that will be executed as part of the job
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Install dependencies
run: |
apt-get -y update
DEBIAN_FRONTEND=noninteractive apt install -y cmake libglm-dev libassimp-dev libboost-graph-dev
- name: Build CUDA
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
pip install trimesh
- uses: actions/checkout@v3
with:
submodules: true
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build ${{matrix.backend}}
run: |
mkdir buildCUDA
cd buildCUDA
cmake -DCMAKE_BUILD_TYPE=Release .. && make
- name: Build CPP
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
# note that the test for CUDA backend does not really test CUDA, as we
# don't have CUDA GPU on GitHub Action
run: |
mkdir buildCPP
cd buildCPP
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_USE_CPP=ON .. && make
- name: Test CPP
run: |
cd buildCPP/test
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
cd build/test
./manifold_test
cd ../../
python3 bindings/python/examples/run_all.py
- name: Coverage Report
# only do code coverage for default sequential backend, it seems that TBB
# backend will cause failure
# perhaps issue related to invalid memory access?
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
run: |
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
cd test
./manifold_test
cd ../
lcov --capture --directory . --output-file ./code_coverage_test.info
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
lcov --remove ./code_coverage_total.info '/usr/*' '*/third_party/*' '*/test/*' '*/extras/*' '*/bindings/*' --output-file ./code_coverage.info
- uses: codecov/codecov-action@v2
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
with:
files: build/code_coverage.info
fail_ci_if_error: false
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
build_wasm:
runs-on: ubuntu-20.04
if: github.event.pull_request.draft == false
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
- uses: actions/checkout@v3
with:
submodules: true
- name: Setup WASM
run: |
# setup emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build WASM
run: |
source ./emsdk/emsdk_env.sh
git apply thrust.diff
mkdir build
cd build
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
- name: Test WASM
run: |
cd build/test
node ./manifold_test.js
cd ../bindings/wasm
npm install
npm test
- name: Upload WASM files
uses: actions/upload-artifact@v3
with:
name: wasm
path: |
build/bindings/wasm/manifold.js
build/bindings/wasm/manifold.wasm
build/bindings/wasm/manifold.d.ts
retention-days: 7
build_windows:
strategy:
matrix:
parallel_backend: [NONE]
cuda_support: [OFF]
max-parallel: 1
runs-on: windows-2019
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
with:
submodules: true
- uses: jwlawson/actions-setup-cmake@v1.12
- uses: ilammy/msvc-dev-cmd@v1
- name: Install CUDA
if: matrix.backend == 'CUDA'
env:
cuda: "11.3.0"
visual_studio: "Visual Studio 16 2019"
shell: powershell
run: |
Invoke-RestMethod https://raw.githubusercontent.com/ptheywood/cuda-cmake-github-actions/21811d232fc775fa8d44dddf7f8c5ce2cf8d2b81/scripts/actions/install_cuda_windows.ps1 -OutFile install_cuda_windows.ps1
.\install_cuda_windows.ps1
- name: nvcc check
if: matrix.backend == 'CUDA'
shell: powershell
run: |
nvcc -V
ls $env:CUDA_PATH
ls $env:CUDA_PATH\bin
ls $env:CUDA_PATH\include
- name: Build ${{matrix.backend}}
shell: powershell
run: |
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
cd build
cmake --build . --target ALL_BUILD --config Release
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
shell: bash
run: |
cd build/test
./Release/manifold_test.exe
build_mac:
strategy:
matrix:
parallel_backend: [NONE, TBB]
runs-on: macos-latest
if: github.event.pull_request.draft == false
steps:
- name: Install OpenMP
if: matrix.parallel_backend == 'OMP'
run: brew install libomp
- name: Install TBB
if: matrix.parallel_backend == 'TBB'
run: brew install tbb
- uses: actions/checkout@v3
with:
submodules: true
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build
run: |
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
- name: Test
run: |
cd build/test
./manifold_test
build_nix:
strategy:
matrix:
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v15
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
+8
View File
@@ -106,3 +106,11 @@ Temporary Items
# End of https://www.gitignore.io/api/osx,c++,cuda,linux,cmake
build
buildWASM
docs/html
node_modules/
__pycache__
.vscode/c_cpp_properties.json
*.sublime-project
*.sublime-workspace
.cache
+13
View File
@@ -0,0 +1,13 @@
[submodule "third_party/thrust"]
path = src/third_party/thrust
url = https://github.com/NVIDIA/thrust
[submodule "third_party/glm"]
path = src/third_party/glm
url = https://github.com/g-truc/glm
[submodule "third_party/google_test"]
path = test/third_party/google_test
url = https://github.com/google/googletest.git
[submodule "third_party/pybind11"]
path = bindings/python/third_party/pybind11
url = https://github.com/pybind/pybind11
branch = stable
-22
View File
@@ -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
}
+119 -96
View File
@@ -1,98 +1,121 @@
{
"files.associations": {
"*.cu": "cpp",
"*.cuh": "cpp",
"*.m": "matlab",
"deque": "cpp",
"vector": "cpp",
"cctype": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"strstream": "cpp",
"*.tcc": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cstdint": "cpp",
"unordered_map": "cpp",
"exception": "cpp",
"slist": "cpp",
"fstream": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"new": "cpp",
"ostream": "cpp",
"numeric": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"csignal": "cpp",
"bitset": "cpp",
"complex": "cpp",
"list": "cpp",
"algorithm": "cpp",
"cfenv": "cpp",
"*.ipp": "cpp",
"string": "cpp",
"forward_list": "cpp",
"unordered_set": "cpp",
"valarray": "cpp",
"__bit_reference": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__functional_base": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__nullptr": "cpp",
"__split_buffer": "cpp",
"__string": "cpp",
"__threading_support": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"bit": "cpp",
"ios": "cpp",
"iterator": "cpp",
"locale": "cpp",
"map": "cpp",
"mutex": "cpp",
"optional": "cpp",
"string_view": "cpp",
"condition_variable": "cpp",
"set": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"variant": "cpp"
},
"C_Cpp.clang_format_style": "google",
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DMANIFOLD_USE_CPP=ON",
// "-DMANIFOLD_USE_OMP=ON"
],
"files.associations": {
"*.cu": "cpp",
"*.cuh": "cpp",
"*.m": "matlab",
"deque": "cpp",
"vector": "cpp",
"cctype": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"strstream": "cpp",
"*.tcc": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cstdint": "cpp",
"unordered_map": "cpp",
"exception": "cpp",
"slist": "cpp",
"fstream": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"new": "cpp",
"ostream": "cpp",
"numeric": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"csignal": "cpp",
"bitset": "cpp",
"complex": "cpp",
"list": "cpp",
"algorithm": "cpp",
"cfenv": "cpp",
"*.ipp": "cpp",
"string": "cpp",
"forward_list": "cpp",
"unordered_set": "cpp",
"valarray": "cpp",
"__bit_reference": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__functional_base": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__nullptr": "cpp",
"__split_buffer": "cpp",
"__string": "cpp",
"__threading_support": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"bit": "cpp",
"ios": "cpp",
"iterator": "cpp",
"locale": "cpp",
"map": "cpp",
"mutex": "cpp",
"optional": "cpp",
"string_view": "cpp",
"condition_variable": "cpp",
"set": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"variant": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"codecvt": "cpp",
"regex": "cpp",
"future": "cpp",
"shared_mutex": "cpp",
"__bits": "cpp",
"compare": "cpp",
"concepts": "cpp",
"queue": "cpp",
"stack": "cpp",
"__hash_table": "cpp"
},
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DBUILD_SHARED_LIBS=ON",
"-DMANIFOLD_EXPORT=OFF",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE",
"-DCODE_COVERAGE=OFF",
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
],
"search.exclude": {
"**/third_party/*": true,
"build*": true,
"docs/*": true
},
"editor.tabSize": 2,
"editor.detectIndentation": false,
}
+9
View File
@@ -0,0 +1,9 @@
# This is the list of Manifold's significant contributors.
#
# This does not necessarily list everyone who has contributed code,
# especially since many employees of one corporation may be contributing.
# To see the full list of contributors, see the revision history in
# source control.
Emmett Lalish <elalish>
Chun Kit LAM <pca006132>
Google LLC
+66 -40
View File
@@ -1,49 +1,75 @@
cmake_minimum_required(VERSION 3.12)
if (NOT CMAKE_CUDA_COMPILER)
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")
endif()
project(manifold LANGUAGES CXX CUDA)
# Copyright 2020 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.18)
project(manifold LANGUAGES CXX)
set(CMAKE_VERBOSE_MAKEFILE ON)
# OPTION(DEBUG_CMAKE_TARGETS "enable debug output for cmake target properties" OFF)
# if(DEBUG_CMAKE_TARGETS)
# set(CMAKE_DEBUG_TARGET_PROPERTIES
# INCLUDE_DIRECTORIES
# )
# endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
OPTION( MANIFOLD_USE_OMP
"Use OpenMP as the Thrust backend instead of CUDA."
OFF
)
OPTION( MANIFOLD_USE_CPP
"Use C++ (single-threaded) as the Thrust backend instead of CUDA."
OFF
)
option(MANIFOLD_EXPORT off)
option(MANIFOLD_DEBUG off)
option(MANIFOLD_USE_CUDA off)
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
set(MAINFOLD_FLAGS -Werror -Wall -Wno-sign-compare)
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda)
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3)
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
if(EMSCRIPTEN)
message("Building for Emscripten")
set(MANIFOLD_FLAGS -fexceptions)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
endif()
IF(MANIFOLD_USE_OMP)
message("------------------------- Using OpenMP instead of CUDA.")
find_package(OpenMP REQUIRED)
set(MANIFOLD_OMP_INCLUDE OpenMP::OpenMP_CXX)
set(MANIFOLD_NVCC_FLAGS ${MANIFOLD_NVCC_FLAGS} -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_OMP -Xcompiler=-fopenmp)
ENDIF(MANIFOLD_USE_OMP)
option(PYBIND11_FINDPYTHON on)
IF(MANIFOLD_USE_CPP)
message("------------------------- Using C++ instead of CUDA.")
set(MANIFOLD_NVCC_FLAGS ${MANIFOLD_NVCC_FLAGS} -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_CPP)
ENDIF(MANIFOLD_USE_CPP)
option(BUILD_TEST_CGAL off)
add_subdirectory(utilities)
add_subdirectory(collider)
add_subdirectory(polygon)
add_subdirectory(manifold)
add_subdirectory(third_party)
add_subdirectory(meshIO)
option(BUILD_SHARED_LIBS off)
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
if(MANIFOLD_USE_CUDA)
enable_language(CUDA)
find_package(CUDA REQUIRED)
# we cannot set THRUST_INC_DIR when building with CUDA, otherwise the
# compiler will not use the system CUDA headers which causes incompatibility
# clear THRUST_INC_DIR, we use the one from nvcc
set(THRUST_INC_DIR "")
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3 -lineinfo)
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda --expt-relaxed-constexpr
"$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>"
"$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>")
endif()
if(NOT MSVC)
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=${WARNING_FLAGS}>")
endif()
if(CODE_COVERAGE AND NOT MSVC)
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
add_link_options("-coverage")
endif()
add_subdirectory(src)
add_subdirectory(samples)
add_subdirectory(test)
add_subdirectory(tools)
add_subdirectory(extras)
add_subdirectory(bindings)
+29
View File
@@ -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/).
-17
View File
@@ -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
+2584
View File
File diff suppressed because it is too large Load Diff
+88 -7
View File
@@ -1,20 +1,101 @@
[![codecov](https://codecov.io/github/elalish/manifold/branch/master/graph/badge.svg?token=IIA8G5HVS7)](https://codecov.io/github/elalish/manifold)
[![twitter](https://img.shields.io/twitter/follow/manifoldcad?style=social&logo=twitter)](https://twitter.com/intent/follow?screen_name=manifoldcad)
## [ManifoldCAD.org](https://manifoldcad.org)
If you like OpenSCAD / OpenJSCAD, you might also like ManifoldCAD - our own solid modelling web app. The operations are not GPU-accelerated, but it's still pretty fast and a good way to test out our Manifold library.
![A metallic Menger sponge](https://elalish.github.io/manifold/samples/models/mengerSponge3.webp "A metallic Menger sponge")
# Manifold
This is a geometry library dedicated to creating and operating on manifold meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
[**High-level Documentation**](https://elalish.blogspot.com/search/label/Manifold) | [**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm) and Python to make this library more portable and easy to use. We have only three dependencies (all built-in header-only libraries): graphlite provides a connected components algorithm, GLM is a compact vector library, and Thrust is Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms). We make use of submodules to ensure compatibility.
## What's here
This library is intended to be fast with guaranteed manifold output. As such you need manifold meshes as input, which can be hard to come by since most 3D graphics meshes are not. This library can create simple primitive meshes but also links in Assimp, which will import many kinds of 3D files, but you'll get an error if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
The most significant contribution here is a guaranteed manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If anyone knows of another, please tell me. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial.
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible, though it could be improved even more with JS or Python bindings to avoid the syntax and compiling of C++.
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
## Building
The canonical build instructions are in the [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml) file, as that is what this project's continuous integration server uses to build and test. I have only built under Ubuntu Linux, and the CI uses Nvidia's Cuda 11 Docker image. Part of my [road map](https://github.com/elalish/manifold/wiki/Manifold-Library#road-map) is to migrate from Thrust to C++20 parallel algorithms, which will alleviate the need to install the Cuda Developer Kit to build.
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
Build and test (Ubuntu or similar):
```
git clone --recurse-submodules https://github.com/elalish/manifold.git
cd manifold
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
test/manifold_test
```
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
- `MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
- `MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
### WASM
To build the JS WASM library, first install NodeJS and set up emscripten:
(on Mac):
```
brew install nodejs
brew install emscripten
```
(on Linux):
```
sudo apt install nodejs
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk/emsdk_env.sh
```
Then build:
```
cd manifold
mkdir buildWASM
cd buildWASM
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
node test/manifold_test.js
```
### Python
The CMake script will build the python binding `pymanifold` automatically. To
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
can be found in `bindings/python/examples`. To see exported samples, run:
```
sudo apt install pkg-config libpython3-dev python3 python3-distutils python3-pip
pip install trimesh
python3 run_all.py -e
```
Run the following code in the interpreter for
python binding documentation:
```
>>> import pymanifold
>>> help(pymanifold)
```
For more detailed documentation, please refer to the C++ API.
## Contributing
@@ -22,4 +103,4 @@ Contributions are welcome! A lower barrier contribution is to simply make a PR t
## About the author
This library is by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee, but this is not a Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
+21
View File
@@ -0,0 +1,21 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if(NOT EMSCRIPTEN)
add_subdirectory(python)
endif()
if(EMSCRIPTEN)
add_subdirectory(wasm)
endif()
+24
View File
@@ -0,0 +1,24 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
project(python)
add_subdirectory(third_party)
pybind11_add_module(pymanifold pymanifold.cpp)
target_link_libraries(pymanifold PRIVATE manifold)
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(pymanifold PUBLIC cxx_std_14)
target_include_directories(pymanifold
PRIVATE ${PYBIND11_DIR}/include
)
+79
View File
@@ -0,0 +1,79 @@
"""
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from pymanifold import Manifold
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
INCHES = 25.4
brick_depth = (3 + 5/8) * INCHES
brick_height = (2 + 1/4) * INCHES
brick_length = (7 + 5/8) * INCHES
mortar_gap = (3/8) * INCHES
def brick():
return Manifold.cube(brick_length, brick_depth, brick_height)
def halfbrick():
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
def row(length):
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
for x in range(length)]
return Manifold(bricks)
def wall(length, height, alternate=0):
bricks = [row(length).translate(
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
(brick_height+mortar_gap)*z) for z in range(height)]
return Manifold(bricks)
def walls(length, width, height):
return Manifold([
wall(length, height),
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
wall(length, height, 1).translate(
0, (width)*(brick_length+mortar_gap), 0),
wall(width, height).rotate(0, 0, 90)
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
])
def floor(length, width):
results = [walls(length, width, 1)]
if length > 1 and width > 1:
results.append(floor(length-1, width-1).translate(brick_depth +
mortar_gap, brick_depth+mortar_gap, 0))
if length == 1 and width > 1:
results.append(row(width-1).rotate(0, 0, 90))
if width == 1 and length > 1:
results.append(row(length-1).translate(2*(brick_depth +
mortar_gap), brick_depth+mortar_gap, 0))
results.append(halfbrick().translate(
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
return Manifold(results)
def run(width=10, length=10, height=10):
return walls(length, width, height) + floor(length, width)
@@ -0,0 +1,30 @@
"""
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from pymanifold import Manifold
from functools import reduce
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
def run(n=5, overlap=True):
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
spheres = [Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
for i in range(n) for j in range(n)]
spheres = reduce(lambda a, b: a + b, spheres)
return a - spheres
+42
View File
@@ -0,0 +1,42 @@
"""
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import pathlib
import sys
import importlib
import trimesh
from time import time
if __name__ == "__main__":
current_file = pathlib.Path(__file__)
current_dir = current_file.parent
files = [f.parts[-1][:-3]
for f in current_dir.glob('*.py') if f != current_file]
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
for f in files:
module = importlib.import_module(f)
t0 = time()
model = module.run()
mesh = model.to_mesh()
if export_models:
meshOut = trimesh.Trimesh(
vertices=mesh.vert_pos, faces=mesh.tri_verts)
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
print(f'Exported model to {f}.glb')
t1 = time()
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
+504
View File
@@ -0,0 +1,504 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "manifold.h"
#include "pybind11/functional.h"
#include "pybind11/numpy.h"
#include "pybind11/operators.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
namespace py = pybind11;
using namespace manifold;
typedef std::tuple<float, float> Float2;
typedef std::tuple<float, float, float> Float3;
struct PolygonsWrapper {
std::unique_ptr<Polygons> polygons;
};
PYBIND11_MODULE(pymanifold, m) {
m.doc() =
"Python binding for the manifold library. Please check the C++ "
"documentation for APIs.\n"
"This binding will perform copying to make the API more familiar to "
"OpenSCAD users.";
py::class_<Manifold>(m, "Manifold")
.def(py::init<>())
.def(py::init([](std::vector<Manifold> &manifolds) {
Manifold result;
for (Manifold &manifold : manifolds) result += manifold;
return result;
}),
"Construct manifold as the union of a set of manifolds.")
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"transform",
[](Manifold self, py::array_t<float> &mat) {
auto mat_view = mat.unchecked<2>();
if (mat_view.shape(0) != 3 || mat_view.shape(1) != 4)
throw std::runtime_error("Invalid matrix shape");
glm::mat4x3 mat_glm;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
mat_glm[j][i] = mat_view(i, j);
}
}
return self.Transform(mat_glm);
},
py::arg("m"),
"Transform this Manifold in space. The first three columns form a "
"3x3 matrix\n"
"transform and the last is a translation vector. This operation can "
"be\n"
"chained. Transforms are combined and applied lazily.\n"
"\n"
":param m: The affine transform matrix to apply to all the vertices.")
.def(
"translate",
[](Manifold self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
return self.Translate(glm::vec3(x, y, z));
},
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
"Move this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param x: X axis translation. (default 0.0).\n"
":param y: Y axis translation. (default 0.0).\n"
":param z: Z axis translation. (default 0.0).")
.def(
"translate",
[](Manifold self, py::array_t<float> &t) {
auto t_view = t.unchecked<1>();
if (t.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
},
py::arg("t"),
"Move this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param v: The vector to add to every vertex.")
.def("scale",
static_cast<Manifold (*)(Manifold, float)>(
[](Manifold self, float scale) {
return self.Scale(glm::vec3(scale));
}),
py::arg("scale"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param scale: The scalar multiplier for each component of every "
"vertices.")
.def(
"scale",
[](Manifold self, py::array_t<float> &scale) {
auto scale_view = scale.unchecked<1>();
if (scale_view.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
return self.Scale(v);
},
py::arg("v"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are\n"
"combined and applied lazily.\n"
"\n"
":param v: The vector to multiply every vertex by component.")
.def(
"rotate",
[](Manifold self, py::array_t<float> &v) {
auto v_view = v.unchecked<1>();
if (v_view.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return self.Rotate(v_view(0), v_view(1), v_view(2));
},
py::arg("v"),
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then\n"
"Y, then Z, in degrees. We use degrees so that we can minimize "
"rounding error,\n"
"and eliminate it completely for any multiples of 90 degrees. "
"Additionally, more\n"
"efficient code paths are used to update the manifold when the "
"transforms only\n"
"rotate by multiples of 90 degrees. This operation can be chained. "
"Transforms\n"
"are combined and applied lazily.\n"
"\n"
":param v: [X, Y, Z] rotation in degrees.")
.def(
"rotate",
[](Manifold self, float xDegrees = 0.0f, float yDegrees = 0.0f,
float zDegrees = 0.0f) {
return self.Rotate(xDegrees, yDegrees, zDegrees);
},
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
py::arg("z_degrees") = 0.0f,
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then\n"
"Y, then Z, in degrees. We use degrees so that we can minimize "
"rounding error,\n"
"and eliminate it completely for any multiples of 90 degrees. "
"Additionally, more\n"
"efficient code paths are used to update the manifold when the "
"transforms only\n"
"rotate by multiples of 90 degrees. This operation can be chained. "
"Transforms\n"
"are combined and applied lazily.\n"
"\n"
":param x: X rotation in degrees. (default 0.0).\n"
":param y: Y rotation in degrees. (default 0.0).\n"
":param z: Z rotation in degrees. (default 0.0).")
.def(
"warp",
[](Manifold self, const std::function<Float3(Float3)> &f) {
return self.Warp([&f](glm::vec3 &v) {
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
v.x = std::get<0>(fv);
v.y = std::get<1>(fv);
v.z = std::get<2>(fv);
});
},
py::arg("f"))
.def(
"refine", [](Manifold self, int n) { return self.Refine(n); },
py::arg("n"),
"Increase the density of the mesh by splitting every edge into n "
"pieces. For\n"
"instance, with n = 2, each triangle will be split into 4 triangles. "
"These\n"
"will all be coplanar (and will not be immediately collapsed) unless "
"the\n"
"Mesh/Manifold has halfedgeTangents specified (e.g. from the "
"Smooth()\n"
"constructor), in which case the new vertices will be moved to the\n"
"interpolated surface according to their barycentric coordinates.\n"
"\n"
":param n: The number of pieces to split every edge into. Must be > "
"1.")
.def("to_mesh", &Manifold::GetMesh)
.def_static("smooth", Manifold::Smooth,
"Constructs a smooth version of the input mesh by creating "
"tangents; this\n"
"method will throw if you have supplied tangents with your "
"mesh already. The\n"
"actual triangle resolution is unchanged; use the Refine() "
"method to\n"
"interpolate to a higher-resolution curve.\n"
"\n"
"By default, every edge is calculated for maximum smoothness "
"(very much\n"
"approximately), attempting to minimize the maximum mean "
"Curvature magnitude.\n"
"No higher-order derivatives are considered, as the "
"interpolation is\n"
"independent per triangle, only sharing constraints on their "
"boundaries.\n"
"\n"
":param mesh: input Mesh.\n"
":param sharpenedEdges: If desired, you can supply a vector "
"of sharpened\n"
"halfedges, which should in general be a small subset of all "
"halfedges. Order\n"
"of entries doesn't matter, as each one specifies the "
"desired smoothness\n"
"(between zero and one, with one the default for all "
"unspecified halfedges)\n"
"and the halfedge index (3 * triangle index + [0,1,2] where "
"0 is the edge\n"
"between triVert 0 and 1, etc).\n"
"\n"
"At a smoothness value of zero, a sharp crease is made. The "
"smoothness is\n"
"interpolated along each edge, so the specified value should "
"be thought of as\n"
"an average. Where exactly two sharpened edges meet at a "
"vertex, their\n"
"tangents are rotated to be colinear so that the sharpened "
"edge can be\n"
"continuous. Vertices with only one sharpened edge are "
"completely smooth,\n"
"allowing sharpened edges to smoothly vanish at termination. "
"A single vertex\n"
"can be sharpened by sharping all edges that are incident on "
"it, allowing\n"
"cones to be formed.")
.def_static(
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
py::arg("mesh"))
.def_static(
"tetrahedron", []() { return Manifold::Tetrahedron(); },
"Constructs a tetrahedron centered at the origin with one vertex at "
"(1,1,1)\n"
"and the rest at similarly symmetric points.")
.def_static(
"cube",
[](Float3 size, bool center = false) {
return Manifold::Cube(
glm::vec3(std::get<0>(size), std::get<1>(size),
std::get<2>(size)),
center);
},
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first\n"
"octant, touching the origin.\n"
"\n"
":param size: The X, Y, and Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cube",
[](py::array_t<float> &size, bool center = false) {
auto size_view = size.unchecked<1>();
if (size_view.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return Manifold::Cube(
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
},
py::arg("size"), py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first\n"
"octant, touching the origin.\n"
"\n"
":param size: The X, Y, and Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cube",
[](float x, float y, float z, bool center = false) {
return Manifold::Cube(glm::vec3(x, y, z), center);
},
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first\n"
"octant, touching the origin.\n"
"\n"
":param x: The X dimensions of the box.\n"
":param y: The Y dimensions of the box.\n"
":param z: The Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cylinder",
[](float height, float radiusLow, float radiusHigh = -1.0f,
int circularSegments = 0) {
return Manifold::Cylinder(height, radiusLow, radiusHigh,
circularSegments);
},
py::arg("height"), py::arg("radius_low"),
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
"A convenience constructor for the common case of extruding a "
"circle. Can also\n"
"form cones if both radii are specified.\n"
"\n"
":param height: Z-extent\n"
":param radiusLow: Radius of bottom circle. Must be positive.\n"
":param radiusHigh: Radius of top circle. Can equal zero. Default "
"(-1) is equal to\n"
"radiusLow.\n"
":param circularSegments: How many line segments to use around the "
"circle.\n"
"Default (-1) is calculated by the static Defaults.\n"
":param center: Set to true to shift the center to the origin. "
"Default is\n"
"origin at the bottom.")
.def_static(
"sphere",
[](float radius, int circularSegments = 0) {
return Manifold::Sphere(radius, circularSegments);
},
py::arg("radius"), py::arg("circular_segments") = 0,
"Constructs a geodesic sphere of a given radius.\n"
"\n"
":param radius: Radius of the sphere. Must be positive.\n"
":param circularSegments: Number of segments along its\n"
"diameter. This number will always be rounded up to the nearest "
"factor of\n"
"four, as this sphere is constructed by refining an octahedron. This "
"means\n"
"there are a circle of vertices on all three of the axis planes. "
"Default is\n"
"calculated by the static Defaults.");
py::class_<PolygonsWrapper>(m, "Polygons")
.def(py::init([](std::vector<std::vector<Float2>> &polygons) {
std::vector<SimplePolygon> simplePolygons(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
std::vector<PolyVert> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = {
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
j};
}
simplePolygons[i] = {vertices};
}
return PolygonsWrapper{std::make_unique<Polygons>(simplePolygons)};
}),
py::arg("polygons"),
"Construct the Polygons object from a list of simple polygon, "
"where each simple polygon is a list of points (pair of floats).")
.def(
"extrude",
[](PolygonsWrapper &self, float height, int nDivisions = 0,
float twistDegrees = 0.0f,
Float2 scaleTop = std::make_tuple(1.0f, 1.0f)) {
glm::vec2 scaleTopVec(std::get<0>(scaleTop), std::get<1>(scaleTop));
return Manifold::Extrude(*self.polygons, height, nDivisions,
twistDegrees, scaleTopVec);
},
py::arg("height"), py::arg("n_divisions") = 0,
py::arg("twist_degrees") = 0.0f,
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
"Constructs a manifold from the set of polygons by extruding them "
"along the\n"
"Z-axis.\n"
"\n"
":param height: Z-extent of extrusion.\n"
":param nDivisions: Number of extra copies of the crossSection to "
"insert into\n"
"the shape vertically; especially useful in combination with "
"twistDegrees to\n"
"avoid interpolation artifacts. Default is none.\n"
":param twistDegrees: Amount to twist the top crossSection relative "
"to the\n"
"bottom, interpolated linearly for the divisions in between.\n"
":param scaleTop: Amount to scale the top (independently in X and "
"Y). If the\n"
"scale is (0, 0), a pure cone is formed with only a single vertex at "
"the top.\n"
"Default (1, 1).")
.def(
"revolve",
[](PolygonsWrapper &self, int circularSegments = 0) {
return Manifold::Revolve(*self.polygons, circularSegments);
},
py::arg("circular_segments") = 0,
"Constructs a manifold from the set of polygons by revolving this "
"cross-section\n"
"around its Y-axis and then setting this as the Z-axis of the "
"resulting\n"
"manifold. If the polygons cross the Y-axis, only the part on the "
"positive X\n"
"side is used. Geometrically valid input will result in "
"geometrically valid\n"
"output.\n"
"\n"
":param circularSegments: Number of segments along its diameter. "
"Default is\n"
"calculated by the static Defaults.");
py::class_<Mesh>(m, "Mesh")
.def(py::init([](py::array_t<float> &vertPos, py::array_t<int> &triVerts,
py::array_t<float> &vertNormal,
py::array_t<float> &halfedgeTangent) {
auto vertPos_view = vertPos.unchecked<2>();
auto triVerts_view = triVerts.unchecked<2>();
auto vertNormal_view = vertNormal.unchecked<2>();
auto halfedgeTangent_view = halfedgeTangent.unchecked<2>();
if (vertPos_view.shape(1) != 3)
throw std::runtime_error("Invalid vert_pos shape");
if (triVerts_view.shape(1) != 3)
throw std::runtime_error("Invalid tri_verts shape");
if (vertNormal_view.shape(0) != 0) {
if (vertNormal_view.shape(1) != 3)
throw std::runtime_error("Invalid vert_normal shape");
if (vertNormal_view.shape(0) != vertPos_view.shape(0))
throw std::runtime_error(
"vert_normal must have the same length as vert_pos");
}
if (halfedgeTangent_view.shape(0) != 0) {
if (halfedgeTangent_view.shape(1) != 4)
throw std::runtime_error("Invalid halfedge_tangent shape");
if (halfedgeTangent_view.shape(0) != triVerts_view.shape(0) * 3)
throw std::runtime_error(
"halfedge_tangent must be three times as long as "
"tri_verts");
}
std::vector<glm::vec3> vertPos_vec(vertPos_view.shape(0));
std::vector<glm::ivec3> triVerts_vec(triVerts_view.shape(0));
std::vector<glm::vec3> vertNormal_vec(vertNormal_view.shape(0));
std::vector<glm::vec4> halfedgeTangent_vec(
halfedgeTangent_view.shape(0));
for (int i = 0; i < vertPos_view.shape(0); i++)
for (const int j : {0, 1, 2})
vertPos_vec[i][j] = vertPos_view(i, j);
for (int i = 0; i < triVerts_view.shape(0); i++)
for (const int j : {0, 1, 2})
triVerts_vec[i][j] = triVerts_view(i, j);
for (int i = 0; i < vertNormal_view.shape(0); i++)
for (const int j : {0, 1, 2})
vertNormal_vec[i][j] = vertNormal_view(i, j);
for (int i = 0; i < halfedgeTangent_view.shape(0); i++)
for (const int j : {0, 1, 2, 3})
halfedgeTangent_vec[i][j] = halfedgeTangent_view(i, j);
return Mesh({vertPos_vec, triVerts_vec, vertNormal_vec,
halfedgeTangent_vec});
}),
py::arg("vert_pos"), py::arg("tri_verts"), py::arg("vert_normal"),
py::arg("halfedge_tangent"))
.def_property_readonly("vert_pos",
[](Mesh &self) {
const int numVert = self.vertPos.size();
py::array_t<float> vert_pos({numVert, 3});
auto vert_pos_view =
vert_pos.mutable_unchecked<2>();
for (int i = 0; i < numVert; ++i)
for (const int j : {0, 1, 2})
vert_pos_view(i, j) = self.vertPos[i][j];
return vert_pos;
})
.def_property_readonly("tri_verts",
[](Mesh &self) {
const int numTri = self.triVerts.size();
py::array_t<int> tri_verts({numTri, 3});
auto tri_verts_view =
tri_verts.mutable_unchecked<2>();
for (int i = 0; i < numTri; ++i)
for (const int j : {0, 1, 2})
tri_verts_view(i, j) = self.triVerts[i][j];
return tri_verts;
})
.def_property_readonly(
"vert_normal",
[](Mesh &self) {
const int numVert = self.vertNormal.size();
py::array_t<float> vert_normal({numVert, 3});
auto vert_normal_view = vert_normal.mutable_unchecked<2>();
for (int i = 0; i < numVert; ++i)
for (const int j : {0, 1, 2})
vert_normal_view(i, j) = self.vertNormal[i][j];
return vert_normal;
})
.def_property_readonly("halfedge_tangent", [](Mesh &self) {
const int numEdge = self.halfedgeTangent.size();
py::array_t<float> halfedge_tangent({numEdge, 4});
auto halfedge_tangent_view = halfedge_tangent.mutable_unchecked<2>();
for (int i = 0; i < numEdge; ++i)
for (const int j : {0, 1, 2, 3})
halfedge_tangent_view(i, j) = self.halfedgeTangent[i][j];
return halfedge_tangent;
});
}
+1
View File
@@ -0,0 +1 @@
add_subdirectory(pybind11)
+38
View File
@@ -0,0 +1,38 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
project(wasm)
add_executable(manifoldjs bindings.cpp)
# make sure that we recompile the wasm when bindings.js is being modified
set_source_files_properties(bindings.cpp OBJECT_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
target_link_libraries(manifoldjs manifold sdf)
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1)
target_compile_features(manifoldjs PUBLIC cxx_std_14)
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS examples;test)
add_custom_command(
TARGET manifoldjs POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_BINARY_DIR}/examples/)
+201
View File
@@ -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.
+27
View File
@@ -0,0 +1,27 @@
[![codecov](https://codecov.io/github/elalish/manifold/branch/master/graph/badge.svg?token=IIA8G5HVS7)](https://codecov.io/github/elalish/manifold)
[![twitter](https://img.shields.io/twitter/follow/manifoldcad?style=social&logo=twitter)](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.
![A metallic Menger sponge](https://elalish.github.io/manifold/samples/models/mengerSponge3.webp "A metallic Menger sponge")
# Manifold
[**High-level Documentation**](https://elalish.blogspot.com/search/label/Manifold) | [**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
## What's here
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
## Note on memory management
Since Manifold is a WASM module, it does not automatically garbage-collect like regular JavaScript. You must manually `delete()` each manifold object constructed by your scripts, see https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287.
## About the author
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <emscripten/bind.h>
#include <emscripten/val.h>
using namespace emscripten;
#include <manifold.h>
#include <sdf.h>
using namespace manifold;
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
Manifold UnionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, Manifold::OpType::ADD);
;
}
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, Manifold::OpType::SUBTRACT);
;
}
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, Manifold::OpType::INTERSECT);
;
}
std::vector<SimplePolygon> ToPolygon(
std::vector<std::vector<glm::vec2>>& polygons) {
std::vector<SimplePolygon> simplePolygons(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
std::vector<PolyVert> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = {polygons[i][j], j};
}
simplePolygons[i] = {vertices};
}
return simplePolygons;
}
val GetMeshJS(const Manifold& manifold) {
MeshGL mesh = manifold.GetMeshGL();
val meshJS = val::object();
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertPos",
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
.call<val>("slice"));
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
mesh.vertNormal.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
return meshJS;
}
MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
if (mesh["vertNormal"] != val::undefined()) {
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
return out;
}
Manifold FromMeshJS(const val& mesh) { return Manifold(MeshJS2GL(mesh)); }
Manifold Smooth(const val& mesh,
const std::vector<Smoothness>& sharpenedEdges = {}) {
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
}
Manifold Extrude(std::vector<std::vector<glm::vec2>>& polygons, float height,
int nDivisions, float twistDegrees, glm::vec2 scaleTop) {
return Manifold::Extrude(ToPolygon(polygons), height, nDivisions,
twistDegrees, scaleTop);
}
Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
int circularSegments) {
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
}
Manifold Transform(Manifold& manifold, std::vector<float>& mat) {
glm::mat4x3 matrix;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
return manifold.Transform(matrix);
}
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
return manifold.Warp(f);
}
Manifold LevelSetJs(uintptr_t funcPtr, Box bounds, float edgeLength,
float level) {
float (*f)(const glm::vec3&) =
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
Mesh m = LevelSet(f, bounds, edgeLength, level);
return Manifold(m);
}
EMSCRIPTEN_BINDINGS(whatever) {
value_object<glm::vec2>("vec2")
.field("x", &glm::vec2::x)
.field("y", &glm::vec2::y);
value_object<glm::ivec3>("ivec3")
.field("0", &glm::ivec3::x)
.field("1", &glm::ivec3::y)
.field("2", &glm::ivec3::z);
value_object<glm::vec3>("vec3")
.field("x", &glm::vec3::x)
.field("y", &glm::vec3::y)
.field("z", &glm::vec3::z);
value_object<glm::vec4>("vec4")
.field("x", &glm::vec4::x)
.field("y", &glm::vec4::y)
.field("z", &glm::vec4::z)
.field("w", &glm::vec4::w);
enum_<Manifold::Error>("status")
.value("NO_ERROR", Manifold::Error::NO_ERROR)
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
.value("PROPERTIES_WRONG_LENGTH",
Manifold::Error::PROPERTIES_WRONG_LENGTH)
.value("TRI_PROPERTIES_WRONG_LENGTH",
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
value_object<Smoothness>("smoothness")
.field("halfedge", &Smoothness::halfedge)
.field("smoothness", &Smoothness::smoothness);
value_object<Properties>("properties")
.field("surfaceArea", &Properties::surfaceArea)
.field("volume", &Properties::volume);
value_object<BaryRef>("baryRef")
.field("meshID", &BaryRef::meshID)
.field("originalID", &BaryRef::originalID)
.field("tri", &BaryRef::tri)
.field("vertBary", &BaryRef::vertBary);
value_object<MeshRelation>("meshRelation")
.field("barycentric", &MeshRelation::barycentric)
.field("triBary", &MeshRelation::triBary);
value_object<Curvature>("curvature")
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
.field("minMeanCurvature", &Curvature::minMeanCurvature)
.field("maxGaussianCurvature", &Curvature::maxGaussianCurvature)
.field("minGaussianCurvature", &Curvature::minGaussianCurvature)
.field("vertMeanCurvature", &Curvature::vertMeanCurvature)
.field("vertGaussianCurvature", &Curvature::vertGaussianCurvature);
register_vector<glm::ivec3>("Vector_ivec3");
register_vector<glm::vec3>("Vector_vec3");
register_vector<glm::vec2>("Vector_vec2");
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
register_vector<float>("Vector_f32");
register_vector<Manifold>("Vector_manifold");
register_vector<Smoothness>("Vector_smoothness");
register_vector<BaryRef>("Vector_baryRef");
register_vector<glm::vec4>("Vector_vec4");
class_<Manifold>("Manifold")
.constructor(&FromMeshJS)
.function("add", &Union)
.function("subtract", &Difference)
.function("intersect", &Intersection)
.function("_GetMeshJS", &GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &Warp)
.function("_Transform", &Transform)
.function("_Translate", &Manifold::Translate)
.function("_Rotate", &Manifold::Rotate)
.function("_Scale", &Manifold::Scale)
.function("_Decompose", &Manifold::Decompose)
.function("isEmpty", &Manifold::IsEmpty)
.function("status", &Manifold::Status)
.function("numVert", &Manifold::NumVert)
.function("numEdge", &Manifold::NumEdge)
.function("numTri", &Manifold::NumTri)
.function("_boundingBox", &Manifold::BoundingBox)
.function("precision", &Manifold::Precision)
.function("genus", &Manifold::Genus)
.function("getProperties", &Manifold::GetProperties)
.function("_getCurvature", &Manifold::GetCurvature)
.function("originalID", &Manifold::OriginalID)
.function("asOriginal", &Manifold::AsOriginal)
.function("_getMeshRelation", &Manifold::GetMeshRelation);
function("_Cube", &Manifold::Cube);
function("_Cylinder", &Manifold::Cylinder);
function("_Sphere", &Manifold::Sphere);
function("tetrahedron", &Manifold::Tetrahedron);
function("_Smooth", &Smooth);
function("_Extrude", &Extrude);
function("_Revolve", &Revolve);
function("_LevelSet", &LevelSetJs);
function("_unionN", &UnionN);
function("_differenceN", &DifferenceN);
function("_intersectionN", &IntersectionN);
function("_Compose", &Manifold::Compose);
function("setMinCircularAngle", &Manifold::SetMinCircularAngle);
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
function("setCircularSegments", &Manifold::SetCircularSegments);
function("getCircularSegments", &Manifold::GetCircularSegments);
}
+333
View File
@@ -0,0 +1,333 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var _ManifoldInitialized = false;
Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
function toVec(vec, list, f = x => x) {
if (list) {
for (let x of list) {
vec.push_back(f(x));
}
}
return vec;
}
function fromVec(vec, f = x => x) {
const result = [];
const size = vec.size();
for (let i = 0; i < size; i++) result.push(f(vec.get(i)));
return result;
}
function polygons2vec(polygons) {
if (polygons[0].length < 3) {
polygons = [polygons];
}
return toVec(
new Module.Vector2_vec2(), polygons,
poly => toVec(new Module.Vector_vec2(), poly, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
}));
}
function disposePolygons(polygonsVec) {
for (let i = 0; i < polygonsVec.size(); i++) polygonsVec.get(i).delete();
polygonsVec.delete();
}
function vararg2vec(vec) {
if (vec[0] instanceof Array)
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
if (typeof (vec[0]) == 'number')
// default to 0
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
return vec[0];
}
Module.Manifold.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
const vert = [x, y, z];
func(vert);
setValue(vec3Ptr, vert[0], 'float');
setValue(vec3Ptr + 4, vert[1], 'float');
setValue(vec3Ptr + 8, vert[2], 'float');
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
// note that the matrix is using column major (same as glm)
Module.Manifold.prototype.transform = function(mat) {
console.assert(mat.length == 4, 'expects a 3x4 matrix');
const vec = new Module.Vector_f32();
for (let col of mat) {
console.assert(col.length == 3, 'expects a 3x4 matrix');
for (let x of col) mat.push_back(x);
}
const result = this._Transform(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec(vec));
};
Module.Manifold.prototype.rotate = function(vec) {
return this._Rotate(...vec);
};
Module.Manifold.prototype.scale = function(vec) {
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec([vec]));
};
Module.Manifold.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.getCurvature = function() {
const result = this._getCurvature();
const oldMeanCurvature = result.vertMeanCurvature;
const oldGaussianCurvature = result.vertGaussianCurvature;
result.vertMeanCurvature = fromVec(oldMeanCurvature);
result.vertGaussianCurvature = fromVec(oldGaussianCurvature);
oldMeanCurvature.delete();
oldGaussianCurvature.delete();
return result;
};
class Mesh {
constructor({
triVerts = new Uint32Array(),
vertPos = new Float32Array(),
vertNormal,
halfedgeTangent
} = {}) {
this.triVerts = triVerts;
this.vertPos = vertPos;
this.vertNormal = vertNormal;
this.halfedgeTangent = halfedgeTangent;
}
get numTri() {
return this.triVerts.length / 3;
}
get numVert() {
return this.vertPos.length / 3;
}
verts(tri) {
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
}
position(vert) {
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
}
normal(vert) {
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function() {
return new Mesh(this._GetMeshJS());
};
Module.Manifold.prototype.getMeshRelation = function() {
const result = this._getMeshRelation();
const oldBarycentric = result.barycentric;
const oldTriBary = result.triBary;
const conversion1 = v => [v.x, v.y, v.z];
const conversion2 = v => [v[0], v[1], v[2]];
const conversion3 = v => {
return {
meshID: v.meshID,
originalID: v.originalID,
tri: v.tri,
vertBary: conversion2(v.vertBary)
};
};
result.barycentric = fromVec(oldBarycentric, conversion1);
result.triBary = fromVec(oldTriBary, conversion3);
oldBarycentric.delete();
oldTriBary.delete();
return result;
};
Module.Manifold.prototype.boundingBox = function() {
const result = this._boundingBox();
return {
min: ['x', 'y', 'z'].map(f => result.min[f]),
max: ['x', 'y', 'z'].map(f => result.max[f]),
};
};
Module.ManifoldError = function ManifoldError(code, ...args) {
let message = 'Unknown error';
switch (code) {
case Module.status.NON_FINITE_VERTEX.value:
message = 'Non-finite vertex';
break;
case Module.status.NOT_MANIFOLD.value:
message = 'Not manifold';
break;
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
message = 'Vertex index out of bounds';
break;
case Module.status.PROPERTIES_WRONG_LENGTH.value:
message = 'Properties have wrong length';
break;
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
message = 'Tri properties have wrong length';
break;
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
message = 'Tri properties out of bounds';
}
const base = Error.apply(this, [message, ...args]);
base.name = this.name = 'ManifoldError';
this.message = base.message;
this.stack = base.stack;
this.code = code;
};
Module.ManifoldError.prototype = Object.create(Error.prototype, {
constructor:
{value: Module.ManifoldError, writable: true, configurable: true}
});
const ManifoldCtor = Module.Manifold;
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
const status = manifold.status();
if (status.value !== 0) {
throw new Module.ManifoldError(status.value);
}
return manifold;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
Module.cube = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1, z: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0], z: args[0]};
else
size = vararg2vec(args);
const center = args[1] || false;
return Module._Cube(size, center);
};
Module.cylinder = function(
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
return Module._Cylinder(
height, radiusLow, radiusHigh, circularSegments, center);
};
Module.sphere = function(radius, circularSegments = 0) {
return Module._Sphere(radius, circularSegments);
};
Module.smooth = function(mesh, sharpenedEdges = []) {
const sharp = new Module.Vector_smoothness();
toVec(sharp, sharpenedEdges);
const result = Module._Smooth(mesh, sharp);
sharp.delete();
return result;
};
Module.extrude = function(
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0]) {
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
const polygonsVec = polygons2vec(polygons);
const result = Module._Extrude(
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
disposePolygons(polygonsVec);
return result;
};
Module.revolve = function(polygons, circularSegments = 0) {
const polygonsVec = polygons2vec(polygons);
const result = Module._Revolve(polygonsVec, circularSegments);
disposePolygons(polygonsVec);
return result;
};
Module.compose = function(manifolds) {
const vec = new Module.Vector_manifold();
toVec(vec, manifolds);
const result = Module._Compose(vec);
vec.delete();
return result;
};
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
const bounds2 = {
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
};
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
const vert = [x, y, z];
return sdf(vert);
}, 'fi');
const out = Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level);
removeFunction(wasmFuncPtr);
return out;
};
function batchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_manifold();
for (const m of args) v.push_back(m);
const result = Module['_' + name + 'N'](v);
v.delete();
return result;
}
}
Module.union = batchbool('union');
Module.difference = batchbool('difference');
Module.intersection = batchbool('intersection');
};
+214
View File
@@ -0,0 +1,214 @@
/*
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
font-family: 'Rubik', sans-serif;
font-size: 24px;
line-height: 24px;
font-weight: 800;
-webkit-font-smoothing: antialiased;
}
button {
display: block;
padding: 0 1em;
cursor: pointer;
height: 36px;
border: none;
border-radius: 18px;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 800;
text-align: left;
-webkit-font-smoothing: antialiased;
}
a:link,
a:visited {
color: black;
text-decoration: underline;
}
a:hover,
a:active {
color: black;
text-decoration: none;
}
button:disabled {
color: black;
background-color: #eee !important;
cursor: auto;
}
button:hover {
filter: brightness(0.9);
}
input {
border: none;
height: 36px;
background-color: transparent;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 800;
text-align: left;
-webkit-font-smoothing: antialiased;
}
input:focus {
outline: none;
}
p {
margin: 1em;
text-align: left;
}
model-viewer {
position: relative;
flex: 3;
width: 100%;
background-color: #ddd;
}
#console {
flex: 1;
min-height: 0;
max-height: 25vh;
overflow-y: scroll;
background-color: #eee;
white-space: pre-wrap;
font-size: 14px;
font-weight: 100;
line-height: 20px;
padding: 8px;
}
.page {
height: 100vh;
display: flex;
flex-direction: column;
}
.container {
display: flex;
}
.col {
position: relative;
width: 50vw;
}
.center {
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
}
.margin {
font-size: 16px;
margin: 1em;
}
#file {
width: 250px;
}
.dropdown {
display: none;
position: absolute;
z-index: 1;
width: 250px;
max-height: 80vh;
overflow-y: auto;
}
.show {
display: block;
}
.uparrow {
display: inline-block;
position: relative;
border: solid black;
border-width: 0 3px 3px 0;
padding: 3px;
transform: rotate(-135deg);
}
.down {
transform: rotate(45deg);
}
.item {
width: 100%;
position: relative;
}
.icon {
display: inline-block;
background-color: transparent;
position: absolute;
width: 36px;
top: 0px;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.cancel {
background-image: url(icons/close.png);
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.icon:hover {
background-color: rgba(0, 0, 0, 0.2);
}
.blue {
background-color: rgb(148, 221, 255);
}
.green {
background-color: rgb(131, 255, 131);
}
.red {
background-color: rgb(255, 177, 177);
}
@media only screen and (max-width: 600px) {
.container {
flex-direction: column;
width: 100%;
}
.col {
width: 100%;
}
}
+376
View File
@@ -0,0 +1,376 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const exampleFunctions = examples.functionBodies;
let editor = undefined;
// File UI ------------------------------------------------------------
const fileButton = document.querySelector('#file');
const currentElement = document.querySelector('#current');
const arrow = document.querySelector('.uparrow');
const dropdown = document.querySelector('.dropdown');
const hideDropdown = function() {
dropdown.classList.remove('show');
arrow.classList.remove('down');
};
const toggleDropdown = function(event) {
event.stopPropagation();
dropdown.classList.toggle('show');
arrow.classList.toggle('down');
};
fileButton.onclick = toggleDropdown;
document.body.onclick = hideDropdown;
const prefix = 'ManifoldCAD';
function getScript(name) {
return window.localStorage.getItem(prefix + name);
}
function setScript(name, code) {
window.localStorage.setItem(prefix + name, code);
}
function removeScript(name) {
window.localStorage.removeItem(prefix + name);
}
function nthKey(n) {
if (n >= window.localStorage.length) return;
const key = window.localStorage.key(n);
if (key.startsWith(prefix)) {
return key.slice(prefix.length);
}
}
function saveCurrent() {
if (editor) {
const currentName = currentElement.textContent;
if (!exampleFunctions.get(currentName)) {
setScript(currentName, editor.getValue());
}
}
};
window.onpagehide = saveCurrent;
window.beforeunload = saveCurrent;
let switching = false;
let isExample = true;
function switchTo(scriptName) {
if (editor) {
switching = true;
currentElement.textContent = scriptName;
setScript('currentName', scriptName);
const code =
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
isExample = exampleFunctions.get(scriptName) != null;
editor.setValue(code);
}
}
function createDropdownItem(name) {
const container = document.createElement('div');
container.classList.add('item');
const button = document.createElement('button');
container.appendChild(button);
button.type = 'button';
button.classList.add('blue', 'item');
const label = document.createElement('span');
button.appendChild(label);
label.textContent = name;
button.onclick = function() {
saveCurrent();
switchTo(label.textContent);
};
return button;
}
function addIcon(button) {
const icon = document.createElement('button');
icon.classList.add('icon');
button.parentElement.appendChild(icon);
return icon;
}
function uniqueName(name) {
let num = 1;
let newName = name;
while (getScript(newName) != null || exampleFunctions.get(newName) != null) {
newName = name + ' ' + num++;
}
return newName;
}
function addEdit(button) {
const label = button.firstChild;
const edit = addIcon(button);
edit.style.backgroundImage = 'url(icons/pencil.png)';
edit.style.right = '30px';
edit.onclick = function(event) {
event.stopPropagation();
const oldName = label.textContent;
const code = getScript(oldName);
const form = document.createElement('form');
const inputElement = document.createElement('input');
inputElement.value = oldName;
label.textContent = '';
button.appendChild(form);
form.appendChild(inputElement);
inputElement.focus();
inputElement.setSelectionRange(0, oldName.length);
function rename() {
const input = inputElement.value;
inputElement.blur();
if (!input) return;
const newName = uniqueName(input);
label.textContent = newName;
if (currentElement.textContent == oldName) {
currentElement.textContent = newName;
}
removeScript(oldName);
setScript(newName, code);
}
form.onsubmit = rename;
inputElement.onclick = function(event) {
event.stopPropagation();
};
inputElement.onblur = function() {
button.removeChild(form);
label.textContent = oldName;
};
};
const trash = addIcon(button);
trash.style.backgroundImage = 'url(icons/trash.png)';
trash.style.right = '0px';
let lastClick = 0;
trash.onclick = function(event) {
event.stopPropagation();
if (button.classList.contains('blue')) {
lastClick = performance.now();
button.classList.remove('blue');
button.classList.add('red');
document.body.addEventListener('click', function() {
button.classList.add('blue');
button.classList.remove('red');
}, {once: true});
} else if (performance.now() - lastClick > 500) {
removeScript(label.textContent);
if (currentElement.textContent == label.textContent) {
switchTo('Intro');
}
const container = button.parentElement;
container.parentElement.removeChild(container);
}
};
}
const newButton = document.querySelector('#new');
function newItem(code) {
const name = uniqueName('New Script');
setScript(name, code);
const nextButton = createDropdownItem(name);
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
addEdit(nextButton);
nextButton.click();
};
newButton.onclick = function() {
newItem('');
};
const runButton = document.querySelector('#compile');
const poster = document.querySelector('#poster');
let manifoldInitialized = false;
let autoExecute = true;
function initializeRun() {
runButton.disabled = false;
if (autoExecute) {
runButton.click();
} else {
poster.textContent = 'Auto-run disabled due to prior failure';
}
}
// Editor ------------------------------------------------------------
let tsWorker = undefined;
require.config({
paths:
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
});
require(['vs/editor/editor.main'], async function() {
const content =
await fetch('manifold.d.ts').then(response => response.text());
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
editor = monaco.editor.create(
document.getElementById('editor'),
{language: 'typescript', automaticLayout: true});
const w = await monaco.languages.typescript.getTypeScriptWorker();
tsWorker = await w(editor.getModel().uri);
editor.getModel().updateOptions({tabSize: 2});
for (const [name] of exampleFunctions) {
const button = createDropdownItem(name);
dropdown.appendChild(button.parentElement);
}
let currentName = currentElement.textContent;
for (let i = 0; i < window.localStorage.length; i++) {
const key = nthKey(i);
if (!key) continue;
if (key === 'currentName') {
currentName = getScript(key);
} else if (key === 'safe') {
autoExecute = getScript(key) !== 'false';
} else {
const button = createDropdownItem(key);
newButton.insertAdjacentElement('afterend', button.parentElement);
addEdit(button);
}
}
switchTo(currentName);
if (manifoldInitialized) {
initializeRun();
}
editor.onDidChangeModelContent(e => {
runButton.disabled = false;
if (switching) {
switching = false;
return;
}
if (isExample) {
const cursor = editor.getPosition();
newItem(editor.getValue());
editor.setPosition(cursor);
}
});
window.onresize = () => {
editor.layout({});
};
});
// Execution ------------------------------------------------------------
const consoleElement = document.querySelector('#console');
const oldLog = console.log;
console.log = function(message) {
consoleElement.textContent += message + '\r\n';
consoleElement.scrollTop = consoleElement.scrollHeight;
oldLog(message);
};
function clearConsole() {
consoleElement.textContent = '';
}
function enableCancel() {
runButton.firstChild.style.visibility = 'hidden';
runButton.classList.add('red', 'cancel');
}
function disableCancel() {
runButton.firstChild.style.visibility = 'visible';
runButton.classList.remove('red', 'cancel');
}
let t0 = performance.now();
function finishRun() {
disableCancel();
const t1 = performance.now();
const log = consoleElement.textContent;
// Remove "Running..."
consoleElement.textContent = log.substring(log.indexOf('\n') + 1);
console.log(
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
}
const mv = document.querySelector('model-viewer');
let objectURL = null;
let manifoldWorker = null;
function createWorker() {
manifoldWorker = new Worker('worker.js');
manifoldWorker.onmessage = function(e) {
if (e.data == null) {
if (tsWorker != null && !manifoldInitialized) {
initializeRun();
}
manifoldInitialized = true;
return;
}
if (e.data.log != null) {
consoleElement.textContent += e.data.log + '\r\n';
consoleElement.scrollTop = consoleElement.scrollHeight;
return;
}
finishRun();
runButton.disabled = true;
setScript('safe', 'true');
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
mv.src = objectURL;
if (objectURL == null) {
mv.showPoster();
poster.textContent = 'Error';
}
}
}
createWorker();
async function run() {
saveCurrent();
setScript('safe', 'false');
enableCancel();
clearConsole();
console.log('Running...');
const output = await tsWorker.getEmitOutput(editor.getModel().uri.toString());
manifoldWorker.postMessage(output.outputFiles[0].text);
t0 = performance.now();
}
function cancel() {
manifoldWorker.terminate();
createWorker();
finishRun();
console.log('Run canceled');
}
runButton.onclick = function() {
if (runButton.classList.contains('cancel')) {
cancel();
} else {
run();
}
};
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
+440
View File
@@ -0,0 +1,440 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
(function(exports) {
exports.functions = {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the API
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
// static API - these functions can also be used bare. Use console.log() to
// print output (lower-right). This editor defines Z as up and units of mm.
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
// All changes are automatically saved and restored between sessions.
// This app is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// To share your code with another browser/device, simply copy the text to a
// file.
// See the script drop-down above ("Intro") for usage examples.
return result;
},
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as a
// puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions =
50; // Number of divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
const tet = tetrahedron().scale(scale);
const box = [];
box.push([2, -2], [2, 2]);
for (let i = 0; i <= nDivisions; ++i) {
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
}
const screw = extrude(box, 2, nDivisions, 270)
.rotate([0, 0, -45])
.translate([0, 0, -1])
.scale(scale);
const result = tet.intersect(screw);
return result;
},
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
function roundedFrame(edgeLength, radius, circularSegments = 0) {
const edge = cylinder(edgeLength, radius, -1, circularSegments);
const corner = sphere(radius, circularSegments);
let edge1 = union(corner, edge);
edge1 = edge1.rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
let edge2 = edge1.rotate([0, 0, 180]);
edge2 = union(edge2, edge1);
edge2 =
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
let edge4 = edge2.rotate([0, 0, 90]);
edge4 = union(edge4, edge2);
let frame = edge4.translate([0, 0, -edgeLength / 2]);
frame = union(frame, frame.rotate([180, 0, 0]));
return frame;
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
return result;
},
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function. This
// example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
const func = (v) => {
const x2 = v[0] * v[0];
const y2 = v[1] * v[1];
const z = v[2];
const z2 = z * z;
const a = x2 + 9 / 4 * y2 + z2;
const b = z * z2 * (x2 + 9 / 80 * y2);
const a2 = a * a;
const a3 = a * a2;
const step = (r) => {
const r2 = r * r;
const r4 = r2 * r2;
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
let r = 1.5;
let dr = 1;
while (Math.abs(dr) > 0.0001) {
dr = step(r);
r -= dr;
}
// Update radius
v[0] *= r;
v[1] *= r;
v[2] *= r;
};
const ball = sphere(1, 200);
const heart = ball.warp(func);
const box = heart.boundingBox();
const result = heart.scale(100 / (box.max[0] - box.min[0]));
return result;
},
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with smooth()
// and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
const radius = 30;
const offset = 20;
const wiggles = 12;
const sharpness = 0.8;
const n = 50;
const vertPos = [];
const triVerts = [];
vertPos.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
for (let i = 0; i < 2 * wiggles; ++i) {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
vertPos.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(0, 2 + i, 2 + j);
halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(1, 2 + j, 2 + i);
}
const scallop = new Mesh();
scallop.triVerts = Uint32Array.from(triVerts);
scallop.vertPos = Float32Array.from(vertPos);
const result = smooth(scallop, sharpenedEdges).refine(n);
return result;
},
TorusKnot: function() {
// Creates a classic torus knot, defined as a string wrapping periodically
// around the surface of an imaginary donut. If p and q have a common factor
// then you will get multiple separate, interwoven knots. This is an example
// of using the warp() method, thus avoiding any direct handling of
// triangles.
// @param p The number of times the thread passes through the donut hole.
// @param q The number of times the thread circles the donut.
// @param majorRadius Radius of the interior of the imaginary donut.
// @param minorRadius Radius of the small cross-section of the imaginary
// donut.
// @param threadRadius Radius of the small cross-section of the actual
// object.
// @param circularSegments Number of linear segments making up the
// threadRadius circle. Default is getCircularSegments(threadRadius).
// @param linearSegments Number of segments along the length of the knot.
// Default makes roughly square facets.
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
const kLoops = gcd(p, q);
p /= kLoops;
q /= kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * q * majorRadius / threadRadius;
const circle = [];
const dPhi = 2 * 3.14159 / n;
const offset = 2;
for (let i = 0; i < n; ++i) {
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
}
const point = new THREE.Vector3();
const axis = new THREE.Vector3();
const func =
(v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
point.set(Math.cos(phi), 0, Math.sin(phi))
.multiplyScalar(threadRadius);
const r = majorRadius + minorRadius * Math.cos(theta);
point.applyAxisAngle(
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
point.x += minorRadius;
point.applyAxisAngle(axis.set(0, 1, 0), theta);
point.x += majorRadius;
point.applyAxisAngle(axis.set(0, 0, 1), psi);
v[0] = point.x;
v[1] = point.y;
v[2] = point.z;
}
let knot = revolve(circle, m).warp(func);
if (kLoops > 1) {
const knots = [];
for (let k = 0; k < kLoops; ++k) {
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
}
knot = compose(knots);
}
return knot;
}
// This recreates Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
const result = torusKnot(1, 3, 25, 10, 3.75);
return result;
},
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
function vec2add(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
if (depth == maxDepth) return;
const offsets = [
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
[0.0, -w]
];
for (let offset of offsets)
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
}
function mengerSponge(n) {
let result = cube([1, 1, 1], true);
const holes = [];
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
const hole = compose(holes);
result = difference(result, hole);
result = difference(result, hole.rotate([90, 0, 0]));
result = difference(result, hole.rotate([0, 90, 0]));
return result;
}
const result = mengerSponge(3).scale(100);
return result;
},
StretchyBracelet: function() {
// Recreates Stretchy Bracelet by Emmett Lalish:
// https://www.thingiverse.com/thing:13505
function base(
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
function rotate(v, theta) {
return [
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
];
}
let b = cylinder(width, radius + twistRadius / 2);
const circle = [];
const dPhiDeg = 180 / nDivision;
for (let i = 0; i < 2 * nDivision; ++i) {
circle.push([
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
]);
}
let decor =
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
0, radius, 0
]);
for (let i = 0; i < nDecor; i++)
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
const stretch = [];
const dPhiRad = 2 * Math.PI / nCut;
const p0 = [outerRadius, 0];
const p1 = [innerRadius, -cut];
const p2 = [innerRadius, cut];
for (let i = 0; i < nCut; ++i) {
stretch.push(rotate(p0, dPhiRad * i));
stretch.push(rotate(p1, dPhiRad * i));
stretch.push(rotate(p2, dPhiRad * i));
stretch.push(rotate(p0, dPhiRad * i));
}
b = intersection(extrude(stretch, width), b);
return b;
}
function stretchyBracelet(
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
const twistRadius = Math.PI * radius / nDecor;
const decorRadius = twistRadius * 1.5;
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
const innerRadius = outerRadius - height;
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
const adjThickness = 0.5 * thickness * height / cut;
return difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
const result = stretchyBracelet();
return result;
},
GyroidModule: function() {
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
const size = 20;
const n = 20;
const pi = 3.14159;
function gyroid(p) {
const x = p[0] - pi / 4;
const y = p[1] - pi / 4;
const z = p[2] - pi / 4;
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
const period = 2 * pi;
const box = {
min: [-period, -period, -period],
max: [period, period, period]
};
return levelSet(gyroid, box, period / n, level).scale(size / period);
};
function rhombicDodecahedron() {
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
result = result.subtract(gyroidOffset(0.4));
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
return result;
}
};
exports.functionBodies = new Map();
for (const [func, code] of Object.entries(exports.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first four leading spaces
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
const name =
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
exports.functionBodies.set(name, body);
};
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+56
View File
@@ -0,0 +1,56 @@
<!doctype html>
<html lang="en">
<head>
<title>ManifoldCAD</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<link type="text/css" href="editor.css" rel="stylesheet" />
</head>
<body>
<div class="page">
<div class="container" style="background-color: rgb(162 136 179);">
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
</p>
<div class="margin" style="position: relative;">
<button type="button" id="file" class="blue"><b class="uparrow"></b>&nbsp;&nbsp;<span
id="current">Intro</span></button>
<div class="dropdown">
<button type="button" class="green item" id="new">New</button>
</div>
</div>
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
<button type="button" id="download" class="blue margin">Save GLB</button>
</div>
<div class="container" style="flex: 2;">
<div id="editor" class="col"></div>
<div class="col container" style="flex-direction: column;">
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
<div class="center" slot="poster">
<p id="poster" style="text-align: center; line-height: 36px;">
Loading...
</p>
</div>
</model-viewer>
<div id="console"></div>
</div>
</div>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="examples.js"></script>
<script src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
</html>
+127
View File
@@ -0,0 +1,127 @@
<!doctype html>
<html lang="en">
<head>
<title>Manifold - <code></code>&lt;model-viewer&gt;</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/">&lt;model-viewer&gt;</a>
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
</p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
<option value="union">Union</option>
</select>
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
wasm.setup();
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
color: 'yellow',
metalness: 1,
roughness: 0.2
}));
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
push2MV(mesh);
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
});
const mv = document.querySelector('model-viewer');
let objectURL = null;
const exporter = new THREE.GLTFExporter();
function push2MV(mesh) {
exporter.parse(
mesh,
(gltf) => {
const blob = new Blob([gltf], { type: 'application/octet-stream' });
URL.revokeObjectURL(objectURL);
objectURL = URL.createObjectURL(blob);
mv.src = objectURL;
},
() => console.log('GLTF export failed!'),
{ binary: true }
);
}
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
</script>
</html>
+119
View File
@@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<title>Manifold - three.js texture example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
canvas {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
}
</style>
</head>
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>.
</p>
<input type="checkbox"/><br />
<canvas id="output"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="manifold.js"></script>
<script>
Module().then(function (wasm) {
wasm.setup();
const cube = new wasm.Mesh();
cube.vertPos = new Float32Array(24);
cube.triVerts = new Uint32Array(36);
cube.vertPos.set([
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1]);
cube.triVerts.set([
0, 2, 1, 0, 3, 2,
4, 5, 6, 4, 6, 7,
0, 1, 5, 0, 5, 4,
1, 2, 6, 1, 6, 5,
2, 3, 7, 2, 7, 6,
3, 0, 4, 3, 4, 7]);
const manifold = wasm.Manifold(cube);
const result = manifold.add(manifold.rotate([90, 0, 90]).translate([1, 1, 0]));
// we have a manifold, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 1, 100);
camera.position.z = 10;
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(mesh2geometry(result.getMesh()),
new THREE.MeshNormalMaterial({
flatShading: true
}));
const wireframe = new THREE.WireframeGeometry(mesh.geometry);
const lines = new THREE.LineSegments(wireframe);
scene.add(mesh);
scene.add(lines);
lines.visible = false;
const checkbox = document.querySelector('input');
checkbox.onchange = function (event) {
mesh.visible = !checkbox.checked;
lines.visible = checkbox.checked;
};
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
scene.rotation.x = time / 2000;
scene.rotation.y = time / 1000;
renderer.render(scene, camera);
});
});
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
</script>
</html>
+121
View File
@@ -0,0 +1,121 @@
<!doctype html>
<html lang="en">
<head>
<title>Manifold - three.js Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
canvas {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
}
</style>
</head>
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>.
</p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
<option value="union">Union</option>
</select><br />
<canvas id="output"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
wasm.setup();
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
});
});
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
</script>
</html>
+154
View File
@@ -0,0 +1,154 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
importScripts(
'manifold.js', threePath + 'build/three.js',
threePath + 'examples/js/exporters/GLTFExporter.js');
// manifold member functions that returns a new manifold
const memberFunctions = [
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
'scale', 'asOriginal', 'decompose'
];
// top level functions that constructs a new manifold
const constructors = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'Mesh'
];
const exposedFunctions = constructors.concat(utils);
let wasm;
Module().then(function(tmp) {
wasm = tmp;
wasm.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = wasm.Manifold.prototype[name];
wasm.Manifold.prototype['_' + name] = originalFn;
wasm.Manifold.prototype[name] = function(...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
}
}
for (const name of constructors) {
const originalFn = wasm[name];
wasm[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
wasm.cleanup =
function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
}
postMessage(null);
});
const oldLog = console.log;
console.log = function(...args) {
let message = '';
for (const arg of args) {
if (typeof arg == 'object') {
message += JSON.stringify(arg, null, 4);
} else {
message += arg.toString();
}
}
postMessage({log: message});
oldLog(...args);
};
onmessage =
(e) => {
const content = e.data + '\nexportGLB(result);\n';
try {
const f = new Function(...exposedFunctions, content);
f(...exposedFunctions.map(name => wasm[name]));
} catch (error) {
console.log(error.toString());
postMessage({objectURL: null});
} finally {
wasm.cleanup();
}
}
// Export & Rendering
// ------------------------------------------------------------
const mesh = new THREE.Mesh(
undefined,
new THREE.MeshStandardMaterial(
{color: 'yellow', metalness: 1, roughness: 0.2}));
const rotation = new THREE.Matrix4();
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
mesh.scale.setScalar(0.001); // mm -> m
const exporter = new THREE.GLTFExporter();
function exportGLB(manifold) {
console.log(`Triangles: ${manifold.numTri().toLocaleString()}`);
const box = manifold.boundingBox();
const size = [0, 0, 0];
for (let i = 0; i < 3; i++) {
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
}
console.log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
const volume = Math.round(manifold.getProperties().volume / 10);
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(manifold.getMesh());
exporter.parse(
mesh,
(gltf) => {
const blob = new Blob([gltf], {type: 'application/octet-stream'});
postMessage({objectURL: URL.createObjectURL(blob)});
},
() => {
console.log('glTF export failed!');
postMessage({objectURL: null});
},
{binary: true});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
+452
View File
@@ -0,0 +1,452 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
type Vec2 = [number, number];
type Vec3 = [number, number, number];
type Vec4 = [number, number, number, number];
type Matrix3x4 = [Vec3, Vec3, Vec3, Vec3];
type SimplePolygon = Vec2[];
type Polygons = SimplePolygon|SimplePolygon[];
type Box = {
min: Vec3,
max: Vec3
};
type Smoothness = {
halfedge: number,
smoothness: number
};
type Properties = {
surfaceArea: number,
volume: number
};
type BaryRef = {
meshID: number,
originalID: number,
tri: number,
vertBary: Vec3
};
type Curvature = {
maxMeanCurvature: number,
minMeanCurvature: number,
maxGaussianCurvature: number,
minGaussianCurvature: number,
vertMeanCurvature: number[],
vertGaussianCurvature: number[]
};
type MeshRelation = {
barycentric: Vec3[],
triBary: BaryRef[],
};
declare class Mesh {
vertPos: Float32Array;
triVerts: Uint32Array;
vertNormal?: Float32Array;
halfedgeTangent?: Float32Array;
get numTri(): number;
get numVert(): number;
verts(tri: number): Uint32Array<3>;
position(vert: number): Float32Array<3>;
normal(vert: number): Float32Array<3>;
tangent(halfedge: number): Float32Array<4>;
}
declare class Manifold {
/**
* Create a Manifold from a Mesh object.
*/
constructor(mesh: Mesh);
/**
* Transform this Manifold in space. The first three columns form a 3x3 matrix
* transform and the last is a translation vector. This operation can be
* chained. Transforms are combined and applied lazily.
*
* @param m The affine transform matrix to apply to all the vertices.
*/
transform(m: Matrix3x4): Manifold;
/**
* Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
* error, and eliminate it completely for any multiples of 90 degrees.
* Additionally, more efficient code paths are used to update the manifold
* when the transforms only rotate by multiples of 90 degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3|number): Manifold;
/**
* Boolean union
*/
add(other: Manifold): Manifold;
/**
* Boolean difference
*/
subtract(other: Manifold): Manifold;
/**
* Boolean intersection
*/
intersect(other: Manifold): Manifold;
/**
* Increase the density of the mesh by splitting every edge into n pieces. For
* instance, with n = 2, each triangle will be split into 4 triangles. These
* will all be coplanar (and will not be immediately collapsed) unless the
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
* constructor), in which case the new vertices will be moved to the
* interpolated surface according to their barycentric coordinates.
*
* @param n The number of pieces to split every edge into. Must be > 1.
*/
refine(n: number): Manifold;
/**
* This function does not change the topology, but allows the vertices to be
* moved according to any arbitrary input function. It is easy to create a
* function that warps a geometrically valid object into one which overlaps,
* but that is not checked here, so it is up to the user to choose their
* function with discretion.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec3) => void): Manifold;
/**
* This operation returns a vector of Manifolds that are topologically
* disconnected. If everything is connected, the vector is length one,
* containing a copy of the original. It is the inverse operation of
* Compose().
*/
decompose(): Manifold[];
/**
* Does the Manifold have any triangles?
*/
isEmpty(): boolean;
/**
* The number of vertices in the Manifold.
*/
numVert(): number;
/**
* The number of triangles in the Manifold.
*/
numTri(): number;
/**
* The number of edges in the Manifold.
*/
numEdge(): number;
/**
* Returns the axis-aligned bounding box of all the Manifold's vertices.
*/
boundingBox(): Box;
/**
* Returns the precision of this Manifold's vertices, which tracks the
* approximate rounding error over all the transforms and operations that have
* led to this state. Any triangles that are colinear within this precision
* are considered degenerate and removed. This is the value of &epsilon;
* defining
* [&epsilon;-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
precision(): number;
/**
* The genus is a topological property of the manifold, representing the
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
* a single mesh, so it is best to call Decompose() first.
*/
genus(): number;
/**
* Returns the surface area and volume of the manifold. These properties are
* clamped to zero for a given face if they are within the Precision(). This
* means degenerate manifolds can by identified by testing these properties as
* == 0.
*/
getProperties(): Properties;
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex (returned
* as vectors in the structure) and also returns their minimum and maximum
* values.
*/
getCurvature(): Curvature;
/**
* This returns a Mesh of simple vectors of vertices and triangles suitable
* for saving or other operations outside of the context of this library.
*/
getMesh(): Mesh;
/**
* Gets the relationship to the previous meshes, for the purpose of assigning
* properties like texture coordinates. The triBary vector is the same length
* as Mesh.triVerts: BaryRef.originalID indicates the source mesh and
* BaryRef.tri is that mesh's triangle index to which these barycentric
* coordinates refer. BaryRef.vertBary gives an index for each vertex into the
* barycentric vector if that index is >= 0, indicating it is a new vertex. If
* the index is < 0, this indicates it is an original vertex, the index + 3
* vert of the referenced triangle.
*
* BaryRef.meshID is a unique ID to the particular instance of a given mesh.
* For instance, if you want to convert the triangle mesh to a polygon mesh,
* all the triangles from a given face will have the same .meshID and .tri
* values.
*/
getMeshRelation(): MeshRelation;
/**
* If you copy a manifold, but you want this new copy to have new properties
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
* meaning it will now be referenced by its descendants instead of the meshes
* it was built from, allowing you to differentiate the copies when applying
* your properties to the final result.
*
* This function also condenses all coplanar faces in the relation, and
* collapses those edges. If you want to have inconsistent properties across
* these faces, meaning you want to preserve some of these edges, you should
* instead call GetMesh(), calculate your properties and use these to
* construct a new manifold.
*/
asOriginal(): Manifold;
/**
* If this mesh is an original, this returns its meshID that can be referenced
* by product manifolds' MeshRelation. If this manifold is a product, this
* returns -1.
*/
originalID(): number;
}
/**
* Constructs a unit cube (edge lengths all one), by default in the first
* octant, touching the origin.
*
* @param size The X, Y, and Z dimensions of the box.
* @param center Set to true to shift the center to the origin.
*/
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can also
* form cones if both radii are specified.
*
* @param height Z-extent
* @param radiusLow Radius of bottom circle. Must be positive.
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
* radiusLow.
* @param circularSegments How many line segments to use around the circle.
* Default is calculated by the static Defaults.
* @param center Set to true to shift the center to the origin. Default is
* origin at the bottom.
*/
declare function cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
*
* @param radius Radius of the sphere. Must be positive.
* @param circularSegments Number of segments along its
* diameter. This number will always be rounded up to the nearest factor of
* four, as this sphere is constructed by refining an octahedron. This means
* there are a circle of vertices on all three of the axis planes. Default is
* calculated by the static Defaults.
*/
declare function sphere(radius: number, circularSegments?: number): Manifold;
/**
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangents with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
* By default, every edge is calculated for maximum smoothness (very much
* approximately), attempting to minimize the maximum mean Curvature magnitude.
* No higher-order derivatives are considered, as the interpolation is
* independent per triangle, only sharing constraints on their boundaries.
*
* @param mesh input Mesh.
* @param sharpenedEdges If desired, you can supply a vector of sharpened
* halfedges, which should in general be a small subset of all halfedges. Order
* of entries doesn't matter, as each one specifies the desired smoothness
* (between zero and one, with one the default for all unspecified halfedges)
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
* between triVert 0 and 1, etc).
*
* At a smoothness value of zero, a sharp crease is made. The smoothness is
* interpolated along each edge, so the specified value should be thought of as
* an average. Where exactly two sharpened edges meet at a vertex, their
* tangents are rotated to be colinear so that the sharpened edge can be
* continuous. Vertices with only one sharpened edge are completely smooth,
* allowing sharpened edges to smoothly vanish at termination. A single vertex
* can be sharpened by sharping all edges that are incident on it, allowing
* cones to be formed.
*/
declare function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
/**
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
* and the rest at similarly symmetric points.
*/
declare function tetrahedron(): Manifold;
/**
* Constructs a manifold from a set of polygons by extruding them along the
* Z-axis.
*
* @param crossSection A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
* Default {1, 1}.
*/
declare function extrude(
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
/**
* Constructs a manifold from a set of polygons by revolving this cross-section
* around its Y-axis and then setting this as the Z-axis of the resulting
* manifold. If the polygons cross the Y-axis, only the part on the positive X
* side is used. Geometrically valid input will result in geometrically valid
* output.
*
* @param crossSection A set of non-overlapping polygons to revolve.
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
*/
declare function revolve(
crossSection: Polygons, circularSegments?: number): Manifold;
declare function union(a: Manifold, b: Manifold): Manifold;
declare function difference(a: Manifold, b: Manifold): Manifold;
declare function intersection(a: Manifold, b: Manifold): Manifold;
declare function union(manifolds: Manifold[]): Manifold;
declare function difference(manifolds: Manifold[]): Manifold;
declare function intersection(manifolds: Manifold[]): Manifold;
/**
* Constructs a new manifold from a list of other manifolds. This is a purely
* topological operation, so care should be taken to avoid creating
* overlapping results. It is the inverse operation of Decompose().
*
* @param manifolds A list of Manifolds to lazy-union together.
*/
declare function compose(manifolds: Manifold[]): Manifold;
/**
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
* cubic grid (two shifted cubic grids). This means if your function's interior
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
* the manifold, which is due to the underlying grid.
*
* @param sdf The signed-distance function which returns the signed distance of
* a given point in R^3. Positive values are inside, negative outside.
* @param bounds An axis-aligned box that defines the extent of the grid.
* @param edgeLength Approximate maximum edge length of the triangles in the
* final result. This affects grid spacing, and hence has a strong effect on
* performance.
* @param level You can inset your Mesh by using a positive value, or outset
* it with a negative value.
*/
declare function levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
/**
* @name Defaults
* These static properties control how circular shapes are quantized by
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
declare function setMinCircularAngle(angle: number): void;
declare function setMinCircularEdgeLength(length: number): void;
declare function setCircularSegments(segments: number): void;
declare function getCircularSegments(radius: number): number;
///@}
/**
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
* constructor, this method does not dispose the Mesh after using it.
*
* @param meshVec The serialized Mesh object to convert into a Manifold.
*/
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
declare interface ManifoldStatic {
cube: typeof cube;
cylinder: typeof cylinder;
sphere: typeof sphere;
smooth: typeof smooth;
tetrahedron: typeof tetrahedron;
extrude: typeof extrude;
revolve: typeof revolve;
union: typeof union;
difference: typeof difference;
intersection: typeof intersection;
compose: typeof compose;
levelSet: typeof levelSet;
setMinCircularAngle: typeof setMinCircularAngle;
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
setCircularSegments: typeof setCircularSegments;
getCircularSegments: typeof getCircularSegments;
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
Manifold: typeof Manifold;
setup: () => void;
}
declare function Module(): Promise<ManifoldStatic>;
+1705
View File
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"name": "manifold-3d",
"version": "1.0.0",
"description": "Geometry library for topological robustness",
"main": "manifold.js",
"files": [
"manifold.js",
"manifold.wasm",
"manifold.d.ts",
"examples/examples.js",
"test/test.js"
],
"typings": "manifold.d.ts",
"types": "manifold.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/elalish/manifold.git"
},
"keywords": [
"3D",
"mesh",
"manifold",
"geometry",
"solid",
"CAD",
"triangle",
"SDF"
],
"contributors": [
"Emmett Lalish <elalish>",
"Chun Kit LAM <pca006132>"
],
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/elalish/manifold/issues"
},
"homepage": "https://github.com/elalish/manifold#readme",
"scripts": {
"test": "mocha -u tdd -t 30000"
},
"devDependencies": {
"chai": "*",
"mocha": "*",
"three": "^0.146.0"
}
}
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const expect = require('chai').expect;
const wasm = require('../manifold.js');
const examples = require('../examples/examples');
const THREE = require('three');
// manifold member functions that returns a new manifold
const memberFunctions = [
'add', 'subtract', 'intersect', 'refine', 'transform', 'translate', 'rotate',
'scale', 'asOriginal', 'decompose'
];
// top level functions that constructs a new manifold
const constructors = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'Mesh'
];
const exposedFunctions = constructors.concat(utils);
wasm().then(function(Module) {
Module.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = Module.Manifold.prototype[name];
Module.Manifold.prototype['_' + name] = originalFn;
Module.Manifold.prototype[name] = function(...args) {
const result = this['_' + name](...args);
manifoldRegistry.push(result);
return result;
}
}
for (const name of constructors) {
const originalFn = Module[name];
Module[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
Module.cleanup =
function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
}
function runExample(name) {
try {
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
;
const f = new Function(...exposedFunctions, 'THREE', content);
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
const prop = manifold.getProperties();
const genus = manifold.genus();
return {...prop, genus};
} finally {
Module.cleanup();
}
}
suite('Examples', () => {
test('Intro', () => {
const result = runExample('Intro');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
});
test('Tetrahedron Puzzle', () => {
const result = runExample('Tetrahedron Puzzle');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
});
test('Rounded Frame', () => {
const result = runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(353706, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
});
test('Heart', () => {
const result = runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(282743, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
});
test('Scallop', () => {
const result = runExample('Scallop');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
});
test('Torus Knot', () => {
const result = runExample('Torus Knot');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
});
test('Menger Sponge', () => {
const result = runExample('Menger Sponge');
expect(result.genus).to.equal(1409, 'Genus');
expect(result.volume).to.be.closeTo(406457, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
});
test('Stretchy Bracelet', () => {
const result = runExample('Stretchy Bracelet');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
});
test('Gyroid Module', () => {
const result = runExample('Gyroid Module');
expect(result.genus).to.equal(15, 'Genus');
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
});
});
});
-24
View File
@@ -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
)
-8
View File
@@ -1,8 +0,0 @@
version: '3.4'
services:
manifold:
image: manifold
build:
context: .
dockerfile: Dockerfile
-6
View File
@@ -1,6 +0,0 @@
version: '3.4'
services:
manifold:
image: manifold
build: .
Binary file not shown.
+33
View File
@@ -0,0 +1,33 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
project(extras)
add_executable(perfTest perf_test.cpp)
target_link_libraries(perfTest manifold)
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(perfTest PUBLIC cxx_std_14)
if(BUILD_TEST_CGAL)
add_executable(perfTestCGAL perf_test_cgal.cpp)
find_package(CGAL REQUIRED COMPONENTS Core)
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
endif()
+22
View File
@@ -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
+3 -3
View File
@@ -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()
+79
View File
@@ -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;
}
}
Generated
+42
View File
@@ -0,0 +1,42 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1649676176,
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1657296039,
"narHash": "sha256-Ghh39+aS+pw5sTP/ZO8VIKE6sBhMadDaQZtf+3yu4Vc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "71d7a4c037dc4f3e98d5c4a81b941933cf5bf675",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-22.05",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+130
View File
@@ -0,0 +1,130 @@
{
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
manifold =
{ parallel-backend ? "none"
, cuda-support ? false
, doCheck ? true
, build-tools ? [ ]
, ...
}: pkgs.stdenv.mkDerivation {
inherit doCheck;
pname =
if cuda-support then
"manifold-${parallel-backend}-cuda"
else
"manifold-${parallel-backend}";
version = "beta";
src = self;
patches = [ ./thrust.diff ];
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages(ps: with ps; [trimesh])) ]) ++ build-tools ++
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
cmakeFlags = [
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
];
checkPhase = ''
cd test
./manifold_test
cd ../../
PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python python3 bindings/python/examples/run_all.py
cd build
'';
installPhase = ''
mkdir -p $out
cp src/manifold/libmanifold.a $out/
cp extras/perfTest $out
cp bindings/python/pymanifold* $out
'';
};
parallelBackends = [
{ parallel-backend = "none"; }
{
parallel-backend = "omp";
build-tools = [ pkgs.llvmPackages_13.openmp ];
}
{
parallel-backend = "tbb";
build-tools = with pkgs; [ tbb pkg-config ];
}
];
buildMatrix = with pkgs; with lib; lists.flatten (map
(env: map
(x: x // env)
parallelBackends) [
{ cuda-support = false; }
{
cuda-support = true;
}
]);
devShell = { additional ? [ ] }: pkgs.mkShell {
buildInputs = with pkgs; [
cmake
llvmPackages_13.openmp
clang-tools
clang_13
emscripten
tbb
lcov
] ++ additional;
};
in
{
packages = (builtins.listToAttrs
(map
(x: {
name = "manifold-" + x.parallel-backend + (if
x.cuda-support then "-cuda" else "");
value = manifold x;
})
buildMatrix)) // {
manifold-js = pkgs.buildEmscriptenPackage {
name = "manifold-js";
version = "beta";
src = self;
nativeBuildInputs = (with pkgs; [ cmake python39 ]);
buildInputs = [ pkgs.nodejs ];
configurePhase = ''
mkdir -p .emscriptencache
export EM_CACHE=$(pwd)/.emscriptencache
mkdir build
cd build
mkdir cache
export EM_CACHE=$(pwd)/cache
emcmake cmake -DCMAKE_BUILD_TYPE=Release ..
'';
buildPhase = ''
emmake make -j''${NIX_BUILD_CORES}
'';
checkPhase = ''
cd test
node manifold_test.js
cd ../
'';
installPhase = ''
mkdir -p $out
cp {extras,wasm}/*.js $out/
cp {extras,wasm}/*.wasm $out/
'';
};
};
devShell = devShell { };
devShells.cuda = devShell {
additional = with pkgs.cudaPackages; [
cuda_nvcc
cuda_cudart
cuda_cccl
];
};
}
);
}
-25
View File
@@ -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
)
-190
View File
@@ -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
-401
View File
@@ -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
-15
View File
@@ -1,15 +0,0 @@
project (meshIO)
add_library(${PROJECT_NAME} src/meshIO.cpp)
target_include_directories( ${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_link_libraries( ${PROJECT_NAME}
PUBLIC utilities
PRIVATE assimp
)
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
-13
View File
@@ -1,13 +0,0 @@
project (polygon)
add_library(${PROJECT_NAME} src/polygon.cpp)
target_include_directories( ${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_link_libraries( ${PROJECT_NAME}
PUBLIC utilities
)
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
-35
View File
@@ -1,35 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <functional>
#include "structs.h"
namespace manifold {
std::vector<glm::ivec3> Triangulate(const Polygons &polys,
float precision = -1);
std::vector<Halfedge> Polygons2Edges(const Polygons &polys);
std::vector<Halfedge> Triangles2Edges(const std::vector<glm::ivec3> &triangles);
void CheckTopology(const std::vector<Halfedge> &halfedges);
void CheckTopology(const std::vector<glm::ivec3> &triangles,
const Polygons &polys);
void CheckGeometry(const std::vector<glm::ivec3> &triangles,
const Polygons &polys, float precision);
void Dump(const Polygons &polys);
ExecutionParams &PolygonParams();
} // namespace manifold
+43 -6
View File
@@ -1,14 +1,51 @@
project (samples)
# Copyright 2020 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
add_library(${PROJECT_NAME} src/knot.cpp src/bracelet.cpp src/mengerSponge.cpp src/roundedFrame.cpp src/tet_puzzle.cpp src/scallop.cpp)
project(samples)
target_include_directories( ${PROJECT_NAME}
add_library(samples src/bracelet.cpp src/knot.cpp src/menger_sponge.cpp src/rounded_frame.cpp src/scallop.cpp src/tet_puzzle.cpp)
target_include_directories(samples
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_link_libraries( ${PROJECT_NAME}
target_link_libraries(samples
PUBLIC manifold
)
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(samples PUBLIC cxx_std_14)
project(samplesGPU)
add_library(samplesGPU src/gyroid_module.cpp)
if(MANIFOLD_USE_CUDA)
set_source_files_properties(src/gyroid_module.cpp PROPERTIES LANGUAGE CUDA)
set_property(TARGET samplesGPU PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_include_directories(samplesGPU
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_link_libraries(samplesGPU
PUBLIC manifold sdf
)
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(samplesGPU
PUBLIC cxx_std_14
PRIVATE cxx_std_17
)
+22 -32
View File
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,51 +15,41 @@
#pragma once
#include "manifold.h"
// These are mostly 3D-printable designs I've invented over the years,
// translated from their original OpenSCAD to C++ to demonstrate the usage of
// this library. You can find the originals here:
// http://www.thingiverse.com/emmett
// These also each have tests you can find in test/samples_test.cpp, which have
// nice parameter choices for making some of the specific designs I print. While
// the source code is under the Apache License above, I license all of my
// designs (the output of those tests if you uncomment the export lines) under
// CC-BY-SA: https://creativecommons.org/licenses/by-sa/2.0/, which means you're
// welcome to print and sell them, so long as you attribute the design to
// Emmett Lalish and share any derivative works under the same license.
namespace manifold {
// p is the number of times the thread passes through the donut hole and q is
// the number of times the thread circles the donut. If p and q have a common
// factor then you will get multiple separate, interwoven knots. This is an
// example of using the Warp() method on a manifold, thus avoiding any
// handling of triangles. It also demonstrates copying meshes, since that is not
// automatic.
/** @defgroup Samples
* @brief Examples of usage and interesting designs
*
* These are mostly 3D-printable designs I've invented over the years,
* translated from their original OpenSCAD to C++ to demonstrate the usage of
* this library. You can find the originals here:
* http://www.thingiverse.com/emmett These also each have tests you can find in
* test/samples_test.cpp, which have nice parameter choices for making some of
* the specific designs I print. While the source code is under the Apache
* License above, I license all of my designs (the output of those tests if you
* uncomment the export lines) under CC-BY-SA:
* https://creativecommons.org/licenses/by-sa/2.0/, which means you're welcome
* to print and sell them, so long as you attribute the design to Emmett Lalish
* and share any derivative works under the same license.
* @{
*/
Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
float threadRadius, int circularSegments = 0,
int linearSegments = 0);
// The overall size is given by radius; the radius left for your wrist is
// roughly radius - height. Its length along your arm (the height of the print)
// is given by width. The thickness parameter is the width of the material,
// which should be equal to your printer's nozzle diameter. The number of twisty
// shapes around the outside is nDecor, while the number of cuts that enable
// stretching is nCut. The resolution is given by nDivision, the number of
// divisions along the width.
Manifold StretchyBracelet(float radius = 30.0f, float height = 8.0f,
float width = 15.0f, float thickness = 0.4f,
int nDecor = 20, int nCut = 27, int nDivision = 30);
// The classic cubic fractal, going down recursively n times.
Manifold MengerSponge(int n = 3);
// A cubic frame with cylinders for edges and spheres at the corners.
Manifold RoundedFrame(float edgeLength, float radius);
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments = 0);
// A tetrahedron cut into two halves that can screw together.
Manifold TetPuzzle(float edgeLength, float gap, int nDivisions);
// A smoothed manifold demonstrating selective edge sharpening. Refine before
// export to see the curvature.
Manifold Scallop();
Manifold GyroidModule(float size = 20, int n = 20);
/** @} */ // end of Samples
} // namespace manifold
Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 598 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

+22 -6
View File
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -31,12 +31,12 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
decorRadius * sind(dPhiDeg * i)),
0});
}
Manifold decor = std::move(Manifold::Extrude(circle, width, nDivision, 180)
.Scale({1.0f, 0.5f, 1.0f})
.Translate({0.0f, radius, 0.0f}));
Manifold decor = Manifold::Extrude(circle, width, nDivision, 180)
.Scale({1.0f, 0.5f, 1.0f})
.Translate({0.0f, radius, 0.0f});
for (int i = 0; i < nDecor; ++i) {
base += decor.Rotate(0, 0, 360.0f / nDecor);
base += decor.Rotate(0, 0, (360.0f / nDecor) * i);
}
Polygons stretch(1);
@@ -52,7 +52,7 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
}
base = Manifold::Extrude(stretch, width) ^ base;
base.SetAsOriginal();
base = base.AsOriginal();
return base;
}
@@ -60,6 +60,22 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
namespace manifold {
/**
* My Stretchy Bracelet: this is one of my most popular designs, largely because
* it's quick and easy to 3D print. The defaults are picked to work well; change
* the radius to fit your wrist. Changing the other values too much may break
* the design.
*
* @param radius The overall size; the radius left for your wrist is roughly
* radius - height.
* @param height Thickness of the bracelet around your wrist.
* @param width The length along your arm (the height of the print).
* @param thickness The width of the material, which should be equal to your
* printer's nozzle diameter.
* @param nDecor The number of twisty shapes around the outside.
* @param nCut The number of cuts that enable stretching.
* @param nDivision the number of divisions along the width.
*/
Manifold StretchyBracelet(float radius, float height, float width,
float thickness, int nDecor, int nCut,
int nDivision) {
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "samples.h"
#include "sdf.h"
namespace {
struct Gyroid {
__host__ __device__ float operator()(glm::vec3 p) const {
p -= glm::pi<float>() / 4;
return cos(p.x) * sin(p.y) + cos(p.y) * sin(p.z) + cos(p.z) * sin(p.x);
}
};
Manifold RhombicDodecahedron(float size) {
Manifold box =
Manifold::Cube(size * glm::sqrt(2.0f) * glm::vec3(1, 1, 2), true);
Manifold result = box.Rotate(90, 45) ^ box.Rotate(90, 45, 90);
return result ^ box.Rotate(0, 0, 45);
}
} // namespace
namespace manifold {
/**
* Creates a rhombic dodecahedral module of a gyroid manifold, which can be
* assembled together to tile space continuously. This one is designed to be
* 3D-printable, as it is oriented with minimal overhangs. This sample
* demonstrates the use of a Signed Distance Function (SDF) to create smooth,
* complex manifolds.
*/
Manifold GyroidModule(float size, int n) {
auto gyroid = [&](float level) {
const float period = glm::two_pi<float>();
return Manifold(LevelSet(Gyroid(), {glm::vec3(-period), glm::vec3(period)},
period / n, level))
.Scale(glm::vec3(size / period));
};
Manifold result = (RhombicDodecahedron(size) ^ gyroid(-0.4)) - gyroid(0.4);
return result.Rotate(-45, 0, 90).Translate({0, 0, size / glm::sqrt(2.0f)});
}
} // namespace manifold
+36 -21
View File
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <glm/gtx/rotate_vector.hpp>
#include "samples.h"
namespace {
@@ -23,6 +21,22 @@ int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
namespace manifold {
/**
* Creates a classic torus knot, defined as a string wrapping periodically
* around the surface of an imaginary donut. If p and q have a common factor
* then you will get multiple separate, interwoven knots. This is an example of
* using the Manifold.Warp() method, thus avoiding any handling of triangles.
*
* @param p The number of times the thread passes through the donut hole.
* @param q The number of times the thread circles the donut.
* @param majorRadius Radius of the interior of the imaginary donut.
* @param minorRadius Radius of the small cross-section of the imaginary donut.
* @param threadRadius Radius of the small cross-section of the actual object.
* @param circularSegments Number of linear segments making up the threadRadius
* circle. Default is Manifold.GetCircularSegments().
* @param linearSegments Number of segments along the length of the knot.
* Default makes roughly square facets.
*/
Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
float threadRadius, int circularSegments,
int linearSegments) {
@@ -42,31 +56,32 @@ Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
Manifold knot = Manifold::Revolve(circle, m);
knot.Warp([p, q, majorRadius, minorRadius, threadRadius](glm::vec3& v) {
float psi = q * atan2(v.x, v.y);
float theta = psi * p / q;
glm::vec2 xy = glm::vec2(v);
float x1 = sqrt(glm::dot(xy, xy));
float phi = atan2(x1 - 2, v.z);
v = glm::vec3(cos(phi), 0.0f, sin(phi));
v *= threadRadius;
float r = majorRadius + minorRadius * cos(theta);
v = glm::rotateX(v, -float(atan2(p * minorRadius, q * r)));
v.x += minorRadius;
v = glm::rotateY(v, theta);
v.x += majorRadius;
v = glm::rotateZ(v, psi);
});
knot =
knot.Warp([p, q, majorRadius, minorRadius, threadRadius](glm::vec3& v) {
float psi = q * atan2(v.x, v.y);
float theta = psi * p / q;
glm::vec2 xy = glm::vec2(v);
float x1 = sqrt(glm::dot(xy, xy));
float phi = atan2(x1 - 2, v.z);
v = glm::vec3(cos(phi), 0.0f, sin(phi));
v *= threadRadius;
float r = majorRadius + minorRadius * cos(theta);
v = glm::rotateX(v, -float(atan2(p * minorRadius, q * r)));
v.x += minorRadius;
v = glm::rotateY(v, theta);
v.x += majorRadius;
v = glm::rotateZ(v, psi);
});
if (kLoops > 1) {
std::vector<Manifold> knots;
for (float k = 0; k < kLoops; ++k) {
knots.emplace_back(knot);
knots.back().Rotate(0, 0, 360.0f * (k / kLoops) * (q / float(p)));
knots.push_back(
knot.Rotate(0, 0, 360.0f * (k / kLoops) * (q / float(p))));
}
knot = Manifold::Compose(knots);
}
return knot;
}
} // namespace manifold
} // namespace manifold
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -21,8 +21,8 @@ using namespace manifold;
void Fractal(std::vector<Manifold>& holes, Manifold& hole, float w,
glm::vec2 position, int depth, int maxDepth) {
w /= 3;
holes.emplace_back(hole);
holes.back().Scale({w, w, 1.0f}).Translate(glm::vec3(position, 0.0f));
holes.push_back(
hole.Scale({w, w, 1.0f}).Translate(glm::vec3(position, 0.0f)));
if (depth == maxDepth) return;
glm::vec2 offsets[8] = {{-w, -w}, {-w, 0.0f}, {-w, w}, {0.0f, w},
@@ -35,6 +35,12 @@ void Fractal(std::vector<Manifold>& holes, Manifold& hole, float w,
namespace manifold {
/**
* The classic cubic fractal.
*
* @param n Fractal depth. Warning: scales exponentially, n = 4 has almost
* 400,000 triangles!
*/
Manifold MengerSponge(int n) {
Manifold result = Manifold::Cube(glm::vec3(1.0f), true);
@@ -44,9 +50,16 @@ Manifold MengerSponge(int n) {
Manifold hole = Manifold::Compose(holes);
result -= hole;
result -= hole.Rotate(90);
result -= hole.Rotate(0, 0, 90);
result.SetAsOriginal();
hole = hole.Rotate(90);
result -= hole;
hole = hole.Rotate(0, 0, 90);
result -= hole;
// Alternative order causes degenerate triangles
// Manifold tmp1 = hole.Rotate(90) + hole.Rotate(90).Rotate(0, 0, 90);
// Manifold tmp2 = hole + tmp1;
// result = result - tmp2;
return result;
}
} // namespace manifold
-40
View File
@@ -1,40 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "samples.h"
namespace manifold {
Manifold RoundedFrame(float edgeLength, float radius) {
Manifold edge = Manifold::Cylinder(edgeLength, radius);
Manifold corner = Manifold::Sphere(radius);
Manifold edge1 = corner + edge;
edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0});
Manifold edge2 = edge1;
edge2.Rotate(0, 0, 180);
edge2 += edge1;
edge2 += edge.Translate({-edgeLength / 2, -edgeLength / 2, 0});
Manifold edge4 = edge2;
edge4.Rotate(0, 0, 90);
edge4 += edge2;
Manifold frame = edge4.Translate({0, 0, -edgeLength / 2});
frame += edge4.Rotate(180);
return frame;
}
} // namespace manifold
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "samples.h"
namespace manifold {
/**
* A cubic frame with cylinders for edges and spheres at the corners.
* Demonstrates how at 90-degree intersections, the sphere and cylinder facets
* match up perfectly.
*
* @param edgeLength Distance between the corners.
* @param radius Radius of the frame members.
* @param circularSegments Number of segments in the cylinders and spheres.
* Defaults to Manifold.GetCircularSegments().
*/
Manifold RoundedFrame(float edgeLength, float radius, int circularSegments) {
Manifold edge = Manifold::Cylinder(edgeLength, radius, -1, circularSegments);
Manifold corner = Manifold::Sphere(radius, circularSegments);
Manifold edge1 = corner + edge;
edge1 = edge1.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0});
Manifold edge2 = edge1.Rotate(0, 0, 180);
edge2 += edge1;
edge2 += edge.Translate({-edgeLength / 2, -edgeLength / 2, 0});
Manifold edge4 = edge2.Rotate(0, 0, 90);
edge4 += edge2;
Manifold frame = edge4.Translate({0, 0, -edgeLength / 2});
frame += frame.Rotate(180);
return frame;
}
} // namespace manifold
+6 -2
View File
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,10 @@
namespace manifold {
/**
* A smoothed manifold demonstrating selective edge sharpening with
* Manifold.Smooth(). Use Manifold.Refine() before export to see the curvature.
*/
Manifold Scallop() {
constexpr float height = 1;
constexpr float radius = 3;
@@ -30,7 +34,7 @@ Manifold Scallop() {
const float delta = glm::pi<float>() / wiggles;
for (int i = 0; i < 2 * wiggles; ++i) {
float theta = (i - wiggles) * delta;
float amp = 0.5 * height * glm::cos(theta / 2);
float amp = 0.5 * height * glm::max(glm::cos(0.8f * theta), 0.0f);
scallop.vertPos.push_back({radius * glm::cos(theta),
radius * glm::sin(theta),
+10 -1
View File
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,6 +16,15 @@
namespace manifold {
/**
* A tetrahedron cut into two identical halves that can screw together as a
* puzzle. This only outputs one of the halves. This demonstrates how redundant
* points along a polygon can be used to make twisted extrusions smoother.
*
* @param edgeLength Length of each edge of the overall tetrahedron.
* @param gap Spacing between the two halves to allow sliding.
* @param nDivisions Number of divisions (both ways) in the screw surface.
*/
Manifold TetPuzzle(float edgeLength, float gap, int nDivisions) {
const glm::vec3 scale(edgeLength / (2 * sqrt(2)));
+20
View File
@@ -0,0 +1,20 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
add_subdirectory(third_party)
add_subdirectory(utilities)
add_subdirectory(collider)
add_subdirectory(polygon)
add_subdirectory(manifold)
add_subdirectory(sdf)
+32
View File
@@ -0,0 +1,32 @@
# Copyright 2020 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
project (collider)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} ${SOURCE_FILES})
if(MANIFOLD_USE_CUDA)
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
target_compile_features(${PROJECT_NAME}
PUBLIC cxx_std_14
PRIVATE cxx_std_17
)
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,12 +13,13 @@
// limitations under the License.
#pragma once
#include "sparse.cuh"
#include "structs.h"
#include "vec_dh.cuh"
#include "public.h"
#include "sparse.h"
#include "vec_dh.h"
namespace manifold {
/** @ingroup Private */
class Collider {
public:
Collider() {}
@@ -26,10 +27,10 @@ class Collider {
// Aborts and returns false if transform is not axis aligned.
bool Transform(glm::mat4x3);
void UpdateBoxes(const VecDH<Box>& leafBB);
// Collisions returns a sparse result, where i is the querry index and j is
// Collisions returns a sparse result, where i is the query index and j is
// the leaf index where their bounding boxes overlap.
template <typename T>
SparseIndices Collisions(const VecDH<T>& querriesIn) const;
SparseIndices Collisions(const VecDH<T>& queriesIn) const;
private:
VecDH<Box> nodeBBox_;
@@ -41,4 +42,4 @@ class Collider {
int NumLeaves() const { return NumInternal() + 1; };
};
} // namespace manifold
} // namespace manifold
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,8 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "collider.cuh"
#include "utils.cuh"
#include "collider.h"
#include "par.h"
#include "utils.h"
#ifdef _MSC_VER
#include <intrin.h>
#endif
// Adjustable parameters
constexpr int kInitialLength = 128;
@@ -21,6 +27,35 @@ constexpr int kLengthMultiple = 4;
// Fundamental constants
constexpr int kRoot = 1;
#ifdef _MSC_VER
#ifndef _WINDEF_
typedef unsigned long DWORD;
#endif
uint32_t __inline ctz(uint32_t value) {
DWORD trailing_zero = 0;
if (_BitScanForward(&trailing_zero, value)) {
return trailing_zero;
} else {
// This is undefined, I better choose 32 than 0
return 32;
}
}
uint32_t __inline clz(uint32_t value) {
DWORD leading_zero = 0;
if (_BitScanReverse(&leading_zero, value)) {
return 31 - leading_zero;
} else {
// Same remarks as above
return 32;
}
}
#endif
namespace {
using namespace manifold;
@@ -34,7 +69,7 @@ __host__ __device__ int Leaf2Node(int leaf) { return leaf * 2; }
struct CreateRadixTree {
int* nodeParent_;
thrust::pair<int, int>* internalChildren_;
const VecD<uint32_t> leafMorton_;
const VecDc<uint32_t> leafMorton_;
__host__ __device__ int PrefixLength(uint32_t a, uint32_t b) const {
// count-leading-zeros is used to find the number of identical highest-order
@@ -42,7 +77,14 @@ struct CreateRadixTree {
#ifdef __CUDA_ARCH__
return __clz(a ^ b);
#else
#ifdef _MSC_VER
// return __lzcnt(a ^ b);
return clz(a ^ b);
#else
return __builtin_clz(a ^ b);
#endif
#endif
}
@@ -115,26 +157,28 @@ struct CreateRadixTree {
}
};
template <typename T>
template <typename T, const bool allocateOnly>
struct FindCollisions {
thrust::pair<int*, int*> querryTri_;
int* numOverlaps_;
const int maxOverlaps_;
thrust::pair<int*, int*> queryTri_;
int* counts;
const Box* nodeBBox_;
const thrust::pair<int, int>* internalChildren_;
__host__ __device__ int RecordCollision(int node,
const thrust::tuple<T, int>& query) {
thrust::tuple<T, int>& query) {
const T& queryObj = thrust::get<0>(query);
const int queryIdx = thrust::get<1>(query);
int& count = counts[queryIdx];
bool overlaps = nodeBBox_[node].DoesOverlap(queryObj);
if (overlaps && IsLeaf(node)) {
int pos = AtomicAdd(*numOverlaps_, 1);
if (pos >= maxOverlaps_)
return -1; // Didn't allocate enough memory; bail out
querryTri_.first[pos] = queryIdx;
querryTri_.second[pos] = Node2Leaf(node);
if (allocateOnly) {
count++;
} else {
int pos = count++;
queryTri_.first[pos] = queryIdx;
queryTri_.second[pos] = Node2Leaf(node);
}
}
return overlaps && IsInternal(node); // Should traverse into node
}
@@ -146,15 +190,16 @@ struct FindCollisions {
int top = -1;
// Depth-first search
int node = kRoot;
const int queryIdx = thrust::get<1>(query);
// same implies that this query do not have any collision
if (!allocateOnly && counts[queryIdx] == counts[queryIdx + 1]) return;
while (1) {
int internal = Node2Internal(node);
int child1 = internalChildren_[internal].first;
int child2 = internalChildren_[internal].second;
int traverse1 = RecordCollision(child1, query);
if (traverse1 < 0) return;
int traverse2 = RecordCollision(child2, query);
if (traverse2 < 0) return;
if (!traverse1 && !traverse2) {
if (top < 0) break; // done
@@ -204,51 +249,47 @@ namespace manifold {
*/
Collider::Collider(const VecDH<Box>& leafBB,
const VecDH<uint32_t>& leafMorton) {
ALWAYS_ASSERT(leafBB.size() == leafMorton.size(), userErr,
"vectors must be the same length");
ASSERT(leafBB.size() == leafMorton.size(), userErr,
"vectors must be the same length");
int num_nodes = 2 * leafBB.size() - 1;
// assign and allocate members
nodeBBox_.resize(num_nodes);
nodeParent_.resize(num_nodes, -1);
internalChildren_.resize(leafBB.size() - 1, thrust::make_pair(-1, -1));
// organize tree
thrust::for_each_n(countAt(0), NumInternal(),
CreateRadixTree({nodeParent_.ptrD(),
internalChildren_.ptrD(), leafMorton}));
for_each_n(autoPolicy(NumInternal()), countAt(0), NumInternal(),
CreateRadixTree(
{nodeParent_.ptrD(), internalChildren_.ptrD(), leafMorton}));
UpdateBoxes(leafBB);
}
/**
* For a vector of querry objects, this returns a sparse array of overlaps
* between the querries and the bounding boxes of the collider. Querries are
* For a vector of query objects, this returns a sparse array of overlaps
* between the queries and the bounding boxes of the collider. Queries are
* normally axis-aligned bounding boxes. Points can also be used, and this case
* overlaps are defined as lying in the XY projection of the bounding box.
*/
template <typename T>
SparseIndices Collider::Collisions(const VecDH<T>& querriesIn) const {
int maxOverlaps = 1 << 20;
SparseIndices querryTri(maxOverlaps);
int nOverlaps = 0;
while (1) {
// scalar number of overlaps found
VecDH<int> nOverlapsD(1, 0);
// calculate Bounding Box overlaps
thrust::for_each_n(
zip(querriesIn.cbeginD(), countAt(0)), querriesIn.size(),
FindCollisions<T>({querryTri.ptrDpq(), nOverlapsD.ptrD(), maxOverlaps,
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
nOverlaps = nOverlapsD.H()[0];
if (nOverlaps <= maxOverlaps)
break;
else { // if not enough memory was allocated, guess how much will be needed
int lastQuery = querryTri.Get(0).H().back();
maxOverlaps *= 2 * static_cast<float>(querriesIn.size()) / lastQuery;
querryTri.Resize(maxOverlaps);
}
}
// remove unused part of array
querryTri.Resize(nOverlaps);
return querryTri;
SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
// note that the length is 1 larger than the number of queries so the last
// element can store the sum when using exclusive scan
VecDH<int> counts(queriesIn.size() + 1, 0);
auto policy = autoPolicy(queriesIn.size());
// compute the number of collisions to determine the size for allocation and
// offset, this avoids the need for atomic
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
FindCollisions<T, true>(
{thrust::pair<int*, int*>(nullptr, nullptr), counts.ptrD(),
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
// compute start index for each query and total count
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin());
SparseIndices queryTri(counts.back());
// actually recording collisions
for_each_n(
policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
FindCollisions<T, false>({queryTri.ptrDpq(), counts.ptrD(),
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
return queryTri;
}
/**
@@ -256,19 +297,18 @@ SparseIndices Collider::Collisions(const VecDH<T>& querriesIn) const {
* hierarchy.
*/
void Collider::UpdateBoxes(const VecDH<Box>& leafBB) {
ALWAYS_ASSERT(leafBB.size() == NumLeaves(), userErr,
"must have the same number of updated boxes as original");
// copy in leaf node Boxs
strided_range<VecDH<Box>::IterD> leaves(nodeBBox_.beginD(), nodeBBox_.endD(),
2);
thrust::copy(leafBB.cbeginD(), leafBB.cendD(), leaves.begin());
ASSERT(leafBB.size() == NumLeaves(), userErr,
"must have the same number of updated boxes as original");
// copy in leaf node Boxes
strided_range<VecDH<Box>::Iter> leaves(nodeBBox_.begin(), nodeBBox_.end(), 2);
auto policy = autoPolicy(NumInternal());
copy(policy, leafBB.cbegin(), leafBB.cend(), leaves.begin());
// create global counters
VecDH<int> counter_(NumInternal());
thrust::fill(counter_.beginD(), counter_.endD(), 0);
// kernel over leaves to save internal Boxs
thrust::for_each_n(
countAt(0), NumLeaves(),
BuildInternalBoxes({nodeBBox_.ptrD(), counter_.ptrD(), nodeParent_.ptrD(),
VecDH<int> counter(NumInternal(), 0);
// kernel over leaves to save internal Boxes
for_each_n(
policy, countAt(0), NumLeaves(),
BuildInternalBoxes({nodeBBox_.ptrD(), counter.ptrD(), nodeParent_.ptrD(),
internalChildren_.ptrD()}));
}
@@ -286,8 +326,8 @@ bool Collider::Transform(glm::mat4x3 transform) {
if (count != 2) axisAligned = false;
}
if (axisAligned) {
thrust::for_each(nodeBBox_.beginD(), nodeBBox_.endD(),
TransformBox({transform}));
for_each(autoPolicy(nodeBBox_.size()), nodeBBox_.begin(), nodeBBox_.end(),
TransformBox({transform}));
}
return axisAligned;
}
@@ -297,4 +337,4 @@ template SparseIndices Collider::Collisions<Box>(const VecDH<Box>&) const;
template SparseIndices Collider::Collisions<glm::vec3>(
const VecDH<glm::vec3>&) const;
} // namespace manifold
} // namespace manifold
+35
View File
@@ -0,0 +1,35 @@
# Copyright 2020 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
project (manifold)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} ${SOURCE_FILES})
if(MANIFOLD_USE_CUDA)
set_source_files_properties( ${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
target_link_libraries(${PROJECT_NAME}
PUBLIC utilities
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite
)
target_compile_features(${PROJECT_NAME}
PUBLIC cxx_std_14
PRIVATE cxx_std_17
)
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,14 +16,38 @@
#include <functional>
#include <memory>
#include "structs.h"
#include "public.h"
namespace manifold {
/**
* @ingroup Debug
*
* Allows modification of the assertions checked in MANIFOLD_DEBUG mode.
*
* @return ExecutionParams&
*/
ExecutionParams& ManifoldParams();
class CsgNode;
class CsgLeafNode;
/** @defgroup Core
* @brief The central classes of the library
* @{
*/
class Manifold {
public:
// Creation
/** @name Creation
* Constructors
*/
///@{
Manifold();
~Manifold();
Manifold(const Manifold& other);
Manifold& operator=(const Manifold& other);
Manifold(Manifold&&) noexcept;
Manifold& operator=(Manifold&&) noexcept;
Manifold(
const Mesh&,
@@ -44,20 +68,48 @@ class Manifold {
glm::vec2 scaleTop = glm::vec2(1.0f));
static Manifold Revolve(const Polygons& crossSection,
int circularSegments = 0);
///@}
// Topological
/** @name Topological
* No geometric calculations.
*/
///@{
static Manifold Compose(const std::vector<Manifold>&);
std::vector<Manifold> Decompose() const;
///@}
// Defaults for construction
/** @name Defaults
* These static properties control how circular shapes are quantized by
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
static void SetMinCircularAngle(float degrees);
static void SetMinCircularEdgeLength(float length);
static void SetCircularSegments(int number);
static int GetCircularSegments(float radius);
///@}
// Information
/** @name Information
* Details of the manifold
*/
///@{
Mesh GetMesh() const;
MeshGL GetMeshGL() const;
bool IsEmpty() const;
enum class Error {
NO_ERROR,
NON_FINITE_VERTEX,
NOT_MANIFOLD,
VERTEX_INDEX_OUT_OF_BOUNDS,
PROPERTIES_WRONG_LENGTH,
TRI_PROPERTIES_WRONG_LENGTH,
TRI_PROPERTIES_OUT_OF_BOUNDS,
};
Error Status() const;
int NumVert() const;
int NumEdge() const;
int NumTri() const;
@@ -66,27 +118,43 @@ class Manifold {
int Genus() const;
Properties GetProperties() const;
Curvature GetCurvature() const;
///@}
// Relation
/** @name Relation
* Details of the manifold's relation to its input meshes, for the purposes
* of reapplying mesh properties.
*/
///@{
MeshRelation GetMeshRelation() const;
std::vector<int> GetMeshIDs() const;
int SetAsOriginal();
static std::vector<int> MeshID2Original();
int OriginalID() const;
Manifold AsOriginal() const;
///@}
// Modification
Manifold& Translate(glm::vec3);
Manifold& Scale(glm::vec3);
Manifold& Rotate(float xDegrees, float yDegrees = 0.0f,
float zDegrees = 0.0f);
Manifold& Transform(const glm::mat4x3&);
Manifold& Warp(std::function<void(glm::vec3&)>);
Manifold& Refine(int);
/** @name Modification
*/
///@{
Manifold Translate(glm::vec3) const;
Manifold Scale(glm::vec3) const;
Manifold Rotate(float xDegrees, float yDegrees = 0.0f,
float zDegrees = 0.0f) const;
Manifold Transform(const glm::mat4x3&) const;
Manifold Warp(std::function<void(glm::vec3&)>) const;
Manifold Refine(int) const;
// Manifold RefineToLength(float);
// Manifold RefineToPrecision(float);
///@}
// Boolean
/** @name Boolean
* Combine two manifolds
*/
///@{
/**
* Boolean operation type: ADD (Union), SUBTRACT (Difference), and INTERSECT.
*/
enum class OpType { ADD, SUBTRACT, INTERSECT };
Manifold Boolean(const Manifold& second, OpType op) const;
static Manifold BatchBoolean(const std::vector<Manifold>& manifolds,
OpType op);
// Boolean operation shorthand
Manifold operator+(const Manifold&) const; // ADD (Union)
Manifold& operator+=(const Manifold&);
@@ -98,24 +166,31 @@ class Manifold {
std::pair<Manifold, Manifold> SplitByPlane(glm::vec3 normal,
float originOffset) const;
Manifold TrimByPlane(glm::vec3 normal, float originOffset) const;
///@}
// Testing hooks
/** @name Testing hooks
* These are just for internal testing.
*/
///@{
bool IsManifold() const;
bool MatchesTriNormals() const;
int NumDegenerateTris() const;
int NumOverlaps(const Manifold& second) const;
///@}
~Manifold();
Manifold(const Manifold& other);
Manifold& operator=(const Manifold& other);
Manifold(Manifold&&) noexcept;
Manifold& operator=(Manifold&&) noexcept;
struct Impl;
private:
std::unique_ptr<Impl> pImpl_;
Manifold(std::shared_ptr<CsgNode> pNode_);
Manifold(std::shared_ptr<Impl> pImpl_);
mutable std::shared_ptr<CsgNode> pNode_;
CsgLeafNode& GetCsgLeafNode() const;
static int circularSegments_;
static float circularAngle_;
static float circularEdgeLength_;
};
} // namespace manifold
/** @} */
} // namespace manifold
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -12,10 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "boolean3.cuh"
#include "boolean3.h"
// TODO: make this runtime configurable for quicker debug
constexpr bool kVerbose = false;
#include <limits>
#include "par.h"
using namespace manifold;
@@ -29,7 +30,9 @@ namespace {
__host__ __device__ glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
float dxL = x - pL.x;
float dxR = x - pR.x;
#ifdef MANIFOLD_DEBUG
if (dxL * dxR > 0) printf("Not in domain!\n");
#endif
bool useL = fabs(dxL) < fabs(dxR);
float lambda = (useL ? dxL : dxR) / (pR.x - pL.x);
if (!isfinite(lambda)) return glm::vec2(pL.y, pL.z);
@@ -45,7 +48,9 @@ __host__ __device__ glm::vec4 Intersect(const glm::vec3 &pL,
const glm::vec3 &qR) {
float dyL = qL.y - pL.y;
float dyR = qR.y - pR.y;
#ifdef MANIFOLD_DEBUG
if (dyL * dyR > 0) printf("No intersection!\n");
#endif
bool useL = fabs(dyL) < fabs(dyR);
float dx = pR.x - pL.x;
float lambda = (useL ? dyL : dyR) / (dyL - dyR);
@@ -82,18 +87,18 @@ struct CopyFaceEdges {
};
SparseIndices Filter11(const Manifold::Impl &inP, const Manifold::Impl &inQ,
const SparseIndices &p1q2, const SparseIndices &p2q1) {
const SparseIndices &p1q2, const SparseIndices &p2q1,
ExecutionPolicy policy) {
SparseIndices p1q1(3 * p1q2.size() + 3 * p2q1.size());
thrust::for_each_n(zip(countAt(0), p1q2.beginD(0), p1q2.beginD(1)),
p1q2.size(),
CopyFaceEdges({p1q1.ptrDpq(), inQ.halfedge_.cptrD()}));
for_each_n(policy, zip(countAt(0), p1q2.begin(0), p1q2.begin(1)), p1q2.size(),
CopyFaceEdges({p1q1.ptrDpq(), inQ.halfedge_.cptrD()}));
p1q1.SwapPQ();
thrust::for_each_n(zip(countAt(p1q2.size()), p2q1.beginD(1), p2q1.beginD(0)),
p2q1.size(),
CopyFaceEdges({p1q1.ptrDpq(), inP.halfedge_.cptrD()}));
for_each_n(policy, zip(countAt(p1q2.size()), p2q1.begin(1), p2q1.begin(0)),
p2q1.size(),
CopyFaceEdges({p1q1.ptrDpq(), inP.halfedge_.cptrD()}));
p1q1.SwapPQ();
p1q1.Unique();
p1q1.Unique(policy);
return p1q1;
}
@@ -101,6 +106,17 @@ __host__ __device__ bool Shadows(float p, float q, float dir) {
return p == q ? dir < 0 : p < q;
}
/**
* Since this function is called from two different places, it is necessary that
* it returns identical results for identical input to keep consistency.
* Normally this is trivial as computers make floating-point errors, but are
* at least deterministic. However, in the case of CUDA, these functions can be
* compiled by two different compilers (for the CPU and GPU). We have found that
* the different compilers can cause slightly different rounding errors, so it
* is critical that the two places this function is called both use the same
* compiled function (they must agree on CPU or GPU). This is now taken care of
* by the shared policy_ member.
*/
__host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
const int p0, const int q1, const glm::vec3 *vertPosP,
const glm::vec3 *vertPosQ, const Halfedge *halfedgeQ, const float expandP,
@@ -114,7 +130,7 @@ __host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
Shadows(q1ex, p0x, expandP * normalP[q1e].x)
: Shadows(p0x, q1ex, expandP * normalP[p0].x) -
Shadows(p0x, q1sx, expandP * normalP[p0].x);
glm::vec2 yz01(0.0f / 0.0f);
glm::vec2 yz01(NAN);
if (s01 != 0) {
yz01 = Interpolate(vertPosQ[q1s], vertPosQ[q1e], vertPosP[p0].x);
@@ -175,7 +191,7 @@ struct Kernel11 {
glm::vec3 pRL[2], qRL[2];
// Either the left or right must shadow, but not both. This ensures the
// intersection is between the left and right.
bool shadows;
bool shadows = false;
s11 = 0;
const int p0[2] = {halfedgeP[p1].startVert, halfedgeP[p1].endVert};
@@ -215,13 +231,14 @@ struct Kernel11 {
}
if (s11 == 0) { // No intersection
xyzz11 = glm::vec4(0.0f / 0.0f);
xyzz11 = glm::vec4(NAN);
} else {
#ifdef MANIFOLD_DEBUG
// Assert left and right were both found
if (k != 2) {
printf("k = %d\n", k);
}
#endif
xyzz11 = Intersect(pRL[0], pRL[1], qRL[0], qRL[1]);
const int p1s = halfedgeP[p1].startVert;
@@ -240,16 +257,17 @@ struct Kernel11 {
std::tuple<VecDH<int>, VecDH<glm::vec4>> Shadow11(SparseIndices &p1q1,
const Manifold::Impl &inP,
const Manifold::Impl &inQ,
float expandP) {
float expandP,
ExecutionPolicy policy) {
VecDH<int> s11(p1q1.size());
VecDH<glm::vec4> xyzz11(p1q1.size());
thrust::for_each_n(
zip(xyzz11.beginD(), s11.beginD(), p1q1.beginD(0), p1q1.beginD(1)),
p1q1.size(),
Kernel11({inP.vertPos_.cptrD(), inQ.vertPos_.cptrD(),
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(), expandP,
inP.vertNormal_.cptrD()}));
for_each_n(policy,
zip(xyzz11.begin(), s11.begin(), p1q1.begin(0), p1q1.begin(1)),
p1q1.size(),
Kernel11({inP.vertPos_.cptrD(), inQ.vertPos_.cptrD(),
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(), expandP,
inP.vertNormal_.cptrD()}));
p1q1.KeepFinite(xyzz11, s11);
@@ -276,9 +294,9 @@ struct Kernel02 {
glm::vec3 yzzRL[2];
// Either the left or right must shadow, but not both. This ensures the
// intersection is between the left and right.
bool shadows;
int closestVert;
float minMetric = 1.0f / 0.0f;
bool shadows = false;
int closestVert = -1;
float minMetric = std::numeric_limits<float>::infinity();
s02 = 0;
const glm::vec3 posP = vertPosP[p0];
@@ -312,18 +330,20 @@ struct Kernel02 {
}
if (s02 == 0) { // No intersection
z02 = 0.0f / 0.0f;
z02 = NAN;
} else {
#ifdef MANIFOLD_DEBUG
// Assert left and right were both found
if (k != 2) {
printf("k = %d\n", k);
}
#endif
glm::vec3 vertPos = vertPosP[p0];
z02 = Interpolate(yzzRL[0], yzzRL[1], vertPos.y)[1];
if (forward) {
if (!Shadows(vertPos.z, z02, expandP * vertNormalP[p0].z)) s02 = 0;
} else {
// ASSERT(closestVert != -1, topologyErr, "No closest vert");
if (!Shadows(z02, vertPos.z, expandP * vertNormalP[closestVert].z))
s02 = 0;
}
@@ -334,15 +354,16 @@ struct Kernel02 {
std::tuple<VecDH<int>, VecDH<float>> Shadow02(const Manifold::Impl &inP,
const Manifold::Impl &inQ,
SparseIndices &p0q2, bool forward,
float expandP) {
float expandP,
ExecutionPolicy policy) {
VecDH<int> s02(p0q2.size());
VecDH<float> z02(p0q2.size());
auto vertNormalP =
forward ? inP.vertNormal_.cptrD() : inQ.vertNormal_.cptrD();
thrust::for_each_n(
zip(s02.beginD(), z02.beginD(), p0q2.beginD(!forward),
p0q2.beginD(forward)),
for_each_n(
policy,
zip(s02.begin(), z02.begin(), p0q2.begin(!forward), p0q2.begin(forward)),
p0q2.size(),
Kernel02({inP.vertPos_.cptrD(), inQ.halfedge_.cptrD(),
inQ.vertPos_.cptrD(), forward, expandP, vertNormalP}));
@@ -379,7 +400,7 @@ struct Kernel12 {
glm::vec3 xzyLR1[2];
// Either the left or right must shadow, but not both. This ensures the
// intersection is between the left and right.
bool shadows;
bool shadows = false;
x12 = 0;
const Halfedge edge = halfedgesP[p1];
@@ -427,12 +448,14 @@ struct Kernel12 {
}
if (x12 == 0) { // No intersection
v12 = glm::vec3(0.0f / 0.0f);
v12 = glm::vec3(NAN);
} else {
#ifdef MANIFOLD_DEBUG
// Assert left and right were both found
if (k != 2) {
printf("k = %d\n", k);
}
#endif
const glm::vec4 xzyy =
Intersect(xzyLR0[0], xzyLR0[1], xzyLR1[0], xzyLR1[1]);
v12.x = xzyy[0];
@@ -446,13 +469,13 @@ std::tuple<VecDH<int>, VecDH<glm::vec3>> Intersect12(
const Manifold::Impl &inP, const Manifold::Impl &inQ, const VecDH<int> &s02,
const SparseIndices &p0q2, const VecDH<int> &s11, const SparseIndices &p1q1,
const VecDH<float> &z02, const VecDH<glm::vec4> &xyzz11,
SparseIndices &p1q2, bool forward) {
SparseIndices &p1q2, bool forward, ExecutionPolicy policy) {
VecDH<int> x12(p1q2.size());
VecDH<glm::vec3> v12(p1q2.size());
thrust::for_each_n(
zip(x12.beginD(), v12.beginD(), p1q2.beginD(!forward),
p1q2.beginD(forward)),
for_each_n(
policy,
zip(x12.begin(), v12.begin(), p1q2.begin(!forward), p1q2.begin(forward)),
p1q2.size(),
Kernel12({p0q2.ptrDpq(), s02.ptrD(), z02.cptrD(), p0q2.size(),
p1q1.ptrDpq(), s11.ptrD(), xyzz11.cptrD(), p1q1.size(),
@@ -465,24 +488,24 @@ std::tuple<VecDH<int>, VecDH<glm::vec3>> Intersect12(
};
VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
VecDH<int> &s02, bool reverse) {
VecDH<int> &s02, bool reverse, ExecutionPolicy policy) {
// verts that are not shadowed (not in p0q2) have winding number zero.
VecDH<int> w03(inP.NumVert(), 0);
if (!thrust::is_sorted(p0q2.beginD(reverse), p0q2.endD(reverse)))
thrust::sort_by_key(p0q2.beginD(reverse), p0q2.endD(reverse), s02.beginD());
if (!is_sorted(policy, p0q2.begin(reverse), p0q2.end(reverse)))
sort_by_key(policy, p0q2.begin(reverse), p0q2.end(reverse), s02.begin());
VecDH<int> w03val(w03.size());
VecDH<int> w03vert(w03.size());
// sum known s02 values into w03 (winding number)
auto endPair =
thrust::reduce_by_key(p0q2.beginD(reverse), p0q2.endD(reverse),
s02.beginD(), w03vert.beginD(), w03val.beginD());
thrust::scatter(w03val.beginD(), endPair.second, w03vert.beginD(),
w03.beginD());
auto endPair = reduce_by_key<
thrust::pair<decltype(w03val.begin()), decltype(w03val.begin())>>(
policy, p0q2.begin(reverse), p0q2.end(reverse), s02.begin(),
w03vert.begin(), w03val.begin());
scatter(policy, w03val.begin(), endPair.second, w03vert.begin(), w03.begin());
if (reverse)
thrust::transform(w03.beginD(), w03.endD(), w03.beginD(),
thrust::negate<int>());
transform(policy, w03.begin(), w03.end(), w03.begin(),
thrust::negate<int>());
return w03;
};
} // namespace
@@ -490,16 +513,21 @@ VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
namespace manifold {
Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
Manifold::OpType op)
: inP_(inP), inQ_(inQ), expandP_(op == Manifold::OpType::ADD ? 1.0 : -1.0) {
: inP_(inP),
inQ_(inQ),
expandP_(op == Manifold::OpType::ADD ? 1.0 : -1.0),
policy_(autoPolicy(glm::max(inP.NumEdge(), inQ.NumEdge()))) {
// Symbolic perturbation:
// Union -> expand inP
// Difference, Intersection -> contract inP
Timer filter;
filter.Start();
#ifdef MANIFOLD_DEBUG
Timer broad;
broad.Start();
#endif
if (inP.IsEmpty() || inQ.IsEmpty() || !inP.bBox_.DoesOverlap(inQ.bBox_)) {
if (kVerbose) std::cout << "No overlap, early out" << std::endl;
PRINT("No overlap, early out");
w03_.resize(inP.NumVert(), 0);
w30_.resize(inQ.NumVert(), 0);
return;
@@ -508,76 +536,82 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
// Level 3
// Find edge-triangle overlaps (broad phase)
p1q2_ = inQ_.EdgeCollisions(inP_);
p1q2_.Sort();
if (kVerbose) std::cout << "p1q2 size = " << p1q2_.size() << std::endl;
p2q1_ = inP_.EdgeCollisions(inQ_);
policy_ = autoPolicy(glm::max(p1q2_.size(), p2q1_.size()));
p1q2_.Sort(policy_);
PRINT("p1q2 size = " << p1q2_.size());
p2q1_.SwapPQ();
p2q1_.Sort();
if (kVerbose) std::cout << "p2q1 size = " << p2q1_.size() << std::endl;
p2q1_.Sort(policy_);
PRINT("p2q1 size = " << p2q1_.size());
// Level 2
// Find vertices that overlap faces in XY-projection
SparseIndices p0q2 = inQ.VertexCollisionsZ(inP.vertPos_);
p0q2.Sort();
if (kVerbose) std::cout << "p0q2 size = " << p0q2.size() << std::endl;
p0q2.Sort(policy_);
PRINT("p0q2 size = " << p0q2.size());
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_);
p2q0.SwapPQ();
p2q0.Sort();
if (kVerbose) std::cout << "p2q0 size = " << p2q0.size() << std::endl;
p2q0.Sort(policy_);
PRINT("p2q0 size = " << p2q0.size());
// Find involved edge pairs from Level 3
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_);
if (kVerbose) std::cout << "p1q1 size = " << p1q1.size() << std::endl;
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_, policy_);
PRINT("p1q1 size = " << p1q1.size());
filter.Stop();
Timer levels;
levels.Start();
#ifdef MANIFOLD_DEBUG
broad.Stop();
Timer intersections;
intersections.Start();
#endif
// Level 2
// Build up XY-projection intersection of two edges, including the z-value for
// each edge, keeping only those whose intersection exists.
VecDH<int> s11;
VecDH<glm::vec4> xyzz11;
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_);
if (kVerbose) std::cout << "s11 size = " << s11.size() << std::endl;
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_, policy_);
PRINT("s11 size = " << s11.size());
// Build up Z-projection of vertices onto triangles, keeping only those that
// fall inside the triangle.
VecDH<int> s02;
VecDH<float> z02;
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_);
if (kVerbose) std::cout << "s02 size = " << s02.size() << std::endl;
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_, policy_);
PRINT("s02 size = " << s02.size());
VecDH<int> s20;
VecDH<float> z20;
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_);
if (kVerbose) std::cout << "s20 size = " << s20.size() << std::endl;
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_, policy_);
PRINT("s20 size = " << s20.size());
// Level 3
// Build up the intersection of the edges and triangles, keeping only those
// that intersect, and record the direction the edge is passing through the
// triangle.
std::tie(x12_, v12_) =
Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02, xyzz11, p1q2_, true);
if (kVerbose) std::cout << "x12 size = " << x12_.size() << std::endl;
std::tie(x12_, v12_) = Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02,
xyzz11, p1q2_, true, policy_);
PRINT("x12 size = " << x12_.size());
std::tie(x21_, v21_) =
Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20, xyzz11, p2q1_, false);
if (kVerbose) std::cout << "x21 size = " << x21_.size() << std::endl;
std::tie(x21_, v21_) = Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20,
xyzz11, p2q1_, false, policy_);
PRINT("x21 size = " << x21_.size());
// Sum up the winding numbers of all vertices.
w03_ = Winding03(inP, p0q2, s02, false);
w03_ = Winding03(inP, p0q2, s02, false, policy_);
w30_ = Winding03(inQ, p2q0, s20, true);
w30_ = Winding03(inQ, p2q0, s20, true, policy_);
levels.Stop();
#ifdef MANIFOLD_DEBUG
intersections.Stop();
if (kVerbose) {
filter.Print("Filter");
levels.Print("Levels 1-3");
if (ManifoldParams().verbose) {
broad.Print("Broad phase");
intersections.Print("Intersections");
MemUsage();
}
#endif
}
} // namespace manifold
} // namespace manifold
@@ -1,4 +1,4 @@
// Copyright 2020 Emmett Lalish
// Copyright 2020 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -13,7 +13,14 @@
// limitations under the License.
#pragma once
#include "impl.cuh"
#include "impl.h"
#ifdef MANIFOLD_DEBUG
#define PRINT(msg) \
if (ManifoldParams().verbose) std::cout << msg << std::endl;
#else
#define PRINT(msg)
#endif
/**
* The notation in these files is abbreviated due to the complexity of the
@@ -31,12 +38,13 @@
*
* Note many functions are designed to work symmetrically, for instance for both
* p2q1 and p1q2. Inside of these functions P and Q are marked as though the
* funtion is forwards, but it may include a Boolean "reverse" that indicates P
* function is forwards, but it may include a Boolean "reverse" that indicates P
* and Q have been swapped.
*/
namespace manifold {
/** @ingroup Private */
class Boolean3 {
public:
Boolean3(const Manifold::Impl& inP, const Manifold::Impl& inQ,
@@ -49,5 +57,6 @@ class Boolean3 {
SparseIndices p1q2_, p2q1_;
VecDH<int> x12_, x21_, w03_, w30_;
VecDH<glm::vec3> v12_, v21_;
ExecutionPolicy policy_;
};
} // namespace manifold
} // namespace manifold
@@ -1,4 +1,4 @@
// Copyright 2021 Emmett Lalish
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,12 +15,10 @@
#include <algorithm>
#include <map>
#include "boolean3.cuh"
#include "boolean3.h"
#include "par.h"
#include "polygon.h"
// TODO: make this runtime configurable for quicker debug
constexpr bool kVerbose = false;
using namespace manifold;
using namespace thrust::placeholders;
@@ -78,52 +76,53 @@ std::tuple<VecDH<int>, VecDH<int>> SizeOutput(
Manifold::Impl &outR, const Manifold::Impl &inP, const Manifold::Impl &inQ,
const VecDH<int> &i03, const VecDH<int> &i30, const VecDH<int> &i12,
const VecDH<int> &i21, const SparseIndices &p1q2, const SparseIndices &p2q1,
bool invertQ) {
VecDH<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri());
bool invertQ, ExecutionPolicy policy) {
VecDH<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);
auto sidesPerFaceP = sidesPerFacePQ.ptrD();
auto sidesPerFaceQ = sidesPerFacePQ.ptrD() + inP.NumTri();
thrust::for_each(inP.halfedge_.beginD(), inP.halfedge_.endD(),
CountVerts({sidesPerFaceP, i03.cptrD()}));
thrust::for_each(inQ.halfedge_.beginD(), inQ.halfedge_.endD(),
CountVerts({sidesPerFaceQ, i30.cptrD()}));
thrust::for_each_n(
zip(p1q2.beginD(0), p1q2.beginD(1), i12.beginD()), i12.size(),
for_each(policy, inP.halfedge_.begin(), inP.halfedge_.end(),
CountVerts({sidesPerFaceP, i03.cptrD()}));
for_each(policy, inQ.halfedge_.begin(), inQ.halfedge_.end(),
CountVerts({sidesPerFaceQ, i30.cptrD()}));
for_each_n(
policy, zip(p1q2.begin(0), p1q2.begin(1), i12.begin()), i12.size(),
CountNewVerts({sidesPerFaceP, sidesPerFaceQ, inP.halfedge_.cptrD()}));
thrust::for_each_n(
zip(p2q1.beginD(1), p2q1.beginD(0), i21.beginD()), i21.size(),
for_each_n(
policy, zip(p2q1.begin(1), p2q1.begin(0), i21.begin()), i21.size(),
CountNewVerts({sidesPerFaceQ, sidesPerFaceP, inQ.halfedge_.cptrD()}));
VecDH<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1);
VecDH<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);
auto keepFace =
thrust::make_transform_iterator(sidesPerFacePQ.beginD(), NotZero());
thrust::inclusive_scan(keepFace, keepFace + sidesPerFacePQ.size(),
facePQ2R.beginD() + 1);
int numFaceR = facePQ2R.H().back();
thrust::make_transform_iterator(sidesPerFacePQ.begin(), NotZero());
inclusive_scan(policy, keepFace, keepFace + sidesPerFacePQ.size(),
facePQ2R.begin() + 1);
int numFaceR = facePQ2R.back();
facePQ2R.resize(inP.NumTri() + inQ.NumTri());
outR.faceNormal_.resize(numFaceR);
auto next = thrust::copy_if(inP.faceNormal_.beginD(), inP.faceNormal_.endD(),
keepFace, outR.faceNormal_.beginD(),
thrust::identity<bool>());
auto next = copy_if<decltype(outR.faceNormal_.begin())>(
policy, inP.faceNormal_.begin(), inP.faceNormal_.end(), keepFace,
outR.faceNormal_.begin(), thrust::identity<bool>());
if (invertQ) {
auto start = thrust::make_transform_iterator(inQ.faceNormal_.beginD(),
auto start = thrust::make_transform_iterator(inQ.faceNormal_.begin(),
thrust::negate<glm::vec3>());
auto end = thrust::make_transform_iterator(inQ.faceNormal_.endD(),
auto end = thrust::make_transform_iterator(inQ.faceNormal_.end(),
thrust::negate<glm::vec3>());
thrust::copy_if(start, end, keepFace + inP.NumTri(), next,
thrust::identity<bool>());
copy_if<decltype(inQ.faceNormal_.begin())>(policy, start, end,
keepFace + inP.NumTri(), next,
thrust::identity<bool>());
} else {
thrust::copy_if(inQ.faceNormal_.beginD(), inQ.faceNormal_.endD(),
keepFace + inP.NumTri(), next, thrust::identity<bool>());
copy_if<decltype(inQ.faceNormal_.begin())>(
policy, inQ.faceNormal_.begin(), inQ.faceNormal_.end(),
keepFace + inP.NumTri(), next, thrust::identity<bool>());
}
auto newEnd =
thrust::remove(sidesPerFacePQ.beginD(), sidesPerFacePQ.endD(), 0);
VecDH<int> faceEdge(newEnd - sidesPerFacePQ.beginD() + 1);
thrust::inclusive_scan(sidesPerFacePQ.beginD(), newEnd,
faceEdge.beginD() + 1);
outR.halfedge_.resize(faceEdge.H().back());
auto newEnd = remove<decltype(sidesPerFacePQ.begin())>(
policy, sidesPerFacePQ.begin(), sidesPerFacePQ.end(), 0);
VecDH<int> faceEdge(newEnd - sidesPerFacePQ.begin() + 1, 0);
inclusive_scan(policy, sidesPerFacePQ.begin(), newEnd, faceEdge.begin() + 1);
outR.halfedge_.resize(faceEdge.back());
return std::make_tuple(faceEdge, facePQ2R);
}
@@ -137,15 +136,15 @@ struct EdgePos {
void AddNewEdgeVerts(
std::map<int, std::vector<EdgePos>> &edgesP,
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
const SparseIndices &p1q2, const VecH<int> &i12, const VecH<int> &v12R,
const VecH<Halfedge> &halfedgeP, bool forward) {
const SparseIndices &p1q2, const VecDH<int> &i12, const VecDH<int> &v12R,
const VecDH<Halfedge> &halfedgeP, bool forward) {
// For each edge of P that intersects a face of Q (p1q2), add this vertex to
// P's corresponding edge vector and to the two new edges, which are
// intersections between the face of Q and the two faces of P attached to the
// edge. The direction and duplicity are given by i12, while v12R remaps to
// the output vert index. When forward is false, all is reversed.
const VecH<int> &p1 = p1q2.Get(!forward).H();
const VecH<int> &q2 = p1q2.Get(forward).H();
const VecDH<int> &p1 = p1q2.Get(!forward);
const VecDH<int> &q2 = p1q2.Get(forward);
for (int i = 0; i < p1q2.size(); ++i) {
const int edgeP = p1[i];
const int faceQ = q2[i];
@@ -183,13 +182,12 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
// geometrically valid. If the order does not go start-end-start-end... then
// the input and output are not geometrically valid and this algorithm becomes
// a heuristic.
ALWAYS_ASSERT(edgePos.size() % 2 == 0, topologyErr,
"Non-manifold edge! Not an even number of points.");
ASSERT(edgePos.size() % 2 == 0, topologyErr,
"Non-manifold edge! Not an even number of points.");
int nEdges = edgePos.size() / 2;
auto middle = std::partition(edgePos.begin(), edgePos.end(),
[](EdgePos x) { return x.isStart; });
ALWAYS_ASSERT(middle - edgePos.begin() == nEdges, topologyErr,
"Non-manifold edge!");
ASSERT(middle - edgePos.begin() == nEdges, topologyErr, "Non-manifold edge!");
auto cmp = [](EdgePos a, EdgePos b) { return a.edgePos < b.edgePos; };
std::sort(edgePos.begin(), middle, cmp);
std::sort(middle, edgePos.end(), cmp);
@@ -206,21 +204,20 @@ struct Ref {
int PQ, tri, vert;
};
void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
VecH<int> &facePtrR,
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
VecDH<int> &facePtrR,
std::map<int, std::vector<EdgePos>> &edgesP,
VecH<Ref> &halfedgeRef, const Manifold::Impl &inP,
const VecH<int> &i03, const VecH<int> &vP2R,
const thrust::host_vector<int>::const_iterator faceP2R,
bool forward) {
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
const VecDH<int> &i03, const VecDH<int> &vP2R,
const VecDH<int>::IterC faceP2R, bool forward) {
// Each edge in the map is partially retained; for each of these, look up
// their original verts and include them based on their winding number (i03),
// while remaping them to the output using vP2R. Use the verts position
// while remapping them to the output using vP2R. Use the verts position
// projected along the edge vector to pair them up, then distribute these
// edges to their faces.
VecH<Halfedge> &halfedgeR = outR.halfedge_.H();
const VecH<glm::vec3> &vertPosP = inP.vertPos_.H();
const VecH<Halfedge> &halfedgeP = inP.halfedge_.H();
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
const VecDH<glm::vec3> &vertPosP = inP.vertPos_;
const VecDH<Halfedge> &halfedgeP = inP.halfedge_;
for (auto &value : edgesP) {
const int edgeP = value.first;
@@ -235,13 +232,13 @@ void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
const glm::vec3 edgeVec = vertPosP[vEnd] - vertPosP[vStart];
// Fill in the edge positions of the old points.
for (EdgePos &edge : edgePosP) {
edge.edgePos = glm::dot(outR.vertPos_.H()[edge.vert], edgeVec);
edge.edgePos = glm::dot(outR.vertPos_[edge.vert], edgeVec);
}
int inclusion = i03[vStart];
bool reversed = inclusion < 0;
EdgePos edgePos = {vP2R[vStart],
glm::dot(outR.vertPos_.H()[vP2R[vStart]], edgeVec),
glm::dot(outR.vertPos_[vP2R[vStart]], edgeVec),
inclusion > 0};
for (int j = 0; j < glm::abs(inclusion); ++j) {
edgePosP.push_back(edgePos);
@@ -250,7 +247,7 @@ void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
inclusion = i03[vEnd];
reversed |= inclusion < 0;
edgePos = {vP2R[vEnd], glm::dot(outR.vertPos_.H()[vP2R[vEnd]], edgeVec),
edgePos = {vP2R[vEnd], glm::dot(outR.vertPos_[vP2R[vEnd]], edgeVec),
inclusion < 0};
for (int j = 0; j < glm::abs(inclusion); ++j) {
edgePosP.push_back(edgePos);
@@ -294,12 +291,12 @@ void AppendPartialEdges(Manifold::Impl &outR, VecH<bool> &wholeHalfedgeP,
}
void AppendNewEdges(
Manifold::Impl &outR, VecH<int> &facePtrR,
Manifold::Impl &outR, VecDH<int> &facePtrR,
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
VecH<Ref> &halfedgeRef, const VecH<int> &facePQ2R, const int numFaceP) {
VecDH<Ref> &halfedgeRef, const VecDH<int> &facePQ2R, const int numFaceP) {
// Pair up each edge's verts and distribute to faces based on indices in key.
VecH<Halfedge> &halfedgeR = outR.halfedge_.H();
VecH<glm::vec3> &vertPosR = outR.vertPos_.H();
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
for (auto &value : edgesNew) {
const int faceP = value.first.first;
@@ -402,21 +399,23 @@ struct DuplicateHalfedges {
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
const VecDH<bool> wholeHalfedgeP, const VecDH<int> &i03,
const VecDH<int> &vP2R, const int *faceP2R,
bool forward) {
thrust::for_each_n(
zip(wholeHalfedgeP.beginD(), inP.halfedge_.beginD(), countAt(0)),
inP.halfedge_.size(),
DuplicateHalfedges({outR.halfedge_.ptrD(), halfedgeRef.ptrD(),
facePtrR.ptrD(), inP.halfedge_.cptrD(), i03.cptrD(),
vP2R.cptrD(), faceP2R, forward}));
const VecDH<char> wholeHalfedgeP, const VecDH<int> &i03,
const VecDH<int> &vP2R, const int *faceP2R, bool forward,
ExecutionPolicy policy) {
for_each_n(policy,
zip(wholeHalfedgeP.begin(), inP.halfedge_.begin(), countAt(0)),
inP.halfedge_.size(),
DuplicateHalfedges({outR.halfedge_.ptrD(), halfedgeRef.ptrD(),
facePtrR.ptrD(), inP.halfedge_.cptrD(),
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
}
struct CreateBarycentric {
glm::vec3 *barycentricR;
BaryRef *faceRef;
int *idx;
const int offsetQ;
const int firstNewVert;
const glm::vec3 *vertPosR;
const glm::vec3 *vertPosP;
@@ -443,6 +442,11 @@ struct CreateBarycentric {
faceRef[halfedgeR.face] = oldRef;
if (halfedgeRef.PQ == 1) {
// Mark the meshID as coming from Q
faceRef[halfedgeR.face].meshID += offsetQ;
}
if (halfedgeR.startVert < firstNewVert) { // retained vert
int i = halfedgeRef.vert;
const int bary = oldRef.vertBary[i];
@@ -476,23 +480,26 @@ struct CreateBarycentric {
std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
Manifold::Impl &outR, const VecDH<Ref> &halfedgeRef,
const Manifold::Impl &inP, const Manifold::Impl &inQ, int firstNewVert,
int numFaceR, bool invertQ) {
int numFaceR, bool invertQ, ExecutionPolicy policy) {
outR.meshRelation_.barycentric.resize(outR.halfedge_.size());
VecDH<BaryRef> faceRef(numFaceR);
VecDH<int> halfedgeBary(halfedgeRef.size());
const int offsetQ = Manifold::Impl::meshIDCounter_;
VecDH<int> idx(1, 0);
thrust::for_each_n(
zip(halfedgeBary.beginD(), halfedgeRef.beginD(),
outR.halfedge_.cbeginD()),
for_each_n(
policy,
zip(halfedgeBary.begin(), halfedgeRef.begin(), outR.halfedge_.cbegin()),
halfedgeRef.size(),
CreateBarycentric(
{outR.meshRelation_.barycentric.ptrD(), faceRef.ptrD(), idx.ptrD(),
firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
offsetQ, firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
inQ.vertPos_.cptrD(), inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
inP.meshRelation_.triBary.cptrD(), inQ.meshRelation_.triBary.cptrD(),
inP.meshRelation_.barycentric.cptrD(),
inQ.meshRelation_.barycentric.cptrD(), invertQ, outR.precision_}));
outR.meshRelation_.barycentric.resize(idx.H()[0]);
outR.meshRelation_.barycentric.resize(idx[0]);
return std::make_pair(faceRef, halfedgeBary);
}
} // namespace
@@ -500,36 +507,16 @@ std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
namespace manifold {
Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
#ifdef MANIFOLD_DEBUG
Timer assemble;
assemble.Start();
#endif
if ((expandP_ > 0) != (op == Manifold::OpType::ADD))
std::cout << "Warning! Result op type not compatible with constructor op "
"type: coplanar faces may have incorrect results."
<< std::endl;
int c1, c2, c3;
switch (op) {
case Manifold::OpType::ADD:
c1 = 1;
c2 = 1;
c3 = -1;
if (kVerbose) std::cout << "ADD" << std::endl;
break;
case Manifold::OpType::SUBTRACT:
c1 = 1;
c2 = 0;
c3 = -1;
if (kVerbose) std::cout << "SUBTRACT" << std::endl;
break;
case Manifold::OpType::INTERSECT:
c1 = 0;
c2 = 0;
c3 = 1;
if (kVerbose) std::cout << "INTERSECT" << std::endl;
break;
default:
throw std::invalid_argument("invalid enum: OpType.");
}
ASSERT((expandP_ > 0) == (op == Manifold::OpType::ADD), logicErr,
"Result op type not compatible with constructor op type.");
const int c1 = op == Manifold::OpType::INTERSECT ? 0 : 1;
const int c2 = op == Manifold::OpType::ADD ? 1 : 0;
const int c3 = op == Manifold::OpType::INTERSECT ? 1 : -1;
if (w03_.size() == 0) {
if (w30_.size() != 0 && op == Manifold::OpType::ADD) {
@@ -550,35 +537,36 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
VecDH<int> i21(x21_.size());
VecDH<int> i03(w03_.size());
VecDH<int> i30(w30_.size());
thrust::transform(x12_.beginD(), x12_.endD(), i12.beginD(), c3 * _1);
thrust::transform(x21_.beginD(), x21_.endD(), i21.beginD(), c3 * _1);
thrust::transform(w03_.beginD(), w03_.endD(), i03.beginD(), c1 + c3 * _1);
thrust::transform(w30_.beginD(), w30_.endD(), i30.beginD(), c2 + c3 * _1);
transform(policy_, x12_.begin(), x12_.end(), i12.begin(), c3 * _1);
transform(policy_, x21_.begin(), x21_.end(), i21.begin(), c3 * _1);
transform(policy_, w03_.begin(), w03_.end(), i03.begin(), c1 + c3 * _1);
transform(policy_, w30_.begin(), w30_.end(), i30.begin(), c2 + c3 * _1);
VecDH<int> vP2R(inP_.NumVert());
thrust::exclusive_scan(i03.beginD(), i03.endD(), vP2R.beginD(), 0, AbsSum());
int numVertR = AbsSum()(vP2R.H().back(), i03.H().back());
exclusive_scan(policy_, i03.begin(), i03.end(), vP2R.begin(), 0, AbsSum());
int numVertR = AbsSum()(vP2R.back(), i03.back());
const int nPv = numVertR;
VecDH<int> vQ2R(inQ_.NumVert());
thrust::exclusive_scan(i30.beginD(), i30.endD(), vQ2R.beginD(), numVertR,
AbsSum());
numVertR = AbsSum()(vQ2R.H().back(), i30.H().back());
exclusive_scan(policy_, i30.begin(), i30.end(), vQ2R.begin(), numVertR,
AbsSum());
numVertR = AbsSum()(vQ2R.back(), i30.back());
const int nQv = numVertR - nPv;
VecDH<int> v12R(v12_.size());
if (v12_.size() > 0) {
thrust::exclusive_scan(i12.beginD(), i12.endD(), v12R.beginD(), numVertR,
AbsSum());
numVertR = AbsSum()(v12R.H().back(), i12.H().back());
exclusive_scan(policy_, i12.begin(), i12.end(), v12R.begin(), numVertR,
AbsSum());
numVertR = AbsSum()(v12R.back(), i12.back());
}
const int n12 = numVertR - nPv - nQv;
VecDH<int> v21R(v21_.size());
if (v21_.size() > 0) {
thrust::exclusive_scan(i21.beginD(), i21.endD(), v21R.beginD(), numVertR,
AbsSum());
numVertR = AbsSum()(v21R.H().back(), i21.H().back());
exclusive_scan(policy_, i21.begin(), i21.end(), v21R.begin(), numVertR,
AbsSum());
numVertR = AbsSum()(v21R.back(), i21.back());
}
const int n21 = numVertR - nPv - nQv - n12;
@@ -592,22 +580,20 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
outR.vertPos_.resize(numVertR);
// Add vertices, duplicating for inclusion numbers not in [-1, 1].
// Retained vertices from P and Q:
thrust::for_each_n(zip(i03.beginD(), vP2R.beginD(), inP_.vertPos_.beginD()),
inP_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
thrust::for_each_n(zip(i30.beginD(), vQ2R.beginD(), inQ_.vertPos_.beginD()),
inQ_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
for_each_n(policy_, zip(i03.begin(), vP2R.begin(), inP_.vertPos_.begin()),
inP_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
for_each_n(policy_, zip(i30.begin(), vQ2R.begin(), inQ_.vertPos_.begin()),
inQ_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
// New vertices created from intersections:
thrust::for_each_n(zip(i12.beginD(), v12R.beginD(), v12_.beginD()),
i12.size(), DuplicateVerts({outR.vertPos_.ptrD()}));
thrust::for_each_n(zip(i21.beginD(), v21R.beginD(), v21_.beginD()),
i21.size(), DuplicateVerts({outR.vertPos_.ptrD()}));
for_each_n(policy_, zip(i12.begin(), v12R.begin(), v12_.begin()), i12.size(),
DuplicateVerts({outR.vertPos_.ptrD()}));
for_each_n(policy_, zip(i21.begin(), v21R.begin(), v21_.begin()), i21.size(),
DuplicateVerts({outR.vertPos_.ptrD()}));
if (kVerbose) {
std::cout << nPv << " verts from inP" << std::endl;
std::cout << nQv << " verts from inQ" << std::endl;
std::cout << n12 << " new verts from edgesP -> facesQ" << std::endl;
std::cout << n21 << " new verts from facesP -> edgesQ" << std::endl;
}
PRINT(nPv << " verts from inP");
PRINT(nQv << " verts from inQ");
PRINT(n12 << " new verts from edgesP -> facesQ");
PRINT(n21 << " new verts from facesP -> edgesQ");
// Build up new polygonal faces from triangle intersections. At this point the
// calculation switches from parallel to serial.
@@ -620,76 +606,94 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
// This key is the face index of <P, Q>
std::map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12.H(), v12R.H(),
inP_.halfedge_.H(), true);
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21.H(), v21R.H(),
inQ_.halfedge_.H(), false);
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true);
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false);
// Level 4
VecDH<int> faceEdge;
VecDH<int> facePQ2R;
std::tie(faceEdge, facePQ2R) =
SizeOutput(outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ);
std::tie(faceEdge, facePQ2R) = SizeOutput(
outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ, policy_);
const int numFaceR = faceEdge.size() - 1;
// This gets incremented for each halfedge that's added to a face so that the
// next one knows where to slot in.
VecDH<int> facePtrR = faceEdge;
// Intersected halfedges are marked false.
VecDH<bool> wholeHalfedgeP(inP_.halfedge_.size(), true);
VecDH<bool> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
VecDH<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
VecDH<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
// The halfedgeRef contains the data that will become triBary once the faces
// are triangulated.
VecDH<Ref> halfedgeRef(2 * outR.NumEdge());
AppendPartialEdges(outR, wholeHalfedgeP.H(), facePtrR.H(), edgesP,
halfedgeRef.H(), inP_, i03.H(), vP2R.H(), facePQ2R.begin(),
true);
AppendPartialEdges(outR, wholeHalfedgeQ.H(), facePtrR.H(), edgesQ,
halfedgeRef.H(), inQ_, i30.H(), vQ2R.H(),
facePQ2R.begin() + inP_.NumTri(), false);
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
i03, vP2R, facePQ2R.begin(), true);
AppendPartialEdges(outR, wholeHalfedgeQ, facePtrR, edgesQ, halfedgeRef, inQ_,
i30, vQ2R, facePQ2R.begin() + inP_.NumTri(), false);
AppendNewEdges(outR, facePtrR.H(), edgesNew, halfedgeRef.H(), facePQ2R.H(),
AppendNewEdges(outR, facePtrR, edgesNew, halfedgeRef, facePQ2R,
inP_.NumTri());
AppendWholeEdges(outR, facePtrR, halfedgeRef, inP_, wholeHalfedgeP, i03, vP2R,
facePQ2R.cptrD(), true);
facePQ2R.cptrD(), true, policy_);
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
facePQ2R.cptrD() + inP_.NumTri(), false);
facePQ2R.cptrD() + inP_.NumTri(), false, policy_);
VecDH<BaryRef> faceRef;
VecDH<int> halfedgeBary;
std::tie(faceRef, halfedgeBary) = CalculateMeshRelation(
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ);
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ, policy_);
#ifdef MANIFOLD_DEBUG
assemble.Stop();
Timer triangulate;
triangulate.Start();
#endif
// Level 6
if (ManifoldParams().intermediateChecks)
ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
outR.Face2Tri(faceEdge, faceRef, halfedgeBary);
outR.DuplicateMeshIDs();
outR.CollapseDegenerates();
#ifdef MANIFOLD_DEBUG
triangulate.Stop();
Timer finish;
finish.Start();
Timer simplify;
simplify.Start();
#endif
if (ManifoldParams().intermediateChecks)
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
outR.SimplifyTopology();
if (ManifoldParams().intermediateChecks)
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
outR.IncrementMeshIDs(0, outR.NumTri());
#ifdef MANIFOLD_DEBUG
simplify.Stop();
Timer sort;
sort.Start();
#endif
outR.Finish();
finish.Stop();
if (kVerbose) {
#ifdef MANIFOLD_DEBUG
sort.Stop();
if (ManifoldParams().verbose) {
assemble.Print("Assembly");
triangulate.Print("Triangulation");
finish.Print("Finishing the manifold");
simplify.Print("Simplification");
sort.Print("Sorting");
std::cout << outR.NumVert() << " verts and " << outR.NumTri() << " tris"
<< std::endl;
}
#endif
return outR;
}
} // namespace manifold
} // namespace manifold

Some files were not shown because too many files have changed in this diff Show More