Compare commits

...
453 Commits
Author SHA1 Message Date
pca006132 311d25a8cf fixes 2023-07-23 14:34:42 +08:00
pca006132 6fa0e7becb make to_mesh accept MeshGL 2023-07-21 13:33:35 +08:00
pca006132 e425761d8e fix according to comments 2023-07-21 12:56:06 +08:00
pca006132 a4cc9ee54b fix hsplit 2023-07-20 00:52:56 +08:00
pca006132 2a7f9cf48f clean up array related stuff 2023-07-20 00:48:23 +08:00
pca006132 99740dafcb Merge remote-tracking branch 'upstream/master' into pythonMeshGL 2023-07-19 22:40:48 +08:00
pca006132 9157350a11 updated python meshgl 2023-07-19 22:40:27 +08:00
pca006132 a8626f907c please gcc (#496)
* please gcc

* added documentation for mallocManaged
2023-07-19 10:58:28 +08:00
Geoff deRosenroll 2f22b92352 3d Convex Hull via Akuukka QuickHull (#489)
Add convex hull methods to Manifold using newly vendored https://github.com/akuukka/quickhull
2023-07-17 17:09:59 -07:00
Stéphane GINIER a3d7991563 Optimize sort_by_key (SortFaces/SortVerts) (#494)
* Optimize sort_by_key (SortFaces/SortVerts)

Remap boxes separately

* Fix build validation
2023-07-17 10:36:26 +08:00
Stéphane GINIER 2aef6e4728 Optimise SortEntry memory footprint (#493) 2023-07-15 22:21:26 +08:00
pca006132 c167a48d36 optimization in #483 (#491)
* remove extra prefetch

this is in the hot path and causes performance issues

* implemented optimization proposed by @stephomi in #483

* formatting

* fix cuda build

* combine the loop
2023-07-15 19:01:32 +08:00
Emmett Lalish f6cf8d23d3 updated python mesh 2023-07-14 23:01:28 -07:00
Peter Harman da3f323113 (C bindings) Pass additional data to level set SDF function (#427)
Allows a developer to pass some context to the level set function in the C FFI bindings, such as a pointer to an object in the host language, so that parallel calls can be threadsafe.
2023-07-13 12:50:40 -07:00
Emmett Lalish 8b7bf4231b updated python doc strings (#488)
* updated doc strings

* pymanifold -> manifold3d
2023-07-13 11:34:31 -07:00
Stéphane GINIER ac2cfd857f Replace map with unordered_map (#484)
Replace map usage with vector
2023-07-13 07:15:08 -07:00
pca006132 285bb0754b fix nixos js build (#473)
* fix nix js build

* fix js CrossSection::rotate binding

* add wasm hull operation
2023-07-12 12:03:19 +08:00
Geoff deRosenroll ba5fead6d5 Add 2d hull operation (#486)
This adds a 2d convex hull operation to CrossSection (ported from OCADml, which I ported based on BOSL2).
2023-07-10 23:43:47 -07:00
Emmett Lalish 96c57c0591 Fix Merge manifoldness issues (#481)
* test passes

* fixed merge properly

* fixed relation

* fixed pinched verts

* added WASM debug instructions
2023-07-07 09:40:10 -07:00
Emmett Lalish 4b65ff3afe Fix operations with different numbers of properties (#480)
* added tests and fixed a bug

* more testing, fixed compose
2023-07-06 13:12:33 -07:00
Geoff deRosenroll 709021d9d2 Remove RectClip API + Rect::AsCrossSection fix (#478)
Following discussion on #455, this removes RectClip from our API, as it does not provide the same guarantees that Clipper2's regular boolean clipping operations give us (that make sure that CrossSections can be extruded successfully).

Also, this fixes AsCrossSection which was returning empty CrossSections due to incorrect winding followed by a Positive fill rule union operation. Now it is working as advertised, while skipping over the needless clipping by implementing it as a CrossSection constructor (building a PathsD directly).
2023-07-05 10:41:03 -07:00
John Collins 6cec951aed Partial Revolve (#455)
* handle verts on revolve axis

* Tests working

* Const declares, test cycle start vert

* clang-format

* clang-format test code

* fix header declaration

* WASM bindings

* format bindings.js

* clang format

* Address comments
2023-07-04 15:52:48 -07:00
Emmett Lalish dd3445ddb1 fixed WASM segfault (#474) 2023-06-30 08:55:02 -07:00
Axel Angel 37db0d0182 python bindings: mirror and Mesh with 2 args (#459)
* python bindings: Add mirror

* python bindings: fix Mesh constructor optional arguments
2023-06-27 07:32:44 -07:00
Emmett Lalish 1b9243df2a Update HTML examples for WASM API changes (#470)
updated for WASM API changes
2023-06-25 20:40:16 -07:00
Emmett Lalish 8b4c79c485 added Fill Rule test (#464) 2023-06-22 16:04:19 -07:00
Geoff deRosenroll 7a197af83d Update Clipper2 to latest commit (#463)
* Update to latest clipper2 commit

* Update RectClip binding - rename and dropped param
2023-06-22 15:02:05 -07:00
Geoff deRosenroll a65fedb2f9 Clean up CrossSection constructor in pymanifold (#461)
Remove vertices intermediary and {vertices} assignment
2023-06-21 14:33:13 -07:00
Geoff deRosenroll 6ae9dee640 Workaround assimp exporting empty 3mfs (#454)
Workaround assimp producing meshless 3mf files by nesting the node holding the mesh into a child of the root node if the extension is .3mf
2023-06-15 10:45:01 -07:00
Geoff deRosenroll 79daba1200 Fix oversized malloc error (gcc inlining related) (#453)
Fixes #450

This breaks the allocation size calculation in ManagedVec::shrink_to_fit onto a separate line in order to work around spooky inlining (I think) by GCC that trips up an excessive allocation compilation error in malloc.
2023-06-15 10:42:49 -07:00
Geoff deRosenroll 2f8e3174f7 Update C bindings to curvature and set properites additions (#451)
Add C bindings to `Manifold::ReserveIDs`, `Manifold::SetProperties`, and `Manifold::CalculateCurvature`.
2023-06-14 23:08:53 -07:00
Emmett Lalish cce35aa5a0 Fix RemoveIfFolded() in the decimator (#447)
* enabled test

* remove test

* test infinite loops

* test infinite loops

* reduced test

* update volume

* fixed RemoveIfFolded

* rename test

* allow degenerates
2023-06-10 16:44:25 -07:00
Emmett Lalish e727b115c0 Change curvature API (#448)
* new curvature API compiles

* tests pass

* update JS example and fix bindings
2023-06-09 22:36:15 -07:00
Johnny Bigert f6431112a8 Update Python bindings (#445)
* Added more Python bindings (CrossSection) and some clarifying Python examples on how to create polygons etc
* Returned extrude from Python example and renamed accordingly.
* Removed PolygonWrapper class from Python bindings
2023-06-08 10:52:10 -07:00
Geoff deRosenroll 2643c291ed Use quality globals instead of arc tolerance for offset (#444) 2023-06-01 11:52:55 -07:00
Emmett Lalish e024edf103 ManifoldCAD docs update (#446)
* fix back compat

* updated docs

* Revert "fix back compat"

This reverts commit 5f102e567577ab8fcd7f2215801f5dcea9d13e8e.
2023-06-01 10:31:05 -07:00
Geoff deRosenroll cbfeed1672 Update JS bindings with CrossSection + reorganize (#440)
- add CrossSection class
- move static method (constructors) under their respective classes
-add Manifold.{split, splitByPlane}
- enable usage of instanceof on Manifold (and now CrossSection) objects simplify registry indirection in worker.ts, enabling method de-structuring (opening static function names out of class module name-spaces)

Build changes:
- break C++ helpers out from bindings.cpp to helpers.cpp
- ensure that ts files are copied during builds even when nothing is recompiled

Note that in addition to the new bindings, the most of the existing toplevel functions have been moved under their respective classes as static methods (breaking change!). I haven't added any new examples yet, but the existing ones are passing and do make use of CrossSection now (some transparently, and some directly), but I think enough of this is together that review is worthwhile.
2023-06-01 00:31:57 -07:00
Emmett Lalish 9dac47a76a Fix editor warp crash (#442)
* reset errored worker

* catch non-finite warp errors
2023-05-26 22:37:10 -07:00
Emmett Lalish eb41b9d36a Add CI check for formatting more languages (#441)
* add format files

* some fixes
2023-05-26 08:19:02 -07:00
Emmett Lalish 72935213e8 Editor scene (#438)
* SetProperties working

* cleanup

* added Node

* switched to Vite

* scene export working

* vertex color output works

* added unlit

* fixed rename bug

* switch worker to TS

* don't fail on empty manifolds

* reusing meshes

* cleanup, updated gyroid example

* change signature of setPropFunc

* updated examples

* add dist for githack testing

* Revert "add dist for githack testing"

This reverts commit 8769d7ab08dfaab9fe7824726f7a7229bf553bb3.

* remove duplicate files

* updated docs

* fixed some bugs

* multi-material working

* switched to vitest

* tests pass

* fixed examples

* fix CI

* update deployment

* tweak logging

* cleanup

* fix copy

* updated READMEs

* extend timeout
2023-05-25 09:40:02 -07:00
Geoff deRosenroll e1ebc66425 (C) Only link MeshIO when MANIFOLD_EXPORT is on (#436)
Only link MeshIO in C bindings when `MANIFOLD_EXPORT` is defined. MeshIO related code has been better consolidated to limit the number of `#ifdef` blocks required.
2023-05-21 00:14:36 -07:00
Emmett Lalish ef52b0cce2 Add SetProperties() (#433)
* SetProperties working

* cleanup
2023-05-16 21:22:49 -07:00
Geoff deRosenroll 812ca5c30f Add manifold_import_meshgl (#434)
Adds missing C binding to MeshIO::ImportMesh
2023-05-16 16:48:40 -07:00
Emmett Lalish f064b2a5fd make-manifold (#428)
* make-manifold working

* prettier

* fixed stack exceeded

* download only manifolds

* keep any meshes that are manifold

* fixed extension accessor writer

* fix read-write roundtrip
2023-05-15 07:50:28 -07:00
pca006132 18abd3ffcf fix compose index error (#430)
* fix compose index error

https://github.com/elalish/manifold/issues/425#issuecomment-1540356917

* simplify test case
2023-05-13 00:36:07 +08:00
Loosetooth 1615b72e83 add unionError test case (#429) 2023-05-11 08:06:09 -07:00
Emmett Lalish 58fb7e3300 EXT_manifold for glTF (#420)
* extension export works

* reading GLBs

* trying to merge documents

* add await

* removed extra materials

* added builds

* several fixes

* fix versions

* move to built

* fixed test.js

* updated README

* disable nix JS test for now

* fix codecov timeout

* fix test
2023-05-04 20:49:39 -07:00
David Braun 1959a30560 Fix bug in three.html example. (#419)
Fix bug in three.html.

The bug didn't change the output but made it harder for newcomers to learn the JavaScript API.
2023-04-21 15:24:11 -07:00
Emmett Lalish 4bdb761d5e fixed warp (#418)
* fixed warp

* fix pi

* ease up test tolerance

* improve cbind test

* fix build

* better bind testing
2023-04-21 17:02:16 +08:00
Emmett Lalish ba042ee8db fix warp simplification (#416)
* fixed warp simplification

* fix test
2023-04-20 11:52:47 -07:00
Emmett Lalish 97317888bc added global coplanarity check (#408)
* added global coplanarity check

* fix early-out

* added test, fixed precision

* one more precision
2023-04-12 08:03:47 -07:00
Emmett Lalish a9bf836574 Added file conversion and debug utility (#407)
* added file conversion utility

* added verbose output

* fix build
2023-04-11 11:38:35 -07:00
Emmett Lalish 29dc753513 Fix triangulator assertion (#406)
fixed debug check
2023-04-11 10:10:17 -07:00
Emmett Lalish 3a517a490e Update polygon tests (#405)
* simplify polygon tests

* fix polygon Dump output

* updated docs

* removed IsManifold
2023-04-11 09:10:34 -07:00
Emmett Lalish aeba26760b Add Merge function (#394)
* start Merge

* started Merge function

* added collider

* refactor now builds

* connect vectors

* finished Merge, updated Collider

* added tests

* added sorting, updated graph_lite

* revert submodule change

* fixed indexing

* fixed prop dedupe bug

* fixed test

* update meshIO linking

* fix MSVC

* use assimp on Mac

* add precision handling

* improved tests

* revert test tweak

* update collider

* clean up multiset

* updated docs
2023-04-09 17:16:40 -07:00
pca006132 85d74b4ccf Flip triangles in csg_tree (#404)
* flip triangles in csg_tree

* added test
2023-04-08 01:11:56 +08:00
Jiří Hon a5f92e2522 fix typo in typescript types (#401) 2023-04-04 13:28:43 -07:00
Jiří Hon fbe4cc6a86 Add JS bindings for triangulation (#393)
* js binding for triangulation

* don't use CrossSection class as it alters the order of the vertices

* remove unused code,
rename original Triangulate to TriangulateIdx,
move TriangulateJS from bindings.cpp to polygon.cpp,
rename TrinagulateJS to Triangulate for consistent C++ and JS API,
add optional precision parameter
2023-04-04 10:19:35 -07:00
Emmett Lalishandpca006132 f25d9601cb update thrust to 2.1 (#389)
* update thrust to 2.1

* fix typo

* does it work?

* fix shell script

* use checkout recursive option

* enable emscripten pthread support

* use GLM CMake

* remove -pthread

* fix emscripten build

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-03-30 20:44:50 -07:00
Geoff deRosenroll 07513bd951 Update Clipper2 commit + avoid building Clipper2Z (#392)
- Clipper2 submodule commit updated
- skip building Clipper2Z with new build option
- fix breaking API change in wrapper
2023-03-25 23:05:18 -07:00
pca006132 9a795162f4 [WIP] Python update (#378)
* nicer docstring

* some missing APIs

* split methods

* cross section python API
2023-03-23 12:54:05 +08:00
Geoff deRosenroll cdce9e7618 Improve handling of invalid shape construction. (#385)
Add `InvalidConstruction` to `Manifold::Error` and return empty manifolds with it set when manifold construction is attempted with invalid parameters

- zero / negative dimensions for Cube
- zero / negative radius for Sphere
- zero / negative height for Extrude
- empty CrossSection for Extrude/Revolve

Also, Circle and Square have been updated to return empty CrossSections for similar arguments.
2023-03-22 21:39:26 -07:00
Emmett Lalish 628edc8e26 fix URL (#386) 2023-03-22 08:04:02 -07:00
Emmett Lalish d209ead1c2 update readme (#384)
* updated readme

* forgot to save

* added performance plot
2023-03-21 22:31:40 -07:00
pca006132 112f9ba501 parallelize some csg operations (#381)
* parallelize some csg operations

* use lower_bound on sorted vector
2023-03-20 08:31:11 -07:00
Geoff deRosenroll 2a63971983 Add missing C bindings + revamp composition with opaque vectors (#382)
- Add bindings to `CrossSection::{Compose,Decompose,NumVert,NumContour,Warp}` 
- Add bindings to `Boolean` and `BatchBoolean` for both `Manifold` and `CrossSection`
- Add `ManifoldManifoldVec` and `ManifoldCrossSectionVec` opaque types for `std::vector` pointers to help clean up bindings involving vectors (particularly when they are returned with dynamic size, *e.g.* `manifold_decompose`
- Remove two-step Components workaround previously introduced to allow return of C array from `manifold_decompose`
2023-03-19 22:19:19 -07:00
Geoff deRosenroll 4962ebf879 Add CrossSection decomposition (#376)
Decompose breaks a CrossSection into independent complex polygons. 

Compose implemented as a batch union (preserving the CrossSection guaranteed lack of intersections).
2023-03-16 22:23:42 -07:00
Emmett Lalish d713986f62 Add missing JS bindings (#375)
* readd transform

* added mirror to wasm bindings

* added trimByPlane wasm bindings

* fixed bugs

* update test

* trying to fix submodule
2023-03-16 13:00:28 -07:00
Geoff deRosenroll 3b72852b0f Add documentation comments to CrossSection and Rect (#374) 2023-03-16 11:45:58 -07:00
Olivier Chafik e17eb038bf Simplify the CsgOpNodes as we build them, rather than in GetChildren (#368)
* Flatten the CsgOpNodes as we build them, rather than in GetChildren

* Don't inline reused nodes in CsgNode::Boolean

* Reinstate difference -> union rewrite

* No need for opportunistic flattening anymore

* Fix typo

* Remove CsgOpNode::Impl::simplified_

* Don't overskip referenced nodes in simplification

* Avoid needless copy of shared pointers

* Disambiguate CsgOpNode::Impl::flatten_ -> forcedToLeafNodes_ and align GetChildren arg naming

* Simplified CsgOpNode::GetChildren

* Simpler (a-(b+c)) -> (a-b-c) transform in CsgOpNode::Boolean to work better w/ CsgOpNode::ToLeafNode

* Simpler build hints for large_scene_test.cpp

* [flatten] Refine rules about tree flattening + added test case

* [manifold] Propagate transforms when flattening op trees

* [flatten] move tests from manifold_test to boolean_test
2023-03-16 10:09:12 -07:00
Jiří Hon 7b0758eddc locateFile property in Module function (#371)
Allows to provide custom url of the manifold.wasm file. This is needed for Vite. Partial fix for #365
2023-03-15 07:56:53 -07:00
Geoff deRosenroll 20178cb209 Add NumVert, NumContour, and Warp to CrossSection (#370) 2023-03-14 23:06:07 -07:00
Geoff deRosenroll 61d87c503c SimplePolygon/Polygons accessors for C bindings (#369) 2023-03-14 22:11:48 -07:00
Geoff deRosenroll 7c2007771e Make tests optional + fix option declarations (#366)
Break out the MeshIO library from the tests subdirectory, and make building the tests optional. In cases where building/re-building the tests is not wanted/needed, this can shorten build times by over 25% on my machine. A major motivator for this is building 3rd party bindings.

Also included here are descriptors for the option declarations, which I
noticed do not actually set the default value as expected if the
description argument is omitted (defaults to OFF, rather than ON for
`_TEST` and `_PYBIND` for example).
2023-03-14 10:47:08 -07:00
Geoff deRosenroll d82cf061f8 Update pybind11 to latest stable commit (#367)
* Update pybind11 to latest stable commit

* Explicitly install python@3.11 in mac CI

* Turn findpython on
2023-03-14 11:11:58 +08:00
Geoff deRosenroll dca9cce529 Lazy transformations for CrossSection (#355)
Add transformation composition with lazy application when needed, similar to the arrangement used in the CSG tree backing Manifold.
2023-03-10 11:25:47 -08:00
pca006132 754a4811a5 Fix cache concurrency issue (#356) (#357)
* added ConcurrentSharedPtr

* fixed csg tree cache concurrency bug (#356)
2023-03-10 08:33:10 +08:00
Geoff deRosenroll 83131c672e Add Mirror transformation method to Manifold (#354)
Add Mirror transformation (over plane through origin described by given normal) method to Manifold and corresponding C binding.
2023-03-07 23:54:27 -08:00
Geoff deRosenroll bd8da6ce1e Add CrossSection and Rect to C bindings (#353)
* + CrossSection and Rect type interfaces and method functions

* `manifold_extrude` and `manifold_revolve` take `ManifoldCrossSection*`

* fix manifoldc extrude test (`ManifoldCrossSection*` argument)
2023-03-07 12:30:28 -08:00
Emmett Lalish 3c736f9030 Added docs, refactored for single OpType (#352)
refactored for single OpType
2023-03-07 09:31:30 -08:00
Geoff deRosenroll bb24367220 Add Geoff deRosenroll to author list (#351) 2023-03-07 08:02:48 -08:00
Geoff deRosenroll a97c666358 2d-operations via Clipper2 (#341)
* WIP Clipper2 wrapper for 2d subsystem

* Working example in clippoly_test

* Add clean_ property to clippoly

* Square center=false by default

* Square defaults to center=false

* Shorten union example

* + simplification and minkowski functions

* + Circle and note on Minkowski

* + Offset (InflatePaths)

* Clippoly -> CrossSection renaming

* Switch from NonZero to Positive fill rule

* -(minkowski, simplifs, clean_) + pre-alloc vecs

Remove minkowski, simplification algorithms other than simplify,
clean (union) arbitrary contours on construction, and pre-allocate
vectors when length is known.

* - index from polygon conversion + missed vec prealloc

* Use Polygon shorthand over std::vector

* Update copyright date

* Add Rotate

* Update copyrights to 2023

* Extrude and Revolve accept CrossSection only

* Fix incorrect use of vector constructor

* Fix remaining incorrect vector construction

* Revert "Update copyrights to 2023"

This reverts commit af8f6c8c95135eb3376e1f150fe720fddea16f33.

* Re-update copyright notice in cross_section files

* Add missing precision_ argument to cleaning Unions

* Lift circular resolution globals into Quality

* Replace uses of Manifold globals with Quality

* + Ellipse, Mirror, Area

* Include mirror in Union example and test area

* + Rewind (reverse winding of all contours)

* + fillrule/IsEmpty - Ellipse/Rewind

* Move Quality into public.h

* Remove bad definition

* Replace manual circles with CrossSection::Circle

* Lists of empty contours are empty

* + Rect class, RectClip, + const params/methods

* Add Transform to CrossSection and Rect
2023-03-03 17:11:15 -08:00
Emmett Lalish 7c8ce3c935 v2.1 (#350)
new npm release
2023-03-02 10:41:19 -08:00
Emmett Lalish 4d434e4f8c deploy extra types files (#349) 2023-03-02 00:04:16 -08:00
Emmett Lalish 9e9a22d66b clean up editor.d.ts (#348) 2023-03-01 23:33:31 -08:00
Rafael 893a4f8ac3 Properly encapsulate ES6 type defs. in module (#346)
* Properly encapsulate ES6 type defs. in module

* Fixed import file name typo

* Copy type definitions to editor folder (not used for now)

* Dummy commit (trigger CI)
2023-03-01 09:47:45 -08:00
Emmett Lalish 78c03f2611 prod deployment again (#347)
minor cleanup
2023-02-28 22:09:28 -08:00
Emmett Lalish 2915f7aa90 prod github pages to deploy (#345) 2023-02-28 12:48:19 -08:00
Emmett Lalish 95daa54591 ugly fix for editor TS (#344) 2023-02-28 12:16:48 -08:00
Emmett Lalish 2c597d1eef make PolyVert private (#343)
* make PolyVert private

* fix python
2023-02-28 10:03:34 -08:00
Rafael e2a8e94906 Fix ES6 .d.ts exports (#342) 2023-02-28 09:16:06 -08:00
Emmett Lalish eae9653745 added docs, tweaked manifest (#339) 2023-02-25 11:00:17 -08:00
Emmett Lalish feb7fb6edd Convert manifoldCAD.org to a PWA (#336)
* added PWA manifest

* added service worker

* check for service worker
2023-02-25 09:49:19 -08:00
Emmett Lalish 32f887d321 SEO for manifoldCAD.org (#335)
* added meta tags

* added og:image
2023-02-23 10:28:28 -08:00
Emmett Lalish 3a962cfa53 Make robust to overlaps by default (#299)
* added polygon tests that only fail on some compilers

* revert accidental narrowing of polygon tests

* default to allowing overlaps

* CI experiments
2023-02-23 10:05:50 -08:00
Emmett Lalish 36ca7acf6c add only() to editor (#334)
* added only() to editor

* added docs
2023-02-23 09:32:12 -08:00
pca006132 0704f62eaa added maze benchmark (#333) 2023-02-24 01:00:13 +08:00
Emmett Lalish 5041c38424 Add gl-matrix to editor (#332)
* switch JS to Mat4

* added glMatrix to editor

* added glMatrix types

* type cleanup

* fixed type names

* updated examples

* fixed types and examples
2023-02-23 08:00:52 -08:00
pca006132 c4bf392f66 hashtable atomic load store fix (#331)
fix hashmap race condition
2023-02-21 22:01:40 -08:00
Emmett Lalish 3f42e5e6d7 Adding show() to editor (#330)
* include all transforms

* added show method for editor debug

* await before cleanup

* tighten JS tests

* move out common test functions

* split manifold and boolean tests

* cleanup

* tighten samples tests

* fixed GetMeshGL

* fixed original IDs for empty runs

* link SDF

* update CUDA files

* revert accidentally-removed test function

* added types and docs
2023-02-20 22:00:04 -08:00
pca006132 376a7c676f wasm: transform accepts mat4 (#329) 2023-02-19 18:21:16 -08:00
Emmett Lalish e6bb87b2bb change enum case (#325)
* change enum case

* fix C enums

* more fixes

* another fix
2023-02-16 13:32:28 -08:00
Emmett Lalish 9477deaa9c Fix deploy (#323)
* use pinned gltf-transform

* fix artifact download
2023-02-16 09:24:18 -08:00
Emmett Lalish 02affd5247 convert to ES6 module (#322)
* es6 tests work

* fixed model-viewer example

* fixed three example

* worker not quite working

* removed three.js dependency

* switched to top-level await

* added normalIdx binding

* fix binding
2023-02-16 08:09:31 -08:00
Emmett Lalish 171fe5466e More relations (#321)
transformed is not original
2023-02-15 08:04:42 -08:00
Rafael 5331b68485 Added missing JS bindings (from discussion in #317) (#320)
* Added missing JS bindings (from discussion in #317)

* Fix formatting

* Add reserveIDs JS bindings

* Use numRun instead of numMatrix

* Make numProp, triVerts and vertProperties required options in Mesh ctor.

* Rename MeshJS originalID to runOriginalID, and transform to runTransform

* Fix formatting with clang-format

* Trigger CI

* Added missing C bindings

* Rename matrix to transform in JS type definitions

* Rename numMatrix to numRun in JS type defs
almost forgot this one again

* JS bindings, transform index -> run
2023-02-14 20:52:23 -08:00
Emmett Lalish 20d7ef96fb update CI to cuda 12 (#319) 2023-02-10 23:22:18 -08:00
Emmett Lalish 3560bbf679 added normals update (#317)
* added normals update

* improved test model

* reverted RelatedGL change

* renormalize
2023-02-10 22:22:25 -08:00
Emmett Lalish 4415cfce46 Transform relations (#315)
* added transforms output

* removed meshids

* read MeshGL runs, IDs, and transforms

* cleanup

* replaced lambdas

* refactor reserveIDs

* refactored reference initialization

* added round-trip test

* added multi-material round trip test

* added faceID round trip test

* back to const inputs

* added missing error types

* shorten CI timeouts

* update docs
2023-02-07 09:50:43 -08:00
Geoff deRosenroll bbb1fabcac Remove Mesh from interface + memcpy depuplication (#313) 2023-01-29 08:34:56 -08:00
Emmett LalishandGeoff deRosenroll f43dded234 Multi-material example (#309)
* removed BufferGeometryUtils

* replaced three.js with gltf-transform

* textured sphere working

* added cubeUV

* added textures

* added relation and sorting to MeshGL

* working textures example!

* added faceID

* removed MeshRelation

* some more removal

* fix output for no properties

* test cleanup

* Add new MeshGL accessors (replacing MeshRelation) (#311)

+ new meshgl accessors + remove mesh_relation_size

* removed meshID from public API

* added input checking for MeshGL

* remove C api

---------

Co-authored-by: Geoff deRosenroll <geoffderosenroll@gmail.com>
2023-01-28 13:02:37 -08:00
Geoff deRosenroll 05f9ceb999 Remove manifold_meshgl_vert_normal from header (impl gone) (#310)
Remove meshgl_vert_normal from header (impl gone)
2023-01-26 08:03:29 -08:00
Emmett LalishandGeoff deRosenroll 422c7f26d9 Property output (#290)
* first pass, boolean math needs refactor to work

* refactored property interpolation

* finished edge_op update

* updated sort

* updated MeshGL

* meshGL -> manifold

* existing tests pass

* prod CI

* fixed floating point error

* construct MeshGL from Mesh

* size fix

* added basic testing

* added property refinement test

* added property boolean test

* bug fix?

* fixed tolerance chacking

* fixed segfault

* fixed more bugs

* fixed mod bug

* basic properties tests passing

* added test

* fixed shared properties

* fixed duplicate merge entries

* compacting property verts

* tightened tests, fixed bug

* added MeshGL export

* fixed more bugs

* fixed coplanar tests

* removed old mesh relation from tests

* fixed property output, but still have extra propVerts

* removed barycentric from MeshRelation

* rename BaryRef to TriRef

* refactored boolean property creation

* moved GetBarycentric back out of the public API

* CreateProperties handles continuous and discontinuous properties

* avoid nan

* widened testing precision

* Update manifoldc to MeshGL breaking changes (#306)

* Update manifoldc to MeshGL breaking changes

* Update to latest MeshRelation/TriRef changes

* updated WASM bindings

* attempt at updating three.js example

* updated JS

* fixed examples

Co-authored-by: Geoff deRosenroll <geoffderosenroll@gmail.com>
2023-01-21 21:41:33 -08:00
pca006132 a701d2be8e weird bug for python binding (#307)
* weird bug for python binding

* formatting
2023-01-06 08:06:44 -08:00
Geoff deRosenroll 5b5f56674a Use sqrtf to return float rather than double in level_set test. (#303)
sqrtf to return float rather than double

This hasn't been a problem on my machine, or in the Manifold CI, but
the double <> float mismatch gave a type error in an ubuntu 22.04
workflow on my omanifold project. Not totally sure why as of yet, but
this gets rid of the implicit conversion at the root of it.
2022-12-29 18:51:53 +08:00
Emmett Lalish 1b7b365c3a Fix mirroring (#298)
* supports scale inversion

* update tangents when scaling

* fixed tests
2022-12-27 11:41:52 -08:00
pca006132 812b056179 fix css issue (#296) 2022-12-22 14:30:47 +08:00
Geoff deRosenroll bd46bfd1cf Fix incorrect value for components length + test (#295) 2022-12-22 13:24:09 +08:00
Geoff deRosenroll a7b457db74 C bindings to facilitate static FFI bindings (#292)
* + c bindings to facilitate static FFI bindings

Squashed commit of the following:

commit a05f9b0e0ab079b95c29b4641ae928f02be219f4
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Dec 10 16:21:47 2022 -0800

    Add flag (default on) for python bindings

    Signed-off-by: Geoff deRosenroll <geoffderosenroll@gmail.com>

commit ce5b601fc7578fa43edbf7363a7b625d1d27a1de
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Dec 9 23:45:29 2022 -0800

    conv function naming consistency

commit 1b50cf077dada8c3bc13d77bfa1c8f0a406c047d
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Dec 6 17:42:18 2022 -0800

    manifold_level_set_seq to C API (force sequential)

commit 77e06daf92e7014ce153a336ed685b9370e965c0
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Dec 6 17:24:10 2022 -0800

    Use an optional executionpolicy param in LevelSet

    This allows bindings to avoid breaking their runtime locks while passing
    in function pointers without introducing another compile flag.

commit 12c62b1e33cdacd07f056859fa2038dfd1f62170
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Dec 5 15:09:41 2022 -0800

    Better section comments

commit 24f1d5107e24f41d3860fe6dea4832a523989eb9
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Dec 2 12:01:50 2022 -0800

    Add missing management functions for Components

commit a7d5aae8550c4ace458e78314eab04a3cf5f2053
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Thu Dec 1 23:45:45 2022 -0800

    Break Decompose up to avoid repetition in C binds

commit a650a1531dc52610db8e6009e82e247d7f256d3e
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sun Nov 27 22:41:38 2022 -0800

    Add manifold_get_properties

commit 97a58396cd8e6c5fad698f1280d02d78e0b51ded
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 26 22:53:42 2022 -0800

    circular segment functions corrected to static

commit c9c1ca7ec599b5f14401c85efde19481d86226f8
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Thu Nov 24 21:19:11 2022 -0800

    polygons from dbl ptr of SimplePolygon

commit 8e5390aa729817d0cc652f92a64e6850e4e16e9b
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Wed Nov 23 12:16:44 2022 -0800

    compose and decompose switched to double ptrs

commit 45c78c3e1aa0eaab8622c77b69221226af06861c
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Nov 22 11:40:32 2022 -0800

    Add manifold copy and empty constructors

commit cfc06fe55cd4a508005385bcdb4037ed89695f80
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 21 15:03:00 2022 -0800

    Add MANIFOLD_SEQ_SDF to avoid runtime lock

commit e0f9d09e5ced6fcb2b83722542e99b20d0799e25
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sun Nov 20 16:56:50 2022 -0800

    Add missing export related contructors.

commit 0cbaa4a6090c402f2a2483c35f5b3b7e82d8cf52
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sun Nov 20 16:03:43 2022 -0800

    Add separate destruct functions

commit fd6c1c250e6b91114b0203b40a941fa73e3473ec
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 17:29:12 2022 -0800

    More consistent array handling

commit ec1eef84153b13bba0b5ae78a42084b634a9d9b1
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 16:52:07 2022 -0800

    Replace array args with pointers

commit 9079cbf7bb788e1b8eb992cbe1e3e51dfc36f1ce
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 15:00:47 2022 -0800

    Revert "Switch back to funptr struct args for simplicity"

    This reverts commit dc78841af654bf81c41e74795bac0f724af59dd0.

commit 62c1078fe9b10c88df86da0a5275f47581dad275
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 14:52:37 2022 -0800

    Switch back to funptr struct args for simplicity

commit d30992db9a4224235246531a5ddf1a034e8e0c1a
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 14:06:12 2022 -0800

    Avoid needing to pass in vec struct for funptrs

commit 2ba7bec1653a655c68bba3d2961fd280bc8e8f32
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Sat Nov 19 00:32:09 2022 -0800

    Use template spec to avoid fun ptr to cuda

commit 1faf58b3887747af8659eb1be660e2fc40ff17f9
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 21:54:56 2022 -0800

    Make fPIC more conditional.

commit 6215f89447f63753ca0dd2f2fcee88c7aaf5e548
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 21:35:09 2022 -0800

    Avoid passing dynamic lambda to GPU

commit 30fa3dc323df8962e3802a58f346b813a8a759f5
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 18:02:04 2022 -0800

    note and temporary hack RE cuda compilation

commit bb4dfe93cb8e0e7b582450f6d0c0d8d5ba5cef9a
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 17:21:25 2022 -0800

    Set graphlite library to OBJECT

commit e803da50b983c931c69e42fe3fea30aff534034b
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 16:04:28 2022 -0800

    Convert sublibraries to OBJECT to avoid archives

commit d0e9d176e79f18c645e538a5df19263d11630fcd
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Fri Nov 18 15:28:15 2022 -0800

    Add missing polygon size and deletion funcs

commit 7db72848b9012d9f1a69b28ad878fb65f4524fc3
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Nov 15 22:26:17 2022 -0800

    Remove static flag (control with shared flag)

commit 5ae33f34716998e3d8b3368c619b81f145e34cfe
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Tue Nov 15 22:16:27 2022 -0800

    Add options to avoid python bindings

commit 4181a586288db47acbd55f8e94e94ec4a7fcd252
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 22:02:10 2022 -0800

    Rename to manifoldc

commit c4b14d64655ca2fb3ce8ac80af7390b959fed3c7
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 21:51:45 2022 -0800

    Cleanup unused typedefs and simplify names

commit a282c90f1f62141ee5c2cd9303ccc8649f2a6bb3
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 21:23:07 2022 -0800

    Placeholder (terrible) decompose binding

commit 9409e81abb2c3ba3ffb0983aa0c14e261d032cc2
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 20:37:02 2022 -0800

    Turn on export in CLI instead

commit 9dad24b787af0420ef32719c86fb42e11af9a8c1
Author: Geoff deRosenroll <geoffderosenroll@gmail.com>
Date:   Mon Nov 14 20:33:17 2022 -0800

    "Rebased" WIP cbindings

* Turn on -fPIC whenever shared libs are being built

* Add parens to disambiguate sdf policy logic

* Bump public std from 14 to 17

* Add binding flags to workflow builds

* Add export when building cbindings

* Bump ubuntu version (PBR material deprecation)

* Select no-param overload of Decompose

* Add missing const to decompose sig overload

* Re-organize

* variable capitalization consistency

* Drop to std14 to test for regression fix

* Attempted old libassimp compatibility hack

* Rebump to std_17

* Temporarily disabled

TODO: investigate why this is failing on Mac, but not other backends.

* Remove unused tuple include

* Remove uneccesary auto-includes

* Properly disable Boolean_Close

* rename discrete -> numComponents + early return

* Rename discrete -> numComponents

* Rename conv.hh -> conv.h

* Prune auto-includes

* Note on level_set vs level_set_seq

* Set export on for c-bindings

* Adding C binding tests.

* Separate and don't require CBIND build/test

* Remove bad flag

* Take const char ptr for filename

* Remove testc executable

* Add tests from removed testc
2022-12-18 21:59:36 +01:00
pca006132andEmmett Lalish 87059508e7 Fix memory leak (#280)
* generic hashtable implementation

The original hashtable is modified to accept any value type. The key is
still uint64 because atomic is impossible for general key type, and it
seems that we don't really need the key to be generic.
A "better" hash function is added to replace the original identity hash
function, which should hopefully give less collision, seems doing
nothing for now because our test cases don't have much collision.

* fix #245

* fixed uninitialized field

* change hashtable probe step

* put hash function in template parameter

* fixed compiler error

Co-authored-by: Emmett Lalish <elalish@gmail.com>
2022-11-17 10:55:04 +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
209 changed files with 38074 additions and 50173 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,js,ts,html'
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
- name: Download built examples
uses: dawidd6/action-download-artifact@v2
with:
workflow: manifold.yml
workflow_conclusion: completed
pr: ${{github.event.pull_request.number}}
name: wasm
path: ./public
- name: Deploy to Github Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
force_orphan: true
cname: manifoldcad.org
publish_dir: ./public
+210 -23
View File
@@ -2,7 +2,7 @@
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
@@ -10,35 +10,222 @@ on:
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-20.04
timeout-minutes: 45
strategy:
matrix:
cuda_support: [ON, OFF]
parallel_backend: [NONE, OMP, TBB]
runs-on: ubuntu-22.04
if: github.event.pull_request.draft == false
container:
image: docker://nvidia/cuda:11.1-devel-ubuntu20.04
# Steps represent a sequence of tasks that will be executed as part of the job
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Install dependencies
run: |
apt-get -y update
DEBIAN_FRONTEND=noninteractive apt install -y cmake libglm-dev libassimp-dev libboost-graph-dev
- name: Build CUDA
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
pip install trimesh
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build ${{matrix.backend}}
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
run: |
mkdir buildCUDA
cd buildCUDA
cmake -DCMAKE_BUILD_TYPE=Release .. && make
- name: Build CPP
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
# note that the test for CUDA backend does not really test CUDA, as we
# don't have CUDA GPU on GitHub Action
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
run: |
mkdir buildCPP
cd buildCPP
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_USE_CPP=ON .. && make
- name: Test CPP
run: |
cd buildCPP/test
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
cd build/test
./manifold_test
cd ../../
python3 bindings/python/examples/run_all.py
- name: Coverage Report
# only do code coverage for default sequential backend, it seems that TBB
# backend will cause failure
# perhaps issue related to invalid memory access?
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
cd test
./manifold_test
cd ../
lcov --capture --directory . --output-file ./code_coverage_test.info
lcov --add-tracefile ./code_coverage_init.info --add-tracefile ./code_coverage_test.info --output-file ./code_coverage_total.info
lcov --remove ./code_coverage_total.info '/usr/*' '*/third_party/*' '*/test/*' '*/extras/*' '*/bindings/*' --output-file ./code_coverage.info
- uses: codecov/codecov-action@v2
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
with:
files: build/code_coverage.info
fail_ci_if_error: false
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
build_cbind:
timeout-minutes: 30
runs-on: ubuntu-22.04
if: github.event.pull_request.draft == false
container:
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
steps:
- name: Install dependencies
run: |
apt-get -y update
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build C bindings with OMP and CUDA
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=OMP -DMANIFOLD_USE_CUDA=ON .. && make
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
# note that the test for CUDA backend does not really test CUDA, as we
# don't have CUDA GPU on GitHub Action
run: |
cd build/test
./manifold_test --gtest_filter=CBIND.*
build_wasm:
timeout-minutes: 30
runs-on: ubuntu-22.04
if: github.event.pull_request.draft == false
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Setup WASM
run: |
# setup emscripten
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build WASM
run: |
source ./emsdk/emsdk_env.sh
mkdir build
cd build
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
- name: Test WASM
run: |
cd build/test
node ./manifold_test.js
- name: Test examples
run: |
cd bindings/wasm/examples
npm ci
npm run build
npm test
- name: Upload WASM files
uses: actions/upload-artifact@v3
with:
name: wasm
path: bindings/wasm/examples/dist/
retention-days: 90
build_windows:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE]
cuda_support: [OFF]
max-parallel: 1
runs-on: windows-2019
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v1.12
- uses: ilammy/msvc-dev-cmd@v1
- name: Install CUDA
if: matrix.backend == 'CUDA'
env:
cuda: "11.3.0"
visual_studio: "Visual Studio 16 2019"
shell: powershell
run: |
Invoke-RestMethod https://raw.githubusercontent.com/ptheywood/cuda-cmake-github-actions/21811d232fc775fa8d44dddf7f8c5ce2cf8d2b81/scripts/actions/install_cuda_windows.ps1 -OutFile install_cuda_windows.ps1
.\install_cuda_windows.ps1
- name: nvcc check
if: matrix.backend == 'CUDA'
shell: powershell
run: |
nvcc -V
ls $env:CUDA_PATH
ls $env:CUDA_PATH\bin
ls $env:CUDA_PATH\include
- name: Build ${{matrix.backend}}
shell: powershell
run: |
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DPYBIND11_FINDPYTHON=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
cd build
cmake --build . --target ALL_BUILD --config Release
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
shell: bash
run: |
cd build/test
./Release/manifold_test.exe
build_mac:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE, TBB]
runs-on: macos-latest
if: github.event.pull_request.draft == false
steps:
- name: Install common dependencies
run: |
brew install pkg-config assimp
pip install trimesh
- name: Install OpenMP
if: matrix.parallel_backend == 'OMP'
run: brew install libomp
- name: Install TBB
if: matrix.parallel_backend == 'TBB'
run: brew install tbb
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build
run: |
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
- name: Test
run: |
cd build/test
./manifold_test
build_nix:
timeout-minutes: 30
strategy:
matrix:
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: cachix/install-nix-action@v15
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
+14
View File
@@ -106,3 +106,17 @@ Temporary Items
# End of https://www.gitignore.io/api/osx,c++,cuda,linux,cmake
build
buildWASM
docs/html
node_modules/
bindings/wasm/examples/built
bindings/wasm/examples/dist
bindings/wasm/examples/public/manifold*
__pycache__
.vscode/c_cpp_properties.json
*.sublime-project
*.sublime-workspace
.cache
# PyCharm
.idea
+19
View File
@@ -0,0 +1,19 @@
[submodule "third_party/thrust"]
path = src/third_party/thrust
url = https://github.com/NVIDIA/thrust
[submodule "third_party/glm"]
path = src/third_party/glm
url = https://github.com/g-truc/glm
[submodule "third_party/google_test"]
path = test/third_party/google_test
url = https://github.com/google/googletest.git
[submodule "third_party/pybind11"]
path = bindings/python/third_party/pybind11
url = https://github.com/pybind/pybind11
branch = stable
[submodule "src/third_party/clipper2"]
path = src/third_party/clipper2
url = https://github.com/AngusJohnson/clipper2
[submodule "src/third_party/quickhull"]
path = src/third_party/quickhull
url = https://github.com/akuukka/quickhull
-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
}
+22 -22
View File
@@ -1,26 +1,26 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "manifold test",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/test/manifold_test",
"args": [
"--gtest_break_on_failure",
"--gtest_filter=Manifold.GetMesh"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/test",
"environment": [
{
"name": "manifold test",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/test/manifold_test",
"args": [
"--gtest_break_on_failure",
"--gtest_filter=Manifold.GetMesh"
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}/build/test",
"environment": [
{
"name": "MALLOC_CHECK_",
"value": "2"
}
]
"name": "MALLOC_CHECK_",
"value": "2"
}
]
]
}
]
}
+141 -96
View File
@@ -1,98 +1,143 @@
{
"files.associations": {
"*.cu": "cpp",
"*.cuh": "cpp",
"*.m": "matlab",
"deque": "cpp",
"vector": "cpp",
"cctype": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"strstream": "cpp",
"*.tcc": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cstdint": "cpp",
"unordered_map": "cpp",
"exception": "cpp",
"slist": "cpp",
"fstream": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"new": "cpp",
"ostream": "cpp",
"numeric": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"csignal": "cpp",
"bitset": "cpp",
"complex": "cpp",
"list": "cpp",
"algorithm": "cpp",
"cfenv": "cpp",
"*.ipp": "cpp",
"string": "cpp",
"forward_list": "cpp",
"unordered_set": "cpp",
"valarray": "cpp",
"__bit_reference": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__functional_base": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__nullptr": "cpp",
"__split_buffer": "cpp",
"__string": "cpp",
"__threading_support": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"bit": "cpp",
"ios": "cpp",
"iterator": "cpp",
"locale": "cpp",
"map": "cpp",
"mutex": "cpp",
"optional": "cpp",
"string_view": "cpp",
"condition_variable": "cpp",
"set": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"variant": "cpp"
},
"C_Cpp.clang_format_style": "google",
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DMANIFOLD_USE_CPP=ON",
// "-DMANIFOLD_USE_OMP=ON"
],
"files.associations": {
"*.cu": "cpp",
"*.cuh": "cpp",
"*.m": "matlab",
"deque": "cpp",
"vector": "cpp",
"cctype": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"strstream": "cpp",
"*.tcc": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cstdint": "cpp",
"unordered_map": "cpp",
"exception": "cpp",
"slist": "cpp",
"fstream": "cpp",
"functional": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"new": "cpp",
"ostream": "cpp",
"numeric": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"system_error": "cpp",
"thread": "cpp",
"cinttypes": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"csignal": "cpp",
"bitset": "cpp",
"complex": "cpp",
"list": "cpp",
"algorithm": "cpp",
"cfenv": "cpp",
"*.ipp": "cpp",
"string": "cpp",
"forward_list": "cpp",
"unordered_set": "cpp",
"valarray": "cpp",
"__bit_reference": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__functional_base": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__nullptr": "cpp",
"__split_buffer": "cpp",
"__string": "cpp",
"__threading_support": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"bit": "cpp",
"ios": "cpp",
"iterator": "cpp",
"locale": "cpp",
"map": "cpp",
"mutex": "cpp",
"optional": "cpp",
"string_view": "cpp",
"condition_variable": "cpp",
"set": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"variant": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"codecvt": "cpp",
"regex": "cpp",
"future": "cpp",
"shared_mutex": "cpp",
"__bits": "cpp",
"compare": "cpp",
"concepts": "cpp",
"queue": "cpp",
"stack": "cpp",
"__hash_table": "cpp",
"__std_stream": "cpp",
"__verbose_abort": "cpp",
"any": "cpp",
"barrier": "cpp",
"charconv": "cpp",
"coroutine": "cpp",
"latch": "cpp",
"semaphore": "cpp",
"span": "cpp",
"__sso_allocator": "cpp"
},
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DBUILD_SHARED_LIBS=ON",
"-DMANIFOLD_CBIND=ON",
"-DMANIFOLD_EXPORT=ON",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE",
"-DCODE_COVERAGE=OFF",
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
],
"search.exclude": {
"**/third_party/*": true,
"build*": true,
"docs/*": true
},
"editor.tabSize": 2,
"editor.detectIndentation": false,
"cSpell.words": [
"Bary",
"Coplanarity",
"csaszar",
"Gyroid",
"halfedge",
"halfedges",
"Tris",
"Verts"
],
"javascript.format.semicolons": "insert",
}
+10
View File
@@ -0,0 +1,10 @@
# This is the list of Manifold's significant contributors.
#
# This does not necessarily list everyone who has contributed code,
# especially since many employees of one corporation may be contributing.
# To see the full list of contributors, see the revision history in
# source control.
Emmett Lalish <elalish>
Chun Kit LAM <pca006132>
Geoff deRosenroll <geoffder>
Google LLC
+85 -42
View File
@@ -1,49 +1,92 @@
cmake_minimum_required(VERSION 3.12)
if (NOT CMAKE_CUDA_COMPILER)
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")
endif()
project(manifold LANGUAGES CXX CUDA)
# Copyright 2020 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
cmake_minimum_required(VERSION 3.18)
project(manifold LANGUAGES CXX)
set(CMAKE_VERBOSE_MAKEFILE ON)
# OPTION(DEBUG_CMAKE_TARGETS "enable debug output for cmake target properties" OFF)
# if(DEBUG_CMAKE_TARGETS)
# set(CMAKE_DEBUG_TARGET_PROPERTIES
# INCLUDE_DIRECTORIES
# )
# endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
OPTION( MANIFOLD_USE_OMP
"Use OpenMP as the Thrust backend instead of CUDA."
OFF
)
OPTION( MANIFOLD_USE_CPP
"Use C++ (single-threaded) as the Thrust backend instead of CUDA."
OFF
)
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
option(MANIFOLD_TEST "Enable testing suite" ON)
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
option(MANIFOLD_USE_CUDA "Enable GPU support via CUDA" OFF)
option(MANIFOLD_PYBIND "Build python bindings" ON)
option(MANIFOLD_CBIND "Build C (FFI) bindings" OFF)
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
set(MANIFOLD_FLAGS -O3)
set(MAINFOLD_FLAGS -Werror -Wall -Wno-sign-compare)
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda)
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3)
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
if(EMSCRIPTEN)
message("Building for Emscripten")
set(MANIFOLD_FLAGS -fexceptions -D_LIBCUDACXX_HAS_THREAD_API_EXTERNAL -D_LIBCUDACXX_HAS_THREAD_API_CUDA)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
endif()
IF(MANIFOLD_USE_OMP)
message("------------------------- Using OpenMP instead of CUDA.")
find_package(OpenMP REQUIRED)
set(MANIFOLD_OMP_INCLUDE OpenMP::OpenMP_CXX)
set(MANIFOLD_NVCC_FLAGS ${MANIFOLD_NVCC_FLAGS} -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_OMP -Xcompiler=-fopenmp)
ENDIF(MANIFOLD_USE_OMP)
option(PYBIND11_FINDPYTHON "Enable PyBind to perform FindPython for you" ON)
IF(MANIFOLD_USE_CPP)
message("------------------------- Using C++ instead of CUDA.")
set(MANIFOLD_NVCC_FLAGS ${MANIFOLD_NVCC_FLAGS} -DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_CPP)
ENDIF(MANIFOLD_USE_CPP)
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
add_subdirectory(utilities)
add_subdirectory(collider)
add_subdirectory(polygon)
add_subdirectory(manifold)
add_subdirectory(third_party)
add_subdirectory(meshIO)
add_subdirectory(samples)
add_subdirectory(test)
add_subdirectory(tools)
option(BUILD_SHARED_LIBS "Build dynamic/shared libraries" OFF)
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
# Allow shared libraries to be relocatable (add Place Independent Code flag).
# Also include when statically linking C bindings to avoid issues with bundling
# artefacts in host languages using them for FFI.
add_compile_options(-fPIC)
endif()
if(MANIFOLD_USE_CUDA)
enable_language(CUDA)
find_package(CUDA REQUIRED)
# we cannot set THRUST_INC_DIR when building with CUDA, otherwise the
# compiler will not use the system CUDA headers which causes incompatibility
# clear THRUST_INC_DIR, we use the one from nvcc
set(THRUST_INC_DIR "")
set(MANIFOLD_NVCC_RELEASE_FLAGS -O3 -lineinfo)
set(MANIFOLD_NVCC_DEBUG_FLAGS -G)
set(MANIFOLD_NVCC_FLAGS -Xcudafe --diag_suppress=esa_on_defaulted_function_ignored --extended-lambda --expt-relaxed-constexpr
"$<$<CONFIG:RELEASE>:${MANIFOLD_NVCC_RELEASE_FLAGS}>"
"$<$<CONFIG:DEBUG>:${MANIFOLD_NVCC_DEBUG_FLAGS}>")
endif()
if(NOT MSVC)
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=${WARNING_FLAGS}>")
endif()
if(CODE_COVERAGE AND NOT MSVC)
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
add_link_options("-coverage")
endif()
add_subdirectory(src)
add_subdirectory(bindings)
if(MANIFOLD_EXPORT)
add_subdirectory(meshIO)
endif()
if(MANIFOLD_TEST)
add_subdirectory(samples)
add_subdirectory(test)
add_subdirectory(extras)
endif()
+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
+109 -7
View File
@@ -1,20 +1,122 @@
[![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)
## Users
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:
<img src="https://elalish.github.io/manifold/samples/models/perfSpheres.png" width="350px"/>
## [ManifoldCAD.org](https://manifoldcad.org)
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not GPU-accelerated, but it's still quite fast and a good way to test out our Manifold library.
![A metallic Menger sponge](https://elalish.github.io/manifold/samples/models/mengerSponge3.webp "A metallic Menger sponge")
### Note for Firefox users
If you find the editor is stuck on **Loading...**, setting
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
discussion of the
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
of this repository may solve the problem.
# Manifold
This is a geometry library dedicated to creating and operating on manifold meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
[**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)
[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
We have four core dependencies, making use of submodules to ensure compatibility:
- `graphlite`: connected components algorithm
- `Clipper2`: provides our 2D subsystem
- `GLM`: a compact vector library
- `Thrust`: Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
## What's here
This library is intended to be fast with guaranteed manifold output. As such you need manifold meshes as input, which can be hard to come by since most 3D graphics meshes are not. This library can create simple primitive meshes but also links in Assimp, which will import many kinds of 3D files, but you'll get an error if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
The most significant contribution here is a guaranteed manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If anyone knows of another, please tell me. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial.
To aid in speed, this library makes extensive use of parallelization, generally through Nvidia's Thrust library. You can switch between the CUDA, OMP, TBB, and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled for CUDA, the code will still run without a GPU, falling back to the serial version of the algorithms. The WASM build is serial-only for now, but still fast.
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible, though it could be improved even more with JS or Python bindings to avoid the syntax and compiling of C++.
Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) directory for examples of how to use this library to make interesting 3D models. You may notice that some of these examples bare a certain resemblance to my OpenSCAD designs on [Thingiverse](https://www.thingiverse.com/emmett), which is no accident. Much as I love OpenSCAD, my library is dramatically faster and the code is more flexible.
## Building
The canonical build instructions are in the [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml) file, as that is what this project's continuous integration server uses to build and test. I have only built under Ubuntu Linux, and the CI uses Nvidia's Cuda 11 Docker image. Part of my [road map](https://github.com/elalish/manifold/wiki/Manifold-Library#road-map) is to migrate from Thrust to C++20 parallel algorithms, which will alleviate the need to install the Cuda Developer Kit to build.
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
Build and test (Ubuntu or similar):
```
git clone --recurse-submodules https://github.com/elalish/manifold.git
cd manifold
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
test/manifold_test
```
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
- `MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
- `MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
- `MANIFOLD_EXPORT=[<OFF>, ON]`: Enables GLB export of 3D models from the tests, requires `libassimp-dev`.
- `MANIFOLD_DEBUG=[<OFF>, ON]`: Enables internal assertions and exceptions.
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
### WASM
To build the JS WASM library, first install NodeJS and set up emscripten:
(on Mac):
```
brew install nodejs
brew install emscripten
```
(on Linux):
```
sudo apt install nodejs
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk/emsdk_env.sh
```
Then build:
```
cd manifold
mkdir buildWASM
cd buildWASM
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
node test/manifold_test.js
```
### Python
The CMake script will build the python binding `manifold3d` automatically. To
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
can be found in `bindings/python/examples`. To see exported samples, run:
```
sudo apt install pkg-config libpython3-dev python3 python3-distutils python3-pip
pip install trimesh
python3 run_all.py -e
```
Run the following code in the interpreter for
python binding documentation:
```
>>> import manifold3d
>>> help(manifold3d)
```
For more detailed documentation, please refer to the C++ API.
## Contributing
@@ -22,4 +124,4 @@ Contributions are welcome! A lower barrier contribution is to simply make a PR t
## About the author
This library is by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee, but this is not a Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
+25
View File
@@ -0,0 +1,25 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
add_subdirectory(c)
endif()
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
add_subdirectory(python)
endif()
if(EMSCRIPTEN)
add_subdirectory(wasm)
endif()
+22
View File
@@ -0,0 +1,22 @@
project(manifoldc)
add_library(${PROJECT_NAME} manifoldc.cpp conv.cpp)
if(MANIFOLD_USE_CUDA)
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
if(MANIFOLD_EXPORT)
target_link_libraries(${PROJECT_NAME} PRIVATE meshIO)
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
endif()
target_link_libraries(
${PROJECT_NAME}
PRIVATE manifold sdf graphlite cross_section
)
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
+85
View File
@@ -0,0 +1,85 @@
#include <conv.h>
#include <manifold.h>
#include <public.h>
#include "types.h"
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
float y2, float z2) {
auto p1 = glm::vec3(x1, y1, z1);
auto p2 = glm::vec3(x2, y2, z2);
auto box = new (mem) Box(p1, p2);
return to_c(box);
}
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
auto v = from_c(b)->Size();
return {v.x, v.y, v.z};
}
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
auto v = from_c(b)->Center();
return {v.x, v.y, v.z};
}
float manifold_box_scale(ManifoldBox *b) { return from_c(b)->Scale(); }
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
auto p = glm::vec3(x, y, z);
return from_c(b)->Contains(p);
}
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
auto outer = *from_c(a);
auto inner = *from_c(b);
return outer.Contains(inner);
}
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
auto box = *from_c(b);
auto p = glm::vec3(x, y, z);
box.Union(p);
}
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
auto box = from_c(a)->Union(*from_c(b));
return to_c(new (mem) Box(box));
}
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4) {
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
auto transformed = from_c(b)->Transform(mat);
return to_c(new (mem) Box(transformed));
}
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
float z) {
auto p = glm::vec3(x, y, z);
auto translated = (*from_c(b)) + p;
return to_c(new (mem) Box(translated));
}
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
float z) {
auto p = glm::vec3(x, y, z);
auto scaled = (*from_c(b)) * p;
return to_c(new (mem) Box(scaled));
}
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
auto p = glm::vec3(x, y, z);
return from_c(b)->DoesOverlap(p);
}
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
return from_c(a)->DoesOverlap(*from_c(b));
}
int manifold_box_is_finite(ManifoldBox *b) { return from_c(b)->IsFinite(); }
+237
View File
@@ -0,0 +1,237 @@
#include <conv.h>
#include <manifold.h>
#include <sdf.h>
#include <vector>
#include "cross_section.h"
#include "include/types.h"
#include "public.h"
#include "types.h"
ManifoldManifold *to_c(manifold::Manifold *m) {
return reinterpret_cast<ManifoldManifold *>(m);
}
ManifoldManifoldVec *to_c(ManifoldVec *ms) {
return reinterpret_cast<ManifoldManifoldVec *>(ms);
}
ManifoldCrossSection *to_c(manifold::CrossSection *cs) {
return reinterpret_cast<ManifoldCrossSection *>(cs);
}
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv) {
return reinterpret_cast<ManifoldCrossSectionVec *>(csv);
}
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
return reinterpret_cast<ManifoldSimplePolygon *>(m);
}
ManifoldPolygons *to_c(manifold::Polygons *m) {
return reinterpret_cast<ManifoldPolygons *>(m);
}
ManifoldMesh *to_c(manifold::Mesh *m) {
return reinterpret_cast<ManifoldMesh *>(m);
}
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
return reinterpret_cast<ManifoldMeshGL *>(m);
}
ManifoldOpType to_c(manifold::OpType optype) {
ManifoldOpType op = MANIFOLD_ADD;
switch (optype) {
case manifold::OpType::Add:
break;
case manifold::OpType::Subtract:
op = MANIFOLD_SUBTRACT;
break;
case manifold::OpType::Intersect:
op = MANIFOLD_INTERSECT;
break;
};
return op;
}
ManifoldError to_c(manifold::Manifold::Error error) {
ManifoldError e = MANIFOLD_NO_ERROR;
switch (error) {
case Manifold::Error::NoError:
break;
case Manifold::Error::NonFiniteVertex:
e = MANIFOLD_NON_FINITE_VERTEX;
break;
case Manifold::Error::NotManifold:
e = MANIFOLD_NOT_MANIFOLD;
break;
case Manifold::Error::VertexOutOfBounds:
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
break;
case Manifold::Error::PropertiesWrongLength:
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
break;
case Manifold::Error::MissingPositionProperties:
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
break;
case Manifold::Error::MergeVectorsDifferentLengths:
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
break;
case Manifold::Error::MergeIndexOutOfBounds:
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
break;
case Manifold::Error::TransformWrongLength:
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
break;
case Manifold::Error::RunIndexWrongLength:
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
break;
case Manifold::Error::FaceIDWrongLength:
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
break;
case Manifold::Error::InvalidConstruction:
e = MANIFOLD_INVALID_CONSTRUCTION;
break;
};
return e;
}
ManifoldBox *to_c(manifold::Box *m) {
return reinterpret_cast<ManifoldBox *>(m);
}
ManifoldRect *to_c(manifold::Rect *m) {
return reinterpret_cast<ManifoldRect *>(m);
}
ManifoldVec2 to_c(glm::vec2 v) { return {v.x, v.y}; }
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
ManifoldProperties to_c(manifold::Properties p) {
return {p.surfaceArea, p.volume};
}
const manifold::Manifold *from_c(ManifoldManifold *m) {
return reinterpret_cast<manifold::Manifold const *>(m);
}
ManifoldVec *from_c(ManifoldManifoldVec *ms) {
return reinterpret_cast<ManifoldVec *>(ms);
}
const manifold::CrossSection *from_c(ManifoldCrossSection *cs) {
return reinterpret_cast<manifold::CrossSection *>(cs);
}
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv) {
return reinterpret_cast<CrossSectionVec *>(csv);
}
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m) {
return reinterpret_cast<manifold::SimplePolygon const *>(m);
}
const manifold::Polygons *from_c(ManifoldPolygons *m) {
return reinterpret_cast<manifold::Polygons const *>(m);
}
const manifold::Mesh *from_c(ManifoldMesh *m) {
return reinterpret_cast<manifold::Mesh const *>(m);
}
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
return reinterpret_cast<manifold::MeshGL const *>(m);
}
OpType from_c(ManifoldOpType optype) {
auto op = OpType::Add;
switch (optype) {
case MANIFOLD_ADD:
break;
case MANIFOLD_SUBTRACT:
op = OpType::Subtract;
break;
case MANIFOLD_INTERSECT:
op = OpType::Intersect;
break;
};
return op;
}
CrossSection::FillRule from_c(ManifoldFillRule fillrule) {
auto fr = CrossSection::FillRule::EvenOdd;
switch (fillrule) {
case MANIFOLD_FILL_RULE_EVEN_ODD:
break;
case MANIFOLD_FILL_RULE_NON_ZERO:
fr = CrossSection::FillRule::NonZero;
break;
case MANIFOLD_FILL_RULE_POSITIVE:
fr = CrossSection::FillRule::Positive;
break;
case MANIFOLD_FILL_RULE_NEGATIVE:
fr = CrossSection::FillRule::Negative;
break;
};
return fr;
}
CrossSection::JoinType from_c(ManifoldJoinType join_type) {
auto jt = CrossSection::JoinType::Square;
switch (join_type) {
case MANIFOLD_JOIN_TYPE_SQUARE:
break;
case MANIFOLD_JOIN_TYPE_ROUND:
jt = CrossSection::JoinType::Round;
break;
case MANIFOLD_JOIN_TYPE_MITER:
jt = CrossSection::JoinType::Miter;
break;
};
return jt;
}
const manifold::Box *from_c(ManifoldBox *m) {
return reinterpret_cast<manifold::Box const *>(m);
}
const manifold::Rect *from_c(ManifoldRect *m) {
return reinterpret_cast<manifold::Rect const *>(m);
}
glm::vec2 from_c(ManifoldVec2 v) { return glm::vec2(v.x, v.y); }
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
auto vec = std::vector<glm::vec3>();
for (int i = 0; i < length; ++i) {
vec.push_back(from_c(vs[i]));
}
return vec;
}
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
auto vec = std::vector<glm::ivec3>();
for (int i = 0; i < length; ++i) {
vec.push_back(from_c(vs[i]));
}
return vec;
}
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
auto vec = std::vector<glm::vec4>();
for (int i = 0; i < length; ++i) {
vec.push_back(from_c(vs[i]));
}
return vec;
}
+209
View File
@@ -0,0 +1,209 @@
#include <conv.h>
#include <manifold.h>
#include <public.h>
#include <vector>
#include "cross_section.h"
#include "types.h"
ManifoldCrossSection *manifold_cross_section_empty(void *mem) {
return to_c(new (mem) CrossSection());
}
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
ManifoldCrossSection *cs) {
auto cross = *from_c(cs);
return to_c(new (mem) CrossSection(cross));
}
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr) {
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
}
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
ManifoldPolygons *p,
ManifoldFillRule fr) {
return to_c(new (mem) CrossSection(*from_c(p), from_c(fr)));
}
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem) {
return to_c(new (mem) CrossSectionVec());
}
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz) {
return to_c(new (mem) CrossSectionVec(sz));
}
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
size_t sz) {
from_c(csv)->reserve(sz);
}
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv) {
return from_c(csv)->size();
}
ManifoldCrossSection *manifold_cross_section_vec_get(
void *mem, ManifoldCrossSectionVec *csv, int idx) {
auto cs = (*from_c(csv))[idx];
return to_c(new (mem) CrossSection(cs));
}
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
ManifoldCrossSection *cs) {
(*from_c(csv))[idx] = *from_c(cs);
}
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
ManifoldCrossSection *cs) {
return from_c(csv)->push_back(*from_c(cs));
}
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
int center) {
auto cs = CrossSection::Square(glm::vec2(x, y), center);
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
int circular_segments) {
auto cs = CrossSection::Circle(radius, circular_segments);
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b,
ManifoldOpType op) {
auto cs = from_c(a)->Boolean(*from_c(b), from_c(op));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_batch_boolean(
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op) {
auto cs = CrossSection::BatchBoolean(*from_c(csv), from_c(op));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_union(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b) {
auto cs = (*from_c(a)) + (*from_c(b));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_difference(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
auto cs = (*from_c(a)) - (*from_c(b));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_intersection(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b) {
auto cs = (*from_c(a)) ^ (*from_c(b));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
ManifoldCrossSection *cs,
float x, float y) {
auto translated = from_c(cs)->Translate(glm::vec2(x, y));
return to_c(new (mem) CrossSection(translated));
}
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
ManifoldCrossSection *cs,
float deg) {
auto rotated = from_c(cs)->Rotate(deg);
return to_c(new (mem) CrossSection(rotated));
}
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
ManifoldCrossSection *cs,
float x, float y) {
auto scaled = from_c(cs)->Scale(glm::vec2(x, y));
return to_c(new (mem) CrossSection(scaled));
}
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
ManifoldCrossSection *cs,
float ax_x, float ax_y) {
auto mirrored = from_c(cs)->Mirror(glm::vec2(ax_x, ax_y));
return to_c(new (mem) CrossSection(mirrored));
}
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
ManifoldCrossSection *cs,
float x1, float y1,
float x2, float y2,
float x3, float y3) {
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
auto transformed = from_c(cs)->Transform(mat);
return to_c(new (mem) CrossSection(transformed));
}
ManifoldCrossSection *manifold_cross_section_warp(
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float)) {
std::function<void(glm::vec2 & v)> warp = [fun](glm::vec2 &v) {
v = from_c(fun(v.x, v.y));
};
auto warped = from_c(cs)->Warp(warp);
return to_c(new (mem) CrossSection(warped));
}
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
ManifoldCrossSection *cs,
double epsilon) {
auto simplified = from_c(cs)->Simplify(epsilon);
return to_c(new (mem) CrossSection(simplified));
}
ManifoldCrossSection *manifold_cross_section_offset(
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
double miter_limit, int circular_segments) {
auto offset =
from_c(cs)->Offset(delta, from_c(jt), miter_limit, circular_segments);
return to_c(new (mem) CrossSection(offset));
}
double manifold_cross_section_area(ManifoldCrossSection *cs) {
return from_c(cs)->Area();
}
int manifold_cross_section_num_vert(ManifoldCrossSection *cs) {
return from_c(cs)->NumVert();
}
int manifold_cross_section_num_contour(ManifoldCrossSection *cs) {
return from_c(cs)->NumContour();
}
int manifold_cross_section_is_empty(ManifoldCrossSection *cs) {
return from_c(cs)->IsEmpty();
}
ManifoldRect *manifold_cross_section_bounds(void *mem,
ManifoldCrossSection *cs) {
auto rect = from_c(cs)->Bounds();
return to_c(new (mem) Rect(rect));
}
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
ManifoldCrossSection *cs) {
auto ps = from_c(cs)->ToPolygons();
return to_c(new (mem) Polygons(ps));
}
ManifoldCrossSection *manifold_cross_section_compose(
void *mem, ManifoldCrossSectionVec *csv) {
auto cs = CrossSection::Compose(*from_c(csv));
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSectionVec *manifold_cross_section_decompose(
void *mem, ManifoldCrossSection *cs) {
auto comps = from_c(cs)->Decompose();
return to_c(new (mem) CrossSectionVec(comps));
}
+66
View File
@@ -0,0 +1,66 @@
#pragma once
#include <manifold.h>
#include <public.h>
#include <sdf.h>
#include <types.h>
#include <vector>
#include "cross_section.h"
using ManifoldVec = std::vector<Manifold>;
using CrossSectionVec = std::vector<CrossSection>;
ManifoldManifold *to_c(manifold::Manifold *m);
ManifoldManifoldVec *to_c(ManifoldVec *ms);
ManifoldCrossSection *to_c(manifold::CrossSection *cs);
ManifoldCrossSectionVec *to_c(CrossSectionVec *csv);
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
ManifoldPolygons *to_c(manifold::Polygons *ps);
ManifoldMesh *to_c(manifold::Mesh *m);
ManifoldMeshGL *to_c(manifold::MeshGL *m);
ManifoldBox *to_c(manifold::Box *m);
ManifoldRect *to_c(manifold::Rect *m);
ManifoldError to_c(manifold::Manifold::Error error);
ManifoldVec2 to_c(glm::vec2 v);
ManifoldVec3 to_c(glm::vec3 v);
ManifoldIVec3 to_c(glm::ivec3 v);
ManifoldProperties to_c(manifold::Properties p);
const manifold::Manifold *from_c(ManifoldManifold *m);
ManifoldVec *from_c(ManifoldManifoldVec *ms);
const manifold::CrossSection *from_c(ManifoldCrossSection *cs);
CrossSectionVec *from_c(ManifoldCrossSectionVec *csv);
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
const manifold::Polygons *from_c(ManifoldPolygons *m);
const manifold::Mesh *from_c(ManifoldMesh *m);
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
OpType from_c(ManifoldOpType op);
CrossSection::FillRule from_c(ManifoldFillRule fillrule);
CrossSection::JoinType from_c(ManifoldJoinType jt);
const manifold::Box *from_c(ManifoldBox *m);
const manifold::Rect *from_c(ManifoldRect *r);
glm::vec2 from_c(ManifoldVec2 v);
glm::vec3 from_c(ManifoldVec3 v);
glm::ivec3 from_c(ManifoldIVec3 v);
glm::vec4 from_c(ManifoldVec4 v);
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
template <typename T>
std::vector<T> vector_of_array(T *ts, size_t length) {
auto vec = std::vector<T>();
for (int i = 0; i < length; ++i) {
vec.push_back(ts[i]);
}
return vec;
}
template <typename T>
T *copy_data(void *mem, std::vector<T> v) {
T *ts = reinterpret_cast<T *>(mem);
memcpy(ts, v.data(), sizeof(T) * v.size());
return ts;
}
+386
View File
@@ -0,0 +1,386 @@
#include <stddef.h>
#include <stdint.h>
#include <types.h>
#ifdef __cplusplus
#include <cstdint>
extern "C" {
#endif
// Polygons
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
size_t length);
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
size_t length);
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p);
size_t manifold_polygons_length(ManifoldPolygons *ps);
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx);
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
int idx);
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
ManifoldPolygons *ps,
int idx);
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
int pt_idx);
// Mesh Construction
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
size_t n_props, uint32_t *tri_verts,
size_t n_tris);
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
size_t n_verts, size_t n_props,
uint32_t *tri_verts, size_t n_tris,
float *halfedge_tangent);
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
// SDF
// By default, the execution policy (sequential or parallel) of
// manifold_level_set will be chosen automatically depending on the size of the
// job and whether Manifold has been compiled with a PAR backend. If you are
// using these bindings from a language that has a runtime lock preventing the
// parallel execution of closures, then you should use manifold_level_set_seq to
// force sequential execution.
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
ManifoldBox *bounds, float edge_length,
float level);
ManifoldMeshGL *manifold_level_set_seq(void *mem,
float (*sdf)(float, float, float),
ManifoldBox *bounds, float edge_length,
float level);
// The _context variants of manifold_level_set allow a pointer to be passed
// back to each invocation of the sdf function pointer, for languages that
// need additional data.
ManifoldMeshGL *manifold_level_set_context(
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
float edge_length, float level, void *ctx);
ManifoldMeshGL *manifold_level_set_seq_context(
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
float edge_length, float level, void *ctx);
// Manifold Vectors
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem);
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz);
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz);
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms);
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
int idx);
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
ManifoldManifold *m);
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
ManifoldManifold *m);
// Manifold Booleans
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
ManifoldManifold *b, ManifoldOpType op);
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
ManifoldOpType op);
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
ManifoldManifold *b);
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
ManifoldManifold *b);
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
ManifoldManifold *b);
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
ManifoldManifold *a, ManifoldManifold *b);
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset);
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset);
// Manifold Transformations
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4);
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
float ny, float nz);
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
ManifoldVec3 (*fun)(float, float, float));
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
// Manifold Shapes / Constructors
ManifoldManifold *manifold_empty(void *mem);
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_tetrahedron(void *mem);
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
int center);
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
float radius_high, int circular_segments,
int center);
ManifoldManifold *manifold_sphere(void *mem, float radius,
int circular_segments);
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
int *half_edges, float *smoothness,
int n_idxs);
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
float height, int slices,
float twist_degrees, float scale_x,
float scale_y);
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
int circular_segments);
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms);
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
// Manifold Info
int manifold_is_empty(ManifoldManifold *m);
ManifoldError manifold_status(ManifoldManifold *m);
int manifold_num_vert(ManifoldManifold *m);
int manifold_num_edge(ManifoldManifold *m);
int manifold_num_tri(ManifoldManifold *m);
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
float manifold_precision(ManifoldManifold *m);
int manifold_genus(ManifoldManifold *m);
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
int manifold_get_circular_segments(float radius);
int manifold_original_id(ManifoldManifold *m);
uint32_t manifold_reserve_ids(uint32_t n);
ManifoldManifold *manifold_set_properties(
void *mem, ManifoldManifold *m, int num_prop,
void (*fun)(float *new_prop, ManifoldVec3 position, const float *old_prop));
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
int gaussian_idx, int mean_idx);
// CrossSection Shapes/Constructors
ManifoldCrossSection *manifold_cross_section_empty(void *mem);
ManifoldCrossSection *manifold_cross_section_copy(void *mem,
ManifoldCrossSection *cs);
ManifoldCrossSection *manifold_cross_section_of_simple_polygon(
void *mem, ManifoldSimplePolygon *p, ManifoldFillRule fr);
ManifoldCrossSection *manifold_cross_section_of_polygons(void *mem,
ManifoldPolygons *p,
ManifoldFillRule fr);
ManifoldCrossSection *manifold_cross_section_square(void *mem, float x, float y,
int center);
ManifoldCrossSection *manifold_cross_section_circle(void *mem, float radius,
int circular_segments);
ManifoldCrossSection *manifold_cross_section_compose(
void *mem, ManifoldCrossSectionVec *csv);
ManifoldCrossSectionVec *manifold_cross_section_decompose(
void *mem, ManifoldCrossSection *cs);
// CrossSection Vectors
ManifoldCrossSectionVec *manifold_cross_section_empty_vec(void *mem);
ManifoldCrossSectionVec *manifold_cross_section_vec(void *mem, size_t sz);
void manifold_cross_section_vec_reserve(ManifoldCrossSectionVec *csv,
size_t sz);
size_t manifold_cross_section_vec_length(ManifoldCrossSectionVec *csv);
ManifoldCrossSection *manifold_cross_section_vec_get(
void *mem, ManifoldCrossSectionVec *csv, int idx);
void manifold_cross_section_vec_set(ManifoldCrossSectionVec *csv, int idx,
ManifoldCrossSection *cs);
void manifold_cross_section_vec_push_back(ManifoldCrossSectionVec *csv,
ManifoldCrossSection *cs);
// CrossSection Booleans
ManifoldCrossSection *manifold_cross_section_boolean(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b,
ManifoldOpType op);
ManifoldCrossSection *manifold_cross_section_batch_boolean(
void *mem, ManifoldCrossSectionVec *csv, ManifoldOpType op);
ManifoldCrossSection *manifold_cross_section_union(void *mem,
ManifoldCrossSection *a,
ManifoldCrossSection *b);
ManifoldCrossSection *manifold_cross_section_difference(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
ManifoldCrossSection *manifold_cross_section_intersection(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
// CrossSection Transformation
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
ManifoldCrossSection *cs,
float x, float y);
ManifoldCrossSection *manifold_cross_section_rotate(void *mem,
ManifoldCrossSection *cs,
float deg);
ManifoldCrossSection *manifold_cross_section_scale(void *mem,
ManifoldCrossSection *cs,
float x, float y);
ManifoldCrossSection *manifold_cross_section_mirror(void *mem,
ManifoldCrossSection *cs,
float ax_x, float ax_y);
ManifoldCrossSection *manifold_cross_section_transform(void *mem,
ManifoldCrossSection *cs,
float x1, float y1,
float x2, float y2,
float x3, float y3);
ManifoldCrossSection *manifold_cross_section_warp(
void *mem, ManifoldCrossSection *cs, ManifoldVec2 (*fun)(float, float));
ManifoldCrossSection *manifold_cross_section_simplify(void *mem,
ManifoldCrossSection *cs,
double epsilon);
ManifoldCrossSection *manifold_cross_section_offset(
void *mem, ManifoldCrossSection *cs, double delta, ManifoldJoinType jt,
double miter_limit, int circular_segments);
// CrossSection Info
double manifold_cross_section_area(ManifoldCrossSection *cs);
int manifold_cross_section_num_vert(ManifoldCrossSection *cs);
int manifold_cross_section_num_contour(ManifoldCrossSection *cs);
int manifold_cross_section_is_empty(ManifoldCrossSection *cs);
ManifoldRect *manifold_cross_section_bounds(void *mem,
ManifoldCrossSection *cs);
ManifoldPolygons *manifold_cross_section_to_polygons(void *mem,
ManifoldCrossSection *cs);
// Rectangle
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2);
ManifoldVec2 manifold_rect_min(ManifoldRect *r);
ManifoldVec2 manifold_rect_max(ManifoldRect *r);
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r);
ManifoldVec2 manifold_rect_center(ManifoldRect *r);
float manifold_rect_scale(ManifoldRect *r);
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y);
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b);
void manifold_rect_include_pt(ManifoldRect *r, float x, float y);
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b);
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
float y1, float x2, float y2, float x3,
float y3);
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
float y);
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y);
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r);
int manifold_rect_is_empty(ManifoldRect *r);
int manifold_rect_is_finite(ManifoldRect *r);
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
ManifoldRect *r);
// Bounding Box
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
float y2, float z2);
ManifoldVec3 manifold_box_min(ManifoldBox *b);
ManifoldVec3 manifold_box_max(ManifoldBox *b);
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
ManifoldVec3 manifold_box_center(ManifoldBox *b);
float manifold_box_scale(ManifoldBox *b);
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4);
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
float z);
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
float z);
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
int manifold_box_is_finite(ManifoldBox *b);
// Static Quality Globals
void manifold_set_min_circular_angle(float degrees);
void manifold_set_min_circular_edge_length(float length);
void manifold_set_circular_segments(int number);
// Manifold Mesh Extraction
int manifold_meshgl_num_prop(ManifoldMeshGL *m);
int manifold_meshgl_num_vert(ManifoldMeshGL *m);
int manifold_meshgl_num_tri(ManifoldMeshGL *m);
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m);
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m);
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m);
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m);
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m);
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m);
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m);
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m);
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m);
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
// memory size
size_t manifold_manifold_size();
size_t manifold_manifold_vec_size();
size_t manifold_cross_section_size();
size_t manifold_cross_section_vec_size();
size_t manifold_simple_polygon_size();
size_t manifold_polygons_size();
size_t manifold_manifold_pair_size();
size_t manifold_meshgl_size();
size_t manifold_box_size();
size_t manifold_rect_size();
size_t manifold_curvature_size();
// destruction
void manifold_destruct_manifold(ManifoldManifold *m);
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms);
void manifold_destruct_cross_section(ManifoldCrossSection *m);
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv);
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
void manifold_destruct_polygons(ManifoldPolygons *p);
void manifold_destruct_meshgl(ManifoldMeshGL *m);
void manifold_destruct_box(ManifoldBox *b);
void manifold_destruct_rect(ManifoldRect *b);
// pointer free + destruction
void manifold_delete_manifold(ManifoldManifold *m);
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms);
void manifold_delete_cross_section(ManifoldCrossSection *cs);
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv);
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
void manifold_delete_polygons(ManifoldPolygons *p);
void manifold_delete_meshgl(ManifoldMeshGL *m);
void manifold_delete_box(ManifoldBox *b);
void manifold_delete_rect(ManifoldRect *b);
// MeshIO / Export
#ifdef MANIFOLD_EXPORT
ManifoldMaterial *manifold_material(void *mem);
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
void manifold_material_set_vert_color(ManifoldMaterial *mat,
ManifoldVec4 *vert_color, size_t n_vert);
ManifoldExportOptions *manifold_export_options(void *mem);
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
int faceted);
void manifold_export_options_set_material(ManifoldExportOptions *options,
ManifoldMaterial *mat);
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
ManifoldExportOptions *options);
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
int force_cleanup);
size_t manifold_material_size();
size_t manifold_export_options_size();
void manifold_destruct_material(ManifoldMaterial *m);
void manifold_destruct_export_options(ManifoldExportOptions *options);
void manifold_delete_material(ManifoldMaterial *m);
void manifold_delete_export_options(ManifoldExportOptions *options);
#endif
#ifdef __cplusplus
}
#endif
+92
View File
@@ -0,0 +1,92 @@
#pragma once
#include <stddef.h>
// opaque pointers
typedef struct ManifoldManifold ManifoldManifold;
typedef struct ManifoldManifoldVec ManifoldManifoldVec;
typedef struct ManifoldCrossSection ManifoldCrossSection;
typedef struct ManifoldCrossSectionVec ManifoldCrossSectionVec;
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
typedef struct ManifoldPolygons ManifoldPolygons;
typedef struct ManifoldMesh ManifoldMesh;
typedef struct ManifoldMeshGL ManifoldMeshGL;
typedef struct ManifoldBox ManifoldBox;
typedef struct ManifoldRect ManifoldRect;
#ifdef MANIFOLD_EXPORT
typedef struct ManifoldMaterial ManifoldMaterial;
typedef struct ManifoldExportOptions ManifoldExportOptions;
#endif
// structs
typedef struct ManifoldManifoldPair {
ManifoldManifold* first;
ManifoldManifold* second;
} ManifoldManifoldPair;
typedef struct ManifoldVec2 {
float x;
float y;
} ManifoldVec2;
typedef struct ManifoldVec3 {
float x;
float y;
float z;
} ManifoldVec3;
typedef struct ManifoldIVec3 {
int x;
int y;
int z;
} ManifoldIVec3;
typedef struct ManifoldVec4 {
float x;
float y;
float z;
float w;
} ManifoldVec4;
typedef struct ManifoldProperties {
float surface_area;
float volume;
} ManifoldProperties;
// enums
typedef enum ManifoldOpType {
MANIFOLD_ADD,
MANIFOLD_SUBTRACT,
MANIFOLD_INTERSECT
} ManifoldOpType;
typedef enum ManifoldError {
MANIFOLD_NO_ERROR,
MANIFOLD_NON_FINITE_VERTEX,
MANIFOLD_NOT_MANIFOLD,
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
MANIFOLD_PROPERTIES_WRONG_LENGTH,
MANIFOLD_MISSING_POSITION_PROPERTIES,
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
MANIFOLD_TRANSFORM_WRONG_LENGTH,
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
MANIFOLD_FACE_ID_WRONG_LENGTH,
MANIFOLD_INVALID_CONSTRUCTION,
} ManifoldError;
typedef enum ManifoldFillRule {
MANIFOLD_FILL_RULE_EVEN_ODD,
MANIFOLD_FILL_RULE_NON_ZERO,
MANIFOLD_FILL_RULE_POSITIVE,
MANIFOLD_FILL_RULE_NEGATIVE
} ManifoldFillRule;
typedef enum ManifoldJoinType {
MANIFOLD_JOIN_TYPE_SQUARE,
MANIFOLD_JOIN_TYPE_ROUND,
MANIFOLD_JOIN_TYPE_MITER,
} ManifoldJoinType;
+571
View File
@@ -0,0 +1,571 @@
#include <conv.h>
#include <cross_section.h>
#include <manifold.h>
#include <manifoldc.h>
#include <public.h>
#include <sdf.h>
#include <vector>
#include "box.cpp"
#include "cross.cpp"
#include "include/conv.h"
#include "include/types.h"
#include "rect.cpp"
#include "types.h"
#ifdef MANIFOLD_EXPORT
#include "meshio.cpp"
#endif
using namespace manifold;
namespace {
ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
ManifoldBox *bounds, float edge_length, float level,
bool seq) {
// typing with std::function rather than auto compiles when CUDA is on,
// passing it into GPU (and crashing) is avoided dynamically in `sdf.h`
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
return (sdf(v.x, v.y, v.z));
};
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
return to_c(new (mem) MeshGL(mesh));
}
ManifoldMeshGL *level_set_context(
void *mem, float (*sdf_context)(float, float, float, void *),
ManifoldBox *bounds, float edge_length, float level, bool seq, void *ctx) {
// Bind function with context argument to one without
using namespace std::placeholders;
std::function<float(float, float, float)> sdf =
std::bind(sdf_context, _1, _2, _3, ctx);
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
return (sdf(v.x, v.y, v.z));
};
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
return to_c(new (mem) MeshGL(mesh));
}
} // namespace
#ifdef __cplusplus
extern "C" {
#endif
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
size_t length) {
auto vec = new (mem) std::vector<glm::vec2>;
for (int i = 0; i < length; ++i) {
vec->push_back({ps[i].x, ps[i].y});
}
return to_c(vec);
}
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
size_t length) {
auto vec = new (mem) std::vector<SimplePolygon>;
auto polys = reinterpret_cast<SimplePolygon **>(ps);
for (int i = 0; i < length; ++i) {
vec->push_back(*polys[i]);
}
return to_c(vec);
}
size_t manifold_simple_polygon_length(ManifoldSimplePolygon *p) {
return from_c(p)->size();
}
size_t manifold_polygons_length(ManifoldPolygons *ps) {
return from_c(ps)->size();
}
size_t manifold_polygons_simple_length(ManifoldPolygons *ps, int idx) {
return (*from_c(ps))[idx].size();
}
ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon *p,
int idx) {
return to_c((*from_c(p))[idx]);
}
ManifoldSimplePolygon *manifold_polygons_get_simple(void *mem,
ManifoldPolygons *ps,
int idx) {
auto sp = (*from_c(ps))[idx];
return to_c(new (mem) SimplePolygon(sp));
}
ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
int pt_idx) {
return to_c((*from_c(ps))[simple_idx][pt_idx]);
}
ManifoldManifoldVec *manifold_manifold_empty_vec(void *mem) {
return to_c(new (mem) ManifoldVec());
}
ManifoldManifoldVec *manifold_manifold_vec(void *mem, size_t sz) {
return to_c(new (mem) ManifoldVec(sz));
}
void manifold_manifold_vec_reserve(ManifoldManifoldVec *ms, size_t sz) {
from_c(ms)->reserve(sz);
}
size_t manifold_manifold_vec_length(ManifoldManifoldVec *ms) {
return from_c(ms)->size();
}
ManifoldManifold *manifold_manifold_vec_get(void *mem, ManifoldManifoldVec *ms,
int idx) {
auto m = (*from_c(ms))[idx];
return to_c(new (mem) Manifold(m));
}
void manifold_manifold_vec_set(ManifoldManifoldVec *ms, int idx,
ManifoldManifold *m) {
(*from_c(ms))[idx] = *from_c(m);
}
void manifold_manifold_vec_push_back(ManifoldManifoldVec *ms,
ManifoldManifold *m) {
return from_c(ms)->push_back(*from_c(m));
}
ManifoldManifold *manifold_boolean(void *mem, ManifoldManifold *a,
ManifoldManifold *b, ManifoldOpType op) {
auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_batch_boolean(void *mem, ManifoldManifoldVec *ms,
ManifoldOpType op) {
auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
ManifoldManifold *b) {
auto m = (*from_c(a)) + (*from_c(b));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
ManifoldManifold *b) {
auto m = (*from_c(a)) - (*from_c(b));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
ManifoldManifold *b) {
auto m = (*from_c(a)) ^ (*from_c(b));
return to_c(new (mem) Manifold(m));
}
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
ManifoldManifold *a, ManifoldManifold *b) {
auto pair = from_c(a)->Split(*from_c(b));
auto first = new (mem_first) Manifold(pair.first);
auto second = new (mem_second) Manifold(pair.second);
return {to_c(first), to_c(second)};
}
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset) {
auto normal = glm::vec3(normal_x, normal_y, normal_z);
auto pair = from_c(m)->SplitByPlane(normal, offset);
auto first = new (mem_first) Manifold(pair.first);
auto second = new (mem_second) Manifold(pair.second);
return {to_c(first), to_c(second)};
}
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset) {
auto normal = glm::vec3(normal_x, normal_y, normal_z);
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
return to_c(new (mem) Manifold(trimmed));
}
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto v = glm::vec3(x, y, z);
auto translated = from_c(m)->Translate(v);
return to_c(new (mem) Manifold(translated));
}
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto rotated = from_c(m)->Rotate(x, y, z);
return to_c(new (mem) Manifold(rotated));
}
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto s = glm::vec3(x, y, z);
auto scaled = from_c(m)->Scale(s);
return to_c(new (mem) Manifold(scaled));
}
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
float y1, float z1, float x2, float y2,
float z2, float x3, float y3, float z3,
float x4, float y4, float z4) {
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
auto transformed = from_c(m)->Transform(mat);
return to_c(new (mem) Manifold(transformed));
}
ManifoldManifold *manifold_mirror(void *mem, ManifoldManifold *m, float nx,
float ny, float nz) {
auto mirrored = from_c(m)->Mirror({nx, ny, nz});
return to_c(new (mem) Manifold(mirrored));
}
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
ManifoldVec3 (*fun)(float, float, float)) {
std::function<void(glm::vec3 & v)> warp = [fun](glm::vec3 &v) {
v = from_c(fun(v.x, v.y, v.z));
};
auto warped = from_c(m)->Warp(warp);
return to_c(new (mem) Manifold(warped));
}
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
ManifoldBox *bounds, float edge_length,
float level) {
return level_set(mem, sdf, bounds, edge_length, level, false);
}
ManifoldMeshGL *manifold_level_set_seq(void *mem,
float (*sdf)(float, float, float),
ManifoldBox *bounds, float edge_length,
float level) {
return level_set(mem, sdf, bounds, edge_length, level, true);
}
ManifoldMeshGL *manifold_level_set_context(
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
float edge_length, float level, void *ctx) {
return level_set_context(mem, sdf, bounds, edge_length, level, false, ctx);
}
ManifoldMeshGL *manifold_level_set_seq_context(
void *mem, float (*sdf)(float, float, float, void *), ManifoldBox *bounds,
float edge_length, float level, void *ctx) {
return level_set_context(mem, sdf, bounds, edge_length, level, true, ctx);
}
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
auto refined = from_c(m)->Refine(refine);
return to_c(new (mem) Manifold(refined));
}
ManifoldManifold *manifold_empty(void *mem) {
return to_c(new (mem) Manifold());
}
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m) {
return to_c(new (mem) Manifold(*from_c(m)));
}
ManifoldManifold *manifold_tetrahedron(void *mem) {
auto m = Manifold::Tetrahedron();
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
int center) {
auto size = glm::vec3(x, y, z);
auto m = Manifold::Cube(size, center);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
float radius_high, int circular_segments,
int center) {
auto m = Manifold::Cylinder(height, radius_low, radius_high,
circular_segments, center);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_sphere(void *mem, float radius,
int circular_segments) {
auto m = Manifold::Sphere(radius, circular_segments);
return to_c(new (mem) Manifold(m));
}
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
size_t n_props, uint32_t *tri_verts,
size_t n_tris) {
auto mesh = new (mem) MeshGL();
mesh->numProp = n_props;
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
return to_c(mesh);
}
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
size_t n_verts, size_t n_props,
uint32_t *tri_verts, size_t n_tris,
float *halfedge_tangent) {
auto mesh = new (mem) MeshGL();
mesh->numProp = n_props;
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
return to_c(mesh);
}
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
int *half_edges, float *smoothness,
int n_edges) {
auto smooth = std::vector<Smoothness>();
for (int i = 0; i < n_edges; ++i) {
smooth.push_back({half_edges[i], smoothness[i]});
}
auto m = Manifold::Smooth(*from_c(mesh), smooth);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
auto m = Manifold(*from_c(mesh));
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_extrude(void *mem, ManifoldCrossSection *cs,
float height, int slices,
float twist_degrees, float scale_x,
float scale_y) {
auto scale = glm::vec2(scale_x, scale_y);
auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_revolve(void *mem, ManifoldCrossSection *cs,
int circular_segments) {
auto m = Manifold::Revolve(*from_c(cs), circular_segments);
return to_c(new (mem) Manifold(m));
}
ManifoldManifold *manifold_compose(void *mem, ManifoldManifoldVec *ms) {
auto composed = Manifold::Compose(*from_c(ms));
return to_c(new (mem) Manifold(composed));
}
ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m) {
auto comps = from_c(m)->Decompose();
return to_c(new (mem) std::vector<Manifold>(comps));
}
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m) {
auto mesh = from_c(m)->GetMeshGL();
return to_c(new (mem) MeshGL(mesh));
}
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
return to_c(new (mem) MeshGL(*from_c(m)));
}
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m) {
return from_c(m)->vertProperties.size();
}
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m) {
return from_c(m)->triVerts.size();
}
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m) {
return from_c(m)->mergeFromVert.size();
}
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
return from_c(m)->runIndex.size();
}
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m) {
return from_c(m)->runOriginalID.size();
}
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m) {
return from_c(m)->runTransform.size();
}
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m) {
return from_c(m)->faceID.size();
}
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m) {
return from_c(m)->halfedgeTangent.size();
}
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->vertProperties);
}
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->triVerts);
}
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->mergeFromVert);
}
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->mergeToVert);
}
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->runIndex);
}
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->runOriginalID);
}
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->runTransform);
}
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->faceID);
}
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->halfedgeTangent);
}
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
auto orig = from_c(m)->AsOriginal();
return to_c(new (mem) Manifold(orig));
}
int manifold_original_id(ManifoldManifold *m) {
return from_c(m)->OriginalID();
}
int manifold_is_empty(ManifoldManifold *m) { return from_c(m)->IsEmpty(); }
ManifoldError manifold_status(ManifoldManifold *m) {
auto error = from_c(m)->Status();
return to_c(error);
}
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
return to_c(from_c(m)->GetProperties());
}
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
auto box = from_c(m)->BoundingBox();
return to_c(new (mem) Box(box));
}
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }
ManifoldManifold *manifold_set_properties(void *mem, ManifoldManifold *m,
int num_prop,
void (*fun)(float *new_prop,
ManifoldVec3 position,
const float *old_prop)) {
std::function<void(float *, glm::vec3, const float *)> f =
[fun](float *new_prop, glm::vec3 v, const float *old_prop) {
fun(new_prop, to_c(v), old_prop);
};
auto man = from_c(m)->SetProperties(num_prop, f);
return to_c(new (mem) Manifold(man));
};
ManifoldManifold *manifold_calculate_curvature(void *mem, ManifoldManifold *m,
int gaussian_idx, int mean_idx) {
auto man = from_c(m)->CalculateCurvature(gaussian_idx, mean_idx);
return to_c(new (mem) Manifold(man));
}
// Static Quality Globals
void manifold_set_min_circular_angle(float degrees) {
Quality::SetMinCircularAngle(degrees);
}
void manifold_set_min_circular_edge_length(float length) {
Quality::SetMinCircularEdgeLength(length);
}
void manifold_set_circular_segments(int number) {
Quality::SetCircularSegments(number);
}
int manifold_get_circular_segments(float radius) {
return Quality::GetCircularSegments(radius);
}
// memory size
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
size_t manifold_cross_section_vec_size() {
return sizeof(std::vector<CrossSection>);
}
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
size_t manifold_polygons_size() { return sizeof(Polygons); }
size_t manifold_manifold_size() { return sizeof(Manifold); }
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
size_t manifold_box_size() { return sizeof(Box); }
size_t manifold_rect_size() { return sizeof(Rect); }
// pointer free + destruction
void manifold_delete_cross_section(ManifoldCrossSection *c) {
delete from_c(c);
}
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec *csv) {
delete from_c(csv);
}
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p) {
delete from_c(p);
}
void manifold_delete_polygons(ManifoldPolygons *p) { delete from_c(p); }
void manifold_delete_manifold(ManifoldManifold *m) { delete from_c(m); }
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms) {
delete from_c(ms);
}
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
void manifold_delete_rect(ManifoldRect *r) { delete from_c(r); }
// destruction
void manifold_destruct_cross_section(ManifoldCrossSection *cs) {
from_c(cs)->~CrossSection();
}
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec *csv) {
from_c(csv)->~CrossSectionVec();
}
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p) {
from_c(p)->~SimplePolygon();
}
void manifold_destruct_polygons(ManifoldPolygons *p) { from_c(p)->~Polygons(); }
void manifold_destruct_manifold(ManifoldManifold *m) { from_c(m)->~Manifold(); }
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms) {
from_c(ms)->~ManifoldVec();
}
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
void manifold_destruct_rect(ManifoldRect *r) { from_c(r)->~Rect(); }
#ifdef __cplusplus
}
#endif
+104
View File
@@ -0,0 +1,104 @@
#include "meshIO.h"
#include <conv.h>
#include "include/types.h"
// C <-> C++ conversions
ManifoldMaterial *to_c(manifold::Material *m) {
return reinterpret_cast<ManifoldMaterial *>(m);
}
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
return reinterpret_cast<ManifoldExportOptions *>(m);
}
manifold::Material *from_c(ManifoldMaterial *mat) {
return reinterpret_cast<manifold::Material *>(mat);
}
manifold::ExportOptions *from_c(ManifoldExportOptions *options) {
return reinterpret_cast<manifold::ExportOptions *>(options);
}
#ifdef __cplusplus
extern "C" {
#endif
// material
ManifoldMaterial *manifold_material(void *mem) {
return to_c(new (mem) manifold::Material());
}
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
from_c(mat)->roughness = roughness;
}
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
from_c(mat)->metalness = metalness;
}
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
from_c(mat)->color = from_c(color);
}
void manifold_material_set_vert_color(ManifoldMaterial *mat,
ManifoldVec4 *vert_color, size_t n_vert) {
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
}
// export options
ManifoldExportOptions *manifold_export_options(void *mem) {
return to_c(new (mem) manifold::ExportOptions());
}
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
int faceted) {
from_c(options)->faceted = faceted;
}
void manifold_export_options_set_material(ManifoldExportOptions *options,
ManifoldMaterial *mat) {
from_c(options)->mat = *from_c(mat);
}
// mesh IO
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
ManifoldExportOptions *options) {
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
}
ManifoldMeshGL *manifold_import_meshgl(void *mem, const char *filename,
int force_cleanup) {
auto m = manifold::ImportMesh(std::string(filename), force_cleanup);
return to_c(new (mem) manifold::MeshGL(m));
}
// memory size
size_t manifold_material_size() { return sizeof(manifold::Material); }
size_t manifold_export_options_size() {
return sizeof(manifold::ExportOptions);
}
// memory free + destruction
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
void manifold_delete_export_options(ManifoldExportOptions *m) {
delete (from_c(m));
}
// destruction
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
void manifold_destruct_export_options(ManifoldExportOptions *m) {
from_c(m)->~ExportOptions();
}
#ifdef __cplusplus
}
#endif
+93
View File
@@ -0,0 +1,93 @@
#include <conv.h>
#include <manifold.h>
#include <public.h>
#include "cross_section.h"
#include "types.h"
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2) {
auto p1 = glm::vec2(x1, y1);
auto p2 = glm::vec2(x2, y2);
auto rect = new (mem) Rect(p1, p2);
return to_c(rect);
}
ManifoldVec2 manifold_rect_min(ManifoldRect *r) {
return to_c((*from_c(r)).min);
}
ManifoldVec2 manifold_rect_max(ManifoldRect *r) {
return to_c((*from_c(r)).max);
}
ManifoldVec2 manifold_rect_dimensions(ManifoldRect *r) {
auto v = from_c(r)->Size();
return {v.x, v.y};
}
ManifoldVec2 manifold_rect_center(ManifoldRect *r) {
auto v = from_c(r)->Center();
return {v.x, v.y};
}
float manifold_rect_scale(ManifoldRect *r) { return from_c(r)->Scale(); }
int manifold_rect_contains_pt(ManifoldRect *r, float x, float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
return rect.Contains(p);
}
int manifold_rect_contains_rect(ManifoldRect *a, ManifoldRect *b) {
auto outer = *from_c(a);
auto inner = *from_c(b);
return outer.Contains(inner);
}
void manifold_rect_include_pt(ManifoldRect *r, float x, float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
rect.Union(p);
}
ManifoldRect *manifold_rect_union(void *mem, ManifoldRect *a, ManifoldRect *b) {
auto rect = from_c(a)->Union(*from_c(b));
return to_c(new (mem) Rect(rect));
}
ManifoldRect *manifold_rect_transform(void *mem, ManifoldRect *r, float x1,
float y1, float x2, float y2, float x3,
float y3) {
auto mat = glm::mat3x2(x1, y1, x2, y2, x3, y3);
auto transformed = from_c(r)->Transform(mat);
return to_c(new (mem) Rect(transformed));
}
ManifoldRect *manifold_rect_translate(void *mem, ManifoldRect *r, float x,
float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
auto translated = (*from_c(r)) + p;
return to_c(new (mem) Rect(translated));
}
ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y) {
auto rect = *from_c(r);
auto p = glm::vec2(x, y);
auto scaled = (*from_c(r)) * p;
return to_c(new (mem) Rect(scaled));
}
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r) {
return from_c(a)->DoesOverlap(*from_c(r));
}
int manifold_rect_is_empty(ManifoldRect *r) { return from_c(r)->IsEmpty(); }
int manifold_rect_is_finite(ManifoldRect *r) { return from_c(r)->IsFinite(); }
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
ManifoldRect *r) {
auto cs = from_c(r)->AsCrossSection();
return to_c(new (mem) CrossSection(cs));
}
+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(manifold3d manifold3d.cpp)
target_link_libraries(manifold3d PRIVATE manifold)
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(manifold3d PUBLIC cxx_std_17)
target_include_directories(manifold3d
PRIVATE ${PYBIND11_DIR}/include
)
+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 manifold3d import Manifold
# https://gist.github.com/deckar01/ef11def51de7e71d9f288c6e5819fdb7
INCHES = 25.4
brick_depth = (3 + 5/8) * INCHES
brick_height = (2 + 1/4) * INCHES
brick_length = (7 + 5/8) * INCHES
mortar_gap = (3/8) * INCHES
def brick():
return Manifold.cube(brick_length, brick_depth, brick_height)
def halfbrick():
return Manifold.cube((brick_length - mortar_gap)/2, brick_depth, brick_height)
def row(length):
bricks = [brick().translate((brick_length + mortar_gap)*x, 0, 0)
for x in range(length)]
return Manifold(bricks)
def wall(length, height, alternate=0):
bricks = [row(length).translate(
((z+alternate) % 2)*(brick_depth+mortar_gap), 0,
(brick_height+mortar_gap)*z) for z in range(height)]
return Manifold(bricks)
def walls(length, width, height):
return Manifold([
wall(length, height),
wall(width, height, 1).rotate(0, 0, 90).translate(brick_depth, 0, 0),
wall(length, height, 1).translate(
0, (width)*(brick_length+mortar_gap), 0),
wall(width, height).rotate(0, 0, 90)
.translate((length+0.5)*(brick_length+mortar_gap) - mortar_gap, 0, 0),
])
def floor(length, width):
results = [walls(length, width, 1)]
if length > 1 and width > 1:
results.append(floor(length-1, width-1).translate(brick_depth +
mortar_gap, brick_depth+mortar_gap, 0))
if length == 1 and width > 1:
results.append(row(width-1).rotate(0, 0, 90))
if width == 1 and length > 1:
results.append(row(length-1).translate(2*(brick_depth +
mortar_gap), brick_depth+mortar_gap, 0))
results.append(halfbrick().translate(
brick_depth+mortar_gap, brick_depth+mortar_gap, 0))
return Manifold(results)
def run(width=10, length=10, height=10):
return walls(length, width, height) + floor(length, width)
@@ -0,0 +1,30 @@
"""
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from manifold3d import Manifold
from functools import reduce
# https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355#file-cube-with-half-spheres-dents-scad
def run(n=5, overlap=True):
a = Manifold.cube(n, n, 0.5).translate(-0.5, -0.5, -0.5)
spheres = [Manifold.sphere(0.45 if overlap else 0.55, 50).translate(i, j, 0)
for i in range(n) for j in range(n)]
spheres = reduce(lambda a, b: a + b, spheres)
return a - spheres
+38
View File
@@ -0,0 +1,38 @@
from manifold3d import CrossSection, FillRule
def run():
# create a polygon
polygon_points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
polygons_points = [polygon_points]
# create a cross-section
cross_section = CrossSection(polygons_points)
polygons = cross_section.to_polygons()
polygon = polygons[0]
if set(polygon) != set(polygon_points):
raise Exception(f"{polygon=} differs from {polygon_points=}")
# extrude a polygon to create a manifold
extruded_polygon = cross_section.extrude(10.0)
eps = 0.001
observed_volume = extruded_polygon.get_volume()
expected_volume = 10.0
if abs(observed_volume - expected_volume) > eps:
raise Exception(f"{observed_volume=} differs from {expected_volume=}")
observed_surface_area = extruded_polygon.get_surface_area()
expected_surface_area = 42.0
if abs(observed_surface_area - expected_surface_area) > eps:
raise Exception(f"{observed_surface_area=} differs from {expected_surface_area=}")
# get bounding box from manifold
observed_bbox = extruded_polygon.bounding_box
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
if observed_bbox != expected_bbox:
raise Exception(f"{observed_bbox=} differs from {expected_bbox=}")
return extruded_polygon
if __name__ == "__main__":
run()
File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
"""
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import pathlib
import sys
import importlib
import trimesh
import numpy as np
from time import time
if __name__ == "__main__":
current_file = pathlib.Path(__file__)
current_dir = current_file.parent
files = [f.parts[-1][:-3]
for f in current_dir.glob('*.py') if f != current_file]
export_models = len(sys.argv) == 2 and sys.argv[-1] == '-e'
for f in files:
module = importlib.import_module(f)
t0 = time()
model = module.run()
mesh = model.to_mesh()
if export_models:
if mesh.vert_properties.shape[1] > 3:
vertices = np.hsplit(mesh.vert_properties, 3)[0]
else:
vertices = mesh.vert_properties
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts)
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
print(f'Exported model to {f}.glb')
t1 = time()
print(f'Took {(t1-t0)*1000:.1f}ms for {f}')
@@ -0,0 +1,7 @@
from manifold3d import Manifold
def run():
# for some reason this causes collider error
obj = Manifold.cube()
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
return obj
+876
View File
@@ -0,0 +1,876 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cross_section.h"
#include "manifold.h"
#include "pybind11/functional.h"
#include "pybind11/numpy.h"
#include "pybind11/operators.h"
#include "pybind11/pybind11.h"
#include "pybind11/stl.h"
namespace py = pybind11;
using namespace manifold;
typedef std::tuple<float, float> Float2;
typedef std::tuple<float, float, float> Float3;
constexpr auto pyArrayFlags = py::array::c_style | py::array::forcecast;
PYBIND11_MAKE_OPAQUE(std::vector<int>);
PYBIND11_MAKE_OPAQUE(std::vector<uint32_t>);
PYBIND11_MAKE_OPAQUE(std::vector<float>);
template <typename T>
std::vector<T> toVector(const py::array_t<T> &arr) {
return std::vector<T>(arr.data(), arr.data() + arr.size());
}
PYBIND11_MODULE(manifold3d, m) {
m.doc() = "Python binding for the Manifold library.";
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
"Sets an angle constraint the default number of circular segments for "
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
"and Manifold::Revolve() constructors. The number of segments will be "
"rounded up to the nearest factor of four."
"\n\n"
":param angle: The minimum angle in degrees between consecutive "
"segments. The angle will increase if the the segments hit the minimum "
"edge length.\n"
"Default is 10 degrees.");
m.def("set_min_circular_edge_length", Quality::SetMinCircularEdgeLength,
"Sets a length constraint the default number of circular segments for "
"the CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), "
"and Manifold::Revolve() constructors. The number of segments will be "
"rounded up to the nearest factor of four."
"\n\n"
":param length: The minimum length of segments. The length will "
"increase if the the segments hit the minimum angle. Default is 1.0.");
m.def("set_circular_segments", Quality::SetCircularSegments,
"Sets the default number of circular segments for the "
"CrossSection::Circle(), Manifold::Cylinder(), Manifold::Sphere(), and "
"Manifold::Revolve() constructors. Overrides the edge length and angle "
"constraints and sets the number of segments to exactly this value."
"\n\n"
":param number: Number of circular segments. Default is 0, meaning no "
"constraint is applied.");
m.def("get_circular_segments", Quality::GetCircularSegments,
"Determine the result of the SetMinCircularAngle(), "
"SetMinCircularEdgeLength(), and SetCircularSegments() defaults."
"\n\n"
":param radius: For a given radius of circle, determine how many "
"default");
py::class_<Manifold>(m, "Manifold")
.def(py::init<>())
.def(py::init([](std::vector<Manifold> &manifolds) {
Manifold result;
if (manifolds.size() >= 1) {
// for some reason using Manifold() as the initial object
// will cause failure for python specifically
// unable to reproduce with c++ directly
Manifold first = manifolds[0];
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
return first;
} else {
return Manifold();
}
}),
"Construct manifold as the union of a set of manifolds.")
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"transform",
[](Manifold &self, py::array_t<float> &mat) {
auto mat_view = mat.unchecked<2>();
if (mat.ndim() != 2 || mat.shape(0) != 3 || mat.shape(1) != 4)
throw std::runtime_error("Invalid matrix shape, expected (3, 4)");
glm::mat4x3 mat_glm;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
mat_glm[j][i] = mat_view(i, j);
}
}
return self.Transform(mat_glm);
},
py::arg("m"),
"Transform this Manifold in space. The first three columns form a "
"3x3 matrix transform and the last is a translation vector. This "
"operation can be chained. Transforms are combined and applied "
"lazily.\n"
"\n\n"
":param m: The affine transform matrix to apply to all the vertices.")
.def(
"translate",
[](Manifold &self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
return self.Translate(glm::vec3(x, y, z));
},
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
"Move this Manifold in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param x: X axis translation. (default 0.0).\n"
":param y: Y axis translation. (default 0.0).\n"
":param z: Z axis translation. (default 0.0).")
.def(
"translate",
[](Manifold &self, py::array_t<float> &t) {
auto t_view = t.unchecked<1>();
if (t.ndim() != 1 || t.shape(0) != 3)
throw std::runtime_error("Invalid vector shape");
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
},
py::arg("t"),
"Move this Manifold in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param v: The vector to add to every vertex.")
.def(
"scale",
[](Manifold &self, float scale) {
return self.Scale(glm::vec3(scale));
},
py::arg("scale"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param scale: The scalar multiplier for each component of every "
"vertices.")
.def(
"scale",
[](Manifold &self, py::array_t<float> &scale) {
auto scale_view = scale.unchecked<1>();
if (scale.ndim() != 1 || scale.shape(0) != 3)
throw std::runtime_error("Invalid vector shape, expected (3)");
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
return self.Scale(v);
},
py::arg("v"),
"Scale this Manifold in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param v: The vector to multiply every vertex by component.")
.def(
"mirror",
[](Manifold &self, py::array_t<float> &mirror) {
auto v_view = mirror.unchecked<1>();
if (mirror.ndim() != 1 || mirror.shape(0) != 3)
throw std::runtime_error("Invalid vector shape, expected (3)");
glm::vec3 v(v_view(0), v_view(1), v_view(2));
return self.Mirror(v);
},
py::arg("v"),
"Mirror this Manifold in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param mirror: The vector defining the axis of mirroring.")
.def(
"rotate",
[](Manifold &self, py::array_t<float> &v) {
auto v_view = v.unchecked<1>();
if (v.ndim() != 1 || v.shape(0) != 3)
throw std::runtime_error("Invalid vector shape, expected (3)");
return self.Rotate(v_view(0), v_view(1), v_view(2));
},
py::arg("v"),
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then Y, then Z, in degrees. We use degrees so that we can "
"minimize rounding error, and eliminate it completely for any "
"multiples of 90 degrees. Additionally, more efficient code paths "
"are used to update the manifold when the transforms only rotate by "
"multiples of 90 degrees. This operation can be chained. Transforms "
"are combined and applied lazily."
"\n\n"
":param v: [X, Y, Z] rotation in degrees.")
.def(
"rotate",
[](Manifold &self, float xDegrees = 0.0f, float yDegrees = 0.0f,
float zDegrees = 0.0f) {
return self.Rotate(xDegrees, yDegrees, zDegrees);
},
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
py::arg("z_degrees") = 0.0f,
"Applies an Euler angle rotation to the manifold, first about the X "
"axis, then Y, then Z, in degrees. We use degrees so that we can "
"minimize rounding error, and eliminate it completely for any "
"multiples of 90 degrees. Additionally, more efficient code paths "
"are used to update the manifold when the transforms only rotate by "
"multiples of 90 degrees. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param x: X rotation in degrees. (default 0.0).\n"
":param y: Y rotation in degrees. (default 0.0).\n"
":param z: Z rotation in degrees. (default 0.0).")
.def(
"warp",
[](Manifold &self, const std::function<Float3(Float3)> &f) {
return self.Warp([&f](glm::vec3 &v) {
Float3 fv = f(std::make_tuple(v.x, v.y, v.z));
v.x = std::get<0>(fv);
v.y = std::get<1>(fv);
v.z = std::get<2>(fv);
});
},
py::arg("f"),
"This function does not change the topology, but allows the vertices "
"to be moved according to any arbitrary input function. It is easy "
"to create a function that warps a geometrically valid object into "
"one which overlaps, but that is not checked here, so it is up to "
"the user to choose their function with discretion."
"\n\n"
":param warpFunc: A function that modifies a given vertex position.")
.def(
"refine", [](Manifold &self, int n) { return self.Refine(n); },
py::arg("n"),
"Increase the density of the mesh by splitting every edge into n "
"pieces. For instance, with n = 2, each triangle will be split into "
"4 triangles. These will all be coplanar (and will not be "
"immediately collapsed) unless the Mesh/Manifold has "
"halfedgeTangents specified (e.g. from the Smooth() constructor), "
"in which case the new vertices will be moved to the interpolated "
"surface according to their barycentric coordinates.\n"
"\n"
":param n: The number of pieces to split every edge into. Must be > "
"1.")
.def(
"to_mesh",
[](Manifold &self, std::optional<py::array_t<uint32_t>> &normalIdx) {
glm::ivec3 v(0);
if (normalIdx.has_value()) {
if (normalIdx.value().ndim() != 1 ||
normalIdx.value().shape(0) != 3)
throw std::runtime_error("Invalid vector shape, expected (3)");
auto normalIdx_view = normalIdx.value().unchecked<1>();
v = glm::ivec3(normalIdx_view(0), normalIdx_view(1),
normalIdx_view(2));
}
return self.GetMeshGL(v);
},
"The most complete output of this library, returning a MeshGL that "
"is designed to easily push into a renderer, including all "
"interleaved vertex properties that may have been input. It also "
"includes relations to all the input meshes that form a part of "
"this result and the transforms applied to each."
"\n\n"
":param normalIdx: If the original MeshGL inputs that formed this "
"manifold had properties corresponding to normal vectors, you can "
"specify which property channels these are (x, y, z), which will "
"cause this output MeshGL to automatically update these normals "
"according to the applied transforms and front/back side. Each "
"channel must be >= 3 and < numProp, and all original MeshGLs must "
"use the same channels for their normals.",
py::arg("normalIdx") = py::none())
.def("num_vert", &Manifold::NumVert,
"The number of vertices in the Manifold.")
.def("num_edge", &Manifold::NumEdge,
"The number of edges in the Manifold.")
.def("num_tri", &Manifold::NumTri,
"The number of triangles in the Manifold.")
.def("num_prop", &Manifold::NumProp,
"The number of properties per vertex in the Manifold")
.def("num_prop_vert", &Manifold::NumPropVert,
"The number of property vertices in the Manifold. This will always "
"be >= NumVert, as some physical vertices may be duplicated to "
"account for different properties on different neighboring "
"triangles.")
.def("precision", &Manifold::Precision,
"Returns the precision of this Manifold's vertices, which tracks "
"the approximate rounding error over all the transforms and "
"operations that have led to this state. Any triangles that are "
"colinear within this precision are considered degenerate and "
"removed. This is the value of &epsilon; defining "
"[&epsilon;-valid](https://github.com/elalish/manifold/wiki/"
"Manifold-Library#definition-of-%CE%B5-valid).")
.def("genus", &Manifold::Genus,
"The genus is a topological property of the manifold, representing "
"the number of \"handles\". A sphere is 0, torus 1, etc. It is only "
"meaningful for a single mesh, so it is best to call Decompose() "
"first.")
.def(
"get_volume",
[](Manifold &self) { return self.GetProperties().volume; },
"Get the volume of the manifold\n This is clamped to zero for a "
"given face if they are within the Precision().")
.def(
"get_surface_area",
[](Manifold &self) { return self.GetProperties().surfaceArea; },
"Get the surface area of the manifold\n This is clamped to zero for "
"a given face if they are within the Precision().")
.def("original_id", &Manifold::OriginalID,
"If this mesh is an original, this returns its meshID that can be "
"referenced by product manifolds' MeshRelation. If this manifold is "
"a product, this returns -1.")
.def("as_original", &Manifold::AsOriginal,
"This function condenses all coplanar faces in the relation, and "
"collapses those edges. In the process the relation to ancestor "
"meshes is lost and this new Manifold is marked an original. "
"Properties are preserved, so if they do not match across an edge, "
"that edge will be kept.")
.def("is_empty", &Manifold::IsEmpty,
"Does the Manifold have any triangles?")
.def(
"decompose", [](Manifold &self) { return self.Decompose(); },
"This operation returns a vector of Manifolds that are "
"topologically disconnected. If everything is connected, the vector "
"is length one, containing a copy of the original. It is the inverse "
"operation of Compose().")
.def("split", &Manifold::Split,
"Split cuts this manifold in two using the cutter manifold. The "
"first result is the intersection, second is the difference. This "
"is more efficient than doing them separately.")
.def(
"split_by_plane",
[](Manifold &self, Float3 normal, float originOffset) {
return self.SplitByPlane(
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
originOffset);
},
py::arg("normal"), py::arg("origin_offset"),
"Convenient version of Split() for a half-space."
"\n\n"
":param normal: This vector is normal to the cutting plane and its "
"length does not matter. The first result is in the direction of "
"this vector, the second result is on the opposite side.\n"
":param originOffset: The distance of the plane from the origin in "
"the direction of the normal vector.")
.def(
"trim_by_plane",
[](Manifold &self, Float3 normal, float originOffset) {
return self.TrimByPlane(
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
originOffset);
},
py::arg("normal"), py::arg("origin_offset"),
"Identical to SplitByPlane(), but calculating and returning only the "
"first result."
"\n\n"
":param normal: This vector is normal to the cutting plane and its "
"length does not matter. The result is in the direction of this "
"vector from the plane.\n"
":param originOffset: The distance of the plane from the origin in "
"the direction of the normal vector.")
.def_property_readonly(
"bounding_box",
[](Manifold &self) {
auto b = self.BoundingBox();
py::tuple box = py::make_tuple(b.min[0], b.min[1], b.min[2],
b.max[0], b.max[1], b.max[2]);
return box;
},
"Gets the manifold bounding box as a tuple "
"(xmin, ymin, zmin, xmax, ymax, zmax).")
.def_static(
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
"Constructs a smooth version of the input mesh by creating tangents; "
"this method will throw if you have supplied tangents with your "
"mesh already. The actual triangle resolution is unchanged; use the "
"Refine() method to interpolate to a higher-resolution curve."
"\n\n"
"By default, every edge is calculated for maximum smoothness (very "
"much approximately), attempting to minimize the maximum mean "
"Curvature magnitude. No higher-order derivatives are considered, "
"as the interpolation is independent per triangle, only sharing "
"constraints on their boundaries."
"\n\n"
":param mesh: input Mesh.\n"
":param sharpenedEdges: If desired, you can supply a vector of "
"sharpened halfedges, which should in general be a small subset of "
"all halfedges. Order of entries doesn't matter, as each one "
"specifies the desired smoothness (between zero and one, with one "
"the default for all unspecified halfedges) and the halfedge index "
"(3 * triangle index + [0,1,2] where 0 is the edge between triVert 0 "
"and 1, etc)."
"\n\n"
"At a smoothness value of zero, a sharp crease is made. The "
"smoothness is interpolated along each edge, so the specified value "
"should be thought of as an average. Where exactly two sharpened "
"edges meet at a vertex, their tangents are rotated to be colinear "
"so that the sharpened edge can be continuous. Vertices with only "
"one sharpened edge are completely smooth, allowing sharpened edges "
"to smoothly vanish at termination. A single vertex can be sharpened "
"by sharping all edges that are incident on it, allowing cones to be "
"formed.")
.def_static(
"from_mesh", [](const MeshGL &mesh) { return Manifold(mesh); },
py::arg("mesh"))
.def_static(
"tetrahedron", []() { return Manifold::Tetrahedron(); },
"Constructs a tetrahedron centered at the origin with one vertex at "
"(1,1,1) and the rest at similarly symmetric points.")
.def_static(
"cube",
[](Float3 size, bool center = false) {
return Manifold::Cube(
glm::vec3(std::get<0>(size), std::get<1>(size),
std::get<2>(size)),
center);
},
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first octant, touching the origin."
"\n\n"
":param size: The X, Y, and Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cube",
[](py::array_t<float> &size, bool center = false) {
auto size_view = size.unchecked<1>();
if (size.ndim() != 1 || size.shape(0) != 3)
throw std::runtime_error("Invalid vector shape, expected (3)");
return Manifold::Cube(
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
},
py::arg("size"), py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first octant, touching the origin."
"\n\n"
":param size: The X, Y, and Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cube",
[](float x, float y, float z, bool center = false) {
return Manifold::Cube(glm::vec3(x, y, z), center);
},
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
"Constructs a unit cube (edge lengths all one), by default in the "
"first octant, touching the origin."
"\n\n"
":param x: The X dimensions of the box.\n"
":param y: The Y dimensions of the box.\n"
":param z: The Z dimensions of the box.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"cylinder",
[](float height, float radiusLow, float radiusHigh = -1.0f,
int circularSegments = 0, bool center = false) {
return Manifold::Cylinder(height, radiusLow, radiusHigh,
circularSegments, center);
},
py::arg("height"), py::arg("radius_low"),
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
py::arg("center") = false,
"A convenience constructor for the common case of extruding a "
"circle. Can also form cones if both radii are specified."
"\n\n"
":param height: Z-extent\n"
":param radiusLow: Radius of bottom circle. Must be positive.\n"
":param radiusHigh: Radius of top circle. Can equal zero. Default "
"(-1) is equal to radiusLow.\n"
":param circularSegments: How many line segments to use around the "
"circle. Default (-1) is calculated by the static Defaults.\n"
":param center: Set to true to shift the center to the origin. "
"Default is origin at the bottom.")
.def_static(
"sphere",
[](float radius, int circularSegments = 0) {
return Manifold::Sphere(radius, circularSegments);
},
py::arg("radius"), py::arg("circular_segments") = 0,
"Constructs a geodesic sphere of a given radius.\n"
"\n"
":param radius: Radius of the sphere. Must be positive.\n"
":param circularSegments: Number of segments along its diameter. "
"This number will always be rounded up to the nearest factor of "
"four, as this sphere is constructed by refining an octahedron. This "
"means there are a circle of vertices on all three of the axis "
"planes. Default is calculated by the static Defaults.")
.def_static("reserve_ids", Manifold::ReserveIDs, py::arg("n"),
"Returns the first of n sequential new unique mesh IDs for "
"marking sets of triangles that can be looked up after "
"further operations. Assign to MeshGL.runOriginalID vector");
py::class_<MeshGL>(m, "Mesh")
.def(
// note that reshape requires mutable array_t, but this will not
// affect the original array passed into the function
py::init(
[](py::array_t<float, pyArrayFlags> &vertProp,
py::array_t<int, pyArrayFlags> &triVerts,
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
&mergeFromVert,
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
&mergeToVert,
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
&runIndex,
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
&runOriginalID,
std::optional<py::array_t<float, pyArrayFlags>> &runTransform,
const std::optional<py::array_t<uint32_t, pyArrayFlags>>
&faceID,
const std::optional<py::array_t<float, pyArrayFlags>>
&halfedgeTangent,
float precision) {
MeshGL out;
out.numProp = vertProp.shape(1);
vertProp = vertProp.reshape({-1});
out.vertProperties = toVector<float>(vertProp);
if (triVerts.ndim() != 2 || triVerts.shape(1) != 3)
throw std::runtime_error(
"Invalid tri_verts shape, expected (-1, 3)");
triVerts = triVerts.reshape({-1});
out.triVerts = toVector<uint32_t>(triVerts);
if (mergeFromVert.has_value())
out.mergeFromVert = toVector<uint32_t>(mergeFromVert.value());
if (mergeToVert.has_value())
out.mergeToVert = toVector<uint32_t>(mergeToVert.value());
if (runIndex.has_value())
out.runIndex = toVector<uint32_t>(runIndex.value());
if (runOriginalID.has_value())
out.runOriginalID = toVector<uint32_t>(runOriginalID.value());
if (runTransform.has_value()) {
auto runTransform1 = runTransform.value();
if (runTransform1.ndim() != 3 ||
runTransform1.shape(1) != 4 ||
runTransform1.shape(2) != 3)
throw std::runtime_error(
"Invalid run_transform shape, expected (-1, 4, 3)");
runTransform1 = runTransform1.reshape({-1});
out.runTransform = toVector<float>(runTransform1);
}
if (faceID.has_value())
out.faceID = toVector<uint32_t>(faceID.value());
if (halfedgeTangent.has_value()) {
auto halfedgeTangent1 = halfedgeTangent.value();
if (halfedgeTangent1.ndim() != 3 ||
halfedgeTangent1.shape(1) != 3 ||
halfedgeTangent1.shape(2) != 4)
throw std::runtime_error(
"Invalid halfedge_tangent shape, expected (-1, 3, 4)");
halfedgeTangent1 = halfedgeTangent1.reshape({-1});
out.halfedgeTangent = toVector<float>(halfedgeTangent1);
}
return out;
}),
py::arg("vert_properties"), py::arg("tri_verts"),
py::arg("merge_from_vert") = py::none(),
py::arg("merge_to_vert") = py::none(),
py::arg("run_index") = py::none(),
py::arg("run_original_id") = py::none(),
py::arg("run_transform") = py::none(),
py::arg("face_id") = py::none(),
py::arg("halfedge_tangent") = py::none(), py::arg("precision") = 0)
.def_property_readonly(
"vert_properties",
[](const MeshGL &self) {
return py::array(self.vertProperties.size(),
self.vertProperties.data())
.reshape(std::array<int, 2>{-1, (int)self.numProp});
})
.def_property_readonly("tri_verts",
[](const MeshGL &self) {
return py::array(self.triVerts.size(),
self.triVerts.data())
.reshape(std::array<int, 2>{-1, 3});
})
.def_property_readonly("run_transform",
[](const MeshGL &self) {
return py::array(self.runTransform.size(),
self.runTransform.data())
.reshape(std::array<int, 3>{-1, 4, 3});
})
.def_property_readonly("halfedge_tangent",
[](const MeshGL &self) {
return py::array(self.halfedgeTangent.size(),
self.halfedgeTangent.data())
.reshape(std::array<int, 3>{-1, 3, 4});
})
.def_readonly("merge_from_vert", &MeshGL::mergeFromVert)
.def_readonly("merge_to_vert", &MeshGL::mergeToVert)
.def_readonly("run_index", &MeshGL::runIndex)
.def_readonly("run_original_id", &MeshGL::runOriginalID)
.def_readonly("face_id", &MeshGL::faceID);
py::enum_<CrossSection::FillRule>(m, "FillRule")
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
"Only odd numbered sub-regions are filled.")
.value("NonZero", CrossSection::FillRule::NonZero,
"Only non-zero sub-regions are filled.")
.value("Positive", CrossSection::FillRule::Positive,
"Only sub-regions with winding counts > 0 are filled.")
.value("Negative", CrossSection::FillRule::Negative,
"Only sub-regions with winding counts < 0 are filled.");
py::enum_<CrossSection::JoinType>(m, "JoinType")
.value("Square", CrossSection::JoinType::Square,
"Squaring is applied uniformly at all joins where the internal "
"join angle is less that 90 degrees. The squared edge will be at "
"exactly the offset distance from the join vertex.")
.value(
"Round", CrossSection::JoinType::Square,
"Rounding is applied to all joins that have convex external angles, "
"and it maintains the exact offset distance from the join vertex.")
.value(
"Miter", CrossSection::JoinType::Miter,
"There's a necessary limit to mitered joins (to avoid narrow angled "
"joins producing excessively long and narrow "
"[spikes](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/"
"Classes/ClipperOffset/Properties/MiterLimit.htm)). So where mitered "
"joins would exceed a given maximum miter distance (relative to the "
"offset distance), these are 'squared' instead.");
py::class_<CrossSection>(
m, "CrossSection",
"Two-dimensional cross sections guaranteed to be without "
"self-intersections, or overlaps between polygons (from construction "
"onwards). This class makes use of the "
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
"for polygon clipping (boolean) and offsetting operations.")
.def(py::init<>())
.def(py::init([](std::vector<std::vector<Float2>> &polygons,
CrossSection::FillRule fillrule) {
std::vector<SimplePolygon> simplePolygons(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
simplePolygons[i] = std::vector<glm::vec2>(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
simplePolygons[i][j] = {std::get<0>(polygons[i][j]),
std::get<1>(polygons[i][j])};
}
}
return CrossSection(simplePolygons, fillrule);
}),
py::arg("polygons"),
py::arg("fillrule") = CrossSection::FillRule::Positive,
"Create a 2d cross-section from a set of contours (complex "
"polygons). A boolean union operation (with Positive filling rule "
"by default) performed to combine overlapping polygons and ensure "
"the resulting CrossSection is free of intersections."
"\n\n"
":param contours: A set of closed paths describing zero or more "
"complex polygons.\n"
":param fillrule: The filling rule used to interpret polygon "
"sub-regions in contours.")
.def("area", &CrossSection::Area,
"Return the total area covered by complex polygons making up the "
"CrossSection.")
.def("num_vert", &CrossSection::NumVert,
"Return the number of vertices in the CrossSection.")
.def("num_contour", &CrossSection::NumContour,
"Return the number of contours (both outer and inner paths) in the "
"CrossSection.")
.def("is_empty", &CrossSection::IsEmpty,
"Does the CrossSection contain any contours?")
.def(
"translate",
[](CrossSection self, Float2 v) {
return self.Translate({std::get<0>(v), std::get<1>(v)});
},
"Move this CrossSection in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param v: The vector to add to every vertex.")
.def("rotate", &CrossSection::Rotate,
"Applies a (Z-axis) rotation to the CrossSection, in degrees. This "
"operation can be chained. Transforms are combined and applied "
"lazily."
"\n\n"
":param degrees: degrees about the Z-axis to rotate.")
.def(
"scale",
[](CrossSection self, Float2 s) {
return self.Scale({std::get<0>(s), std::get<1>(s)});
},
"Scale this CrossSection in space. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param v: The vector to multiply every vertex by per component.")
.def(
"mirror",
[](CrossSection self, Float2 ax) {
return self.Mirror({std::get<0>(ax), std::get<1>(ax)});
},
"Mirror this CrossSection over the arbitrary axis described by the "
"unit form of the given vector. If the length of the vector is zero, "
"an empty CrossSection is returned. This operation can be chained. "
"Transforms are combined and applied lazily."
"\n\n"
":param ax: the axis to be mirrored over")
.def(
"transform",
[](CrossSection self, py::array_t<float> &mat) {
auto mat_view = mat.unchecked<2>();
if (mat.ndim() != 2 || mat.shape(0) != 2 || mat.shape(1) != 3)
throw std::runtime_error("Invalid matrix shape, expected (2, 3)");
glm::mat3x2 mat_glm;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
mat_glm[j][i] = mat_view(i, j);
}
}
return self.Transform(mat_glm);
},
"Transform this CrossSection in space. The first two columns form a "
"2x2 matrix transform and the last is a translation vector. This "
"operation can be chained. Transforms are combined and applied "
"lazily."
"\n\n"
":param m: The affine transform matrix to apply to all the vertices.")
.def(
"warp",
[](CrossSection self, const std::function<Float2(Float2)> &f) {
return self.Warp([&f](glm::vec2 &v) {
Float2 fv = f(std::make_tuple(v.x, v.y));
v.x = std::get<0>(fv);
v.y = std::get<1>(fv);
});
},
py::arg("f"),
"Move the vertices of this CrossSection (creating a new one) "
"according to any arbitrary input function, followed by a union "
"operation (with a Positive fill rule) that ensures any introduced "
"intersections are not included in the result."
"\n\n"
":param warpFunc: A function that modifies a given vertex position.")
.def("simplify", &CrossSection::Simplify,
"Remove vertices from the contours in this CrossSection that are "
"less than the specified distance epsilon from an imaginary line "
"that passes through its two adjacent vertices. Near duplicate "
"vertices and collinear points will be removed at lower epsilons, "
"with elimination of line segments becoming increasingly aggressive "
"with larger epsilons."
"\n\n"
"It is recommended to apply this function following Offset, in "
"order to clean up any spurious tiny line segments introduced that "
"do not improve quality in any meaningful way. This is particularly "
"important if further offseting operations are to be performed, "
"which would compound the issue.")
.def("offset", &CrossSection::Offset, py::arg("delta"),
py::arg("join_type"), py::arg("miter_limit") = 2.0,
py::arg("arc_tolerance") = 0.0,
"Inflate the contours in CrossSection by the specified delta, "
"handling corners according to the given JoinType."
"\n\n"
":param delta: Positive deltas will cause the expansion of "
"outlining contours to expand, and retraction of inner (hole) "
"contours. Negative deltas will have the opposite effect.\n"
":param jt: The join type specifying the treatment of contour joins "
"(corners).\n"
":param miter_limit: The maximum distance in multiples of delta "
"that vertices can be offset from their original positions with "
"before squaring is applied, <B>when the join type is Miter</B> "
"(default is 2, which is the minimum allowed). See the [Clipper2 "
"MiterLimit](http://www.angusj.com/clipper2/Docs/Units/"
"Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm) "
"page for a visual example.\n"
":param circularSegments: Number of segments per 360 degrees of "
"<B>JoinType::Round</B> corners (roughly, the number of vertices "
"that will be added to each contour). Default is calculated by the "
"static Quality defaults according to the radius.")
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def("decompose", &CrossSection::Decompose,
"This operation returns a vector of CrossSections that are "
"topologically disconnected, each containing one outline contour "
"with zero or more holes.")
.def(
"to_polygons",
[](CrossSection self) {
const Polygons &data = self.ToPolygons();
py::list polygon_list;
for (int i = 0; i < data.size(); ++i) {
py::list polygon;
for (int j = 0; j < data[i].size(); ++j) {
auto f = data[i][j];
py::tuple vertex = py::make_tuple(f[0], f[1]);
polygon.append(vertex);
}
polygon_list.append(polygon);
}
return polygon_list;
},
"Returns the vertices of the cross-section's polygons as a "
"List[List[Tuple[float, float]]].")
.def(
"extrude",
[](CrossSection self, float height, int nDivisions = 0,
float twistDegrees = 0.0f,
Float2 scaleTop = std::make_tuple(1.0f, 1.0f)) {
glm::vec2 scaleTopVec(std::get<0>(scaleTop), std::get<1>(scaleTop));
return Manifold::Extrude(self, height, nDivisions, twistDegrees,
scaleTopVec);
},
py::arg("height"), py::arg("n_divisions") = 0,
py::arg("twist_degrees") = 0.0f,
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
"Constructs a manifold from the set of polygons by extruding them "
"along the Z-axis.\n"
"\n"
":param height: Z-extent of extrusion.\n"
":param nDivisions: Number of extra copies of the crossSection to "
"insert into the shape vertically; especially useful in combination "
"with twistDegrees to avoid interpolation artifacts. Default is "
"none.\n"
":param twistDegrees: Amount to twist the top crossSection relative "
"to the bottom, interpolated linearly for the divisions in between.\n"
":param scaleTop: Amount to scale the top (independently in X and "
"Y). If the scale is (0, 0), a pure cone is formed with only a "
"single vertex at the top. Default (1, 1).")
.def(
"revolve",
[](CrossSection self, int circularSegments = 0) {
return Manifold::Revolve(self, circularSegments);
},
py::arg("circular_segments") = 0,
"Constructs a manifold from the set of polygons by revolving this "
"cross-section around its Y-axis and then setting this as the Z-axis "
"of the resulting manifold. If the polygons cross the Y-axis, only "
"the part on the positive X side is used. Geometrically valid input "
"will result in geometrically valid output.\n"
"\n"
":param circularSegments: Number of segments along its diameter. "
"Default is calculated by the static Defaults.")
.def_static(
"square",
[](Float2 dims, bool center) {
return CrossSection::Square({std::get<0>(dims), std::get<1>(dims)},
center);
},
py::arg("dims"), py::arg("center") = false,
"Constructs a square with the given XY dimensions. By default it is "
"positioned in the first quadrant, touching the origin. If any "
"dimensions in size are negative, or if all are zero, an empty "
"Manifold will be returned."
"\n\n"
":param size: The X, and Y dimensions of the square.\n"
":param center: Set to true to shift the center to the origin.")
.def_static(
"circle",
[](float radius, int circularSegments) {
return CrossSection::Circle(radius, circularSegments);
},
py::arg("radius"), py::arg("circularSegments") = 0,
"Constructs a circle of a given radius."
"\n\n"
":param radius: Radius of the circle. Must be positive.\n"
":param circularSegments: Number of segments along its diameter. "
"Default is calculated by the static Quality defaults according to "
"the radius.");
}
+1
View File
@@ -0,0 +1 @@
add_subdirectory(pybind11)
+46
View File
@@ -0,0 +1,46 @@
# Copyright 2022 The Manifold Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
project(wasm)
add_executable(manifoldjs bindings.cpp)
set_source_files_properties(bindings.cpp PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS})
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
target_compile_features(manifoldjs PUBLIC cxx_std_17)
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)
# ensure that interface files are copied over when modified
add_custom_target(js_deps ALL
DEPENDS manifoldjs
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
add_custom_command(
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
+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.
+31
View File
@@ -0,0 +1,31 @@
[![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 [discussion](https://github.com/elalish/manifold/discussions/256#discussioncomment-3944287).
## Examples
Please see our usage [examples](https://github.com/elalish/manifold/tree/master/bindings/wasm/examples) to see how to interface this library with `three.js`, `<model-viewer>`, and `glTF`. Of particular note are the included libraries for lossless roundtrip of manifold meshes through glTF files, via a new extension: [EXT_manifold](https://github.com/KhronosGroup/glTF/pull/2286).
## About the author
This library was started by [Emmett Lalish](https://elalish.blogspot.com/). I am currently a Google employee and this is my 20% project, not an official Google project. At my day job I'm the maintainer of [\<model-viewer\>](https://modelviewer.dev/). I was the first employee at a 3D video startup, [Omnivor](https://www.omnivor.io/), and before that I worked on 3D printing at Microsoft, including [3D Builder](https://www.microsoft.com/en-us/p/3d-builder/9wzdncrfj3t6?activetab=pivot%3Aoverviewtab). Originally an aerospace engineer, I started at a small DARPA contractor doing seedling projects, one of which became [Sea Hunter](https://en.wikipedia.org/wiki/Sea_Hunter). I earned my doctorate from the University of Washington in control theory and published some [papers](https://www.researchgate.net/scientific-contributions/75011026_Emmett_Lalish).
+185
View File
@@ -0,0 +1,185 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <manifold.h>
#include <polygon.h>
#include <sdf.h>
#include <vector>
#include "cross_section.h"
#include "helpers.cpp"
using namespace emscripten;
using namespace manifold;
EMSCRIPTEN_BINDINGS(whatever) {
value_object<glm::vec2>("vec2")
.field("x", &glm::vec2::x)
.field("y", &glm::vec2::y);
value_object<glm::ivec3>("ivec3")
.field("0", &glm::ivec3::x)
.field("1", &glm::ivec3::y)
.field("2", &glm::ivec3::z);
value_object<glm::vec3>("vec3")
.field("x", &glm::vec3::x)
.field("y", &glm::vec3::y)
.field("z", &glm::vec3::z);
value_object<glm::vec4>("vec4")
.field("x", &glm::vec4::x)
.field("y", &glm::vec4::y)
.field("z", &glm::vec4::z)
.field("w", &glm::vec4::w);
enum_<Manifold::Error>("status")
.value("NoError", Manifold::Error::NoError)
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
.value("NotManifold", Manifold::Error::NotManifold)
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
.value("MissingPositionProperties",
Manifold::Error::MissingPositionProperties)
.value("MergeVectorsDifferentLengths",
Manifold::Error::MergeVectorsDifferentLengths)
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
enum_<CrossSection::FillRule>("fillrule")
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
.value("NonZero", CrossSection::FillRule::NonZero)
.value("Positive", CrossSection::FillRule::Positive)
.value("Negative", CrossSection::FillRule::Negative);
enum_<CrossSection::JoinType>("jointype")
.value("Square", CrossSection::JoinType::Square)
.value("Round", CrossSection::JoinType::Round)
.value("Miter", CrossSection::JoinType::Miter);
value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
value_object<Smoothness>("smoothness")
.field("halfedge", &Smoothness::halfedge)
.field("smoothness", &Smoothness::smoothness);
value_object<Properties>("properties")
.field("surfaceArea", &Properties::surfaceArea)
.field("volume", &Properties::volume);
register_vector<glm::ivec3>("Vector_ivec3");
register_vector<glm::vec3>("Vector_vec3");
register_vector<glm::vec2>("Vector_vec2");
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
register_vector<float>("Vector_f32");
register_vector<CrossSection>("Vector_crossSection");
register_vector<Manifold>("Vector_manifold");
register_vector<Smoothness>("Vector_smoothness");
register_vector<glm::vec4>("Vector_vec4");
class_<CrossSection>("CrossSection")
.constructor(&cross_js::OfPolygons)
.function("_add", &cross_js::Union)
.function("_subtract", &cross_js::Difference)
.function("_intersect", &cross_js::Intersection)
.function("_Warp", &cross_js::Warp)
.function("transform", &cross_js::Transform)
.function("_Translate", &CrossSection::Translate)
.function("rotate", &CrossSection::Rotate)
.function("_Scale", &CrossSection::Scale)
.function("_Mirror", &CrossSection::Mirror)
.function("_Decompose", &CrossSection::Decompose)
.function("isEmpty", &CrossSection::IsEmpty)
.function("area", &CrossSection::Area)
.function("numVert", &CrossSection::NumVert)
.function("numContour", &CrossSection::NumContour)
.function("_Bounds", &CrossSection::Bounds)
.function("simplify", &CrossSection::Simplify)
.function("_Offset", &cross_js::Offset)
.function("_ToPolygons", &CrossSection::ToPolygons)
.function("hull",
select_overload<CrossSection() const>(&CrossSection::Hull));
// CrossSection Static Methods
function("_Square", &CrossSection::Square);
function("_Circle", &CrossSection::Circle);
function("_crossSectionCompose", &CrossSection::Compose);
function("_crossSectionUnionN", &cross_js::UnionN);
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
class_<Manifold>("Manifold")
.constructor(&man_js::FromMeshJS)
.function("add", &man_js::Union)
.function("subtract", &man_js::Difference)
.function("intersect", &man_js::Intersection)
.function("_Split", &man_js::Split)
.function("_SplitByPlane", &man_js::SplitByPlane)
.function("_TrimByPlane", &Manifold::TrimByPlane)
.function("_GetMeshJS", &js::GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &man_js::Warp)
.function("_SetProperties", &man_js::SetProperties)
.function("transform", &man_js::Transform)
.function("_Translate", &Manifold::Translate)
.function("_Rotate", &Manifold::Rotate)
.function("_Scale", &Manifold::Scale)
.function("_Mirror", &Manifold::Mirror)
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
&Manifold::Decompose))
.function("isEmpty", &Manifold::IsEmpty)
.function("status", &Manifold::Status)
.function("numVert", &Manifold::NumVert)
.function("numEdge", &Manifold::NumEdge)
.function("numTri", &Manifold::NumTri)
.function("numProp", &Manifold::NumProp)
.function("numPropVert", &Manifold::NumPropVert)
.function("_boundingBox", &Manifold::BoundingBox)
.function("precision", &Manifold::Precision)
.function("genus", &Manifold::Genus)
.function("getProperties", &Manifold::GetProperties)
.function("calculateCurvature", &Manifold::CalculateCurvature)
.function("originalID", &Manifold::OriginalID)
.function("asOriginal", &Manifold::AsOriginal);
// Manifold Static Methods
function("_Cube", &Manifold::Cube);
function("_Cylinder", &Manifold::Cylinder);
function("_Sphere", &Manifold::Sphere);
function("_Tetrahedron", &Manifold::Tetrahedron);
function("_Smooth", &js::Smooth);
function("_Extrude", &Manifold::Extrude);
function("_Triangulate", &Triangulate);
function("_Revolve", &Manifold::Revolve);
function("_LevelSet", &man_js::LevelSet);
function("_Merge", &js::Merge);
function("_ReserveIDs", &Manifold::ReserveIDs);
function("_manifoldCompose", &Manifold::Compose);
function("_manifoldUnionN", &man_js::UnionN);
function("_manifoldDifferenceN", &man_js::DifferenceN);
function("_manifoldIntersectionN", &man_js::IntersectionN);
// Quality Globals
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
function("setCircularSegments", &Quality::SetCircularSegments);
function("getCircularSegments", &Quality::GetCircularSegments);
}
+618
View File
@@ -0,0 +1,618 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var _ManifoldInitialized = false;
Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
// conversion utilities
function toVec(vec, list, f = x => x) {
if (list) {
for (let x of list) {
vec.push_back(f(x));
}
}
return vec;
}
function fromVec(vec, f = x => x) {
const result = [];
const size = vec.size();
for (let i = 0; i < size; i++) result.push(f(vec.get(i)));
return result;
}
function vec2polygons(vec, f = x => x) {
const result = [];
const nPoly = vec.size();
for (let i = 0; i < nPoly; i++) {
const v = vec.get(i);
const nPts = v.size();
const poly = [];
for (let j = 0; j < nPts; j++) {
poly.push(f(v.get(j)));
}
result.push(poly);
}
return result;
}
function polygons2vec(polygons) {
if (polygons[0].length < 3) {
polygons = [polygons];
}
return toVec(
new Module.Vector2_vec2(), polygons,
poly => toVec(new Module.Vector_vec2(), poly, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
}));
}
function disposePolygons(polygonsVec) {
for (let i = 0; i < polygonsVec.size(); i++) polygonsVec.get(i).delete();
polygonsVec.delete();
}
function vararg2vec2(vec) {
if (vec[0] instanceof Array) return {x: vec[0][0], y: vec[0][1]};
if (typeof (vec[0]) == 'number')
// default to 0
return {x: vec[0] || 0, y: vec[1] || 0};
return vec[0];
}
function vararg2vec3(vec) {
if (vec[0] instanceof Array)
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
if (typeof (vec[0]) == 'number')
// default to 0
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
return vec[0];
}
function fillRuleToInt(fillRule) {
return fillRule == 'EvenOdd' ? 0 :
fillRule == 'NonZero' ? 1 :
fillRule == 'Negative' ? 3 :
/* Positive */ 2;
}
function joinTypeToInt(joinType) {
return joinType == 'Round' ? 1 : joinType == 'Miter' ? 2 : /* Square */ 0;
}
// CrossSection methods
const CrossSectionCtor = Module.CrossSection;
function cross(polygons, fillRule = 'Positive') {
if (polygons instanceof CrossSectionCtor) {
return polygons;
} else {
const polygonsVec = polygons2vec(polygons);
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
disposePolygons(polygonsVec);
return cs;
}
};
Module.CrossSection.prototype.translate = function(...vec) {
return this._Translate(vararg2vec2(vec));
};
Module.CrossSection.prototype.scale = function(vec) {
// if only one factor provided, scale both x and y with it
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec});
}
return this._Scale(vararg2vec2([vec]));
};
Module.CrossSection.prototype.mirror = function(vec) {
return this._Mirror(vararg2vec2([vec]));
};
Module.CrossSection.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec2Ptr) {
const x = getValue(vec2Ptr, 'float');
const y = getValue(vec2Ptr + 4, 'float');
const vert = [x, y];
func(vert);
setValue(vec2Ptr, vert[0], 'float');
setValue(vec2Ptr + 4, vert[1], 'float');
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
Module.CrossSection.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.CrossSection.prototype.bounds = function() {
const result = this._Bounds();
return {
min: ['x', 'y'].map(f => result.min[f]),
max: ['x', 'y'].map(f => result.max[f]),
};
};
Module.CrossSection.prototype.offset = function(
delta, joinType = 'Square', miterLimit = 2.0, circularSegments = 0) {
return this._Offset(
delta, joinTypeToInt(joinType), miterLimit, circularSegments);
};
Module.CrossSection.prototype.extrude = function(
height, nDivisions = 0, twistDegrees = 0.0, scaleTop = [1.0, 1.0],
center = false) {
scaleTop = vararg2vec2([scaleTop]);
const man =
Module._Extrude(this, height, nDivisions, twistDegrees, scaleTop);
return (center ? man.translate([0., 0., -height / 2.]) : man);
};
Module.CrossSection.prototype.revolve = function(
circularSegments = 0, revolveDegrees = 360.0) {
return Module._Revolve(this, circularSegments, revolveDegrees);
};
Module.CrossSection.prototype.add = function(other) {
return this._add(cross(other));
};
Module.CrossSection.prototype.subtract = function(other) {
return this._subtract(cross(other));
};
Module.CrossSection.prototype.intersect = function(other) {
return this._intersect(cross(other));
};
Module.CrossSection.prototype.toPolygons = function() {
const vec = this._ToPolygons();
const result = vec2polygons(vec);
vec.delete();
return result;
};
// Manifold methods
Module.Manifold.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
const vert = [x, y, z];
func(vert);
setValue(vec3Ptr, vert[0], 'float');
setValue(vec3Ptr + 4, vert[1], 'float');
setValue(vec3Ptr + 8, vert[2], 'float');
}, 'vi');
const out = this._Warp(wasmFuncPtr);
removeFunction(wasmFuncPtr);
const status = out.status();
if (status.value !== 0) {
throw new Module.ManifoldError(status.value);
}
return out;
};
Module.Manifold.prototype.setProperties = function(numProp, func) {
const oldNumProp = this.numProp();
const wasmFuncPtr = addFunction(function(newPtr, vec3Ptr, oldPtr) {
const newProp = [];
for (let i = 0; i < numProp; ++i) {
newProp[i] = getValue(newPtr + 4 * i, 'float');
}
const pos = [];
for (let i = 0; i < 3; ++i) {
pos[i] = getValue(vec3Ptr + 4 * i, 'float');
}
const oldProp = [];
for (let i = 0; i < oldNumProp; ++i) {
oldProp[i] = getValue(oldPtr + 4 * i, 'float');
}
func(newProp, pos, oldProp);
for (let i = 0; i < numProp; ++i) {
setValue(newPtr + 4 * i, newProp[i], 'float');
}
}, 'viii');
const out = this._SetProperties(numProp, wasmFuncPtr);
removeFunction(wasmFuncPtr);
return out;
};
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec3(vec));
};
Module.Manifold.prototype.rotate = function(vec) {
return this._Rotate(...vec);
};
Module.Manifold.prototype.scale = function(vec) {
// if only one factor provided, scale all three dimensions (xyz) with it
if (typeof vec == 'number') {
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec3([vec]));
};
Module.Manifold.prototype.mirror = function(vec) {
return this._Mirror(vararg2vec3([vec]));
};
Module.Manifold.prototype.trimByPlane = function(normal, offset = 0.) {
return this._TrimByPlane(vararg2vec3([normal]), offset);
};
Module.Manifold.prototype.split = function(manifold) {
const vec = this._split(manifold);
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.splitByPlane = function(normal, offset = 0.) {
const vec = this._splitByPlane(vararg2vec3([normal]), offset);
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.boundingBox = function() {
const result = this._boundingBox();
return {
min: ['x', 'y', 'z'].map(f => result.min[f]),
max: ['x', 'y', 'z'].map(f => result.max[f]),
};
};
class Mesh {
constructor({
numProp = 3,
triVerts = new Uint32Array(),
vertProperties = new Float32Array(),
mergeFromVert,
mergeToVert,
runIndex,
runOriginalID,
faceID,
halfedgeTangent,
runTransform
} = {}) {
this.numProp = numProp;
this.triVerts = triVerts;
this.vertProperties = vertProperties;
this.mergeFromVert = mergeFromVert;
this.mergeToVert = mergeToVert;
this.runIndex = runIndex;
this.runOriginalID = runOriginalID;
this.faceID = faceID;
this.halfedgeTangent = halfedgeTangent;
this.runTransform = runTransform;
}
get numTri() {
return this.triVerts.length / 3;
}
get numVert() {
return this.vertProperties.length / this.numProp;
}
get numRun() {
return this.runOriginalID.length;
}
merge() {
const {changed, mesh} = Module._Merge(this);
Object.assign(this, {...mesh});
return changed;
}
verts(tri) {
return this.triVerts.subarray(3 * tri, 3 * (tri + 1));
}
position(vert) {
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
}
extras(vert) {
return this.vertProperties.subarray(
numProp * vert + 3, numProp * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
transform(run) {
const mat4 = new Array(16);
for (const col of [0, 1, 2, 3]) {
for (const row of [0, 1, 2]) {
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
}
}
mat4[15] = 1;
return mat4;
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
if (normalIdx instanceof Array)
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
return new Mesh(this._GetMeshJS(normalIdx));
};
Module.ManifoldError = function ManifoldError(code, ...args) {
let message = 'Unknown error';
switch (code) {
case Module.status.NonFiniteVertex.value:
message = 'Non-finite vertex';
break;
case Module.status.NotManifold.value:
message = 'Not manifold';
break;
case Module.status.VertexOutOfBounds.value:
message = 'Vertex index out of bounds';
break;
case Module.status.PropertiesWrongLength.value:
message = 'Properties have wrong length';
break;
case Module.status.MissingPositionProperties.value:
message = 'Less than three properties';
break;
case Module.status.MergeVectorsDifferentLengths.value:
message = 'Merge vectors have different lengths';
break;
case Module.status.MergeIndexOutOfBounds.value:
message = 'Merge index out of bounds';
break;
case Module.status.TransformWrongLength.value:
message = 'Transform vector has wrong length';
break;
case Module.status.RunIndexWrongLength.value:
message = 'Run index vector has wrong length';
break;
case Module.status.FaceIDWrongLength.value:
message = 'Face ID vector has wrong length';
case Module.status.InvalidConstruction.value:
message = 'Manifold constructed with invalid parameters';
}
const base = Error.apply(this, [message, ...args]);
base.name = this.name = 'ManifoldError';
this.message = base.message;
this.stack = base.stack;
this.code = code;
};
Module.ManifoldError.prototype = Object.create(Error.prototype, {
constructor:
{value: Module.ManifoldError, writable: true, configurable: true}
});
// CrossSection Constructors
Module.CrossSection = function(polygons, fillRule = 'Positive') {
const polygonsVec = polygons2vec(polygons);
const cs = new CrossSectionCtor(polygonsVec, fillRuleToInt(fillRule));
disposePolygons(polygonsVec);
return cs;
};
Module.CrossSection.ofPolygons = function(polygons, fillRule = 'Positive') {
return new Module.CrossSection(polygons, fillRule);
};
Module.CrossSection.square = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0]};
else
size = vararg2vec2(args);
const center = args[1] || false;
return Module._Square(size, center);
};
Module.CrossSection.circle = function(radius, circularSegments = 0) {
return Module._Circle(radius, circularSegments);
};
// allows args to be either CrossSection or polygons (constructed with
// Positive fill)
function crossSectionBatchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_crossSection();
for (const cs of args) v.push_back(cross(cs));
const result = Module['_crossSection' + name](v);
v.delete();
return result;
};
}
Module.CrossSection.compose = crossSectionBatchbool('Compose');
Module.CrossSection.union = crossSectionBatchbool('UnionN');
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
// not work as desired unless we refer back to the original like this
Object.defineProperty(Module.CrossSection, Symbol.hasInstance, {
get: () => (t) => {
return (t instanceof CrossSectionCtor);
}
});
// Manifold Constructors
const ManifoldCtor = Module.Manifold;
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
const status = manifold.status();
if (status.value !== 0) {
throw new Module.ManifoldError(status.value);
}
return manifold;
};
Module.Manifold.ofMesh = function(mesh) {
return new Module.Manifold(mesh);
};
Module.Manifold.tetrahedron = function() {
return Module._Tetrahedron();
};
Module.Manifold.cube = function(...args) {
let size = undefined;
if (args.length == 0)
size = {x: 1, y: 1, z: 1};
else if (typeof args[0] == 'number')
size = {x: args[0], y: args[0], z: args[0]};
else
size = vararg2vec3(args);
const center = args[1] || false;
return Module._Cube(size, center);
};
Module.Manifold.cylinder = function(
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
return Module._Cylinder(
height, radiusLow, radiusHigh, circularSegments, center);
};
Module.Manifold.sphere = function(radius, circularSegments = 0) {
return Module._Sphere(radius, circularSegments);
};
Module.Manifold.smooth = function(mesh, sharpenedEdges = []) {
const sharp = new Module.Vector_smoothness();
toVec(sharp, sharpenedEdges);
const result = Module._Smooth(mesh, sharp);
sharp.delete();
return result;
};
Module.Manifold.extrude = function(
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0], center = false) {
const cs = (polygons instanceof CrossSectionCtor) ?
polygons :
Module.CrossSection(polygons, 'Positive');
return cs.extrude(height, nDivisions, twistDegrees, scaleTop, center);
};
Module.Manifold.revolve = function(
polygons, circularSegments = 0, revolveDegrees = 360.0) {
const cs = (polygons instanceof CrossSectionCtor) ?
polygons :
Module.CrossSection(polygons, 'Positive');
return cs.revolve(circularSegments, revolveDegrees);
};
Module.Manifold.reserveIDs = function(n) {
return Module._ReserveIDs(n);
};
Module.Manifold.compose = function(manifolds) {
const vec = new Module.Vector_manifold();
toVec(vec, manifolds);
const result = Module._manifoldCompose(vec);
vec.delete();
return result;
};
function manifoldBatchbool(name) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_manifold();
for (const m of args) v.push_back(m);
const result = Module['_manifold' + name + 'N'](v);
v.delete();
return result;
};
}
Module.Manifold.union = manifoldBatchbool('Union');
Module.Manifold.difference = manifoldBatchbool('Difference');
Module.Manifold.intersection = manifoldBatchbool('Intersection');
Module.Manifold.levelSet = function(sdf, bounds, edgeLength, level = 0) {
const bounds2 = {
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
};
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
const vert = [x, y, z];
return sdf(vert);
}, 'fi');
const out = Module._LevelSet(wasmFuncPtr, bounds2, edgeLength, level);
removeFunction(wasmFuncPtr);
return out;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
// not work as desired unless we refer back to the original like this
Object.defineProperty(Module.Manifold, Symbol.hasInstance, {
get: () => (t) => {
return (t instanceof ManifoldCtor);
}
});
// Top-level functions
Module.triangulate = function(polygons, precision = -1) {
const polygonsVec = polygons2vec(polygons);
const result = fromVec(
Module._Triangulate(polygonsVec, precision), (x) => [x[0], x[1], x[2]]);
disposePolygons(polygonsVec);
return result;
};
};
+1
View File
@@ -0,0 +1 @@
manifold*
+45
View File
@@ -0,0 +1,45 @@
# WASM/JS Examples
This is the home of our editor, [ManifoldCAD.org](https://manifoldcad.org/), as well as our other public examples of integrations with `three.js`, `<model-viewer>`, and `glTF`. Included are `manifold-gltf.js` and `gltf-io.js`, which are intended to be fairly general-purpose libraries for interfacing between manifolds and glTF. We should probably make them into their own npm package at some point.
## Local development
First, follow the directions in the root README to get your C++ build environment set up and working for WASM. From this directory (`bindings/wasm/examples/`) you can test the JS bindings by running:
```
npm install
npm test
```
To develop the manifoldCAD.org editor as well as our other example pages, run
```
npm run dev
```
which will serve the pages, watch for changes, and automatically rebuild and refresh. This build step doesn't do TS type-checking, so to verify everything is correct (beyond VSCode's TS linting), run
```
npm run build
```
See `package.json` for other useful scripts.
Note that the `emcmake` command automatically copies your WASM build into `built/`, (here, not just under the `buildWASM` directory) which is then packaged by Vite into `dist/assets/`.
To debug the WASM build directly in Chrome dev tools, simply build in debug mode:
```
emcmake cmake -DCMAKE_BUILD_TYPE=Debug .. && emmake make
```
and install the [DWARF](goo.gle/wasm-debugging-extension) Chrome extension.
When testing [ManifoldCAD.org](https://manifoldcad.org/) (either locally or the
deployed version) note that it uses a service worker for faster loading. This
means you need to open the page twice to see updates (the first time loads the
old version and caches the new one, the second time loads the new version from
cache). To see changes on each reload, open Chrome dev tools, go to the
Application tab and check "update on reload".
### Note for firefox users
To use the manifoldCAD.org editor (`npm run dev`), you'll likely have to set
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
discussion of the
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
of this repository.
+224
View File
@@ -0,0 +1,224 @@
/*
Copyright 2022 The Manifold Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
font-family: 'Rubik', sans-serif;
font-size: 24px;
line-height: 24px;
font-weight: 800;
-webkit-font-smoothing: antialiased;
}
button {
display: block;
padding: 0 1em;
cursor: pointer;
height: 36px;
border: none;
border-radius: 18px;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 800;
text-align: left;
-webkit-font-smoothing: antialiased;
}
a:link,
a:visited {
color: black;
text-decoration: underline;
}
a:hover,
a:active {
color: black;
text-decoration: none;
}
button:disabled {
color: black;
background-color: #eee !important;
cursor: auto;
}
button:hover {
filter: brightness(0.9);
}
input {
border: none;
height: 36px;
background-color: transparent;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 800;
text-align: left;
-webkit-font-smoothing: antialiased;
}
input:focus {
outline: none;
}
p {
margin: 1em;
text-align: left;
}
model-viewer {
position: relative;
flex: 3;
width: 100%;
background-color: #ddd;
}
#console {
flex: 1;
min-height: 0;
max-height: 25vh;
overflow-y: scroll;
background-color: #eee;
white-space: pre-wrap;
font-size: 14px;
font-weight: 100;
line-height: 20px;
padding: 8px;
}
.page {
height: 100vh;
display: flex;
flex-direction: column;
}
.container {
display: flex;
}
.col {
position: relative;
width: 50vw;
}
.center {
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
}
.header .margin {
font-size: 16px;
margin: 1em;
}
#file {
width: 250px;
}
.dropdown {
display: none;
position: absolute;
z-index: 1;
width: 250px;
max-height: 80vh;
overflow-y: auto;
}
.show {
display: block;
}
.uparrow {
display: inline-block;
position: relative;
border: solid black;
border-width: 0 3px 3px 0;
padding: 3px;
transform: rotate(-135deg);
}
.down {
transform: rotate(45deg);
}
.item {
width: 100%;
position: relative;
}
.icon {
display: inline-block;
background-color: transparent;
position: absolute;
width: 36px;
top: 0px;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.cancel {
background-image: url(/icons/close.png);
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
}
.edit {
background-image: url(/icons/pencil.png);
right: 30px;
}
.trash {
background-image: url(/icons/trash.png);
right: 0px;
}
.icon:hover {
background-color: rgba(0, 0, 0, 0.2);
}
.blue {
background-color: rgb(148, 221, 255);
}
.green {
background-color: rgb(131, 255, 131);
}
.red {
background-color: rgb(255, 177, 177);
}
@media only screen and (max-width: 600px) {
.container {
flex-direction: column;
width: 100%;
}
.col {
width: 100%;
}
}
+420
View File
@@ -0,0 +1,420 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import ManifoldWorker from './worker?worker';
// Loaded globally by examples.js
const exampleFunctions = self.examples.functionBodies;
if (navigator.serviceWorker) {
navigator.serviceWorker.register(
'/service-worker.js', {scope: './index.html'});
}
let editor = undefined;
// File UI ------------------------------------------------------------
const fileButton = document.querySelector('#file');
const currentElement = document.querySelector('#current');
const arrow = document.querySelector('.uparrow');
const dropdown = document.querySelector('.dropdown');
const hideDropdown = function() {
dropdown.classList.remove('show');
arrow.classList.remove('down');
};
const toggleDropdown = function(event) {
event.stopPropagation();
dropdown.classList.toggle('show');
arrow.classList.toggle('down');
};
fileButton.onclick = toggleDropdown;
document.body.onclick = hideDropdown;
const prefix = 'ManifoldCAD';
function getScript(name) {
return window.localStorage.getItem(prefix + name);
}
function setScript(name, code) {
window.localStorage.setItem(prefix + name, code);
}
function removeScript(name) {
window.localStorage.removeItem(prefix + name);
}
function nthKey(n) {
if (n >= window.localStorage.length) return;
const key = window.localStorage.key(n);
if (key.startsWith(prefix)) {
return key.slice(prefix.length);
}
}
function saveCurrent() {
if (editor) {
const currentName = currentElement.textContent;
if (!exampleFunctions.get(currentName)) {
setScript(currentName, editor.getValue());
}
}
};
window.onpagehide = saveCurrent;
window.beforeunload = saveCurrent;
let switching = false;
let isExample = true;
function switchTo(scriptName) {
if (editor) {
switching = true;
currentElement.textContent = scriptName;
setScript('currentName', scriptName);
const code =
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
isExample = exampleFunctions.get(scriptName) != null;
editor.setValue(code);
}
}
function createDropdownItem(name) {
const container = document.createElement('div');
container.classList.add('item');
const button = document.createElement('button');
container.appendChild(button);
button.type = 'button';
button.classList.add('blue', 'item');
const label = document.createElement('span');
button.appendChild(label);
label.textContent = name;
button.onclick = function() {
saveCurrent();
switchTo(label.textContent);
};
// Stop text input spaces from triggering the button
button.onkeyup = function(event) {
event.preventDefault();
};
return button;
}
function addIcon(button) {
const icon = document.createElement('button');
icon.classList.add('icon');
button.parentElement.appendChild(icon);
return icon;
}
function uniqueName(name) {
let num = 1;
let newName = name;
while (getScript(newName) != null || exampleFunctions.get(newName) != null) {
newName = name + ' ' + num++;
}
return newName;
}
function addEdit(button) {
const label = button.firstChild;
const edit = addIcon(button);
edit.classList.add('edit');
edit.onclick = function(event) {
event.stopPropagation();
const oldName = label.textContent;
const code = getScript(oldName);
const form = document.createElement('form');
const inputElement = document.createElement('input');
inputElement.value = oldName;
label.textContent = '';
button.appendChild(form);
form.appendChild(inputElement);
inputElement.focus();
inputElement.setSelectionRange(0, oldName.length);
function rename() {
const input = inputElement.value;
inputElement.blur();
if (!input) return;
const newName = uniqueName(input);
label.textContent = newName;
if (currentElement.textContent == oldName) {
currentElement.textContent = newName;
}
removeScript(oldName);
setScript(newName, code);
}
form.onsubmit = rename;
inputElement.onclick = function(event) {
event.stopPropagation();
};
inputElement.onblur = function() {
button.removeChild(form);
label.textContent = oldName;
};
};
const trash = addIcon(button);
trash.classList.add('trash');
let lastClick = 0;
trash.onclick = function(event) {
event.stopPropagation();
if (button.classList.contains('blue')) {
lastClick = performance.now();
button.classList.remove('blue');
button.classList.add('red');
document.body.addEventListener('click', function() {
button.classList.add('blue');
button.classList.remove('red');
}, {once: true});
} else if (performance.now() - lastClick > 500) {
removeScript(label.textContent);
if (currentElement.textContent == label.textContent) {
switchTo('Intro');
}
const container = button.parentElement;
container.parentElement.removeChild(container);
}
};
}
const newButton = document.querySelector('#new');
function newItem(code) {
const name = uniqueName('New Script');
setScript(name, code);
const nextButton = createDropdownItem(name);
newButton.insertAdjacentElement('afterend', nextButton.parentElement);
addEdit(nextButton);
nextButton.click();
};
newButton.onclick = function() {
newItem('');
};
const runButton = document.querySelector('#compile');
const poster = document.querySelector('#poster');
let manifoldInitialized = false;
let autoExecute = true;
function initializeRun() {
runButton.disabled = false;
if (autoExecute) {
runButton.click();
} else {
poster.textContent = 'Auto-run disabled due to prior failure';
}
}
// Editor ------------------------------------------------------------
let tsWorker = undefined;
async function getManifoldDTS() {
const global = await fetch('/manifold-global-types.d.ts')
.then(response => response.text());
const encapsulated = await fetch('/manifold-encapsulated-types.d.ts')
.then(response => response.text());
return `
${global.replaceAll('export', '')}
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
declare interface ManifoldToplevel {
CrossSection: typeof T.CrossSection;
Manifold: typeof T.Manifold;
Mesh: typeof T.Mesh;
triangulate: typeof T.triangulate;
setMinCircularAngle: typeof T.setMinCircularAngle;
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
setCircularSegments: typeof T.setCircularSegments;
getCircularSegments: typeof T.getCircularSegments;
setup: () => void;
}
declare const module: ManifoldToplevel;
`;
}
async function getEditorDTS() {
const global = await fetch('/editor.d.ts').then(response => response.text());
return `${global.replace(/^import.*$/gm, '')}`;
}
require.config({
paths:
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
});
require(['vs/editor/editor.main'], async function() {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
await getManifoldDTS());
monaco.languages.typescript.typescriptDefaults.addExtraLib(
await getEditorDTS());
editor = monaco.editor.create(
document.getElementById('editor'),
{language: 'typescript', automaticLayout: true});
const w = await monaco.languages.typescript.getTypeScriptWorker();
tsWorker = await w(editor.getModel().uri);
editor.getModel().updateOptions({tabSize: 2});
for (const [name] of exampleFunctions) {
const button = createDropdownItem(name);
dropdown.appendChild(button.parentElement);
}
let currentName = currentElement.textContent;
for (let i = 0; i < window.localStorage.length; i++) {
const key = nthKey(i);
if (!key) continue;
if (key === 'currentName') {
currentName = getScript(key);
} else if (key === 'safe') {
autoExecute = getScript(key) !== 'false';
} else {
const button = createDropdownItem(key);
newButton.insertAdjacentElement('afterend', button.parentElement);
addEdit(button);
}
}
switchTo(currentName);
if (manifoldInitialized) {
initializeRun();
}
editor.onDidChangeModelContent(e => {
runButton.disabled = false;
if (switching) {
switching = false;
return;
}
if (isExample) {
const cursor = editor.getPosition();
newItem(editor.getValue());
editor.setPosition(cursor);
}
});
window.onresize = () => {
editor.layout({});
};
});
// Execution ------------------------------------------------------------
const consoleElement = document.querySelector('#console');
const oldLog = console.log;
console.log = function(message) {
consoleElement.textContent += message + '\r\n';
consoleElement.scrollTop = consoleElement.scrollHeight;
oldLog(message);
};
function clearConsole() {
consoleElement.textContent = '';
}
function enableCancel() {
runButton.firstChild.style.visibility = 'hidden';
runButton.classList.add('red', 'cancel');
}
function disableCancel() {
runButton.firstChild.style.visibility = 'visible';
runButton.classList.remove('red', 'cancel');
}
let t0 = performance.now();
function finishRun() {
disableCancel();
const t1 = performance.now();
const log = consoleElement.textContent;
// Remove "Running..."
consoleElement.textContent = log.substring(log.indexOf('\n') + 1);
console.log(
`Took ${(Math.round((t1 - t0) / 10) / 100).toLocaleString()} seconds`);
}
const mv = document.querySelector('model-viewer');
let objectURL = null;
let manifoldWorker = null;
function createWorker() {
manifoldWorker = new ManifoldWorker();
manifoldWorker.onmessage = function(e) {
if (e.data == null) {
if (tsWorker != null && !manifoldInitialized) {
initializeRun();
}
manifoldInitialized = true;
return;
}
if (e.data.log != null) {
consoleElement.textContent += e.data.log + '\r\n';
consoleElement.scrollTop = consoleElement.scrollHeight;
return;
}
finishRun();
runButton.disabled = true;
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
mv.src = objectURL;
if (objectURL == null) {
mv.showPoster();
poster.textContent = 'Error';
createWorker();
} else {
setScript('safe', 'true');
}
}
}
createWorker();
async function run() {
saveCurrent();
setScript('safe', 'false');
enableCancel();
clearConsole();
console.log('Running...');
const output = await tsWorker.getEmitOutput(editor.getModel().uri.toString());
manifoldWorker.postMessage(output.outputFiles[0].text);
t0 = performance.now();
}
function cancel() {
manifoldWorker.terminate();
createWorker();
finishRun();
console.log('Run canceled');
}
runButton.onclick = function() {
if (runButton.classList.contains('cancel')) {
cancel();
} else {
run();
}
};
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
+285
View File
@@ -0,0 +1,285 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Accessor} from '@gltf-transform/core';
import {EXTManifold} from './manifold-gltf.js';
export const attributeDefs = {
'POSITION': {type: Accessor.Type.VEC3, components: 3},
'NORMAL': {type: Accessor.Type.VEC3, components: 3},
'TANGENT': {type: Accessor.Type.VEC4, components: 4},
'TEXCOORD_0': {type: Accessor.Type.VEC2, components: 2},
'TEXCOORD_1': {type: Accessor.Type.VEC2, components: 2},
'COLOR_0': {type: Accessor.Type.VEC3, components: 3},
'JOINTS_0': {type: Accessor.Type.VEC4, components: 4},
'WEIGHTS_0': {type: Accessor.Type.VEC4, components: 4},
'SKIP_1': {type: null, components: 1},
'SKIP_2': {type: null, components: 2},
'SKIP_3': {type: null, components: 3},
'SKIP_4': {type: null, components: 4},
};
export function setupIO(io) {
return io.registerExtensions([EXTManifold]);
}
function readPrimitive(primitive, numProp, attributes) {
const position = primitive.getAttribute('POSITION');
const numVert = position.getCount();
const vertProperties = [];
let offset = 0;
for (const attribute of attributes) {
const size = attributeDefs[attribute].components;
if (attribute === 'SKIP') {
offset += size;
continue;
}
const accessor = primitive.getAttribute(attribute);
if (accessor) {
const array = accessor.getArray();
for (let i = 0; i < numVert; ++i) {
for (let j = 0; j < size; ++j) {
vertProperties[numProp * i + offset + j] = array[i * size + j];
}
}
} else {
for (let i = 0; i < numVert; ++i) {
for (let j = 0; j < size; ++j) {
vertProperties[numProp * i + offset + j] = 0;
}
}
}
offset += size;
}
return vertProperties;
}
export function readMesh(mesh, attributes, materials) {
const primitives = mesh.listPrimitives();
if (primitives.length === 0) {
return {};
}
if (attributes.length === 0) {
const attributeSet = new Set();
for (const primitive of primitives) {
const semantics = primitive.listSemantics();
for (const semantic of semantics) {
attributeSet.add(semantic);
}
}
for (const semantic in attributeDefs) {
if (attributeSet.has(semantic)) {
attributes.push(semantic);
attributeSet.delete(semantic);
}
}
for (const semantic of attributeSet.keys()) {
attributes.push(semantic);
}
}
if (attributes.length < 1 || attributes[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
const numProp = attributes.map((def) => attributeDefs[def].components)
.reduce((a, b) => a + b);
const manifoldPrimitive = mesh.getExtension('EXT_manifold');
let vertPropArray = [];
let triVertArray = [];
const runIndexArray = [0];
const mergeFromVert = [];
const mergeToVert = [];
if (manifoldPrimitive != null) {
vertPropArray = readPrimitive(primitives[0], numProp, attributes);
for (const primitive of primitives) {
triVertArray = [...triVertArray, ...primitive.getIndices().getArray()];
runIndexArray.push(triVertArray.length);
materials.push(primitive.getMaterial());
}
const mergeTriVert = manifoldPrimitive.getMergeIndices()?.getArray() ?? [];
const mergeTo = manifoldPrimitive.getMergeValues()?.getArray() ?? [];
const vert2merge = new Map();
for (const [i, idx] of mergeTriVert.entries()) {
vert2merge.set(triVertArray[idx], mergeTo[i]);
}
for (const [from, to] of vert2merge.entries()) {
mergeFromVert.push(from);
mergeToVert.push(to);
}
} else {
for (const primitive of primitives) {
const numVert = vertPropArray.length / numProp;
vertPropArray =
[...vertPropArray, ...readPrimitive(primitive, numProp, attributes)];
triVertArray = [
...triVertArray,
...primitive.getIndices().getArray().map((i) => i + numVert)
];
runIndexArray.push(triVertArray.length);
materials.push(primitive.getMaterial());
}
}
const vertProperties = new Float32Array(vertPropArray);
const triVerts = new Uint32Array(triVertArray);
const runIndex = new Uint32Array(runIndexArray);
return {
numProp,
triVerts,
vertProperties,
runIndex,
mergeFromVert,
mergeToVert
};
}
export function writeMesh(doc, manifoldMesh, attributes, materials) {
if (doc.getRoot().listBuffers().length === 0) {
doc.createBuffer();
}
const buffer = doc.getRoot().listBuffers()[0];
const manifoldExtension = doc.createExtension(EXTManifold);
const attributeUnion = [];
for (const matAttributes of attributes) {
matAttributes.forEach((attribute, i) => {
if (i >= attributeUnion.length) {
attributeUnion.push(attribute);
} else {
const size = attributeDefs[attribute].components;
const unionSize = attributeDefs[attributeUnion[i]].components;
if (size != unionSize) {
throw new Error(
'Attribute sizes do not correspond: ', attribute, ' and ',
attributeUnion[i]);
}
if (attributeDefs[attributeUnion[i]].type == null) {
attributeUnion[i] = attribute;
}
}
});
}
if (attributeUnion.length < 1 || attributeUnion[0] !== 'POSITION')
throw new Error('First attribute must be "POSITION".');
const mesh = doc.createMesh();
const numPrimitive = manifoldMesh.runIndex.length - 1;
for (let run = 0; run < numPrimitive; ++run) {
const indices = doc.createAccessor('indices')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array());
const primitive = doc.createPrimitive().setIndices(indices);
const material = materials[run];
if (material) {
primitive.setMaterial(material);
}
mesh.addPrimitive(primitive);
}
const numVert = manifoldMesh.numVert;
const numProp = manifoldMesh.numProp;
let offset = 0;
attributeUnion.forEach((attribute, aIdx) => {
if (attribute === 'SKIP') {
++offset;
return;
}
const def = attributeDefs[attribute];
if (def == null)
throw new Error(attribute + ' is not a recognized attribute.');
const n = def.components;
if (offset + n > numProp) throw new Error('Too many attribute channels.');
const array = new Float32Array(n * numVert);
for (let v = 0; v < numVert; ++v) {
for (let i = 0; i < n; ++i) {
array[n * v + i] =
manifoldMesh.vertProperties[numProp * v + offset + i];
}
}
const accessor =
doc.createAccessor().setBuffer(buffer).setType(def.type).setArray(
array);
mesh.listPrimitives().forEach((primitive, pIdx) => {
if (attributes[pIdx].length > aIdx &&
attributeDefs[attributes[pIdx][aIdx]].type != null) {
primitive.setAttribute(attribute, accessor);
}
});
offset += n;
});
const manifoldPrimitive = manifoldExtension.createManifoldPrimitive();
mesh.setExtension('EXT_manifold', manifoldPrimitive);
const indices = doc.createAccessor('indices')
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(manifoldMesh.triVerts);
manifoldPrimitive.setIndices(indices);
manifoldPrimitive.setRunIndex(manifoldMesh.runIndex);
const vert2merge = [...Array(manifoldMesh.numVert).keys()];
for (const [i, from] of manifoldMesh.mergeFromVert.entries()) {
vert2merge[from] = manifoldMesh.mergeToVert[i];
}
const ind = [];
const val = [];
for (const [i, vert] of manifoldMesh.triVerts.entries()) {
const newVert = vert2merge[vert];
if (vert !== newVert) {
ind.push(i);
val.push(newVert);
}
}
const indicesAccessor = doc.createAccessor()
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(ind));
const valuesAccessor = doc.createAccessor()
.setBuffer(buffer)
.setType(Accessor.Type.SCALAR)
.setArray(new Uint32Array(val));
manifoldPrimitive.setMerge(indicesAccessor, valuesAccessor);
return mesh;
}
export function disposeMesh(mesh) {
if (!mesh) return;
const primitives = mesh.listPrimitives();
for (const primitive of primitives) {
primitive.getIndices()?.dispose();
for (const accessor of primitive.listAttributes()) {
accessor.dispose();
}
}
mesh.dispose();
}
export async function loadTexture(texture, uri) {
const response = await fetch(uri);
const blob = await response.blob();
texture.setMimeType(blob.type);
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
}
+77
View File
@@ -0,0 +1,77 @@
<!doctype html>
<html lang="en">
<head>
<title>ManifoldCAD</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description"
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
<meta name="theme-color" content="#A288B3">
<meta property="og:title" content="ManifoldCAD">
<meta property="og:type" content="website">
<meta property="og:url" content="https://manifoldcad.org/" />
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
<meta property="og:image:width" content="850">
<meta property="og:image:height" content="850">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
<link rel="manifest" href="/manifest.json">
<link type="text/css" href="editor.css" rel="stylesheet">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "ManifoldCAD",
"applicationCategory": "DesignApplication",
"offers": {
"@type": "Offer",
"price": "0"
}
}
</script>
<script>
self.ModelViewerElement = self.ModelViewerElement || {};
self.ModelViewerElement.modelCacheSize = 0;
</script>
<script type="module" src="/examples.js"></script>
</head>
<body>
<div class="page">
<div class="container header" style="background-color: #A288B3;">
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
<div class="margin" style="position: relative;">
<button type="button" id="file" class="blue"><b class="uparrow"></b>&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 type="module" src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>
</html>
+189
View File
@@ -0,0 +1,189 @@
<!doctype html>
<html lang="en">
<head>
<title>Make manifold glTF</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
model-viewer {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
border: 3px solid black;
border-radius: 10px;
margin-top: 5px;
}
#poster {
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
}
</style>
</head>
<body>
<p>Load a glTF/GLB model and our <a href="https://github.com/elalish/manifold">Manifold</a> library will attempt to
merge it into a set of manifold objects. If the model is water-tight, you can download the new GLB which will have
our <a href="https://github.com/KhronosGroup/glTF/pull/2286">EXT_manifold</a> extension, thus preserving the
manifold data without losing any mesh properties.</p>
<p> If the View Manifold GLB checkbox is enabled, then some meshes in the model have open edges and don't represent a
solid object. Check this box to see only the manifold parts, which will also enable them to be downloaded. If the
download button is still disabled, it means there were no manifold meshes found. </p>
<input type="file" id="input">
<button id="download" disabled>Download manifold GLB</button>
<input type="checkbox" id="viewFinal" disabled>
<label for="viewFinal">View Manifold GLB</label>
<model-viewer camera-controls shadow-intensity="1" alt="Loaded GLB model">
<span id="poster" slot="poster">Drop a GLB here</span>
</model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<script type="module">
import { WebIO } from '@gltf-transform/core';
import { prune } from '@gltf-transform/functions';
import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
import { SimpleDropzone } from 'simple-dropzone';
import { readMesh, writeMesh, setupIO } from './gltf-io.js';
import Module from './built/manifold.js';
const io = setupIO(new WebIO());
io.registerExtensions(KHRONOS_EXTENSIONS);
const wasm = await Module();
wasm.setup();
const { Manifold, Mesh } = wasm;
const mv = document.querySelector('model-viewer');
const inputEl = document.querySelector('#input');
const downloadButton = document.querySelector('#download');
const checkbox = document.querySelector('#viewFinal');
const dropCtrl = new SimpleDropzone(mv, inputEl);
let inputGLBurl = null;
let outputGLBurl = null;
let allManifold = true;
let anyManifold = false;
dropCtrl.on('drop', async ({ files }) => {
for (const [path, file] of files) {
const filename = file.name.toLowerCase();
if (filename.match(/\.(gltf|glb)$/)) {
URL.revokeObjectURL(inputGLBurl);
inputGLBurl = URL.createObjectURL(file);
await writeGLB(await readGLB(inputGLBurl));
updateUI();
break;
}
}
});
function updateUI() {
if (allManifold) {
checkbox.checked = true;
checkbox.disabled = true;
} else if (anyManifold) {
checkbox.checked = false;
checkbox.disabled = false;
} else {
checkbox.checked = false;
checkbox.disabled = true;
}
onClick();
}
checkbox.onclick = onClick;
function onClick() {
mv.src = checkbox.checked ? outputGLBurl : inputGLBurl;
downloadButton.disabled = !checkbox.checked;
};
downloadButton.onclick = function () {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = outputGLBurl;
link.click();
};
async function writeGLB(doc) {
URL.revokeObjectURL(outputGLBurl);
if (!anyManifold) {
return;
}
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], { type: 'application/octet-stream' });
outputGLBurl = URL.createObjectURL(blob);
}
async function readGLB(url) {
allManifold = false;
anyManifold = false;
updateUI();
allManifold = true;
const docIn = await io.read(url);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const attributes = [];
const mesh = node.getMesh();
if (!mesh) {
continue;
}
const materials = [];
const tmpMesh = readMesh(mesh, attributes, materials);
tmpMesh.runOriginalID = [];
const firstID = Manifold.reserveIDs(materials.length);
for (let i = 0; i < materials.length; ++i) {
tmpMesh.runOriginalID.push(firstID + i);
}
const manifoldMesh = new Mesh(tmpMesh);
mesh.dispose();
const matAttributes = [];
materials.forEach(() => matAttributes.push(attributes));
manifoldMesh.merge();
try {
// Test manifoldness - will throw if not.
const manifold = Manifold(manifoldMesh);
node.setMesh(writeMesh(docIn, manifold.getMesh(), matAttributes, materials));
manifold.delete();
anyManifold = true;
} catch (e) {
console.log(mesh.getName(), e);
allManifold = false;
}
}
await docIn.transform(prune());
return docIn;
}
</script>
</html>
+180
View File
@@ -0,0 +1,180 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Extension, ExtensionProperty, PropertyType} from '@gltf-transform/core';
const NAME = 'EXT_manifold';
export class ManifoldPrimitive extends ExtensionProperty {
static EXTENSION_NAME = NAME;
init() {
this.EXTENSION_NAME = NAME;
this.propertyType = 'ManifoldPrimitive';
this.parentTypes = [PropertyType.MESH];
}
getDefaults() {
return Object.assign(
super.getDefaults(),
{manifoldPrimitive: null, mergeIndices: null, mergeValues: null});
}
getMergeIndices() {
return this.getRef('mergeIndices');
}
getMergeValues() {
return this.getRef('mergeValues');
}
setMerge(indicesAccessor, valuesAccessor) {
if (indicesAccessor.getCount() !== valuesAccessor.getCount())
throw new Error('merge vectors must be the same length.');
this.setRef('mergeIndices', indicesAccessor);
return this.setRef('mergeValues', valuesAccessor);
}
getRunIndex() {
return this.get('runIndex');
}
setRunIndex(runIndex) {
return this.set('runIndex', runIndex);
}
setIndices(indices) {
return this.setRef('indices', indices);
}
getIndices() {
return this.getRef('indices');
}
}
export class EXTManifold extends Extension {
extensionName = NAME;
static EXTENSION_NAME = NAME;
createManifoldPrimitive() {
return new ManifoldPrimitive(this.document.getGraph());
}
read(context) {
const {json} = context.jsonDoc;
const meshDefs = json.meshes || [];
meshDefs.forEach((meshDef, meshIndex) => {
if (!meshDef.extensions || !meshDef.extensions[NAME]) return;
const mesh = context.meshes[meshIndex];
const manifoldPrimitive = this.createManifoldPrimitive();
mesh.setExtension(NAME, manifoldPrimitive);
const manifoldDef = meshDef.extensions[NAME];
if (manifoldDef.manifoldPrimitive) {
let count = 0;
const runIndex = [];
runIndex.push(count);
for (const primitive of mesh.listPrimitives()) {
count += primitive.getIndices().getCount();
runIndex.push(count);
}
manifoldPrimitive.setRunIndex(runIndex);
}
if (manifoldDef.mergeIndices && manifoldDef.mergeValues) {
manifoldPrimitive.setMerge(
context.accessors[manifoldDef.mergeIndices],
context.accessors[manifoldDef.mergeValues]);
}
});
return this;
}
write(context) {
const {json} = context.jsonDoc;
this.document.getRoot().listMeshes().forEach((mesh) => {
const manifoldPrimitive = mesh.getExtension(NAME);
if (!manifoldPrimitive) return;
const meshIndex = context.meshIndexMap.get(mesh);
const meshDef = json.meshes[meshIndex];
const runIndex = manifoldPrimitive.getRunIndex();
const numPrimitive = runIndex.length - 1;
if (numPrimitive !== meshDef.primitives.length) {
throw new Error(
'The number of primitives must be exactly one less than the length of runIndex.');
}
const mergeIndicesIndex =
context.accessorIndexMap.get(manifoldPrimitive.getMergeIndices());
const mergeValuesIndex =
context.accessorIndexMap.get(manifoldPrimitive.getMergeValues());
const mergeIndices = json.accessors[mergeIndicesIndex];
const mergeValues = json.accessors[mergeValuesIndex];
const existingPrimitive = meshDef.primitives[0];
const primitive = {
indices: context.accessorIndexMap.get(manifoldPrimitive.getIndices()),
mode: existingPrimitive.mode,
attributes: {'POSITION': existingPrimitive.attributes['POSITION']}
};
const indices = json.accessors[primitive.indices];
if (!indices) {
return;
}
if (mergeIndices && mergeValues) {
indices.sparse = {
count: mergeIndices.count,
indices: {
bufferView: mergeIndices.bufferView,
byteOffset: mergeIndices.byteOffset,
componentType: mergeIndices.componentType
},
values: {
bufferView: mergeValues.bufferView,
byteOffset: mergeValues.byteOffset,
}
};
}
for (let i = 0; i < numPrimitive; ++i) {
const accessor = json.accessors[meshDef.primitives[i].indices];
accessor.bufferView = indices.bufferView;
accessor.byteOffset = indices.byteOffset + 4 * runIndex[i];
accessor.count = runIndex[i + 1] - runIndex[i];
}
meshDef.extensions = meshDef.extensions || {};
meshDef.extensions[NAME] = {
manifoldPrimitive: primitive,
mergeIndices: mergeIndicesIndex,
mergeValues: mergeValuesIndex
};
// Test the manifold primitive by replacing the material primitives
// meshDef.primitives = [primitive];
});
return this;
}
}
+151
View File
@@ -0,0 +1,151 @@
<!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 <a
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
three.js example is <a href="three.html">here</a>. </p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
<option value="union">Union</option>
</select>
<button id="download">Download manifold GLB</button>
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<script type="module">
import { Document, WebIO } from '@gltf-transform/core';
import { clearNodeTransform, flatten, prune } from '@gltf-transform/functions';
import { loadTexture, readMesh, writeMesh, disposeMesh, setupIO } from './gltf-io.js';
import Module from './built/manifold.js';
const io = setupIO(new WebIO());
const doc = new Document();
const wasm = await Module();
wasm.setup();
const { Manifold, Mesh } = wasm;
const attributes = ['POSITION', 'TEXCOORD_0'];
const materialMap = new Map();
async function readGLB(url, attributes, materialMap) {
const manifolds = [];
const docIn = await io.read(url);
await docIn.transform(flatten());
const nodes = docIn.getRoot().listNodes();
const ids = [];
for (const node of nodes) {
clearNodeTransform(node);
const materials = [];
const manifoldMesh = readMesh(node.getMesh(), attributes, materials);
manifoldMesh.runOriginalID = [];
const firstID = Manifold.reserveIDs(materials.length);
for (let i = 0; i < materials.length; ++i) {
manifoldMesh.runOriginalID.push(firstID + i);
ids.push(firstID + i);
}
manifolds.push(Manifold(new Mesh(manifoldMesh)));
}
// pull in materials, TODO: replace with transfer() when available
const startIdx = doc.getRoot().listMaterials().length;
doc.merge(docIn);
doc.getRoot().listScenes().forEach((s) => s.dispose());
doc.getRoot().listBuffers().forEach((s) => s.dispose());
for (const [i, id] of ids.entries()) {
const material = doc.getRoot().listMaterials()[startIdx + i];
materialMap.set(id, material);
}
return Manifold.union(manifolds);
}
const space = await readGLB('/models/space.glb', attributes, materialMap);
const moon = await readGLB('/models/moon.glb', attributes, materialMap);
const node = doc.createNode();
doc.createScene().addChild(node);
function csg(operation) {
push2MV(Manifold[operation](space, moon));
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
const mv = document.querySelector('model-viewer');
let objectURL = null;
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function () {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
async function push2MV(manifold) {
// From Z-up to Y-up (glTF)
const manifoldMesh = manifold.rotate([-90, 0, 0]).getMesh();
disposeMesh(node.getMesh());
const materials = [];
const matAttributes = [];
for (const id of manifoldMesh.runOriginalID) {
materials.push(materialMap.get(id));
matAttributes.push(attributes);
}
const mesh = writeMesh(doc, manifoldMesh, matAttributes, materials);
node.setMesh(mesh);
await doc.transform(prune());
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], { type: 'application/octet-stream' });
URL.revokeObjectURL(objectURL);
objectURL = URL.createObjectURL(blob);
mv.src = objectURL;
}
</script>
</html>
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
{
"name": "manifold-examples",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"build:watch": "vite build --watch",
"preview": "vite preview",
"test": "vitest",
"test:ui": "vitest --ui",
"test:run": "vitest run"
},
"dependencies": {
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.151.2"
},
"devDependencies": {
"@vitest/ui": "^0.31.1",
"@vitest/web-worker": "^0.31.1",
"typescript": "5.0.4",
"vite": "^4.3.2",
"vitest": "^0.31.1"
}
}
File diff suppressed because it is too large Load Diff
+531
View File
@@ -0,0 +1,531 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export const examples = {
functions: {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the
// API docs. Type e.g. "box." to see the Manifold API. Type
// "CrossSection." or "Manifold." to list the 2D and 3D constructors,
// respectively. Type "module." to see the static API - these functions
// can also be used bare. Use console.log() to print output (lower-right).
// This editor defines Z as up and units of mm.
const {cube, sphere} = Manifold;
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result", or create at least one
// GLTFNode - see Menger Sponge and Gyroid Module examples.
const result = box.subtract(ball);
// For visual debug, wrap any shape with show() and it and all of its
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
// only() to ghost out everything else, akin to * in OpenSCAD.
// All changes are automatically saved and restored between sessions.
// This PWA is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// Consider installing it (icon in the search bar) for easy access.
// See the script drop-down above ("Intro") for usage examples. The
// gl-matrix package from npm is automatically imported for convenience -
// its API is available in the top-level glMatrix object.
// Use GLTFNode for disjoint manifolds rather than compose(), as this will
// keep them better organized in the GLB. This will also allow you to
// specify material properties, and even vertex colors via
// setProperties(). See Tetrahedron Puzzle example.
return result;
},
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as
// a puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions = 50; // Divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
const tet = Manifold.tetrahedron().scale(scale);
const box = [];
box.push([1, -1], [1, 1]);
for (let i = 0; i <= nDivisions; ++i) {
box.push([gap / (4 * scale), 1 - i * 2 / nDivisions]);
}
const cyan = [0, 1, 1];
const magenta = [1, 0, 1];
const fade = (color, pos) => {
for (let i = 0; i < 3; ++i) {
color[i] = cyan[i] * pos[2] + magenta[i] * (1 - pos[2]);
}
};
// setProperties(3, fade) creates three channels of vertex properties
// according to the above fade function. setMaterial assigns these
// channels as colors, and sets the factor to white, since our default is
// yellow.
const screw = setMaterial(
Manifold.extrude(box, 1, nDivisions, 270).setProperties(3, fade),
{baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']});
const result = tet.intersect(
screw.rotate([0, 0, -45]).translate([0, 0, -0.5]).scale(2 * scale));
// Assigned materials are only applied to a GLTFNode. Note that material
// definitions cascade, applying recursively to all child surfaces, but
// overridden by any materials defined lower down. Our default material:
// {
// roughness = 0.2,
// metallic = 1,
// baseColorFactor = [1, 1, 0],
// alpha = 1,
// unlit = false,
// name = ''
// }
const node = new GLTFNode();
node.manifold = result;
return result;
},
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
const {sphere, cylinder, union} = Manifold;
function roundedFrame(edgeLength, radius, circularSegments = 0) {
const edge = cylinder(edgeLength, radius, -1, circularSegments);
const corner = sphere(radius, circularSegments);
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
const edge2 = union(
union(edge1, edge1.rotate([0, 0, 180])),
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
0, 0, -edgeLength / 2
]);
return union(edge4, edge4.rotate([180, 0, 0]));
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
return result;
},
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function.
// This example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
const func = (v) => {
const x2 = v[0] * v[0];
const y2 = v[1] * v[1];
const z = v[2];
const z2 = z * z;
const a = x2 + 9 / 4 * y2 + z2;
const b = z * z2 * (x2 + 9 / 80 * y2);
const a2 = a * a;
const a3 = a * a2;
const step = (r) => {
const r2 = r * r;
const r4 = r2 * r2;
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df =
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
let r = 1.5;
let dr = 1;
while (Math.abs(dr) > 0.0001) {
dr = step(r);
r -= dr;
}
// Update radius
v[0] *= r;
v[1] *= r;
v[2] *= r;
};
const ball = Manifold.sphere(1, 200);
const heart = ball.warp(func);
const box = heart.boundingBox();
const result = heart.scale(100 / (box.max[0] - box.min[0]));
return result;
},
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with
// smooth() and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
const radius = 30;
const offset = 20;
const wiggles = 12;
const sharpness = 0.8;
const n = 50;
const positions = [];
const triangles = [];
positions.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
for (let i = 0; i < 2 * wiggles; ++i) {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
positions.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triangles.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triangles.push(0, 2 + i, 2 + j);
halfedge = triangles.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triangles.push(1, 2 + j, 2 + i);
}
const triVerts = Uint32Array.from(triangles);
const vertProperties = Float32Array.from(positions);
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
const colorCurvature = (color, pos, oldProp) => {
const a = Math.max(0, Math.min(1, oldProp[0] / 3 + 0.5));
const b = a * a * (3 - 2 * a);
const red = [1, 0, 0];
const blue = [0, 0, 1];
for (let i = 0; i < 3; ++i) {
color[i] = (1 - b) * blue[i] + b * red[i];
}
};
const result = Manifold.smooth(scallop, sharpenedEdges)
.refine(n)
.calculateCurvature(-1, 0)
.setProperties(3, colorCurvature);
const node = new GLTFNode();
node.manifold = result;
node.material = {
baseColorFactor: [1, 1, 1],
metallic: 0,
attributes: ['COLOR_0']
};
return result;
},
TorusKnot: function() {
// Creates a classic torus knot, defined as a string wrapping periodically
// around the surface of an imaginary donut. If p and q have a common
// factor then you will get multiple separate, interwoven knots. This is
// an example of using the warp() method, thus avoiding any direct
// handling of triangles.
// @param p The number of times the thread passes through the donut hole.
// @param q The number of times the thread circles the donut.
// @param majorRadius Radius of the interior of the imaginary donut.
// @param minorRadius Radius of the small cross-section of the imaginary
// donut.
// @param threadRadius Radius of the small cross-section of the actual
// object.
// @param circularSegments Number of linear segments making up the
// threadRadius circle. Default is getCircularSegments(threadRadius).
// @param linearSegments Number of segments along the length of the knot.
// Default makes roughly square facets.
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
const {vec3} = glMatrix;
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
const kLoops = gcd(p, q);
p /= kLoops;
q /= kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * q * majorRadius / threadRadius;
const offset = 2
const circle = CrossSection.circle(1, n).translate([offset, 0]);
const func = (v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
vec3.set(
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
const center = vec3.fromValues(0, 0, 0);
const r = majorRadius + minorRadius * Math.cos(theta);
vec3.rotateX(v, v, center, -Math.atan2(p * minorRadius, q * r));
v[0] += minorRadius;
vec3.rotateY(v, v, center, theta);
v[0] += majorRadius;
vec3.rotateZ(v, v, center, psi);
};
let knot = Manifold.revolve(circle, m).warp(func);
if (kLoops > 1) {
const knots = [];
for (let k = 0; k < kLoops; ++k) {
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
}
knot = Manifold.compose(knots);
}
return knot;
}
// This recreates Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
const result = torusKnot(1, 3, 25, 10, 3.75);
return result;
},
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
const {vec2} = glMatrix;
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
if (depth == maxDepth) return;
const offsets = [
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
];
for (let offset of offsets)
fractal(
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
maxDepth);
}
function mengerSponge(n) {
let result = Manifold.cube([1, 1, 1], true);
const holes = [];
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
const hole = Manifold.compose(holes);
result = Manifold.difference(
result,
hole,
hole.rotate([90, 0, 0]),
hole.rotate([0, 90, 0]),
);
return result;
}
const posColors = (newProp, pos) => {
for (let i = 0; i < 3; ++i) {
newProp[i] = (1 - pos[i]) / 2;
}
};
const result = mengerSponge(3)
.trimByPlane([1, 1, 1], 0)
.setProperties(3, posColors)
.scale(100);
const node = new GLTFNode();
node.manifold = result;
node.material = {baseColorFactor: [1, 1, 1], attributes: ['COLOR_0']};
return result;
},
StretchyBracelet: function() {
// Recreates Stretchy Bracelet by Emmett Lalish:
// https://www.thingiverse.com/thing:13505
const {vec2} = glMatrix;
function base(
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
let b = Manifold.cylinder(width, radius + twistRadius / 2);
const circle = [];
const dPhiDeg = 180 / nDivision;
for (let i = 0; i < 2 * nDivision; ++i) {
circle.push([
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
]);
}
let decor = Manifold.extrude(circle, width, nDivision, 180)
.scale([1, 0.5, 1])
.translate([0, radius, 0]);
for (let i = 0; i < nDecor; i++)
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
const stretch = [];
const dPhiRad = 2 * Math.PI / nCut;
const o = vec2.fromValues(0, 0);
const p0 = vec2.fromValues(outerRadius, 0);
const p1 = vec2.fromValues(innerRadius, -cut);
const p2 = vec2.fromValues(innerRadius, cut);
for (let i = 0; i < nCut; ++i) {
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
}
const result =
Manifold.intersection(Manifold.extrude(stretch, width), b);
return result;
}
function stretchyBracelet(
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
const twistRadius = Math.PI * radius / nDecor;
const decorRadius = twistRadius * 1.5;
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
const innerRadius = outerRadius - height;
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
const adjThickness = 0.5 * thickness * height / cut;
return Manifold.difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
const result = stretchyBracelet();
return result;
},
GyroidModule: function() {
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
// use of a Signed Distance Function (SDF) to create smooth, complex
// manifolds.
const {vec3} = glMatrix;
// number of modules along pyramid edge (use 1 for print orientation)
const m = 4;
// module size
const size = 20;
// SDF resolution
const n = 20;
const pi = 3.14159;
function gyroid(p) {
const x = p[0] - pi / 4;
const y = p[1] - pi / 4;
const z = p[2] - pi / 4;
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
const period = 2 * pi;
const box = {
min: vec3.fromValues(-period, -period, -period),
max: vec3.fromValues(period, period, period)
};
return Manifold.levelSet(gyroid, box, period / n, level)
.scale(size / period);
};
function rhombicDodecahedron() {
const box = Manifold.cube([1, 1, 2], true).scale(size * Math.sqrt(2));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
const gyroidModule = rhombicDodecahedron()
.intersect(gyroidOffset(-0.4))
.subtract(gyroidOffset(0.4));
if (m > 1) {
for (let i = 0; i < m; ++i) {
for (let j = i; j < m; ++j) {
for (let k = j; k < m; ++k) {
const node = new GLTFNode();
node.manifold = gyroidModule;
node.translation =
[(k + i - j) * size, (k - i) * size, (-j) * size];
node.material = {
baseColorFactor:
[(k + i - j + 1) / m, (k - i + 1) / m, (j + 1) / m]
};
}
}
}
}
const result = gyroidModule.rotate([-45, 0, 90]).translate([
0, 0, size / Math.sqrt(2)
]);
return result;
}
},
functionBodies: new Map()
};
for (const [func, code] of Object.entries(examples.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first six leading spaces
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
const name =
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
examples.functionBodies.set(name, body);
};
if (typeof self !== 'undefined') {
self.examples = examples;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,42 @@
{
"name": "ManifoldCAD",
"icons": [
{
"src": "/icons/mengerSponge192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/icons/mengerSponge512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"id": "/",
"start_url": "/",
"background_color": "#FFFFFF",
"display": "standalone",
"orientation": "landscape",
"scope": "/",
"theme_color": "#A288B3",
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
"categories": [
"design",
"developer",
"education",
"graphics",
"productivity"
],
"screenshots": [
{
"src": "/icons/manifoldCAD.png",
"type": "image/png",
"sizes": "2906x1980"
},
{
"src": "/icons/manifoldCADonly.png",
"type": "image/png",
"sizes": "2906x1980"
}
]
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,41 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Increment version when updating CDN URLs to clean up cache.
const cacheName = 'manifoldCAD-cache-v1';
self.addEventListener('activate', e => {
e.waitUntil(caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== cacheName) {
return caches.delete(key);
}
}));
}));
});
// Serves from cache, then updates the cache in the background from the network,
// if available. Update available on refresh.
self.addEventListener(
'fetch',
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
const networkFetch = fetch(e.request).then(response => {
const clone = response.clone();
caches.open(cacheName).then(cache => {
cache.put(e.request, clone);
});
return response;
});
return cachedResponse || networkFetch;
}))});
+117
View File
@@ -0,0 +1,117 @@
<!doctype html>
<html lang="en">
<head>
<title>Manifold - three.js Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
canvas {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
}
</style>
</head>
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &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 type="module">
import * as THREE from 'three';
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import Module from './built/manifold.js';
const wasm = await Module();
wasm.setup();
const { Manifold, Mesh } = wasm;
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const manifold_1 = Manifold.cube([0.2, 0.2, 0.2], true);
const manifold_2 = new Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(Manifold[operation](manifold_1, manifold_2).getMesh());
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
});
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const vertProperties = geometry.attributes.position.array;
const triVerts = geometry.index.array;
return new Mesh({ numProp: 3, vertProperties, triVerts });
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
</script>
</html>
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"moduleResolution": "node",
"lib": [
"es2022",
"dom"
],
"sourceMap": true,
"inlineSources": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"declaration": true,
"skipLibCheck": true,
},
"include": [
"./*.ts"
],
"exclude": []
}
+26
View File
@@ -0,0 +1,26 @@
// vite.config.js
import {resolve} from 'path'
import {defineConfig} from 'vite'
export default defineConfig({
test: {testTimeout: 15000},
worker: {
format: 'es',
},
build: {
target: 'esnext',
rollupOptions: {
input: {
manifoldCAD: resolve(__dirname, 'index.html'),
makeManifold: resolve(__dirname, 'make-manifold.html'),
modelViewer: resolve(__dirname, 'model-viewer.html'),
three: resolve(__dirname, 'three.html'),
},
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
},
},
})
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import '@vitest/web-worker';
import {WebIO} from '@gltf-transform/core';
import {expect, suite, test} from 'vitest';
import Module from './built/manifold.js';
import {readMesh, setupIO} from './gltf-io.js';
import {examples} from './public/examples.js';
import ManifoldWorker from './worker?worker';
const io = setupIO(new WebIO());
const wasm = await Module();
wasm.setup();
function initialized(worker) {
return new Promise((resolve) => {
worker.onmessage = function(e) {
if (e.data == null) {
resolve();
} else {
reject();
}
};
});
}
let objectURL = null;
async function runExample(name) {
const worker = new ManifoldWorker();
await initialized(worker);
return new Promise((resolve, reject) => {
worker.onmessageerror = function({data}) {
reject(data);
};
worker.onerror = function(e) {
reject(e);
};
worker.onmessage = async function(e) {
try {
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
if (objectURL == null) {
reject('no objectURL');
}
const docIn = await io.read(objectURL);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const mesh = node.getMesh();
if (!mesh) {
continue;
}
const attributes = [];
const materials = [];
const manifoldMesh = readMesh(mesh, attributes, materials);
const manifold = wasm.Manifold(manifoldMesh);
const prop = manifold.getProperties();
const genus = manifold.genus();
console.log(genus);
manifold.delete();
resolve({...prop, genus});
}
} catch (e) {
reject(e);
}
};
worker.postMessage(examples.functionBodies.get(name));
});
}
suite('Examples', () => {
test('Intro', async () => {
const result = await runExample('Intro');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
});
test('Tetrahedron Puzzle', async () => {
const result = await runExample('Tetrahedron Puzzle');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
});
test('Rounded Frame', async () => {
const result = await runExample('Rounded Frame');
expect(result.genus).to.equal(5, 'Genus');
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
});
test('Heart', async () => {
const result = await runExample('Heart');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
});
test('Scallop', async () => {
const result = await runExample('Scallop');
expect(result.genus).to.equal(0, 'Genus');
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
});
test('Torus Knot', async () => {
const result = await runExample('Torus Knot');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(20786, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(11177, 1, 'Surface Area');
});
test('Menger Sponge', async () => {
const result = await runExample('Menger Sponge');
expect(result.genus).to.equal(729, 'Genus');
expect(result.volume).to.be.closeTo(203222, 10, 'Volume');
expect(result.surfaceArea).to.be.closeTo(130475, 10, 'Surface Area');
});
test('Stretchy Bracelet', async () => {
const result = await runExample('Stretchy Bracelet');
expect(result.genus).to.equal(1, 'Genus');
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
});
test('Gyroid Module', async () => {
const result = await runExample('Gyroid Module');
expect(result.genus).to.equal(15, 'Genus');
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
});
});
+464
View File
@@ -0,0 +1,464 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Document, Material, Node, WebIO} from '@gltf-transform/core';
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
import * as glMatrix from 'gl-matrix';
import Module from './built/manifold';
//@ts-ignore
import {setupIO, writeMesh} from './gltf-io';
import type {GLTFMaterial, Quat} from './public/editor';
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
interface WorkerStatic extends ManifoldToplevel {
GLTFNode: typeof GLTFNode;
show(manifold: Manifold): Manifold;
only(manifold: Manifold): Manifold;
setMaterial(manifold: Manifold, material: GLTFMaterial): void;
cleanup(): void;
}
const module = await Module() as unknown as WorkerStatic;
module.setup();
// Faster on modern browsers than Float32Array
glMatrix.glMatrix.setMatrixArrayType(Array);
const io = setupIO(new WebIO());
io.registerExtensions(KHRONOS_EXTENSIONS);
// manifold static methods (that return a new manifold)
const manifoldStaticFunctions = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'compose',
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh'
];
// manifold member functions (that return a new manifold)
const manifoldMemberFunctions = [
'add', 'subtract', 'intersect', 'decompose', 'warp', 'transform', 'translate',
'rotate', 'scale', 'mirror', 'refine', 'setProperties', 'asOriginal',
'trimByPlane', 'split', 'splitByPlane'
];
// CrossSection static methods (that return a new cross-section)
const crossSectionStaticFunctions = [
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
'ofPolygons'
];
// CrossSection member functions (that return a new cross-section)
const crossSectionMemberFunctions = [
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset'
];
// top level functions that construct a new manifold/mesh
const toplevelConstructors = ['show', 'only', 'setMaterial'];
const toplevel = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
'getCircularSegments', 'Mesh', 'GLTFNode', 'Manifold', 'CrossSection'
];
const exposedFunctions = toplevelConstructors.concat(toplevel);
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
const memoryRegistry = new Array<Manifold|CrossSection>();
function addMembers(
className: string, methodNames: Array<string>, areStatic: boolean) {
//@ts-ignore
const cls = module[className];
const obj = areStatic ? cls : cls.prototype;
for (const name of methodNames) {
if (name != 'cylinder') {
const originalFn = obj[name];
obj[name] = function(...args: any) {
//@ts-ignore
const result = originalFn(...args);
memoryRegistry.push(result);
return result;
};
}
}
}
addMembers('Manifold', manifoldMemberFunctions, false);
addMembers('Manifold', manifoldStaticFunctions, true);
addMembers('CrossSection', crossSectionMemberFunctions, false);
addMembers('CrossSection', crossSectionStaticFunctions, true);
for (const name of toplevelConstructors) {
//@ts-ignore
const originalFn = module[name];
//@ts-ignore
module[name] = function(...args: any) {
const result = originalFn(...args);
memoryRegistry.push(result);
return result;
};
}
module.cleanup = function() {
for (const obj of memoryRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
memoryRegistry.length = 0;
};
// Debug setup to show source meshes
let ghost = false;
const shown = new Map<number, Mesh>();
const singles = new Map<number, Mesh>();
const SHOW = {
baseColorFactor: [1, 0, 0],
alpha: 0.25,
roughness: 1,
metallic: 0
} as GLTFMaterial;
const GHOST = {
baseColorFactor: [0.5, 0.5, 0.5],
alpha: 0.25,
roughness: 1,
metallic: 0
} as GLTFMaterial;
const nodes = new Array<GLTFNode>();
const id2material = new Map<number, GLTFMaterial>();
const materialCache = new Map<GLTFMaterial, Material>();
function cleanup() {
ghost = false;
shown.clear();
singles.clear();
nodes.length = 0;
id2material.clear();
materialCache.clear();
}
class GLTFNode {
private _parent?: GLTFNode;
manifold?: Manifold;
translation?: Vec3;
rotation?: Vec3;
scale?: Vec3;
material?: GLTFMaterial;
name?: string;
constructor(parent?: GLTFNode) {
this._parent = parent;
nodes.push(this);
}
clone(parent?: GLTFNode) {
const copy = {...this};
copy._parent = parent;
nodes.push(copy);
return copy;
}
get parent() {
return this._parent;
}
}
module.GLTFNode = GLTFNode;
module.setMaterial = (manifold: Manifold, material: GLTFMaterial): Manifold => {
const out = manifold.asOriginal();
id2material.set(out.originalID(), material);
return out;
};
function debug(manifold: Manifold, map: Map<number, Mesh>) {
let result = manifold.asOriginal();
map.set(result.originalID(), result.getMesh());
return result;
};
module.show = (manifold) => {
return debug(manifold, shown);
};
module.only = (manifold) => {
ghost = true;
return debug(manifold, singles);
};
// Setup complete
self.postMessage(null);
if (self.console) {
const oldLog = self.console.log;
self.console.log = function(...args) {
let message = '';
for (const arg of args) {
if (arg == null) {
message += 'undefined';
} else if (typeof arg == 'object') {
message += JSON.stringify(arg, null, 4);
} else {
message += arg.toString();
}
}
self.postMessage({log: message});
oldLog(...args);
};
}
// Swallow informational logs in testing framework
function log(...args: any[]) {
if (self.console) {
self.console.log(...args);
}
}
self.onmessage = async (e) => {
const content = e.data +
'\nreturn exportGLB(typeof result === "undefined" ? undefined : result);\n';
try {
const f = new Function(
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
await f(
exportGLB, glMatrix, module, //@ts-ignore
...exposedFunctions.map(name => module[name]));
} catch (error: any) {
console.log(error.toString());
self.postMessage({objectURL: null});
} finally {
module.cleanup();
cleanup();
}
};
function createGLTFnode(doc: Document, node: GLTFNode) {
const out = doc.createNode(node.name);
if (node.translation) {
out.setTranslation(node.translation);
}
if (node.rotation) {
const {quat} = glMatrix;
const deg2rad = Math.PI / 180;
const q = quat.create() as Quat;
quat.rotateX(q, q, deg2rad * node.rotation[0]);
quat.rotateY(q, q, deg2rad * node.rotation[1]);
quat.rotateZ(q, q, deg2rad * node.rotation[2]);
out.setRotation(q);
}
if (node.scale) {
out.setScale(node.scale);
}
return out;
}
function getBackupMaterial(node?: GLTFNode): GLTFMaterial {
if (node == null) {
return {};
}
if (node.material == null) {
node.material = getBackupMaterial(node.parent);
}
return node.material;
}
function makeDefaultedMaterial(doc: Document, {
roughness = 0.2,
metallic = 1,
baseColorFactor = [1, 1, 0],
alpha = 1,
unlit = false,
name = ''
}: GLTFMaterial = {}): Material {
const material = doc.createMaterial(name);
if (unlit) {
const unlit = doc.createExtension(KHRMaterialsUnlit).createUnlit();
material.setExtension('KHR_materials_unlit', unlit);
}
if (alpha < 1) {
material.setAlphaMode(Material.AlphaMode.BLEND).setDoubleSided(true);
}
return material.setRoughnessFactor(roughness)
.setMetallicFactor(metallic)
.setBaseColorFactor([...baseColorFactor, alpha]);
}
function getCachedMaterial(doc: Document, matDef: GLTFMaterial): Material {
if (!materialCache.has(matDef)) {
materialCache.set(matDef, makeDefaultedMaterial(doc, matDef));
}
return materialCache.get(matDef)!;
}
function addMesh(
doc: Document, node: Node, manifold: Manifold,
backupMaterial: GLTFMaterial = {}) {
const numTri = manifold.numTri();
if (numTri == 0) {
log('Empty manifold, skipping.');
return;
}
log(`Triangles: ${numTri.toLocaleString()}`);
const box = manifold.boundingBox();
const size = [0, 0, 0];
for (let i = 0; i < 3; i++) {
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
}
log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
const volume = Math.round(manifold.getProperties().volume / 10);
log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
// From Z-up to Y-up (glTF)
const manifoldMesh = manifold.getMesh();
const materials = new Array<GLTFMaterial>();
const attributes = new Array<Array<string>>();
for (const id of manifoldMesh.runOriginalID!) {
const material = id2material.get(id) || backupMaterial;
materials.push(material);
attributes.push(['POSITION', ...material.attributes ?? []]);
}
const gltfMaterials = materials.map((matDef) => {
return getCachedMaterial(doc, ghost ? GHOST : matDef);
});
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
let inMesh = shown.get(id);
let single = false;
if (inMesh == null) {
single = true;
inMesh = singles.get(id);
}
if (inMesh == null) {
continue;
}
const mat = single ? materials[run] : SHOW;
const debugNode =
doc.createNode('debug')
.setMesh(writeMesh(
doc, inMesh, attributes, [getCachedMaterial(doc, mat)]))
.setMatrix(manifoldMesh.transform(run));
node.addChild(debugNode);
}
}
function cloneNode(toNode: Node, fromNode: Node) {
toNode.setMesh(fromNode.getMesh());
fromNode.listChildren().forEach((child) => {
const clone = child.clone();
toNode.addChild(clone);
});
}
function cloneNodeNewMaterial(
doc: Document, toNode: Node, fromNode: Node, backupMaterial: Material,
oldBackupMaterial: Material) {
cloneNode(toNode, fromNode);
const mesh = doc.createMesh();
toNode.setMesh(mesh);
fromNode.getMesh()!.listPrimitives().forEach((primitive) => {
const newPrimitive = primitive.clone();
if (primitive.getMaterial() === oldBackupMaterial) {
newPrimitive.setMaterial(backupMaterial);
}
mesh.addPrimitive(newPrimitive);
});
}
function createNodeFromCache(
doc: Document, nodeDef: GLTFNode,
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
const node = createGLTFnode(doc, nodeDef);
if (nodeDef.manifold != null) {
const backupMaterial = getBackupMaterial(nodeDef);
const cachedNodes = manifold2node.get(nodeDef.manifold);
if (cachedNodes == null) {
addMesh(doc, node, nodeDef.manifold, backupMaterial);
const cache = new Map<GLTFMaterial, Node>();
cache.set(backupMaterial, node);
manifold2node.set(nodeDef.manifold, cache);
} else {
const cachedNode = cachedNodes.get(backupMaterial);
if (cachedNode == null) {
const [oldBackupMaterial, oldNode] = cachedNodes.entries().next().value;
cloneNodeNewMaterial(
doc, node, oldNode, getCachedMaterial(doc, backupMaterial),
getCachedMaterial(doc, oldBackupMaterial));
cachedNodes.set(backupMaterial, node);
} else {
cloneNode(node, cachedNode);
}
}
}
return node;
}
async function exportGLB(manifold?: Manifold) {
const doc = new Document();
const halfRoot2 = Math.sqrt(2) / 2;
const mm2m = 1 / 1000;
const wrapper = doc.createNode('wrapper')
.setRotation([-halfRoot2, 0, 0, halfRoot2])
.setScale([mm2m, mm2m, mm2m]);
doc.createScene().addChild(wrapper);
if (nodes.length > 0) {
const node2gltf = new Map<GLTFNode, Node>();
const manifold2node = new Map<Manifold, Map<GLTFMaterial, Node>>();
let leafNodes = 0;
for (const nodeDef of nodes) {
node2gltf.set(nodeDef, createNodeFromCache(doc, nodeDef, manifold2node));
if (nodeDef.manifold) {
++leafNodes;
}
}
for (const nodeDef of nodes) {
const gltfNode = node2gltf.get(nodeDef)!;
if (nodeDef.parent == null) {
wrapper.addChild(gltfNode);
} else {
node2gltf.get(nodeDef.parent)!.addChild(gltfNode);
}
}
log('Total glTF nodes: ', nodes.length,
', Total mesh references: ', leafNodes);
} else {
if (manifold == null) {
log('No output because "result" is undefined and no "GLTFNode"s were created.');
return;
}
const node = doc.createNode();
addMesh(doc, node, manifold);
wrapper.addChild(node);
}
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], {type: 'application/octet-stream'});
self.postMessage({objectURL: URL.createObjectURL(blob)});
}
+222
View File
@@ -0,0 +1,222 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <manifold.h>
#include <polygon.h>
#include <sdf.h>
#include <vector>
#include "cross_section.h"
using namespace emscripten;
using namespace manifold;
namespace js {
val MeshGL2JS(const MeshGL& mesh) {
val meshJS = val::object();
meshJS.set("numProp", mesh.numProp);
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertProperties",
val(typed_memory_view(mesh.vertProperties.size(),
mesh.vertProperties.data()))
.call<val>("slice"));
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
mesh.mergeFromVert.data()))
.call<val>("slice"));
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
mesh.mergeToVert.data()))
.call<val>("slice"));
meshJS.set("runIndex",
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
.call<val>("slice"));
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
mesh.runOriginalID.data()))
.call<val>("slice"));
meshJS.set("faceID",
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
mesh.runTransform.data()))
.call<val>("slice"));
return meshJS;
}
MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.numProp = mesh["numProp"].as<int>();
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertProperties =
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
if (mesh["mergeFromVert"] != val::undefined()) {
out.mergeFromVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
}
if (mesh["mergeToVert"] != val::undefined()) {
out.mergeToVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
}
if (mesh["runIndex"] != val::undefined()) {
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
}
if (mesh["runOriginalID"] != val::undefined()) {
out.runOriginalID =
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
}
if (mesh["faceID"] != val::undefined()) {
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
if (mesh["runTransform"] != val::undefined()) {
out.runTransform =
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
}
return out;
}
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
MeshGL mesh = manifold.GetMeshGL(normalIdx);
return MeshGL2JS(mesh);
}
val Merge(const val& mesh) {
val out = val::object();
MeshGL meshGL = MeshJS2GL(mesh);
bool changed = meshGL.Merge();
out.set("changed", changed);
out.set("mesh", changed ? MeshGL2JS(meshGL) : mesh);
return out;
}
Manifold Smooth(const val& mesh,
const std::vector<Smoothness>& sharpenedEdges = {}) {
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
}
} // namespace js
namespace cross_js {
CrossSection OfPolygons(std::vector<std::vector<glm::vec2>> polygons,
int fill_rule) {
auto fr = fill_rule == 0 ? CrossSection::FillRule::EvenOdd
: fill_rule == 1 ? CrossSection::FillRule::NonZero
: fill_rule == 2 ? CrossSection::FillRule::Positive
: CrossSection::FillRule::Negative;
return CrossSection(polygons, fr);
}
CrossSection Union(const CrossSection& a, const CrossSection& b) {
return a + b;
}
CrossSection Difference(const CrossSection& a, const CrossSection& b) {
return a - b;
}
CrossSection Intersection(const CrossSection& a, const CrossSection& b) {
return a ^ b;
}
CrossSection UnionN(const std::vector<CrossSection>& cross_sections) {
return CrossSection::BatchBoolean(cross_sections, OpType::Add);
}
CrossSection DifferenceN(const std::vector<CrossSection>& cross_sections) {
return CrossSection::BatchBoolean(cross_sections, OpType::Subtract);
}
CrossSection IntersectionN(const std::vector<CrossSection>& cross_sections) {
return CrossSection::BatchBoolean(cross_sections, OpType::Intersect);
}
CrossSection Transform(CrossSection& cross_section, const val& mat) {
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
glm::mat3x2 matrix;
for (const int col : {0, 1, 2})
for (const int row : {0, 1}) matrix[col][row] = array[col * 3 + row];
return cross_section.Transform(matrix);
}
CrossSection Warp(CrossSection& cross_section, uintptr_t funcPtr) {
void (*f)(glm::vec2&) = reinterpret_cast<void (*)(glm::vec2&)>(funcPtr);
return cross_section.Warp(f);
}
CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
double miter_limit, double arc_tolerance) {
auto jt = join_type == 0 ? CrossSection::JoinType::Square
: join_type == 1 ? CrossSection::JoinType::Round
: CrossSection::JoinType::Miter;
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
}
} // namespace cross_js
namespace man_js {
Manifold FromMeshJS(const val& mesh) { return Manifold(js::MeshJS2GL(mesh)); }
Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }
Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
Manifold UnionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Add);
}
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Subtract);
}
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Intersect);
}
Manifold Transform(Manifold& manifold, const val& mat) {
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
glm::mat4x3 matrix;
for (const int col : {0, 1, 2, 3})
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
return manifold.Transform(matrix);
}
Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
return manifold.Warp(f);
}
Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) {
void (*f)(float*, glm::vec3, const float*) =
reinterpret_cast<void (*)(float*, glm::vec3, const float*)>(funcPtr);
return manifold.SetProperties(numProp, f);
}
Manifold LevelSet(uintptr_t funcPtr, Box bounds, float edgeLength,
float level) {
float (*f)(const glm::vec3&) =
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
Mesh m = LevelSet(f, bounds, edgeLength, level);
return Manifold(m);
}
std::vector<Manifold> Split(Manifold& a, Manifold& b) {
auto [r1, r2] = a.Split(b);
return {r1, r2};
}
std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
float originOffset) {
auto [a, b] = m.SplitByPlane(normal, originOffset);
return {a, b};
}
} // namespace man_js
+850
View File
@@ -0,0 +1,850 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {Box, FillRule, JoinType, Mat4, Polygons, Properties, Rect, SealedFloat32Array, SealedUint32Array, SimplePolygon, Smoothness, Vec2, Vec3} from './manifold-global-types';
/**
* Triangulates a set of /epsilon-valid polygons.
*
* @param polygons The set of polygons, wound CCW and representing multiple
* polygons and/or holes.
* @param precision The value of epsilon, bounding the uncertainty of the input
* @return The triangles, referencing the original polygon points in order.
*/
export function triangulate(polygons: Polygons, precision?: number): Vec3[];
/**
* @name Defaults
* These static properties control how circular shapes are quantized by
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
export function setMinCircularAngle(angle: number): void;
export function setMinCircularEdgeLength(length: number): void;
export function setCircularSegments(segments: number): void;
export function getCircularSegments(radius: number): number;
///@}
export class CrossSection {
/**
* Create a 2d cross-section from a set of contours (complex polygons). A
* boolean union operation (with Positive filling rule by default) is
* performed to combine overlapping polygons and ensure the resulting
* CrossSection is free of intersections.
*
* @param contours A set of closed paths describing zero or more complex
* polygons.
* @param fillRule The filling rule used to interpret polygon sub-regions in
* contours.
*/
constructor(polygons: Polygons, fillRule?: FillRule);
// Shapes
static square(size?: Vec2|number, center?: boolean): CrossSection;
static circle(radius: number, circularSegments?: number): CrossSection;
// Extrusions (2d to 3d manifold)
/**
* Constructs a manifold by extruding the cross-section along Z-axis.
*
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the
* top. Default {1, 1}.
* @param center If true, the extrusion is centered on the z-axis through the
* origin
* as opposed to resting on the XY plane as is default.
*/
extrude(
height: number, nDivisions?: number, twistDegrees?: number,
scaleTop?: Vec2|number, center?: boolean): Manifold;
/**
* Constructs a manifold by revolving this cross-section around its Y-axis and
* then setting this as the Z-axis of the resulting manifold. If the contours
* cross the Y-axis, only the part on the positive X side is used.
* Geometrically valid input will result in geometrically valid output.
*
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
*/
revolve(circularSegments?: number, revolveDegrees?: number): Manifold;
// Transformations
/**
* Transform this CrossSection in space. Stored in column-major order. This
* operation can be chained. Transforms are combined and applied lazily.
*
* @param m The affine transformation matrix to apply to all the vertices. The
* last row is ignored.
*/
transform(m: Mat3): CrossSection;
/**
* Move this CrossSection in space. This operation can be chained. Transforms
* are combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec2): CrossSection;
/**
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param degrees degrees about the Z-axis to rotate.
*/
rotate(v: number): CrossSection;
/**
* Scale this CrossSection in space. This operation can be chained. Transforms
* are combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec2|number): CrossSection;
/**
* Mirror this CrossSection over the arbitrary axis described by the unit form
* of the given vector. If the length of the vector is zero, an empty
* CrossSection is returned. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param ax the axis to be mirrored over
*/
mirror(v: Vec2): CrossSection;
/**
* Move the vertices of this CrossSection (creating a new one) according to
* any arbitrary input function, followed by a union operation (with a
* Positive fill rule) that ensures any introduced intersections are not
* included in the result.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec2) => void): CrossSection;
/**
* Inflate the contours in CrossSection by the specified delta, handling
* corners according to the given JoinType.
*
* @param delta Positive deltas will cause the expansion of outlining contours
* to expand, and retraction of inner (hole) contours. Negative deltas will
* have the opposite effect.
* @param joinType The join type specifying the treatment of contour joins
* (corners).
* @param miterLimit The maximum distance in multiples of delta that vertices
* can be offset from their original positions with before squaring is
* applied, **when the join type is Miter** (default is 2, which is the
* minimum allowed). See the [Clipper2
* MiterLimit](http://www.angusj.com/clipper2/Docs/Units/Clipper.Offset/Classes/ClipperOffset/Properties/MiterLimit.htm)
* page for a visual example.
* @param circularSegments Number of segments per 360 degrees of
* <B>JoinType::Round</B> corners (roughly, the number of vertices that
* will be added to each contour). Default is calculated by the static Quality
* defaults according to the radius.
*/
offset(
delta: number, joinType?: JoinType, miterLimit?: number,
circularSegments?: number): CrossSection;
/**
* Compute the convex hull of the contours in this CrossSection.
*/
hull(): CrossSection;
/**
* Remove vertices from the contours in this CrossSection that are less than
* the specified distance epsilon from an imaginary line that passes through
* its two adjacent vertices. Near duplicate vertices and collinear points
* will be removed at lower epsilons, with elimination of line segments
* becoming increasingly aggressive with larger epsilons.
*
* It is recommended to apply this function following Offset, in order to
* clean up any spurious tiny line segments introduced that do not improve
* quality in any meaningful way. This is particularly important if further
* offseting operations are to be performed, which would compound the issue.
*
* @param epsilon minimum distance vertices must diverge from the hypothetical
* outline without them in order to be included in the output (default
* 1e-6)
*/
simplify(epsilon?: number): CrossSection;
// Clipping Operations
/**
* Boolean union
*/
add(other: CrossSection|Polygons): CrossSection;
/**
* Boolean difference
*/
subtract(other: CrossSection|Polygons): CrossSection;
/**
* Boolean intersection
*/
intersect(other: CrossSection|Polygons): CrossSection;
/**
* Boolean union of the cross-sections a and b
*/
static union(a: CrossSection|Polygons, b: CrossSection|Polygons):
CrossSection;
/**
* Boolean difference of the cross-section b from the cross-section a
*/
static difference(a: CrossSection|Polygons, b: CrossSection|Polygons):
CrossSection;
/**
* Boolean intersection of the cross-sections a and b
*/
static intersection(a: CrossSection|Polygons, b: CrossSection|Polygons):
CrossSection;
/**
* Boolean union of a list of cross-sections
*/
static union(polygons: (CrossSection|Polygons)[]): CrossSection;
/**
* Boolean difference of the tail of a list of cross-sections from its head
*/
static difference(polygons: (CrossSection|Polygons)[]): CrossSection;
/**
* Boolean intersection of a list of cross-sections
*/
static intersection(polygons: (CrossSection|Polygons)[]): CrossSection;
// Topological Operations
/**
* Construct a CrossSection from a vector of other Polygons (batch
* boolean union).
*/
static compose(polygons: (CrossSection|Polygons)[]): CrossSection;
/**
* This operation returns a vector of CrossSections that are topologically
* disconnected, each containing one outline contour with zero or more
* holes.
*/
decompose(): CrossSection[];
// Polygon Conversion
/**
* Create a 2d cross-section from a set of contours (complex polygons). A
* boolean union operation (with Positive filling rule by default) is
* performed to combine overlapping polygons and ensure the resulting
* CrossSection is free of intersections.
*
* @param contours A set of closed paths describing zero or more complex
* polygons.
* @param fillRule The filling rule used to interpret polygon sub-regions in
* contours.
*/
static ofPolygons(polygons: Polygons, fillRule?: FillRule): CrossSection;
/**
* Return the contours of this CrossSection as a list of simple polygons.
*/
toPolygons(): SimplePolygon[];
// Properties
/**
* Return the total area covered by complex polygons making up the
* CrossSection.
*/
area(): number;
/**
* Does the CrossSection (not) have any contours?
*/
isEmpty(): boolean;
/**
* The number of vertices in the CrossSection.
*/
numVert(): number;
/**
* The number of contours in the CrossSection.
*/
numContour(): number;
/**
* Returns the axis-aligned bounding rectangle of all the CrossSection's
* vertices.
*/
bounds(): Rect;
// Memory
/**
* Frees the WASM memory of this CrossSection, since these cannot be
* garbage-collected automatically.
*/
delete(): void;
}
export class Manifold {
/**
* Convert a Mesh into a Manifold, retaining its properties and merging only
* the positions according to the merge vectors. Will throw an error if the
* result is not an oriented 2-manifold. Will collapse degenerate triangles
* and unnecessary vertices.
*
* All fields are read, making this structure suitable for a lossless
* round-trip of data from getMesh(). For multi-material input, use
* reserveIDs() to set a unique originalID for each material, and sort the
* materials into triangle runs.
*/
constructor(mesh: Mesh);
// Shapes
/**
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
* and the rest at similarly symmetric points.
*/
static tetrahedron(): Manifold;
/**
* Constructs a unit cube (edge lengths all one), by default in the first
* octant, touching the origin.
*
* @param size The X, Y, and Z dimensions of the box.
* @param center Set to true to shift the center to the origin.
*/
static cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can
* also form cones if both radii are specified.
*
* @param height Z-extent
* @param radiusLow Radius of bottom circle. Must be positive.
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
* radiusLow.
* @param circularSegments How many line segments to use around the circle.
* Default is calculated by the static Defaults.
* @param center Set to true to shift the center to the origin. Default is
* origin at the bottom.
*/
static cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
*
* @param radius Radius of the sphere. Must be positive.
* @param circularSegments Number of segments along its
* diameter. This number will always be rounded up to the nearest factor of
* four, as this sphere is constructed by refining an octahedron. This means
* there are a circle of vertices on all three of the axis planes. Default is
* calculated by the static Defaults.
*/
static sphere(radius: number, circularSegments?: number): Manifold;
// Extrusions from 2d shapes
/**
* Constructs a manifold from a set of polygons/cross-section by extruding
* them along the Z-axis.
*
* @param polygons A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the
* top. Default {1, 1}.
* @param center If true, the extrusion is centered on the z-axis through the
* origin
* as opposed to resting on the XY plane as is default.
*/
static extrude(
polygons: CrossSection|Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2|number,
center?: boolean): Manifold;
/**
* Constructs a manifold from a set of polygons/cross-section by revolving
* them around the Y-axis and then setting this as the Z-axis of the resulting
* manifold. If the polygons cross the Y-axis, only the part on the positive X
* side is used. Geometrically valid input will result in geometrically valid
* output.
*
* @param polygons A set of non-overlapping polygons to revolve.
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
* @param revolveDegrees Number of degrees to revolve. Default is 360 degrees.
*/
static revolve(
polygons: CrossSection|Polygons, circularSegments?: number,
revolveDegrees?: number): Manifold;
// Mesh Conversion
/**
* Convert a Mesh into a Manifold, retaining its properties and merging only
* the positions according to the merge vectors. Will throw an error if the
* result is not an oriented 2-manifold. Will collapse degenerate triangles
* and unnecessary vertices.
*
* All fields are read, making this structure suitable for a lossless
* round-trip of data from getMesh(). For multi-material input, use
* reserveIDs() to set a unique originalID for each material, and sort the
* materials into triangle runs.
*/
static ofMesh(mesh: Mesh): Manifold;
/**
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangents with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
* By default, every edge is calculated for maximum smoothness (very much
* approximately), attempting to minimize the maximum mean Curvature
* magnitude. No higher-order derivatives are considered, as the interpolation
* is independent per triangle, only sharing constraints on their boundaries.
*
* @param mesh input Mesh.
* @param sharpenedEdges If desired, you can supply a vector of sharpened
* halfedges, which should in general be a small subset of all halfedges.
* Order of entries doesn't matter, as each one specifies the desired
* smoothness (between zero and one, with one the default for all unspecified
* halfedges) and the halfedge index (3 * triangle index + [0,1,2] where 0 is
* the edge between triVert 0 and 1, etc).
*
* At a smoothness value of zero, a sharp crease is made. The smoothness is
* interpolated along each edge, so the specified value should be thought of
* as an average. Where exactly two sharpened edges meet at a vertex, their
* tangents are rotated to be colinear so that the sharpened edge can be
* continuous. Vertices with only one sharpened edge are completely smooth,
* allowing sharpened edges to smoothly vanish at termination. A single vertex
* can be sharpened by sharping all edges that are incident on it, allowing
* cones to be formed.
*/
static smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
// Signed Distance Functions
/**
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
* cubic grid (two shifted cubic grids). This means if your function's
* interior exceeds the given bounds, you will see a kind of egg-crate shape
* closing off the manifold, which is due to the underlying grid.
*
* @param sdf The signed-distance function which returns the signed distance
* of
* a given point in R^3. Positive values are inside, negative outside.
* @param bounds An axis-aligned box that defines the extent of the grid.
* @param edgeLength Approximate maximum edge length of the triangles in the
* final result. This affects grid spacing, and hence has a strong effect on
* performance.
* @param level You can inset your Mesh by using a positive value, or outset
* it with a negative value.
*/
static levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
// Transformations
/**
* Transform this Manifold in space. Stored in column-major order. This
* operation can be chained. Transforms are combined and applied lazily.
*
* @param m The affine transformation matrix to apply to all the vertices. The
* last row is ignored.
*/
transform(m: Mat4): Manifold;
/**
* Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
* error, and eliminate it completely for any multiples of 90 degrees.
* Additionally, more efficient code paths are used to update the manifold
* when the transforms only rotate by multiples of 90 degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3|number): Manifold;
/**
* Mirror this Manifold over the plane described by the unit form of the given
* normal vector. If the length of the normal is zero, an empty Manifold is
* returned. This operation can be chained. Transforms are combined and
* applied lazily.
*
* @param normal The normal vector of the plane to be mirrored over
*/
mirror(v: Vec3): Manifold;
/**
* This function does not change the topology, but allows the vertices to be
* moved according to any arbitrary input function. It is easy to create a
* function that warps a geometrically valid object into one which overlaps,
* but that is not checked here, so it is up to the user to choose their
* function with discretion.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec3) => void): Manifold;
/**
* Increase the density of the mesh by splitting every edge into n pieces. For
* instance, with n = 2, each triangle will be split into 4 triangles. These
* will all be coplanar (and will not be immediately collapsed) unless the
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
* constructor), in which case the new vertices will be moved to the
* interpolated surface according to their barycentric coordinates.
*
* @param n The number of pieces to split every edge into. Must be > 1.
*/
refine(n: number): Manifold;
/**
* Create a new copy of this manifold with updated vertex properties by
* supplying a function that takes the existing position and properties as
* input. You may specify any number of output properties, allowing creation
* and removal of channels. Note: undefined behavior will result if you read
* past the number of input properties or write past the number of output
* properties.
*
* @param numProp The new number of properties per vertex.
* @param propFunc A function that modifies the properties of a given vertex.
*/
setProperties(
numProp: number,
propFunc: (newProp: number[], position: Vec3, oldProp: number[]) => void):
Manifold;
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex and assigns
* them as vertex properties on the given channels.
*
* @param gaussianIdx The property channel index in which to store the
* Gaussian curvature. An index < 0 will be ignored (stores nothing). The
* property set will be automatically expanded to include the channel
* index specified.
* @param meanIdx The property channel index in which to store the mean
* curvature. An index < 0 will be ignored (stores nothing). The property
* set will be automatically expanded to include the channel index
* specified.
*/
calculateCurvature(gaussianIdx: number, meanIdx: number): Manifold;
// Boolean Operations
/**
* Boolean union
*/
add(other: Manifold): Manifold;
/**
* Boolean difference
*/
subtract(other: Manifold): Manifold;
/**
* Boolean intersection
*/
intersect(other: Manifold): Manifold;
/**
* Boolean union of the manifolds a and b
*/
static union(a: Manifold, b: Manifold): Manifold;
/**
* Boolean difference of the manifold b from the manifold a
*/
static difference(a: Manifold, b: Manifold): Manifold;
/**
* Boolean intersection of the manifolds a and b
*/
static intersection(a: Manifold, b: Manifold): Manifold;
/**
* Boolean union of a list of manifolds
*/
static union(manifolds: Manifold[]): Manifold;
/**
* Boolean difference of the tail of a list of manifolds from its head
*/
static difference(manifolds: Manifold[]): Manifold;
/**
* Boolean intersection of a list of manifolds
*/
static intersection(manifolds: Manifold[]): Manifold;
/**
* Split cuts this manifold in two using the cutter manifold. The first result
* is the intersection, second is the difference. This is more efficient than
* doing them separately.
*
* @param cutter
*/
split(cutter: Manifold): Manifold[];
/**
* Convenient version of Split() for a half-space.
*
* @param normal This vector is normal to the cutting plane and its length
* does
* not matter. The first result is in the direction of this vector, the second
* result is on the opposite side.
* @param originOffset The distance of the plane from the origin in the
* direction of the normal vector.
*/
splitByPlane(normal: Vec3, originOffset: number): Manifold[];
/**
* Removes everything behind the given half-space plane.
*
* @param normal This vector is normal to the cutting plane and its length
* does not matter. The result is in the direction of this vector from the
* plane.
* @param originOffset The distance of the plane from the origin in the
* direction of the normal vector.
*/
trimByPlane(normal: Vec3, originOffset: number): Manifold;
// Topological Operations
/**
* Constructs a new manifold from a list of other manifolds. This is a purely
* topological operation, so care should be taken to avoid creating
* overlapping results. It is the inverse operation of Decompose().
*
* @param manifolds A list of Manifolds to lazy-union together.
*/
static compose(manifolds: Manifold[]): Manifold;
/**
* This operation returns a vector of Manifolds that are topologically
* disconnected. If everything is connected, the vector is length one,
* containing a copy of the original. It is the inverse operation of
* Compose().
*/
decompose(): Manifold[];
// Property Access
/**
* Does the Manifold have any triangles?
*/
isEmpty(): boolean;
/**
* The number of vertices in the Manifold.
*/
numVert(): number;
/**
* The number of triangles in the Manifold.
*/
numTri(): number;
/**
* The number of edges in the Manifold.
*/
numEdge(): number;
/**
* The number of properties per vertex in the Manifold.
*/
numProp(): number;
/**
* The number of property vertices in the Manifold. This will always be >=
* numVert, as some physical vertices may be duplicated to account for
* different properties on different neighboring triangles.
*/
numPropVert(): number
/**
* Returns the axis-aligned bounding box of all the Manifold's vertices.
*/
boundingBox(): Box;
/**
* Returns the precision of this Manifold's vertices, which tracks the
* approximate rounding error over all the transforms and operations that have
* led to this state. Any triangles that are colinear within this precision
* are considered degenerate and removed. This is the value of &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;
// Export
/**
* Returns a Mesh that is designed to easily push into a renderer, including
* all interleaved vertex properties that may have been input. It also
* includes relations to all the input meshes that form a part of this result
* and the transforms applied to each.
*
* @param normalIdx If the original Mesh inputs that formed this manifold had
* properties corresponding to normal vectors, you can specify which property
* channels these are (x, y, z), which will cause this output Mesh to
* automatically update these normals according to the applied transforms and
* front/back side. Each channel must be >= 3 and < numProp, and all original
* Meshes must use the same channels for their normals.
*/
getMesh(normalIdx?: Vec3): Mesh;
// ID Management
/**
* If you copy a manifold, but you want this new copy to have new properties
* (e.g. a different UV mapping), you can reset its IDs to a new original,
* meaning it will now be referenced by its descendants instead of the meshes
* it was built from, allowing you to differentiate the copies when applying
* your properties to the final result.
*
* This function also condenses all coplanar faces in the relation, and
* collapses those edges. If you want to have inconsistent properties across
* these faces, meaning you want to preserve some of these edges, you should
* instead call GetMesh(), calculate your properties and use these to
* construct a new manifold.
*/
asOriginal(): Manifold;
/**
* If this mesh is an original, this returns its ID that can be referenced
* by product manifolds. If this manifold is a product, this
* returns -1.
*/
originalID(): number;
/**
* Returns the first of n sequential new unique mesh IDs for marking sets of
* triangles that can be looked up after further operations. Assign to
* Mesh.runOriginalID vector.
*/
static reserveIDs(count: number): number;
// Memory
/**
* Frees the WASM memory of this Manifold, since these cannot be
* garbage-collected automatically.
*/
delete(): void;
}
export class Mesh {
constructor(options: {
numProp: number,
vertProperties: Float32Array,
triVerts: Uint32Array,
mergeFromVert?: Uint32Array,
mergeToVert?: Uint32Array,
runIndex?: Uint32Array,
runOriginalID?: Uint32Array,
runTransform?: Float32Array,
faceID?: Uint32Array,
halfedgeTangent?: Float32Array
});
numProp: number;
vertProperties: Float32Array;
triVerts: Uint32Array;
mergeFromVert?: Uint32Array;
mergeToVert?: Uint32Array;
runIndex?: Uint32Array;
runOriginalID?: Uint32Array;
runTransform?: Float32Array;
faceID?: Uint32Array;
halfedgeTangent?: Float32Array;
get numTri(): number;
get numVert(): number;
get numRun(): number;
merge(): boolean;
verts(tri: number): SealedUint32Array<3>;
position(vert: number): SealedFloat32Array<3>;
extras(vert: number): Float32Array;
tangent(halfedge: number): SealedFloat32Array<4>;
transform(run: number): Mat4;
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export interface SealedUint32Array<N extends number> extends Uint32Array {
length: N;
}
export interface SealedFloat32Array<N extends number> extends Float32Array {
length: N;
}
export type Vec2 = [number, number];
export type Vec3 = [number, number, number];
// 4x4 matrix stored in column-major order
export type Mat4 = [
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
];
export type SimplePolygon = Vec2[];
export type Polygons = SimplePolygon|SimplePolygon[];
export type Rect = {
min: Vec2,
max: Vec2
};
export type Box = {
min: Vec3,
max: Vec3
};
export type Smoothness = {
halfedge: number,
smoothness: number
};
export type Properties = {
surfaceArea: number,
volume: number
};
export type FillRule = 'EvenOdd'|'NonZero'|'Positive'|'Negative'
export type JoinType = 'Square'|'Round'|'Miter'
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as T from './manifold-encapsulated-types';
export * from './manifold-global-types';
export type CrossSection = T.CrossSection;
export type Manifold = T.Manifold;
export type Mesh = T.Mesh;
export interface ManifoldToplevel {
CrossSection: typeof T.CrossSection;
Manifold: typeof T.Manifold;
Mesh: typeof T.Mesh;
triangulate: typeof T.triangulate;
setMinCircularAngle: typeof T.setMinCircularAngle;
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
setCircularSegments: typeof T.setCircularSegments;
getCircularSegments: typeof T.getCircularSegments;
setup: () => void;
}
export default function Module(config?: {locateFile: () => string}):
Promise<ManifoldToplevel>;
+13
View File
@@ -0,0 +1,13 @@
{
"name": "manifold-3d",
"version": "2.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "manifold-3d",
"version": "2.1.0",
"license": "Apache-2.0"
}
}
}
+39
View File
@@ -0,0 +1,39 @@
{
"name": "manifold-3d",
"version": "2.1.0",
"description": "Geometry library for topological robustness",
"main": "manifold.js",
"files": [
"manifold.js",
"manifold.wasm",
"manifold.d.ts",
"manifold-encapsulated-types.d.ts",
"manifold-global-types.d.ts"
],
"typings": "manifold.d.ts",
"types": "manifold.d.ts",
"type": "module",
"repository": {
"type": "git",
"url": "git+https://github.com/elalish/manifold.git"
},
"keywords": [
"3D",
"mesh",
"manifold",
"geometry",
"solid",
"CAD",
"triangle",
"SDF"
],
"contributors": [
"Emmett Lalish <elalish>",
"Chun Kit LAM <pca006132>"
],
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/elalish/manifold/issues"
},
"homepage": "https://github.com/elalish/manifold#readme"
}
-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.
+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.
project(extras)
add_executable(perfTest perf_test.cpp)
target_link_libraries(perfTest manifold)
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(perfTest PUBLIC cxx_std_17)
add_executable(largeSceneTest large_scene_test.cpp)
target_link_libraries(largeSceneTest manifold)
target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(largeSceneTest PUBLIC cxx_std_17)
if(MANIFOLD_EXPORT)
add_executable(convertFile convert_file.cpp)
target_link_libraries(convertFile manifold meshIO)
target_compile_options(convertFile PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(convertFile PUBLIC cxx_std_17)
endif()
if(BUILD_TEST_CGAL)
add_executable(perfTestCGAL perf_test_cgal.cpp)
find_package(CGAL REQUIRED COMPONENTS Core)
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
endif()
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include "manifold.h"
#include "meshIO.h"
using namespace manifold;
/*
*/
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Specify an input filename." << std::endl;
return 1;
}
manifold::ManifoldParams().verbose = true;
const std::string filename(argv[1]);
MeshGL input = ImportMesh(filename);
std::cout << input.NumVert() << " vertices, " << input.NumTri()
<< " triangles" << std::endl;
if (input.Merge())
std::cout << filename << " is not manifold, attempting to fix."
<< std::endl;
const Manifold manifold(input);
if (manifold.Status() != Manifold::Error::NoError) {
std::cout << "Could not make a valid manifold, error: "
<< (int)manifold.Status() << std::endl;
return 2;
}
const std::vector<Manifold> parts = manifold.Decompose();
std::cout << parts.size() << " objects:" << std::endl;
for (const Manifold& part : parts) {
auto prop = part.GetProperties();
std::cout << part.NumVert() << " vertices, " << part.NumTri()
<< " triangles, volume = " << prop.volume
<< ", surface area = " << prop.surfaceArea << std::endl;
}
if (argc == 3) {
std::string outName = argv[2];
std::cout << "Writing " << outName << std::endl;
ExportMesh(outName, manifold.GetMeshGL(), {});
}
return 0;
}
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2020 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <iostream>
#include "manifold.h"
using namespace manifold;
/*
Build & execute with the following command:
( mkdir -p build && cd build && \
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_PAR=TBB .. && \
make -j && \
time ./extras/largeSceneTest 50 )
*/
int main(int argc, char **argv) {
int n = 20;
if (argc == 2) n = atoi(argv[1]);
std::cout << "n = " << n << std::endl;
auto start = std::chrono::high_resolution_clock::now();
Manifold scene;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 0; k < n; ++k) {
if (i == 0 && j == 0 && k == 0) continue;
Manifold sphere = Manifold::Sphere(1).Translate(glm::vec3(i, j, k));
scene = scene.Boolean(sphere, OpType::Add);
}
}
}
scene.NumTri();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << "nTri = " << scene.NumTri() << ", time = " << elapsed.count()
<< " sec" << std::endl;
}
+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": 1689008574,
"narHash": "sha256-VFMgyHDiqsGDkRg73alv6OdHJAqhybryWHv77bSCGIw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "4a729ce4b1fe5ec4fffc71c67c96aa5184ebb462",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+130
View File
@@ -0,0 +1,130 @@
{
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
manifold =
{ parallel-backend ? "none"
, cuda-support ? false
, doCheck ? true
, build-tools ? [ ]
, ...
}: pkgs.stdenv.mkDerivation {
inherit doCheck;
pname =
if cuda-support then
"manifold-${parallel-backend}-cuda"
else
"manifold-${parallel-backend}";
version = "beta";
src = self;
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages (ps: with ps; [ trimesh ])) ]) ++ build-tools ++
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
cmakeFlags = [
"-DMANIFOLD_PYBIND=ON"
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
];
checkPhase = ''
cd test
./manifold_test
cd ../../
PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python python3 bindings/python/examples/run_all.py
cd build
'';
installPhase = ''
mkdir -p $out
cp src/manifold/libmanifold.a $out/
cp extras/perfTest $out
cp bindings/python/manifold3d* $out
'';
};
parallelBackends = [
{ parallel-backend = "none"; }
{
parallel-backend = "omp";
build-tools = [ pkgs.llvmPackages_13.openmp ];
}
{
parallel-backend = "tbb";
build-tools = with pkgs; [ tbb pkg-config ];
}
];
buildMatrix = with pkgs; with lib; lists.flatten (map
(env: map
(x: x // env)
parallelBackends) [
{ cuda-support = false; }
{
cuda-support = true;
}
]);
devShell = { additional ? [ ] }: pkgs.mkShell {
buildInputs = with pkgs; [
cmake
llvmPackages_13.openmp
clang-tools
clang_13
emscripten
tbb
lcov
] ++ additional;
};
in
{
packages = (builtins.listToAttrs
(map
(x: {
name = "manifold-" + x.parallel-backend + (if
x.cuda-support then "-cuda" else "");
value = manifold x;
})
buildMatrix)) // {
manifold-js = pkgs.buildEmscriptenPackage {
name = "manifold-js";
version = "beta";
src = self;
nativeBuildInputs = (with pkgs; [ cmake python39 ]);
buildInputs = [ pkgs.nodejs ];
configurePhase = ''
mkdir -p .emscriptencache
export EM_CACHE=$(pwd)/.emscriptencache
mkdir build
cd build
mkdir cache
export EM_CACHE=$(pwd)/cache
emcmake cmake -DCMAKE_BUILD_TYPE=Release ..
'';
buildPhase = ''
emmake make -j''${NIX_BUILD_CORES}
'';
checkPhase = ''
cd test
node manifold_test.js
cd ../
'';
installPhase = ''
mkdir -p $out
cp {extras,wasm}/*.js $out/
cp {extras,wasm}/*.wasm $out/
'';
};
};
devShell = devShell { };
devShells.cuda = devShell {
additional = with pkgs.cudaPackages; [
cuda_nvcc
cuda_cudart
cuda_cccl
];
};
}
);
}
-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
)
-121
View File
@@ -1,121 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <functional>
#include <memory>
#include "structs.h"
namespace manifold {
class Manifold {
public:
// Creation
Manifold();
Manifold(
const Mesh&,
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
const std::vector<float>& properties = std::vector<float>(),
const std::vector<float>& propertyTolerance = std::vector<float>());
static Manifold Smooth(const Mesh&,
const std::vector<Smoothness>& sharpenedEdges = {});
static Manifold Tetrahedron();
static Manifold Cube(glm::vec3 size = glm::vec3(1.0f), bool center = false);
static Manifold Cylinder(float height, float radiusLow,
float radiusHigh = -1.0f, int circularSegments = 0,
bool center = false);
static Manifold Sphere(float radius, int circularSegments = 0);
static Manifold Extrude(Polygons crossSection, float height,
int nDivisions = 0, float twistDegrees = 0.0f,
glm::vec2 scaleTop = glm::vec2(1.0f));
static Manifold Revolve(const Polygons& crossSection,
int circularSegments = 0);
// Topological
static Manifold Compose(const std::vector<Manifold>&);
std::vector<Manifold> Decompose() const;
// Defaults for construction
static void SetMinCircularAngle(float degrees);
static void SetMinCircularEdgeLength(float length);
static void SetCircularSegments(int number);
static int GetCircularSegments(float radius);
// Information
Mesh GetMesh() const;
bool IsEmpty() const;
int NumVert() const;
int NumEdge() const;
int NumTri() const;
Box BoundingBox() const;
float Precision() const;
int Genus() const;
Properties GetProperties() const;
Curvature GetCurvature() const;
// Relation
MeshRelation GetMeshRelation() const;
std::vector<int> GetMeshIDs() const;
int SetAsOriginal();
static std::vector<int> MeshID2Original();
// Modification
Manifold& Translate(glm::vec3);
Manifold& Scale(glm::vec3);
Manifold& Rotate(float xDegrees, float yDegrees = 0.0f,
float zDegrees = 0.0f);
Manifold& Transform(const glm::mat4x3&);
Manifold& Warp(std::function<void(glm::vec3&)>);
Manifold& Refine(int);
// Manifold RefineToLength(float);
// Manifold RefineToPrecision(float);
// Boolean
enum class OpType { ADD, SUBTRACT, INTERSECT };
Manifold Boolean(const Manifold& second, OpType op) const;
// Boolean operation shorthand
Manifold operator+(const Manifold&) const; // ADD (Union)
Manifold& operator+=(const Manifold&);
Manifold operator-(const Manifold&) const; // SUBTRACT (Difference)
Manifold& operator-=(const Manifold&);
Manifold operator^(const Manifold&) const; // INTERSECT
Manifold& operator^=(const Manifold&);
std::pair<Manifold, Manifold> Split(const Manifold&) const;
std::pair<Manifold, Manifold> SplitByPlane(glm::vec3 normal,
float originOffset) const;
Manifold TrimByPlane(glm::vec3 normal, float originOffset) const;
// Testing hooks
bool IsManifold() const;
bool MatchesTriNormals() const;
int NumDegenerateTris() const;
int NumOverlaps(const Manifold& second) const;
~Manifold();
Manifold(const Manifold& other);
Manifold& operator=(const Manifold& other);
Manifold(Manifold&&) noexcept;
Manifold& operator=(Manifold&&) noexcept;
struct Impl;
private:
std::unique_ptr<Impl> pImpl_;
static int circularSegments_;
static float circularAngle_;
static float circularEdgeLength_;
};
} // namespace manifold
-469
View File
@@ -1,469 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <thrust/sequence.h>
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include "impl.cuh"
#include "polygon.h"
namespace {
using namespace manifold;
using namespace thrust::placeholders;
struct ToSphere {
float length;
__host__ __device__ void operator()(glm::vec3& v) {
v = glm::cos(glm::half_pi<float>() * (1.0f - v));
v = length * glm::normalize(v);
if (isnan(v.x)) v = glm::vec3(0.0);
}
};
struct UpdateTriBary {
const int nextBary;
__host__ __device__ BaryRef operator()(BaryRef ref) {
for (int i : {0, 1, 2})
if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary;
return ref;
}
};
struct UpdateHalfedge {
const int nextVert;
const int nextEdge;
const int nextFace;
__host__ __device__ Halfedge operator()(Halfedge edge) {
edge.startVert += nextVert;
edge.endVert += nextVert;
edge.pairedHalfedge += nextEdge;
edge.face += nextFace;
return edge;
}
};
int ConnectedComponents(VecDH<int>& components, int numVert,
const VecDH<Halfedge>& halfedges) {
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> graph(
numVert);
for (int i = 0; i < halfedges.size(); ++i) {
const Halfedge halfedge = halfedges.H()[i];
if (halfedge.IsForward()) {
boost::add_edge(halfedge.startVert, halfedge.endVert, graph);
}
}
components.resize(numVert);
int numComponent = boost::connected_components(graph, components.H().data());
return numComponent;
}
struct Equals {
int val;
__host__ __device__ bool operator()(int x) { return x == val; }
};
struct RemoveFace {
const Halfedge* halfedge;
const int* vertLabel;
const int keepLabel;
__host__ __device__ bool operator()(int face) {
return vertLabel[halfedge[3 * face].startVert] != keepLabel;
}
};
} // namespace
namespace manifold {
/*
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangnets with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
* By default, every edge is calculated for maximum smoothness (very much
* approximately), attempting to minimize the maximum mean curvature magnitude.
* No higher-order derivatives are considered, as the interpolation is
* independent per triangle, only sharing constraints on their boundaries.
*
* If desired, you can supply a vector of sharpened halfedges, which should in
* general be a small subset of all halfedges. Order of entries doesn't matter,
* as each one specifies the desired smoothness (between zero and one, with one
* the default for all unspecified halfedges) and the halfedge index (3 *
* triangle index + [0,1,2] where 0 is the edge between triVert 0 and 1, etc).
*
* At a smoothness value of zero, a sharp crease is made. The smoothness is
* interpolated along each edge, so the specified value should be thought of as
* an average. Where exactly two sharpened edges meet at a vertex, their
* tangents are rotated to be colinear so that the sharpened edge can be
* continuous. Vertices with only one sharpened edge are completely smooth,
* allowing sharpened edges to smoothly vanish at termination. A single vertex
* can be sharpened by sharping all edges that are incident on it, allowing
* cones to be formed.
*/
Manifold Manifold::Smooth(const Mesh& mesh,
const std::vector<Smoothness>& sharpenedEdges) {
ALWAYS_ASSERT(
mesh.halfedgeTangent.empty(), std::runtime_error,
"when supplying tangents, the normal constructor should be used "
"rather than Smooth().");
Manifold manifold(mesh);
manifold.pImpl_->CreateTangents(sharpenedEdges);
return manifold;
}
/**
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
* and the rest at similarly symmetric points.
*/
Manifold Manifold::Tetrahedron() {
Manifold tetrahedron;
tetrahedron.pImpl_ = std::make_unique<Impl>(Impl::Shape::TETRAHEDRON);
return tetrahedron;
}
/**
* Constructs a unit cube (edge lengths all one), by default in the first
* octant, touching the origin. Set center to true to shift the center to the
* origin.
*/
Manifold Manifold::Cube(glm::vec3 size, bool center) {
Manifold cube;
cube.pImpl_ = std::make_unique<Impl>(Impl::Shape::CUBE);
cube.Scale(size);
if (center) cube.Translate(-size / 2.0f);
return cube;
}
/**
* A convenience constructor for the common case of extruding a circle. Can also
* form cones if both radii are specified. Set center to true to center the
* manifold vertically on the origin (default places the bottom on the origin).
*/
Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
int circularSegments, bool center) {
float scale = radiusHigh >= 0.0f ? radiusHigh / radiusLow : 1.0f;
float radius = max(radiusLow, radiusHigh);
int n = circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
Polygons circle(1);
float dPhi = 360.0f / n;
for (int i = 0; i < n; ++i) {
circle[0].push_back(
{radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)), 0});
}
Manifold cylinder =
Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale));
if (center) cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f));
return cylinder;
}
/**
* Constructs a sphere of a given radius and number of segments along its
* diameter. This number will always be rounded up to the nearest factor of
* four, as this sphere is constructed by refining an octahedron. This means
* there are a circle of vertices on all three of the axis planes.
*/
Manifold Manifold::Sphere(float radius, int circularSegments) {
int n = circularSegments > 0 ? (circularSegments + 3) / 4
: GetCircularSegments(radius) / 4;
Manifold sphere;
sphere.pImpl_ = std::make_unique<Impl>(Impl::Shape::OCTAHEDRON);
sphere.pImpl_->Subdivide(n);
thrust::for_each_n(sphere.pImpl_->vertPos_.beginD(), sphere.NumVert(),
ToSphere({radius}));
sphere.pImpl_->Finish();
// Ignore preceding octahedron.
sphere.pImpl_->ReinitializeReference();
return sphere;
}
/**
* Constructs a manifold from a set of polygons by extruding them along the
* Z-axis. The overall height and the scale at the top (X and Y independently)
* can be specified, as can a twist, to be applied linearly. In the case of
* twist, it can also be helpful to specify nDivisions, which specifies the
* quantization of the triangles vertically. If the scale is {0,0}, a pure cone
* is formed with only a single vertex at the top.
*/
Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
float twistDegrees, glm::vec2 scaleTop) {
ALWAYS_ASSERT(scaleTop.x >= 0 && scaleTop.y >= 0, userErr,
"scale values cannot be negative");
Manifold extrusion;
++nDivisions;
auto& vertPos = extrusion.pImpl_->vertPos_.H();
VecDH<glm::ivec3> triVertsDH;
auto& triVerts = triVertsDH.H();
int nCrossSection = 0;
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
int idx = 0;
for (auto& poly : crossSection) {
nCrossSection += poly.size();
for (PolyVert& polyVert : poly) {
vertPos.push_back({polyVert.pos.x, polyVert.pos.y, 0.0f});
polyVert.idx = idx++;
}
}
for (int i = 1; i < nDivisions + 1; ++i) {
float alpha = i / float(nDivisions);
float phi = alpha * twistDegrees;
glm::mat2 transform(cosd(phi), sind(phi), -sind(phi), cosd(phi));
glm::vec2 scale = glm::mix(glm::vec2(1.0f), scaleTop, alpha);
transform = transform * glm::mat2(scale.x, 0.0f, 0.0f, scale.y);
int j = 0;
int idx = 0;
for (const auto& poly : crossSection) {
for (int vert = 0; vert < poly.size(); ++vert) {
int offset = idx + nCrossSection * i;
int thisVert = vert + offset;
int lastVert = (vert == 0 ? poly.size() : vert) - 1 + offset;
if (i == nDivisions && isCone) {
triVerts.push_back({nCrossSection * i + j, lastVert - nCrossSection,
thisVert - nCrossSection});
} else {
glm::vec2 pos = transform * poly[vert].pos;
vertPos.push_back({pos.x, pos.y, height * alpha});
triVerts.push_back({thisVert, lastVert, thisVert - nCrossSection});
triVerts.push_back(
{lastVert, lastVert - nCrossSection, thisVert - nCrossSection});
}
}
++j;
idx += poly.size();
}
}
if (isCone)
for (int j = 0; j < crossSection.size(); ++j) // Duplicate vertex for Genus
vertPos.push_back({0.0f, 0.0f, height});
std::vector<glm::ivec3> top = Triangulate(crossSection);
for (const glm::ivec3& tri : top) {
triVerts.push_back({tri[0], tri[2], tri[1]});
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
}
extrusion.pImpl_->CreateHalfedges(triVertsDH);
extrusion.pImpl_->Finish();
extrusion.pImpl_->InitializeNewReference();
return extrusion;
}
/**
* Constructs a manifold from a set of polygons by revolving this cross-section
* around its Y-axis and then setting this as the Z-axis of the resulting
* manifold. If the polygons cross the Y-axis, only the part on the positive X
* side is used. Geometrically valid input will result in geometrically valid
* output.
*/
Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
float radius = 0.0f;
for (const auto& poly : crossSection) {
for (const auto& vert : poly) {
radius = max(radius, vert.pos.x);
}
}
int nDivisions =
circularSegments > 2 ? circularSegments : GetCircularSegments(radius);
Manifold revoloid;
auto& vertPos = revoloid.pImpl_->vertPos_.H();
VecDH<glm::ivec3> triVertsDH;
auto& triVerts = triVertsDH.H();
float dPhi = 360.0f / nDivisions;
for (const auto& poly : crossSection) {
int start = -1;
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
if (poly[polyVert].pos.x <= 0) {
start = polyVert;
break;
}
}
if (start == -1) { // poly all positive
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
int startVert = vertPos.size();
int lastStart =
startVert +
(polyVert == 0 ? nDivisions * (poly.size() - 1) : -nDivisions);
for (int slice = 0; slice < nDivisions; ++slice) {
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
float phi = slice * dPhi;
glm::vec2 pos = poly[polyVert].pos;
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
triVerts.push_back({startVert + slice, startVert + lastSlice,
lastStart + lastSlice});
triVerts.push_back(
{lastStart + lastSlice, lastStart + slice, startVert + slice});
}
}
} else { // poly crosses zero
int polyVert = start;
glm::vec2 pos = poly[polyVert].pos;
do {
glm::vec2 lastPos = pos;
polyVert = (polyVert + 1) % poly.size();
pos = poly[polyVert].pos;
if (pos.x > 0) {
if (lastPos.x <= 0) {
float a = pos.x / (pos.x - lastPos.x);
vertPos.push_back({0.0f, 0.0f, glm::mix(pos.y, lastPos.y, a)});
}
int startVert = vertPos.size();
for (int slice = 0; slice < nDivisions; ++slice) {
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
float phi = slice * dPhi;
glm::vec2 pos = poly[polyVert].pos;
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
if (lastPos.x > 0) {
triVerts.push_back({startVert + slice, startVert + lastSlice,
startVert - nDivisions + lastSlice});
triVerts.push_back({startVert - nDivisions + lastSlice,
startVert - nDivisions + slice,
startVert + slice});
} else {
triVerts.push_back(
{startVert - 1, startVert + slice, startVert + lastSlice});
}
}
} else if (lastPos.x > 0) {
int startVert = vertPos.size();
float a = pos.x / (pos.x - lastPos.x);
vertPos.push_back({0.0f, 0.0f, glm::mix(pos.y, lastPos.y, a)});
for (int slice = 0; slice < nDivisions; ++slice) {
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
triVerts.push_back({startVert, startVert - nDivisions + lastSlice,
startVert - nDivisions + slice});
}
}
} while (polyVert != start);
}
}
revoloid.pImpl_->CreateHalfedges(triVertsDH);
revoloid.pImpl_->Finish();
revoloid.pImpl_->InitializeNewReference();
return revoloid;
}
/**
* Constructs a new manifold from a vector of other manifolds. This is a purely
* topological operation, so care should be taken to avoid creating
* geometrically-invalid results.
*/
Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
int numVert = 0;
int numEdge = 0;
int numTri = 0;
int numBary = 0;
for (const Manifold& manifold : manifolds) {
numVert += manifold.NumVert();
numEdge += manifold.NumEdge();
numTri += manifold.NumTri();
numBary += manifold.pImpl_->meshRelation_.barycentric.size();
}
Manifold out;
Impl& combined = *(out.pImpl_);
combined.vertPos_.resize(numVert);
combined.halfedge_.resize(2 * numEdge);
combined.faceNormal_.resize(numTri);
combined.halfedgeTangent_.resize(2 * numEdge);
combined.meshRelation_.barycentric.resize(numBary);
combined.meshRelation_.triBary.resize(numTri);
int nextVert = 0;
int nextEdge = 0;
int nextTri = 0;
int nextBary = 0;
for (const Manifold& manifold : manifolds) {
const Impl& impl = *(manifold.pImpl_);
impl.ApplyTransform();
thrust::copy(impl.vertPos_.beginD(), impl.vertPos_.endD(),
combined.vertPos_.beginD() + nextVert);
thrust::copy(impl.faceNormal_.beginD(), impl.faceNormal_.endD(),
combined.faceNormal_.beginD() + nextTri);
thrust::copy(impl.halfedgeTangent_.beginD(), impl.halfedgeTangent_.endD(),
combined.halfedgeTangent_.beginD() + nextEdge);
thrust::copy(impl.meshRelation_.barycentric.beginD(),
impl.meshRelation_.barycentric.endD(),
combined.meshRelation_.barycentric.beginD() + nextBary);
thrust::transform(impl.meshRelation_.triBary.beginD(),
impl.meshRelation_.triBary.endD(),
combined.meshRelation_.triBary.beginD() + nextTri,
UpdateTriBary({nextBary}));
thrust::transform(impl.halfedge_.beginD(), impl.halfedge_.endD(),
combined.halfedge_.beginD() + nextEdge,
UpdateHalfedge({nextVert, nextEdge, nextTri}));
nextVert += manifold.NumVert();
nextEdge += 2 * manifold.NumEdge();
nextTri += manifold.NumTri();
nextBary += impl.meshRelation_.barycentric.size();
}
combined.DuplicateMeshIDs();
combined.Finish();
return out;
}
/**
* This operation returns a copy of this manifold, but as a vector of meshes
* that are topologically disconnected.
*/
std::vector<Manifold> Manifold::Decompose() const {
VecDH<int> vertLabel;
int numLabel = ConnectedComponents(vertLabel, NumVert(), pImpl_->halfedge_);
if (numLabel == 1) {
std::vector<Manifold> meshes(1);
meshes[0] = *this;
return meshes;
}
std::vector<Manifold> meshes(numLabel);
for (int i = 0; i < numLabel; ++i) {
meshes[i].pImpl_->vertPos_.resize(NumVert());
VecDH<int> vertNew2Old(NumVert());
int nVert =
thrust::copy_if(
zip(pImpl_->vertPos_.beginD(), countAt(0)),
zip(pImpl_->vertPos_.endD(), countAt(NumVert())),
vertLabel.beginD(),
zip(meshes[i].pImpl_->vertPos_.beginD(), vertNew2Old.beginD()),
Equals({i})) -
zip(meshes[i].pImpl_->vertPos_.beginD(), countAt(0));
meshes[i].pImpl_->vertPos_.resize(nVert);
VecDH<int> faceNew2Old(NumTri());
thrust::sequence(faceNew2Old.beginD(), faceNew2Old.endD());
int nFace =
thrust::remove_if(
faceNew2Old.beginD(), faceNew2Old.endD(),
RemoveFace({pImpl_->halfedge_.cptrD(), vertLabel.cptrD(), i})) -
faceNew2Old.beginD();
faceNew2Old.resize(nFace);
meshes[i].pImpl_->GatherFaces(*pImpl_, faceNew2Old);
meshes[i].pImpl_->ReindexVerts(vertNew2Old, pImpl_->NumVert());
meshes[i].pImpl_->Finish();
meshes[i].pImpl_->transform_ = pImpl_->transform_;
}
return meshes;
}
} // namespace manifold
-411
View File
@@ -1,411 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "impl.cuh"
namespace {
using namespace manifold;
__host__ __device__ glm::ivec3 TriOf(int edge) {
glm::ivec3 triEdge;
triEdge[0] = edge;
triEdge[1] = NextHalfedge(triEdge[0]);
triEdge[2] = NextHalfedge(triEdge[1]);
return triEdge;
}
__host__ __device__ bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
const glm::vec2 e[3] = {v1 - v0, v2 - v1, v0 - v2};
float l[3];
for (int i : {0, 1, 2}) l[i] = glm::dot(e[i], e[i]);
return l[0] > l[1] && l[0] > l[2];
}
struct ShortEdge {
const Halfedge* halfedge;
const glm::vec3* vertPos;
const float precision;
__host__ __device__ bool operator()(int edge) {
if (halfedge[edge].pairedHalfedge < 0) return false;
// Flag short edges
const glm::vec3 delta =
vertPos[halfedge[edge].endVert] - vertPos[halfedge[edge].startVert];
return glm::dot(delta, delta) < precision * precision;
}
};
struct FlagEdge {
const Halfedge* halfedge;
const BaryRef* triBary;
__host__ __device__ bool operator()(int edge) {
if (halfedge[edge].pairedHalfedge < 0) return false;
// Flag redundant edges - those where the startVert is surrounded by only
// two original triangles.
const BaryRef ref0 = triBary[edge / 3];
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
const BaryRef ref1 = triBary[current / 3];
while (current != edge) {
current = NextHalfedge(halfedge[current].pairedHalfedge);
int tri = current / 3;
const BaryRef ref = triBary[tri];
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
return false;
}
return true;
}
};
struct SwappableEdge {
const Halfedge* halfedge;
const glm::vec3* vertPos;
const glm::vec3* triNormal;
const float precision;
__host__ __device__ bool operator()(int edge) {
if (halfedge[edge].pairedHalfedge < 0) return false;
int tri = halfedge[edge].face;
glm::ivec3 triedge = TriOf(edge);
glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[tri]);
glm::vec2 v[3];
for (int i : {0, 1, 2})
v[i] = projection * vertPos[halfedge[triedge[i]].startVert];
// if (CCW(v[0], v[1], v[2], precision) < 0) printf("tri %d is CW!\n", tri);
if (CCW(v[0], v[1], v[2], precision) > 0 || !Is01Longest(v[0], v[1], v[2]))
return false;
// Switch to neighbor's projection.
edge = halfedge[edge].pairedHalfedge;
tri = halfedge[edge].face;
triedge = TriOf(edge);
projection = GetAxisAlignedProjection(triNormal[tri]);
for (int i : {0, 1, 2})
v[i] = projection * vertPos[halfedge[triedge[i]].startVert];
return CCW(v[0], v[1], v[2], precision) > 0 ||
Is01Longest(v[0], v[1], v[2]);
}
};
} // namespace
namespace manifold {
/**
* Collapses degenerate triangles by removing edges shorter than precision_ and
* any edge that is preceeded by an edge that joins the same two face relations.
* It also performs edge swaps on the long edges of degenerate triangles, though
* there are some configurations of degenerates that cannot be removed this way.
*
* Rather than actually removing the edges, this step merely marks them for
* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.
*/
void Manifold::Impl::CollapseDegenerates() {
VecDH<int> flaggedEdges(halfedge_.size());
int numFlagged =
thrust::copy_if(
countAt(0), countAt(halfedge_.size()), flaggedEdges.beginD(),
ShortEdge({halfedge_.cptrD(), vertPos_.cptrD(), precision_})) -
flaggedEdges.beginD();
flaggedEdges.resize(numFlagged);
for (const int edge : flaggedEdges.H()) CollapseEdge(edge);
flaggedEdges.resize(halfedge_.size());
numFlagged =
thrust::copy_if(
countAt(0), countAt(halfedge_.size()), flaggedEdges.beginD(),
FlagEdge({halfedge_.cptrD(), meshRelation_.triBary.cptrD()})) -
flaggedEdges.beginD();
flaggedEdges.resize(numFlagged);
for (const int edge : flaggedEdges.H()) CollapseEdge(edge);
flaggedEdges.resize(halfedge_.size());
numFlagged = thrust::copy_if(
countAt(0), countAt(halfedge_.size()), flaggedEdges.beginD(),
SwappableEdge({halfedge_.cptrD(), vertPos_.cptrD(),
faceNormal_.cptrD(), precision_})) -
flaggedEdges.beginD();
flaggedEdges.resize(numFlagged);
for (const int edge : flaggedEdges.H()) {
RecursiveEdgeSwap(edge);
}
}
void Manifold::Impl::PairUp(int edge0, int edge1) {
VecH<Halfedge>& halfedge = halfedge_.H();
halfedge[edge0].pairedHalfedge = edge1;
halfedge[edge1].pairedHalfedge = edge0;
}
// Traverses CW around startEdge.endVert from startEdge to endEdge
// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point
// to vert instead.
void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {
VecH<Halfedge>& halfedge = halfedge_.H();
while (startEdge != endEdge) {
halfedge[startEdge].endVert = vert;
startEdge = NextHalfedge(startEdge);
halfedge[startEdge].startVert = vert;
startEdge = halfedge[startEdge].pairedHalfedge;
}
}
// In the event that the edge collapse would create a non-manifold edge,
// instead we duplicate the two verts and attach the manifolds the other way
// across this edge.
void Manifold::Impl::FormLoop(int current, int end) {
VecH<Halfedge>& halfedge = halfedge_.H();
VecH<glm::vec3>& vertPos = vertPos_.H();
int startVert = vertPos.size();
vertPos.push_back(vertPos[halfedge[current].startVert]);
int endVert = vertPos.size();
vertPos.push_back(vertPos[halfedge[current].endVert]);
int oldMatch = halfedge[current].pairedHalfedge;
int newMatch = halfedge[end].pairedHalfedge;
UpdateVert(startVert, oldMatch, newMatch);
UpdateVert(endVert, end, current);
halfedge[current].pairedHalfedge = newMatch;
halfedge[newMatch].pairedHalfedge = current;
halfedge[end].pairedHalfedge = oldMatch;
halfedge[oldMatch].pairedHalfedge = end;
RemoveIfFolded(end);
}
void Manifold::Impl::CollapseTri(const glm::ivec3& triEdge) {
VecH<Halfedge>& halfedge = halfedge_.H();
int pair1 = halfedge[triEdge[1]].pairedHalfedge;
int pair2 = halfedge[triEdge[2]].pairedHalfedge;
halfedge[pair1].pairedHalfedge = pair2;
halfedge[pair2].pairedHalfedge = pair1;
for (int i : {0, 1, 2}) {
halfedge[triEdge[i]] = {-1, -1, -1, -1};
}
}
void Manifold::Impl::RemoveIfFolded(int edge) {
VecH<Halfedge>& halfedge = halfedge_.H();
VecH<glm::vec3>& vertPos = vertPos_.H();
const glm::ivec3 tri0edge = TriOf(edge);
const glm::ivec3 tri1edge = TriOf(halfedge[edge].pairedHalfedge);
if (halfedge[tri0edge[1]].endVert == halfedge[tri1edge[1]].endVert) {
for (int i : {0, 1, 2}) {
vertPos[halfedge[tri0edge[i]].startVert] = glm::vec3(0.0f / 0.0f);
halfedge[tri0edge[i]] = {-1, -1, -1, -1};
halfedge[tri1edge[i]] = {-1, -1, -1, -1};
}
}
}
void Manifold::Impl::CollapseEdge(const int edge) {
VecH<Halfedge>& halfedge = halfedge_.H();
VecH<glm::vec3>& vertPos = vertPos_.H();
VecH<glm::vec3>& triNormal = faceNormal_.H();
VecH<BaryRef>& triBary = meshRelation_.triBary.H();
const Halfedge toRemove = halfedge[edge];
if (toRemove.pairedHalfedge < 0) return;
const int endVert = toRemove.endVert;
const glm::ivec3 tri0edge = TriOf(edge);
const glm::ivec3 tri1edge = TriOf(toRemove.pairedHalfedge);
const glm::vec3 pNew = vertPos[endVert];
const glm::vec3 pOld = vertPos[toRemove.startVert];
const glm::vec3 delta = pNew - pOld;
const bool shortEdge = glm::dot(delta, delta) < precision_ * precision_;
std::vector<int> edges;
// Orbit endVert
int current = halfedge[tri0edge[1]].pairedHalfedge;
while (current != tri1edge[2]) {
current = NextHalfedge(current);
edges.push_back(current);
current = halfedge[current].pairedHalfedge;
}
// Orbit startVert
int start = halfedge[tri1edge[1]].pairedHalfedge;
const BaryRef ref0 = triBary[edge / 3];
const BaryRef ref1 = triBary[toRemove.pairedHalfedge / 3];
if (!shortEdge) {
current = start;
glm::vec3 pLast = vertPos[halfedge[tri1edge[1]].endVert];
while (current != tri0edge[2]) {
current = NextHalfedge(current);
glm::vec3 pNext = vertPos[halfedge[current].endVert];
const int tri = current / 3;
const BaryRef ref = triBary[tri];
// Don't collapse if the edge is not redundant (this may have changed due
// to the collapse of neighbors).
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
return;
// Don't collapse edge if it would cause a triangle to invert.
const glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[tri]);
if (CCW(projection * pNext, projection * pLast, projection * pNew,
precision_) < 0)
return;
pLast = pNext;
current = halfedge[current].pairedHalfedge;
}
}
// Remove toRemove.startVert and replace with endVert.
vertPos[toRemove.startVert] = glm::vec3(0.0f / 0.0f);
CollapseTri(tri1edge);
// Orbit startVert
current = start;
while (current != tri0edge[2]) {
current = NextHalfedge(current);
if (!shortEdge) {
// Update the shifted triangles to the vertBary of endVert
const int tri = current / 3;
const int vIdx = current - 3 * tri;
triBary[tri].vertBary[vIdx] =
(ref0.meshID == triBary[tri].meshID && ref0.tri == triBary[tri].tri)
? ref0.vertBary[(edge + 1) % 3]
: ref1.vertBary[toRemove.pairedHalfedge % 3];
}
const int vert = halfedge[current].endVert;
const int next = halfedge[current].pairedHalfedge;
for (int i = 0; i < edges.size(); ++i) {
if (vert == halfedge[edges[i]].endVert) {
FormLoop(edges[i], current);
start = next;
edges.resize(i);
break;
}
}
current = next;
}
UpdateVert(endVert, start, tri0edge[2]);
CollapseTri(tri0edge);
RemoveIfFolded(start);
}
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
const VecH<glm::vec3>& vertPos = vertPos_.H();
VecH<Halfedge>& halfedge = halfedge_.H();
VecH<glm::vec3>& triNormal = faceNormal_.H();
VecH<BaryRef>& triBary = meshRelation_.triBary.H();
if (halfedge[edge].pairedHalfedge < 0) return;
const int pair = halfedge[edge].pairedHalfedge;
const glm::ivec3 tri0edge = TriOf(edge);
const glm::ivec3 tri1edge = TriOf(pair);
const glm::ivec3 perm0 = TriOf(edge % 3);
const glm::ivec3 perm1 = TriOf(pair % 3);
glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[edge / 3]);
glm::vec2 v[4];
for (int i : {0, 1, 2})
v[i] = projection * vertPos[halfedge[tri0edge[i]].startVert];
// Only operate on the long edge of a degenerate triangle.
if (CCW(v[0], v[1], v[2], precision_) > 0 || !Is01Longest(v[0], v[1], v[2]))
return;
// Switch to neighbor's projection.
projection = GetAxisAlignedProjection(triNormal[halfedge[pair].face]);
for (int i : {0, 1, 2})
v[i] = projection * vertPos[halfedge[tri0edge[i]].startVert];
v[3] = projection * vertPos[halfedge[tri1edge[2]].startVert];
auto SwapEdge = [&]() {
// The 0-verts are swapped to the opposite 2-verts.
const int v0 = halfedge[tri0edge[2]].startVert;
const int v1 = halfedge[tri1edge[2]].startVert;
halfedge[tri0edge[0]].startVert = v1;
halfedge[tri0edge[2]].endVert = v1;
halfedge[tri1edge[0]].startVert = v0;
halfedge[tri1edge[2]].endVert = v0;
PairUp(tri0edge[0], halfedge[tri1edge[2]].pairedHalfedge);
PairUp(tri1edge[0], halfedge[tri0edge[2]].pairedHalfedge);
PairUp(tri0edge[2], tri1edge[2]);
// Both triangles are now subsets of the neighboring triangle.
const int tri0 = halfedge[tri0edge[0]].face;
const int tri1 = halfedge[tri1edge[0]].face;
triNormal[tri0] = triNormal[tri1];
triBary[tri0] = triBary[tri1];
triBary[tri0].vertBary[perm0[1]] = triBary[tri1].vertBary[perm1[0]];
triBary[tri0].vertBary[perm0[0]] = triBary[tri1].vertBary[perm1[2]];
// Calculate a new barycentric coordinate for the split triangle.
const glm::vec3 uvw0 = UVW(triBary[tri1].vertBary[perm1[0]],
meshRelation_.barycentric.cptrH());
const glm::vec3 uvw1 = UVW(triBary[tri1].vertBary[perm1[1]],
meshRelation_.barycentric.cptrH());
const float l01 = glm::length(v[1] - v[0]);
const float l02 = glm::length(v[2] - v[0]);
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
const glm::vec3 uvw2 = a * uvw0 + (1 - a) * uvw1;
// And assign it.
const int newBary = meshRelation_.barycentric.size();
meshRelation_.barycentric.H().push_back(uvw2);
triBary[tri1].vertBary[perm1[0]] = newBary;
triBary[tri0].vertBary[perm0[2]] = newBary;
// if the new edge already exists, duplicate the verts and split the mesh.
int current = halfedge[tri1edge[0]].pairedHalfedge;
const int endVert = halfedge[tri1edge[1]].endVert;
while (current != tri0edge[1]) {
current = NextHalfedge(current);
if (halfedge[current].endVert == endVert) {
FormLoop(tri0edge[2], current);
RemoveIfFolded(tri0edge[2]);
return;
}
current = halfedge[current].pairedHalfedge;
}
};
// Only operate if the other triangles are not degenerate.
if (CCW(v[1], v[0], v[3], precision_) <= 0) {
if (!Is01Longest(v[1], v[0], v[3])) return;
// Two facing, long-edge degenerates can swap.
SwapEdge();
const glm::vec2 e23 = v[3] - v[2];
if (glm::dot(e23, e23) < precision_ * precision_) {
CollapseEdge(tri0edge[2]);
} else {
RecursiveEdgeSwap(tri0edge[0]);
RecursiveEdgeSwap(tri0edge[1]);
RecursiveEdgeSwap(tri1edge[0]);
RecursiveEdgeSwap(tri1edge[1]);
}
return;
} else if (CCW(v[0], v[3], v[2], precision_) <= 0 ||
CCW(v[1], v[2], v[3], precision_) <= 0) {
return;
}
// Normal path
SwapEdge();
RecursiveEdgeSwap(halfedge[tri0edge[1]].pairedHalfedge);
RecursiveEdgeSwap(halfedge[tri1edge[0]].pairedHalfedge);
}
} // namespace manifold
-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
-641
View File
@@ -1,641 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <thrust/execution_policy.h>
#include <thrust/logical.h>
#include <algorithm>
#include <boost/config.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <map>
#include "impl.cuh"
namespace {
using namespace manifold;
__host__ __device__ void AtomicAddVec3(glm::vec3& target,
const glm::vec3& add) {
for (int i : {0, 1, 2}) {
#ifdef __CUDA_ARCH__
atomicAdd(&target[i], add[i]);
#else
#pragma omp atomic
target[i] += add[i];
#endif
}
}
struct Normalize {
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
};
struct Transform4x3 {
const glm::mat4x3 transform;
__host__ __device__ void operator()(glm::vec3& position) {
position = transform * glm::vec4(position, 1.0f);
}
};
struct TransformNormals {
const glm::mat3 transform;
__host__ __device__ void operator()(glm::vec3& normal) {
normal = glm::normalize(transform * normal);
if (isnan(normal.x)) normal = glm::vec3(0.0f);
}
};
struct AssignNormals {
glm::vec3* vertNormal;
const glm::vec3* vertPos;
const Halfedge* halfedges;
const float precision;
const bool calculateTriNormal;
__host__ __device__ void operator()(thrust::tuple<glm::vec3&, int> in) {
glm::vec3& triNormal = thrust::get<0>(in);
const int face = thrust::get<1>(in);
glm::ivec3 triVerts;
for (int i : {0, 1, 2}) triVerts[i] = halfedges[3 * face + i].startVert;
glm::vec3 edge[3];
for (int i : {0, 1, 2}) {
const int j = (i + 1) % 3;
edge[i] = glm::normalize(vertPos[triVerts[j]] - vertPos[triVerts[i]]);
}
if (calculateTriNormal) {
triNormal = glm::normalize(glm::cross(edge[0], edge[1]));
if (isnan(triNormal.x)) triNormal = glm::vec3(0, 0, 1);
}
// corner angles
glm::vec3 phi;
float dot = -glm::dot(edge[2], edge[0]);
phi[0] = dot >= 1 ? 0 : (dot <= -1 ? glm::pi<float>() : glm::acos(dot));
dot = -glm::dot(edge[0], edge[1]);
phi[1] = dot >= 1 ? 0 : (dot <= -1 ? glm::pi<float>() : glm::acos(dot));
phi[2] = glm::pi<float>() - phi[0] - phi[1];
// assign weighted sum
for (int i : {0, 1, 2}) {
AtomicAddVec3(vertNormal[triVerts[i]], phi[i] * triNormal);
}
}
};
struct Tri2Halfedges {
Halfedge* halfedges;
TmpEdge* edges;
__host__ __device__ void operator()(
thrust::tuple<int, const glm::ivec3&> in) {
const int tri = thrust::get<0>(in);
const glm::ivec3& triVerts = thrust::get<1>(in);
for (const int i : {0, 1, 2}) {
const int j = (i + 1) % 3;
const int edge = 3 * tri + i;
halfedges[edge] = {triVerts[i], triVerts[j], -1, tri};
edges[edge] = TmpEdge(triVerts[i], triVerts[j], edge);
}
}
};
struct LinkHalfedges {
Halfedge* halfedges;
const TmpEdge* edges;
__host__ __device__ void operator()(int k) {
const int i = 2 * k;
const int j = i + 1;
const int pair0 = edges[i].halfedgeIdx;
const int pair1 = edges[j].halfedgeIdx;
if (halfedges[pair0].startVert != halfedges[pair1].endVert ||
halfedges[pair0].endVert != halfedges[pair1].startVert ||
halfedges[pair0].face == halfedges[pair1].face)
printf("Not manifold!\n");
halfedges[pair0].pairedHalfedge = pair1;
halfedges[pair1].pairedHalfedge = pair0;
}
};
struct SwapHalfedges {
Halfedge* halfedges;
const TmpEdge* edges;
__host__ void operator()(int k) {
const int i = 2 * k;
const int j = i - 2;
const TmpEdge thisEdge = edges[i];
const TmpEdge lastEdge = edges[j];
if (thisEdge.first == lastEdge.first &&
thisEdge.second == lastEdge.second) {
const int swap0idx = thisEdge.halfedgeIdx;
Halfedge& swap0 = halfedges[swap0idx];
const int swap1idx = swap0.pairedHalfedge;
Halfedge& swap1 = halfedges[swap1idx];
const int next0idx = swap0idx + ((swap0idx + 1) % 3 == 0 ? -2 : 1);
const int next1idx = swap1idx + ((swap1idx + 1) % 3 == 0 ? -2 : 1);
Halfedge& next0 = halfedges[next0idx];
Halfedge& next1 = halfedges[next1idx];
next0.startVert = swap0.endVert = next1.endVert;
swap0.pairedHalfedge = next1.pairedHalfedge;
halfedges[swap0.pairedHalfedge].pairedHalfedge = swap0idx;
next1.startVert = swap1.endVert = next0.endVert;
swap1.pairedHalfedge = next0.pairedHalfedge;
halfedges[swap1.pairedHalfedge].pairedHalfedge = swap1idx;
next0.pairedHalfedge = next1idx;
next1.pairedHalfedge = next0idx;
}
}
};
struct InitializeBaryRef {
const int meshID;
const Halfedge* halfedge;
__host__ __device__ void operator()(thrust::tuple<BaryRef&, int> inOut) {
BaryRef& baryRef = thrust::get<0>(inOut);
int tri = thrust::get<1>(inOut);
// Leave existing meshID if input is negative
if (meshID >= 0) baryRef.meshID = meshID;
baryRef.tri = tri;
baryRef.vertBary = {-3, -2, -1};
}
};
struct CheckProperties {
const int numSets;
__host__ __device__ bool operator()(glm::ivec3 triProp) {
bool good = true;
for (int i : {0, 1, 2}) good &= (triProp[i] >= 0 && triProp[i] < numSets);
return good;
}
};
struct CoplanarEdge {
float* triArea;
const Halfedge* halfedge;
const glm::vec3* vertPos;
const glm::ivec3* triProp;
const float* prop;
const float* propTol;
const int numProp;
const float precision;
__host__ __device__ void operator()(
thrust::tuple<thrust::pair<int, int>&, int> inOut) {
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
const int edgeIdx = thrust::get<1>(inOut);
const Halfedge edge = halfedge[edgeIdx];
if (!edge.IsForward()) return;
const Halfedge pair = halfedge[edge.pairedHalfedge];
const glm::vec3 base = vertPos[edge.startVert];
const int baseNum = edgeIdx - 3 * edge.face;
const int jointNum = edge.pairedHalfedge - 3 * pair.face;
const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
const glm::vec3 jointVec = vertPos[pair.startVert] - base;
const glm::vec3 edgeVec =
vertPos[halfedge[3 * edge.face + edgeNum].startVert] - base;
const glm::vec3 pairVec =
vertPos[halfedge[3 * pair.face + pairNum].startVert] - base;
const float length = glm::max(glm::length(jointVec), glm::length(edgeVec));
const float lengthPair =
glm::max(glm::length(jointVec), glm::length(pairVec));
glm::vec3 normal = glm::cross(jointVec, edgeVec);
const float area = glm::length(normal);
const float areaPair = glm::length(glm::cross(pairVec, jointVec));
// Don't link degenerate triangles
if (area < length * precision || areaPair < lengthPair * precision) return;
const float volume = glm::abs(glm::dot(normal, pairVec));
// Only operate on coplanar triangles
if (volume > glm::max(area, areaPair) * precision) return;
// Check property linearity
if (area > 0) {
normal /= area;
for (int i = 0; i < numProp; ++i) {
const float scale = precision / propTol[i];
const float baseProp = prop[numProp * triProp[edge.face][baseNum] + i];
const float jointProp =
prop[numProp * triProp[pair.face][jointNum] + i];
const float edgeProp = prop[numProp * triProp[edge.face][edgeNum] + i];
const float pairProp = prop[numProp * triProp[pair.face][pairNum] + i];
const glm::vec3 iJointVec =
jointVec + normal * scale * (jointProp - baseProp);
const glm::vec3 iEdgeVec =
edgeVec + normal * scale * (edgeProp - baseProp);
const glm::vec3 iPairVec =
pairVec + normal * scale * (pairProp - baseProp);
glm::vec3 cross = glm::cross(iJointVec, iEdgeVec);
const float area = glm::max(
glm::length(cross), glm::length(glm::cross(iPairVec, iJointVec)));
const float volume = glm::abs(glm::dot(cross, iPairVec));
// Only operate on consistent triangles
if (volume > area * precision) return;
}
}
triArea[edge.face] = area;
triArea[pair.face] = areaPair;
face2face.first = edge.face;
face2face.second = pair.face;
}
};
struct EdgeBox {
const glm::vec3* vertPos;
__host__ __device__ void operator()(
thrust::tuple<Box&, const TmpEdge&> inout) {
const TmpEdge& edge = thrust::get<1>(inout);
thrust::get<0>(inout) = Box(vertPos[edge.first], vertPos[edge.second]);
}
};
} // namespace
namespace manifold {
std::vector<int> Manifold::Impl::meshID2Original_;
/**
* Create a manifold from an input triangle Mesh. Will throw if the Mesh is not
* manifold. TODO: update halfedgeTangent during CollapseDegenerates.
*/
Manifold::Impl::Impl(const Mesh& mesh,
const std::vector<glm::ivec3>& triProperties,
const std::vector<float>& properties,
const std::vector<float>& propertyTolerance)
: vertPos_(mesh.vertPos), halfedgeTangent_(mesh.halfedgeTangent) {
CheckDevice();
CalculateBBox();
SetPrecision();
CreateAndFixHalfedges(mesh.triVerts);
CalculateNormals();
InitializeNewReference(triProperties, properties, propertyTolerance);
CollapseDegenerates();
Finish();
}
/**
* Create eiter a unit tetrahedron, cube or octahedron. The cube is in the first
* octant, while the others are symmetric about the origin.
*/
Manifold::Impl::Impl(Shape shape) {
std::vector<glm::vec3> vertPos;
std::vector<glm::ivec3> triVerts;
switch (shape) {
case Shape::TETRAHEDRON:
vertPos = {{-1.0f, -1.0f, 1.0f},
{-1.0f, 1.0f, -1.0f},
{1.0f, -1.0f, -1.0f},
{1.0f, 1.0f, 1.0f}};
triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
break;
case Shape::CUBE:
vertPos = {{0.0f, 0.0f, 0.0f}, //
{1.0f, 0.0f, 0.0f}, //
{1.0f, 1.0f, 0.0f}, //
{0.0f, 1.0f, 0.0f}, //
{0.0f, 0.0f, 1.0f}, //
{1.0f, 0.0f, 1.0f}, //
{1.0f, 1.0f, 1.0f}, //
{0.0f, 1.0f, 1.0f}};
triVerts = {{0, 2, 1}, {0, 3, 2}, //
{4, 5, 6}, {4, 6, 7}, //
{0, 1, 5}, {0, 5, 4}, //
{1, 2, 6}, {1, 6, 5}, //
{2, 3, 7}, {2, 7, 6}, //
{3, 0, 4}, {3, 4, 7}};
break;
case Shape::OCTAHEDRON:
vertPos = {{1.0f, 0.0f, 0.0f}, //
{-1.0f, 0.0f, 0.0f}, //
{0.0f, 1.0f, 0.0f}, //
{0.0f, -1.0f, 0.0f}, //
{0.0f, 0.0f, 1.0f}, //
{0.0f, 0.0f, -1.0f}};
triVerts = {{0, 2, 4}, {1, 5, 3}, //
{2, 1, 4}, {3, 5, 0}, //
{1, 3, 4}, {0, 5, 2}, //
{3, 0, 4}, {2, 5, 1}};
break;
default:
throw userErr("Unrecognized shape!");
}
vertPos_ = vertPos;
CreateHalfedges(triVerts);
Finish();
InitializeNewReference();
}
/**
* When a manifold is copied, it is given a new unique set of mesh relation IDs,
* identifying a particular instance of a copied input mesh. The original mesh
* ID can be found using the meshID2Original mapping.
*/
void Manifold::Impl::DuplicateMeshIDs() {
std::map<int, int> old2new;
for (BaryRef& ref : meshRelation_.triBary) {
if (old2new.find(ref.meshID) == old2new.end()) {
old2new[ref.meshID] = meshID2Original_.size();
meshID2Original_.push_back(meshID2Original_[ref.meshID]);
}
ref.meshID = old2new[ref.meshID];
}
}
void Manifold::Impl::ReinitializeReference(int meshID) {
thrust::for_each_n(zip(meshRelation_.triBary.beginD(), countAt(0)), NumTri(),
InitializeBaryRef({meshID, halfedge_.cptrD()}));
}
int Manifold::Impl::InitializeNewReference(
const std::vector<glm::ivec3>& triProperties,
const std::vector<float>& properties,
const std::vector<float>& propertyTolerance) {
meshRelation_.triBary.resize(NumTri());
const int nextMeshID = meshID2Original_.size();
meshID2Original_.push_back(nextMeshID);
ReinitializeReference(nextMeshID);
const int numProps = propertyTolerance.size();
VecDH<glm::ivec3> triPropertiesD(triProperties);
VecDH<float> propertiesD(properties);
VecDH<float> propertyToleranceD(propertyTolerance);
if (numProps > 0) {
ALWAYS_ASSERT(
triProperties.size() == NumTri() || triProperties.size() == 0, userErr,
"If specified, triProperties vector length must match NumTri().");
ALWAYS_ASSERT(properties.size() % numProps == 0, userErr,
"properties vector must be a multiple of the size of "
"propertyTolerance.");
const int numSets = properties.size() / numProps;
ALWAYS_ASSERT(thrust::all_of(triPropertiesD.beginD(), triPropertiesD.endD(),
CheckProperties({numSets})),
userErr,
"triProperties value is outside the properties range.");
}
VecDH<thrust::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
VecDH<float> triArea(NumTri());
thrust::for_each_n(
zip(face2face.beginD(), countAt(0)), halfedge_.size(),
CoplanarEdge({triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
triPropertiesD.cptrD(), propertiesD.cptrD(),
propertyToleranceD.cptrD(), numProps, precision_}));
boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> graph(
NumTri());
for (int i = 0; i < face2face.size(); ++i) {
const thrust::pair<int, int> edge = face2face.H()[i];
if (edge.first < 0) continue;
boost::add_edge(edge.first, edge.second, graph);
}
std::vector<int> components(NumTri());
const int numComponent =
boost::connected_components(graph, components.data());
std::vector<int> comp2tri(numComponent, -1);
for (int tri = 0; tri < NumTri(); ++tri) {
const int comp = components[tri];
const int current = comp2tri[comp];
if (current < 0 || triArea.H()[tri] > triArea.H()[current]) {
comp2tri[comp] = tri;
triArea.H()[comp] = triArea.H()[tri];
}
}
VecH<BaryRef>& triBary = meshRelation_.triBary.H();
std::map<std::pair<int, int>, int> triVert2bary;
for (int tri = 0; tri < NumTri(); ++tri) {
const int refTri = comp2tri[components[tri]];
if (refTri == tri) continue;
glm::mat3 triPos;
for (int i : {0, 1, 2}) {
const int vert = halfedge_.H()[3 * refTri + i].startVert;
triPos[i] = vertPos_.H()[vert];
triVert2bary[{refTri, vert}] = i - 3;
}
glm::ivec3 vertBary;
bool coplanar = true;
for (int i : {0, 1, 2}) {
const int vert = halfedge_.H()[3 * tri + i].startVert;
if (triVert2bary.find({refTri, vert}) == triVert2bary.end()) {
const glm::vec3 uvw =
GetBarycentric(vertPos_.H()[vert], triPos, precision_);
if (isnan(uvw[0])) {
coplanar = false;
triVert2bary[{refTri, vert}] = -4;
break;
}
triVert2bary[{refTri, vert}] = meshRelation_.barycentric.size();
meshRelation_.barycentric.H().push_back(uvw);
}
const int bary = triVert2bary[{refTri, vert}];
if (bary < -3) {
coplanar = false;
break;
}
vertBary[i] = bary;
}
if (coplanar) {
BaryRef& ref = triBary[tri];
ref.tri = refTri;
ref.vertBary = vertBary;
}
}
return nextMeshID;
}
/**
* Create the halfedge_ data structure from an input triVerts array like Mesh.
*/
void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
const int numTri = triVerts.size();
halfedge_.resize(3 * numTri);
VecDH<TmpEdge> edge(3 * numTri);
thrust::for_each_n(zip(countAt(0), triVerts.beginD()), numTri,
Tri2Halfedges({halfedge_.ptrD(), edge.ptrD()}));
thrust::sort(edge.beginD(), edge.endD());
thrust::for_each_n(countAt(0), halfedge_.size() / 2,
LinkHalfedges({halfedge_.ptrD(), edge.cptrD()}));
}
/**
* Create the halfedge_ data structure from an input triVerts array like Mesh.
* Check that the input is an even-manifold, and if it is not 2-manifold,
* perform edge swaps until it is. This is a host function.
*/
void Manifold::Impl::CreateAndFixHalfedges(const VecDH<glm::ivec3>& triVerts) {
const int numTri = triVerts.size();
halfedge_.resize(3 * numTri);
VecDH<TmpEdge> edge(3 * numTri);
thrust::for_each_n(zip(countAt(0), triVerts.begin()), numTri,
Tri2Halfedges({halfedge_.ptrH(), edge.ptrH()}));
// Stable sort is required here so that halfedges from the same face are
// paired together (the triangles were created in face order). In some
// degenerate situations the triangulator can add the same internal edge in
// two different faces, causing this edge to not be 2-manifold. We detect this
// and fix it by swapping one of the identical edges, so it is important that
// we have the edges paired according to their face.
std::stable_sort(edge.begin(), edge.end());
thrust::for_each_n(thrust::host, countAt(0), halfedge_.size() / 2,
LinkHalfedges({halfedge_.ptrH(), edge.cptrH()}));
thrust::for_each(thrust::host, countAt(1), countAt(halfedge_.size() / 2),
SwapHalfedges({halfedge_.ptrH(), edge.cptrH()}));
}
/**
* Does a full recalculation of the face bounding boxes, including updating the
* collider, but does not resort the faces.
*/
void Manifold::Impl::Update() {
CalculateBBox();
VecDH<Box> faceBox;
VecDH<uint32_t> faceMorton;
GetFaceBoxMorton(faceBox, faceMorton);
collider_.UpdateBoxes(faceBox);
}
void Manifold::Impl::ApplyTransform() const {
// This const_cast is here because these operations cancel out, leaving the
// state conceptually unchanged. This enables lazy transformation evaluation.
const_cast<Impl*>(this)->ApplyTransform();
}
/**
* Bake the manifold's transform into its vertices. This function allows lazy
* evaluation, which is important because often several transforms are applied
* between operations.
*/
void Manifold::Impl::ApplyTransform() {
if (transform_ == glm::mat4x3(1.0f)) return;
thrust::for_each(vertPos_.beginD(), vertPos_.endD(),
Transform4x3({transform_}));
glm::mat3 normalTransform =
glm::inverse(glm::transpose(glm::mat3(transform_)));
thrust::for_each(faceNormal_.beginD(), faceNormal_.endD(),
TransformNormals({normalTransform}));
thrust::for_each(vertNormal_.beginD(), vertNormal_.endD(),
TransformNormals({normalTransform}));
// This optimization does a cheap collider update if the transform is
// axis-aligned.
if (!collider_.Transform(transform_)) Update();
const float oldScale = bBox_.Scale();
transform_ = glm::mat4x3(1.0f);
CalculateBBox();
const float newScale = bBox_.Scale();
precision_ *= glm::max(1.0f, newScale / oldScale) *
glm::max(glm::length(transform_[0]),
glm::max(glm::length(transform_[1]),
glm::length(transform_[2])));
// Maximum of inherited precision loss and translational precision loss.
SetPrecision(precision_);
}
/**
* Sets the precision based on the bounding box, and limits its minimum value by
* the optional input.
*/
void Manifold::Impl::SetPrecision(float minPrecision) {
precision_ = glm::max(minPrecision, kTolerance * bBox_.Scale());
if (!glm::isfinite(precision_)) precision_ = -1;
}
/**
* If face normals are already present, this function uses them to compute
* vertex normals (angle-weighted pseudo-normals); otherwise it also computes
* the face normals. Face normals are only calculated when needed because nearly
* degenerate faces will accrue rounding error, while the Boolean can retain
* their original normal, which is more accurate and can help with merging
* coplanar faces.
*
* If the face normals have been invalidated by an operation like Warp(), ensure
* you do faceNormal_.resize(0) before calling this function to force
* recalculation.
*/
void Manifold::Impl::CalculateNormals() {
vertNormal_.resize(NumVert());
thrust::fill(vertNormal_.beginD(), vertNormal_.endD(), glm::vec3(0));
bool calculateTriNormal = false;
if (faceNormal_.size() != NumTri()) {
faceNormal_.resize(NumTri());
calculateTriNormal = true;
}
thrust::for_each_n(
zip(faceNormal_.beginD(), countAt(0)), NumTri(),
AssignNormals({vertNormal_.ptrD(), vertPos_.cptrD(), halfedge_.cptrD(),
precision_, calculateTriNormal}));
thrust::for_each(vertNormal_.beginD(), vertNormal_.endD(), Normalize());
}
/**
* Returns a sparse array of the bounding box overlaps between the edges of the
* input manifold, Q and the faces of this manifold. Returned indices only
* point to forward halfedges.
*/
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q) const {
VecDH<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
const int numEdge = edges.size();
VecDH<Box> QedgeBB(numEdge);
thrust::for_each_n(zip(QedgeBB.beginD(), edges.cbeginD()), numEdge,
EdgeBox({Q.vertPos_.cptrD()}));
SparseIndices q1p2 = collider_.Collisions(QedgeBB);
thrust::for_each(q1p2.beginD(0), q1p2.endD(0), ReindexEdge({edges.cptrD()}));
return q1p2;
}
/**
* Returns a sparse array of the input vertices that project inside the XY
* bounding boxes of the faces of this manifold.
*/
SparseIndices Manifold::Impl::VertexCollisionsZ(
const VecDH<glm::vec3>& vertsIn) const {
return collider_.Collisions(vertsIn);
}
} // namespace manifold
-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
-338
View File
@@ -1,338 +0,0 @@
// Copyright 2021 Emmett Lalish
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <thrust/sequence.h>
#include "impl.cuh"
namespace {
using namespace manifold;
constexpr uint32_t kNoCode = 0xFFFFFFFFu;
struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
__host__ __device__ void MakeForward(Halfedge& a) {
if (!a.IsForward()) {
int tmp = a.startVert;
a.startVert = a.endVert;
a.endVert = tmp;
}
}
__host__ __device__ int MaxOrMinus(int a, int b) {
return glm::min(a, b) < 0 ? -1 : glm::max(a, b);
}
__host__ __device__ Halfedge operator()(Halfedge a, Halfedge b) {
MakeForward(a);
MakeForward(b);
a.startVert = glm::min(a.startVert, b.startVert);
a.endVert = glm::max(a.endVert, b.endVert);
a.face = MaxOrMinus(a.face, b.face);
a.pairedHalfedge = MaxOrMinus(a.pairedHalfedge, b.pairedHalfedge);
return a;
}
};
__host__ __device__ uint32_t SpreadBits3(uint32_t v) {
v = 0xFF0000FFu & (v * 0x00010001u);
v = 0x0F00F00Fu & (v * 0x00000101u);
v = 0xC30C30C3u & (v * 0x00000011u);
v = 0x49249249u & (v * 0x00000005u);
return v;
}
__host__ __device__ uint32_t MortonCode(glm::vec3 position, Box bBox) {
// Unreferenced vertices are marked NaN, and this will sort them to the end
// (the Morton code only uses the first 30 of 32 bits).
if (isnan(position.x)) return kNoCode;
glm::vec3 xyz = (position - bBox.min) / (bBox.max - bBox.min);
xyz = glm::min(glm::vec3(1023.0f), glm::max(glm::vec3(0.0f), 1024.0f * xyz));
uint32_t x = SpreadBits3(static_cast<uint32_t>(xyz.x));
uint32_t y = SpreadBits3(static_cast<uint32_t>(xyz.y));
uint32_t z = SpreadBits3(static_cast<uint32_t>(xyz.z));
return x * 4 + y * 2 + z;
}
struct Morton {
const Box bBox;
__host__ __device__ void operator()(
thrust::tuple<uint32_t&, const glm::vec3&> inout) {
glm::vec3 position = thrust::get<1>(inout);
thrust::get<0>(inout) = MortonCode(position, bBox);
}
};
struct FaceMortonBox {
const Halfedge* halfedge;
const glm::vec3* vertPos;
const Box bBox;
__host__ __device__ void operator()(
thrust::tuple<uint32_t&, Box&, int> inout) {
uint32_t& mortonCode = thrust::get<0>(inout);
Box& faceBox = thrust::get<1>(inout);
int face = thrust::get<2>(inout);
// Removed tris are marked by all halfedges having pairedHalfedge = -1, and
// this will sort them to the end (the Morton code only uses the first 30 of
// 32 bits).
if (halfedge[3 * face].pairedHalfedge < 0) {
mortonCode = kNoCode;
return;
}
glm::vec3 center(0.0f);
for (const int i : {0, 1, 2}) {
const glm::vec3 pos = vertPos[halfedge[3 * face + i].startVert];
center += pos;
faceBox.Union(pos);
}
center /= 3;
mortonCode = MortonCode(center, bBox);
}
};
struct Reindex {
const int* indexInv;
__host__ __device__ void operator()(Halfedge& edge) {
if (edge.startVert < 0) return;
edge.startVert = indexInv[edge.startVert];
edge.endVert = indexInv[edge.endVert];
}
};
template <typename T>
void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
VecDH<T> tmp(inOut);
inOut.resize(new2Old.size());
thrust::gather(new2Old.beginD(), new2Old.endD(), tmp.beginD(),
inOut.beginD());
}
template void Permute<BaryRef>(VecDH<BaryRef>&, const VecDH<int>&);
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
struct ReindexFace {
Halfedge* halfedge;
glm::vec4* halfedgeTangent;
const Halfedge* oldHalfedge;
const glm::vec4* oldHalfedgeTangent;
const int* faceNew2Old;
const int* faceOld2New;
__host__ __device__ void operator()(int newFace) {
const int oldFace = faceNew2Old[newFace];
for (const int i : {0, 1, 2}) {
const int oldEdge = 3 * oldFace + i;
Halfedge edge = oldHalfedge[oldEdge];
edge.face = newFace;
const int pairedFace = edge.pairedHalfedge / 3;
const int offset = edge.pairedHalfedge - 3 * pairedFace;
edge.pairedHalfedge = 3 * faceOld2New[pairedFace] + offset;
const int newEdge = 3 * newFace + i;
halfedge[newEdge] = edge;
if (oldHalfedgeTangent != nullptr) {
halfedgeTangent[newEdge] = oldHalfedgeTangent[oldEdge];
}
}
}
};
} // namespace
namespace manifold {
/**
* Once halfedge_ has been filled in, this function can be called to create the
* rest of the internal data structures. This function also removes the verts
* and halfedges flagged for removal (NaN verts and -1 halfedges).
*/
void Manifold::Impl::Finish() {
if (halfedge_.size() == 0) return;
CalculateBBox();
SetPrecision(precision_);
if (!bBox_.isFinite()) {
vertPos_.resize(0);
halfedge_.resize(0);
faceNormal_.resize(0);
return;
}
SortVerts();
VecDH<Box> faceBox;
VecDH<uint32_t> faceMorton;
GetFaceBoxMorton(faceBox, faceMorton);
SortFaces(faceBox, faceMorton);
if (halfedge_.size() == 0) return;
ALWAYS_ASSERT(halfedge_.size() % 6 == 0, topologyErr,
"Not an even number of faces after sorting faces!");
Halfedge extrema = {0, 0, 0, 0};
extrema =
thrust::reduce(halfedge_.beginD(), halfedge_.endD(), extrema, Extrema());
ALWAYS_ASSERT(extrema.startVert >= 0, topologyErr,
"Vertex index is negative!");
ALWAYS_ASSERT(extrema.endVert < NumVert(), topologyErr,
"Vertex index exceeds number of verts!");
ALWAYS_ASSERT(extrema.face >= 0, topologyErr, "Face index is negative!");
ALWAYS_ASSERT(extrema.face < NumTri(), topologyErr,
"Face index exceeds number of faces!");
ALWAYS_ASSERT(extrema.pairedHalfedge >= 0, topologyErr,
"Halfedge index is negative!");
ALWAYS_ASSERT(extrema.pairedHalfedge < 2 * NumEdge(), topologyErr,
"Halfedge index exceeds number of halfedges!");
CalculateNormals();
collider_ = Collider(faceBox, faceMorton);
}
/**
* Sorts the vertices according to their Morton code.
*/
void Manifold::Impl::SortVerts() {
VecDH<uint32_t> vertMorton(NumVert());
thrust::for_each_n(zip(vertMorton.beginD(), vertPos_.cbeginD()), NumVert(),
Morton({bBox_}));
VecDH<int> vertNew2Old(NumVert());
thrust::sequence(vertNew2Old.beginD(), vertNew2Old.endD());
thrust::sort_by_key(vertMorton.beginD(), vertMorton.endD(),
zip(vertPos_.beginD(), vertNew2Old.beginD()));
ReindexVerts(vertNew2Old, NumVert());
// Verts were flagged for removal with NaNs and assigned kNoCode to sort them
// to the end, which allows them to be removed.
const int newNumVert =
thrust::find(vertMorton.beginD(), vertMorton.endD(), kNoCode) -
vertMorton.beginD();
vertPos_.resize(newNumVert);
}
/**
* Updates the halfedges to point to new vert indices based on a mapping,
* vertNew2Old. This may be a subset, so the total number of original verts is
* also given.
*/
void Manifold::Impl::ReindexVerts(const VecDH<int>& vertNew2Old,
int oldNumVert) {
VecDH<int> vertOld2New(oldNumVert);
thrust::scatter(countAt(0), countAt(NumVert()), vertNew2Old.beginD(),
vertOld2New.beginD());
thrust::for_each(halfedge_.beginD(), halfedge_.endD(),
Reindex({vertOld2New.cptrD()}));
}
/**
* Fills the faceBox and faceMorton input with the bounding boxes and Morton
* codes of the faces, respectively. The Morton code is based on the center of
* the bounding box.
*/
void Manifold::Impl::GetFaceBoxMorton(VecDH<Box>& faceBox,
VecDH<uint32_t>& faceMorton) const {
faceBox.resize(NumTri());
faceMorton.resize(NumTri());
thrust::for_each_n(
zip(faceMorton.beginD(), faceBox.beginD(), countAt(0)), NumTri(),
FaceMortonBox({halfedge_.cptrD(), vertPos_.cptrD(), bBox_}));
}
/**
* Sorts the faces of this manifold according to their input Morton code. The
* bounding box and Morton code arrays are also sorted accordingly.
*/
void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
VecDH<uint32_t>& faceMorton) {
VecDH<int> faceNew2Old(NumTri());
thrust::sequence(faceNew2Old.beginD(), faceNew2Old.endD());
thrust::sort_by_key(faceMorton.beginD(), faceMorton.endD(),
zip(faceBox.beginD(), faceNew2Old.beginD()));
// Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode
// to sort them to the end, which allows them to be removed.
const int newNumTri =
thrust::find(faceMorton.beginD(), faceMorton.endD(), kNoCode) -
faceMorton.beginD();
faceBox.resize(newNumTri);
faceMorton.resize(newNumTri);
faceNew2Old.resize(newNumTri);
GatherFaces(faceNew2Old);
}
/**
* Creates the halfedge_ vector for this manifold by copying a set of faces from
* another manifold, given by oldHalfedge. Input faceNew2Old defines the old
* faces to gather into this.
*/
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
const int numTri = faceNew2Old.size();
if (meshRelation_.triBary.size() == NumTri())
Permute(meshRelation_.triBary, faceNew2Old);
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
VecDH<Halfedge> oldHalfedge(halfedge_);
VecDH<glm::vec4> oldHalfedgeTangent(halfedgeTangent_);
VecDH<int> faceOld2New(oldHalfedge.size() / 3);
thrust::scatter(countAt(0), countAt(numTri), faceNew2Old.beginD(),
faceOld2New.beginD());
halfedge_.resize(3 * numTri);
if (oldHalfedgeTangent.size() != 0) halfedgeTangent_.resize(3 * numTri);
thrust::for_each_n(
countAt(0), numTri,
ReindexFace({halfedge_.ptrD(), halfedgeTangent_.ptrD(),
oldHalfedge.cptrD(), oldHalfedgeTangent.cptrD(),
faceNew2Old.cptrD(), faceOld2New.cptrD()}));
}
void Manifold::Impl::GatherFaces(const Impl& old,
const VecDH<int>& faceNew2Old) {
const int numTri = faceNew2Old.size();
meshRelation_.triBary.resize(numTri);
thrust::gather(faceNew2Old.beginD(), faceNew2Old.endD(),
old.meshRelation_.triBary.beginD(),
meshRelation_.triBary.beginD());
meshRelation_.barycentric = old.meshRelation_.barycentric;
DuplicateMeshIDs();
if (old.faceNormal_.size() == old.NumTri()) {
faceNormal_.resize(numTri);
thrust::gather(faceNew2Old.beginD(), faceNew2Old.endD(),
old.faceNormal_.beginD(), faceNormal_.beginD());
}
VecDH<int> faceOld2New(old.NumTri());
thrust::scatter(countAt(0), countAt(numTri), faceNew2Old.beginD(),
faceOld2New.beginD());
halfedge_.resize(3 * numTri);
if (old.halfedgeTangent_.size() != 0) halfedgeTangent_.resize(3 * numTri);
thrust::for_each_n(
countAt(0), numTri,
ReindexFace({halfedge_.ptrD(), halfedgeTangent_.ptrD(),
old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
faceNew2Old.cptrD(), faceOld2New.cptrD()}));
}
} // namespace manifold

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