Compare 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
Emmett Lalish f6cf8d23d3 updated python mesh 2023-07-14 23:01:28 -07:00
68 changed files with 1705 additions and 2737 deletions
+51 -21
View File
@@ -15,27 +15,32 @@ jobs:
timeout-minutes: 45
strategy:
matrix:
parallel_backend: [NONE, TBB]
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:12.0.1-devel-ubuntu22.04
steps:
- name: Install dependencies
run: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
apt-get -y update
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
pip install trimesh
- uses: actions/checkout@v3
with:
submodules: recursive
- uses: jwlawson/actions-setup-cmake@v1.12
- name: Build ${{matrix.backend}}
if: matrix.parallel_backend != 'NONE'
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
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 ${{matrix.parallel_backend}}
if: matrix.parallel_backend != 'NONE'
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: |
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
cd build/test
@@ -46,11 +51,11 @@ jobs:
# 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'
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}} -DCODE_COVERAGE=ON .. && make
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
@@ -59,31 +64,35 @@ jobs:
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'
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
with:
files: build/code_coverage.info
fail_ci_if_error: false
name: ${{matrix.parallel_backend}}
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: |
sudo apt-get -y update
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
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 TBB
- 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=TBB .. && make
- name: Test ${{matrix.parallel_backend}}
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.*
@@ -135,7 +144,8 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE, TBB]
parallel_backend: [NONE]
cuda_support: [OFF]
max-parallel: 1
runs-on: windows-2019
if: github.event.pull_request.draft == false
@@ -145,13 +155,30 @@ jobs:
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}} -A x64
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}}
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
shell: bash
run: |
cd build/test
@@ -167,8 +194,11 @@ jobs:
steps:
- name: Install common dependencies
run: |
brew install pkg-config googletest assimp
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
@@ -190,7 +220,7 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
variant: [none, tbb, js]
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
+2 -6
View File
@@ -118,9 +118,9 @@
"-DMANIFOLD_EXPORT=ON",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=TBB",
"-DMANIFOLD_PAR=NONE",
"-DCODE_COVERAGE=OFF",
"-DCMAKE_CXX_FLAGS='-fsanitize=address,undefined'" //''" //
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
],
"search.exclude": {
"**/third_party/*": true,
@@ -140,8 +140,4 @@
"Verts"
],
"javascript.format.semicolons": "insert",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none",
}
+32 -37
View File
@@ -21,36 +21,19 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
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)
option(TRACY_ENABLE "Use tracy profiling" OFF)
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF)
if(TRACY_ENABLE)
option(CMAKE_BUILD_TYPE "Build type" RelWithDebInfo)
if(TRACY_MEMORY_USAGE)
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} -DTRACY_MEMORY_USAGE)
endif()
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
endif()
else()
option(CMAKE_BUILD_TYPE "Build type" Release)
endif()
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"NONE\"")
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} -O3)
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
set(MANIFOLD_FLAGS -O3)
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 -fexceptions -sDISABLE_EXCEPTION_CATCHING=0")
set(MANIFOLD_PYBIND OFF)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
endif()
if(MANIFOLD_PYBIND)
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
endif()
option(PYBIND11_FINDPYTHON "Enable PyBind to perform FindPython for you" ON)
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
@@ -58,28 +41,40 @@ 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(CMAKE_EXPORT_COMPILE_COMMANDS AND !EMSCRIPTEN)
# for nixos
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
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)
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused -Wno-array-bounds -Wno-stringop-overflow)
else()
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
endif()
set(MANIFOLD_FLAGS
"${MANIFOLD_FLAGS}"
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>")
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)
set(MANIFOLD_FLAGS
"${MANIFOLD_FLAGS}"
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>")
add_compile_options(
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
add_link_options("-coverage")
endif()
+5 -11
View File
@@ -43,9 +43,7 @@ This library is fast with guaranteed manifold output. As such you need manifold
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 TBB, and serial C++ backends by setting a CMake flag. Not everything is so parallelizable, for instance a [polygon triangulation](https://github.com/elalish/manifold/wiki/Manifold-Library#polygon-triangulation) algorithm is included which is serial. Even if compiled with parallel backend, the code will still fall back to the serial version of the algorithms if the problem size is small. The WASM build is serial-only for now, but still fast.
> Note: OMP and CUDA backends are now removed
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.
@@ -63,8 +61,9 @@ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
test/manifold_test
```
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
- `MANIFOLD_PAR=[<NONE>, TBB]`: Provides multi-thread parallelization, requires `libtbb-dev` if `TBB` backend is selected.
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`.
@@ -100,7 +99,7 @@ node test/manifold_test.js
### Python
The CMake script will build the python binding `manifold3d` automatically. To
use the extension, please add `$BUILD_DIR/bindings/python` to your `PYTHONPATH`, where
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:
```
@@ -123,11 +122,6 @@ For more detailed documentation, please refer to the C++ API.
Contributions are welcome! A lower barrier contribution is to simply make a PR that adds a test, especially if it repros an issue you've found. Simply name it prepended with DISABLED_, so that it passes the CI. That will be a very strong signal to me to fix your issue. However, if you know how to fix it yourself, then including the fix in your PR would be much appreciated!
## Profiling
There is now basic support for the [Tracy profiler](https://github.com/wolfpld/tracy) for our tests.
To enable tracing, compile with `-DTRACY_ENABLE=on` cmake option, and run the test with Tracy server running.
## 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).
-24
View File
@@ -106,30 +106,6 @@ ManifoldCrossSection *manifold_cross_section_intersection(
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
ManifoldCrossSection *cs) {
auto hulled = from_c(cs)->Hull();
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_batch_hull(
void *mem, ManifoldCrossSectionVec *css) {
auto hulled = CrossSection::Hull(*from_c(css));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
void *mem, ManifoldSimplePolygon *ps) {
auto hulled = CrossSection::Hull(*from_c(ps));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_hull_polygons(
void *mem, ManifoldPolygons *ps) {
auto hulled = CrossSection::Hull(*from_c(ps));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
ManifoldCrossSection *cs,
float x, float y) {
-29
View File
@@ -8,7 +8,6 @@ extern "C" {
#endif
// Polygons
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
size_t length);
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
@@ -25,7 +24,6 @@ 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);
@@ -96,14 +94,7 @@ ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset);
// Convex Hulls
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms);
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps, size_t length);
// Manifold Transformations
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
@@ -121,7 +112,6 @@ ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
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);
@@ -147,7 +137,6 @@ 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);
@@ -215,17 +204,6 @@ ManifoldCrossSection *manifold_cross_section_difference(
ManifoldCrossSection *manifold_cross_section_intersection(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
// CrossSection Convex Hulls
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
ManifoldCrossSection *cs);
ManifoldCrossSection *manifold_cross_section_batch_hull(
void *mem, ManifoldCrossSectionVec *css);
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
void *mem, ManifoldSimplePolygon *ps);
ManifoldCrossSection *manifold_cross_section_hull_polygons(
void *mem, ManifoldPolygons *ps);
// CrossSection Transformation
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
@@ -290,7 +268,6 @@ 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);
@@ -315,13 +292,11 @@ 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);
@@ -344,7 +319,6 @@ 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();
@@ -358,7 +332,6 @@ 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);
@@ -370,7 +343,6 @@ 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);
@@ -382,7 +354,6 @@ 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);
-20
View File
@@ -190,26 +190,6 @@ ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
return to_c(new (mem) Manifold(trimmed));
}
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m) {
auto hulled = from_c(m)->Hull();
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms) {
auto hulled = Manifold::Hull(*from_c(ms));
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps,
size_t length) {
std::vector<glm::vec3> vec(length);
for (int i = 0; i < length; ++i) {
vec[i] = {ps[i].x, ps[i].y, ps[i].z};
}
auto hulled = Manifold::Hull(vec);
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto v = glm::vec3(x, y, z);
+2 -5
View File
@@ -36,13 +36,10 @@ if __name__ == "__main__":
mesh = model.to_mesh()
if export_models:
if mesh.vert_properties.shape[1] > 3:
vertices = mesh.vert_properties[:, :3]
colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8)
vertices = np.hsplit(mesh.vert_properties, 3)[0]
else:
vertices = mesh.vert_properties
colors = None
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts,
vertex_colors=colors)
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()
-40
View File
@@ -1,40 +0,0 @@
from manifold3d import Manifold
import numpy as np
def fractal(holes, hole, w, position, depth, maxDepth):
w /= 3
holes.append(hole.scale([w, w, 1.0]).translate(
[position[0], position[1], 0.0]))
if depth == maxDepth:
return
offsets = np.array([
[-w, -w],
[-w, 0.0],
[-w, w],
[0.0, w],
[w, w],
[w, 0.0],
[w, -w],
[0.0, -w],
])
for offset in offsets:
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
def posColors(pos, _):
return [1 - p / 2 for p in pos] + [1.0]
def run(n=1):
result = Manifold.cube([1, 1, 1], True)
holes = []
fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n)
hole = Manifold.compose(holes)
result -= hole
result -= hole.rotate([90, 0, 0])
result -= hole.rotate([0, 90, 0])
return result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale(100)
-72
View File
@@ -96,24 +96,6 @@ PYBIND11_MODULE(manifold3d, m) {
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"hull", [](Manifold &self) { return self.Hull(); },
"Compute the convex hull of all points in this manifold.")
.def_static(
"batch_hull",
[](std::vector<Manifold> &ms) { return Manifold::Hull(ms); },
"Compute the convex hull enveloping a set of manifolds.")
.def_static(
"hull_points",
[](std::vector<Float3> &pts) {
std::vector<glm::vec3> vec(pts.size());
for (int i = 0; i < pts.size(); i++) {
vec[i] = {std::get<0>(pts[i]), std::get<1>(pts[i]),
std::get<2>(pts[i])};
}
return Manifold::Hull(vec);
},
"Compute the convex hull enveloping a set of 3d points.")
.def(
"transform",
[](Manifold &self, py::array_t<float> &mat) {
@@ -254,36 +236,6 @@ PYBIND11_MODULE(manifold3d, m) {
"the user to choose their function with discretion."
"\n\n"
":param warpFunc: A function that modifies a given vertex position.")
.def(
"set_properties",
[](Manifold &self, int newNumProp,
const std::function<py::array_t<float>(
Float3, const py::array_t<float> &)> &f) {
const int oldNumProp = self.NumProp();
return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f](
float *newProps,
glm::vec3 v,
const float *oldProps) {
auto array = f(std::make_tuple(v.x, v.y, v.z),
py::array(oldNumProp, oldProps));
if (array.ndim() != 1 || array.shape(0) != newNumProp)
throw std::runtime_error("Invalid vector shape, expected (" +
std::to_string(newNumProp) + ")");
auto array_view = array.unchecked<1>();
for (int i = 0; i < newNumProp; i++) newProps[i] = array_view(i);
});
},
py::arg("new_num_prop"), py::arg("f"),
"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."
"\n\n"
":param numProp: The new number of properties per vertex."
":param propFunc: A function that modifies the properties of a given "
"vertex.")
.def(
"refine", [](Manifold &self, int n) { return self.Refine(n); },
py::arg("n"),
@@ -458,13 +410,6 @@ PYBIND11_MODULE(manifold3d, m) {
.def_static(
"from_mesh", [](const MeshGL &mesh) { return Manifold(mesh); },
py::arg("mesh"))
.def_static(
"compose",
[](const std::vector<Manifold> &list) {
return Manifold::Compose(list);
},
"combine several manifolds into one without checking for "
"intersections.")
.def_static(
"tetrahedron", []() { return Manifold::Tetrahedron(); },
"Constructs a tetrahedron centered at the origin with one vertex at "
@@ -841,23 +786,6 @@ PYBIND11_MODULE(manifold3d, m) {
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"hull", [](CrossSection &self) { return self.Hull(); },
"Compute the convex hull of this cross-section.")
.def_static(
"batch_hull",
[](std::vector<CrossSection> &cs) { return CrossSection::Hull(cs); },
"Compute the convex hull enveloping a set of cross-sections.")
.def_static(
"hull_points",
[](std::vector<Float2> &pts) {
std::vector<glm::vec2> poly(pts.size());
for (int i = 0; i < pts.size(); i++) {
poly[i] = {std::get<0>(pts[i]), std::get<1>(pts[i])};
}
return CrossSection::Hull(poly);
},
"Compute the convex hull enveloping a set of 2d points.")
.def("decompose", &CrossSection::Decompose,
"This operation returns a vector of CrossSections that are "
"topologically disconnected, each containing one outline contour "
-9
View File
@@ -125,10 +125,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
function("_crossSectionUnionN", &cross_js::UnionN);
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
function("_crossSectionHullPoints",
select_overload<CrossSection(std::vector<glm::vec2>)>(
&CrossSection::Hull));
class_<Manifold>("Manifold")
.constructor(&man_js::FromMeshJS)
@@ -138,7 +134,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
.function("_Split", &man_js::Split)
.function("_SplitByPlane", &man_js::SplitByPlane)
.function("_TrimByPlane", &Manifold::TrimByPlane)
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
.function("_GetMeshJS", &js::GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &man_js::Warp)
@@ -181,10 +176,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
function("_manifoldUnionN", &man_js::UnionN);
function("_manifoldDifferenceN", &man_js::DifferenceN);
function("_manifoldIntersectionN", &man_js::IntersectionN);
function("_manifoldCollectVertices", &man_js::CollectVertices);
function("_manifoldHullPoints",
select_overload<Manifold(const std::vector<glm::vec3>&)>(
&Manifold::Hull));
// Quality Globals
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
-57
View File
@@ -472,36 +472,6 @@ Module.setup = function() {
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
function pushVec2(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
})
}
Module.CrossSection.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec2();
for (const cs of args) {
if (cs instanceof CrossSectionCtor) {
Module._crossSectionCollectVertices(pts, cs);
} else if (
cs instanceof Array && cs.length == 2 && typeof cs[0] == 'number') {
pts.push_back({x: cs[0], y: cs[1]});
} else if (cs.x) {
pts.push_back(cs);
} else {
const wrap =
((cs[0].length == 2 && typeof cs[0][0] == 'number') || cs[0].x);
const polys = wrap ? [cs] : cs;
for (const poly of polys) pushVec2(pts, poly);
}
}
const result = Module._crossSectionHullPoints(pts);
pts.delete();
return result;
};
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
@@ -626,33 +596,6 @@ Module.setup = function() {
return out;
};
function pushVec3(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
return p;
})
}
Module.Manifold.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec3();
for (const m of args) {
if (m instanceof ManifoldCtor) {
Module._manifoldCollectVertices(pts, m);
} else if (
m instanceof Array && m.length == 3 && typeof m[0] == 'number') {
pts.push_back({x: m[0], y: m[1], z: m[2]});
} else if (m.x) {
pts.push_back(m);
} else {
pushVec3(pts, m);
}
}
const result = Module._manifoldHullPoints(pts);
pts.delete();
return result;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
-1
View File
@@ -1 +0,0 @@
declare module '@jscadui/3mf-export';
+8 -43
View File
@@ -130,14 +130,18 @@ model-viewer {
.header .margin {
font-size: 16px;
margin: 1em 0.5em;
margin: 1em;
}
#file {
width: 250px;
}
.dropdown {
display: none;
position: absolute;
z-index: 1;
width: 100%;
width: 250px;
max-height: 80vh;
overflow-y: auto;
}
@@ -146,10 +150,6 @@ model-viewer {
display: block;
}
.hide {
display: none;
}
.uparrow {
display: inline-block;
position: relative;
@@ -174,7 +174,6 @@ model-viewer {
position: absolute;
width: 36px;
top: 0px;
right: 0px;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
@@ -194,6 +193,7 @@ model-viewer {
.trash {
background-image: url(/icons/trash.png);
right: 0px;
}
.icon:hover {
@@ -212,15 +212,6 @@ model-viewer {
background-color: rgb(255, 177, 177);
}
#file-margin {
position: relative;
width: 250px;
}
#save {
width: 125px;
}
@media only screen and (max-width: 600px) {
.container {
flex-direction: column;
@@ -229,31 +220,5 @@ model-viewer {
.col {
width: 100%;
height: 50%;
}
#file-margin {
width: calc(100% - 32px);
}
p {
margin: .2em 1em;
text-align: center;
}
.header .margin {
margin: 0.2em 16px;
}
#button-container {
flex-direction: row;
margin-left: 8px;
margin-right: 8px;
width: calc(100% - 16px);
}
#button-container .margin {
width: calc(50% - 16px);
margin: 8px;
}
}
}
+28 -62
View File
@@ -26,31 +26,20 @@ let editor = undefined;
// File UI ------------------------------------------------------------
const fileButton = document.querySelector('#file');
const currentFileElement = document.querySelector('#current');
const fileArrow = document.querySelector('#file .uparrow');
const fileDropdown = document.querySelector('#fileDropdown');
const saveContainer = document.querySelector('#save');
const saveDropdown = document.querySelector('#saveDropdown');
const saveArrow = document.querySelector('#save .uparrow');
const currentElement = document.querySelector('#current');
const arrow = document.querySelector('.uparrow');
const dropdown = document.querySelector('.dropdown');
const hideDropdown = function() {
fileDropdown.classList.remove('show');
saveDropdown.classList.remove('show');
fileArrow.classList.remove('down');
saveArrow.classList.remove('down');
dropdown.classList.remove('show');
arrow.classList.remove('down');
};
const toggleFileDropdown = function(event) {
const toggleDropdown = function(event) {
event.stopPropagation();
fileDropdown.classList.toggle('show');
fileArrow.classList.toggle('down');
dropdown.classList.toggle('show');
arrow.classList.toggle('down');
};
const toggleSaveDropdown = function(event) {
event.stopPropagation();
saveDropdown.classList.toggle('show');
saveArrow.classList.toggle('down');
};
fileButton.onclick = toggleFileDropdown;
saveArrow.parentElement.onclick = toggleSaveDropdown;
fileButton.onclick = toggleDropdown;
document.body.onclick = hideDropdown;
const prefix = 'ManifoldCAD';
@@ -73,7 +62,7 @@ function nthKey(n) {
function saveCurrent() {
if (editor) {
const currentName = currentFileElement.textContent;
const currentName = currentElement.textContent;
if (!exampleFunctions.get(currentName)) {
setScript(currentName, editor.getValue());
}
@@ -88,7 +77,7 @@ let isExample = true;
function switchTo(scriptName) {
if (editor) {
switching = true;
currentFileElement.textContent = scriptName;
currentElement.textContent = scriptName;
setScript('currentName', scriptName);
const code =
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
@@ -159,8 +148,8 @@ function addEdit(button) {
if (!input) return;
const newName = uniqueName(input);
label.textContent = newName;
if (currentFileElement.textContent == oldName) {
currentFileElement.textContent = newName;
if (currentElement.textContent == oldName) {
currentElement.textContent = newName;
}
removeScript(oldName);
setScript(newName, code);
@@ -193,7 +182,7 @@ function addEdit(button) {
}, {once: true});
} else if (performance.now() - lastClick > 500) {
removeScript(label.textContent);
if (currentFileElement.textContent == label.textContent) {
if (currentElement.textContent == label.textContent) {
switchTo('Intro');
}
const container = button.parentElement;
@@ -280,10 +269,10 @@ require(['vs/editor/editor.main'], async function() {
for (const [name] of exampleFunctions) {
const button = createDropdownItem(name);
fileDropdown.appendChild(button.parentElement);
dropdown.appendChild(button.parentElement);
}
let currentName = currentFileElement.textContent;
let currentName = currentElement.textContent;
for (let i = 0; i < window.localStorage.length; i++) {
const key = nthKey(i);
@@ -358,10 +347,7 @@ function finishRun() {
}
const mv = document.querySelector('model-viewer');
const output = {
glbURL: null,
threeMFURL: null
};
let objectURL = null;
let manifoldWorker = null;
function createWorker() {
@@ -384,16 +370,10 @@ function createWorker() {
finishRun();
runButton.disabled = true;
if (output.threeMFURL != null) {
URL.revokeObjectURL(output.threeMFURL);
output.threeMFURL = null;
}
URL.revokeObjectURL(output.glbURL);
output.glbURL = e.data.glbURL;
output.threeMFURL = e.data.threeMFURL;
threemfButton.disabled = output.threeMFURL == null;
mv.src = output.glbURL;
if (output.glbURL == null) {
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
mv.src = objectURL;
if (objectURL == null) {
mv.showPoster();
poster.textContent = 'Error';
createWorker();
@@ -431,24 +411,10 @@ runButton.onclick = function() {
}
};
function clickSave(saveButton, filename, outputName) {
const container = saveButton.parentElement;
return () => {
const oldSave = saveContainer.firstElementChild;
if (oldSave !== container) {
saveDropdown.insertBefore(oldSave, saveDropdown.firstElementChild);
saveContainer.insertBefore(container, saveDropdown);
container.appendChild(saveArrow.parentElement);
}
const link = document.createElement('a');
link.download = filename;
link.href = output[outputName];
link.click();
};
}
const glbButton = document.querySelector('#glb');
glbButton.onclick = clickSave(glbButton, 'manifold.glb', 'glbURL');
const threemfButton = document.querySelector('#threemf');
threemfButton.onclick = clickSave(threemfButton, 'manifold.3mf', 'threeMFURL');
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
+6 -18
View File
@@ -40,27 +40,15 @@
<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" id="file-margin" >
<button type="button" id="file" class="blue item"><b class="uparrow"></b>&nbsp;&nbsp;<span
<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" id="fileDropdown">
<div class="dropdown">
<button type="button" class="green item" id="new">New</button>
</div>
</div>
<div class="container" id="button-container">
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
<div id="save" class="margin" style="position: relative; ">
<div class="item">
<button type="button" id="glb" class="blue item">Save GLB</button>
<button type="button" class="icon"><b class="uparrow" style="right: 2px;"></b></button>
</div>
<div class="dropdown" id="saveDropdown">
<div class="item">
<button type="button" id="threemf" class="blue item">Save 3MF</button>
</div>
</div>
</div>
</div>
<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>
@@ -86,4 +74,4 @@
<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>
</html>
+4 -16
View File
@@ -11,8 +11,6 @@
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"@jscadui/3mf-export": "^0.4.2",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.151.2"
@@ -413,11 +411,6 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jscadui/3mf-export": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@jscadui/3mf-export/-/3mf-export-0.4.2.tgz",
"integrity": "sha512-VFRxT/wNCVEu4bsY1dwjFvC0WaoGsYbl52YPGr3Ka3GwvQ4Sw9EJsA/SlIdUnCCTtxmC662LWcgDNtgOc3Uayg=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -588,12 +581,6 @@
"vitest": ">=0.30.1 <1"
}
},
"node_modules/@vitest/ui/node_modules/fflate": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
"dev": true
},
"node_modules/@vitest/utils": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.31.1.tgz",
@@ -1013,9 +1000,10 @@
}
},
"node_modules/fflate": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.0.tgz",
"integrity": "sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg=="
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
"dev": true
},
"node_modules/fill-range": {
"version": "7.0.1",
+1 -3
View File
@@ -16,8 +16,6 @@
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"@jscadui/3mf-export": "^0.4.2",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.151.2"
@@ -29,4 +27,4 @@
"vite": "^4.3.2",
"vitest": "^0.31.1"
}
}
}
+6 -6
View File
@@ -39,7 +39,7 @@ function initialized(worker) {
});
}
let glbURL = null;
let objectURL = null;
async function runExample(name) {
const worker = new ManifoldWorker();
@@ -55,12 +55,12 @@ async function runExample(name) {
worker.onmessage = async function(e) {
try {
URL.revokeObjectURL(glbURL);
glbURL = e.data.glbURL;
if (glbURL == null) {
reject('no glbURL)');
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
if (objectURL == null) {
reject('no objectURL');
}
const docIn = await io.read(glbURL);
const docIn = await io.read(objectURL);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const mesh = node.getMesh();
+21 -123
View File
@@ -12,10 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Document, mat4, Material, Node, WebIO} from '@gltf-transform/core';
import {Document, Material, Node, WebIO} from '@gltf-transform/core';
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
import {fileForContentTypes, to3dmodel} from '@jscadui/3mf-export';
import {strToU8, Zippable, zipSync} from 'fflate'
import * as glMatrix from 'gl-matrix';
import Module from './built/manifold';
@@ -44,23 +42,23 @@ io.registerExtensions(KHRONOS_EXTENSIONS);
// manifold static methods (that return a new manifold)
const manifoldStaticFunctions = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'compose',
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh', 'hull'
'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', 'hull'
'trimByPlane', 'split', 'splitByPlane'
];
// CrossSection static methods (that return a new cross-section)
const crossSectionStaticFunctions = [
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
'ofPolygons', 'hull'
'ofPolygons'
];
// CrossSection member functions (that return a new cross-section)
const crossSectionMemberFunctions = [
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset', 'hull'
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset'
];
// top level functions that construct a new manifold/mesh
const toplevelConstructors = ['show', 'only', 'setMaterial'];
@@ -144,8 +142,6 @@ const GHOST = {
const nodes = new Array<GLTFNode>();
const id2material = new Map<number, GLTFMaterial>();
const materialCache = new Map<GLTFMaterial, Material>();
let nextGlobalID = 0;
const object2globalID = new Map<GLTFNode|Manifold, number>();
function cleanup() {
ghost = false;
@@ -154,45 +150,6 @@ function cleanup() {
nodes.length = 0;
id2material.clear();
materialCache.clear();
object2globalID.clear();
nextGlobalID = 0;
}
interface Mesh3MF {
id: string;
vertices: Float32Array;
indices: Uint32Array;
name?: string;
}
interface Child3MF {
objectID: string;
transform?: mat4;
}
interface Component3MF {
id: string;
children: Array<Child3MF>;
name?: string;
}
interface Header {
unit?: 'micron'|'millimeter'|'centimeter'|'inch'|'foot'|'meter';
title?: string;
author?: string;
description?: string;
application?: string;
creationDate?: string;
license?: string;
modificationDate?: string;
}
interface To3MF {
meshes: Array<Mesh3MF>;
components: Array<Component3MF>;
items: Array<Child3MF>;
precision: number;
header: Header;
}
class GLTFNode {
@@ -272,12 +229,12 @@ function log(...args: any[]) {
self.onmessage = async (e) => {
const content = e.data +
'\nreturn exportModels(typeof result === "undefined" ? undefined : result);\n';
'\nreturn exportGLB(typeof result === "undefined" ? undefined : result);\n';
try {
const f = new Function(
'exportModels', 'glMatrix', 'module', ...exposedFunctions, content);
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
await f(
exportModels, glMatrix, module, //@ts-ignore
exportGLB, glMatrix, module, //@ts-ignore
...exposedFunctions.map(name => module[name]));
} catch (error: any) {
console.log(error.toString());
@@ -350,7 +307,7 @@ function getCachedMaterial(doc: Document, matDef: GLTFMaterial): Material {
}
function addMesh(
doc: Document, to3mf: To3MF, node: Node, manifold: Manifold,
doc: Document, node: Node, manifold: Manifold,
backupMaterial: GLTFMaterial = {}) {
const numTri = manifold.numTri();
if (numTri == 0) {
@@ -387,20 +344,6 @@ function addMesh(
node.setMesh(writeMesh(doc, manifoldMesh, attributes, gltfMaterials));
const vertices = manifoldMesh.numProp === 3 ?
manifoldMesh.vertProperties :
new Float32Array(manifoldMesh.numVert * 3);
if (manifoldMesh.numProp > 3) {
for (let i = 0; i < manifoldMesh.numVert; ++i) {
for (let j = 0; j < 3; ++j)
vertices[i * 3 + j] =
manifoldMesh.vertProperties[i * manifoldMesh.numProp + j];
}
}
object2globalID.set(manifold, nextGlobalID);
to3mf.meshes.push(
{vertices, indices: manifoldMesh.triVerts, id: `${nextGlobalID++}`});
for (const [run, id] of manifoldMesh.runOriginalID!.entries()) {
let inMesh = shown.get(id);
let single = false;
@@ -445,18 +388,17 @@ function cloneNodeNewMaterial(
}
function createNodeFromCache(
doc: Document, to3MF: To3MF, nodeDef: GLTFNode,
doc: Document, nodeDef: GLTFNode,
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
const node = createGLTFnode(doc, nodeDef);
const {manifold} = nodeDef;
if (manifold != null) {
if (nodeDef.manifold != null) {
const backupMaterial = getBackupMaterial(nodeDef);
const cachedNodes = manifold2node.get(manifold);
const cachedNodes = manifold2node.get(nodeDef.manifold);
if (cachedNodes == null) {
addMesh(doc, to3MF, node, manifold, backupMaterial);
addMesh(doc, node, nodeDef.manifold, backupMaterial);
const cache = new Map<GLTFMaterial, Node>();
cache.set(backupMaterial, node);
manifold2node.set(manifold, cache);
manifold2node.set(nodeDef.manifold, cache);
} else {
const cachedNode = cachedNodes.get(backupMaterial);
if (cachedNode == null) {
@@ -470,18 +412,10 @@ function createNodeFromCache(
}
}
}
object2globalID.set(nodeDef, nextGlobalID);
to3MF.components.push({
id: `${nextGlobalID++}`,
name: nodeDef.name,
children:
manifold == null ? [] : [{objectID: `${object2globalID.get(manifold)}`}]
});
return node;
}
async function exportModels(manifold?: Manifold) {
async function exportGLB(manifold?: Manifold) {
const doc = new Document();
const halfRoot2 = Math.sqrt(2) / 2;
const mm2m = 1 / 1000;
@@ -490,27 +424,13 @@ async function exportModels(manifold?: Manifold) {
.setScale([mm2m, mm2m, mm2m]);
doc.createScene().addChild(wrapper);
const to3mf = {
meshes: [],
components: [],
items: [],
precision: 7,
header: {
unit: 'millimeter',
title: 'ManifoldCAD.org model',
description: 'ManifoldCAD.org model',
application: 'ManifoldCAD.org',
}
} as To3MF;
if (nodes.length > 0) {
const node2gltf = new Map<GLTFNode, Node>();
const manifold2node = new Map<Manifold, Map<GLTFMaterial, Node>>();
let leafNodes = 0;
for (const nodeDef of nodes) {
node2gltf.set(
nodeDef, createNodeFromCache(doc, to3mf, nodeDef, manifold2node));
node2gltf.set(nodeDef, createNodeFromCache(doc, nodeDef, manifold2node));
if (nodeDef.manifold) {
++leafNodes;
}
@@ -518,19 +438,10 @@ async function exportModels(manifold?: Manifold) {
for (const nodeDef of nodes) {
const gltfNode = node2gltf.get(nodeDef)!;
const child = {
objectID: `${object2globalID.get(nodeDef)}`,
transform: gltfNode.getMatrix()
};
const {parent} = nodeDef;
if (parent == null) {
if (nodeDef.parent == null) {
wrapper.addChild(gltfNode);
to3mf.items.push(child);
} else {
node2gltf.get(parent)!.addChild(gltfNode);
const parent3mf = to3mf.components.find(
(comp) => comp.id == `${object2globalID.get(parent)}`)!;
parent3mf.children.push(child);
node2gltf.get(nodeDef.parent)!.addChild(gltfNode);
}
}
@@ -542,25 +453,12 @@ async function exportModels(manifold?: Manifold) {
return;
}
const node = doc.createNode();
addMesh(doc, to3mf, node, manifold);
addMesh(doc, node, manifold);
wrapper.addChild(node);
to3mf.items.push({objectID: `${object2globalID.get(manifold)}`});
}
const glb = await io.writeBinary(doc);
const blobGLB = new Blob([glb], {type: 'application/octet-stream'});
const model = to3dmodel(to3mf);
const files: Zippable = {};
files['3D/3dmodel.model'] = strToU8(model);
files[fileForContentTypes.name] = strToU8(fileForContentTypes.content);
const zipFile = zipSync(files);
const blob3MF = new Blob(
[zipFile],
{type: 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'});
self.postMessage({
glbURL: URL.createObjectURL(blobGLB),
threeMFURL: URL.createObjectURL(blob3MF)
});
const blob = new Blob([glb], {type: 'application/octet-stream'});
self.postMessage({objectURL: URL.createObjectURL(blob)});
}
-10
View File
@@ -159,11 +159,6 @@ CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
: CrossSection::JoinType::Miter;
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
}
void CollectVertices(std::vector<glm::vec2>& verts, const CrossSection& cs) {
auto polys = cs.ToPolygons();
for (auto poly : polys) verts.insert(verts.end(), poly.begin(), poly.end());
}
} // namespace cross_js
namespace man_js {
@@ -224,9 +219,4 @@ std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
auto [a, b] = m.SplitByPlane(normal, originOffset);
return {a, b};
}
void CollectVertices(std::vector<glm::vec3>& verts, const Manifold& manifold) {
Mesh mesh = manifold.GetMesh();
verts.insert(verts.end(), mesh.vertPos.begin(), mesh.vertPos.end());
}
} // namespace man_js
+5 -28
View File
@@ -111,7 +111,6 @@ export class CrossSection {
* @param v The vector to add to every vertex.
*/
translate(v: Vec2): CrossSection;
translate(x: number, y?: number): CrossSection;
/**
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
@@ -174,6 +173,11 @@ export class CrossSection {
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
@@ -242,18 +246,6 @@ export class CrossSection {
*/
static intersection(polygons: (CrossSection|Polygons)[]): CrossSection;
// Convex Hulls
/**
* Compute the convex hull of the contours in this CrossSection.
*/
hull(): CrossSection;
/**
* Compute the convex hull of all points in a list of polygons/cross-sections.
*/
static hull(polygons: (CrossSection|Polygons)[]): CrossSection;
// Topological Operations
/**
@@ -515,7 +507,6 @@ export class Manifold {
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
translate(x: number, y?: number, z?: number): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
@@ -528,7 +519,6 @@ export class Manifold {
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
rotate(x: number, y?: number, z?: number): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
@@ -685,19 +675,6 @@ export class Manifold {
*/
trimByPlane(normal: Vec3, originOffset: number): Manifold;
// Convex Hulls
/**
* Compute the convex hull of all points in this Manifold.
*/
hull(): Manifold;
/**
* Compute the convex hull of all points contained within a set of Manifolds
* and point vectors.
*/
static hull(points: (Manifold|Vec3)[]): Manifold;
// Topological Operations
/**
-7
View File
@@ -24,13 +24,6 @@ target_link_libraries(largeSceneTest manifold)
target_compile_options(largeSceneTest PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(largeSceneTest PUBLIC cxx_std_17)
if(MANIFOLD_DEBUG)
add_executable(minimizeTestcase minimize_testcase.cpp)
target_link_libraries(minimizeTestcase polygon)
target_compile_options(minimizeTestcase PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(minimizeTestcase PUBLIC cxx_std_17)
endif()
if(MANIFOLD_EXPORT)
add_executable(convertFile convert_file.cpp)
target_link_libraries(convertFile manifold meshIO)
-391
View File
@@ -1,391 +0,0 @@
#include <algorithm>
#include <fstream>
#include <string>
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
#include <execution>
#endif
#include "polygon.h"
#include "utils.h"
using namespace manifold;
inline float cross(glm::vec2 p, glm::vec2 q) { return p.x * q.y - p.y * q.x; }
// return true if p intersects with q
// note that we don't care about collinear, intersection in the ends etc.
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
inline bool intersect(glm::vec2 p0, glm::vec2 p1, glm::vec2 q0, glm::vec2 q1,
float precision) {
glm::vec2 r = p1 - p0;
glm::vec2 s = q1 - q0;
// allow some error in the intersection point
float epsilon_r = 2.0 * precision / glm::length(r);
float epsilon_s = 2.0 * precision / glm::length(s);
float rxs = cross(r, s);
// in case they are nearly collinear, ignore them...
// this is to avoid treating degenerate triangles as intersecting
//
// note that this does not correspond to a fixed angle,
// but seems to work well enough
if (rxs < kTolerance && rxs > -kTolerance) {
return false;
}
float u = cross(q0 - p0, r) / rxs;
float t = cross(q0 - p0, s) / rxs;
// we only care about intersection in the middle of both lines, excluding
// their ends
if (epsilon_r <= u && u <= (1 - epsilon_r) && //
epsilon_s <= t && t <= (1 - epsilon_s)) {
// there can be cases which r and s are close to collinear,
// so even though the lines are not intersecting,
// perturbation along r/s is not enough.
//
// in that case, apply perturbation perpendicular to r
glm::vec2 r_orth = glm::normalize(glm::vec2(-r.y, r.x)) * precision;
float u1 = cross(q0 - p0 + r_orth, r) / rxs;
float u2 = cross(q0 - p0 - r_orth, r) / rxs;
float t1 = cross(q0 - p0 + r_orth, s) / rxs;
float t2 = cross(q0 - p0 - r_orth, s) / rxs;
return 0 <= u1 && u1 <= 1 && //
0 <= t1 && t1 <= 1 && //
0 <= u2 && u2 <= 1 && //
0 <= t2 && t2 <= 1;
return true;
} else {
return false;
}
}
// check if removing point j in polygon i will introduce self-intersection
// this checks if the new edge j-1 <-> j+1 intersects with any other edges,
// assuming the original polygon does not contain self-intersection
bool safeToRemove(const Polygons &polys, int i, int j, float precision) {
if (polys[i].size() == 3) return false;
glm::vec2 prev = polys[i][j == 0 ? polys[i].size() - 1 : (j - 1)];
glm::vec2 next = polys[i][j == (polys[i].size() - 1) ? 0 : (j + 1)];
for (int k = 0; k < polys.size(); k++) {
auto ll = [&](size_t l) {
return l == (polys[k].size() - 1) ? 0 : (l + 1);
};
const glm::vec2 *polysk = polys[k].data();
if (!std::all_of(
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
std::execution::par,
#endif
countAt((size_t)0), countAt(polys[k].size()), [=](size_t l) {
if (i == k && (l == j || ll(l) == j)) return true;
return !intersect(prev, next, polysk[l], polysk[ll(l)],
precision);
}))
return false;
}
return true;
}
std::pair<int, int> findIndex(const Polygons &polys, int i) {
int outer = 0;
while (i >= polys[outer].size()) i -= polys[outer++].size();
return std::make_pair(outer, i);
}
void Dump(const Polygons &polys) {
for (const SimplePolygon &poly : polys) {
std::cout << "polys.push_back({" << std::setprecision(9) << std::endl;
for (const glm::vec2 v : poly) {
std::cout << " {" << v.x << ", " << v.y << "}, //" << std::endl;
}
std::cout << "});" << std::endl;
}
for (const SimplePolygon &poly : polys) {
std::cout << "show(array([" << std::endl;
for (const glm::vec2 v : poly) {
std::cout << " [" << v.x << ", " << v.y << "]," << std::endl;
}
std::cout << "]))" << std::endl;
}
}
void DumpTriangulation(const Polygons &polys, float precision) {
ExecutionParams oldParams = PolygonParams();
manifold::PolygonParams().processOverlaps = true;
std::vector<glm::ivec3> result = Triangulate(polys);
PolygonParams() = oldParams;
for (const glm::ivec3 &tri : result) {
std::pair<int, int> xid = findIndex(polys, tri.x);
std::pair<int, int> yid = findIndex(polys, tri.y);
std::pair<int, int> zid = findIndex(polys, tri.z);
glm::vec2 x = polys[xid.first][xid.second];
glm::vec2 y = polys[yid.first][yid.second];
glm::vec2 z = polys[zid.first][zid.second];
printf("show(array([[%.7f, %.7f], [%.7f, %.7f], [%.7f, %.7f]]))\n", x.x,
x.y, y.x, y.y, z.x, z.y);
if (CCW(x, y, z, precision) == -1) {
printf("^ not CCW\n");
}
}
}
// x direction ray
// idk which direction, but it is a fixed direction...
// https://stackoverflow.com/questions/11716268/point-in-polygon-algorithm
bool rayHit(glm::vec2 point, glm::vec2 q0, glm::vec2 q1) {
return ((q0.y >= point.y) != (q1.y >= point.y)) &&
(point.x <= (q1.x - q0.x) * (point.y - q0.y) / (q1.y - q0.y) + q0.x);
}
// get the list of polygons inside polygon i
// this enumerates over the first point in all other polygons,
// and check if they are inside the current polygon
// this algorithm assumes that polygons are non-intersecting
std::vector<int> getChildren(const Polygons &polys, int i) {
std::vector<int> results;
for (int j = 0; j < polys.size(); j++) {
if (i == j) continue;
glm::vec2 point = polys[j][0];
auto k1 = [&](size_t k) {
return k == (polys[i].size() - 1) ? 0 : (k + 1);
};
int count = std::count_if(
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
std::execution::par,
#endif
countAt((size_t)0), countAt(polys[i].size()),
[&](size_t k) { return rayHit(point, polys[i][k], polys[i][k1(k)]); });
if (count % 2) results.push_back(j);
}
return results;
}
// assuming polys is valid
// we try to remove vertices from polys such that
// 1. the updated polys is still valid (no overlapping edges, correct winding
// direction)
// 2. same error in triangulation (either geometryErr or overlapping triangles)
void simplify(Polygons &polys, float precision = -1) {
bool removedSomething = true;
std::string msg;
while (removedSomething) {
int points = 0;
for (const SimplePolygon &poly : polys) points += poly.size();
std::cout << "trying to simplify " << points << " points" << std::endl;
removedSomething = false;
// try to remove simple polygons
for (int i = 0; i < polys.size(); i++) {
std::vector<int> children = getChildren(polys, i);
// if there are children, we can't remove it or we will mess up with
// winding direction...
if (!children.empty()) continue;
SimplePolygon poly = std::move(polys[i]);
polys.erase(polys.begin() + i);
try {
std::vector<glm::ivec3> result = Triangulate(polys, precision);
polys.insert(polys.begin() + i, std::move(poly));
} catch (geometryErr &e) {
if (msg.size() > 0 && msg.compare(e.what()) != 0) {
polys.insert(polys.begin() + i, std::move(poly));
} else {
removedSomething = true;
msg = e.what();
}
} catch (topologyErr &e) {
polys.insert(polys.begin() + i, std::move(poly));
}
}
for (int i = 0; i < polys.size(); i++) {
std::vector<int> children = getChildren(polys, i);
for (int j = 0; j < polys[i].size(); j++) {
// removed vertex cannot change inclusion relation
// we just check if the vertex
// x: intersects with j0, j (original edge 1)
// y: intersects with j, j1 (original edge 2)
// z: intersects with j0, j1 (new edge)
// if (x ^ y ^ z) is true, it means that the count % 2 is changed,
// and we changed inclusion relation, so vertex j cannot be removed
int j0 = j == 0 ? polys[i].size() - 1 : j - 1;
int j1 = j == polys[i].size() - 1 ? 0 : j + 1;
if (std::any_of(children.begin(), children.end(), [&](int k) {
return rayHit(polys[k][0], polys[i][j0], polys[i][j]) ^
rayHit(polys[k][0], polys[i][j], polys[i][j1]) ^
rayHit(polys[k][0], polys[i][j0], polys[i][j1]);
}))
continue;
// removed vertex cannot introduce intersection
if (!safeToRemove(polys, i, j, precision)) continue;
glm::vec2 removed = polys[i][j];
polys[i].erase(polys[i].begin() + j);
try {
std::vector<glm::ivec3> result = Triangulate(polys, precision);
polys[i].insert(polys[i].begin() + j, removed);
} catch (geometryErr &e) {
if (msg.size() > 0 && msg.compare(e.what()) != 0) {
polys[i].insert(polys[i].begin() + j, removed);
} else {
removedSomething = true;
msg = e.what();
}
} catch (topologyErr &e) {
polys[i].insert(polys[i].begin() + j, removed);
}
}
}
}
std::cout << "Error message for triangulation:" << std::endl
<< msg.c_str() << std::endl;
}
struct Edge {
glm::vec2 west; // -x side vertex
glm::vec2 east; // +x side vertex
size_t i, j; // indices of origin vertex
bool operator<(const Edge &other) const {
return west.x < other.west.x ||
(west.x == other.west.x && east.x < other.east.x);
}
Edge(const Polygons &polys, size_t i, size_t j) : i(i), j(j) {
size_t j1 = j == polys[i].size() - 1 ? 0 : j + 1;
east = polys[i][j];
west = polys[i][j1];
if (west.x > east.x) std::swap(east, west);
}
};
int isValid(const Polygons &polys, float precision = -1) {
size_t numEdge = 0;
for (const SimplePolygon &poly : polys) numEdge += poly.size();
std::vector<Edge> edges;
edges.reserve(numEdge);
for (size_t i = 0; i < polys.size(); i++)
for (size_t j = 0; j < polys[i].size(); j++)
edges.push_back(Edge(polys, i, j));
std::sort(edges.begin(), edges.end());
// check intersection
std::mutex mutex;
std::vector<std::pair<Edge, Edge>> overlappingPairs;
std::for_each( //
countAt((size_t)0), countAt(edges.size()), [&](size_t i) {
// check all subsequent edges e' until e'.west.x > e.east.x
// this ensures we checked all edges e' where
// e.west.x < e'.west.x < e.east.x
Edge e = edges[i];
for (size_t j = i + 1; j < edges.size() && edges[j].west.x < e.east.x;
j++) {
if (intersect(e.west, e.east, edges[j].west, edges[j].east,
precision)) {
mutex.lock();
overlappingPairs.push_back(std::make_pair(e, edges[j]));
mutex.unlock();
}
}
});
if (!overlappingPairs.empty()) {
std::cout << "found " << overlappingPairs.size() << " self-intersection"
<< std::endl;
for (const std::pair<Edge, Edge> &pairs : overlappingPairs) {
std::cout << pairs.first.i << " " << pairs.first.j << "<->" //
<< pairs.second.i << " " << pairs.second.j << std::endl;
printf("array([[%.7f, %.7f], [%.7f, %.7f]])\n", pairs.first.west.x,
pairs.first.west.y, pairs.first.east.x, pairs.first.east.y);
printf("array([[%.7f, %.7f], [%.7f, %.7f]])\n", pairs.second.west.x,
pairs.second.west.y, pairs.second.east.x, pairs.second.east.y);
}
} else {
std::cout << "No self-intersection" << std::endl;
}
return 0;
}
int main(int argc, char **argv) {
PolygonParams().intermediateChecks = true;
PolygonParams().processOverlaps = false;
PolygonParams().suppressErrors = true;
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <input file>" << std::endl;
return 0;
}
std::ifstream fin(argv[1]);
std::string line;
float precision = -1;
Polygons polys;
SimplePolygon poly;
// search for precision first
while (std::getline(fin, line)) {
int index = line.find("Precision = ");
if (index != std::string::npos) {
std::istringstream iss(line.substr(index + 12));
if (!(iss >> precision)) {
std::cerr << "Error parsing precision" << std::endl;
return 1;
}
break;
}
}
std::cout << "Precision = " << precision << std::endl;
// fill polygons
while (std::getline(fin, line)) {
if (line.length() > 5 && line.substr(0, 5).compare(" {") == 0) {
std::istringstream iss(line.substr(5));
float x, y;
if (!(iss >> x)) {
std::cerr << "Error parsing coordinate:" << std::endl;
std::cerr << "Line: " << line << std::endl;
return 1;
}
iss.ignore(2);
if (!(iss >> y)) {
std::cerr << "Error parsing coordinate:" << std::endl;
std::cerr << "Line: " << line << std::endl;
return 1;
}
poly.push_back(glm::vec2(x, y));
} else if (line.length() >= 3 && line.substr(0, 3).compare("});") == 0) {
if (poly.size() < 3) {
std::cerr << "Error: empty/invalid polygon" << std::endl;
return 1;
}
polys.push_back(std::move(poly));
poly.clear();
} else if (line.length() >= 5 && line.substr(0, 5).compare("array") == 0) {
break;
}
}
if (polys.size() == 0) {
std::cerr << "Error: empty polygon" << std::endl;
return 1;
}
if (precision == -1) {
float bound = 0;
for (const SimplePolygon &poly : polys) {
for (const glm::vec2 &pt : poly) {
bound = glm::max(bound, glm::abs(pt.x));
bound = glm::max(bound, glm::abs(pt.y));
}
}
precision = bound * kTolerance;
}
std::cout << "------------" << std::endl;
isValid(polys, precision);
simplify(polys, precision);
std::cout << "------------" << std::endl;
std::cout << "Final polygon:" << std::endl;
Dump(polys);
std::cout << "------------" << std::endl;
std::cout << "Erroneous triangulation:" << std::endl;
DumpTriangulation(polys, precision);
return 0;
}
Generated
+6 -42
View File
@@ -1,15 +1,12 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"lastModified": 1649676176,
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
"type": "github"
},
"original": {
@@ -18,30 +15,13 @@
"type": "github"
}
},
"gtest-src": {
"flake": false,
"locked": {
"lastModified": 1690989893,
"narHash": "sha256-t0RchAHTJbuI5YW4uyBPykTvcjy90JW9AOPNjIhwh6U=",
"owner": "google",
"repo": "googletest",
"rev": "f8d7d77c06936315286eb55f8de22cd23c188571",
"type": "github"
},
"original": {
"owner": "google",
"ref": "v1.14.0",
"repo": "googletest",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1691006197,
"narHash": "sha256-DbtxVWPt+ZP5W0Usg7jAyTomIM//c3Jtfa59Ht7AV8s=",
"lastModified": 1689008574,
"narHash": "sha256-VFMgyHDiqsGDkRg73alv6OdHJAqhybryWHv77bSCGIw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "66aedfd010204949cb225cf749be08cb13ce1813",
"rev": "4a729ce4b1fe5ec4fffc71c67c96aa5184ebb462",
"type": "github"
},
"original": {
@@ -53,24 +33,8 @@
"root": {
"inputs": {
"flake-utils": "flake-utils",
"gtest-src": "gtest-src",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
+36 -17
View File
@@ -1,12 +1,8 @@
{
inputs.flake-utils.url = "github:numtide/flake-utils";
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
inputs.gtest-src = {
url = "github:google/googletest/v1.14.0";
flake = false;
};
outputs = { self, nixpkgs, flake-utils, gtest-src }:
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let
@@ -16,23 +12,25 @@
};
manifold =
{ parallel-backend ? "none"
, cuda-support ? false
, doCheck ? true
, build-tools ? [ ]
, ...
}: pkgs.stdenv.mkDerivation {
inherit doCheck;
pname = "manifold-${parallel-backend}";
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 ]))
gtest
]) ++ build-tools;
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
@@ -50,11 +48,24 @@
};
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
@@ -64,8 +75,6 @@
emscripten
tbb
lcov
gtest
tracy
] ++ additional;
};
in
@@ -73,10 +82,11 @@
packages = (builtins.listToAttrs
(map
(x: {
name = "manifold-" + x.parallel-backend;
name = "manifold-" + x.parallel-backend + (if
x.cuda-support then "-cuda" else "");
value = manifold x;
})
parallelBackends)) // {
buildMatrix)) // {
manifold-js = pkgs.buildEmscriptenPackage {
name = "manifold-js";
version = "beta";
@@ -88,7 +98,9 @@
export EM_CACHE=$(pwd)/.emscriptencache
mkdir build
cd build
emcmake cmake -DCMAKE_BUILD_TYPE=Release -DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=${gtest-src} ..
mkdir cache
export EM_CACHE=$(pwd)/cache
emcmake cmake -DCMAKE_BUILD_TYPE=Release ..
'';
buildPhase = ''
emmake make -j''${NIX_BUILD_CORES}
@@ -106,6 +118,13 @@
};
};
devShell = devShell { };
devShells.cuda = devShell {
additional = with pkgs.cudaPackages; [
cuda_nvcc
cuda_cudart
cuda_cccl
];
};
}
);
}
+5
View File
@@ -31,6 +31,11 @@ project(samplesGPU)
add_library(samplesGPU src/gyroid_module.cpp)
if(MANIFOLD_USE_CUDA)
set_source_files_properties(src/gyroid_module.cpp PROPERTIES LANGUAGE CUDA)
set_property(TARGET samplesGPU PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_include_directories(samplesGPU
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
+6 -1
View File
@@ -15,7 +15,12 @@
project (collider)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} ${SOURCE_FILES})
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
if(MANIFOLD_USE_CUDA)
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
+1 -2
View File
@@ -26,8 +26,7 @@ class Collider {
Collider(const VecDH<Box>& leafBB, const VecDH<uint32_t>& leafMorton);
bool Transform(glm::mat4x3);
void UpdateBoxes(const VecDH<Box>& leafBB);
template <const bool selfCollision = false, const bool inverted = false,
typename T>
template <const bool selfCollision = false, typename T>
SparseIndices Collisions(const VecDH<T>& queriesIn) const;
private:
+59 -108
View File
@@ -24,7 +24,6 @@
// Adjustable parameters
constexpr int kInitialLength = 128;
constexpr int kLengthMultiple = 4;
constexpr int kSequentialThreshold = 512;
// Fundamental constants
constexpr int kRoot = 1;
@@ -60,30 +59,36 @@ uint32_t __inline clz(uint32_t value) {
namespace {
using namespace manifold;
bool IsLeaf(int node) { return node % 2 == 0; }
bool IsInternal(int node) { return node % 2 == 1; }
int Node2Internal(int node) { return (node - 1) / 2; }
int Internal2Node(int internal) { return internal * 2 + 1; }
int Node2Leaf(int node) { return node / 2; }
int Leaf2Node(int leaf) { return leaf * 2; }
__host__ __device__ bool IsLeaf(int node) { return node % 2 == 0; }
__host__ __device__ bool IsInternal(int node) { return node % 2 == 1; }
__host__ __device__ int Node2Internal(int node) { return (node - 1) / 2; }
__host__ __device__ int Internal2Node(int internal) { return internal * 2 + 1; }
__host__ __device__ int Node2Leaf(int node) { return node / 2; }
__host__ __device__ int Leaf2Node(int leaf) { return leaf * 2; }
struct CreateRadixTree {
int* nodeParent_;
thrust::pair<int, int>* internalChildren_;
const VecDc<uint32_t> leafMorton_;
int PrefixLength(uint32_t a, uint32_t b) const {
__host__ __device__ int PrefixLength(uint32_t a, uint32_t b) const {
// count-leading-zeros is used to find the number of identical highest-order
// bits
#ifdef __CUDA_ARCH__
return __clz(a ^ b);
#else
#ifdef _MSC_VER
// return __lzcnt(a ^ b);
return clz(a ^ b);
#else
return __builtin_clz(a ^ b);
#endif
#endif
}
int PrefixLength(int i, int j) const {
__host__ __device__ int PrefixLength(int i, int j) const {
if (j < 0 || j >= leafMorton_.size()) {
return -1;
} else {
@@ -98,7 +103,7 @@ struct CreateRadixTree {
}
}
int RangeEnd(int i) const {
__host__ __device__ int RangeEnd(int i) const {
// Determine direction of range (+1 or -1)
int dir = PrefixLength(i, i + 1) - PrefixLength(i, i - 1);
dir = (dir > 0) - (dir < 0);
@@ -116,7 +121,7 @@ struct CreateRadixTree {
return i + dir * length;
}
int FindSplit(int first, int last) const {
__host__ __device__ int FindSplit(int first, int last) const {
int commonPrefix = PrefixLength(first, last);
// Find the furthest object that shares more than commonPrefix bits with the
// first one, using binary search.
@@ -133,7 +138,7 @@ struct CreateRadixTree {
return split;
}
void operator()(int internal) {
__host__ __device__ void operator()(int internal) {
int first = internal;
// Find the range of objects with a common prefix
int last = RangeEnd(first);
@@ -152,27 +157,36 @@ struct CreateRadixTree {
}
};
template <typename T, const bool selfCollision, typename Recorder>
template <typename T, const bool allocateOnly, const bool selfCollision>
struct FindCollisions {
thrust::pair<int*, int*> queryTri_;
int* counts;
const Box* nodeBBox_;
const thrust::pair<int, int>* internalChildren_;
const Recorder& recorder;
int RecordCollision(int node, thrust::tuple<T, int>& query) {
__host__ __device__ int RecordCollision(int node,
thrust::tuple<T, int>& query) {
const T& queryObj = thrust::get<0>(query);
const int queryIdx = thrust::get<1>(query);
int& count = counts[queryIdx];
bool overlaps = nodeBBox_[node].DoesOverlap(queryObj);
if (overlaps && IsLeaf(node)) {
int leafIdx = Node2Leaf(node);
const int leafIdx = Node2Leaf(node);
if (!selfCollision || leafIdx != queryIdx) {
recorder.record(queryIdx, leafIdx);
if (allocateOnly) {
count++;
} else {
int pos = count++;
queryTri_.first[pos] = queryIdx;
queryTri_.second[pos] = leafIdx;
}
}
}
return overlaps && IsInternal(node); // Should traverse into node
}
void operator()(thrust::tuple<T, int> query) {
__host__ __device__ void operator()(thrust::tuple<T, int> query) {
// stack cannot overflow because radix tree has max depth 30 (Morton code) +
// 32 (index).
int stack[64];
@@ -181,7 +195,7 @@ struct FindCollisions {
int node = kRoot;
const int queryIdx = thrust::get<1>(query);
// same implies that this query do not have any collision
if (recorder.earlyexit(queryIdx)) return;
if (!allocateOnly && counts[queryIdx] == counts[queryIdx + 1]) return;
while (1) {
int internal = Node2Internal(node);
int child1 = internalChildren_[internal].first;
@@ -200,56 +214,16 @@ struct FindCollisions {
}
}
}
recorder.end(queryIdx);
}
};
struct CountCollisions {
int* counts;
char* empty;
void record(int queryIdx, int _leafIdx) const { counts[queryIdx]++; }
bool earlyexit(int _queryIdx) const { return false; }
void end(int queryIdx) const {
if (counts[queryIdx] == 0) empty[queryIdx] = 1;
}
};
template <const bool inverted>
struct SeqCollisionRecorder {
SparseIndices& queryTri_;
void record(int queryIdx, int leafIdx) const {
if (inverted)
queryTri_.Add(leafIdx, queryIdx);
else
queryTri_.Add(queryIdx, leafIdx);
}
bool earlyexit(int queryIdx) const { return false; }
void end(int queryIdx) const {}
};
template <const bool inverted>
struct ParCollisionRecorder {
SparseIndices& queryTri;
int* counts;
char* empty;
void record(int queryIdx, int leafIdx) const {
int pos = counts[queryIdx]++;
if (inverted)
queryTri.Set(pos, leafIdx, queryIdx);
else
queryTri.Set(pos, queryIdx, leafIdx);
}
bool earlyexit(int queryIdx) const { return empty[queryIdx] == 1; }
void end(int queryIdx) const {}
};
struct BuildInternalBoxes {
Box* nodeBBox_;
int* counter_;
const int* nodeParent_;
const thrust::pair<int, int>* internalChildren_;
void operator()(int leaf) {
__host__ __device__ void operator()(int leaf) {
int node = Leaf2Node(leaf);
do {
node = nodeParent_[node];
@@ -263,7 +237,9 @@ struct BuildInternalBoxes {
struct TransformBox {
const glm::mat4x3 transform;
void operator()(Box& box) { box = box.Transform(transform); }
__host__ __device__ void operator()(Box& box) {
box = box.Transform(transform);
}
};
} // namespace
@@ -298,42 +274,27 @@ Collider::Collider(const VecDH<Box>& leafBB,
* the query vector is the leaf vector, set selfCollision to true, which will
* then not report any collisions between an index and itself.
*/
template <const bool selfCollision, const bool inverted, typename T>
template <const bool selfCollision, typename T>
SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
// note that the length is 1 larger than the number of queries so the last
// element can store the sum when using exclusive scan
if (queriesIn.size() < kSequentialThreshold) {
SparseIndices queryTri;
for_each_n(ExecutionPolicy::Seq, zip(queriesIn.cbegin(), countAt(0)),
queriesIn.size(),
FindCollisions<T, selfCollision, SeqCollisionRecorder<inverted>>{
nodeBBox_.ptrD(), internalChildren_.ptrD(), {queryTri}});
return queryTri;
} else {
// compute the number of collisions to determine the size for allocation and
// offset, this avoids the need for atomic
VecDH<int> counts(queriesIn.size() + 1, 0);
VecDH<char> empty(queriesIn.size(), 0);
for_each_n(ExecutionPolicy::Par, zip(queriesIn.cbegin(), countAt(0)),
queriesIn.size(),
FindCollisions<T, selfCollision, CountCollisions>{
nodeBBox_.ptrD(),
internalChildren_.ptrD(),
{counts.ptrD(), empty.ptrD()}});
// compute start index for each query and total count
exclusive_scan(ExecutionPolicy::Par, counts.begin(), counts.end(),
counts.begin(), 0, std::plus<int>());
if (counts.back() == 0) return SparseIndices(0);
SparseIndices queryTri(counts.back());
// actually recording collisions
for_each_n(ExecutionPolicy::Par, zip(queriesIn.cbegin(), countAt(0)),
queriesIn.size(),
FindCollisions<T, selfCollision, ParCollisionRecorder<inverted>>{
nodeBBox_.ptrD(),
internalChildren_.ptrD(),
{queryTri, counts.ptrD(), empty.ptrD()}});
return queryTri;
}
VecDH<int> counts(queriesIn.size() + 1, 0);
auto policy = autoPolicy(queriesIn.size());
// compute the number of collisions to determine the size for allocation and
// offset, this avoids the need for atomic
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
FindCollisions<T, true, selfCollision>(
{thrust::pair<int*, int*>(nullptr, nullptr), counts.ptrD(),
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
// compute start index for each query and total count
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin());
SparseIndices queryTri(counts.back());
// actually recording collisions
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
FindCollisions<T, false, selfCollision>(
{queryTri.ptrDpq(), counts.ptrD(), nodeBBox_.ptrD(),
internalChildren_.ptrD()}));
return queryTri;
}
/**
@@ -376,22 +337,12 @@ bool Collider::Transform(glm::mat4x3 transform) {
return axisAligned;
}
template SparseIndices Collider::Collisions<true, false, Box>(
template SparseIndices Collider::Collisions<true, Box>(const VecDH<Box>&) const;
template SparseIndices Collider::Collisions<false, Box>(
const VecDH<Box>&) const;
template SparseIndices Collider::Collisions<false, false, Box>(
const VecDH<Box>&) const;
template SparseIndices Collider::Collisions<false, false, glm::vec3>(
const VecDH<glm::vec3>&) const;
template SparseIndices Collider::Collisions<true, true, Box>(
const VecDH<Box>&) const;
template SparseIndices Collider::Collisions<false, true, Box>(
const VecDH<Box>&) const;
template SparseIndices Collider::Collisions<false, true, glm::vec3>(
template SparseIndices Collider::Collisions<false, glm::vec3>(
const VecDH<glm::vec3>&) const;
} // namespace manifold
+1 -1
View File
@@ -15,7 +15,7 @@
project (cross_section)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} ${SOURCE_FILES})
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
-23
View File
@@ -612,12 +612,6 @@ CrossSection CrossSection::Offset(double delta, JoinType jointype,
return CrossSection(ps);
}
/**
* Compute the convex hull enveloping a set of cross-sections.
*
* @param crossSections A vector of cross-sections over which to compute a
* convex hull.
*/
CrossSection CrossSection::Hull(
const std::vector<CrossSection>& crossSections) {
int n = 0;
@@ -635,31 +629,14 @@ CrossSection CrossSection::Hull(
return CrossSection(C2::PathsD{HullImpl(pts)});
}
/**
* Compute the convex hull of this cross-section.
*/
CrossSection CrossSection::Hull() const {
return Hull(std::vector<CrossSection>{*this});
}
/**
* Compute the convex hull of a set of points. If the given points are fewer
* than 3, an empty CrossSection will be returned.
*
* @param pts A vector of 2-dimensional points over which to compute a convex
* hull.
*/
CrossSection CrossSection::Hull(SimplePolygon pts) {
return CrossSection(C2::PathsD{HullImpl(pts)});
}
/**
* Compute the convex hull of a set of points/polygons. If the given points are
* fewer than 3, an empty CrossSection will be returned.
*
* @param pts A vector of vectors of 2-dimensional points over which to compute
* a convex hull.
*/
CrossSection CrossSection::Hull(const Polygons polys) {
SimplePolygon pts;
for (auto poly : polys) {
+5
View File
@@ -17,6 +17,11 @@ project (manifold)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} ${SOURCE_FILES})
if(MANIFOLD_USE_CUDA)
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(${PROJECT_NAME}
PUBLIC utilities cross_section
+99 -108
View File
@@ -27,7 +27,7 @@ namespace {
// carefully designed to minimize rounding error and to eliminate it at edge
// cases to ensure consistency.
glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
__host__ __device__ glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
float dxL = x - pL.x;
float dxR = x - pR.x;
#ifdef MANIFOLD_DEBUG
@@ -42,8 +42,10 @@ glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
return yz;
}
glm::vec4 Intersect(const glm::vec3 &pL, const glm::vec3 &pR,
const glm::vec3 &qL, const glm::vec3 &qR) {
__host__ __device__ glm::vec4 Intersect(const glm::vec3 &pL,
const glm::vec3 &pR,
const glm::vec3 &qL,
const glm::vec3 &qR) {
float dyL = qL.y - pL.y;
float dyR = qR.y - pR.y;
#ifdef MANIFOLD_DEBUG
@@ -65,27 +67,21 @@ glm::vec4 Intersect(const glm::vec3 &pL, const glm::vec3 &pR,
return xyzz;
}
template <const bool inverted>
struct CopyFaceEdges {
const SparseIndices &p1q1;
// const int *p1q1;
// x can be either vert or edge (0 or 1).
SparseIndices &pXq1;
thrust::pair<int *, int *> pXq1;
const Halfedge *halfedgesQ;
void operator()(thrust::tuple<int, int> in) {
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
int idx = 3 * thrust::get<0>(in);
int i = thrust::get<1>(in);
int pX = p1q1.Get(i, inverted);
int q2 = p1q1.Get(i, !inverted);
const int pX = thrust::get<1>(in);
const int q2 = thrust::get<2>(in);
for (const int j : {0, 1, 2}) {
const int q1 = 3 * q2 + j;
for (const int i : {0, 1, 2}) {
pXq1.first[idx + i] = pX;
const int q1 = 3 * q2 + i;
const Halfedge edge = halfedgesQ[q1];
int a = pX;
int b = edge.IsForward() ? q1 : edge.pairedHalfedge;
if (inverted) std::swap(a, b);
pXq1.Set(idx + j, a, b);
pXq1.second[idx + i] = edge.IsForward() ? q1 : edge.pairedHalfedge;
}
}
};
@@ -94,15 +90,21 @@ SparseIndices Filter11(const Manifold::Impl &inP, const Manifold::Impl &inQ,
const SparseIndices &p1q2, const SparseIndices &p2q1,
ExecutionPolicy policy) {
SparseIndices p1q1(3 * p1q2.size() + 3 * p2q1.size());
for_each_n(policy, zip(countAt(0), countAt(0)), p1q2.size(),
CopyFaceEdges<false>({p1q2, p1q1, inQ.halfedge_.cptrD()}));
for_each_n(policy, zip(countAt(p1q2.size()), countAt(0)), p2q1.size(),
CopyFaceEdges<true>({p2q1, p1q1, inP.halfedge_.cptrD()}));
for_each_n(policy, zip(countAt(0), p1q2.begin(0), p1q2.begin(1)), p1q2.size(),
CopyFaceEdges({p1q1.ptrDpq(), inQ.halfedge_.cptrD()}));
p1q1.SwapPQ();
for_each_n(policy, zip(countAt(p1q2.size()), p2q1.begin(1), p2q1.begin(0)),
p2q1.size(),
CopyFaceEdges({p1q1.ptrDpq(), inP.halfedge_.cptrD()}));
p1q1.SwapPQ();
p1q1.Unique(policy);
return p1q1;
}
bool Shadows(float p, float q, float dir) { return p == q ? dir < 0 : p < q; }
__host__ __device__ bool Shadows(float p, float q, float dir) {
return p == q ? dir < 0 : p < q;
}
/**
* Since this function is called from two different places, it is necessary that
@@ -115,7 +117,7 @@ bool Shadows(float p, float q, float dir) { return p == q ? dir < 0 : p < q; }
* compiled function (they must agree on CPU or GPU). This is now taken care of
* by the shared policy_ member.
*/
thrust::pair<int, glm::vec2> Shadow01(
__host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
const int p0, const int q1, const glm::vec3 *vertPosP,
const glm::vec3 *vertPosQ, const Halfedge *halfedgeQ, const float expandP,
const glm::vec3 *normalP, const bool reverse) {
@@ -146,44 +148,27 @@ thrust::pair<int, glm::vec2> Shadow01(
return thrust::make_pair(s01, yz01);
}
// https://github.com/scandum/binary_search/blob/master/README.md
// much faster than standard binary search on large arrays
int monobound_quaternary_search(const int64_t *array, unsigned int array_size,
int64_t key) {
if (array_size == 0) {
__host__ __device__ int BinarySearch(
const thrust::pair<const int *, const int *> keys, const int size,
const thrust::pair<int, int> key) {
if (size <= 0) return -1;
int left = 0;
int right = size - 1;
int m;
thrust::pair<int, int> keyM;
while (1) {
m = right - (right - left) / 2;
keyM = thrust::make_pair(keys.first[m], keys.second[m]);
if (left == right) break;
if (keyM > key)
right = m - 1;
else
left = m;
}
if (keyM == key)
return m;
else
return -1;
}
unsigned int bot = 0;
unsigned int top = array_size;
while (top >= 65536) {
unsigned int mid = top / 4;
top -= mid * 3;
if (key < array[bot + mid * 2]) {
if (key >= array[bot + mid]) {
bot += mid;
}
} else {
bot += mid * 2;
if (key >= array[bot + mid]) {
bot += mid;
}
}
}
while (top > 3) {
unsigned int mid = top / 2;
if (key >= array[bot + mid]) {
bot += mid;
}
top -= mid;
}
while (top--) {
if (key == array[bot + top]) {
return bot + top;
}
}
return -1;
}
struct Kernel11 {
@@ -193,13 +178,13 @@ struct Kernel11 {
const Halfedge *halfedgeQ;
float expandP;
const glm::vec3 *normalP;
const SparseIndices &p1q1;
void operator()(thrust::tuple<int, glm::vec4 &, int &> inout) {
const int p1 = p1q1.Get(thrust::get<0>(inout), false);
const int q1 = p1q1.Get(thrust::get<0>(inout), true);
glm::vec4 &xyzz11 = thrust::get<1>(inout);
int &s11 = thrust::get<2>(inout);
__host__ __device__ void operator()(
thrust::tuple<glm::vec4 &, int &, int, int> inout) {
glm::vec4 &xyzz11 = thrust::get<0>(inout);
int &s11 = thrust::get<1>(inout);
const int p1 = thrust::get<2>(inout);
const int q1 = thrust::get<3>(inout);
// For pRL[k], qRL[k], k==0 is the left and k==1 is the right.
int k = 0;
@@ -277,10 +262,12 @@ std::tuple<VecDH<int>, VecDH<glm::vec4>> Shadow11(SparseIndices &p1q1,
VecDH<int> s11(p1q1.size());
VecDH<glm::vec4> xyzz11(p1q1.size());
for_each_n(policy, zip(countAt(0), xyzz11.begin(), s11.begin()), p1q1.size(),
for_each_n(policy,
zip(xyzz11.begin(), s11.begin(), p1q1.begin(0), p1q1.begin(1)),
p1q1.size(),
Kernel11({inP.vertPos_.cptrD(), inQ.vertPos_.cptrD(),
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(), expandP,
inP.vertNormal_.cptrD(), p1q1}));
inP.vertNormal_.cptrD()}));
p1q1.KeepFinite(xyzz11, s11);
@@ -294,13 +281,13 @@ struct Kernel02 {
const bool forward;
const float expandP;
const glm::vec3 *vertNormalP;
const SparseIndices &p0q2;
void operator()(thrust::tuple<int, int &, float &> inout) {
const int p0 = p0q2.Get(thrust::get<0>(inout), !forward);
const int q2 = p0q2.Get(thrust::get<0>(inout), forward);
int &s02 = thrust::get<1>(inout);
float &z02 = thrust::get<2>(inout);
__host__ __device__ void operator()(
thrust::tuple<int &, float &, int, int> inout) {
int &s02 = thrust::get<0>(inout);
float &z02 = thrust::get<1>(inout);
const int p0 = thrust::get<2>(inout);
const int q2 = thrust::get<3>(inout);
// For yzzLR[k], k==0 is the left and k==1 is the right.
int k = 0;
@@ -375,9 +362,11 @@ std::tuple<VecDH<int>, VecDH<float>> Shadow02(const Manifold::Impl &inP,
auto vertNormalP =
forward ? inP.vertNormal_.cptrD() : inQ.vertNormal_.cptrD();
for_each_n(
policy, zip(countAt(0), s02.begin(), z02.begin()), p0q2.size(),
policy,
zip(s02.begin(), z02.begin(), p0q2.begin(!forward), p0q2.begin(forward)),
p0q2.size(),
Kernel02({inP.vertPos_.cptrD(), inQ.halfedge_.cptrD(),
inQ.vertPos_.cptrD(), forward, expandP, vertNormalP, p0q2}));
inQ.vertPos_.cptrD(), forward, expandP, vertNormalP}));
p0q2.KeepFinite(z02, s02);
@@ -385,11 +374,11 @@ std::tuple<VecDH<int>, VecDH<float>> Shadow02(const Manifold::Impl &inP,
};
struct Kernel12 {
const VecDH<int64_t> &p0q2;
const thrust::pair<const int *, const int *> p0q2;
const int *s02;
const float *z02;
const int size02;
const VecDH<int64_t> &p1q1;
const thrust::pair<const int *, const int *> p1q1;
const int *s11;
const glm::vec4 *xyzz11;
const int size11;
@@ -397,13 +386,13 @@ struct Kernel12 {
const Halfedge *halfedgesQ;
const glm::vec3 *vertPosP;
const bool forward;
const SparseIndices &p1q2;
void operator()(thrust::tuple<int, int &, glm::vec3 &> inout) {
int p1 = p1q2.Get(thrust::get<0>(inout), !forward);
int q2 = p1q2.Get(thrust::get<0>(inout), forward);
int &x12 = thrust::get<1>(inout);
glm::vec3 &v12 = thrust::get<2>(inout);
__host__ __device__ void operator()(
thrust::tuple<int &, glm::vec3 &, int, int> inout) {
int &x12 = thrust::get<0>(inout);
glm::vec3 &v12 = thrust::get<1>(inout);
const int p1 = thrust::get<2>(inout);
const int q2 = thrust::get<3>(inout);
// For xzyLR-[k], k==0 is the left and k==1 is the right.
int k = 0;
@@ -417,9 +406,9 @@ struct Kernel12 {
const Halfedge edge = halfedgesP[p1];
for (int vert : {edge.startVert, edge.endVert}) {
const int64_t key = forward ? SparseIndices::EncodePQ(vert, q2)
: SparseIndices::EncodePQ(q2, vert);
const int idx = monobound_quaternary_search(p0q2.ptrD(), size02, key);
const auto key =
forward ? thrust::make_pair(vert, q2) : thrust::make_pair(q2, vert);
const int idx = BinarySearch(p0q2, size02, key);
if (idx != -1) {
const int s = s02[idx];
x12 += s * ((vert == edge.startVert) == forward ? 1 : -1);
@@ -438,9 +427,9 @@ struct Kernel12 {
const int q1 = 3 * q2 + i;
const Halfedge edge = halfedgesQ[q1];
const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge;
const int64_t key = forward ? SparseIndices::EncodePQ(p1, q1F)
: SparseIndices::EncodePQ(q1F, p1);
const int idx = monobound_quaternary_search(p1q1.ptrD(), size11, key);
const auto key =
forward ? thrust::make_pair(p1, q1F) : thrust::make_pair(q1F, p1);
const int idx = BinarySearch(p1q1, size11, key);
if (idx != -1) { // s is implicitly zero for anything not found
const int s = s11[idx];
x12 -= s * (edge.IsForward() ? 1 : -1);
@@ -484,30 +473,34 @@ std::tuple<VecDH<int>, VecDH<glm::vec3>> Intersect12(
VecDH<int> x12(p1q2.size());
VecDH<glm::vec3> v12(p1q2.size());
for_each_n(policy, zip(countAt(0), x12.begin(), v12.begin()), p1q2.size(),
Kernel12({p0q2.AsVec64(), s02.ptrD(), z02.cptrD(), p0q2.size(),
p1q1.AsVec64(), s11.ptrD(), xyzz11.cptrD(), p1q1.size(),
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
inP.vertPos_.cptrD(), forward, p1q2}));
for_each_n(
policy,
zip(x12.begin(), v12.begin(), p1q2.begin(!forward), p1q2.begin(forward)),
p1q2.size(),
Kernel12({p0q2.ptrDpq(), s02.ptrD(), z02.cptrD(), p0q2.size(),
p1q1.ptrDpq(), s11.ptrD(), xyzz11.cptrD(), p1q1.size(),
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
inP.vertPos_.cptrD(), forward}));
p1q2.KeepFinite(v12, x12);
return std::make_tuple(x12, v12);
};
VecDH<int> Winding03(const Manifold::Impl &inP, VecDH<int> &vertices,
VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
VecDH<int> &s02, bool reverse, ExecutionPolicy policy) {
// verts that are not shadowed (not in p0q2) have winding number zero.
VecDH<int> w03(inP.NumVert(), 0);
// checking is slow, so just sort and reduce
stable_sort_by_key(policy, vertices.begin(), vertices.end(), s02.begin());
if (!is_sorted(policy, p0q2.begin(reverse), p0q2.end(reverse)))
sort_by_key(policy, p0q2.begin(reverse), p0q2.end(reverse), s02.begin());
VecDH<int> w03val(w03.size());
VecDH<int> w03vert(w03.size());
// sum known s02 values into w03 (winding number)
auto endPair = reduce_by_key<
thrust::pair<decltype(w03val.begin()), decltype(w03val.begin())>>(
policy, vertices.begin(), vertices.end(), s02.begin(), w03vert.begin(),
w03val.begin());
policy, p0q2.begin(reverse), p0q2.end(reverse), s02.begin(),
w03vert.begin(), w03val.begin());
scatter(policy, w03val.begin(), endPair.second, w03vert.begin(), w03.begin());
if (reverse)
@@ -543,12 +536,13 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
// Level 3
// Find edge-triangle overlaps (broad phase)
p1q2_ = inQ_.EdgeCollisions(inP_);
p2q1_ = inP_.EdgeCollisions(inQ_, true); // inverted
p2q1_ = inP_.EdgeCollisions(inQ_);
policy_ = autoPolicy(glm::max(p1q2_.size(), p2q1_.size()));
p1q2_.Sort(policy_);
PRINT("p1q2 size = " << p1q2_.size());
p2q1_.SwapPQ();
p2q1_.Sort(policy_);
PRINT("p2q1 size = " << p2q1_.size());
@@ -558,7 +552,8 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
p0q2.Sort(policy_);
PRINT("p0q2 size = " << p0q2.size());
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_, true); // inverted
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_);
p2q0.SwapPQ();
p2q0.Sort(policy_);
PRINT("p2q0 size = " << p2q0.size());
@@ -604,14 +599,10 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
xyzz11, p2q1_, false, policy_);
PRINT("x21 size = " << x21_.size());
VecDH<int> p0 = p0q2.Copy(false);
p0q2.Resize(0);
VecDH<int> q0 = p2q0.Copy(true);
p2q0.Resize(0);
// Sum up the winding numbers of all vertices.
w03_ = Winding03(inP, p0, s02, false, policy_);
w03_ = Winding03(inP, p0q2, s02, false, policy_);
w30_ = Winding03(inQ, q0, s20, true, policy_);
w30_ = Winding03(inQ, p2q0, s20, true, policy_);
#ifdef MANIFOLD_DEBUG
intersections.Stop();
+44 -93
View File
@@ -13,21 +13,8 @@
// limitations under the License.
#include <algorithm>
#include <array>
#include <map>
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
#include <tbb/concurrent_map.h>
#include <tbb/parallel_for.h>
template <typename K, typename V>
using concurrent_map = tbb::concurrent_map<K, V>;
#else
template <typename K, typename V>
// not really concurrent when tbb is disabled
using concurrent_map = std::map<K, V>;
#endif
#include "boolean3.h"
#include "par.h"
#include "polygon.h"
@@ -35,23 +22,16 @@ using concurrent_map = std::map<K, V>;
using namespace manifold;
using namespace thrust::placeholders;
template <>
struct std::hash<std::pair<int, int>> {
size_t operator()(const std::pair<int, int> &p) const {
return std::hash<int>()(p.first) ^ std::hash<int>()(p.second);
}
};
namespace {
struct AbsSum : public thrust::binary_function<int, int, int> {
int operator()(int a, int b) { return abs(a) + abs(b); }
__host__ __device__ int operator()(int a, int b) { return abs(a) + abs(b); }
};
struct DuplicateVerts {
glm::vec3 *vertPosR;
void operator()(thrust::tuple<int, int, glm::vec3> in) {
__host__ __device__ void operator()(thrust::tuple<int, int, glm::vec3> in) {
int inclusion = abs(thrust::get<0>(in));
int vertR = thrust::get<1>(in);
glm::vec3 vertPosP = thrust::get<2>(in);
@@ -66,22 +46,20 @@ struct CountVerts {
int *count;
const int *inclusion;
void operator()(const Halfedge &edge) {
__host__ __device__ void operator()(const Halfedge &edge) {
AtomicAdd(count[edge.face], glm::abs(inclusion[edge.startVert]));
}
};
template <const bool inverted>
struct CountNewVerts {
int *countP;
int *countQ;
const SparseIndices &pq;
const Halfedge *halfedges;
void operator()(thrust::tuple<int, int> in) {
int edgeP = pq.Get(thrust::get<0>(in), inverted);
int faceQ = pq.Get(thrust::get<0>(in), !inverted);
int inclusion = glm::abs(thrust::get<1>(in));
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
int edgeP = thrust::get<0>(in);
int faceQ = thrust::get<1>(in);
int inclusion = glm::abs(thrust::get<2>(in));
AtomicAdd(countQ[faceQ], inclusion);
const Halfedge half = halfedges[edgeP];
@@ -91,7 +69,7 @@ struct CountNewVerts {
};
struct NotZero : public thrust::unary_function<int, int> {
int operator()(int x) const { return x > 0 ? 1 : 0; }
__host__ __device__ int operator()(int x) const { return x > 0 ? 1 : 0; }
};
std::tuple<VecDH<int>, VecDH<int>> SizeOutput(
@@ -107,12 +85,12 @@ std::tuple<VecDH<int>, VecDH<int>> SizeOutput(
CountVerts({sidesPerFaceP, i03.cptrD()}));
for_each(policy, inQ.halfedge_.begin(), inQ.halfedge_.end(),
CountVerts({sidesPerFaceQ, i30.cptrD()}));
for_each_n(policy, zip(countAt(0), i12.begin()), i12.size(),
CountNewVerts<false>(
{sidesPerFaceP, sidesPerFaceQ, p1q2, inP.halfedge_.cptrD()}));
for_each_n(policy, zip(countAt(0), i21.begin()), i21.size(),
CountNewVerts<true>(
{sidesPerFaceQ, sidesPerFaceP, p2q1, inQ.halfedge_.cptrD()}));
for_each_n(
policy, zip(p1q2.begin(0), p1q2.begin(1), i12.begin()), i12.size(),
CountNewVerts({sidesPerFaceP, sidesPerFaceQ, inP.halfedge_.cptrD()}));
for_each_n(
policy, zip(p2q1.begin(1), p2q1.begin(0), i21.begin()), i21.size(),
CountNewVerts({sidesPerFaceQ, sidesPerFaceP, inQ.halfedge_.cptrD()}));
VecDH<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);
auto keepFace =
@@ -156,9 +134,8 @@ struct EdgePos {
};
void AddNewEdgeVerts(
// we need concurrent_map because we will be adding things concurrently
concurrent_map<int, std::vector<EdgePos>> &edgesP,
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
std::map<int, std::vector<EdgePos>> &edgesP,
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
const SparseIndices &p1q2, const VecDH<int> &i12, const VecDH<int> &v12R,
const VecDH<Halfedge> &halfedgeP, bool forward) {
// For each edge of P that intersects a face of Q (p1q2), add this vertex to
@@ -166,63 +143,37 @@ void AddNewEdgeVerts(
// intersections between the face of Q and the two faces of P attached to the
// edge. The direction and duplicity are given by i12, while v12R remaps to
// the output vert index. When forward is false, all is reversed.
const VecDH<int> &p1 = p1q2.Copy(!forward);
const VecDH<int> &q2 = p1q2.Copy(forward);
auto process = [&](std::function<void(size_t)> lock,
std::function<void(size_t)> unlock, int i) {
const VecDH<int> &p1 = p1q2.Get(!forward);
const VecDH<int> &q2 = p1q2.Get(forward);
for (int i = 0; i < p1q2.size(); ++i) {
const int edgeP = p1[i];
const int faceQ = q2[i];
const int vert = v12R[i];
const int inclusion = i12[i];
auto &edgePosP = edgesP[edgeP];
Halfedge halfedge = halfedgeP[edgeP];
std::pair<int, int> keyRight = {halfedgeP[halfedge.pairedHalfedge].face,
faceQ};
if (!forward) std::swap(keyRight.first, keyRight.second);
std::pair<int, int> key = {halfedgeP[halfedge.pairedHalfedge].face, faceQ};
if (!forward) std::swap(key.first, key.second);
auto &edgePosRight = edgesNew[key];
std::pair<int, int> keyLeft = {halfedge.face, faceQ};
if (!forward) std::swap(keyLeft.first, keyLeft.second);
key = {halfedge.face, faceQ};
if (!forward) std::swap(key.first, key.second);
auto &edgePosLeft = edgesNew[key];
bool direction = inclusion < 0;
std::hash<std::pair<int, int>> pairHasher;
std::array<std::tuple<bool, size_t, std::vector<EdgePos> *>, 3> edges = {
std::make_tuple(direction, std::hash<int>{}(edgeP), &edgesP[edgeP]),
std::make_tuple(direction ^ !forward, // revert if not forward
pairHasher(keyRight), &edgesNew[keyRight]),
std::make_tuple(direction ^ forward, // revert if forward
pairHasher(keyLeft), &edgesNew[keyLeft])};
for (const auto &tuple : edges) {
lock(std::get<1>(tuple));
for (int j = 0; j < glm::abs(inclusion); ++j)
std::get<2>(tuple)->push_back({vert + j, 0.0f, std::get<0>(tuple)});
unlock(std::get<1>(tuple));
direction = !direction;
EdgePos edgePos = {vert, 0.0f, inclusion < 0};
EdgePos edgePosRev = edgePos;
edgePosRev.isStart = !edgePos.isStart;
for (int j = 0; j < glm::abs(inclusion); ++j) {
edgePosP.push_back(edgePos);
edgePosRight.push_back(forward ? edgePos : edgePosRev);
edgePosLeft.push_back(forward ? edgePosRev : edgePos);
++edgePos.vert;
++edgePosRev.vert;
}
};
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
// parallelize operations, requires concurrent_map so we can only enable this
// with tbb
if (p1q2.size() > 128) {
// ideally we should have 1 mutex per key, but 128 is enough to avoid
// contention for most of the cases
std::array<std::mutex, 128> mutexes;
static tbb::affinity_partitioner ap;
auto processFun = std::bind(
process, [&](size_t hash) { mutexes[hash % mutexes.size()].lock(); },
[&](size_t hash) { mutexes[hash % mutexes.size()].unlock(); },
std::placeholders::_1);
tbb::parallel_for(
tbb::blocked_range<int>(0, p1q2.size(), 32),
[&](const tbb::blocked_range<int> &range) {
for (int i = range.begin(); i != range.end(); i++) processFun(i);
},
ap);
return;
}
#endif
auto processFun = std::bind(
process, [](size_t _) {}, [](size_t _) {}, std::placeholders::_1);
for (int i = 0; i < p1q2.size(); ++i) processFun(i);
}
std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
@@ -248,7 +199,7 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
VecDH<int> &facePtrR,
concurrent_map<int, std::vector<EdgePos>> &edgesP,
std::map<int, std::vector<EdgePos>> &edgesP,
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
const VecDH<int> &i03, const VecDH<int> &vP2R,
const VecDH<int>::IterC faceP2R, bool forward) {
@@ -331,7 +282,7 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
void AppendNewEdges(
Manifold::Impl &outR, VecDH<int> &facePtrR,
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
VecDH<TriRef> &halfedgeRef, const VecDH<int> &facePQ2R,
const int numFaceP) {
// Pair up each edge's verts and distribute to faces based on indices in key.
@@ -392,7 +343,7 @@ struct DuplicateHalfedges {
const int *faceP2R;
const bool forward;
void operator()(thrust::tuple<bool, Halfedge, int> in) {
__host__ __device__ void operator()(thrust::tuple<bool, Halfedge, int> in) {
if (!thrust::get<0>(in)) return;
Halfedge halfedge = thrust::get<1>(in);
if (!halfedge.IsForward()) return;
@@ -452,7 +403,7 @@ struct MapTriRef {
const TriRef *triRefQ;
const int offsetQ;
void operator()(TriRef &triRef) {
__host__ __device__ void operator()(TriRef &triRef) {
const int tri = triRef.tri;
const bool PQ = triRef.meshID == 0;
triRef = PQ ? triRefP[tri] : triRefQ[tri];
@@ -490,7 +441,7 @@ struct Barycentric {
const Halfedge *halfedgeR;
const float precision;
void operator()(thrust::tuple<int, TriRef> in) {
__host__ __device__ void operator()(thrust::tuple<int, TriRef> in) {
const int tri = thrust::get<0>(in);
const TriRef refPQ = thrust::get<1>(in);
if (halfedgeR[3 * tri].startVert < 0) return;
@@ -701,9 +652,9 @@ Manifold::Impl Boolean3::Result(OpType op) const {
// This key is the forward halfedge index of P or Q. Only includes intersected
// edges.
concurrent_map<int, std::vector<EdgePos>> edgesP, edgesQ;
std::map<int, std::vector<EdgePos>> edgesP, edgesQ;
// This key is the face index of <P, Q>
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
std::map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
AddNewEdgeVerts(edgesP, edgesNew, p1q2_, i12, v12R, inP_.halfedge_, true);
AddNewEdgeVerts(edgesQ, edgesNew, p2q1_, i21, v21R, inQ_.halfedge_, false);
+3 -3
View File
@@ -27,7 +27,7 @@ using namespace thrust::placeholders;
struct ToSphere {
float length;
void operator()(glm::vec3& v) {
__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);
@@ -36,7 +36,7 @@ struct ToSphere {
struct Equals {
int val;
bool operator()(int x) { return x == val; }
__host__ __device__ bool operator()(int x) { return x == val; }
};
struct RemoveFace {
@@ -44,7 +44,7 @@ struct RemoveFace {
const int* vertLabel;
const int keepLabel;
bool operator()(int face) {
__host__ __device__ bool operator()(int face) {
return vertLabel[halfedge[3 * face].startVert] != keepLabel;
}
};
+19 -66
View File
@@ -12,29 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
#include <tbb/tbb.h>
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
#include <tbb/concurrent_priority_queue.h>
#endif
#include "csg_tree.h"
#include <algorithm>
#include <variant>
#include "boolean3.h"
#include "csg_tree.h"
#include "impl.h"
#include "mesh_fixes.h"
#include "par.h"
constexpr int kParallelThreshold = 4096;
namespace {
using namespace manifold;
struct Transform4x3 {
const glm::mat4x3 transform;
glm::vec3 operator()(glm::vec3 position) {
__host__ __device__ glm::vec3 operator()(glm::vec3 position) {
return transform * glm::vec4(position, 1.0f);
}
};
@@ -44,7 +36,7 @@ struct UpdateHalfedge {
const int nextEdge;
const int nextFace;
Halfedge operator()(Halfedge edge) {
__host__ __device__ Halfedge operator()(Halfedge edge) {
edge.startVert += nextVert;
edge.endVert += nextVert;
edge.pairedHalfedge += nextEdge;
@@ -56,7 +48,7 @@ struct UpdateHalfedge {
struct UpdateTriProp {
const int nextProp;
glm::ivec3 operator()(glm::ivec3 tri) {
__host__ __device__ glm::ivec3 operator()(glm::ivec3 tri) {
tri += nextProp;
return tri;
}
@@ -65,7 +57,7 @@ struct UpdateTriProp {
struct UpdateMeshIDs {
const int offset;
TriRef operator()(TriRef ref) {
__host__ __device__ TriRef operator()(TriRef ref) {
ref.meshID += offset;
return ref;
}
@@ -74,7 +66,9 @@ struct UpdateMeshIDs {
struct CheckOverlap {
const Box *boxes;
const size_t i;
bool operator()(int j) { return boxes[i].DoesOverlap(boxes[j]); }
__host__ __device__ bool operator()(int j) {
return boxes[i].DoesOverlap(boxes[j]);
}
};
} // namespace
namespace manifold {
@@ -203,7 +197,9 @@ Manifold::Impl CsgLeafNode::Compose(
// if we are already parallelizing for each node, do not perform multithreaded
// copying as it will slightly hurt performance
if (nodes.size() > 1 && policy == ExecutionPolicy::Par)
if (nodes.size() > 1 &&
(policy == ExecutionPolicy::Par ||
(policy == ExecutionPolicy::ParUnseq && !CudaEnabled())))
policy = ExecutionPolicy::Seq;
for_each_n_host(
@@ -436,59 +432,14 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
OpType operation,
std::vector<std::shared_ptr<const Manifold::Impl>> &results) {
using SharedImpl = std::variant<std::shared_ptr<const Manifold::Impl>,
std::shared_ptr<Manifold::Impl>>;
auto getImplPtr = [](const SharedImpl &p) -> const Manifold::Impl * {
if (std::holds_alternative<std::shared_ptr<const Manifold::Impl>>(p)) {
return std::get<std::shared_ptr<const Manifold::Impl>>(p).get();
} else {
return std::get<std::shared_ptr<Manifold::Impl>>(p).get();
}
};
ASSERT(operation != OpType::Subtract, logicErr,
"BatchBoolean doesn't support Difference.");
auto cmpFn = [&getImplPtr](const SharedImpl &a, const SharedImpl &b) {
return getImplPtr(a)->NumVert() < getImplPtr(b)->NumVert();
auto cmpFn = [](std::shared_ptr<const Manifold::Impl> a,
std::shared_ptr<const Manifold::Impl> b) {
// invert the order because we want a min heap
return a->NumVert() > b->NumVert();
};
// common cases
if (results.size() == 0) return std::make_shared<Manifold::Impl>();
if (results.size() == 1)
return std::make_shared<Manifold::Impl>(*results.front());
if (results.size() == 2) {
Boolean3 boolean(*results[0], *results[1], operation);
return std::make_shared<Manifold::Impl>(boolean.Result(operation));
}
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
tbb::task_group group;
tbb::concurrent_priority_queue<SharedImpl, decltype(cmpFn)> queue(cmpFn);
for (auto result : results) {
queue.emplace(result);
}
results.clear();
std::function<void()> process = [&]() {
while (queue.size() > 1) {
SharedImpl a, b;
if (!queue.try_pop(a)) continue;
if (!queue.try_pop(b)) {
queue.push(a);
continue;
}
group.run([&, a, b]() {
const Manifold::Impl *aImpl;
const Manifold::Impl *bImpl;
Boolean3 boolean(*getImplPtr(a), *getImplPtr(b), operation);
queue.emplace(
std::make_shared<Manifold::Impl>(boolean.Result(operation)));
return group.run(process);
});
}
};
group.run_and_wait(process);
SharedImpl r;
queue.try_pop(r);
return std::get<std::shared_ptr<Manifold::Impl>>(r);
#else
// apply boolean operations starting from smaller meshes
// the assumption is that boolean operations on smaller meshes is faster,
// due to less data being copied and processed
@@ -509,8 +460,10 @@ std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
std::make_shared<const Manifold::Impl>(boolean.Result(operation)));
std::push_heap(results.begin(), results.end(), cmpFn);
}
return std::make_shared<Manifold::Impl>(*results.front());
#endif
if (results.size() == 1) {
return std::make_shared<Manifold::Impl>(*results.front());
}
return std::make_shared<Manifold::Impl>();
}
/**
+19 -42
View File
@@ -18,7 +18,7 @@
namespace {
using namespace manifold;
glm::ivec3 TriOf(int edge) {
__host__ __device__ glm::ivec3 TriOf(int edge) {
glm::ivec3 triEdge;
triEdge[0] = edge;
triEdge[1] = NextHalfedge(triEdge[0]);
@@ -26,7 +26,7 @@ glm::ivec3 TriOf(int edge) {
return triEdge;
}
bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
__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]);
@@ -36,7 +36,7 @@ bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
struct DuplicateEdge {
const Halfedge* sortedHalfedge;
bool operator()(int edge) {
__host__ __device__ bool operator()(int edge) {
const Halfedge& halfedge = sortedHalfedge[edge];
const Halfedge& nextHalfedge = sortedHalfedge[edge + 1];
return halfedge.startVert == nextHalfedge.startVert &&
@@ -49,7 +49,7 @@ struct ShortEdge {
const glm::vec3* vertPos;
const float precision;
bool operator()(int edge) const {
__host__ __device__ bool operator()(int edge) const {
if (halfedge[edge].pairedHalfedge < 0) return false;
// Flag short edges
const glm::vec3 delta =
@@ -62,7 +62,7 @@ struct FlagEdge {
const Halfedge* halfedge;
const TriRef* triRef;
bool operator()(int edge) const {
__host__ __device__ bool operator()(int edge) const {
if (halfedge[edge].pairedHalfedge < 0) return false;
// Flag redundant edges - those where the startVert is surrounded by only
// two original triangles.
@@ -87,7 +87,7 @@ struct SwappableEdge {
const glm::vec3* triNormal;
const float precision;
bool operator()(int edge) const {
__host__ __device__ bool operator()(int edge) const {
if (halfedge[edge].pairedHalfedge < 0) return false;
int tri = halfedge[edge].face;
@@ -176,8 +176,6 @@ void Manifold::Impl::SimplifyTopology() {
}
#endif
std::vector<int> scratchBuffer;
scratchBuffer.reserve(10);
{
numFlagged = 0;
ShortEdge se{halfedge_.cptrD(), vertPos_.cptrD(), precision_};
@@ -185,8 +183,7 @@ void Manifold::Impl::SimplifyTopology() {
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
for (int i = 0; i < nbEdges; ++i) {
if (bflags[i]) {
CollapseEdge(i, scratchBuffer);
scratchBuffer.resize(0);
CollapseEdge(i);
numFlagged++;
}
}
@@ -206,8 +203,7 @@ void Manifold::Impl::SimplifyTopology() {
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
for (int i = 0; i < nbEdges; ++i) {
if (bflags[i]) {
CollapseEdge(i, scratchBuffer);
scratchBuffer.resize(0);
CollapseEdge(i);
numFlagged++;
}
}
@@ -226,19 +222,10 @@ void Manifold::Impl::SimplifyTopology() {
precision_};
for_each_n(policy, countAt(0), nbEdges,
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
std::vector<int> edgeSwapStack;
std::vector<int> visited(halfedge_.size(), -1);
int tag = 0;
for (int i = 0; i < nbEdges; ++i) {
if (bflags[i]) {
RecursiveEdgeSwap(i);
numFlagged++;
tag++;
RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer);
while (!edgeSwapStack.empty()) {
int last = edgeSwapStack.back();
edgeSwapStack.pop_back();
RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer);
}
}
}
}
@@ -385,7 +372,7 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
}
}
void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
void Manifold::Impl::CollapseEdge(const int edge) {
VecDH<TriRef>& triRef = meshRelation_.triRef;
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
@@ -401,6 +388,7 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
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]) {
@@ -495,19 +483,13 @@ void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
RemoveIfFolded(start);
}
void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,
std::vector<int>& visited,
std::vector<int>& edgeSwapStack,
std::vector<int>& edges) {
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
VecDH<TriRef>& triRef = meshRelation_.triRef;
if (edge < 0) return;
const int pair = halfedge_[edge].pairedHalfedge;
if (pair < 0) return;
// avoid infinite recursion
if (visited[edge] == tag && visited[pair] == tag) return;
const glm::ivec3 tri0edge = TriOf(edge);
const glm::ivec3 tri1edge = TriOf(pair);
const glm::ivec3 perm0 = TriOf(edge % 3);
@@ -586,14 +568,12 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,
SwapEdge();
const glm::vec2 e23 = v[3] - v[2];
if (glm::dot(e23, e23) < precision_ * precision_) {
tag++;
CollapseEdge(tri0edge[2], edges);
edges.resize(0);
CollapseEdge(tri0edge[2]);
} else {
visited[edge] = tag;
visited[pair] = tag;
edgeSwapStack.insert(edgeSwapStack.end(), {tri1edge[1], tri1edge[0],
tri0edge[1], tri0edge[0]});
RecursiveEdgeSwap(tri0edge[0]);
RecursiveEdgeSwap(tri0edge[1]);
RecursiveEdgeSwap(tri1edge[0]);
RecursiveEdgeSwap(tri1edge[1]);
}
return;
} else if (CCW(v[0], v[3], v[2], precision_) <= 0 ||
@@ -602,11 +582,8 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,
}
// Normal path
SwapEdge();
visited[edge] = tag;
visited[pair] = tag;
edgeSwapStack.insert(edgeSwapStack.end(),
{halfedge_[tri1edge[0]].pairedHalfedge,
halfedge_[tri0edge[1]].pairedHalfedge});
RecursiveEdgeSwap(halfedge_[tri0edge[1]].pairedHalfedge);
RecursiveEdgeSwap(halfedge_[tri1edge[0]].pairedHalfedge);
}
void Manifold::Impl::SplitPinchedVerts() {
+24 -81
View File
@@ -12,11 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
#include <tbb/tbb.h>
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
#include <tbb/concurrent_map.h>
#endif
#include <map>
#include "impl.h"
@@ -24,9 +19,6 @@
namespace manifold {
using GeneralTriangulation = std::function<std::vector<glm::ivec3>(int)>;
using AddTriangle = std::function<void(int, glm::ivec3, glm::vec3, TriRef)>;
/**
* 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
@@ -43,8 +35,11 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
VecDH<glm::vec3> triNormal;
VecDH<TriRef>& triRef = meshRelation_.triRef;
triRef.resize(0);
auto processFace = [&](GeneralTriangulation general, AddTriangle addTri,
int face) {
triVerts.reserve(halfedge_.size());
triNormal.reserve(halfedge_.size());
triRef.reserve(halfedge_.size());
for (int face = 0; face < faceEdge.size() - 1; ++face) {
const int firstEdge = faceEdge[face];
const int lastEdge = faceEdge[face + 1];
const int numEdge = lastEdge - firstEdge;
@@ -68,7 +63,9 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
ASSERT(ends[0] == tri[1] && ends[1] == tri[2] && ends[2] == tri[0],
topologyErr, "These 3 edges do not form a triangle!");
addTri(face, tri, normal, halfedgeRef[firstEdge]);
triVerts.push_back(tri);
triNormal.push_back(normal);
triRef.push_back(halfedgeRef[firstEdge]);
} else if (numEdge == 4) { // Pair of triangles
int mapping[4] = {halfedge_[firstEdge].startVert,
halfedge_[firstEdge + 1].startVert,
@@ -114,79 +111,25 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
}
}
for (const auto& tri : {tri0, tri1}) {
addTri(face, tri, normal, halfedgeRef[firstEdge]);
}
} else { // General triangulation
for (const auto& tri : general(face)) {
addTri(face, tri, normal, halfedgeRef[firstEdge]);
}
}
};
auto generalTriangulation = [&](int face) {
const glm::vec3 normal = faceNormal_[face];
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
const PolygonsIdx polys = Face2Polygons(face, projection, faceEdge);
return TriangulateIdx(polys, precision_);
};
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
tbb::task_group group;
// map from face to triangle
tbb::concurrent_unordered_map<int, std::vector<glm::ivec3>> results;
VecDH<int> triCount(faceEdge.size());
triCount.back() = 0;
// precompute number of triangles per face, and launch async tasks to
// triangulate complex faces
for_each_host(autoPolicy(faceEdge.size()), countAt(0),
countAt(faceEdge.size() - 1), [&](int face) {
triCount[face] = faceEdge[face + 1] - faceEdge[face] - 2;
ASSERT(triCount[face] >= 1, topologyErr,
"face has less than three edges.");
if (triCount[face] > 2)
group.run([&, face] {
std::vector<glm::ivec3> newTris =
generalTriangulation(face);
triCount[face] = newTris.size();
results[face] = std::move(newTris);
});
});
group.wait();
// prefix sum computation (assign unique index to each face) and preallocation
exclusive_scan(autoPolicy(triCount.size()), triCount.begin(), triCount.end(),
triCount.begin(), 0);
triVerts.resize(triCount.back());
triNormal.resize(triCount.back());
triRef.resize(triCount.back());
auto processFace2 = std::bind(
processFace, [&](int face) { return std::move(results[face]); },
[&](int face, glm::ivec3 tri, glm::vec3 normal, TriRef r) {
triVerts[triCount[face]] = tri;
triNormal[triCount[face]] = normal;
triRef[triCount[face]] = r;
triCount[face]++;
},
std::placeholders::_1);
// set triangles in parallel
for_each_host(autoPolicy(faceEdge.size()), countAt(0),
countAt(faceEdge.size() - 1), processFace2);
#else
triVerts.reserve(faceEdge.size());
triNormal.reserve(faceEdge.size());
triRef.reserve(faceEdge.size());
auto processFace2 = std::bind(
processFace, generalTriangulation,
[&](int _face, glm::ivec3 tri, glm::vec3 normal, TriRef r) {
for (auto tri : {tri0, tri1}) {
triVerts.push_back(tri);
triNormal.push_back(normal);
triRef.push_back(r);
},
std::placeholders::_1);
for (int face = 0; face < faceEdge.size() - 1; ++face) {
processFace2(face);
}
#endif
triRef.push_back(halfedgeRef[firstEdge]);
}
} else { // General triangulation
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
const PolygonsIdx polys = Face2Polygons(face, projection, faceEdge);
std::vector<glm::ivec3> newTris = TriangulateIdx(polys, precision_);
for (auto tri : newTris) {
triVerts.push_back(tri);
triNormal.push_back(normal);
triRef.push_back(halfedgeRef[firstEdge]);
}
}
}
faceNormal_ = std::move(triNormal);
CreateHalfedges(triVerts);
}
+30 -37
View File
@@ -31,24 +31,29 @@ using namespace manifold;
constexpr uint64_t kRemove = std::numeric_limits<uint64_t>::max();
void AtomicAddVec3(glm::vec3& target, const glm::vec3& add) {
__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
std::atomic<float>& tar = reinterpret_cast<std::atomic<float>&>(target[i]);
float old_val = tar.load(std::memory_order_relaxed);
while (!tar.compare_exchange_weak(old_val, old_val + add[i],
std::memory_order_relaxed))
;
#endif
}
}
struct Normalize {
void operator()(glm::vec3& v) { v = SafeNormalize(v); }
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
};
struct Transform4x3 {
const glm::mat4x3 transform;
glm::vec3 operator()(glm::vec3 position) {
__host__ __device__ glm::vec3 operator()(glm::vec3 position) {
return transform * glm::vec4(position, 1.0f);
}
};
@@ -60,7 +65,7 @@ struct AssignNormals {
const float precision;
const bool calculateTriNormal;
void operator()(thrust::tuple<glm::vec3&, int> in) {
__host__ __device__ void operator()(thrust::tuple<glm::vec3&, int> in) {
glm::vec3& triNormal = thrust::get<0>(in);
const int face = thrust::get<1>(in);
@@ -97,7 +102,8 @@ struct Tri2Halfedges {
Halfedge* halfedges;
glm::uint64_t* edges;
void operator()(thrust::tuple<int, const glm::ivec3&> in) {
__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}) {
@@ -118,7 +124,7 @@ struct LinkHalfedges {
const int* ids;
const int numEdge;
void operator()(int i) {
__host__ __device__ void operator()(int i) {
const int pair0 = ids[i];
const int pair1 = ids[i + numEdge];
halfedges[pair0].pairedHalfedge = pair1;
@@ -129,10 +135,9 @@ struct LinkHalfedges {
struct MarkVerts {
int* vert;
void operator()(glm::ivec3 triVerts) {
__host__ __device__ void operator()(glm::ivec3 triVerts) {
for (int i : {0, 1, 2}) {
reinterpret_cast<std::atomic<int>*>(vert)[triVerts[i]].store(
1, std::memory_order_relaxed);
vert[triVerts[i]] = 1;
}
}
};
@@ -140,7 +145,7 @@ struct MarkVerts {
struct ReindexTriVerts {
const int* old2new;
void operator()(glm::ivec3& triVerts) {
__host__ __device__ void operator()(glm::ivec3& triVerts) {
for (int i : {0, 1, 2}) {
triVerts[i] = old2new[triVerts[i]];
}
@@ -151,7 +156,7 @@ struct InitializeTriRef {
const int meshID;
const Halfedge* halfedge;
void operator()(thrust::tuple<TriRef&, int> inOut) {
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
TriRef& baryRef = thrust::get<0>(inOut);
int tri = thrust::get<1>(inOut);
@@ -164,7 +169,9 @@ struct InitializeTriRef {
struct UpdateMeshID {
const HashTableD<uint32_t> meshIDold2new;
void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; }
__host__ __device__ void operator()(TriRef& ref) {
ref.meshID = meshIDold2new[ref.meshID];
}
};
struct CoplanarEdge {
@@ -178,8 +185,7 @@ struct CoplanarEdge {
const int numProp;
const float precision;
// FIXME: race condition
void operator()(
__host__ __device__ void operator()(
thrust::tuple<thrust::pair<int, int>&, thrust::pair<int, int>&, int>
inOut) {
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
@@ -277,7 +283,7 @@ struct CheckCoplanarity {
const int* components;
const float precision;
void operator()(int tri) {
__host__ __device__ void operator()(int tri) {
const int component = components[tri];
const int referenceTri = comp2tri[component];
if (referenceTri < 0 || referenceTri == tri) return;
@@ -293,8 +299,7 @@ struct CheckCoplanarity {
// triangle, unmark the entire component so that none of its triangles are
// marked coplanar.
if (glm::abs(glm::dot(normal, vert - origin)) > precision) {
reinterpret_cast<std::atomic<int>*>(comp2tri)[component].store(
-1, std::memory_order_relaxed);
comp2tri[component] = -1;
break;
}
}
@@ -304,7 +309,8 @@ struct CheckCoplanarity {
struct EdgeBox {
const glm::vec3* vertPos;
void operator()(thrust::tuple<Box&, const TmpEdge&> inout) {
__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]);
}
@@ -857,8 +863,7 @@ void Manifold::Impl::IncrementMeshIDs() {
* the input manifold, Q and the faces of this manifold. Returned indices only
* point to forward halfedges.
*/
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q,
bool inverted) const {
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q) const {
VecDH<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
const int numEdge = edges.size();
VecDH<Box> QedgeBB(numEdge);
@@ -866,18 +871,9 @@ SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q,
for_each_n(policy, zip(QedgeBB.begin(), edges.cbegin()), numEdge,
EdgeBox({Q.vertPos_.cptrD()}));
SparseIndices q1p2(0);
if (inverted)
q1p2 = collider_.Collisions<false, true>(QedgeBB);
else
q1p2 = collider_.Collisions<false, false>(QedgeBB);
SparseIndices q1p2 = collider_.Collisions(QedgeBB);
if (inverted)
for_each(policy, countAt(0), countAt(q1p2.size()),
ReindexEdge<true>({edges.cptrD(), q1p2}));
else
for_each(policy, countAt(0), countAt(q1p2.size()),
ReindexEdge<false>({edges.cptrD(), q1p2}));
for_each(policy, q1p2.begin(0), q1p2.end(0), ReindexEdge({edges.cptrD()}));
return q1p2;
}
@@ -885,11 +881,8 @@ SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q,
* 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,
bool inverted) const {
if (inverted)
return collider_.Collisions<false, true>(vertsIn);
else
return collider_.Collisions<false, false>(vertsIn);
SparseIndices Manifold::Impl::VertexCollisionsZ(
const VecDH<glm::vec3>& vertsIn) const {
return collider_.Collisions(vertsIn);
}
} // namespace manifold
+4 -7
View File
@@ -77,9 +77,8 @@ struct Manifold::Impl {
void MarkFailure(Error status);
void Warp(std::function<void(glm::vec3&)> warpFunc);
Impl Transform(const glm::mat4x3& transform) const;
SparseIndices EdgeCollisions(const Impl& B, bool inverted = false) const;
SparseIndices VertexCollisionsZ(const VecDH<glm::vec3>& vertsIn,
bool inverted = false) const;
SparseIndices EdgeCollisions(const Impl& B) const;
SparseIndices VertexCollisionsZ(const VecDH<glm::vec3>& vertsIn) const;
bool IsEmpty() const { return NumVert() == 0; }
int NumVert() const { return vertPos_.size(); }
@@ -121,10 +120,8 @@ struct Manifold::Impl {
// edge_op.cu
void SimplifyTopology();
void DedupeEdge(int edge);
void CollapseEdge(int edge, std::vector<int>& edges);
void RecursiveEdgeSwap(int edge, int& tag, std::vector<int>& visited,
std::vector<int>& edgeSwapStack,
std::vector<int>& edges);
void CollapseEdge(int edge);
void RecursiveEdgeSwap(int edge);
void RemoveIfFolded(int edge);
void PairUp(int edge0, int edge1);
void UpdateVert(int vert, int startEdge, int endEdge);
+5 -8
View File
@@ -26,12 +26,12 @@ namespace {
using namespace manifold;
using namespace thrust::placeholders;
ExecutionParams manifoldParams;
ExecutionParams params;
struct MakeTri {
const Halfedge* halfedges;
void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
__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);
@@ -51,7 +51,7 @@ struct UpdateProperties {
const Halfedge* halfedges;
std::function<void(float*, glm::vec3, const float*)> propFunc;
void operator()(int tri) {
__host__ __device__ void operator()(int tri) {
for (int i : {0, 1, 2}) {
const int vert = halfedges[3 * tri + i].startVert;
const int propVert = triProperties[tri][i];
@@ -260,10 +260,7 @@ MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
if (meshID != lastID) {
Impl::Relation rel;
auto it = meshIDtransform.find(meshID);
if (it != meshIDtransform.end()) rel = it->second;
addRun(out, runNormalTransform, tri, rel);
addRun(out, runNormalTransform, tri, meshIDtransform.at(meshID));
meshIDtransform.erase(meshID);
lastID = meshID;
}
@@ -778,7 +775,7 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const {
return *this ^ Halfspace(BoundingBox(), normal, originOffset);
}
ExecutionParams& ManifoldParams() { return manifoldParams; }
ExecutionParams& ManifoldParams() { return params; }
/**
* Compute the convex hull of a set of points. If the given points are fewer
+4 -4
View File
@@ -3,7 +3,7 @@
namespace {
using namespace manifold;
inline int FlipHalfedge(int halfedge) {
__host__ __device__ inline int FlipHalfedge(int halfedge) {
const int tri = halfedge / 3;
const int vert = 2 - (halfedge - 3 * tri);
return 3 * tri + vert;
@@ -12,7 +12,7 @@ inline int FlipHalfedge(int halfedge) {
struct TransformNormals {
const glm::mat3 transform;
glm::vec3 operator()(glm::vec3 normal) {
__host__ __device__ glm::vec3 operator()(glm::vec3 normal) {
normal = glm::normalize(transform * normal);
if (isnan(normal.x)) normal = glm::vec3(0.0f);
return normal;
@@ -25,7 +25,7 @@ struct TransformTangents {
const glm::vec4* oldTangents;
const Halfedge* halfedge;
void operator()(thrust::tuple<glm::vec4&, int> inOut) {
__host__ __device__ void operator()(thrust::tuple<glm::vec4&, int> inOut) {
glm::vec4& tangent = thrust::get<0>(inOut);
int edge = thrust::get<1>(inOut);
if (invert) {
@@ -40,7 +40,7 @@ struct TransformTangents {
struct FlipTris {
Halfedge* halfedge;
void operator()(thrust::tuple<TriRef&, int> inOut) {
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
TriRef& bary = thrust::get<0>(inOut);
const int tri = thrust::get<1>(inOut);
+17 -15
View File
@@ -29,7 +29,7 @@ struct FaceAreaVolume {
const glm::vec3* vertPos;
const float precision;
thrust::pair<float, float> operator()(int face) {
__host__ __device__ thrust::pair<float, float> operator()(int face) {
float perimeter = 0;
glm::vec3 edge[3];
for (int i : {0, 1, 2}) {
@@ -49,7 +49,7 @@ struct FaceAreaVolume {
struct PosMin
: public thrust::binary_function<glm::vec3, glm::vec3, glm::vec3> {
glm::vec3 operator()(glm::vec3 a, glm::vec3 b) {
__host__ __device__ glm::vec3 operator()(glm::vec3 a, glm::vec3 b) {
if (isnan(a.x)) return b;
if (isnan(b.x)) return a;
return glm::min(a, b);
@@ -58,7 +58,7 @@ struct PosMin
struct PosMax
: public thrust::binary_function<glm::vec3, glm::vec3, glm::vec3> {
glm::vec3 operator()(glm::vec3 a, glm::vec3 b) {
__host__ __device__ glm::vec3 operator()(glm::vec3 a, glm::vec3 b) {
if (isnan(a.x)) return b;
if (isnan(b.x)) return a;
return glm::max(a, b);
@@ -66,11 +66,13 @@ struct PosMax
};
struct FiniteVert {
bool operator()(glm::vec3 v) { return glm::all(glm::isfinite(v)); }
__host__ __device__ bool operator()(glm::vec3 v) {
return glm::all(glm::isfinite(v));
}
};
struct MakeMinMax {
glm::ivec2 operator()(glm::ivec3 tri) {
__host__ __device__ glm::ivec2 operator()(glm::ivec3 tri) {
return glm::ivec2(glm::min(tri[0], glm::min(tri[1], tri[2])),
glm::max(tri[0], glm::max(tri[1], tri[2])));
}
@@ -78,7 +80,7 @@ struct MakeMinMax {
struct MinMax
: public thrust::binary_function<glm::ivec2, glm::ivec2, glm::ivec2> {
glm::ivec2 operator()(glm::ivec2 a, glm::ivec2 b) {
__host__ __device__ glm::ivec2 operator()(glm::ivec2 a, glm::ivec2 b) {
a[0] = glm::min(a[0], b[0]);
a[1] = glm::max(a[1], b[1]);
return a;
@@ -88,8 +90,8 @@ struct MinMax
struct SumPair : public thrust::binary_function<thrust::pair<float, float>,
thrust::pair<float, float>,
thrust::pair<float, float>> {
thrust::pair<float, float> operator()(thrust::pair<float, float> a,
thrust::pair<float, float> b) {
__host__ __device__ thrust::pair<float, float> operator()(
thrust::pair<float, float> a, thrust::pair<float, float> b) {
a.first += b.first;
a.second += b.second;
return a;
@@ -105,7 +107,7 @@ struct CurvatureAngles {
const glm::vec3* vertPos;
const glm::vec3* triNormal;
void operator()(int tri) {
__host__ __device__ void operator()(int tri) {
glm::vec3 edge[3];
glm::vec3 edgeLength(0.0);
for (int i : {0, 1, 2}) {
@@ -140,7 +142,8 @@ struct CurvatureAngles {
};
struct NormalizeCurvature {
void operator()(thrust::tuple<float&, float&, float, float> inOut) {
__host__ __device__ void operator()(
thrust::tuple<float&, float&, float, float> inOut) {
float& meanCurvature = thrust::get<0>(inOut);
float& gaussianCurvature = thrust::get<1>(inOut);
float area = thrust::get<2>(inOut);
@@ -163,8 +166,7 @@ struct UpdateProperties {
const int gaussianIdx;
const int meanIdx;
// FIXME: race condition
void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
__host__ __device__ void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
glm::ivec3& triProp = thrust::get<0>(inOut);
const int tri = thrust::get<1>(inOut);
@@ -193,7 +195,7 @@ struct UpdateProperties {
struct CheckHalfedges {
const Halfedge* halfedges;
bool operator()(int edge) {
__host__ __device__ bool operator()(int edge) {
const Halfedge halfedge = halfedges[edge];
if (halfedge.startVert == -1 && halfedge.endVert == -1) return true;
if (halfedge.pairedHalfedge == -1) return false;
@@ -211,7 +213,7 @@ struct CheckHalfedges {
struct NoDuplicates {
const Halfedge* halfedges;
bool operator()(int edge) {
__host__ __device__ bool operator()(int edge) {
const Halfedge halfedge = halfedges[edge];
if (halfedge.startVert == -1 && halfedge.endVert == -1 &&
halfedge.pairedHalfedge == -1)
@@ -227,7 +229,7 @@ struct CheckCCW {
const glm::vec3* triNormal;
const float tol;
bool operator()(int face) {
__host__ __device__ bool operator()(int face) {
if (halfedges[3 * face].pairedHalfedge < 0) return true;
const glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[face]);
+24 -20
View File
@@ -15,7 +15,6 @@
#pragma once
#include "par.h"
#include "sparse.h"
#include "utils.h"
#include "vec_dh.h"
@@ -24,23 +23,25 @@ namespace manifold {
/** @addtogroup Private
* @{
*/
inline glm::vec3 SafeNormalize(glm::vec3 v) {
__host__ __device__ inline glm::vec3 SafeNormalize(glm::vec3 v) {
v = glm::normalize(v);
return glm::isfinite(v.x) ? v : glm::vec3(0);
}
inline float MaxPrecision(float minPrecision, const Box& bBox) {
__host__ __device__ inline float MaxPrecision(float minPrecision,
const Box& bBox) {
float precision = glm::max(minPrecision, kTolerance * bBox.Scale());
return glm::isfinite(precision) ? precision : -1;
}
inline int NextHalfedge(int current) {
__host__ __device__ inline int NextHalfedge(int current) {
++current;
if (current % 3 == 0) current -= 3;
return current;
}
inline glm::vec3 UVW(int vert, const glm::vec3* barycentric) {
__host__ __device__ inline glm::vec3 UVW(int vert,
const glm::vec3* barycentric) {
glm::vec3 uvw(0.0f);
if (vert < 0) {
uvw[vert + 3] = 1;
@@ -50,7 +51,8 @@ inline glm::vec3 UVW(int vert, const glm::vec3* barycentric) {
return uvw;
}
inline glm::mat3 NormalTransform(const glm::mat4x3& transform) {
__host__ __device__ inline glm::mat3 NormalTransform(
const glm::mat4x3& transform) {
return glm::inverse(glm::transpose(glm::mat3(transform)));
}
@@ -58,7 +60,8 @@ inline glm::mat3 NormalTransform(const glm::mat4x3& transform) {
* By using the closest axis-aligned projection to the normal instead of a
* projection along the normal, we avoid introducing any rounding error.
*/
inline glm::mat3x2 GetAxisAlignedProjection(glm::vec3 normal) {
__host__ __device__ inline glm::mat3x2 GetAxisAlignedProjection(
glm::vec3 normal) {
glm::vec3 absNormal = glm::abs(normal);
float xyzMax;
glm::mat2x3 projection;
@@ -79,8 +82,9 @@ inline glm::mat3x2 GetAxisAlignedProjection(glm::vec3 normal) {
return glm::transpose(projection);
}
inline glm::vec3 GetBarycentric(const glm::vec3& v, const glm::mat3& triPos,
float precision) {
__host__ __device__ inline glm::vec3 GetBarycentric(const glm::vec3& v,
const glm::mat3& triPos,
float precision) {
const glm::mat3 edges(triPos[2] - triPos[1], triPos[0] - triPos[2],
triPos[1] - triPos[0]);
const glm::vec3 d2(glm::dot(edges[0], edges[0]), glm::dot(edges[1], edges[1]),
@@ -134,8 +138,8 @@ struct Halfedge {
int startVert, endVert;
int pairedHalfedge;
int face;
bool IsForward() const { return startVert < endVert; }
bool operator<(const Halfedge& other) const {
__host__ __device__ bool IsForward() const { return startVert < endVert; }
__host__ __device__ bool operator<(const Halfedge& other) const {
return startVert == other.startVert ? endVert < other.endVert
: startVert < other.startVert;
}
@@ -166,21 +170,22 @@ struct TriRef {
struct TmpEdge {
int first, second, halfedgeIdx;
TmpEdge() {}
TmpEdge(int start, int end, int idx) {
__host__ __device__ TmpEdge() {}
__host__ __device__ TmpEdge(int start, int end, int idx) {
first = glm::min(start, end);
second = glm::max(start, end);
halfedgeIdx = idx;
}
bool operator<(const TmpEdge& other) const {
__host__ __device__ bool operator<(const TmpEdge& other) const {
return first == other.first ? second < other.second : first < other.first;
}
};
/** @} */
struct Halfedge2Tmp {
void operator()(thrust::tuple<TmpEdge&, const Halfedge&, int> inout) {
__host__ __device__ void operator()(
thrust::tuple<TmpEdge&, const Halfedge&, int> inout) {
const Halfedge& halfedge = thrust::get<1>(inout);
int idx = thrust::get<2>(inout);
if (!halfedge.IsForward()) idx = -1;
@@ -190,7 +195,9 @@ struct Halfedge2Tmp {
};
struct TmpInvalid {
bool operator()(const TmpEdge& edge) { return edge.halfedgeIdx < 0; }
__host__ __device__ bool operator()(const TmpEdge& edge) {
return edge.halfedgeIdx < 0;
}
};
VecDH<TmpEdge> inline CreateTmpEdges(const VecDH<Halfedge>& halfedge) {
@@ -207,13 +214,10 @@ VecDH<TmpEdge> inline CreateTmpEdges(const VecDH<Halfedge>& halfedge) {
return edges;
}
template <const bool inverted>
struct ReindexEdge {
const TmpEdge* edges;
SparseIndices& indices;
void operator()(int i) {
int& edge = indices.Get(i, inverted);
__host__ __device__ void operator()(int& edge) {
edge = edges[edge].halfedgeIdx;
}
};
+27 -19
View File
@@ -20,7 +20,7 @@
namespace {
using namespace manifold;
glm::vec3 OrthogonalTo(glm::vec3 in, glm::vec3 ref) {
__host__ __device__ glm::vec3 OrthogonalTo(glm::vec3 in, glm::vec3 ref) {
in -= glm::dot(in, ref) * ref;
return in;
}
@@ -29,7 +29,7 @@ glm::vec3 OrthogonalTo(glm::vec3 in, glm::vec3 ref) {
* The total number of verts if a triangle is subdivided naturally such that
* each edge has edgeVerts verts along it (edgeVerts >= -1).
*/
int VertsPerTri(int edgeVerts) {
__host__ __device__ int VertsPerTri(int edgeVerts) {
return (edgeVerts * edgeVerts + edgeVerts) / 2;
}
@@ -54,7 +54,7 @@ void FillRetainedVerts(VecDH<Barycentric>& vertBary,
struct ReindexHalfedge {
int* half2Edge;
void operator()(thrust::tuple<int, TmpEdge> in) {
__host__ __device__ void operator()(thrust::tuple<int, TmpEdge> in) {
const int edge = thrust::get<0>(in);
const int halfedge = thrust::get<1>(in).halfedgeIdx;
@@ -68,7 +68,7 @@ struct EdgeVerts {
const int startIdx;
const int n;
void operator()(thrust::tuple<int, TmpEdge> in) {
__host__ __device__ void operator()(thrust::tuple<int, TmpEdge> in) {
int edge = thrust::get<0>(in);
TmpEdge edgeVerts = thrust::get<1>(in);
@@ -97,7 +97,7 @@ struct InteriorVerts {
const int n;
const Halfedge* halfedge;
void operator()(int tri) {
__host__ __device__ void operator()(int tri) {
const float invTotal = 1.0f / n;
int pos = startIdx + tri * VertsPerTri(n - 2);
for (int i = 0; i <= n; ++i) {
@@ -126,14 +126,14 @@ struct SplitTris {
const int triIdx;
const int n;
int EdgeVert(int i, int inHalfedge) const {
__host__ __device__ int EdgeVert(int i, int inHalfedge) const {
bool forward = halfedge[inHalfedge].IsForward();
int edge = forward ? half2Edge[inHalfedge]
: half2Edge[halfedge[inHalfedge].pairedHalfedge];
return edgeIdx + (n - 1) * edge + (forward ? i - 1 : n - 1 - i);
}
int TriVert(int i, int j, int tri) const {
__host__ __device__ int TriVert(int i, int j, int tri) const {
--i;
--j;
int m = n - 2;
@@ -142,7 +142,7 @@ struct SplitTris {
return triIdx + vertsPerTri * tri + vertOffset;
}
int Vert(int i, int j, int tri) const {
__host__ __device__ int Vert(int i, int j, int tri) const {
bool edge0 = i == 0;
bool edge1 = j == 0;
bool edge2 = j == n - i;
@@ -164,7 +164,7 @@ struct SplitTris {
return TriVert(i, j, tri);
}
void operator()(int tri) {
__host__ __device__ void operator()(int tri) {
int pos = n * n * tri;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n - i; ++j) {
@@ -187,7 +187,8 @@ struct SmoothBezier {
const glm::vec3* vertNormal;
const Halfedge* halfedge;
void operator()(thrust::tuple<glm::vec4&, Halfedge> inOut) {
__host__ __device__ void operator()(
thrust::tuple<glm::vec4&, Halfedge> inOut) {
glm::vec4& tangent = thrust::get<0>(inOut);
const Halfedge edge = thrust::get<1>(inOut);
@@ -216,23 +217,29 @@ struct InterpTri {
const glm::vec4* halfedgeTangent;
const glm::vec3* vertPos;
glm::vec4 Homogeneous(glm::vec4 v) const {
__host__ __device__ glm::vec4 Homogeneous(glm::vec4 v) const {
v.x *= v.w;
v.y *= v.w;
v.z *= v.w;
return v;
}
glm::vec4 Homogeneous(glm::vec3 v) const { return glm::vec4(v, 1.0f); }
__host__ __device__ glm::vec4 Homogeneous(glm::vec3 v) const {
return glm::vec4(v, 1.0f);
}
glm::vec3 HNormalize(glm::vec4 v) const { return glm::vec3(v) / v.w; }
__host__ __device__ glm::vec3 HNormalize(glm::vec4 v) const {
return glm::vec3(v) / v.w;
}
glm::vec4 Bezier(glm::vec3 point, glm::vec4 tangent) const {
__host__ __device__ glm::vec4 Bezier(glm::vec3 point,
glm::vec4 tangent) const {
return Homogeneous(glm::vec4(point, 0) + tangent);
}
glm::mat2x4 CubicBezier2Linear(glm::vec4 p0, glm::vec4 p1, glm::vec4 p2,
glm::vec4 p3, float x) const {
__host__ __device__ glm::mat2x4 CubicBezier2Linear(glm::vec4 p0, glm::vec4 p1,
glm::vec4 p2, glm::vec4 p3,
float x) const {
glm::mat2x4 out;
glm::vec4 p12 = glm::mix(p1, p2, x);
out[0] = glm::mix(glm::mix(p0, p1, x), p12, x);
@@ -240,15 +247,16 @@ struct InterpTri {
return out;
}
glm::vec3 BezierPoint(glm::mat2x4 points, float x) const {
__host__ __device__ glm::vec3 BezierPoint(glm::mat2x4 points, float x) const {
return HNormalize(glm::mix(points[0], points[1], x));
}
glm::vec3 BezierTangent(glm::mat2x4 points) const {
__host__ __device__ glm::vec3 BezierTangent(glm::mat2x4 points) const {
return glm::normalize(HNormalize(points[1]) - HNormalize(points[0]));
}
void operator()(thrust::tuple<glm::vec3&, Barycentric> inOut) {
__host__ __device__ void operator()(
thrust::tuple<glm::vec3&, Barycentric> inOut) {
glm::vec3& pos = thrust::get<0>(inOut);
const int tri = thrust::get<1>(inOut).tri;
const glm::vec3 uvw = thrust::get<1>(inOut).uvw;
+21 -21
View File
@@ -14,7 +14,6 @@
#include <thrust/sequence.h>
#include <atomic>
#include <numeric>
#include <set>
@@ -28,7 +27,7 @@ using namespace manifold;
constexpr uint32_t kNoCode = 0xFFFFFFFFu;
struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
void MakeForward(Halfedge& a) {
__host__ __device__ void MakeForward(Halfedge& a) {
if (!a.IsForward()) {
int tmp = a.startVert;
a.startVert = a.endVert;
@@ -36,11 +35,11 @@ struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
}
}
int MaxOrMinus(int a, int b) {
__host__ __device__ int MaxOrMinus(int a, int b) {
return glm::min(a, b) < 0 ? -1 : glm::max(a, b);
}
Halfedge operator()(Halfedge a, Halfedge b) {
__host__ __device__ Halfedge operator()(Halfedge a, Halfedge b) {
MakeForward(a);
MakeForward(b);
a.startVert = glm::min(a.startVert, b.startVert);
@@ -51,7 +50,7 @@ struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
}
};
uint32_t SpreadBits3(uint32_t v) {
__host__ __device__ uint32_t SpreadBits3(uint32_t v) {
v = 0xFF0000FFu & (v * 0x00010001u);
v = 0x0F00F00Fu & (v * 0x00000101u);
v = 0xC30C30C3u & (v * 0x00000011u);
@@ -59,7 +58,7 @@ uint32_t SpreadBits3(uint32_t v) {
return v;
}
uint32_t MortonCode(glm::vec3 position, Box bBox) {
__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;
@@ -75,7 +74,8 @@ uint32_t MortonCode(glm::vec3 position, Box bBox) {
struct Morton {
const Box bBox;
void operator()(thrust::tuple<uint32_t&, const glm::vec3&> inout) {
__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);
}
@@ -86,7 +86,8 @@ struct FaceMortonBox {
const glm::vec3* vertPos;
const Box bBox;
void operator()(thrust::tuple<uint32_t&, Box&, int> inout) {
__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);
@@ -115,7 +116,7 @@ struct FaceMortonBox {
struct Reindex {
const int* indexInv;
void operator()(Halfedge& edge) {
__host__ __device__ void operator()(Halfedge& edge) {
if (edge.startVert < 0) return;
edge.startVert = indexInv[edge.startVert];
edge.endVert = indexInv[edge.endVert];
@@ -125,10 +126,9 @@ struct Reindex {
struct MarkProp {
int* keep;
void operator()(glm::ivec3 triProp) {
__host__ __device__ void operator()(glm::ivec3 triProp) {
for (const int i : {0, 1, 2}) {
reinterpret_cast<std::atomic<int>*>(keep)[triProp[i]].store(
1, std::memory_order_relaxed);
keep[triProp[i]] = 1;
}
}
};
@@ -138,7 +138,7 @@ struct GatherProps {
const float* oldProperties;
const int numProp;
void operator()(thrust::tuple<int, int, int> in) {
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
const int oldIdx = thrust::get<0>(in);
const int newIdx = thrust::get<1>(in);
const int keep = thrust::get<2>(in);
@@ -152,7 +152,7 @@ struct GatherProps {
struct ReindexProps {
const int* old2new;
void operator()(glm::ivec3& triProp) {
__host__ __device__ void operator()(glm::ivec3& triProp) {
for (const int i : {0, 1, 2}) {
triProp[i] = old2new[triProp[i]];
}
@@ -178,7 +178,7 @@ struct ReindexFace {
const int* faceNew2Old;
const int* faceOld2New;
void operator()(int newFace) {
__host__ __device__ void operator()(int newFace) {
const int oldFace = faceNew2Old[newFace];
for (const int i : {0, 1, 2}) {
const int oldEdge = 3 * oldFace + i;
@@ -202,7 +202,8 @@ struct VertMortonBox {
const float tol;
const Box bBox;
void operator()(thrust::tuple<uint32_t&, Box&, int> inout) {
__host__ __device__ void operator()(
thrust::tuple<uint32_t&, Box&, int> inout) {
uint32_t& mortonCode = thrust::get<0>(inout);
Box& vertBox = thrust::get<1>(inout);
int vert = thrust::get<2>(inout);
@@ -219,7 +220,7 @@ struct VertMortonBox {
};
struct Duplicate {
thrust::pair<float, float> operator()(float x) {
__host__ __device__ thrust::pair<float, float> operator()(float x) {
return thrust::make_pair(x, x);
}
};
@@ -227,8 +228,8 @@ struct Duplicate {
struct MinMax : public thrust::binary_function<thrust::pair<float, float>,
thrust::pair<float, float>,
thrust::pair<float, float>> {
thrust::pair<float, float> operator()(thrust::pair<float, float> a,
thrust::pair<float, float> b) {
__host__ __device__ thrust::pair<float, float> operator()(
thrust::pair<float, float> a, thrust::pair<float, float> b) {
return thrust::make_pair(glm::min(a.first, b.first),
glm::max(a.second, b.second));
}
@@ -589,8 +590,7 @@ bool MeshGL::Merge() {
static_cast<int>(mergeToVert[i]));
}
for (int i = 0; i < toMerge.size(); ++i) {
graph.add_edge(openVerts[toMerge.Get(i, false)],
openVerts[toMerge.Get(i, true)]);
graph.add_edge(openVerts[toMerge.Get(0)[i]], openVerts[toMerge.Get(1)[i]]);
}
std::vector<int> vertLabels;
+1 -1
View File
@@ -15,7 +15,7 @@
project (polygon)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} ${SOURCE_FILES})
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
+372 -366
View File
@@ -15,14 +15,8 @@
#include "polygon.h"
#include <algorithm>
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
#include <execution>
#endif
#include <list>
#include <map>
#if __has_include(<memory_resource>)
#include <memory_resource>
#endif
#include <queue>
#include <set>
#include <stack>
@@ -32,7 +26,7 @@
namespace {
using namespace manifold;
static ExecutionParams params;
ExecutionParams params;
#ifdef MANIFOLD_DEBUG
struct PolyEdge {
@@ -83,7 +77,6 @@ std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx &polys) {
std::vector<PolyEdge> Triangles2Edges(
const std::vector<glm::ivec3> &triangles) {
std::vector<PolyEdge> halfedges;
halfedges.reserve(triangles.size() * 3);
for (const glm::ivec3 &tri : triangles) {
halfedges.push_back({tri[0], tri[1]});
halfedges.push_back({tri[1], tri[2]});
@@ -144,7 +137,7 @@ void CheckTopology(const std::vector<glm::ivec3> &triangles,
void CheckGeometry(const std::vector<glm::ivec3> &triangles,
const PolygonsIdx &polys, float precision) {
std::unordered_map<int, glm::vec2> vertPos;
std::map<int, glm::vec2> vertPos;
for (const auto &poly : polys) {
for (int i = 0; i < poly.size(); ++i) {
vertPos[poly[i].idx] = poly[i].pos;
@@ -208,7 +201,7 @@ class Monotones {
monotones_.push_back({poly[i].pos, //
poly[i].idx, //
0, monotones_.end(), monotones_.end(),
activeEdges_.end()});
activePairs_.end(), activePairs_.end()});
bound = glm::max(
bound, glm::max(glm::abs(poly[i].pos.x), glm::abs(poly[i].pos.y)));
@@ -310,25 +303,14 @@ class Monotones {
private:
struct VertAdj;
struct Edge;
enum VertType { Start, Backward, Forward, Merge, End, Skip };
#if __has_include(<memory_resource>)
typedef std::pmr::list<VertAdj>::iterator VertItr;
typedef std::pmr::list<Edge>::iterator EdgeItr;
std::pmr::monotonic_buffer_resource mbr;
std::pmr::polymorphic_allocator<int> pa{&mbr};
std::pmr::list<VertAdj> monotones_{pa}; // sweep-line list of verts
std::pmr::list<Edge> activeEdges_{pa}; // west to east monotone edges
std::pmr::list<Edge> inactiveEdges_{pa}; // completed monotones
#else
typedef std::list<VertAdj>::iterator VertItr;
typedef std::list<Edge>::iterator EdgeItr;
struct EdgePair;
typedef std::list<EdgePair>::iterator PairItr;
enum VertType { Start, WestSide, EastSide, Merge, End, Skip };
std::list<VertAdj> monotones_; // sweep-line list of verts
std::list<Edge> activeEdges_; // west to east monotone edges
std::list<Edge> inactiveEdges_; // completed monotones
#endif
std::list<VertAdj> monotones_; // sweep-line list of verts
std::list<EdgePair> activePairs_; // west to east list of monotone edge pairs
std::list<EdgePair> inactivePairs_; // completed monotones
float precision_; // a triangle of this height or less is degenerate
/**
@@ -343,7 +325,7 @@ class Monotones {
int mesh_idx; // This is a global index into the manifold.
int index;
VertItr left, right;
EdgeItr edgeL, edgeR;
PairItr eastPair, westPair;
bool Processed() const { return index < 0; }
void SetSkip() { index = -2; }
@@ -378,16 +360,28 @@ class Monotones {
* certain.
*/
struct Edge {
VertItr south; //, chain;
EdgeItr linked, next; //, bottomEast;
bool forward, linked2east, flipped, eastCertain;
float minDegenerateY = 0;
struct EdgePair {
VertItr vWest, vEast, vMerge;
PairItr nextPair;
bool westCertain, eastCertain, startCertain;
VertItr North() const { return forward ? south->right : south->left; }
int WestOf(VertItr vert, float precision) const {
int westOf = CCW(vEast->right->pos, vEast->pos, vert->pos, precision);
if (westOf == 0 && !vert->right->Processed())
westOf =
CCW(vEast->right->pos, vEast->pos, vert->right->pos, precision);
if (westOf == 0 && !vert->left->Processed())
westOf = CCW(vEast->right->pos, vEast->pos, vert->left->pos, precision);
return westOf;
}
int EastOf(VertItr vert, float precision) const {
return CCW(south->pos, North()->pos, vert->pos, precision);
int eastOf = CCW(vWest->pos, vWest->left->pos, vert->pos, precision);
if (eastOf == 0 && !vert->right->Processed())
eastOf = CCW(vWest->pos, vWest->left->pos, vert->right->pos, precision);
if (eastOf == 0 && !vert->left->Processed())
eastOf = CCW(vWest->pos, vWest->left->pos, vert->left->pos, precision);
return eastOf;
}
};
@@ -398,7 +392,7 @@ class Monotones {
class Triangulator {
public:
Triangulator(VertItr vert, float precision) : precision_(precision) {
reflex_chain_.push_back(vert);
reflex_chain_.push(vert);
other_side_ = vert;
}
int NumTriangles() const { return triangles_output_; }
@@ -413,14 +407,14 @@ class Monotones {
*/
void ProcessVert(const VertItr vi, bool onRight, bool last,
std::vector<glm::ivec3> &triangles) {
VertItr v_top = reflex_chain_.back();
VertItr v_top = reflex_chain_.top();
if (reflex_chain_.size() < 2) {
reflex_chain_.push_back(vi);
reflex_chain_.push(vi);
onRight_ = onRight;
return;
}
reflex_chain_.pop_back();
VertItr vj = reflex_chain_.back();
reflex_chain_.pop();
VertItr vj = reflex_chain_.top();
if (onRight_ == onRight && !last) {
// This only creates enough triangles to ensure the reflex chain is
// still reflex.
@@ -429,13 +423,13 @@ class Monotones {
while (ccw == (onRight_ ? 1 : -1) || ccw == 0) {
AddTriangle(triangles, vi, vj, v_top);
v_top = vj;
reflex_chain_.pop_back();
reflex_chain_.pop();
if (reflex_chain_.empty()) break;
vj = reflex_chain_.back();
vj = reflex_chain_.top();
ccw = CCW(vi->pos, vj->pos, v_top->pos, precision_);
}
reflex_chain_.push_back(v_top);
reflex_chain_.push_back(vi);
reflex_chain_.push(v_top);
reflex_chain_.push(vi);
} else {
// This branch empties the reflex chain and switches sides. It must be
// used for the last point, as it will output all the triangles
@@ -444,19 +438,19 @@ class Monotones {
onRight_ = !onRight_;
VertItr v_last = v_top;
while (!reflex_chain_.empty()) {
vj = reflex_chain_.back();
vj = reflex_chain_.top();
AddTriangle(triangles, vi, v_last, vj);
v_last = vj;
reflex_chain_.pop_back();
reflex_chain_.pop();
}
reflex_chain_.push_back(v_top);
reflex_chain_.push_back(vi);
reflex_chain_.push(v_top);
reflex_chain_.push(vi);
other_side_ = v_top;
}
}
private:
std::vector<VertItr> reflex_chain_;
std::stack<VertItr> reflex_chain_;
VertItr other_side_; // The end vertex across from the reflex chain
bool onRight_; // The side the reflex chain is on
int triangles_output_ = 0;
@@ -476,26 +470,38 @@ class Monotones {
right->left = left;
}
void UpdateEdge(EdgeItr edge, VertItr vert) {
edge->south = vert;
vert->edgeL = edge;
vert->edgeR = edge;
void SetVWest(PairItr pair, VertItr vert) {
pair->vWest = vert;
vert->eastPair = pair;
}
void LinkEdges(EdgeItr edge1, EdgeItr edge2) {
edge1->linked = edge2;
edge2->linked = edge1;
void SetVEast(PairItr pair, VertItr vert) {
pair->vEast = vert;
vert->westPair = pair;
}
void SetEastCertainty(PairItr westPair, bool certain) {
westPair->eastCertain = certain;
std::next(westPair)->westCertain = certain;
}
PairItr GetPair(VertItr vert, VertType type) const {
// Merge returns westPair, as this is the one that will be removed.
return type == WestSide ? vert->eastPair : vert->westPair;
}
bool Coincident(glm::vec2 p0, glm::vec2 p1) const {
glm::vec2 sep = p0 - p1;
return glm::dot(sep, sep) < precision_ * precision_;
}
void CloseEnd(VertItr vert) {
EdgeItr edgeR = vert->right->edgeL;
EdgeItr edgeL = vert->left->edgeR;
edgeR->south = vert;
edgeL->south = vert;
vert->edgeR = edgeR;
vert->edgeL = edgeL;
edgeL->eastCertain = true;
edgeR->eastCertain = true;
PairItr eastPair = vert->right->eastPair;
PairItr westPair = vert->left->westPair;
SetVWest(eastPair, vert);
SetVEast(westPair, vert);
westPair->westCertain = true;
westPair->eastCertain = true;
}
/**
@@ -503,67 +509,49 @@ class Monotones {
* determines the topology of the vertex relative to the sweep line.
*/
VertType ProcessVert(VertItr vert) {
PairItr eastPair = vert->right->eastPair;
PairItr westPair = vert->left->westPair;
if (vert->right->Processed()) {
if (vert->left->Processed()) {
const EdgeItr edgeR = vert->right->edgeL;
const EdgeItr edgeL = vert->left->edgeR;
// if (CCW(vert->pos, vert->right->pos, vert->left->pos, precision_) ==
// 0) {
// if (edgeL->linked2east && !edgeR->linked2east) {
// activeEdges_.splice(edgeL, activeEdges_, edgeR->linked,
// std::next(edgeR));
// } else if (!edgeL->linked2east && edgeR->linked2east) {
// activeEdges_.splice(edgeR, activeEdges_, edgeL->linked,
// std::next(edgeL));
// }
// }
if (std::next(edgeR) != edgeL && std::next(edgeL) != edgeR) {
if (westPair == eastPair) {
// facing in
PRINT("End");
CloseEnd(vert);
return End;
} else if (westPair != activePairs_.end() &&
std::next(westPair) == eastPair) {
// facing out
PRINT("Merge");
CloseEnd(vert);
// westPair will be removed and eastPair takes over.
SetVWest(eastPair, westPair->vWest);
return Merge;
} else { // not neighbors
PRINT("Skip");
return Skip;
}
edgeR->south = vert;
edgeL->south = vert;
vert->edgeR = edgeR;
vert->edgeL = edgeL;
LinkEdges(edgeL->linked, edgeR->linked);
if (std::next(edgeR) == edgeL) { // facing in
PRINT("End");
return End;
} else { // facing out
PRINT("Merge");
return Merge;
}
} else {
const EdgeItr bwdEdge = vert->right->edgeL;
const EdgeItr fwdEdge = std::next(bwdEdge);
if (!vert->IsPast(vert->right, precision_) &&
!fwdEdge->south->right->IsPast(vert, precision_) &&
vert->IsPast(fwdEdge->south, precision_) &&
vert->pos.x > fwdEdge->south->right->pos.x + precision_) {
PRINT("Skip backward edge");
if (!eastPair->vEast->right->IsPast(vert, precision_) &&
vert->IsPast(eastPair->vEast, precision_) &&
vert->pos.x > eastPair->vEast->right->pos.x + precision_) {
PRINT("Skip WEST");
return Skip;
}
UpdateEdge(bwdEdge, vert);
PRINT("Backward");
return Backward;
SetVWest(eastPair, vert);
PRINT("WestSide");
return WestSide;
}
} else {
if (vert->left->Processed()) {
const EdgeItr fwdEdge = vert->left->edgeR;
const EdgeItr bwdEdge = std::prev(fwdEdge);
if (!vert->IsPast(vert->left, precision_) &&
!bwdEdge->south->left->IsPast(vert, precision_) &&
vert->IsPast(bwdEdge->south, precision_) &&
vert->pos.x < bwdEdge->south->left->pos.x - precision_) {
PRINT("Skip forward edge");
if (!westPair->vWest->left->IsPast(vert, precision_) &&
vert->IsPast(westPair->vWest, precision_) &&
vert->pos.x < westPair->vWest->left->pos.x - precision_) {
PRINT("Skip EAST");
return Skip;
}
UpdateEdge(fwdEdge, vert);
PRINT("Forward");
return Forward;
SetVEast(westPair, vert);
PRINT("EastSide");
return EastSide;
} else {
PRINT("Start");
return Start;
@@ -572,180 +560,210 @@ class Monotones {
}
/**
* Remove this edge and its pair to the east, but save them and mark the edge
* they were next to. When the reverse sweep happens, it will be placed next
* to its last neighbor instead of using geometry.
* Remove this pair, but save it and mark the pair it was next to. When the
* reverse sweep happens, it will be placed next to its last neighbor instead
* of using geometry.
*/
void RemovePair(EdgeItr westEdge) {
EdgeItr eastEdge = std::next(westEdge);
EdgeItr nextEast = std::next(eastEdge);
westEdge->next = eastEdge->next = nextEast;
inactiveEdges_.splice(inactiveEdges_.end(), activeEdges_, westEdge,
nextEast);
void RemovePair(PairItr pair) {
pair->nextPair = std::next(pair);
inactivePairs_.splice(inactivePairs_.end(), activePairs_, pair);
}
VertType PlaceStart(VertItr vert) {
EdgeItr eastEdge = activeEdges_.begin();
while (eastEdge != activeEdges_.end() && eastEdge->EastOf(vert, 0) <= 0) {
++eastEdge;
}
/**
* When vert is a Start, this determines if it is backwards (forming a void or
* hole). Usually the first return is adequate, but if it is degenerate, the
* function will continue to search up the neighbors until the degeneracy is
* broken and a certain answer is returned. Like CCW, this function returns 1
* for a hole, -1 for a start, and 0 only if the entire polygon degenerates to
* a line.
*/
int IsHole(VertItr vert) const {
VertItr left = vert->left;
VertItr right = vert->right;
VertItr center = vert;
// TODO: if left or right is Processed(), determine from east/west
while (left != right) {
if (Coincident(left->pos, center->pos)) {
left = left->left;
continue;
}
if (Coincident(right->pos, center->pos)) {
right = right->right;
continue;
}
if (Coincident(left->pos, right->pos)) {
vert = center;
center = left;
left = left->left;
if (left == right) break;
right = right->right;
continue;
}
int isHole = CCW(right->pos, center->pos, left->pos, precision_);
if (center != vert) {
isHole += CCW(left->pos, center->pos, vert->pos, precision_) +
CCW(vert->pos, center->pos, right->pos, precision_);
}
if (isHole != 0) return isHole;
bool isHole = CCW(vert->left->pos, vert->pos, vert->right->pos, 0) < 0;
const bool holeCertain =
CCW(vert->left->pos, vert->pos, vert->right->pos, precision_) != 0;
const bool shouldBeStart =
eastEdge == activeEdges_.end() || !eastEdge->forward;
if (isHole == shouldBeStart) { // invalid
if (!holeCertain) {
isHole = !isHole;
} else { // shift to a valid position
if (eastEdge != activeEdges_.end() &&
eastEdge->EastOf(vert, precision_) <= 0) {
++eastEdge;
} else if (eastEdge != activeEdges_.begin() &&
std::prev(eastEdge)->EastOf(vert, precision_) >= 0) {
--eastEdge;
glm::vec2 edgeLeft = left->pos - center->pos;
glm::vec2 edgeRight = right->pos - center->pos;
if (glm::dot(edgeLeft, edgeRight) > 0) {
if (glm::dot(edgeLeft, edgeLeft) < glm::dot(edgeRight, edgeRight)) {
center = left;
left = left->left;
} else {
return Skip;
center = right;
right = right->right;
}
} else {
if (left->pos.y < right->pos.y) {
left = left->left;
} else {
right = right->right;
}
}
}
return 0;
}
const EdgeItr noEdge = activeEdges_.end();
// if (eastEdge != activeEdges_.begin()) {
// const EdgeItr westEdge = std::prev(eastEdge);
// westEdge->eastCertain = false;
// }
const EdgeItr newEastEdge = activeEdges_.insert(
eastEdge, {vert, noEdge, noEdge, !isHole, false, false,
eastEdge == activeEdges_.end() ||
eastEdge->EastOf(vert, precision_) > 0});
const EdgeItr newWestEdge = activeEdges_.insert(
newEastEdge, {vert, noEdge, noEdge, isHole, true, false, holeCertain});
vert->edgeR = isHole ? newWestEdge : newEastEdge;
vert->edgeL = isHole ? newEastEdge : newWestEdge;
LinkEdges(newEastEdge, newWestEdge);
return Start;
/**
* If the simple polygon connected to the input vert degenerates to a single
* line (more strict than IsHole==0), then any triangulation is admissible,
* since every possible triangle will be degenerate.
*/
bool IsColinearPoly(const VertItr start) const {
VertItr vert = start;
VertItr left = start;
VertItr right = left->right;
// Find the longest edge to improve error
float length2 = 0;
while (right != start) {
glm::vec2 edge = left->pos - right->pos;
const float l2 = glm::dot(edge, edge);
if (l2 > length2) {
length2 = l2;
vert = left;
}
left = right;
right = right->right;
}
right = vert->right;
left = vert->left;
while (left != vert) {
if (CCW(left->pos, vert->pos, right->pos, precision_) != 0) return false;
left = left->left;
}
return true;
}
/**
* Causes the verts of the simple polygon attached to the input vert to be
* skipped during the forward and backward sweeps, causing this polygon to be
* triangulated as though it is monotone.
*/
void SkipPoly(VertItr vert) {
vert->SetSkip();
VertItr right = vert->right;
while (right != vert) {
right->SetSkip();
right = right->right;
}
}
/**
* A backwards pair (hole) must be interior to a forwards pair for geometric
* validity. In this situation, this function is used to swap their east edges
* such that they become forward neighbor pairs. The outside becomes westPair
* and inside becomes eastPair.
*/
void SwapHole(PairItr outside, PairItr inside) {
VertItr tmp = outside->vEast;
SetVEast(outside, inside->vEast);
SetVEast(inside, tmp);
inside->eastCertain = outside->eastCertain;
activePairs_.splice(std::next(outside), activePairs_, inside);
SetEastCertainty(outside, true);
}
/**
* This is the key function for handling east-west degeneracies, and is the
* purpose of running the sweep-line forwards and backwards. If the ordering
* of inputEdge is uncertain, this function uses the edge ahead of vert to
* of inputPair is uncertain, this function uses the edge ahead of vert to
* check if this new bit of geometric information is enough to place the pair
* with certainty. It can also invert the pair if it is determined to be a
* hole, in which case the inputEdge becomes the eastEdge while the pair it is
* inside of becomes the westEdge.
* hole, in which case the inputPair becomes the eastPair while the pair it is
* inside of becomes the westPair.
*
* This function normally returns false, but will instead return true if the
* certainties conflict, indicating this vertex is not yet geometrically valid
* and must be skipped.
*/
bool PlaceUncertain(const EdgeItr inputEdge) {
const VertItr vert = inputEdge->North();
EdgeItr eastEdge = std::next(inputEdge);
bool swap = false;
bool pastLinked = !inputEdge->linked2east;
bool ShiftEast(const VertItr vert, const PairItr inputPair,
const bool isHole) {
if (inputPair->eastCertain) return false;
// Shift Eastwards
while (eastEdge != activeEdges_.end() &&
!std::prev(eastEdge)->eastCertain && eastEdge->EastOf(vert, 0) < 0) {
if (eastEdge == inputEdge->linked) {
swap = !swap;
pastLinked = true;
PairItr potentialPair = std::next(inputPair);
while (potentialPair != activePairs_.end()) {
const int EastOf = potentialPair->EastOf(vert, precision_);
// This does not trigger a skip because ShiftWest may still succeed, and
// if not it will mark the skip.
if (EastOf > 0 && isHole) return false;
if (EastOf >= 0 && !isHole) { // in the right place
activePairs_.splice(potentialPair, activePairs_, inputPair);
SetEastCertainty(inputPair, EastOf != 0);
return false;
}
++eastEdge;
swap = !swap;
}
// Shift Westwards
if (eastEdge == std::next(inputEdge)) {
pastLinked = inputEdge->linked2east;
eastEdge = inputEdge;
while (eastEdge != activeEdges_.begin()) {
--eastEdge;
if (eastEdge->eastCertain || eastEdge->EastOf(vert, 0) <= 0) {
++eastEdge;
if (eastEdge == inputEdge->linked) pastLinked = false;
break;
}
swap = !swap;
if (eastEdge == inputEdge->linked) {
swap = !swap;
pastLinked = true;
}
}
}
if (eastEdge == inputEdge ||
eastEdge == std::next(inputEdge)) { // has not moved
return false;
}
const bool linkedCertain = LinkedCertain(inputEdge);
if (linkedCertain) {
if (swap) {
return true;
} else {
activeEdges_.splice(eastEdge, activeEdges_, inputEdge);
}
} else {
if (!pastLinked) return true;
EdgeItr westEdge = inputEdge->linked2east ? inputEdge : inputEdge->linked;
const EdgeItr afterEdge =
std::next(inputEdge->linked2east ? inputEdge->linked : inputEdge);
if (swap) {
EdgeItr next = std::next(westEdge);
while (westEdge != afterEdge) {
westEdge->linked2east ^= true;
westEdge->flipped ^= true;
activeEdges_.splice(eastEdge, activeEdges_, westEdge);
eastEdge = westEdge;
westEdge = next++;
}
} else {
EdgeItr test = westEdge;
while (test != afterEdge) {
ASSERT(eastEdge != test++, logicErr, "WTF");
}
activeEdges_.splice(eastEdge, activeEdges_, westEdge, afterEdge);
const int outside = potentialPair->WestOf(vert, precision_);
if (outside <= 0 && isHole) { // certainly a hole
SwapHole(potentialPair, inputPair);
return false;
}
++potentialPair;
}
if (isHole) return true;
activePairs_.splice(activePairs_.end(), activePairs_, inputPair);
inputPair->eastCertain = true;
return false;
}
bool LinkedCertain(const EdgeItr edge) {
bool certain = false;
EdgeItr westEdge = edge->linked2east ? edge : edge->linked;
const EdgeItr eastEdge = edge->linked2east ? edge->linked : edge;
while (westEdge != eastEdge) {
if (westEdge->eastCertain) {
certain = true;
break;
}
ASSERT(westEdge != activeEdges_.end(), logicErr,
"Did not find linked edge!");
++westEdge;
}
return certain;
}
/**
* Identical to the above function, but swapped to search westward instead.
*/
bool ShiftWest(const VertItr vert, const PairItr inputPair,
const bool isHole) {
if (inputPair->westCertain) return false;
void UpdateCertainty(const EdgeItr edge) {
const VertItr vert = edge->North();
if (!edge->eastCertain) {
const EdgeItr eastEdge = std::next(edge);
edge->eastCertain = eastEdge == activeEdges_.end() ||
eastEdge->EastOf(vert, precision_) > 0;
}
if (edge != activeEdges_.begin()) {
const EdgeItr westEdge = std::prev(edge);
if (!westEdge->eastCertain) {
westEdge->eastCertain = westEdge->EastOf(vert, precision_) > 0;
PairItr potentialPair = inputPair;
while (potentialPair != activePairs_.begin()) {
--potentialPair;
const int WestOf = potentialPair->WestOf(vert, precision_);
if (WestOf > 0 && isHole) return true;
if (WestOf >= 0 && !isHole) { // in the right place
SetEastCertainty(potentialPair, WestOf != 0);
if (++potentialPair != inputPair)
activePairs_.splice(potentialPair, activePairs_, inputPair);
return false;
}
const int outside = potentialPair->EastOf(vert, precision_);
if (outside <= 0 && isHole) { // certainly a hole
SwapHole(potentialPair, inputPair);
return false;
}
}
if (isHole) return true;
if (inputPair != activePairs_.begin())
activePairs_.splice(activePairs_.begin(), activePairs_, inputPair);
inputPair->westCertain = true;
return false;
}
/**
@@ -766,11 +784,7 @@ class Monotones {
starts.push_back(v);
}
}
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
std::sort(std::execution::par_unseq, starts.begin(), starts.end(), cmp);
#else
std::sort(starts.begin(), starts.end(), cmp);
#endif
std::vector<VertItr> skipped;
VertItr insertAt = monotones_.begin();
@@ -793,42 +807,40 @@ class Monotones {
++insertAt;
}
if (vert->Processed()) continue;
PRINT("mesh_idx = " << vert->mesh_idx);
if (vert->Processed()) continue;
OVERLAP_ASSERT(
skipped.empty() || !vert->IsPast(skipped.back(), precision_),
"Not Geometrically Valid! None of the skipped verts is valid.");
VertType type = ProcessVert(vert);
PairItr newPair = activePairs_.end();
bool isHole = false;
if (type == Start) {
type = PlaceStart(vert);
newPair = activePairs_.insert(
activePairs_.begin(), {vert, vert, monotones_.end(),
activePairs_.end(), false, false, false});
SetVWest(newPair, vert);
SetVEast(newPair, vert);
const int hole = IsHole(vert);
if (hole == 0 && IsColinearPoly(vert)) {
PRINT("Skip colinear polygon");
SkipPoly(vert);
activePairs_.erase(newPair);
continue;
}
isHole = hole > 0;
}
if (type != Skip && !vert->right->Processed()) {
if (PlaceUncertain(vert->edgeR)) {
if (type == Start) {
activeEdges_.erase(vert->edgeL);
activeEdges_.erase(vert->edgeR);
}
type = Skip;
} else {
UpdateCertainty(vert->edgeR);
}
}
if (type != Skip && !vert->left->Processed()) {
if (PlaceUncertain(vert->edgeL)) {
if (type == Start) {
activeEdges_.erase(vert->edgeL);
activeEdges_.erase(vert->edgeR);
}
type = Skip;
} else {
UpdateCertainty(vert->edgeL);
}
}
const PairItr pair = GetPair(vert, type);
OVERLAP_ASSERT(type == Skip || pair != activePairs_.end(),
"No active pair!");
if (type != Skip && ShiftEast(vert, pair, isHole)) type = Skip;
if (type != Skip && ShiftWest(vert, pair, isHole)) type = Skip;
if (type == Skip) {
OVERLAP_ASSERT(std::next(insertAt) != monotones_.end(),
@@ -838,6 +850,12 @@ class Monotones {
"Not Geometrically Valid! Tried to skip last queued vert.");
skipped.push_back(vert);
PRINT("Skipping vert");
// If a new pair was added, remove it.
if (newPair != activePairs_.end()) {
activePairs_.erase(newPair);
vert->westPair = activePairs_.end();
vert->eastPair = activePairs_.end();
}
continue;
}
@@ -847,10 +865,10 @@ class Monotones {
monotones_.splice(insertAt, monotones_, vert);
switch (type) {
case Backward:
case WestSide:
nextAttached.push(vert->left);
break;
case Forward:
case EastSide:
nextAttached.push(vert->right);
break;
case Start:
@@ -858,10 +876,10 @@ class Monotones {
nextAttached.push(vert->right);
break;
case Merge:
RemovePair(vert->edgeL);
break;
// Mark merge as hole for sweep-back.
pair->vMerge = vert;
case End:
RemovePair(vert->edgeR);
RemovePair(pair);
break;
case Skip:
break;
@@ -875,7 +893,7 @@ class Monotones {
}
#ifdef MANIFOLD_DEBUG
if (params.verbose) ListActive();
if (params.verbose) ListPairs();
#endif
}
return false;
@@ -905,14 +923,6 @@ class Monotones {
return northEast;
}
VertItr CheckSplit(VertItr vert, EdgeItr westEdge) {
if (westEdge->next != activeEdges_.end()) {
vert = SplitVerts(vert, westEdge->next->south);
westEdge->next = activeEdges_.end(); // unmark merge
}
return vert;
}
/**
* This function sweeps back, splitting the input polygons
* into monotone polygons without doing a single geometric calculation.
@@ -929,96 +939,92 @@ class Monotones {
while (vert != monotones_.begin()) {
--vert;
if (vert->Processed()) continue;
PRINT("mesh_idx = " << vert->mesh_idx);
if (vert->Processed()) continue;
VertType type = ProcessVert(vert);
OVERLAP_ASSERT(type != Skip, "Skip should not happen on reverse sweep!");
if (type == Merge) {
vert = CheckSplit(vert, vert->edgeR);
const EdgeItr westOf = std::prev(vert->edgeL);
CheckSplit(vert, westOf);
westOf->next = vert->edgeL;
} else if (type == End) {
CheckSplit(vert, vert->edgeR);
}
PairItr westPair = GetPair(vert, type);
OVERLAP_ASSERT(type == Start || westPair != activePairs_.end(),
"No active pair!");
if (type == Merge || type == End) {
inactiveEdges_.splice(inactiveEdges_.end(), activeEdges_, vert->edgeR);
inactiveEdges_.splice(inactiveEdges_.end(), activeEdges_, vert->edgeL);
} else if (type == Forward) {
CheckSplit(vert, std::prev(vert->edgeL));
} else if (type == Backward) {
CheckSplit(vert, vert->edgeR);
} else if (type == Start) {
// Due to sweeping in the opposite direction, east and west are
// swapped and what was the next pair is now the previous pair and
// begin and end are swapped.
EdgeItr westEdge = vert->edgeL;
EdgeItr eastEdge = vert->edgeR;
EdgeItr eastOf = westEdge->next;
if (std::next(eastEdge) == westEdge) std::swap(eastEdge, westEdge);
if (!westEdge->flipped) {
std::swap(westEdge, eastEdge);
eastOf = eastOf == activeEdges_.end() ? activeEdges_.begin()
: std::next(eastOf);
switch (type) {
case Merge: {
PairItr eastPair = std::next(westPair);
if (eastPair->vMerge != monotones_.end())
vert = SplitVerts(vert, eastPair->vMerge);
eastPair->vMerge = vert;
}
case End:
RemovePair(westPair);
case WestSide:
case EastSide:
if (westPair->vMerge != monotones_.end()) {
VertItr eastVert = SplitVerts(vert, westPair->vMerge);
if (type == WestSide) westPair->vWest = eastVert;
westPair->vMerge = monotones_.end();
}
break;
case Start: {
// Due to sweeping in the opposite direction, east and west are
// swapped and what was the next pair is now the previous pair and
// begin and end are swapped.
PairItr eastPair = westPair;
westPair = eastPair->nextPair;
activePairs_.splice(westPair == activePairs_.end()
? activePairs_.begin()
: std::next(westPair),
inactivePairs_, eastPair);
activeEdges_.splice(eastOf, inactiveEdges_, eastEdge);
activeEdges_.splice(eastEdge, inactiveEdges_, westEdge);
westEdge->forward ^= true;
eastEdge->forward ^= true;
const bool isHole = westEdge->forward;
if (isHole) {
EdgeItr westOf = std::prev(westEdge);
VertItr split =
westOf->next != activeEdges_.end() ? westOf->next->south
: westOf->south->pos.y < eastOf->south->pos.y ? eastOf->south
: westOf->south;
VertItr eastVert = SplitVerts(vert, split);
westOf->next = activeEdges_.end();
UpdateEdge(eastEdge, eastVert);
UpdateEdge(westEdge, vert);
} else { // Start
vert->edgeL = westEdge;
vert->edgeR = eastEdge;
if (eastPair->vMerge == vert) { // Hole
VertItr split = westPair->vMerge != monotones_.end()
? westPair->vMerge
: westPair->vWest->pos.y < westPair->vEast->pos.y
? westPair->vWest
: westPair->vEast;
VertItr eastVert = SplitVerts(vert, split);
westPair->vMerge = monotones_.end();
eastPair->vMerge = monotones_.end();
SetVWest(eastPair, eastVert);
SetVEast(eastPair, split == westPair->vEast ? eastVert->right
: westPair->vEast);
SetVEast(westPair, vert);
} else { // Start
SetVWest(eastPair, vert);
SetVEast(eastPair, vert);
}
break;
}
westEdge->next = activeEdges_.end();
eastEdge->next = activeEdges_.end();
case Skip:
break;
}
vert->SetProcessed(true);
#ifdef MANIFOLD_DEBUG
if (params.verbose) ListActive();
if (params.verbose) ListPairs();
#endif
}
return false;
}
#ifdef MANIFOLD_DEBUG
void ListEdge(const EdgeItr edge) const {
std::cout << (edge->forward ? "Fwd" : "Bwd");
std::cout << ": S = " << edge->south->mesh_idx
<< ", N = " << edge->North()->mesh_idx;
std::cout << (edge->next == activeEdges_.end() ? " none" : " next");
std::cout << (edge->eastCertain ? " certain" : " uncertain") << std::endl;
EdgeItr same = edge->forward ? edge->south->edgeR : edge->south->edgeL;
if (same != edge) std::cout << "edgeR does not point back!" << std::endl;
}
void ListActive() {
#ifdef MANIFOLD_DEBUG
void ListPairs() const {
std::cout << "active edges:" << std::endl;
bool forward = false;
for (auto edge = activeEdges_.begin(); edge != activeEdges_.end(); ++edge) {
ListEdge(edge);
ASSERT(edge->forward == forward, logicErr,
"Active edges are not sanely ordered!");
forward ^= true;
for (const EdgePair &pair : activePairs_) {
std::cout << (pair.westCertain ? "certain " : "uncertain ");
std::cout << "edge West: S = " << pair.vWest->mesh_idx
<< ", N = " << pair.vWest->left->mesh_idx << std::endl;
if (&*(pair.vWest->eastPair) != &pair)
std::cout << "west does not point back!" << std::endl;
std::cout << (pair.eastCertain ? "certain " : "uncertain ");
std::cout << "edge East: S = " << pair.vEast->mesh_idx
<< ", N = " << pair.vEast->right->mesh_idx << std::endl;
if (&*(pair.vEast->westPair) != &pair)
std::cout << "east does not point back!" << std::endl;
}
}
#endif
+32 -21
View File
@@ -23,9 +23,9 @@
namespace {
using namespace manifold;
Uint64 identity(Uint64 x) { return x; }
__host__ __device__ Uint64 identity(Uint64 x) { return x; }
glm::ivec3 TetTri0(int i) {
__host__ __device__ glm::ivec3 TetTri0(int i) {
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
{0, 3, 4}, //
{0, 1, 5}, //
@@ -45,7 +45,7 @@ glm::ivec3 TetTri0(int i) {
return tetTri0[i];
}
glm::ivec3 TetTri1(int i) {
__host__ __device__ glm::ivec3 TetTri1(int i) {
constexpr glm::ivec3 tetTri1[16] = {{-1, -1, -1}, //
{-1, -1, -1}, //
{-1, -1, -1}, //
@@ -65,7 +65,7 @@ glm::ivec3 TetTri1(int i) {
return tetTri1[i];
}
glm::ivec4 Neighbors(int i) {
__host__ __device__ glm::ivec4 Neighbors(int i) {
constexpr glm::ivec4 neighbors[7] = {{0, 0, 0, 1}, //
{1, 0, 0, 0}, //
{0, 1, 0, 0}, //
@@ -76,7 +76,7 @@ glm::ivec4 Neighbors(int i) {
return neighbors[i];
}
Uint64 SpreadBits3(Uint64 v) {
__host__ __device__ Uint64 SpreadBits3(Uint64 v) {
v = v & 0x1fffff;
v = (v | v << 32) & 0x1f00000000ffff;
v = (v | v << 16) & 0x1f0000ff0000ff;
@@ -86,7 +86,7 @@ Uint64 SpreadBits3(Uint64 v) {
return v;
}
Uint64 SqueezeBits3(Uint64 v) {
__host__ __device__ Uint64 SqueezeBits3(Uint64 v) {
v = v & 0x1249249249249249;
v = (v ^ v >> 2) & 0x10c30c30c30c30c3;
v = (v ^ v >> 4) & 0x100f00f00f00f00f;
@@ -99,12 +99,12 @@ Uint64 SqueezeBits3(Uint64 v) {
// This is a modified 3D MortonCode, where the xyz code is shifted by one bit
// and the w bit is added as the least significant. This allows 21 bits per x,
// y, and z channel and 1 for w, filling the 64 bit total.
Uint64 MortonCode(const glm::ivec4& index) {
__host__ __device__ Uint64 MortonCode(const glm::ivec4& index) {
return static_cast<Uint64>(index.w) | (SpreadBits3(index.x) << 1) |
(SpreadBits3(index.y) << 2) | (SpreadBits3(index.z) << 3);
}
glm::ivec4 DecodeMorton(Uint64 code) {
__host__ __device__ glm::ivec4 DecodeMorton(Uint64 code) {
glm::ivec4 index;
index.x = SqueezeBits3(code >> 1);
index.y = SqueezeBits3(code >> 2);
@@ -117,9 +117,9 @@ struct GridVert {
float distance = NAN;
int edgeVerts[7] = {-1, -1, -1, -1, -1, -1, -1};
int Inside() const { return distance > 0 ? 1 : -1; }
__host__ __device__ int Inside() const { return distance > 0 ? 1 : -1; }
int NeighborInside(int i) const {
__host__ __device__ int NeighborInside(int i) const {
return Inside() * (edgeVerts[i] < 0 ? 1 : -1);
}
};
@@ -135,12 +135,12 @@ struct ComputeVerts {
const glm::vec3 spacing;
const float level;
inline glm::vec3 Position(glm::ivec4 gridIndex) const {
inline __host__ __device__ glm::vec3 Position(glm::ivec4 gridIndex) const {
return origin +
spacing * (glm::vec3(gridIndex) + (gridIndex.w == 1 ? 0.0f : -0.5f));
}
inline float BoundedSDF(glm::ivec4 gridIndex) const {
inline __host__ __device__ float BoundedSDF(glm::ivec4 gridIndex) const {
const float d = sdf(Position(gridIndex)) - level;
const glm::ivec3 xyz(gridIndex);
@@ -153,7 +153,7 @@ struct ComputeVerts {
return d;
}
inline void operator()(Uint64 mortonCode) {
inline __host__ __device__ void operator()(Uint64 mortonCode) {
if (gridVerts.Full()) return;
const glm::ivec4 gridIndex = DecodeMorton(mortonCode);
@@ -194,20 +194,22 @@ struct BuildTris {
int* triIndex;
const HashTableD<GridVert, identity> gridVerts;
void CreateTri(const glm::ivec3& tri, const int edges[6]) {
__host__ __device__ void CreateTri(const glm::ivec3& tri,
const int edges[6]) {
if (tri[0] < 0) return;
int idx = AtomicAdd(*triIndex, 1);
triVerts[idx] = {edges[tri[0]], edges[tri[1]], edges[tri[2]]};
}
void CreateTris(const glm::ivec4& tet, const int edges[6]) {
__host__ __device__ void CreateTris(const glm::ivec4& tet,
const int edges[6]) {
const int i = (tet[0] > 0 ? 1 : 0) + (tet[1] > 0 ? 2 : 0) +
(tet[2] > 0 ? 4 : 0) + (tet[3] > 0 ? 8 : 0);
CreateTri(TetTri0(i), edges);
CreateTri(TetTri1(i), edges);
}
void operator()(int idx) {
__host__ __device__ void operator()(int idx) {
Uint64 basekey = gridVerts.KeyAt(idx);
if (basekey == kOpen) return;
@@ -279,7 +281,9 @@ template <>
void for_each_n_wrapper<std::function<float(glm::vec3)>>(
ExecutionPolicy policy, int morton,
ComputeVerts<std::function<float(glm::vec3)>> cv) {
for_each_n(policy, countAt(0), morton, cv);
const auto pol =
policy == ExecutionPolicy::ParUnseq ? ExecutionPolicy::Par : policy;
for_each_n(pol, countAt(0), morton, cv);
}
} // namespace
@@ -299,9 +303,11 @@ namespace manifold {
* the manifold, which is due to the underlying grid.
*
* @param sdf The signed-distance functor, containing this function signature:
* `float operator()(glm::vec3 point)`, which returns the
* `__host__ __device__ float operator()(glm::vec3 point)`, which returns the
* signed distance of a given point in R^3. Positive values are inside,
* negative outside.
* negative outside. The `__host__ __device__` is only needed if you compile for
* CUDA. If you are using a large grid, the advantage of a GPU speedup is
* quite significant.
* @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
@@ -327,8 +333,12 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
// Parallel policies violate will crash language runtimes with runtime locks
// that expect to not be called back by unregistered threads. This allows
// bindings use LevelSet despite being compiled with MANIFOLD_PAR
// active.
const auto pol = !policy.has_value() ? autoPolicy(maxMorton) : policy.value();
// active (CUDA is already avoided when Func is a function ptr).
const auto pol =
(!policy.has_value() ||
(policy.value() == ExecutionPolicy::ParUnseq && !CudaEnabled()))
? autoPolicy(maxMorton)
: policy.value();
int tableSize = glm::min(
2 * maxMorton, static_cast<Uint64>(10 * glm::pow(maxMorton, 0.667)));
@@ -337,6 +347,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
while (1) {
VecDH<int> index(1, 0);
// avoid handing dynamic function pointers to CUDA
for_each_n_wrapper<Func>(
pol, maxMorton + 1,
ComputeVerts<Func>({vertPos.ptrD(), index.ptrD(), gridVerts.D(), sdf,
+1 -1
View File
@@ -1,6 +1,6 @@
project (graphlite)
add_library(${PROJECT_NAME} src/connected_components.cpp)
add_library(${PROJECT_NAME} OBJECT src/connected_components.cpp)
target_include_directories(${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
+32 -48
View File
@@ -14,69 +14,53 @@
project(utilities)
add_library(${PROJECT_NAME} INTERFACE)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
message("CUDA Support: ${MANIFOLD_USE_CUDA}")
message("Parallel Backend: ${MANIFOLD_PAR}")
target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include)
target_include_directories(${PROJECT_NAME} INTERFACE ${THRUST_INC_DIR}/dependencies/libcudacxx/include)
if (TRACY_ENABLE)
include(FetchContent)
FetchContent_Declare(tracy
GIT_REPOSITORY https://github.com/wolfpld/tracy.git
GIT_TAG v0.9.1
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(tracy)
target_link_libraries(${PROJECT_NAME} INTERFACE TracyClient)
target_compile_options(${PROJECT_NAME} INTERFACE -DTRACY_NO_CODE_TRANSFER=1)
endif()
if(MANIFOLD_PAR STREQUAL "TBB")
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
pkg_check_modules(TBB tbb)
endif()
if(NOT TBB_FOUND)
message(STATUS "tbb not found, downloading from source")
include(FetchContent)
set(TBB_TEST OFF CACHE INTERNAL "" FORCE)
set(TBB_STRICT OFF CACHE INTERNAL "" FORCE)
FetchContent_Declare(TBB
GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git
GIT_TAG v2021.10.0
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(TBB)
endif()
target_compile_options(${PROJECT_NAME} INTERFACE -DMANIFOLD_PAR='T')
if(TARGET TBB::tbb)
target_link_libraries(${PROJECT_NAME} INTERFACE TBB::tbb)
else()
target_include_directories(${PROJECT_NAME} INTERFACE ${TBB_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} INTERFACE ${TBB_LINK_LIBRARIES})
endif()
if(MANIFOLD_PAR STREQUAL "OMP")
find_package(OpenMP REQUIRED)
target_include_directories(${PROJECT_NAME} PUBLIC OpenMP::OpenMP_CXX)
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_PAR='O' -fopenmp)
target_link_options(${PROJECT_NAME} PUBLIC -fopenmp)
elseif(MANIFOLD_PAR STREQUAL "TBB")
find_package(PkgConfig REQUIRED)
pkg_check_modules(TBB REQUIRED tbb)
target_include_directories(${PROJECT_NAME} PUBLIC ${TBB_INCLUDE_DIRS})
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_PAR='T')
target_link_libraries(${PROJECT_NAME} PUBLIC ${TBB_LINK_LIBRARIES})
elseif(MANIFOLD_PAR STREQUAL "NONE")
set(MANIFOLD_PAR "CPP")
else()
message(FATAL_ERROR "Invalid value for MANIFOLD_PAR: ${MANIFOLD_PAR}. "
"Should be \"TBB\" or \"NONE\"")
"Should be one of \"TBB\", \"OMP\", \"NONE\"")
endif()
target_include_directories(${PROJECT_NAME} INTERFACE ${THRUST_INC_DIR})
target_link_libraries(${PROJECT_NAME} INTERFACE glm)
target_include_directories(${PROJECT_NAME} PUBLIC ${THRUST_INC_DIR})
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
target_compile_options(${PROJECT_NAME}
INTERFACE ${MANIFOLD_FLAGS}
-DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_${MANIFOLD_PAR}
)
if(MANIFOLD_USE_CUDA)
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
target_compile_options(${PROJECT_NAME}
PUBLIC ${MANIFOLD_FLAGS} -DMANIFOLD_USE_CUDA
"$<$<COMPILE_LANGUAGE:CUDA>:${MANIFOLD_NVCC_FLAGS}>"
)
else()
target_compile_options(${PROJECT_NAME}
PUBLIC ${MANIFOLD_FLAGS}
-DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_${MANIFOLD_PAR}
)
endif()
if(MANIFOLD_DEBUG)
target_compile_options(${PROJECT_NAME}
INTERFACE -DMANIFOLD_DEBUG)
PUBLIC -DMANIFOLD_DEBUG)
endif()
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
+26 -10
View File
@@ -26,28 +26,40 @@ typedef Uint64 (*hash_fun_t)(Uint64);
constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
template <typename T>
T AtomicCAS(T& target, T compare, T val) {
__host__ __device__ T AtomicCAS(T& target, T compare, T val) {
#ifdef __CUDA_ARCH__
return atomicCAS(&target, compare, val);
#else
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
tar.compare_exchange_strong(compare, val, std::memory_order_acq_rel);
return compare;
#endif
}
template <typename T>
void AtomicStore(T& target, T val) {
__host__ __device__ void AtomicStore(T& target, T val) {
#ifdef __CUDA_ARCH__
target = val;
#else
std::atomic<T>& tar = reinterpret_cast<std::atomic<T>&>(target);
// release is good enough, although not really something general
tar.store(val, std::memory_order_release);
#endif
}
template <typename T>
T AtomicLoad(const T& target) {
__host__ __device__ T AtomicLoad(const T& target) {
#ifdef __CUDA_ARCH__
return target;
#else
const std::atomic<T>& tar = reinterpret_cast<const std::atomic<T>&>(target);
// acquire is good enough, although not general
return tar.load(std::memory_order_acquire);
#endif
}
// https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
inline Uint64 hash64bit(Uint64 x) {
__host__ __device__ inline Uint64 hash64bit(Uint64 x) {
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull;
x = (x ^ (x >> 27)) * 0x94d049bb133111ebull;
x = x ^ (x >> 31);
@@ -67,11 +79,13 @@ class HashTableD {
uint32_t step = 1)
: step_{step}, keys_{keys}, values_{values}, used_{used} {}
int Size() const { return keys_.size(); }
__host__ __device__ int Size() const { return keys_.size(); }
bool Full() const { return AtomicLoad(used_[0]) * 2 > Size(); }
__host__ __device__ bool Full() const {
return AtomicLoad(used_[0]) * 2 > Size();
}
void Insert(Uint64 key, const V& val) {
__host__ __device__ void Insert(Uint64 key, const V& val) {
uint32_t idx = H(key) & (Size() - 1);
while (1) {
if (Full()) return;
@@ -87,7 +101,7 @@ class HashTableD {
}
}
V& operator[](Uint64 key) const {
__host__ __device__ V& operator[](Uint64 key) const {
uint32_t idx = H(key) & (Size() - 1);
while (1) {
const Uint64 k = AtomicLoad(keys_[idx]);
@@ -98,8 +112,10 @@ class HashTableD {
}
}
Uint64 KeyAt(int idx) const { return AtomicLoad(keys_[idx]); }
V& At(int idx) const { return values_[idx]; }
__host__ __device__ Uint64 KeyAt(int idx) const {
return AtomicLoad(keys_[idx]);
}
__host__ __device__ V& At(int idx) const { return values_[idx]; }
private:
uint32_t step_;
+70 -90
View File
@@ -24,23 +24,27 @@
#include <thrust/system/cpp/execution_policy.h>
#include <thrust/uninitialized_copy.h>
#include <thrust/unique.h>
#if MANIFOLD_PAR == 'O'
#include <thrust/system/omp/execution_policy.h>
#define MANIFOLD_PAR_NS omp
#elif MANIFOLD_PAR == 'T'
#include <thrust/system/tbb/execution_policy.h>
#include <algorithm>
#include <execution>
#include <numeric>
#define MANIFOLD_PAR_NS tbb
#else
#define MANIFOLD_PAR_NS cpp
#endif
#ifdef MANIFOLD_USE_CUDA
#include <thrust/system/cuda/execution_policy.h>
#endif
namespace manifold {
bool CudaEnabled();
enum class ExecutionPolicy {
ParUnseq,
Par,
Seq,
};
@@ -49,18 +53,52 @@ enum class ExecutionPolicy {
// - Sequential for small workload,
// - Parallel (CPU) for medium workload,
// - GPU for large workload if available.
inline constexpr ExecutionPolicy autoPolicy(int size) {
inline ExecutionPolicy autoPolicy(int size) {
// some random numbers
if (size <= (1 << 12)) {
return ExecutionPolicy::Seq;
}
return ExecutionPolicy::Par;
if (size <= (1 << 16) || !CudaEnabled()) {
return ExecutionPolicy::Par;
}
return ExecutionPolicy::ParUnseq;
}
#ifdef MANIFOLD_USE_CUDA
#define THRUST_DYNAMIC_BACKEND_VOID(NAME) \
template <typename... Args> \
void NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
thrust::NAME(thrust::cuda::par, args...); \
break; \
case ExecutionPolicy::Par: \
thrust::NAME(thrust::MANIFOLD_PAR_NS::par, args...); \
break; \
case ExecutionPolicy::Seq: \
thrust::NAME(thrust::cpp::par, args...); \
break; \
} \
}
#define THRUST_DYNAMIC_BACKEND(NAME, RET) \
template <typename Ret = RET, typename... Args> \
Ret NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
return thrust::NAME(thrust::cuda::par, args...); \
case ExecutionPolicy::Par: \
return thrust::NAME(thrust::MANIFOLD_PAR_NS::par, args...); \
case ExecutionPolicy::Seq: \
break; \
} \
return thrust::NAME(thrust::cpp::par, args...); \
}
#else
#define THRUST_DYNAMIC_BACKEND_VOID(NAME) \
template <typename... Args> \
void NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
case ExecutionPolicy::Par: \
thrust::NAME(thrust::MANIFOLD_PAR_NS::par, args...); \
break; \
@@ -74,6 +112,7 @@ inline constexpr ExecutionPolicy autoPolicy(int size) {
template <typename Ret = RET, typename... Args> \
Ret NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
case ExecutionPolicy::Par: \
return thrust::NAME(thrust::MANIFOLD_PAR_NS::par, args...); \
case ExecutionPolicy::Seq: \
@@ -81,83 +120,22 @@ inline constexpr ExecutionPolicy autoPolicy(int size) {
} \
return thrust::NAME(thrust::cpp::par, args...); \
}
#endif
#define THRUST_DYNAMIC_BACKEND_HOST_VOID(NAME) \
template <typename... Args> \
void NAME##_host(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
case ExecutionPolicy::Par: \
thrust::NAME(thrust::MANIFOLD_PAR_NS::par, args...); \
break; \
case ExecutionPolicy::Seq: \
thrust::NAME(thrust::cpp::par, args...); \
break; \
} \
thrust::NAME(thrust::cpp::par, args...); \
}
#if MANIFOLD_PAR == 'T' && __has_include(<pstl/glue_execution_defs.h>)
// sometimes stl variant is faster
#define STL_DYNAMIC_BACKEND(NAME, RET) \
template <typename Ret = RET, typename... Args> \
Ret NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::Par: \
return std::NAME(std::execution::par_unseq, args...); \
case ExecutionPolicy::Seq: \
break; \
} \
return std::NAME(args...); \
}
#define STL_DYNAMIC_BACKEND_VOID(NAME) \
template <typename... Args> \
void NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::Par: \
std::NAME(std::execution::par_unseq, args...); \
break; \
case ExecutionPolicy::Seq: \
std::NAME(args...); \
break; \
} \
}
template <typename... Args>
void exclusive_scan(ExecutionPolicy policy, Args... args) {
// https://github.com/llvm/llvm-project/issues/59810
std::exclusive_scan(args...);
}
template <typename DerivedPolicy, typename InputIterator1,
typename InputIterator2, typename OutputIterator, typename Predicate>
OutputIterator copy_if(ExecutionPolicy policy, InputIterator1 first,
InputIterator1 last, InputIterator2 stencil,
OutputIterator result, Predicate pred) {
if (policy == ExecutionPolicy::Seq)
return thrust::copy_if(thrust::cpp::par, first, last, stencil, result,
pred);
else
// note: this is not a typo, see
// https://github.com/NVIDIA/thrust/issues/1977
return thrust::copy_if(first, last, stencil, result, pred);
}
template <typename DerivedPolicy, typename InputIterator1,
typename OutputIterator, typename Predicate>
OutputIterator copy_if(ExecutionPolicy policy, InputIterator1 first,
InputIterator1 last, OutputIterator result,
Predicate pred) {
if (policy == ExecutionPolicy::Seq)
return std::copy_if(first, last, result, pred);
else
return std::copy_if(std::execution::par_unseq, first, last, result, pred);
}
#else
#define STL_DYNAMIC_BACKEND(NAME, RET) THRUST_DYNAMIC_BACKEND(NAME, RET)
#define STL_DYNAMIC_BACKEND_VOID(NAME) THRUST_DYNAMIC_BACKEND_VOID(NAME)
THRUST_DYNAMIC_BACKEND_VOID(exclusive_scan)
THRUST_DYNAMIC_BACKEND(copy_if, void)
#endif
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each)
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each_n)
@@ -165,34 +143,36 @@ THRUST_DYNAMIC_BACKEND_VOID(gather)
THRUST_DYNAMIC_BACKEND_VOID(scatter)
THRUST_DYNAMIC_BACKEND_VOID(for_each)
THRUST_DYNAMIC_BACKEND_VOID(for_each_n)
THRUST_DYNAMIC_BACKEND_VOID(sort)
THRUST_DYNAMIC_BACKEND_VOID(stable_sort)
THRUST_DYNAMIC_BACKEND_VOID(fill)
THRUST_DYNAMIC_BACKEND_VOID(sequence)
THRUST_DYNAMIC_BACKEND_VOID(sort_by_key)
THRUST_DYNAMIC_BACKEND_VOID(stable_sort_by_key)
THRUST_DYNAMIC_BACKEND_VOID(copy)
THRUST_DYNAMIC_BACKEND_VOID(transform)
STL_DYNAMIC_BACKEND_VOID(uninitialized_fill)
STL_DYNAMIC_BACKEND_VOID(uninitialized_copy)
STL_DYNAMIC_BACKEND_VOID(stable_sort)
STL_DYNAMIC_BACKEND_VOID(fill)
STL_DYNAMIC_BACKEND_VOID(copy)
STL_DYNAMIC_BACKEND_VOID(inclusive_scan)
STL_DYNAMIC_BACKEND_VOID(copy_n)
STL_DYNAMIC_BACKEND_VOID(sort)
THRUST_DYNAMIC_BACKEND_VOID(inclusive_scan)
THRUST_DYNAMIC_BACKEND_VOID(exclusive_scan)
THRUST_DYNAMIC_BACKEND_VOID(uninitialized_fill)
THRUST_DYNAMIC_BACKEND_VOID(uninitialized_copy)
THRUST_DYNAMIC_BACKEND_VOID(copy_n)
THRUST_DYNAMIC_BACKEND(all_of, bool)
THRUST_DYNAMIC_BACKEND(is_sorted, bool)
THRUST_DYNAMIC_BACKEND(reduce, void)
THRUST_DYNAMIC_BACKEND(count_if, int)
THRUST_DYNAMIC_BACKEND(binary_search, bool)
// void implies that the user have to specify the return type in the template
// argument, as we are unable to deduce it
THRUST_DYNAMIC_BACKEND(transform_reduce, void)
THRUST_DYNAMIC_BACKEND(gather_if, void)
THRUST_DYNAMIC_BACKEND(remove, void)
THRUST_DYNAMIC_BACKEND(copy_if, void)
THRUST_DYNAMIC_BACKEND(remove_if, void)
THRUST_DYNAMIC_BACKEND(unique, void)
THRUST_DYNAMIC_BACKEND(find, void)
THRUST_DYNAMIC_BACKEND(find_if, void)
THRUST_DYNAMIC_BACKEND(reduce_by_key, void)
THRUST_DYNAMIC_BACKEND(transform_reduce, void)
THRUST_DYNAMIC_BACKEND(lower_bound, void)
STL_DYNAMIC_BACKEND(remove, void)
STL_DYNAMIC_BACKEND(find, void)
STL_DYNAMIC_BACKEND(find_if, void)
STL_DYNAMIC_BACKEND(all_of, bool)
STL_DYNAMIC_BACKEND(is_sorted, bool)
STL_DYNAMIC_BACKEND(reduce, void)
STL_DYNAMIC_BACKEND(count_if, int)
STL_DYNAMIC_BACKEND(binary_search, bool)
STL_DYNAMIC_BACKEND(remove_if, void)
STL_DYNAMIC_BACKEND(unique, void)
THRUST_DYNAMIC_BACKEND(gather_if, void)
} // namespace manifold
+1 -1
View File
@@ -106,7 +106,7 @@ inline HOST_DEVICE int CCW(glm::vec2 p0, glm::vec2 p1, glm::vec2 p2,
glm::vec2 v2 = p2 - p0;
float area = v1.x * v2.y - v1.y * v2.x;
float base2 = glm::max(glm::dot(v1, v1), glm::dot(v2, v2));
if (area * area * 4 <= base2 * tol * tol)
if (area * area <= base2 * tol * tol)
return 0;
else
return area > 0 ? 1 : -1;
+89 -86
View File
@@ -25,103 +25,92 @@ namespace manifold {
/** @ingroup Private */
class SparseIndices {
// sparse indices where {p1: q1, p2: q2, ...} are laid out as
// p1 q1 p2 q2 or q1 p1 q2 p2, depending on endianness
// such that the indices are sorted by (p << 32) | q
// COO-style sparse matrix storage. Values corresponding to these indicies are
// stored in vectors separate from this class, but having the same length.
public:
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \
defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || \
defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || \
defined(__MIBSEB__)
static constexpr size_t pOffset = 0;
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \
defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \
defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \
defined(__MIPSEL) || defined(__MIPSEL__) || defined(__EMSCRIPTEN__) || \
defined(_MSC_VER)
static constexpr size_t pOffset = 1;
#else
#error "unknown architecture"
#endif
static constexpr int64_t EncodePQ(int p, int q) {
return (int64_t(p) << 32) | q;
SparseIndices(int size = 0) : p(size), q(size) {}
typedef typename VecDH<int>::Iter Iter;
typedef typename thrust::zip_iterator<thrust::tuple<Iter, Iter>> Zip;
Zip beginPQ() { return zip(p.begin(), q.begin()); }
Zip endPQ() { return zip(p.end(), q.end()); }
Iter begin(bool use_q) { return use_q ? q.begin() : p.begin(); }
Iter end(bool use_q) { return use_q ? q.end() : p.end(); }
int* ptrD(bool use_q) { return use_q ? q.ptrD() : p.ptrD(); }
thrust::pair<int*, int*> ptrDpq(int idx = 0) {
return thrust::make_pair(p.ptrD() + idx, q.ptrD() + idx);
}
SparseIndices() = default;
SparseIndices(size_t size) : data(size) {}
int size() const { return data.size(); }
const thrust::pair<const int*, const int*> ptrDpq(int idx = 0) const {
return thrust::make_pair(p.ptrD() + idx, q.ptrD() + idx);
}
const VecDH<int>& Get(bool use_q) const { return use_q ? q : p; }
VecDH<int> Copy(bool use_q) const {
VecDH<int> out(data.size());
int offset = pOffset;
if (use_q) offset = 1 - offset;
const int* p = ptr();
for_each(autoPolicy(data.size()), countAt(0), countAt((int)data.size()),
[&](int i) { out[i] = p[i * 2 + offset]; });
VecDH<int> out = use_q ? q : p;
return out;
}
void Sort(ExecutionPolicy policy) { sort(policy, data.begin(), data.end()); }
typedef typename VecDH<int>::IterC IterC;
typedef typename thrust::zip_iterator<thrust::tuple<IterC, IterC>> ZipC;
ZipC beginPQ() const { return zip(p.begin(), q.begin()); }
ZipC endPQ() const { return zip(p.end(), q.end()); }
IterC begin(bool use_q) const { return use_q ? q.begin() : p.begin(); }
IterC end(bool use_q) const { return use_q ? q.end() : p.end(); }
const int* ptrD(bool use_q) const { return use_q ? q.ptrD() : p.ptrD(); }
void Resize(int size) { data.resize(size, -1); }
ZipC beginHpq() const { return zip(p.begin(), q.begin()); }
ZipC endHpq() const { return zip(p.end(), q.end()); }
inline int& Get(int i, bool use_q) {
if (use_q)
return ptr()[2 * i + 1 - pOffset];
else
return ptr()[2 * i + pOffset];
int size() const { return p.size(); }
void SwapPQ() { p.swap(q); }
void Sort(ExecutionPolicy policy) { sort(policy, beginPQ(), endPQ()); }
void Resize(int size) {
p.resize(size, -1);
q.resize(size, -1);
}
inline int Get(int i, bool use_q) const {
if (use_q)
return ptr()[2 * i + 1 - pOffset];
else
return ptr()[2 * i + pOffset];
}
inline int64_t GetPQ(int i) const { return data[i]; }
inline void Set(int i, int p, int q) { data[i] = EncodePQ(p, q); }
inline void SetPQ(int i, int64_t pq) { data[i] = pq; }
const VecDH<int64_t>& AsVec64() const { return data; }
inline void Add(int p, int q) { data.push_back(EncodePQ(p, q)); }
void Unique(ExecutionPolicy policy) {
Sort(policy);
int newSize =
unique<decltype(data.begin())>(policy, data.begin(), data.end()) -
data.begin();
unique<decltype(beginPQ())>(policy, beginPQ(), endPQ()) - beginPQ();
Resize(newSize);
}
struct firstZero {
__host__ __device__ bool operator()(thrust::tuple<int, int, int> x) const {
return thrust::get<0>(x) == 0;
}
};
size_t RemoveZeros(VecDH<int>& S) {
ASSERT(S.size() == data.size(), userErr,
ASSERT(S.size() == p.size(), userErr,
"Different number of values than indicies!");
auto zBegin = zip(S.begin(), data.begin());
auto zEnd = zip(S.end(), data.end());
size_t size =
remove_if<decltype(zBegin)>(autoPolicy(S.size()), zBegin, zEnd,
[](thrust::tuple<int, int64_t> x) {
return thrust::get<0>(x) == 0;
}) -
zBegin;
auto zBegin = zip(S.begin(), begin(false), begin(true));
auto zEnd = zip(S.end(), end(false), end(true));
size_t size = remove_if<decltype(zBegin)>(autoPolicy(S.size()), zBegin,
zEnd, firstZero()) -
zBegin;
S.resize(size, -1);
Resize(size);
p.resize(size, -1);
q.resize(size, -1);
return size;
}
template <typename T>
struct firstNonFinite {
bool NotFinite(float v) const { return !isfinite(v); }
bool NotFinite(glm::vec2 v) const { return !isfinite(v[0]); }
bool NotFinite(glm::vec3 v) const { return !isfinite(v[0]); }
bool NotFinite(glm::vec4 v) const { return !isfinite(v[0]); }
__host__ __device__ bool NotFinite(float v) const { return !isfinite(v); }
__host__ __device__ bool NotFinite(glm::vec2 v) const {
return !isfinite(v[0]);
}
__host__ __device__ bool NotFinite(glm::vec3 v) const {
return !isfinite(v[0]);
}
__host__ __device__ bool NotFinite(glm::vec4 v) const {
return !isfinite(v[0]);
}
bool operator()(thrust::tuple<T, int, int64_t> x) const {
__host__ __device__ bool operator()(
thrust::tuple<T, int, int, int> x) const {
bool result = NotFinite(thrust::get<0>(x));
return result;
}
@@ -129,37 +118,51 @@ class SparseIndices {
template <typename T>
size_t KeepFinite(VecDH<T>& v, VecDH<int>& x) {
ASSERT(x.size() == data.size(), userErr,
ASSERT(x.size() == p.size(), userErr,
"Different number of values than indicies!");
auto zBegin = zip(v.begin(), x.begin(), data.begin());
auto zEnd = zip(v.end(), x.end(), data.end());
auto zBegin = zip(v.begin(), x.begin(), begin(false), begin(true));
auto zEnd = zip(v.end(), x.end(), end(false), end(true));
size_t size = remove_if<decltype(zBegin)>(autoPolicy(v.size()), zBegin,
zEnd, firstNonFinite<T>()) -
zBegin;
v.resize(size);
x.resize(size);
Resize(size);
x.resize(size, -1);
p.resize(size, -1);
q.resize(size, -1);
return size;
}
template <typename Iter, typename T>
VecDH<T> Gather(const VecDH<T>& val, const Iter pqBegin, const Iter pqEnd,
T missingVal) const {
ASSERT(val.size() == p.size(), userErr,
"Different number of values than indicies!");
size_t size = pqEnd - pqBegin;
VecDH<T> result(size);
VecDH<char> found(size);
VecDH<int> temp(size);
auto policy = autoPolicy(size);
fill(policy, result.begin(), result.end(), missingVal);
binary_search(policy, beginPQ(), endPQ(), pqBegin, pqEnd, found.begin());
lower_bound(policy, beginPQ(), endPQ(), pqBegin, pqEnd, temp.begin());
gather_if(policy, temp.begin(), temp.end(), found.begin(), val.begin(),
result.begin());
return result;
}
#ifdef MANIFOLD_DEBUG
void Dump() const {
const auto& p = Get(0);
const auto& q = Get(1);
std::cout << "SparseIndices = " << std::endl;
const int* p = ptr();
for (int i = 0; i < size(); ++i) {
std::cout << i << ", p = " << Get(i, false) << ", q = " << Get(i, true)
<< std::endl;
std::cout << i << ", p = " << p[i] << ", q = " << q[i] << std::endl;
}
std::cout << std::endl;
}
#endif
private:
VecDH<int64_t> data;
inline int* ptr() { return reinterpret_cast<int32_t*>(data.ptrD()); }
inline const int* ptr() const {
return reinterpret_cast<const int32_t*>(data.ptrD());
}
VecDH<int> p, q;
};
} // namespace manifold
+12 -5
View File
@@ -14,6 +14,12 @@
// limitations under the License.
#pragma once
#include <thrust/device_vector.h>
#include <thrust/functional.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/tuple.h>
#include <atomic>
#include <mutex>
@@ -98,18 +104,18 @@ thrust::counting_iterator<T> countAt(T i) {
return thrust::make_counting_iterator(i);
}
inline int Next3(int i) {
inline __host__ __device__ int Next3(int i) {
constexpr glm::ivec3 next3(1, 2, 0);
return next3[i];
}
inline int Prev3(int i) {
inline __host__ __device__ int Prev3(int i) {
constexpr glm::ivec3 prev3(2, 0, 1);
return prev3[i];
}
template <typename T>
T AtomicAdd(T& target, T add) {
__host__ __device__ T AtomicAdd(T& target, T add) {
#ifdef __CUDA_ARCH__
// required for synchronization
__threadfence();
@@ -125,7 +131,7 @@ T AtomicAdd(T& target, T add) {
}
template <>
inline int AtomicAdd(int& target, int add) {
inline __host__ __device__ int AtomicAdd(int& target, int add) {
#ifdef __CUDA_ARCH__
// required for synchronization
__threadfence();
@@ -150,7 +156,8 @@ class strided_range {
stride_functor(difference_type stride) : stride(stride) {}
difference_type operator()(const difference_type& i) const {
__host__ __device__ difference_type
operator()(const difference_type& i) const {
return stride * i;
}
};
+339 -157
View File
@@ -13,12 +13,8 @@
// limitations under the License.
#pragma once
#include <exception>
#if TRACY_ENABLE && TRACY_MEMORY_USAGE
#include <tracy/Tracy.hpp>
#else
#define TracyAllocS(ptr, size, n) (void)0
#define TracyFreeS(ptr, n) (void)0
#ifdef MANIFOLD_USE_CUDA
#include <cuda.h>
#endif
// #include "optional_assert.h"
@@ -31,212 +27,398 @@ namespace manifold {
* @{
*/
/*
* Specialized vector implementation with multithreaded fill and uninitialized
* memory optimizations.
* Note that the constructor and resize function will not perform initialization
* if the parameter val is not set. Also, this implementation is a toy
* implementation that did not consider things like non-trivial
* constructor/destructor, please keep T trivial.
*/
// Vector implementation optimized for managed memory, will perform memory
// prefetching to minimize page faults and use parallel/GPU copy/fill depending
// on data size. This will also handle builds without CUDA or builds with CUDA
// but run without CUDA GPU properly.
//
// Note that the constructor and resize function will not perform initialization
// if the parameter val is not set. Also, this implementation is a toy
// implementation that did not consider things like non-trivial
// constructor/destructor, please keep T trivial.
template <typename T>
class VecDH {
class ManagedVec {
public:
using Iter = T *;
using IterC = const T *;
typedef T *Iter;
typedef const T *IterC;
VecDH() {}
// Note that the vector constructed with this constructor will contain
// uninitialized memory. Please specify `val` if you need to make sure that
// the data is initialized.
VecDH(int size) { resize(size); }
VecDH(int size, T val) { resize(size, val); }
VecDH(const std::vector<T> &vec) {
size_ = vec.size();
capacity_ = size_;
auto policy = autoPolicy(size_);
if (size_ != 0) {
ptr_ = reinterpret_cast<T *>(malloc(size_ * sizeof(T)));
if (ptr_ == nullptr) throw std::bad_alloc();
TracyAllocS(ptr_, size_ * sizeof(T), 3);
uninitialized_copy(policy, vec.begin(), vec.end(), ptr_);
}
ManagedVec() {
size_ = 0;
capacity_ = 0;
onHost = true;
}
VecDH(const VecDH<T> &vec) {
size_ = vec.size_;
capacity_ = size_;
auto policy = autoPolicy(size_);
if (size_ != 0) {
ptr_ = reinterpret_cast<T *>(malloc(size_ * sizeof(T)));
if (ptr_ == nullptr) throw std::bad_alloc();
TracyAllocS(ptr_, size_ * sizeof(T), 3);
uninitialized_copy(policy, vec.begin(), vec.end(), ptr_);
}
// note that we leave the memory uninitialized
ManagedVec(size_t n) {
size_ = n;
capacity_ = n;
onHost = autoPolicy(n) != ExecutionPolicy::ParUnseq;
if (n == 0) return;
mallocManaged(&ptr_, size_ * sizeof(T));
}
VecDH(VecDH<T> &&vec) {
ptr_ = vec.ptr_;
size_ = vec.size_;
capacity_ = vec.capacity_;
vec.ptr_ = nullptr;
vec.size_ = 0;
vec.capacity_ = 0;
ManagedVec(size_t n, const T &val) {
size_ = n;
capacity_ = n;
if (n == 0) return;
auto policy = autoPolicy(n);
onHost = policy != ExecutionPolicy::ParUnseq;
mallocManaged(&ptr_, size_ * sizeof(T));
prefetch(ptr_, size_ * sizeof(T), onHost);
uninitialized_fill_n(policy, ptr_, n, val);
}
~VecDH() {
if (ptr_ != nullptr) {
TracyFreeS(ptr_, 3);
free(ptr_);
}
~ManagedVec() {
if (ptr_ != nullptr) freeManaged(ptr_);
ptr_ = nullptr;
size_ = 0;
capacity_ = 0;
}
VecDH<T> &operator=(const VecDH<T> &other) {
if (&other == this) return *this;
if (ptr_ != nullptr) {
TracyFreeS(ptr_, 3);
free(ptr_);
}
size_ = other.size_;
capacity_ = other.size_;
ManagedVec(const std::vector<T> &vec) {
size_ = vec.size();
capacity_ = size_;
auto policy = autoPolicy(size_);
onHost = policy != ExecutionPolicy::ParUnseq;
if (size_ != 0) {
ptr_ = reinterpret_cast<T *>(malloc(size_ * sizeof(T)));
if (ptr_ == nullptr) throw std::bad_alloc();
TracyAllocS(ptr_, size_ * sizeof(T), 3);
uninitialized_copy(policy, other.begin(), other.end(), ptr_);
mallocManaged(&ptr_, size_ * sizeof(T));
fastUninitializedCopy(ptr_, vec.data(), size_, policy);
}
}
ManagedVec(const ManagedVec<T> &vec) {
size_ = vec.size_;
capacity_ = size_;
auto policy = autoPolicy(size_);
onHost = policy != ExecutionPolicy::ParUnseq;
if (size_ != 0) {
mallocManaged(&ptr_, size_ * sizeof(T));
prefetch(ptr_, size_ * sizeof(T), onHost);
uninitialized_copy(policy, vec.begin(), vec.end(), ptr_);
}
}
ManagedVec(ManagedVec<T> &&vec) {
ptr_ = vec.ptr_;
size_ = vec.size_;
capacity_ = vec.capacity_;
onHost = vec.onHost;
vec.ptr_ = nullptr;
vec.size_ = 0;
vec.capacity_ = 0;
}
ManagedVec &operator=(const ManagedVec<T> &vec) {
if (&vec == this) return *this;
if (ptr_ != nullptr) freeManaged(ptr_);
size_ = vec.size_;
capacity_ = vec.size_;
auto policy = autoPolicy(size_);
onHost = policy != ExecutionPolicy::ParUnseq;
if (size_ != 0) {
mallocManaged(&ptr_, size_ * sizeof(T));
prefetch(ptr_, size_ * sizeof(T), onHost);
uninitialized_copy(policy, vec.begin(), vec.end(), ptr_);
}
return *this;
}
VecDH<T> &operator=(VecDH<T> &&other) {
if (&other == this) return *this;
if (ptr_ != nullptr) {
TracyFreeS(ptr_, 3);
free(ptr_);
}
size_ = other.size_;
capacity_ = other.capacity_;
ptr_ = other.ptr_;
other.ptr_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
ManagedVec &operator=(ManagedVec<T> &&vec) {
if (&vec == this) return *this;
if (ptr_ != nullptr) freeManaged(ptr_);
onHost = vec.onHost;
size_ = vec.size_;
capacity_ = vec.capacity_;
ptr_ = vec.ptr_;
vec.ptr_ = nullptr;
vec.size_ = 0;
vec.capacity_ = 0;
return *this;
}
int size() const { return size_; }
void swap(VecDH<T> &other) {
std::swap(ptr_, other.ptr_);
std::swap(size_, other.size_);
std::swap(capacity_, other.capacity_);
void resize(size_t n) {
reserve(n);
size_ = n;
}
Iter begin() { return ptr_; }
void resize(size_t n, const T &val) {
reserve(n);
if (size_ < n) {
uninitialized_fill(autoPolicy(n - size_), ptr_ + size_, ptr_ + n, val);
}
size_ = n;
}
Iter end() { return ptr_ + size_; }
void reserve(size_t n) {
if (n > capacity_) {
T *newBuffer;
mallocManaged(&newBuffer, n * sizeof(T));
prefetch(newBuffer, size_ * sizeof(T), onHost);
if (size_ > 0) {
uninitialized_copy(autoPolicy(size_), ptr_, ptr_ + size_, newBuffer);
}
if (ptr_ != nullptr) freeManaged(ptr_);
ptr_ = newBuffer;
capacity_ = n;
}
}
IterC cbegin() const { return ptr_; }
void shrink_to_fit() {
T *newBuffer = nullptr;
if (size_ > 0) {
int n_bytes = size_ * sizeof(T);
mallocManaged(&newBuffer, n_bytes);
prefetch(newBuffer, n_bytes, onHost);
uninitialized_copy(autoPolicy(size_), ptr_, ptr_ + size_, newBuffer);
}
freeManaged(ptr_);
ptr_ = newBuffer;
capacity_ = size_;
}
IterC cend() const { return ptr_ + size_; }
IterC begin() const { return cbegin(); }
IterC end() const { return cend(); }
T *ptrD() { return ptr_; }
const T *cptrD() const { return ptr_; }
const T *ptrD() const { return cptrD(); }
T *ptrH() { return ptr_; }
const T *cptrH() const { return ptr_; }
const T *ptrH() const { return cptrH(); }
inline T &operator[](int i) { return ptr_[i]; }
inline const T &operator[](int i) const { return ptr_[i]; }
T &back() { return ptr_[size_ - 1]; }
const T &back() const { return ptr_[size_ - 1]; }
inline void push_back(const T &val) {
void push_back(const T &val) {
if (size_ >= capacity_) {
// avoid dangling pointer in case val is a reference of our array
T val_copy = val;
reserve(capacity_ == 0 ? 128 : capacity_ * 2);
onHost = true;
ptr_[size_++] = val_copy;
return;
}
ptr_[size_++] = val;
}
void reserve(int n) {
if (n > capacity_) {
T *newBuffer = reinterpret_cast<T *>(malloc(n * sizeof(T)));
if (newBuffer == nullptr) throw std::bad_alloc();
TracyAllocS(newBuffer, n * sizeof(T), 3);
if (size_ > 0)
uninitialized_copy(autoPolicy(size_), ptr_, ptr_ + size_, newBuffer);
if (ptr_ != nullptr) {
TracyFreeS(ptr_, 3);
free(ptr_);
}
ptr_ = newBuffer;
capacity_ = n;
void swap(ManagedVec<T> &other) {
std::swap(ptr_, other.ptr_);
std::swap(size_, other.size_);
std::swap(capacity_, other.capacity_);
std::swap(onHost, other.onHost);
}
void prefetch_to(bool toHost) const {
if (toHost != onHost) prefetch(ptr_, size_ * sizeof(T), toHost);
onHost = toHost;
}
IterC cbegin() const { return ptr_; }
IterC cend() const { return ptr_ + size_; }
Iter begin() { return ptr_; }
Iter end() { return ptr_ + size_; }
IterC begin() const { return cbegin(); }
IterC end() const { return cend(); }
T *data() { return ptr_; }
const T *data() const { return ptr_; }
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
T &front() { return *ptr_; }
const T &front() const { return *ptr_; }
T &back() { return *(ptr_ + size_ - 1); }
const T &back() const { return *(ptr_ + size_ - 1); }
T &operator[](size_t i) { return *(ptr_ + i); }
const T &operator[](size_t i) const { return *(ptr_ + i); }
bool empty() const { return size_ == 0; }
private:
T *ptr_ = nullptr;
size_t size_ = 0;
size_t capacity_ = 0;
mutable bool onHost = true;
static constexpr int DEVICE_MAX_BYTES = 1 << 16;
static void mallocManaged(T **ptr, size_t bytes) {
// only exists to please the compiler
// see https://github.com/elalish/manifold/pull/496 for background
if (bytes >= (1ull << 63)) {
throw std::bad_alloc();
}
#ifdef MANIFOLD_USE_CUDA
if (CudaEnabled())
cudaMallocManaged(reinterpret_cast<void **>(ptr), bytes);
else
#endif
*ptr = reinterpret_cast<T *>(malloc(bytes));
}
static void freeManaged(T *ptr) {
#ifdef MANIFOLD_USE_CUDA
if (CudaEnabled())
cudaFree(ptr);
else
#endif
free(ptr);
}
static void prefetch(T *ptr, int bytes, bool onHost) {
#ifdef MANIFOLD_USE_CUDA
if (bytes > 0 && CudaEnabled())
cudaMemPrefetchAsync(ptr, std::min(bytes, DEVICE_MAX_BYTES),
onHost ? cudaCpuDeviceId : 0);
#endif
}
// fast routine for memcpy from std::vector to ManagedVec
static void fastUninitializedCopy(T *dst, const T *src, int n,
ExecutionPolicy policy) {
prefetch(dst, n * sizeof(T), policy != ExecutionPolicy::ParUnseq);
switch (policy) {
case ExecutionPolicy::ParUnseq:
#ifdef MANIFOLD_USE_CUDA
cudaMemcpy(dst, src, n * sizeof(T), cudaMemcpyHostToDevice);
#endif
case ExecutionPolicy::Par:
thrust::uninitialized_copy_n(thrust::MANIFOLD_PAR_NS::par, src, n, dst);
break;
case ExecutionPolicy::Seq:
thrust::uninitialized_copy_n(thrust::cpp::par, src, n, dst);
break;
}
}
};
/*
* Host and device vector implementation. This uses `thrust::universal_vector`
* for storage, so data can be moved by the hardware on demand, allows using
* more memory than the available GPU memory, reduce memory overhead and provide
* speedup due to less synchronization.
*
* Due to https://github.com/NVIDIA/thrust/issues/1690 , `push_back` operations
* on universal vectors are *VERY* slow, so a `std::vector` is used as a cache.
* The cache will be created when we perform `push_back` or `reserve` operations
* on the `VecDH`, and destroyed when we try to access device iterator/pointer.
* For better performance, please avoid interspersing `push_back` between device
* memory accesses, as that will cause repeated synchronization and hurts
* performance.
* Note that it is *NOT SAFE* to first obtain a host(device) pointer, perform
* some device(host) modification, and then read the host(device) pointer again
* (on the same vector). The memory will be inconsistent in that case.
*/
template <typename T>
class VecDH {
public:
VecDH() {}
// Note that the vector constructed with this constructor will contain
// uninitialized memory. Please specify `val` if you need to make sure that
// the data is initialized.
VecDH(int size) { impl_.resize(size); }
VecDH(int size, T val) { impl_.resize(size, val); }
VecDH(const std::vector<T> &vec) { impl_ = vec; }
VecDH(const VecDH<T> &other) { impl_ = other.impl_; }
VecDH(VecDH<T> &&other) { impl_ = std::move(other.impl_); }
VecDH<T> &operator=(const VecDH<T> &other) {
impl_ = other.impl_;
return *this;
}
VecDH<T> &operator=(VecDH<T> &&other) {
impl_ = std::move(other.impl_);
return *this;
}
int size() const { return impl_.size(); }
void resize(int newSize, T val = T()) {
bool shrink = size_ > 2 * newSize;
reserve(newSize);
if (size_ < newSize) {
uninitialized_fill(autoPolicy(newSize - size_), ptr_ + size_,
ptr_ + newSize, val);
}
size_ = newSize;
if (shrink) shrink_to_fit();
bool shrink = size() > 2 * newSize;
impl_.resize(newSize, val);
if (shrink) impl_.shrink_to_fit();
}
void shrink_to_fit() {
T *newBuffer = nullptr;
if (size_ > 0) {
newBuffer = reinterpret_cast<T *>(malloc(size_ * sizeof(T)));
if (newBuffer == nullptr) throw std::bad_alloc();
TracyAllocS(newBuffer, size_ * sizeof(T), 3);
uninitialized_copy(autoPolicy(size_), ptr_, ptr_ + size_, newBuffer);
if (ptr_ != nullptr) {
TracyFreeS(ptr_, 3);
free(ptr_);
}
}
ptr_ = newBuffer;
capacity_ = size_;
void swap(VecDH<T> &other) { impl_.swap(other.impl_); }
using Iter = typename ManagedVec<T>::Iter;
using IterC = typename ManagedVec<T>::IterC;
Iter begin() {
impl_.prefetch_to(autoPolicy(size()) != ExecutionPolicy::ParUnseq);
return impl_.begin();
}
Iter end() { return impl_.end(); }
IterC cbegin() const {
impl_.prefetch_to(autoPolicy(size()) != ExecutionPolicy::ParUnseq);
return impl_.cbegin();
}
IterC cend() const { return impl_.cend(); }
IterC begin() const { return cbegin(); }
IterC end() const { return cend(); }
T *ptrD() {
if (size() == 0) return nullptr;
impl_.prefetch_to(autoPolicy(size()) != ExecutionPolicy::ParUnseq);
return impl_.data();
}
const T *cptrD() const {
if (size() == 0) return nullptr;
impl_.prefetch_to(autoPolicy(size()) != ExecutionPolicy::ParUnseq);
return impl_.data();
}
const T *ptrD() const { return cptrD(); }
T *ptrH() {
if (size() == 0) return nullptr;
impl_.prefetch_to(true);
return impl_.data();
}
const T *cptrH() const {
if (size() == 0) return nullptr;
impl_.prefetch_to(true);
return impl_.data();
}
const T *ptrH() const { return cptrH(); }
T &operator[](int i) { return impl_[i]; }
const T &operator[](int i) const { return impl_[i]; }
T &back() { return impl_.back(); }
const T &back() const { return impl_.back(); }
void push_back(const T &val) { impl_.push_back(val); }
void reserve(int n) { impl_.reserve(n); }
#ifdef MANIFOLD_DEBUG
void Dump() const {
std::cout << "VecDH = " << std::endl;
for (int i = 0; i < size_; ++i) {
std::cout << i << ", " << ptr_[i] << ", " << std::endl;
for (int i = 0; i < impl_.size(); ++i) {
std::cout << i << ", " << impl_[i] << ", " << std::endl;
}
std::cout << std::endl;
}
#endif
private:
int size_ = 0;
int capacity_ = 0;
T *ptr_ = nullptr;
ManagedVec<T> impl_;
};
template <typename T>
+36
View File
@@ -0,0 +1,36 @@
// 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.
#ifdef MANIFOLD_USE_CUDA
#include <cuda_runtime.h>
namespace {
int CUDA_DEVICES = -1;
}
namespace manifold {
bool CudaEnabled() {
if (CUDA_DEVICES >= 0) return CUDA_DEVICES > 0;
cudaError_t error = cudaGetDeviceCount(&CUDA_DEVICES);
if (error != cudaSuccess) CUDA_DEVICES = 0;
return CUDA_DEVICES > 0;
}
} // namespace manifold
#else
namespace manifold {
bool CudaEnabled() { return false; }
} // namespace manifold
#endif
+12 -24
View File
@@ -14,6 +14,8 @@
project(manifold_test)
add_subdirectory(third_party)
enable_testing()
set(SOURCE_FILES polygon_test.cpp cross_section_test.cpp manifold_test.cpp boolean_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
@@ -22,30 +24,6 @@ if(MANIFOLD_CBIND)
list(APPEND SOURCE_FILES manifoldc_test.cpp)
endif()
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Prevent installation of GTest with your project
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE)
find_package(GTest)
if(NOT GTest_FOUND)
message(STATUS "GTest not found, downloading from source")
include(FetchContent)
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
OVERRIDE_FIND_PACKAGE
)
FetchContent_MakeAvailable(googletest)
endif()
if(NOT TARGET GTest::GTest)
add_library(GTest::GTest ALIAS gtest)
add_library(GTest::Main ALIAS gtest_main)
endif()
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold sdf samples samplesGPU cross_section)
@@ -58,6 +36,16 @@ if(MANIFOLD_EXPORT)
target_compile_options(${PROJECT_NAME} PUBLIC -DMANIFOLD_EXPORT)
endif()
if(MANIFOLD_USE_CUDA)
set(CUDA_FILES test_main.cpp sdf_test.cpp)
if(MANIFOLD_CBIND)
list(APPEND CUDA_FILES manifoldc_test.cpp)
endif()
set_source_files_properties(${CUDA_FILES} PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
Binary file not shown.
Binary file not shown.
+12 -134
View File
@@ -60,14 +60,14 @@ void TestPoly(const Polygons &polys, int expectedNumTri,
PolygonParams().verbose = options.params.verbose;
std::vector<glm::ivec3> triangles;
// EXPECT_NO_THROW(triangles = Triangulate(polys, precision));
// EXPECT_EQ(triangles.size(), expectedNumTri) << "Basic";
EXPECT_NO_THROW(triangles = Triangulate(polys, precision));
EXPECT_EQ(triangles.size(), expectedNumTri) << "Basic";
EXPECT_NO_THROW(triangles = Triangulate(Turn180(polys), precision));
EXPECT_EQ(triangles.size(), expectedNumTri) << "Turn 180";
// EXPECT_NO_THROW(triangles = Triangulate(Duplicate(polys), precision));
// EXPECT_EQ(triangles.size(), 2 * expectedNumTri) << "Duplicate";
EXPECT_NO_THROW(triangles = Triangulate(Duplicate(polys), precision));
EXPECT_EQ(triangles.size(), 2 * expectedNumTri) << "Duplicate";
PolygonParams().verbose = false;
}
@@ -301,23 +301,6 @@ TEST(Polygon, ColinearY) {
TestPoly(polys, 16);
}
TEST(Polygon, NearlyColinearY) {
Polygons polys;
polys.push_back({
{3.5, 0}, //
{3.0, 0.5}, //
{3.0, 0.0144}, //
{2.0, 0.4}, //
{1.8, 0.1}, //
{1.6, 0.0155}, //
{1.5, 0.0147}, //
{1.4, 0.0144}, //
{1.0, 0.1}, //
{0, 0}, //
});
TestPoly(polys, 8, 0.001);
}
TEST(Polygon, Concave) {
Polygons polys;
polys.push_back({
@@ -517,7 +500,7 @@ TEST(Polygon, Sliver7) {
TestPoly(polys, 6);
}
TEST(Polygon, Sliver8) {
TEST(Polygon, DISABLED_Sliver8) {
Polygons polys;
polys.push_back({
{27.9279995, 4.9749999}, //
@@ -531,7 +514,7 @@ TEST(Polygon, Sliver8) {
TestPoly(polys, 5);
}
TEST(Polygon, Sliver9) {
TEST(Polygon, DISABLED_Sliver9) {
Polygons polys;
polys.push_back({
{1.49183154, -0.4795963766194}, //
@@ -847,6 +830,10 @@ TEST(Polygon, Precision) {
};
TEST(Polygon, Precision2) {
PolygonParams().processOverlaps = true;
const bool intermediateChecks = PolygonParams().intermediateChecks;
PolygonParams().intermediateChecks = false;
Polygons polys;
polys.push_back({
{4.98093176, -0.2479388121113}, //
@@ -869,79 +856,9 @@ TEST(Polygon, Precision2) {
{4.95041943, -0.2417418967819}, //
});
TestPoly(polys, 8);
};
TEST(Polygon, Precision3) {
Polygons polys;
polys.push_back({
{0.630340576, 1.6097908}, //
{0.777489543, 1.58760202}, //
{0.890084028, 1.60028839}, //
{1.05917394, 1.65945554}, //
{1.06027794, 1.65984178}, //
});
TestPoly(polys, 3, 0.00068);
};
TEST(Polygon, Sweep) {
Polygons polys;
polys.push_back({
{0, 0.637057483}, //
{0, 0.637057483}, //
{1.72745752, 4.72425699}, //
{1.72745752, 4.72425699}, //
{1.72745752, 4.72425699}, //
{1.7274611, 4.72425747}, //
{0, 0.637057483}, //
{2.5, 4.82678747}, //
{1.72745752, 4.72425747}, //
{0, 0.637057483}, //
{0, 0.637057483}, //
{0, 0.637057483}, //
{0, 0.637057483}, //
{0, 0.637057483}, //
});
TestPoly(polys, 12);
}
TEST(Polygon, Hole) {
Polygons polys;
polys.push_back({
{0.5, -0.5}, //
{0.5, 0.5}, //
{-0.5, 0.5}, //
{-0.5, 0.5}, //
{-0.5, 0.5}, //
{-0, 0.5}, //
{-0.25, 0.25}, //
{0.25, -0.25}, //
{0.5, 0}, //
{0.5, 0}, //
{0.5, -0.5}, //
{0.5, -0.5}, //
{0.5, -0.5}, //
});
polys.push_back({
{0.5, 0}, //
{-0, 0.5}, //
{-0, 0.5}, //
{0.5, 0.5}, //
{0.5, 0.5}, //
{0.5, 0.5}, //
});
TestPoly(polys, 19);
}
TEST(Polygon, Small) {
Polygons polys;
polys.push_back({
{-0.487163663, 0.00357927009}, //
{-0.49125129, 0.244737789}, //
{-0.491251856, 0.24427411}, //
{-0.491260529, 0.245025411}, //
{-0.49067378, 6.32605588e-05}, //
});
TestPoly(polys, 3, 0.0001);
PolygonParams().processOverlaps = false;
PolygonParams().intermediateChecks = intermediateChecks;
};
TEST(Polygon, Comb) {
@@ -3086,42 +3003,3 @@ TEST(Polygon, BigSponge) {
});
TestPoly(polys, 1771);
}
TEST(Polygon, Woodgrain1) {
Polygons polys;
polys.push_back({
{72.5, -50}, //
{-72.5, 50}, //
{-72.5, -50}, //
});
polys.push_back({
{27.7341537, -49.9853325}, //
{27.4095287, -49.6340637}, //
{27.9608383, -49.758522}, //
});
polys.push_back({
{-1.56910598, -49.98423}, //
{-1.58251643, -49.9838409}, //
{-1.19169295, -48.0386696}, //
{-1.02179492, -49.9855652}, //
});
TestPoly(polys, 12, 0.00145);
}
TEST(Polygon, Woodgrain2) {
Polygons polys;
polys.push_back({
{22.7295628, -49.5001106}, //
{-21.1059895, -49.9855423}, //
{-35.0938339, 10.1798563}, //
{-72.5, -50}, //
{-12.7149582, -50}, //
});
polys.push_back({
{-40.1850586, -49.9840584}, //
{-40.2071075, -49.9845123}, //
{-40.3756485, -49.9855652}, //
{-40.0327415, -19.857235}, //
});
TestPoly(polys, 9, 0.00145);
}
+1 -14
View File
@@ -194,7 +194,7 @@ TEST(Samples, GyroidModule) {
const float size = 20;
Manifold gyroid = GyroidModule(size);
CheckNormals(gyroid);
EXPECT_LE(gyroid.NumDegenerateTris(), 4);
EXPECT_LE(gyroid.NumDegenerateTris(), 2);
EXPECT_EQ(gyroid.Genus(), 15);
CheckGL(gyroid);
@@ -255,16 +255,3 @@ TEST(Samples, Sponge4) {
#endif
}
#endif
#ifdef MANIFOLD_EXPORT
TEST(Samples, SelfIntersect) {
manifold::PolygonParams().processOverlaps = true;
std::string file = __FILE__;
std::string dir = file.substr(0, file.rfind('/'));
Manifold m1 = ImportMesh(dir + "/models/self_intersectA.glb");
Manifold m2 = ImportMesh(dir + "/models/self_intersectB.glb");
Manifold res = m1 + m2;
res.GetMeshGL(); // test crash
manifold::PolygonParams().processOverlaps = false;
}
#endif
-9
View File
@@ -13,19 +13,10 @@
// limitations under the License.
#pragma once
#include <ostream>
#include "gtest/gtest.h"
#include "manifold.h"
#include "public.h"
// somehow gcc11 + gtest 1.11.0 is unable to print glm::ivec3
namespace glm {
inline void PrintTo(const ivec3& point, std::ostream* os) {
*os << "(" << point.x << "," << point.y << "," << point.x << ")";
}
} // namespace glm
using namespace manifold;
struct Options {
+1 -13
View File
@@ -17,14 +17,6 @@
#include "sdf.h"
#include "test.h"
// we need to call some tracy API to establish the connection
#if __has_include(<tracy/Tracy.hpp>)
#include <tracy/Tracy.hpp>
#else
#define FrameMarkStart(x)
#define FrameMarkEnd(x)
#endif
using namespace manifold;
Options options;
@@ -42,9 +34,6 @@ void print_usage() {
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
const char* name = "test setup";
FrameMarkStart(name);
for (int i = 1; i < argc; i++) {
if (argv[i][0] != '-') {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
@@ -78,7 +67,6 @@ int main(int argc, char** argv) {
manifold::PolygonParams().intermediateChecks = true;
manifold::PolygonParams().processOverlaps = false;
FrameMarkEnd(name);
return RUN_ALL_TESTS();
}
@@ -454,4 +442,4 @@ void CheckGL(const Manifold& manifold) {
EXPECT_EQ(meshGL.runTransform.size(), 12 * meshGL.runOriginalID.size());
}
EXPECT_EQ(meshGL.faceID.size(), meshGL.NumTri());
}
}
+37
View File
@@ -0,0 +1,37 @@
# 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.
# ###########################################################################
# Build Google Test #
# ###########################################################################
# Prevent overriding the parent project's compiler/linker
# settings on Windows
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# Prevent installation of GTest with your project
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE)
add_subdirectory(google_test)
# Add aliases for GTest and GMock libraries
if(NOT TARGET GTest::GTest)
add_library(GTest::GTest ALIAS gtest)
add_library(GTest::Main ALIAS gtest_main)
endif()
if(NOT TARGET GTest::GMock)
add_library(GMock::GMock ALIAS gmock)
add_library(GMock::Main ALIAS gmock_main)
endif()