Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
324ff3f065 | ||
|
|
0856539bfc | ||
|
|
621835683f | ||
|
|
bec8164120 | ||
|
|
062399c1c7 | ||
|
|
d51ee709c2 | ||
|
|
206871ac11 | ||
|
|
2055b9c412 | ||
|
|
5e103931f1 | ||
|
|
77051ead35 | ||
|
|
83b78f8356 | ||
|
|
6c6193c25b | ||
|
|
e79d343bec | ||
|
|
1d6a89ac87 | ||
|
|
8651bf8bd5 | ||
|
|
1514c15184 | ||
|
|
cc83e1b7aa | ||
|
|
111350f1ef | ||
|
|
b4f583fcae | ||
|
|
8011fb6072 | ||
|
|
9290d420b0 | ||
|
|
fca66380f5 | ||
|
|
8497e44915 | ||
|
|
a7eac92087 | ||
|
|
15399474fa | ||
|
|
2d513446f7 | ||
|
|
abff7e8ed3 | ||
|
|
7c454dc7a4 | ||
|
|
ffdd97d4d3 | ||
|
|
7facd0ecc0 | ||
|
|
59760b3369 | ||
|
|
5f9b484b6c | ||
|
|
18edefc6c4 | ||
|
|
93e06a2de8 | ||
|
|
047159e2c4 |
@@ -0,0 +1,56 @@
|
||||
name: build_wheels
|
||||
|
||||
on: [release, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build_wheels:
|
||||
name: Build wheels on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, windows-latest, macos-latest]
|
||||
steps:
|
||||
- run: |
|
||||
git config --global submodule.fetchJobs 8
|
||||
git config --global core.longpaths true
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build wheels
|
||||
uses: pypa/cibuildwheel@v2.15.0
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
make_sdist:
|
||||
name: Make SDist
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Optional, use if you use setuptools_scm
|
||||
submodules: true # Optional, use if you have submodules
|
||||
|
||||
- name: Build SDist
|
||||
run: pipx run build --sdist
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: dist/*.tar.gz
|
||||
|
||||
pypi-publish-beta:
|
||||
name: Upload release to PyPI
|
||||
needs: [build_wheels, make_sdist]
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/manifold3d
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: artifact
|
||||
path: dist
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
@@ -15,47 +15,42 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
parallel_backend: [NONE, 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: |
|
||||
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
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libglm-dev libgtest-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip lcov
|
||||
pip install trimesh
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build ${{matrix.backend}}
|
||||
if: matrix.parallel_backend != 'NONE' || matrix.cuda_support != 'OFF'
|
||||
if: matrix.parallel_backend != 'NONE'
|
||||
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}} -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'
|
||||
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'
|
||||
run: |
|
||||
export PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python
|
||||
cd build/test
|
||||
./manifold_test
|
||||
cd ../../
|
||||
python3 bindings/python/examples/run_all.py
|
||||
python3 bindings/python/examples/run_all.py -e
|
||||
- name: Coverage Report
|
||||
# only do code coverage for default sequential backend, it seems that TBB
|
||||
# backend will cause failure
|
||||
# perhaps issue related to invalid memory access?
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
if: matrix.parallel_backend == 'NONE'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
@@ -64,35 +59,31 @@ 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' && matrix.cuda_support == 'OFF'
|
||||
if: matrix.parallel_backend == 'NONE'
|
||||
with:
|
||||
files: build/code_coverage.info
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
|
||||
name: ${{matrix.parallel_backend}}
|
||||
|
||||
build_cbind:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y libgtest-dev libglm-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build C bindings with OMP and CUDA
|
||||
- name: Build C bindings with TBB
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=OMP -DMANIFOLD_USE_CUDA=ON .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=TBB .. && make
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test --gtest_filter=CBIND.*
|
||||
@@ -105,7 +96,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y nodejs libglm-dev
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
@@ -144,8 +135,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
cuda_support: [OFF]
|
||||
parallel_backend: [NONE, TBB]
|
||||
max-parallel: 1
|
||||
runs-on: windows-2019
|
||||
if: github.event.pull_request.draft == false
|
||||
@@ -155,35 +145,40 @@ 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}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DPYBIND11_FINDPYTHON=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -A x64
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
- name: Test ${{matrix.parallel_backend}}
|
||||
shell: bash
|
||||
run: |
|
||||
cd build/test
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mxe:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
max-parallel: 1
|
||||
runs-on: ubuntu-latest
|
||||
container: openscad/mxe-x86_64-gui:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_PYBIND=OFF -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
|
||||
build_mac:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
@@ -194,11 +189,8 @@ jobs:
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
brew install pkg-config assimp
|
||||
brew install pkg-config googletest 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
|
||||
@@ -220,12 +212,23 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
|
||||
variant: [none, tbb, js]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
build_nix_python:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- uses: cachix/install-nix-action@v22
|
||||
- run: nix build -L '.?submodules=1#manifold3d'
|
||||
|
||||
+3
-13
@@ -1,19 +1,9 @@
|
||||
[submodule "third_party/thrust"]
|
||||
path = src/third_party/thrust
|
||||
url = https://github.com/NVIDIA/thrust
|
||||
[submodule "third_party/glm"]
|
||||
path = src/third_party/glm
|
||||
url = https://github.com/g-truc/glm
|
||||
[submodule "third_party/google_test"]
|
||||
path = test/third_party/google_test
|
||||
url = https://github.com/google/googletest.git
|
||||
[submodule "third_party/pybind11"]
|
||||
path = bindings/python/third_party/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
[submodule "src/third_party/clipper2"]
|
||||
path = src/third_party/clipper2
|
||||
url = https://github.com/AngusJohnson/clipper2
|
||||
[submodule "src/third_party/quickhull"]
|
||||
path = src/third_party/quickhull
|
||||
url = https://github.com/akuukka/quickhull
|
||||
[submodule "bindings/python/third_party/nanobind"]
|
||||
path = bindings/python/third_party/nanobind
|
||||
url = https://github.com/wjakob/nanobind
|
||||
|
||||
Vendored
+6
-1
@@ -118,7 +118,7 @@
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=NONE",
|
||||
"-DMANIFOLD_PAR=TBB",
|
||||
"-DCODE_COVERAGE=OFF",
|
||||
"-DCMAKE_CXX_FLAGS=''" //'-fsanitize=address,undefined'"
|
||||
],
|
||||
@@ -140,4 +140,9 @@
|
||||
"Verts"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "ms-python.black-formatter"
|
||||
},
|
||||
"python.formatting.provider": "none",
|
||||
"clang-format.executable": "clang-format",
|
||||
}
|
||||
+44
-39
@@ -16,68 +16,73 @@ cmake_minimum_required(VERSION 3.18)
|
||||
project(manifold LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS "" CACHE STRING "Manifold compiler flags")
|
||||
option(MANIFOLD_EXPORT "Build mesh export (via assimp) utility library" OFF)
|
||||
option(MANIFOLD_TEST "Enable testing suite" ON)
|
||||
option(MANIFOLD_DEBUG "Enable debug tracing/timing" OFF)
|
||||
option(MANIFOLD_USE_CUDA "Enable GPU support via CUDA" OFF)
|
||||
|
||||
option(MANIFOLD_PYBIND "Build python bindings" ON)
|
||||
option(MANIFOLD_CBIND "Build C (FFI) bindings" OFF)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS -O3)
|
||||
option(MANIFOLD_JSBIND "Build js binding" ${EMSCRIPTEN})
|
||||
|
||||
option(TRACY_ENABLE "Use tracy profiling" OFF)
|
||||
option(TRACY_MEMORY_USAGE "Track memory allocation with tracy (expensive)" OFF)
|
||||
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" 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()
|
||||
|
||||
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)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1 -fexceptions -sDISABLE_EXCEPTION_CATCHING=0")
|
||||
set(MANIFOLD_PYBIND OFF)
|
||||
endif()
|
||||
|
||||
option(PYBIND11_FINDPYTHON "Enable PyBind to perform FindPython for you" ON)
|
||||
|
||||
option(BUILD_TEST_CGAL "Build CGAL performance comparisons" OFF)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build dynamic/shared libraries" OFF)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
|
||||
# Allow shared libraries to be relocatable (add Place Independent Code flag).
|
||||
# Also include when statically linking C bindings to avoid issues with bundling
|
||||
# artefacts in host languages using them for FFI.
|
||||
add_compile_options(-fPIC)
|
||||
if(MANIFOLD_PYBIND)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if(Python_VERSION VERSION_LESS 3.12)
|
||||
find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter Development.SABIModule REQUIRED)
|
||||
endif()
|
||||
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}>")
|
||||
if(CMAKE_EXPORT_COMPILE_COMMANDS AND NOT EMSCRIPTEN)
|
||||
# for nixos
|
||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES
|
||||
${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
set(WARNING_FLAGS -Werror -Wall -Wno-sign-compare -Wno-unused)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${WARNING_FLAGS}>"
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=${WARNING_FLAGS}>")
|
||||
if (MSVC)
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} /DNOMINMAX)
|
||||
else()
|
||||
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} ${WARNING_FLAGS} -O3)
|
||||
endif()
|
||||
|
||||
if(CODE_COVERAGE AND NOT MSVC)
|
||||
set(COVERAGE_FLAGS -coverage -fno-inline-small-functions -fkeep-inline-functions -fkeep-static-functions)
|
||||
add_compile_options(
|
||||
"$<$<COMPILE_LANGUAGE:CXX>:${COVERAGE_FLAGS}>"
|
||||
"$<$<COMPILE_LANGUAGE:CUDA>:-Xcompiler=-coverage>")
|
||||
set(MANIFOLD_FLAGS ${MANIFOLD_FLAGS} ${COVERAGE_FLAGS})
|
||||
add_link_options("-coverage")
|
||||
endif()
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(bindings)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ For example, here is a log-log plot of Manifold's performance vs. earlier OpenSC
|
||||
|
||||
## [ManifoldCAD.org](https://manifoldcad.org)
|
||||
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not GPU-accelerated, but it's still quite fast and a good way to test out our Manifold library.
|
||||
If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid modelling web app. Our WASM is not multithreaded yet, but it's still quite fast and a good way to test out our Manifold library.
|
||||
|
||||

|
||||
|
||||
@@ -31,11 +31,15 @@ of this repository may solve the problem.
|
||||
|
||||
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
|
||||
|
||||
We have four core dependencies, making use of submodules to ensure compatibility:
|
||||
- `graphlite`: connected components algorithm
|
||||
- `Clipper2`: provides our 2D subsystem
|
||||
- `GLM`: a compact vector library
|
||||
- `Thrust`: Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
|
||||
System Dependencies (note that we will automatically download the dependency if there is no such package on the system):
|
||||
- [`GLM`](https://github.com/g-truc/glm/): A compact header-only vector library.
|
||||
- [`Thrust`](https://github.com/NVIDIA/thrust): NVIDIA's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)
|
||||
- [`tbb`](https://github.com/oneapi-src/oneTBB/): Intel's thread building blocks library. (only when `MANIFOLD_PAR=TBB` is enabled)
|
||||
- [`gtest`](https://github.com/google/googletest/): Google test library (only when test is enabled, i.e. `MANIFOLD_TEST=ON`)
|
||||
|
||||
Other dependencies:
|
||||
- [`Clipper2`](https://github.com/AngusJohnson/Clipper2): provides our 2D subsystem
|
||||
- [`quickhull`](https://github.com/akuukka/quickhull): 3D convex hull algorithm.
|
||||
|
||||
## What's here
|
||||
|
||||
@@ -43,7 +47,9 @@ 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 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.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -61,13 +67,23 @@ cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON .. && make
|
||||
test/manifold_test
|
||||
```
|
||||
|
||||
CMake flags (usage e.g. `-DMANIFOLD_USE_CUDA=ON`):
|
||||
- `MANIFOLD_USE_CUDA=[<OFF>, ON]`: Provides Nvidia GPU parallelization, requires CUDA Toolkit and its nvcc compiler.
|
||||
- `MANIFOLD_PAR=[<NONE>, OMP, TBB]`: Provides multi-thread parallelization, requires `libomp-dev` or `libtbb-dev`.
|
||||
CMake flags (usage e.g. `-DMANIFOLD_DEBUG=ON`):
|
||||
- `MANIFOLD_JSBIND=[OFF, <ON>]`: Build js binding when using emscripten.
|
||||
- `MANIFOLD_CBIND=[<OFF>, ON]`: Build C FFI binding.
|
||||
- `MANIFOLD_PYBIND=[OFF, <ON>]`: Build python binding.
|
||||
- `MANIFOLD_PAR=[<NONE>, TBB]`: Provides multi-thread parallelization, requires `libtbb-dev` if `TBB` backend is selected.
|
||||
- `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.
|
||||
- `MANIFOLD_TEST=[OFF, <ON>]`: Build unittests.
|
||||
- `TRACY_ENABLE=[<OFF>, ON]`: Enable integration with tracy profiler.
|
||||
See profiling section below.
|
||||
- `BUILD_TEST_CGAL=[<OFF>, ON]`: Builds a CGAL-based performance [comparison](https://github.com/elalish/manifold/tree/master/extras), requires `libcgal-dev`.
|
||||
|
||||
Offline building:
|
||||
- `FETCHCONTENT_SOURCE_DIR_GLM`: path to glm source.
|
||||
- `FETCHCONTENT_SOURCE_DIR_GOOGLETEST`: path to googletest source.
|
||||
- `FETCHCONTENT_SOURCE_DIR_THRUST`: path to NVIDIA thrust source.
|
||||
|
||||
The build instructions used by our CI are in [manifold.yml](https://github.com/elalish/manifold/blob/master/.github/workflows/manifold.yml), which is a good source to check if something goes wrong and for instructions specific to other platforms, like Windows.
|
||||
|
||||
### WASM
|
||||
@@ -99,7 +115,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/tools` to your `PYTHONPATH`, where
|
||||
use the extension, please add `$BUILD_DIR/bindings/python` 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:
|
||||
```
|
||||
@@ -122,6 +138,11 @@ 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).
|
||||
|
||||
@@ -20,6 +20,6 @@ if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
if(EMSCRIPTEN AND MANIFOLD_JSBIND)
|
||||
add_subdirectory(wasm)
|
||||
endif()
|
||||
|
||||
@@ -14,9 +14,20 @@ endif()
|
||||
|
||||
target_link_libraries(
|
||||
${PROJECT_NAME}
|
||||
PRIVATE manifold sdf graphlite cross_section
|
||||
PRIVATE manifold sdf cross_section
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
install(
|
||||
FILES include/manifoldc.h include/types.h
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
+16
-3
@@ -1,7 +1,20 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
|
||||
+17
-4
@@ -1,12 +1,25 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <sdf.h>
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "include/types.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
|
||||
+40
-3
@@ -1,10 +1,23 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "conv.h"
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldCrossSection *manifold_cross_section_empty(void *mem) {
|
||||
@@ -106,6 +119,30 @@ 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) {
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
#pragma once
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include <types.h>
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "sdf.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace manifold;
|
||||
using ManifoldVec = std::vector<Manifold>;
|
||||
using CrossSectionVec = std::vector<CrossSection>;
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <types.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdint>
|
||||
@@ -8,6 +23,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
// Polygons
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length);
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
@@ -24,6 +40,7 @@ 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);
|
||||
@@ -94,7 +111,14 @@ 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,
|
||||
@@ -112,6 +136,7 @@ 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);
|
||||
@@ -137,6 +162,7 @@ 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);
|
||||
@@ -204,6 +230,17 @@ 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,
|
||||
@@ -264,10 +301,9 @@ ManifoldRect *manifold_rect_mul(void *mem, ManifoldRect *r, float x, float y);
|
||||
int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r);
|
||||
int manifold_rect_is_empty(ManifoldRect *r);
|
||||
int manifold_rect_is_finite(ManifoldRect *r);
|
||||
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
|
||||
ManifoldRect *r);
|
||||
|
||||
// Bounding Box
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2);
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b);
|
||||
@@ -292,11 +328,13 @@ 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);
|
||||
@@ -319,6 +357,7 @@ 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();
|
||||
@@ -332,6 +371,7 @@ 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);
|
||||
@@ -343,6 +383,7 @@ 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);
|
||||
@@ -354,6 +395,7 @@ 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);
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <conv.h>
|
||||
#include <cross_section.h>
|
||||
#include <manifold.h>
|
||||
@@ -29,8 +43,7 @@ ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
ManifoldMeshGL *level_set_context(
|
||||
@@ -43,8 +56,7 @@ ManifoldMeshGL *level_set_context(
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, !seq);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
} // namespace
|
||||
@@ -190,6 +202,26 @@ 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);
|
||||
|
||||
+16
-3
@@ -1,8 +1,21 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "meshIO.h"
|
||||
|
||||
#include <conv.h>
|
||||
|
||||
#include "include/types.h"
|
||||
#include "conv.h"
|
||||
#include "types.h"
|
||||
|
||||
// C <-> C++ conversions
|
||||
|
||||
|
||||
+16
-9
@@ -1,8 +1,21 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "conv.h"
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldRect *manifold_rect(void *mem, float x1, float y1, float x2, float y2) {
|
||||
@@ -85,9 +98,3 @@ int manifold_rect_does_overlap_rect(ManifoldRect *a, ManifoldRect *r) {
|
||||
int manifold_rect_is_empty(ManifoldRect *r) { return from_c(r)->IsEmpty(); }
|
||||
|
||||
int manifold_rect_is_finite(ManifoldRect *r) { return from_c(r)->IsFinite(); }
|
||||
|
||||
ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
|
||||
ManifoldRect *r) {
|
||||
auto cs = from_c(r)->AsCrossSection();
|
||||
return to_c(new (mem) CrossSection(cs));
|
||||
}
|
||||
|
||||
@@ -15,10 +15,25 @@
|
||||
project(python)
|
||||
|
||||
add_subdirectory(third_party)
|
||||
pybind11_add_module(manifold3d manifold3d.cpp)
|
||||
nanobind_add_module(
|
||||
manifold3d
|
||||
NB_STATIC STABLE_ABI LTO
|
||||
manifold3d.cpp)
|
||||
target_link_libraries(manifold3d PRIVATE manifold)
|
||||
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_options(manifold3d PRIVATE ${MANIFOLD_FLAGS} -DMODULE_NAME=manifold3d)
|
||||
target_compile_features(manifold3d PUBLIC cxx_std_17)
|
||||
target_include_directories(manifold3d
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
set_target_properties(manifold3d PROPERTIES OUTPUT_NAME "manifold3d")
|
||||
|
||||
if(SKBUILD)
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
else()
|
||||
install(
|
||||
TARGETS manifold3d
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT bindings
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from manifold3d import CrossSection, FillRule
|
||||
from manifold3d import CrossSection
|
||||
|
||||
|
||||
def run():
|
||||
@@ -11,7 +11,7 @@ def run():
|
||||
polygons = cross_section.to_polygons()
|
||||
polygon = polygons[0]
|
||||
if set(polygon) != set(polygon_points):
|
||||
raise Exception(f"{polygon=} differs from {polygon_points=}")
|
||||
raise Exception(f"polygon={polygon} differs from polygon_points={polygon_points}")
|
||||
|
||||
# extrude a polygon to create a manifold
|
||||
extruded_polygon = cross_section.extrude(10.0)
|
||||
@@ -19,20 +19,20 @@ def run():
|
||||
observed_volume = extruded_polygon.get_volume()
|
||||
expected_volume = 10.0
|
||||
if abs(observed_volume - expected_volume) > eps:
|
||||
raise Exception(f"{observed_volume=} differs from {expected_volume=}")
|
||||
raise Exception(f"observed_volume={observed_volume} differs from expected_volume={expected_volume}")
|
||||
observed_surface_area = extruded_polygon.get_surface_area()
|
||||
expected_surface_area = 42.0
|
||||
if abs(observed_surface_area - expected_surface_area) > eps:
|
||||
raise Exception(f"{observed_surface_area=} differs from {expected_surface_area=}")
|
||||
raise Exception(f"observed_surface_area={observed_surface_area} differs from expected_surface_area={expected_surface_area}")
|
||||
|
||||
# get bounding box from manifold
|
||||
observed_bbox = extruded_polygon.bounding_box
|
||||
expected_bbox = (0.0, 0.0, 0.0, 1.0, 1.0, 10.0)
|
||||
if observed_bbox != expected_bbox:
|
||||
raise Exception(f"{observed_bbox=} differs from {expected_bbox=}")
|
||||
raise Exception(f"observed_bbox={observed_bbox} differs from expected_bbox={expected_bbox}")
|
||||
|
||||
return extruded_polygon
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
run()
|
||||
|
||||
@@ -18,6 +18,7 @@ import pathlib
|
||||
import sys
|
||||
import importlib
|
||||
import trimesh
|
||||
import numpy as np
|
||||
from time import time
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -34,8 +35,14 @@ if __name__ == "__main__":
|
||||
model = module.run()
|
||||
mesh = model.to_mesh()
|
||||
if export_models:
|
||||
meshOut = trimesh.Trimesh(
|
||||
vertices=mesh.vert_pos, faces=mesh.tri_verts)
|
||||
if mesh.vert_properties.shape[1] > 3:
|
||||
vertices = mesh.vert_properties[:, :3]
|
||||
colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8)
|
||||
else:
|
||||
vertices = mesh.vert_properties
|
||||
colors = None
|
||||
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts,
|
||||
vertex_colors=colors)
|
||||
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
|
||||
print(f'Exported model to {f}.glb')
|
||||
t1 = time()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
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)
|
||||
+411
-255
@@ -12,22 +12,70 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "pybind11/functional.h"
|
||||
#include "pybind11/numpy.h"
|
||||
#include "pybind11/operators.h"
|
||||
#include "pybind11/pybind11.h"
|
||||
#include "pybind11/stl.h"
|
||||
#include "nanobind/nanobind.h"
|
||||
#include "nanobind/ndarray.h"
|
||||
#include "nanobind/operators.h"
|
||||
#include "nanobind/stl/function.h"
|
||||
#include "nanobind/stl/optional.h"
|
||||
#include "nanobind/stl/tuple.h"
|
||||
#include "nanobind/stl/vector.h"
|
||||
|
||||
namespace py = pybind11;
|
||||
template <>
|
||||
struct nanobind::detail::type_caster<glm::vec3> {
|
||||
NB_TYPE_CASTER(glm::vec3, const_name("Vec3"));
|
||||
|
||||
using Caster = make_caster<float>;
|
||||
|
||||
bool from_python(handle src, uint8_t flags, cleanup_list *cleanup) noexcept {
|
||||
size_t size;
|
||||
PyObject *temp;
|
||||
/* Will initialize 'size' and 'temp'. All return values and
|
||||
return parameters are zero/NULL in the case of a failure. */
|
||||
PyObject **o = seq_get(src.ptr(), &size, &temp);
|
||||
if (size != 3) return false;
|
||||
Caster caster;
|
||||
if (o == nullptr) {
|
||||
Py_XDECREF(temp);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
if (!caster.from_python(o[i], flags, cleanup)) {
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
value[i] = caster.value;
|
||||
}
|
||||
Py_XDECREF(temp);
|
||||
return success;
|
||||
}
|
||||
|
||||
static handle from_cpp(glm::vec3 vec, rv_policy policy,
|
||||
cleanup_list *ls) noexcept {
|
||||
std::vector<float> v{vec.x, vec.y, vec.z};
|
||||
return make_caster<std::vector<float>>().from_cpp(&v, policy, ls);
|
||||
}
|
||||
};
|
||||
|
||||
namespace nb = nanobind;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
typedef std::tuple<float, float> Float2;
|
||||
typedef std::tuple<float, float, float> Float3;
|
||||
|
||||
PYBIND11_MODULE(manifold3d, m) {
|
||||
template <typename T>
|
||||
std::vector<T> toVector(const T *arr, size_t size) {
|
||||
return std::vector<T>(arr, arr + size);
|
||||
}
|
||||
|
||||
NB_MODULE(manifold3d, m) {
|
||||
m.doc() = "Python binding for the Manifold library.";
|
||||
|
||||
m.def("set_min_circular_angle", Quality::SetMinCircularAngle,
|
||||
@@ -66,40 +114,58 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
":param radius: For a given radius of circle, determine how many "
|
||||
"default");
|
||||
|
||||
py::class_<Manifold>(m, "Manifold")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
return first;
|
||||
} else {
|
||||
return Manifold();
|
||||
}
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
nb::class_<Manifold>(m, "Manifold")
|
||||
.def(nb::init<>())
|
||||
.def(
|
||||
"__init__",
|
||||
[](Manifold *self, std::vector<Manifold> &manifolds) {
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
new (self) Manifold(first);
|
||||
} else {
|
||||
new (self) Manifold();
|
||||
}
|
||||
},
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(nb::self + nb::self, "Boolean union.")
|
||||
.def(nb::self - nb::self, "Boolean difference.")
|
||||
.def(nb::self ^ nb::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) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat_view.shape(0) != 3 || mat_view.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid matrix shape");
|
||||
[](Manifold &self, nb::ndarray<float, nb::shape<3, 4>> &mat) {
|
||||
if (mat.ndim() != 2 || mat.shape(0) != 3 || mat.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid matrix shape, expected (3, 4)");
|
||||
glm::mat4x3 mat_glm;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
mat_glm[j][i] = mat(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
},
|
||||
py::arg("m"),
|
||||
nb::arg("m"),
|
||||
"Transform this Manifold in space. The first three columns form a "
|
||||
"3x3 matrix transform and the last is a translation vector. This "
|
||||
"operation can be chained. Transforms are combined and applied "
|
||||
@@ -111,74 +177,45 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
[](Manifold &self, float x = 0.0f, float y = 0.0f, float z = 0.0f) {
|
||||
return self.Translate(glm::vec3(x, y, z));
|
||||
},
|
||||
py::arg("x") = 0.0f, py::arg("y") = 0.0f, py::arg("z") = 0.0f,
|
||||
nb::arg("x") = 0.0f, nb::arg("y") = 0.0f, nb::arg("z") = 0.0f,
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param x: X axis translation. (default 0.0).\n"
|
||||
":param y: Y axis translation. (default 0.0).\n"
|
||||
":param z: Z axis translation. (default 0.0).")
|
||||
.def(
|
||||
"translate",
|
||||
[](Manifold &self, py::array_t<float> &t) {
|
||||
auto t_view = t.unchecked<1>();
|
||||
if (t.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Translate(glm::vec3(t_view(0), t_view(1), t_view(2)));
|
||||
},
|
||||
py::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def("translate", &Manifold::Translate, nb::arg("t"),
|
||||
"Move this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to add to every vertex.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold &self, float scale) {
|
||||
return self.Scale(glm::vec3(scale));
|
||||
},
|
||||
py::arg("scale"),
|
||||
nb::arg("scale"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param scale: The scalar multiplier for each component of every "
|
||||
"vertices.")
|
||||
.def(
|
||||
"scale",
|
||||
[](Manifold &self, py::array_t<float> &scale) {
|
||||
auto scale_view = scale.unchecked<1>();
|
||||
if (scale_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
glm::vec3 v(scale_view(0), scale_view(1), scale_view(2));
|
||||
return self.Scale(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def(
|
||||
"mirror",
|
||||
[](Manifold &self, py::array_t<float> &mirror) {
|
||||
auto v_view = mirror.unchecked<1>();
|
||||
if (v_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
glm::vec3 v(v_view(0), v_view(1), v_view(2));
|
||||
return self.Mirror(v);
|
||||
},
|
||||
py::arg("v"),
|
||||
"Mirror this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param mirror: The vector defining the axis of mirroring.")
|
||||
.def("scale", &Manifold::Scale, nb::arg("v"),
|
||||
"Scale this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param v: The vector to multiply every vertex by component.")
|
||||
.def("mirror", &Manifold::Mirror, nb::arg("v"),
|
||||
"Mirror this Manifold in space. This operation can be chained. "
|
||||
"Transforms are combined and applied lazily."
|
||||
"\n\n"
|
||||
":param mirror: The vector defining the axis of mirroring.")
|
||||
.def(
|
||||
"rotate",
|
||||
[](Manifold &self, py::array_t<float> &v) {
|
||||
auto v_view = v.unchecked<1>();
|
||||
if (v_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return self.Rotate(v_view(0), v_view(1), v_view(2));
|
||||
[](Manifold &self, glm::vec3 v) {
|
||||
return self.Rotate(v[0], v[1], v[2]);
|
||||
},
|
||||
py::arg("v"),
|
||||
nb::arg("v"),
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then Y, then Z, in degrees. We use degrees so that we can "
|
||||
"minimize rounding error, and eliminate it completely for any "
|
||||
@@ -194,8 +231,8 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
float zDegrees = 0.0f) {
|
||||
return self.Rotate(xDegrees, yDegrees, zDegrees);
|
||||
},
|
||||
py::arg("x_degrees") = 0.0f, py::arg("y_degrees") = 0.0f,
|
||||
py::arg("z_degrees") = 0.0f,
|
||||
nb::arg("x_degrees") = 0.0f, nb::arg("y_degrees") = 0.0f,
|
||||
nb::arg("z_degrees") = 0.0f,
|
||||
"Applies an Euler angle rotation to the manifold, first about the X "
|
||||
"axis, then Y, then Z, in degrees. We use degrees so that we can "
|
||||
"minimize rounding error, and eliminate it completely for any "
|
||||
@@ -217,7 +254,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
v.z = std::get<2>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"),
|
||||
nb::arg("f"),
|
||||
"This function does not change the topology, but allows the vertices "
|
||||
"to be moved according to any arbitrary input function. It is easy "
|
||||
"to create a function that warps a geometrically valid object into "
|
||||
@@ -225,9 +262,50 @@ 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<nb::object(
|
||||
Float3, const nb::ndarray<nb::numpy, const float, nb::c_contig>
|
||||
&)> &f) {
|
||||
const int oldNumProp = self.NumProp();
|
||||
return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f](
|
||||
float *newProps,
|
||||
glm::vec3 v,
|
||||
const float *oldProps) {
|
||||
auto result =
|
||||
f(std::make_tuple(v.x, v.y, v.z),
|
||||
nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
&oldProps, {static_cast<unsigned long>(oldNumProp)}));
|
||||
nb::ndarray<float, nb::shape<nb::any>> array;
|
||||
std::vector<float> vec;
|
||||
if (nb::try_cast(result, array)) {
|
||||
if (array.ndim() != 1 || array.shape(0) != newNumProp)
|
||||
throw std::runtime_error("Invalid vector shape, expected (" +
|
||||
std::to_string(newNumProp) + ")");
|
||||
for (int i = 0; i < newNumProp; i++) newProps[i] = array(i);
|
||||
} else if (nb::try_cast(result, vec)) {
|
||||
for (int i = 0; i < newNumProp; i++) newProps[i] = vec[i];
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
"Callback in set_properties should return an array");
|
||||
}
|
||||
});
|
||||
},
|
||||
nb::arg("new_num_prop"), nb::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"),
|
||||
nb::arg("n"),
|
||||
"Increase the density of the mesh by splitting every edge into n "
|
||||
"pieces. For instance, with n = 2, each triangle will be split into "
|
||||
"4 triangles. These will all be coplanar (and will not be "
|
||||
@@ -238,7 +316,33 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"\n"
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def("to_mesh", &Manifold::GetMesh)
|
||||
.def(
|
||||
"to_mesh",
|
||||
[](Manifold &self,
|
||||
std::optional<nb::ndarray<uint32_t, nb::shape<3>>> &normalIdx) {
|
||||
glm::ivec3 v(0);
|
||||
if (normalIdx.has_value()) {
|
||||
if (normalIdx->ndim() != 1 || normalIdx->shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape, expected (3)");
|
||||
auto value = *normalIdx;
|
||||
v = glm::ivec3(value(0), value(1), value(2));
|
||||
}
|
||||
return self.GetMeshGL(v);
|
||||
},
|
||||
"The most complete output of this library, returning a MeshGL that "
|
||||
"is designed to easily push into a renderer, including all "
|
||||
"interleaved vertex properties that may have been input. It also "
|
||||
"includes relations to all the input meshes that form a part of "
|
||||
"this result and the transforms applied to each."
|
||||
"\n\n"
|
||||
":param normalIdx: If the original MeshGL inputs that formed this "
|
||||
"manifold had properties corresponding to normal vectors, you can "
|
||||
"specify which property channels these are (x, y, z), which will "
|
||||
"cause this output MeshGL to automatically update these normals "
|
||||
"according to the applied transforms and front/back side. Each "
|
||||
"channel must be >= 3 and < numProp, and all original MeshGLs must "
|
||||
"use the same channels for their normals.",
|
||||
nb::arg("normalIdx") = nb::none())
|
||||
.def("num_vert", &Manifold::NumVert,
|
||||
"The number of vertices in the Manifold.")
|
||||
.def("num_edge", &Manifold::NumEdge,
|
||||
@@ -304,7 +408,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
|
||||
originOffset);
|
||||
},
|
||||
py::arg("normal"), py::arg("origin_offset"),
|
||||
nb::arg("normal"), nb::arg("origin_offset"),
|
||||
"Convenient version of Split() for a half-space."
|
||||
"\n\n"
|
||||
":param normal: This vector is normal to the cutting plane and its "
|
||||
@@ -319,7 +423,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
{std::get<0>(normal), std::get<1>(normal), std::get<2>(normal)},
|
||||
originOffset);
|
||||
},
|
||||
py::arg("normal"), py::arg("origin_offset"),
|
||||
nb::arg("normal"), nb::arg("origin_offset"),
|
||||
"Identical to SplitByPlane(), but calculating and returning only the "
|
||||
"first result."
|
||||
"\n\n"
|
||||
@@ -328,11 +432,19 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"vector from the plane.\n"
|
||||
":param originOffset: The distance of the plane from the origin in "
|
||||
"the direction of the normal vector.")
|
||||
.def_property_readonly(
|
||||
.def("status", &Manifold::Status,
|
||||
"Returns the reason for an input Mesh producing an empty Manifold. "
|
||||
"This Status only applies to Manifolds newly-created from an input "
|
||||
"Mesh - once they are combined into a new Manifold via operations, "
|
||||
"the status reverts to NoError, simply processing the problem mesh "
|
||||
"as empty. Likewise, empty meshes may still show NoError, for "
|
||||
"instance if they are small enough relative to their precision to "
|
||||
"be collapsed to nothing.")
|
||||
.def_prop_ro(
|
||||
"bounding_box",
|
||||
[](Manifold &self) {
|
||||
auto b = self.BoundingBox();
|
||||
py::tuple box = py::make_tuple(b.min[0], b.min[1], b.min[2],
|
||||
nb::tuple box = nb::make_tuple(b.min[0], b.min[1], b.min[2],
|
||||
b.max[0], b.max[1], b.max[2]);
|
||||
return box;
|
||||
},
|
||||
@@ -370,8 +482,15 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"by sharping all edges that are incident on it, allowing cones to be "
|
||||
"formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
|
||||
py::arg("mesh"))
|
||||
"from_mesh", [](const MeshGL &mesh) { return Manifold(mesh); },
|
||||
nb::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 "
|
||||
@@ -384,23 +503,15 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
std::get<2>(size)),
|
||||
center);
|
||||
},
|
||||
py::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
|
||||
py::arg("center") = false,
|
||||
nb::arg("size") = std::make_tuple(1.0f, 1.0f, 1.0f),
|
||||
nb::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
":param size: The X, Y, and Z dimensions of the box.\n"
|
||||
":param center: Set to true to shift the center to the origin.")
|
||||
.def_static(
|
||||
"cube",
|
||||
[](py::array_t<float> &size, bool center = false) {
|
||||
auto size_view = size.unchecked<1>();
|
||||
if (size_view.shape(0) != 3)
|
||||
throw std::runtime_error("Invalid vector shape");
|
||||
return Manifold::Cube(
|
||||
glm::vec3(size_view(0), size_view(1), size_view(2)), center);
|
||||
},
|
||||
py::arg("size"), py::arg("center") = false,
|
||||
"cube", &Manifold::Cube, nb::arg("size"), nb::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
@@ -411,7 +522,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
[](float x, float y, float z, bool center = false) {
|
||||
return Manifold::Cube(glm::vec3(x, y, z), center);
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("z"), py::arg("center") = false,
|
||||
nb::arg("x"), nb::arg("y"), nb::arg("z"), nb::arg("center") = false,
|
||||
"Constructs a unit cube (edge lengths all one), by default in the "
|
||||
"first octant, touching the origin."
|
||||
"\n\n"
|
||||
@@ -426,9 +537,9 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
return Manifold::Cylinder(height, radiusLow, radiusHigh,
|
||||
circularSegments, center);
|
||||
},
|
||||
py::arg("height"), py::arg("radius_low"),
|
||||
py::arg("radius_high") = -1.0f, py::arg("circular_segments") = 0,
|
||||
py::arg("center") = false,
|
||||
nb::arg("height"), nb::arg("radius_low"),
|
||||
nb::arg("radius_high") = -1.0f, nb::arg("circular_segments") = 0,
|
||||
nb::arg("center") = false,
|
||||
"A convenience constructor for the common case of extruding a "
|
||||
"circle. Can also form cones if both radii are specified."
|
||||
"\n\n"
|
||||
@@ -445,7 +556,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
[](float radius, int circularSegments = 0) {
|
||||
return Manifold::Sphere(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circular_segments") = 0,
|
||||
nb::arg("radius"), nb::arg("circular_segments") = 0,
|
||||
"Constructs a geodesic sphere of a given radius.\n"
|
||||
"\n"
|
||||
":param radius: Radius of the sphere. Must be positive.\n"
|
||||
@@ -454,118 +565,145 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"four, as this sphere is constructed by refining an octahedron. This "
|
||||
"means there are a circle of vertices on all three of the axis "
|
||||
"planes. Default is calculated by the static Defaults.")
|
||||
.def_static("reserve_ids", Manifold::ReserveIDs, py::arg("n"),
|
||||
.def_static("reserve_ids", Manifold::ReserveIDs, nb::arg("n"),
|
||||
"Returns the first of n sequential new unique mesh IDs for "
|
||||
"marking sets of triangles that can be looked up after "
|
||||
"further operations. Assign to MeshGL.runOriginalID vector");
|
||||
|
||||
py::class_<Mesh>(m, "Mesh")
|
||||
nb::class_<MeshGL>(m, "Mesh")
|
||||
.def(
|
||||
py::init([](py::array_t<float> &vertPos, py::array_t<int> &triVerts,
|
||||
std::optional<py::array_t<float>> &vertNormal,
|
||||
std::optional<py::array_t<float>> &halfedgeTangent) {
|
||||
auto vertPos_view = vertPos.unchecked<2>();
|
||||
auto triVerts_view = triVerts.unchecked<2>();
|
||||
if (vertPos_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid vert_pos shape");
|
||||
if (triVerts_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid tri_verts shape");
|
||||
std::vector<glm::vec3> vertPos_vec(vertPos_view.shape(0));
|
||||
std::vector<glm::ivec3> triVerts_vec(triVerts_view.shape(0));
|
||||
for (int i = 0; i < vertPos_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
vertPos_vec[i][j] = vertPos_view(i, j);
|
||||
for (int i = 0; i < triVerts_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
triVerts_vec[i][j] = triVerts_view(i, j);
|
||||
// note that reshape requires mutable ndarray, but this will not
|
||||
// affect the original array passed into the function
|
||||
"__init__",
|
||||
[](MeshGL *self,
|
||||
nb::ndarray<float, nb::shape<nb::any, nb::any>, nb::c_contig>
|
||||
&vertProp,
|
||||
nb::ndarray<uint32_t, nb::shape<nb::any, 3>, nb::c_contig>
|
||||
&triVerts,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &mergeFromVert,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &mergeToVert,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &runIndex,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &runOriginalID,
|
||||
std::optional<nb::ndarray<float, nb::shape<nb::any, 4, 3>,
|
||||
nb::c_contig>> &runTransform,
|
||||
const std::optional<nb::ndarray<uint32_t, nb::shape<nb::any>,
|
||||
nb::c_contig>> &faceID,
|
||||
const std::optional<nb::ndarray<float, nb::shape<nb::any, 3, 4>,
|
||||
nb::c_contig>> &halfedgeTangent,
|
||||
float precision) {
|
||||
new (self) MeshGL();
|
||||
MeshGL &out = *self;
|
||||
out.numProp = vertProp.shape(1);
|
||||
out.vertProperties =
|
||||
toVector<float>(vertProp.data(), vertProp.size());
|
||||
|
||||
// optional arguments
|
||||
if (!vertNormal.has_value() && !halfedgeTangent.has_value()) {
|
||||
return Mesh({vertPos_vec, triVerts_vec});
|
||||
} else {
|
||||
auto vertNormal_view = vertNormal.value().unchecked<2>();
|
||||
auto halfedgeTangent_view =
|
||||
halfedgeTangent.value().unchecked<2>();
|
||||
if (vertNormal_view.shape(0) != 0) {
|
||||
if (vertNormal_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid vert_normal shape");
|
||||
if (vertNormal_view.shape(0) != vertPos_view.shape(0))
|
||||
throw std::runtime_error(
|
||||
"vert_normal must have the same length as vert_pos");
|
||||
}
|
||||
if (halfedgeTangent_view.shape(0) != 0) {
|
||||
if (halfedgeTangent_view.shape(1) != 4)
|
||||
throw std::runtime_error("Invalid halfedge_tangent shape");
|
||||
if (halfedgeTangent_view.shape(0) != triVerts_view.shape(0) * 3)
|
||||
throw std::runtime_error(
|
||||
"halfedge_tangent must be three times as long as "
|
||||
"tri_verts");
|
||||
}
|
||||
std::vector<glm::vec3> vertNormal_vec(vertNormal_view.shape(0));
|
||||
std::vector<glm::vec4> halfedgeTangent_vec(
|
||||
halfedgeTangent_view.shape(0));
|
||||
for (int i = 0; i < vertNormal_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2})
|
||||
vertNormal_vec[i][j] = vertNormal_view(i, j);
|
||||
for (int i = 0; i < halfedgeTangent_view.shape(0); i++)
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedgeTangent_vec[i][j] = halfedgeTangent_view(i, j);
|
||||
if (triVerts.ndim() != 2 || triVerts.shape(1) != 3)
|
||||
throw std::runtime_error(
|
||||
"Invalid tri_verts shape, expected (-1, 3)");
|
||||
out.triVerts = toVector<uint32_t>(triVerts.data(), triVerts.size());
|
||||
|
||||
return Mesh({vertPos_vec, triVerts_vec, vertNormal_vec,
|
||||
halfedgeTangent_vec});
|
||||
if (mergeFromVert.has_value())
|
||||
out.mergeFromVert = toVector<uint32_t>(mergeFromVert->data(),
|
||||
mergeFromVert->size());
|
||||
|
||||
if (mergeToVert.has_value())
|
||||
out.mergeToVert =
|
||||
toVector<uint32_t>(mergeToVert->data(), mergeToVert->size());
|
||||
|
||||
if (runIndex.has_value())
|
||||
out.runIndex =
|
||||
toVector<uint32_t>(runIndex->data(), runIndex->size());
|
||||
|
||||
if (runOriginalID.has_value())
|
||||
out.runOriginalID = toVector<uint32_t>(runOriginalID->data(),
|
||||
runOriginalID->size());
|
||||
|
||||
if (runTransform.has_value()) {
|
||||
auto runTransform1 = *runTransform;
|
||||
if (runTransform1.ndim() != 3 || runTransform1.shape(1) != 4 ||
|
||||
runTransform1.shape(2) != 3)
|
||||
throw std::runtime_error(
|
||||
"Invalid run_transform shape, expected (-1, 4, 3)");
|
||||
out.runTransform =
|
||||
toVector<float>(runTransform1.data(), runTransform1.size());
|
||||
}
|
||||
}),
|
||||
py::arg("vert_pos"), py::arg("tri_verts"),
|
||||
py::arg("vert_normal") = py::none(),
|
||||
py::arg("halfedge_tangent") = py::none())
|
||||
.def_property_readonly("vert_pos",
|
||||
[](Mesh &self) {
|
||||
const int numVert = self.vertPos.size();
|
||||
py::array_t<float> vert_pos({numVert, 3});
|
||||
auto vert_pos_view =
|
||||
vert_pos.mutable_unchecked<2>();
|
||||
|
||||
for (int i = 0; i < numVert; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
vert_pos_view(i, j) = self.vertPos[i][j];
|
||||
return vert_pos;
|
||||
})
|
||||
.def_property_readonly("tri_verts",
|
||||
[](Mesh &self) {
|
||||
const int numTri = self.triVerts.size();
|
||||
py::array_t<int> tri_verts({numTri, 3});
|
||||
auto tri_verts_view =
|
||||
tri_verts.mutable_unchecked<2>();
|
||||
if (faceID.has_value())
|
||||
out.faceID = toVector<uint32_t>(faceID->data(), faceID->size());
|
||||
|
||||
for (int i = 0; i < numTri; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
tri_verts_view(i, j) = self.triVerts[i][j];
|
||||
return tri_verts;
|
||||
})
|
||||
.def_property_readonly(
|
||||
"vert_normal",
|
||||
[](Mesh &self) {
|
||||
const int numVert = self.vertNormal.size();
|
||||
py::array_t<float> vert_normal({numVert, 3});
|
||||
auto vert_normal_view = vert_normal.mutable_unchecked<2>();
|
||||
if (halfedgeTangent.has_value()) {
|
||||
auto halfedgeTangent1 = *halfedgeTangent;
|
||||
if (halfedgeTangent1.ndim() != 3 ||
|
||||
halfedgeTangent1.shape(1) != 3 ||
|
||||
halfedgeTangent1.shape(2) != 4)
|
||||
throw std::runtime_error(
|
||||
"Invalid halfedge_tangent shape, expected (-1, 3, 4)");
|
||||
out.halfedgeTangent = toVector<float>(halfedgeTangent1.data(),
|
||||
halfedgeTangent1.size());
|
||||
}
|
||||
},
|
||||
nb::arg("vert_properties"), nb::arg("tri_verts"),
|
||||
nb::arg("merge_from_vert") = nb::none(),
|
||||
nb::arg("merge_to_vert") = nb::none(),
|
||||
nb::arg("run_index") = nb::none(),
|
||||
nb::arg("run_original_id") = nb::none(),
|
||||
nb::arg("run_transform") = nb::none(),
|
||||
nb::arg("face_id") = nb::none(),
|
||||
nb::arg("halfedge_tangent") = nb::none(), nb::arg("precision") = 0)
|
||||
.def_prop_ro("vert_properties",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.vertProperties.data(),
|
||||
{self.vertProperties.size() / self.numProp,
|
||||
self.numProp});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_prop_ro("tri_verts",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const int, nb::c_contig>(
|
||||
self.triVerts.data(), {self.triVerts.size() / 3, 3});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_prop_ro("run_transform",
|
||||
[](const MeshGL &self) {
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.runTransform.data(), {self.runTransform.size() / 12, 4, 3});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_prop_ro("halfedge_tangent",
|
||||
[](const MeshGL &self) {
|
||||
float *data = new float[self.halfedgeTangent.size()];
|
||||
std::copy(self.halfedgeTangent.data(),
|
||||
self.halfedgeTangent.data() +
|
||||
self.halfedgeTangent.size(),
|
||||
data);
|
||||
return nb::ndarray<nb::numpy, const float, nb::c_contig>(
|
||||
self.halfedgeTangent.data(), {self.halfedgeTangent.size() / 12, 3, 4});
|
||||
}, nb::rv_policy::reference_internal)
|
||||
.def_ro("merge_from_vert", &MeshGL::mergeFromVert)
|
||||
.def_ro("merge_to_vert", &MeshGL::mergeToVert)
|
||||
.def_ro("run_index", &MeshGL::runIndex)
|
||||
.def_ro("run_original_id", &MeshGL::runOriginalID)
|
||||
.def_ro("face_id", &MeshGL::faceID);
|
||||
|
||||
for (int i = 0; i < numVert; ++i)
|
||||
for (const int j : {0, 1, 2})
|
||||
vert_normal_view(i, j) = self.vertNormal[i][j];
|
||||
return vert_normal;
|
||||
})
|
||||
.def_property_readonly("halfedge_tangent", [](Mesh &self) {
|
||||
const int numEdge = self.halfedgeTangent.size();
|
||||
py::array_t<float> halfedge_tangent({numEdge, 4});
|
||||
auto halfedge_tangent_view = halfedge_tangent.mutable_unchecked<2>();
|
||||
nb::enum_<Manifold::Error>(m, "Error")
|
||||
.value("NoError", Manifold::Error::NoError)
|
||||
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
|
||||
.value("NotManifold", Manifold::Error::NotManifold)
|
||||
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
|
||||
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
|
||||
.value("MissingPositionProperties",
|
||||
Manifold::Error::MissingPositionProperties)
|
||||
.value("MergeVectorsDifferentLengths",
|
||||
Manifold::Error::MergeVectorsDifferentLengths)
|
||||
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
|
||||
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
|
||||
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
|
||||
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
|
||||
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);
|
||||
|
||||
for (int i = 0; i < numEdge; ++i)
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
halfedge_tangent_view(i, j) = self.halfedgeTangent[i][j];
|
||||
return halfedge_tangent;
|
||||
});
|
||||
|
||||
py::enum_<CrossSection::FillRule>(m, "FillRule")
|
||||
nb::enum_<CrossSection::FillRule>(m, "FillRule")
|
||||
.value("EvenOdd", CrossSection::FillRule::EvenOdd,
|
||||
"Only odd numbered sub-regions are filled.")
|
||||
.value("NonZero", CrossSection::FillRule::NonZero,
|
||||
@@ -575,7 +713,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
.value("Negative", CrossSection::FillRule::Negative,
|
||||
"Only sub-regions with winding counts < 0 are filled.");
|
||||
|
||||
py::enum_<CrossSection::JoinType>(m, "JoinType")
|
||||
nb::enum_<CrossSection::JoinType>(m, "JoinType")
|
||||
.value("Square", CrossSection::JoinType::Square,
|
||||
"Squaring is applied uniformly at all joins where the internal "
|
||||
"join angle is less that 90 degrees. The squared edge will be at "
|
||||
@@ -593,37 +731,39 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"joins would exceed a given maximum miter distance (relative to the "
|
||||
"offset distance), these are 'squared' instead.");
|
||||
|
||||
py::class_<CrossSection>(
|
||||
nb::class_<CrossSection>(
|
||||
m, "CrossSection",
|
||||
"Two-dimensional cross sections guaranteed to be without "
|
||||
"self-intersections, or overlaps between polygons (from construction "
|
||||
"onwards). This class makes use of the "
|
||||
"[Clipper2](http://www.angusj.com/clipper2/Docs/Overview.htm) library "
|
||||
"for polygon clipping (boolean) and offsetting operations.")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<std::vector<Float2>> &polygons,
|
||||
CrossSection::FillRule fillrule) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
simplePolygons[i] = std::vector<glm::vec2>(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
simplePolygons[i][j] = {std::get<0>(polygons[i][j]),
|
||||
std::get<1>(polygons[i][j])};
|
||||
}
|
||||
}
|
||||
return CrossSection(simplePolygons, fillrule);
|
||||
}),
|
||||
py::arg("polygons"),
|
||||
py::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
"Create a 2d cross-section from a set of contours (complex "
|
||||
"polygons). A boolean union operation (with Positive filling rule "
|
||||
"by default) performed to combine overlapping polygons and ensure "
|
||||
"the resulting CrossSection is free of intersections."
|
||||
"\n\n"
|
||||
":param contours: A set of closed paths describing zero or more "
|
||||
"complex polygons.\n"
|
||||
":param fillrule: The filling rule used to interpret polygon "
|
||||
"sub-regions in contours.")
|
||||
.def(nb::init<>())
|
||||
.def(
|
||||
"__init__",
|
||||
[](CrossSection *self, std::vector<std::vector<Float2>> &polygons,
|
||||
CrossSection::FillRule fillrule) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
simplePolygons[i] = std::vector<glm::vec2>(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
simplePolygons[i][j] = {std::get<0>(polygons[i][j]),
|
||||
std::get<1>(polygons[i][j])};
|
||||
}
|
||||
}
|
||||
new (self) CrossSection(simplePolygons, fillrule);
|
||||
},
|
||||
nb::arg("polygons"),
|
||||
nb::arg("fillrule") = CrossSection::FillRule::Positive,
|
||||
"Create a 2d cross-section from a set of contours (complex "
|
||||
"polygons). A boolean union operation (with Positive filling rule "
|
||||
"by default) performed to combine overlapping polygons and ensure "
|
||||
"the resulting CrossSection is free of intersections."
|
||||
"\n\n"
|
||||
":param contours: A set of closed paths describing zero or more "
|
||||
"complex polygons.\n"
|
||||
":param fillrule: The filling rule used to interpret polygon "
|
||||
"sub-regions in contours.")
|
||||
.def("area", &CrossSection::Area,
|
||||
"Return the total area covered by complex polygons making up the "
|
||||
"CrossSection.")
|
||||
@@ -671,14 +811,13 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
":param ax: the axis to be mirrored over")
|
||||
.def(
|
||||
"transform",
|
||||
[](CrossSection self, py::array_t<float> &mat) {
|
||||
auto mat_view = mat.unchecked<2>();
|
||||
if (mat_view.shape(0) != 2 || mat_view.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid matrix shape");
|
||||
[](CrossSection self, nb::ndarray<float, nb::shape<2, 3>> &mat) {
|
||||
if (mat.ndim() != 2 || mat.shape(0) != 2 || mat.shape(1) != 3)
|
||||
throw std::runtime_error("Invalid matrix shape, expected (2, 3)");
|
||||
glm::mat3x2 mat_glm;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
mat_glm[j][i] = mat_view(i, j);
|
||||
mat_glm[j][i] = mat(i, j);
|
||||
}
|
||||
}
|
||||
return self.Transform(mat_glm);
|
||||
@@ -698,7 +837,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
v.y = std::get<1>(fv);
|
||||
});
|
||||
},
|
||||
py::arg("f"),
|
||||
nb::arg("f"),
|
||||
"Move the vertices of this CrossSection (creating a new one) "
|
||||
"according to any arbitrary input function, followed by a union "
|
||||
"operation (with a Positive fill rule) that ensures any introduced "
|
||||
@@ -718,9 +857,9 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"do not improve quality in any meaningful way. This is particularly "
|
||||
"important if further offseting operations are to be performed, "
|
||||
"which would compound the issue.")
|
||||
.def("offset", &CrossSection::Offset, py::arg("delta"),
|
||||
py::arg("join_type"), py::arg("miter_limit") = 2.0,
|
||||
py::arg("arc_tolerance") = 0.0,
|
||||
.def("offset", &CrossSection::Offset, nb::arg("delta"),
|
||||
nb::arg("join_type"), nb::arg("miter_limit") = 2.0,
|
||||
nb::arg("arc_tolerance") = 0.0,
|
||||
"Inflate the contours in CrossSection by the specified delta, "
|
||||
"handling corners according to the given JoinType."
|
||||
"\n\n"
|
||||
@@ -740,9 +879,26 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"<B>JoinType::Round</B> corners (roughly, the number of vertices "
|
||||
"that will be added to each contour). Default is calculated by the "
|
||||
"static Quality defaults according to the radius.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
.def(py::self - py::self, "Boolean difference.")
|
||||
.def(py::self ^ py::self, "Boolean intersection.")
|
||||
.def(nb::self + nb::self, "Boolean union.")
|
||||
.def(nb::self - nb::self, "Boolean difference.")
|
||||
.def(nb::self ^ nb::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 "
|
||||
@@ -751,12 +907,12 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
"to_polygons",
|
||||
[](CrossSection self) {
|
||||
const Polygons &data = self.ToPolygons();
|
||||
py::list polygon_list;
|
||||
nb::list polygon_list;
|
||||
for (int i = 0; i < data.size(); ++i) {
|
||||
py::list polygon;
|
||||
nb::list polygon;
|
||||
for (int j = 0; j < data[i].size(); ++j) {
|
||||
auto f = data[i][j];
|
||||
py::tuple vertex = py::make_tuple(f[0], f[1]);
|
||||
nb::tuple vertex = nb::make_tuple(f[0], f[1]);
|
||||
polygon.append(vertex);
|
||||
}
|
||||
polygon_list.append(polygon);
|
||||
@@ -774,9 +930,9 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
return Manifold::Extrude(self, height, nDivisions, twistDegrees,
|
||||
scaleTopVec);
|
||||
},
|
||||
py::arg("height"), py::arg("n_divisions") = 0,
|
||||
py::arg("twist_degrees") = 0.0f,
|
||||
py::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
nb::arg("height"), nb::arg("n_divisions") = 0,
|
||||
nb::arg("twist_degrees") = 0.0f,
|
||||
nb::arg("scale_top") = std::make_tuple(1.0f, 1.0f),
|
||||
"Constructs a manifold from the set of polygons by extruding them "
|
||||
"along the Z-axis.\n"
|
||||
"\n"
|
||||
@@ -795,7 +951,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
[](CrossSection self, int circularSegments = 0) {
|
||||
return Manifold::Revolve(self, circularSegments);
|
||||
},
|
||||
py::arg("circular_segments") = 0,
|
||||
nb::arg("circular_segments") = 0,
|
||||
"Constructs a manifold from the set of polygons by revolving this "
|
||||
"cross-section around its Y-axis and then setting this as the Z-axis "
|
||||
"of the resulting manifold. If the polygons cross the Y-axis, only "
|
||||
@@ -810,7 +966,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
return CrossSection::Square({std::get<0>(dims), std::get<1>(dims)},
|
||||
center);
|
||||
},
|
||||
py::arg("dims"), py::arg("center") = false,
|
||||
nb::arg("dims"), nb::arg("center") = false,
|
||||
"Constructs a square with the given XY dimensions. By default it is "
|
||||
"positioned in the first quadrant, touching the origin. If any "
|
||||
"dimensions in size are negative, or if all are zero, an empty "
|
||||
@@ -823,7 +979,7 @@ PYBIND11_MODULE(manifold3d, m) {
|
||||
[](float radius, int circularSegments) {
|
||||
return CrossSection::Circle(radius, circularSegments);
|
||||
},
|
||||
py::arg("radius"), py::arg("circularSegments") = 0,
|
||||
nb::arg("radius"), nb::arg("circularSegments") = 0,
|
||||
"Constructs a circle of a given radius."
|
||||
"\n\n"
|
||||
":param radius: Radius of the circle. Must be positive.\n"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
add_subdirectory(pybind11)
|
||||
add_subdirectory(nanobind)
|
||||
|
||||
+1
Submodule bindings/python/third_party/nanobind added at 23ea320229
Vendored
-1
Submodule bindings/python/third_party/pybind11 deleted from 12852cd33b
@@ -14,14 +14,14 @@
|
||||
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "helpers.cpp"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
@@ -125,6 +125,10 @@ 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)
|
||||
@@ -134,6 +138,7 @@ 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)
|
||||
@@ -176,6 +181,10 @@ 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);
|
||||
|
||||
@@ -472,6 +472,36 @@ 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
|
||||
@@ -596,6 +626,33 @@ 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
@@ -0,0 +1 @@
|
||||
declare module '@jscadui/3mf-export';
|
||||
@@ -130,18 +130,14 @@ model-viewer {
|
||||
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#file {
|
||||
width: 250px;
|
||||
margin: 1em 0.5em;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 250px;
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@@ -150,6 +146,10 @@ model-viewer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.uparrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
@@ -174,6 +174,7 @@ model-viewer {
|
||||
position: absolute;
|
||||
width: 36px;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
background-size: 20px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
@@ -193,7 +194,6 @@ model-viewer {
|
||||
|
||||
.trash {
|
||||
background-image: url(/icons/trash.png);
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
@@ -212,6 +212,15 @@ 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;
|
||||
@@ -220,5 +229,31 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,20 +26,31 @@ let editor = undefined;
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
const fileButton = document.querySelector('#file');
|
||||
const currentElement = document.querySelector('#current');
|
||||
const arrow = document.querySelector('.uparrow');
|
||||
const dropdown = document.querySelector('.dropdown');
|
||||
const 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 hideDropdown = function() {
|
||||
dropdown.classList.remove('show');
|
||||
arrow.classList.remove('down');
|
||||
fileDropdown.classList.remove('show');
|
||||
saveDropdown.classList.remove('show');
|
||||
fileArrow.classList.remove('down');
|
||||
saveArrow.classList.remove('down');
|
||||
};
|
||||
const toggleDropdown = function(event) {
|
||||
const toggleFileDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
dropdown.classList.toggle('show');
|
||||
arrow.classList.toggle('down');
|
||||
fileDropdown.classList.toggle('show');
|
||||
fileArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleDropdown;
|
||||
const toggleSaveDropdown = function(event) {
|
||||
event.stopPropagation();
|
||||
saveDropdown.classList.toggle('show');
|
||||
saveArrow.classList.toggle('down');
|
||||
};
|
||||
fileButton.onclick = toggleFileDropdown;
|
||||
saveArrow.parentElement.onclick = toggleSaveDropdown;
|
||||
document.body.onclick = hideDropdown;
|
||||
|
||||
const prefix = 'ManifoldCAD';
|
||||
@@ -62,7 +73,7 @@ function nthKey(n) {
|
||||
|
||||
function saveCurrent() {
|
||||
if (editor) {
|
||||
const currentName = currentElement.textContent;
|
||||
const currentName = currentFileElement.textContent;
|
||||
if (!exampleFunctions.get(currentName)) {
|
||||
setScript(currentName, editor.getValue());
|
||||
}
|
||||
@@ -77,7 +88,7 @@ let isExample = true;
|
||||
function switchTo(scriptName) {
|
||||
if (editor) {
|
||||
switching = true;
|
||||
currentElement.textContent = scriptName;
|
||||
currentFileElement.textContent = scriptName;
|
||||
setScript('currentName', scriptName);
|
||||
const code =
|
||||
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
|
||||
@@ -148,8 +159,8 @@ function addEdit(button) {
|
||||
if (!input) return;
|
||||
const newName = uniqueName(input);
|
||||
label.textContent = newName;
|
||||
if (currentElement.textContent == oldName) {
|
||||
currentElement.textContent = newName;
|
||||
if (currentFileElement.textContent == oldName) {
|
||||
currentFileElement.textContent = newName;
|
||||
}
|
||||
removeScript(oldName);
|
||||
setScript(newName, code);
|
||||
@@ -182,7 +193,7 @@ function addEdit(button) {
|
||||
}, {once: true});
|
||||
} else if (performance.now() - lastClick > 500) {
|
||||
removeScript(label.textContent);
|
||||
if (currentElement.textContent == label.textContent) {
|
||||
if (currentFileElement.textContent == label.textContent) {
|
||||
switchTo('Intro');
|
||||
}
|
||||
const container = button.parentElement;
|
||||
@@ -269,10 +280,10 @@ require(['vs/editor/editor.main'], async function() {
|
||||
|
||||
for (const [name] of exampleFunctions) {
|
||||
const button = createDropdownItem(name);
|
||||
dropdown.appendChild(button.parentElement);
|
||||
fileDropdown.appendChild(button.parentElement);
|
||||
}
|
||||
|
||||
let currentName = currentElement.textContent;
|
||||
let currentName = currentFileElement.textContent;
|
||||
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
const key = nthKey(i);
|
||||
@@ -347,7 +358,10 @@ function finishRun() {
|
||||
}
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const output = {
|
||||
glbURL: null,
|
||||
threeMFURL: null
|
||||
};
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
@@ -370,10 +384,16 @@ function createWorker() {
|
||||
finishRun();
|
||||
runButton.disabled = true;
|
||||
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
mv.src = objectURL;
|
||||
if (objectURL == null) {
|
||||
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) {
|
||||
mv.showPoster();
|
||||
poster.textContent = 'Error';
|
||||
createWorker();
|
||||
@@ -411,10 +431,24 @@ runButton.onclick = function() {
|
||||
}
|
||||
};
|
||||
|
||||
const downloadButton = document.querySelector('#download');
|
||||
downloadButton.onclick = function() {
|
||||
const link = document.createElement('a');
|
||||
link.download = 'manifold.glb';
|
||||
link.href = objectURL;
|
||||
link.click();
|
||||
};
|
||||
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');
|
||||
|
||||
@@ -40,15 +40,27 @@
|
||||
<div class="page">
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
<div class="margin" id="file-margin" >
|
||||
<button type="button" id="file" class="blue item"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
<div class="dropdown">
|
||||
<div class="dropdown" id="fileDropdown">
|
||||
<button type="button" class="green item" id="new">New</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
|
||||
<button type="button" id="download" class="blue margin">Save GLB</button>
|
||||
<div 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>
|
||||
</div>
|
||||
<div class="container" style="flex: 2;">
|
||||
<div id="editor" class="col"></div>
|
||||
@@ -74,4 +86,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>
|
||||
|
||||
Generated
+16
-4
@@ -11,6 +11,8 @@
|
||||
"@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"
|
||||
@@ -411,6 +413,11 @@
|
||||
"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",
|
||||
@@ -581,6 +588,12 @@
|
||||
"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",
|
||||
@@ -1000,10 +1013,9 @@
|
||||
}
|
||||
},
|
||||
"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
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.0.tgz",
|
||||
"integrity": "sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg=="
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"@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"
|
||||
@@ -27,4 +29,4 @@
|
||||
"vite": "^4.3.2",
|
||||
"vitest": "^0.31.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ function initialized(worker) {
|
||||
});
|
||||
}
|
||||
|
||||
let objectURL = null;
|
||||
let glbURL = 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(objectURL);
|
||||
objectURL = e.data.objectURL;
|
||||
if (objectURL == null) {
|
||||
reject('no objectURL');
|
||||
URL.revokeObjectURL(glbURL);
|
||||
glbURL = e.data.glbURL;
|
||||
if (glbURL == null) {
|
||||
reject('no glbURL)');
|
||||
}
|
||||
const docIn = await io.read(objectURL);
|
||||
const docIn = await io.read(glbURL);
|
||||
const nodes = docIn.getRoot().listNodes();
|
||||
for (const node of nodes) {
|
||||
const mesh = node.getMesh();
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Document, Material, Node, WebIO} from '@gltf-transform/core';
|
||||
import {Document, mat4, 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';
|
||||
@@ -42,23 +44,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'
|
||||
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh', 'hull'
|
||||
];
|
||||
// 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'
|
||||
'trimByPlane', 'split', 'splitByPlane', 'hull'
|
||||
];
|
||||
// CrossSection static methods (that return a new cross-section)
|
||||
const crossSectionStaticFunctions = [
|
||||
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
|
||||
'ofPolygons'
|
||||
'ofPolygons', 'hull'
|
||||
];
|
||||
// CrossSection member functions (that return a new cross-section)
|
||||
const crossSectionMemberFunctions = [
|
||||
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
|
||||
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset'
|
||||
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset', 'hull'
|
||||
];
|
||||
// top level functions that construct a new manifold/mesh
|
||||
const toplevelConstructors = ['show', 'only', 'setMaterial'];
|
||||
@@ -142,6 +144,8 @@ 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;
|
||||
@@ -150,6 +154,45 @@ 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 {
|
||||
@@ -229,12 +272,12 @@ function log(...args: any[]) {
|
||||
|
||||
self.onmessage = async (e) => {
|
||||
const content = e.data +
|
||||
'\nreturn exportGLB(typeof result === "undefined" ? undefined : result);\n';
|
||||
'\nreturn exportModels(typeof result === "undefined" ? undefined : result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
'exportModels', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportGLB, glMatrix, module, //@ts-ignore
|
||||
exportModels, glMatrix, module, //@ts-ignore
|
||||
...exposedFunctions.map(name => module[name]));
|
||||
} catch (error: any) {
|
||||
console.log(error.toString());
|
||||
@@ -307,7 +350,7 @@ function getCachedMaterial(doc: Document, matDef: GLTFMaterial): Material {
|
||||
}
|
||||
|
||||
function addMesh(
|
||||
doc: Document, node: Node, manifold: Manifold,
|
||||
doc: Document, to3mf: To3MF, node: Node, manifold: Manifold,
|
||||
backupMaterial: GLTFMaterial = {}) {
|
||||
const numTri = manifold.numTri();
|
||||
if (numTri == 0) {
|
||||
@@ -344,6 +387,20 @@ 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;
|
||||
@@ -388,17 +445,18 @@ function cloneNodeNewMaterial(
|
||||
}
|
||||
|
||||
function createNodeFromCache(
|
||||
doc: Document, nodeDef: GLTFNode,
|
||||
doc: Document, to3MF: To3MF, nodeDef: GLTFNode,
|
||||
manifold2node: Map<Manifold, Map<GLTFMaterial, Node>>): Node {
|
||||
const node = createGLTFnode(doc, nodeDef);
|
||||
if (nodeDef.manifold != null) {
|
||||
const {manifold} = nodeDef;
|
||||
if (manifold != null) {
|
||||
const backupMaterial = getBackupMaterial(nodeDef);
|
||||
const cachedNodes = manifold2node.get(nodeDef.manifold);
|
||||
const cachedNodes = manifold2node.get(manifold);
|
||||
if (cachedNodes == null) {
|
||||
addMesh(doc, node, nodeDef.manifold, backupMaterial);
|
||||
addMesh(doc, to3MF, node, manifold, backupMaterial);
|
||||
const cache = new Map<GLTFMaterial, Node>();
|
||||
cache.set(backupMaterial, node);
|
||||
manifold2node.set(nodeDef.manifold, cache);
|
||||
manifold2node.set(manifold, cache);
|
||||
} else {
|
||||
const cachedNode = cachedNodes.get(backupMaterial);
|
||||
if (cachedNode == null) {
|
||||
@@ -412,10 +470,18 @@ 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 exportGLB(manifold?: Manifold) {
|
||||
async function exportModels(manifold?: Manifold) {
|
||||
const doc = new Document();
|
||||
const halfRoot2 = Math.sqrt(2) / 2;
|
||||
const mm2m = 1 / 1000;
|
||||
@@ -424,13 +490,27 @@ async function exportGLB(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, nodeDef, manifold2node));
|
||||
node2gltf.set(
|
||||
nodeDef, createNodeFromCache(doc, to3mf, nodeDef, manifold2node));
|
||||
if (nodeDef.manifold) {
|
||||
++leafNodes;
|
||||
}
|
||||
@@ -438,10 +518,19 @@ async function exportGLB(manifold?: Manifold) {
|
||||
|
||||
for (const nodeDef of nodes) {
|
||||
const gltfNode = node2gltf.get(nodeDef)!;
|
||||
if (nodeDef.parent == null) {
|
||||
const child = {
|
||||
objectID: `${object2globalID.get(nodeDef)}`,
|
||||
transform: gltfNode.getMatrix()
|
||||
};
|
||||
const {parent} = nodeDef;
|
||||
if (parent == null) {
|
||||
wrapper.addChild(gltfNode);
|
||||
to3mf.items.push(child);
|
||||
} else {
|
||||
node2gltf.get(nodeDef.parent)!.addChild(gltfNode);
|
||||
node2gltf.get(parent)!.addChild(gltfNode);
|
||||
const parent3mf = to3mf.components.find(
|
||||
(comp) => comp.id == `${object2globalID.get(parent)}`)!;
|
||||
parent3mf.children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,12 +542,25 @@ async function exportGLB(manifold?: Manifold) {
|
||||
return;
|
||||
}
|
||||
const node = doc.createNode();
|
||||
addMesh(doc, node, manifold);
|
||||
addMesh(doc, to3mf, 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 blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
self.postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
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)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <manifold.h>
|
||||
#include <polygon.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace manifold;
|
||||
@@ -159,6 +159,11 @@ 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 {
|
||||
@@ -219,4 +224,9 @@ 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
|
||||
|
||||
+28
-5
@@ -111,6 +111,7 @@ 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
|
||||
@@ -173,11 +174,6 @@ 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
|
||||
@@ -246,6 +242,18 @@ 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
|
||||
|
||||
/**
|
||||
@@ -507,6 +515,7 @@ 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,
|
||||
@@ -519,6 +528,7 @@ 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
|
||||
@@ -675,6 +685,19 @@ 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
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,6 +24,13 @@ 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)
|
||||
@@ -35,7 +42,6 @@ if(BUILD_TEST_CGAL)
|
||||
add_executable(perfTestCGAL perf_test_cgal.cpp)
|
||||
find_package(CGAL REQUIRED COMPONENTS Core)
|
||||
target_compile_definitions(perfTestCGAL PRIVATE CGAL_USE_GMPXX)
|
||||
# target_compile_definitions(perfTestCGAL PRIVATE CGAL_DEBUG)
|
||||
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
#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;
|
||||
}
|
||||
@@ -57,10 +57,9 @@ void manifoldToCGALSurfaceMesh(Manifold &manifold, TriangleMesh &cgalMesh) {
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
Manifold sphere = Manifold::Sphere(1, (8 << i) * 4);
|
||||
Manifold sphere2 = sphere;
|
||||
sphere2.Translate(glm::vec3(0.5));
|
||||
Manifold sphere2 = sphere.Translate(glm::vec3(0.5));
|
||||
|
||||
TriangleMesh cgalSphere, cgalSphere2, cgalOut;
|
||||
TriangleMesh cgalSphere, cgalSphere2;
|
||||
manifoldToCGALSurfaceMesh(sphere, cgalSphere);
|
||||
manifoldToCGALSurfaceMesh(sphere2, cgalSphere2);
|
||||
|
||||
|
||||
Generated
+42
-6
@@ -1,12 +1,15 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1649676176,
|
||||
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -15,13 +18,30 @@
|
||||
"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": 1689008574,
|
||||
"narHash": "sha256-VFMgyHDiqsGDkRg73alv6OdHJAqhybryWHv77bSCGIw=",
|
||||
"lastModified": 1692734709,
|
||||
"narHash": "sha256-SCFnyHCyYjwEmgUsHDDuU0TsbVMKeU1vwkR+r7uS2Rg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "4a729ce4b1fe5ec4fffc71c67c96aa5184ebb462",
|
||||
"rev": "b85ed9dcbf187b909ef7964774f8847d554fab3b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -33,8 +53,24 @@
|
||||
"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",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
inputs.gtest-src = {
|
||||
url = "github:google/googletest/v1.14.0";
|
||||
flake = false;
|
||||
};
|
||||
outputs = { self, nixpkgs, flake-utils, gtest-src }:
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let
|
||||
@@ -10,27 +13,61 @@
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
thrust-210 = pkgs.nvidia-thrust.overrideAttrs (_: _: {
|
||||
version = "24486a1";
|
||||
src = builtins.fetchGit {
|
||||
url = "https://github.com/NVIDIA/thrust.git";
|
||||
ref = "main";
|
||||
rev = "24486a169a62a58ef8f824d3dc9613c006b6f5a7";
|
||||
submodules = true;
|
||||
};
|
||||
cmakeFlags = [ "-DTHRUST_ENABLE_HEADER_TESTING=OFF" "-DTHRUST_ENABLE_TESTING=OFF" "-DTHRUST_ENABLE_EXAMPLES=OFF" "-DTHRUST_DEVICE_SYSTEM=CPP" ];
|
||||
fixupPhase = ''
|
||||
cat <<EOT > $out/lib/cmake/thrust/thrust-header-search.cmake
|
||||
# Parse version information from version.h in source tree
|
||||
set(_THRUST_VERSION_INCLUDE_DIR "$out/include")
|
||||
if(EXISTS "\''${_THRUST_VERSION_INCLUDE_DIR}/thrust/version.h")
|
||||
set(_THRUST_VERSION_INCLUDE_DIR "\''${_THRUST_VERSION_INCLUDE_DIR}" CACHE FILEPATH "" FORCE) # Clear old result
|
||||
set_property(CACHE _THRUST_VERSION_INCLUDE_DIR PROPERTY TYPE INTERNAL)
|
||||
endif()
|
||||
EOT
|
||||
cat <<EOT > $out/lib/cmake/libcudacxx/libcudacxx-header-search.cmake
|
||||
# Parse version information from version header:
|
||||
unset(_libcudacxx_VERSION_INCLUDE_DIR CACHE) # Clear old result to force search
|
||||
find_path(_libcudacxx_VERSION_INCLUDE_DIR cuda/std/detail/__config
|
||||
NO_DEFAULT_PATH # Only search explicit paths below:
|
||||
PATHS
|
||||
"\''${CMAKE_CURRENT_LIST_DIR}/../../../include" # Source tree
|
||||
)
|
||||
set_property(CACHE _libcudacxx_VERSION_INCLUDE_DIR PROPERTY TYPE INTERNAL)
|
||||
EOT
|
||||
'';
|
||||
enableParallelBuilding = false;
|
||||
});
|
||||
manifold =
|
||||
{ parallel-backend ? "none"
|
||||
, cuda-support ? false
|
||||
, doCheck ? true
|
||||
, build-tools ? [ ]
|
||||
, ...
|
||||
}: pkgs.stdenv.mkDerivation {
|
||||
inherit doCheck;
|
||||
pname =
|
||||
if cuda-support then
|
||||
"manifold-${parallel-backend}-cuda"
|
||||
else
|
||||
"manifold-${parallel-backend}";
|
||||
version = "beta";
|
||||
pname = "manifold-${parallel-backend}";
|
||||
version = "2.2.0";
|
||||
src = self;
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages (ps: with ps; [ trimesh ])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
nativeBuildInputs = (with pkgs; [
|
||||
cmake
|
||||
thrust-210
|
||||
glm
|
||||
ninja
|
||||
(python39.withPackages
|
||||
(ps: with ps; [ trimesh ]))
|
||||
gtest
|
||||
]) ++ build-tools;
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PYBIND=ON"
|
||||
"-DMANIFOLD_CBIND=ON"
|
||||
"-DBUILD_SHARED_LIBS=ON"
|
||||
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
|
||||
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
|
||||
];
|
||||
checkPhase = ''
|
||||
cd test
|
||||
@@ -39,42 +76,20 @@
|
||||
PYTHONPATH=$PYTHONPATH:$(pwd)/build/bindings/python python3 bindings/python/examples/run_all.py
|
||||
cd build
|
||||
'';
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp src/manifold/libmanifold.a $out/
|
||||
cp extras/perfTest $out
|
||||
cp bindings/python/manifold3d* $out
|
||||
'';
|
||||
};
|
||||
parallelBackends = [
|
||||
{ parallel-backend = "none"; }
|
||||
{
|
||||
parallel-backend = "omp";
|
||||
build-tools = [ pkgs.llvmPackages_13.openmp ];
|
||||
}
|
||||
{
|
||||
parallel-backend = "tbb";
|
||||
build-tools = with pkgs; [ tbb pkg-config ];
|
||||
build-tools = with pkgs; [ tbb_2021_8 pkg-config ];
|
||||
}
|
||||
];
|
||||
buildMatrix = with pkgs; with lib; lists.flatten (map
|
||||
(env: map
|
||||
(x: x // env)
|
||||
parallelBackends) [
|
||||
{ cuda-support = false; }
|
||||
{
|
||||
cuda-support = true;
|
||||
}
|
||||
]);
|
||||
devShell = { additional ? [ ] }: pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
cmake
|
||||
llvmPackages_13.openmp
|
||||
clang-tools
|
||||
clang_13
|
||||
emscripten
|
||||
tbb
|
||||
lcov
|
||||
tbb_2021_8
|
||||
thrust-210
|
||||
gtest
|
||||
] ++ additional;
|
||||
};
|
||||
in
|
||||
@@ -82,11 +97,10 @@
|
||||
packages = (builtins.listToAttrs
|
||||
(map
|
||||
(x: {
|
||||
name = "manifold-" + x.parallel-backend + (if
|
||||
x.cuda-support then "-cuda" else "");
|
||||
name = "manifold-" + x.parallel-backend;
|
||||
value = manifold x;
|
||||
})
|
||||
buildMatrix)) // {
|
||||
parallelBackends)) // {
|
||||
manifold-js = pkgs.buildEmscriptenPackage {
|
||||
name = "manifold-js";
|
||||
version = "beta";
|
||||
@@ -98,9 +112,10 @@
|
||||
export EM_CACHE=$(pwd)/.emscriptencache
|
||||
mkdir build
|
||||
cd build
|
||||
mkdir cache
|
||||
export EM_CACHE=$(pwd)/cache
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||
emcmake cmake -DCMAKE_BUILD_TYPE=Release \
|
||||
-DFETCHCONTENT_SOURCE_DIR_GLM=${pkgs.glm.src} \
|
||||
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=${gtest-src} \
|
||||
-DFETCHCONTENT_SOURCE_DIR_THRUST=${thrust-210.src} ..
|
||||
'';
|
||||
buildPhase = ''
|
||||
emmake make -j''${NIX_BUILD_CORES}
|
||||
@@ -116,15 +131,40 @@
|
||||
cp {extras,wasm}/*.wasm $out/
|
||||
'';
|
||||
};
|
||||
# but how should we make it work with other python versions?
|
||||
manifold3d = with pkgs.python3Packages; buildPythonPackage {
|
||||
pname = "manifold3d";
|
||||
version = "2.2.0";
|
||||
src = self;
|
||||
propagatedBuildInputs = [
|
||||
numpy
|
||||
];
|
||||
buildInputs = with pkgs; [
|
||||
tbb_2021_8
|
||||
thrust-210
|
||||
glm
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cmake
|
||||
ninja
|
||||
setuptools
|
||||
scikit-build-core
|
||||
pyproject-metadata
|
||||
pathspec
|
||||
pkg-config
|
||||
];
|
||||
checkInputs = [
|
||||
trimesh
|
||||
];
|
||||
format = "pyproject";
|
||||
dontUseCmakeConfigure = true;
|
||||
doCheck = true;
|
||||
checkPhase = ''
|
||||
python3 bindings/python/examples/run_all.py
|
||||
'';
|
||||
};
|
||||
};
|
||||
devShell = devShell { };
|
||||
devShells.cuda = devShell {
|
||||
additional = with pkgs.cudaPackages; [
|
||||
cuda_nvcc
|
||||
cuda_cudart
|
||||
cuda_cccl
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,3 +29,14 @@ target_link_libraries(${PROJECT_NAME}
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT devel
|
||||
)
|
||||
install(
|
||||
DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
[project]
|
||||
name = "manifold3d"
|
||||
version = "2.2.0"
|
||||
authors = [
|
||||
{ name="Emmett Lalish", email="elalish@gmail.com" },
|
||||
]
|
||||
description = "Library for geometric robustness"
|
||||
readme = "README.md"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: C++",
|
||||
"Topic :: Multimedia :: Graphics :: 3D Modeling",
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
|
||||
dependencies = [
|
||||
"numpy; python_version<'3.12'",
|
||||
"numpy>=1.26.0b1; python_version>='3.12'"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/elalish/manifold"
|
||||
"Bug Tracker" = "https://github.com/elalish/manifold/issues"
|
||||
|
||||
[build-system]
|
||||
requires = ["scikit-build-core"]
|
||||
build-backend = "scikit_build_core.build"
|
||||
|
||||
[tool.scikit-build]
|
||||
cmake.minimum-version = "3.18"
|
||||
sdist.exclude = [
|
||||
".github",
|
||||
"bindings/c",
|
||||
"bindings/wasm",
|
||||
"docs",
|
||||
"extras",
|
||||
"meshIO",
|
||||
"samples",
|
||||
"test",
|
||||
"oneTBB", # we may have this when we build with cibuildwheel
|
||||
"bindings/python/third_party/nanobind/tests/",
|
||||
"bindings/python/third_party/nanobind/docs/",
|
||||
"src/third_party/clipper2/CPP/Examples/",
|
||||
"src/third_party/clipper2/CPP/Tests/",
|
||||
"src/third_party/clipper2/CSharp/",
|
||||
"src/third_party/clipper2/Delphi/",
|
||||
"src/third_party/clipper2/DLL/",
|
||||
"src/third_party/clipper2/Tests/",
|
||||
]
|
||||
wheel.packages = ["manifold3d"]
|
||||
cmake.args = ["-DMANIFOLD_PAR=TBB", "-DMANIFOLD_TEST=OFF"]
|
||||
install.components = ["bindings"]
|
||||
|
||||
[tool.cibuildwheel]
|
||||
build-frontend = "build"
|
||||
test-requires = ["trimesh"]
|
||||
test-command = "python {project}/bindings/python/examples/run_all.py"
|
||||
# Setuptools bug causes collision between pypy and cpython artifacts
|
||||
manylinux-x86_64-image = "manylinux_2_28"
|
||||
musllinux-x86_64-image = "musllinux_1_2"
|
||||
skip = ["*-win32", "*_i686", "pp*", "*-musllinux*"]
|
||||
|
||||
# only clone TBB once
|
||||
before-all = "git clone --depth 1 --branch v2021.10.0 https://github.com/oneapi-src/oneTBB.git"
|
||||
[tool.cibuildwheel.config-settings]
|
||||
"cmake.define.FETCHCONTENT_SOURCE_DIR_TBB" = "./oneTBB"
|
||||
"cmake.define.FETCHCONTENT_UPDATES_DISCONNECTED" = "ON"
|
||||
|
||||
[tool.cibuildwheel.macos]
|
||||
archs = ["x86_64", "arm64"]
|
||||
environment = "MACOSX_DEPLOYMENT_TARGET=10.14"
|
||||
test-skip = "*-macosx_arm64 *-macosx_universal2:arm64"
|
||||
|
||||
[tool.cibuildwheel.windows]
|
||||
before-build = "pip install delvewheel"
|
||||
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel}"
|
||||
+4
-24
@@ -14,37 +14,17 @@
|
||||
|
||||
project(samples)
|
||||
|
||||
add_library(samples src/bracelet.cpp src/knot.cpp src/menger_sponge.cpp src/rounded_frame.cpp src/scallop.cpp src/tet_puzzle.cpp)
|
||||
add_library(samples src/bracelet.cpp src/knot.cpp src/menger_sponge.cpp
|
||||
src/rounded_frame.cpp src/scallop.cpp src/tet_puzzle.cpp
|
||||
src/gyroid_module.cpp src/condensed_matter.cpp)
|
||||
|
||||
target_include_directories(samples
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(samples
|
||||
PUBLIC manifold
|
||||
PUBLIC manifold sdf
|
||||
)
|
||||
|
||||
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samples PUBLIC cxx_std_17)
|
||||
|
||||
project(samplesGPU)
|
||||
|
||||
add_library(samplesGPU src/gyroid_module.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(src/gyroid_module.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET samplesGPU PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_include_directories(samplesGPU
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(samplesGPU
|
||||
PUBLIC manifold sdf
|
||||
)
|
||||
|
||||
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samplesGPU
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
@@ -51,5 +51,7 @@ Manifold TetPuzzle(float edgeLength, float gap, int nDivisions);
|
||||
Manifold Scallop();
|
||||
|
||||
Manifold GyroidModule(float size = 20, int n = 20);
|
||||
|
||||
Manifold CondensedMatter(int fn = 16);
|
||||
/** @} */ // end of Samples
|
||||
} // namespace manifold
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// From
|
||||
// https://gist.github.com/ochafik/70a6b15e982b7ccd5a79ff9afd99dbcf#file-condensed-matter-scad
|
||||
|
||||
#include "samples.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
using namespace glm;
|
||||
|
||||
constexpr float AtomicRadiusN2 = 0.65;
|
||||
constexpr float BondPairN2 = 1.197;
|
||||
constexpr float AtomicRadiusSi = 1.1;
|
||||
constexpr float LatticeCellSizeSi = 5.4309;
|
||||
constexpr float fccOffset = 0.25;
|
||||
constexpr float AtomicRadiusC = 0.7;
|
||||
constexpr float LatticeCellSizeC = 3.65;
|
||||
constexpr float cellLenA = 2.464;
|
||||
constexpr float cellLenB = cellLenA;
|
||||
constexpr float cellLenC = 6.711;
|
||||
constexpr float cellAngleA = 90;
|
||||
constexpr float cellAngleB = cellAngleA;
|
||||
constexpr float cellAngleC = 120;
|
||||
constexpr float LayerSeperationC = 3.364;
|
||||
|
||||
Manifold bond(int fn, vec3 p1 = {0, 0, 0}, vec3 p2 = {1, 1, 1}, float ar1 = 1.0,
|
||||
float ar2 = 2.0) {
|
||||
float cyR = std::min(ar1, ar2) / 5.0;
|
||||
float dist = length(p1 - p2);
|
||||
vec3 cyC = (p1 + p2) / 2.0f;
|
||||
float beta = degrees(acos((p1.z - p2.z) / dist));
|
||||
float gamma = degrees(atan2(p1.y - p2.y, p1.x - p2.x));
|
||||
vec3 rot = {0.0, beta, gamma};
|
||||
return Manifold::Cylinder(dist, cyR, -1, fn, true)
|
||||
.Rotate(rot.x, rot.y, rot.z)
|
||||
.Translate(cyC);
|
||||
}
|
||||
|
||||
Manifold bondPair(int fn, float d = 0.0, float ar = 1.0) {
|
||||
float axD = pow(d, 1.0 / 3.0);
|
||||
vec3 p1 = {+axD, -axD, -axD};
|
||||
vec3 p2 = {-axD, +axD, +axD};
|
||||
Manifold sphere = Manifold::Sphere(ar, fn);
|
||||
return sphere.Translate(p1) + sphere.Translate(p2) + bond(fn, p1, p2, ar, ar);
|
||||
}
|
||||
|
||||
Manifold hexagonalClosePacked(int fn, vec3 dst = {1.0, 1.0, 1.0},
|
||||
float ar = 1.0) {
|
||||
std::vector<Manifold> parts;
|
||||
vec3 p1 = {0, 0, 0};
|
||||
parts.push_back(Manifold::Sphere(ar, fn));
|
||||
float baseAg = 30;
|
||||
vec3 ag = {baseAg, baseAg + 120, baseAg + 240};
|
||||
vec3 points[] = {{cosd(ag.x) * dst.x, sind(ag.x) * dst.x, 0},
|
||||
{cosd(ag.y) * dst.y, sind(ag.y) * dst.y, 0},
|
||||
{cosd(ag.z) * dst.z, sind(ag.z) * dst.z, 0}};
|
||||
for (vec3 p2 : points) {
|
||||
parts.push_back(Manifold::Sphere(ar, fn).Translate(p2));
|
||||
parts.push_back(bond(fn, p1, p2, ar, ar));
|
||||
}
|
||||
return Manifold::BatchBoolean(parts, OpType::Add);
|
||||
}
|
||||
|
||||
Manifold fccDiamond(int fn, float ar = 1.0, float unitCell = 2.0,
|
||||
float fccOffset = 0.25) {
|
||||
std::vector<Manifold> parts;
|
||||
float huc = unitCell / 2.0;
|
||||
float od = fccOffset * unitCell;
|
||||
vec3 interstitial[] = {
|
||||
{+od, +od, +od}, {+od, -od, -od}, {-od, +od, -od}, {-od, -od, +od}};
|
||||
vec3 corners[] = {{+huc, +huc, +huc},
|
||||
{+huc, -huc, -huc},
|
||||
{-huc, +huc, -huc},
|
||||
{-huc, -huc, +huc}};
|
||||
vec3 fcc[] = {{+huc, 0, 0}, {-huc, 0, 0}, {0, +huc, 0},
|
||||
{0, -huc, 0}, {0, 0, +huc}, {0, 0, -huc}};
|
||||
for (auto p : corners) parts.push_back(Manifold::Sphere(ar, fn).Translate(p));
|
||||
|
||||
for (auto p : fcc) parts.push_back(Manifold::Sphere(ar, fn).Translate(p));
|
||||
for (auto p : interstitial)
|
||||
parts.push_back(Manifold::Sphere(ar, fn).Translate(p));
|
||||
|
||||
vec3 bonds[][2] = {{interstitial[0], corners[0]}, {interstitial[0], fcc[0]},
|
||||
{interstitial[0], fcc[2]}, {interstitial[0], fcc[4]},
|
||||
{interstitial[1], corners[1]}, {interstitial[1], fcc[0]},
|
||||
{interstitial[1], fcc[3]}, {interstitial[1], fcc[5]},
|
||||
{interstitial[2], corners[2]}, {interstitial[2], fcc[1]},
|
||||
{interstitial[2], fcc[2]}, {interstitial[2], fcc[5]},
|
||||
{interstitial[3], corners[3]}, {interstitial[3], fcc[1]},
|
||||
{interstitial[3], fcc[3]}, {interstitial[3], fcc[4]}};
|
||||
for (auto b : bonds) parts.push_back(bond(fn, b[0], b[1], ar, ar));
|
||||
|
||||
return Manifold::BatchBoolean(parts, OpType::Add);
|
||||
}
|
||||
|
||||
Manifold SiCell(int fn, float x = 1.0, float y = 1.0, float z = 1.0) {
|
||||
return fccDiamond(fn, AtomicRadiusSi, LatticeCellSizeSi, fccOffset)
|
||||
.Translate({LatticeCellSizeSi * x, LatticeCellSizeSi * y,
|
||||
LatticeCellSizeSi * z});
|
||||
}
|
||||
|
||||
Manifold SiN2Cell(int fn, float x = 1.0, float y = 1.0, float z = 1.0) {
|
||||
float n2Offset = LatticeCellSizeSi / 8;
|
||||
return bondPair(fn, BondPairN2, AtomicRadiusN2)
|
||||
.Translate({LatticeCellSizeSi * x - n2Offset,
|
||||
LatticeCellSizeSi * y + n2Offset,
|
||||
LatticeCellSizeSi * z + n2Offset}) +
|
||||
SiCell(fn, x, y, z);
|
||||
}
|
||||
|
||||
Manifold GraphiteCell(int fn, vec3 xyz = {1.0, 1.0, 1.0}) {
|
||||
vec3 loc = {(cellLenA * xyz.x * cosd(30) * 2),
|
||||
((cellLenB * sind(30)) + cellLenC) * xyz.y, xyz.z};
|
||||
return hexagonalClosePacked(fn, {cellLenA, cellLenB, cellLenC}, AtomicRadiusC)
|
||||
.Translate(loc);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
Manifold CondensedMatter(int fn) {
|
||||
std::vector<Manifold> parts;
|
||||
float siOffset = 3.0 * LatticeCellSizeSi / 8.0;
|
||||
for (int x = -3; x <= 3; x++)
|
||||
for (int y = -1; y <= 2; y++)
|
||||
parts.push_back(
|
||||
GraphiteCell(fn, {x + (y % 2 == 0 ? 0.0 : 0.5), y,
|
||||
LayerSeperationC * 0.5 + LatticeCellSizeSi * 1.5})
|
||||
.Translate({0, -siOffset, 0})
|
||||
.Rotate(0, 0, 45));
|
||||
|
||||
float xyPlane[] = {-2, -1, 0, +1, +2};
|
||||
for (float x : xyPlane)
|
||||
for (float y : xyPlane) parts.push_back(SiN2Cell(fn, x, y, 1));
|
||||
return Manifold::BatchBoolean(parts, OpType::Add);
|
||||
}
|
||||
} // namespace manifold
|
||||
@@ -12,13 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold.h"
|
||||
#include "samples.h"
|
||||
#include "sdf.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
struct Gyroid {
|
||||
__host__ __device__ float operator()(glm::vec3 p) const {
|
||||
float operator()(glm::vec3 p) const {
|
||||
p -= glm::pi<float>() / 4;
|
||||
return cos(p.x) * sin(p.y) + cos(p.y) * sin(p.z) + cos(p.z) * sin(p.x);
|
||||
}
|
||||
@@ -54,4 +56,4 @@ Manifold GyroidModule(float size, int n) {
|
||||
|
||||
return result.Rotate(-45, 0, 90).Translate({0, 0, size / glm::sqrt(2.0f)});
|
||||
}
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
+2
-1
@@ -12,7 +12,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
add_subdirectory(third_party)
|
||||
# do not add third_party installations
|
||||
add_subdirectory(third_party EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(utilities)
|
||||
add_subdirectory(collider)
|
||||
add_subdirectory(cross_section)
|
||||
|
||||
@@ -15,16 +15,18 @@
|
||||
project (collider)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
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()
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT runtime
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#pragma once
|
||||
#include "public.h"
|
||||
#include "sparse.h"
|
||||
#include "vec_dh.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
@@ -23,17 +23,19 @@ namespace manifold {
|
||||
class Collider {
|
||||
public:
|
||||
Collider() {}
|
||||
Collider(const VecDH<Box>& leafBB, const VecDH<uint32_t>& leafMorton);
|
||||
Collider(const VecView<const Box>& leafBB,
|
||||
const VecView<const uint32_t>& leafMorton);
|
||||
bool Transform(glm::mat4x3);
|
||||
void UpdateBoxes(const VecDH<Box>& leafBB);
|
||||
template <const bool selfCollision = false, typename T>
|
||||
SparseIndices Collisions(const VecDH<T>& queriesIn) const;
|
||||
void UpdateBoxes(const VecView<const Box>& leafBB);
|
||||
template <const bool selfCollision = false, const bool inverted = false,
|
||||
typename T>
|
||||
SparseIndices Collisions(const VecView<const T>& queriesIn) const;
|
||||
|
||||
private:
|
||||
VecDH<Box> nodeBBox_;
|
||||
VecDH<int> nodeParent_;
|
||||
Vec<Box> nodeBBox_;
|
||||
Vec<int> nodeParent_;
|
||||
// even nodes are leaves, odd nodes are internal, root is 1
|
||||
VecDH<thrust::pair<int, int>> internalChildren_;
|
||||
Vec<thrust::pair<int, int>> internalChildren_;
|
||||
|
||||
int NumInternal() const { return internalChildren_.size(); };
|
||||
int NumLeaves() const { return NumInternal() + 1; };
|
||||
|
||||
+123
-80
@@ -24,6 +24,7 @@
|
||||
// Adjustable parameters
|
||||
constexpr int kInitialLength = 128;
|
||||
constexpr int kLengthMultiple = 4;
|
||||
constexpr int kSequentialThreshold = 512;
|
||||
// Fundamental constants
|
||||
constexpr int kRoot = 1;
|
||||
|
||||
@@ -59,36 +60,30 @@ uint32_t __inline clz(uint32_t value) {
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
__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; }
|
||||
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; }
|
||||
|
||||
struct CreateRadixTree {
|
||||
int* nodeParent_;
|
||||
thrust::pair<int, int>* internalChildren_;
|
||||
const VecDc<uint32_t> leafMorton_;
|
||||
VecView<int> nodeParent_;
|
||||
VecView<thrust::pair<int, int>> internalChildren_;
|
||||
const VecView<const uint32_t> leafMorton_;
|
||||
|
||||
__host__ __device__ int PrefixLength(uint32_t a, uint32_t b) const {
|
||||
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
|
||||
}
|
||||
|
||||
__host__ __device__ int PrefixLength(int i, int j) const {
|
||||
int PrefixLength(int i, int j) const {
|
||||
if (j < 0 || j >= leafMorton_.size()) {
|
||||
return -1;
|
||||
} else {
|
||||
@@ -103,7 +98,7 @@ struct CreateRadixTree {
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ int RangeEnd(int i) const {
|
||||
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);
|
||||
@@ -121,7 +116,7 @@ struct CreateRadixTree {
|
||||
return i + dir * length;
|
||||
}
|
||||
|
||||
__host__ __device__ int FindSplit(int first, int last) const {
|
||||
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.
|
||||
@@ -138,7 +133,7 @@ struct CreateRadixTree {
|
||||
return split;
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(int internal) {
|
||||
void operator()(int internal) {
|
||||
int first = internal;
|
||||
// Find the range of objects with a common prefix
|
||||
int last = RangeEnd(first);
|
||||
@@ -157,36 +152,27 @@ struct CreateRadixTree {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, const bool allocateOnly, const bool selfCollision>
|
||||
template <typename T, const bool selfCollision, typename Recorder>
|
||||
struct FindCollisions {
|
||||
thrust::pair<int*, int*> queryTri_;
|
||||
int* counts;
|
||||
const Box* nodeBBox_;
|
||||
const thrust::pair<int, int>* internalChildren_;
|
||||
VecView<const Box> nodeBBox_;
|
||||
VecView<const thrust::pair<int, int>> internalChildren_;
|
||||
Recorder recorder;
|
||||
|
||||
__host__ __device__ int RecordCollision(int node,
|
||||
thrust::tuple<T, int>& query) {
|
||||
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)) {
|
||||
const int leafIdx = Node2Leaf(node);
|
||||
int leafIdx = Node2Leaf(node);
|
||||
if (!selfCollision || leafIdx != queryIdx) {
|
||||
if (allocateOnly) {
|
||||
count++;
|
||||
} else {
|
||||
int pos = count++;
|
||||
queryTri_.first[pos] = queryIdx;
|
||||
queryTri_.second[pos] = leafIdx;
|
||||
}
|
||||
recorder.record(queryIdx, leafIdx);
|
||||
}
|
||||
}
|
||||
return overlaps && IsInternal(node); // Should traverse into node
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<T, int> query) {
|
||||
void operator()(thrust::tuple<T, int> query) {
|
||||
// stack cannot overflow because radix tree has max depth 30 (Morton code) +
|
||||
// 32 (index).
|
||||
int stack[64];
|
||||
@@ -195,7 +181,7 @@ struct FindCollisions {
|
||||
int node = kRoot;
|
||||
const int queryIdx = thrust::get<1>(query);
|
||||
// same implies that this query do not have any collision
|
||||
if (!allocateOnly && counts[queryIdx] == counts[queryIdx + 1]) return;
|
||||
if (recorder.earlyexit(queryIdx)) return;
|
||||
while (1) {
|
||||
int internal = Node2Internal(node);
|
||||
int child1 = internalChildren_[internal].first;
|
||||
@@ -214,16 +200,56 @@ struct FindCollisions {
|
||||
}
|
||||
}
|
||||
}
|
||||
recorder.end(queryIdx);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildInternalBoxes {
|
||||
Box* nodeBBox_;
|
||||
int* counter_;
|
||||
const int* nodeParent_;
|
||||
const thrust::pair<int, int>* internalChildren_;
|
||||
struct CountCollisions {
|
||||
VecView<int> counts;
|
||||
VecView<char> empty;
|
||||
void record(int queryIdx, int _leafIdx) { counts[queryIdx]++; }
|
||||
bool earlyexit(int _queryIdx) { return false; }
|
||||
void end(int queryIdx) {
|
||||
if (counts[queryIdx] == 0) empty[queryIdx] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
__host__ __device__ void operator()(int leaf) {
|
||||
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;
|
||||
VecView<int> counts;
|
||||
VecView<char> empty;
|
||||
void record(int queryIdx, int leafIdx) {
|
||||
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 {
|
||||
VecView<Box> nodeBBox_;
|
||||
VecView<int> counter_;
|
||||
const VecView<int> nodeParent_;
|
||||
const VecView<thrust::pair<int, int>> internalChildren_;
|
||||
|
||||
void operator()(int leaf) {
|
||||
int node = Leaf2Node(leaf);
|
||||
do {
|
||||
node = nodeParent_[node];
|
||||
@@ -237,9 +263,7 @@ struct BuildInternalBoxes {
|
||||
|
||||
struct TransformBox {
|
||||
const glm::mat4x3 transform;
|
||||
__host__ __device__ void operator()(Box& box) {
|
||||
box = box.Transform(transform);
|
||||
}
|
||||
void operator()(Box& box) { box = box.Transform(transform); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -250,8 +274,8 @@ namespace manifold {
|
||||
* bounding boxes and corresponding Morton codes. It is assumed these vectors
|
||||
* are already sorted by increasing Morton code.
|
||||
*/
|
||||
Collider::Collider(const VecDH<Box>& leafBB,
|
||||
const VecDH<uint32_t>& leafMorton) {
|
||||
Collider::Collider(const VecView<const Box>& leafBB,
|
||||
const VecView<const uint32_t>& leafMorton) {
|
||||
ASSERT(leafBB.size() == leafMorton.size(), userErr,
|
||||
"vectors must be the same length");
|
||||
int num_nodes = 2 * leafBB.size() - 1;
|
||||
@@ -261,8 +285,7 @@ Collider::Collider(const VecDH<Box>& leafBB,
|
||||
internalChildren_.resize(leafBB.size() - 1, thrust::make_pair(-1, -1));
|
||||
// organize tree
|
||||
for_each_n(autoPolicy(NumInternal()), countAt(0), NumInternal(),
|
||||
CreateRadixTree(
|
||||
{nodeParent_.ptrD(), internalChildren_.ptrD(), leafMorton}));
|
||||
CreateRadixTree({nodeParent_, internalChildren_, leafMorton}));
|
||||
UpdateBoxes(leafBB);
|
||||
}
|
||||
|
||||
@@ -274,47 +297,57 @@ 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, typename T>
|
||||
SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
|
||||
template <const bool selfCollision, const bool inverted, typename T>
|
||||
SparseIndices Collider::Collisions(const VecView<const T>& queriesIn) const {
|
||||
// note that the length is 1 larger than the number of queries so the last
|
||||
// element can store the sum when using exclusive scan
|
||||
VecDH<int> counts(queriesIn.size() + 1, 0);
|
||||
auto policy = autoPolicy(queriesIn.size());
|
||||
// compute the number of collisions to determine the size for allocation and
|
||||
// offset, this avoids the need for atomic
|
||||
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
|
||||
FindCollisions<T, true, 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;
|
||||
if (queriesIn.size() < kSequentialThreshold) {
|
||||
SparseIndices queryTri;
|
||||
for_each_n(ExecutionPolicy::Seq, zip(queriesIn.cbegin(), countAt(0)),
|
||||
queriesIn.size(),
|
||||
FindCollisions<T, selfCollision, SeqCollisionRecorder<inverted>>{
|
||||
nodeBBox_, internalChildren_, {queryTri}});
|
||||
return queryTri;
|
||||
} else {
|
||||
// compute the number of collisions to determine the size for allocation and
|
||||
// offset, this avoids the need for atomic
|
||||
Vec<int> counts(queriesIn.size() + 1, 0);
|
||||
Vec<char> empty(queriesIn.size(), 0);
|
||||
for_each_n(ExecutionPolicy::Par, zip(queriesIn.cbegin(), countAt(0)),
|
||||
queriesIn.size(),
|
||||
FindCollisions<T, selfCollision, CountCollisions>{
|
||||
nodeBBox_, internalChildren_, {counts, empty}});
|
||||
// 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_, internalChildren_, {queryTri, counts, empty}});
|
||||
return queryTri;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate the collider's internal bounding boxes without changing the
|
||||
* hierarchy.
|
||||
*/
|
||||
void Collider::UpdateBoxes(const VecDH<Box>& leafBB) {
|
||||
void Collider::UpdateBoxes(const VecView<const Box>& leafBB) {
|
||||
ASSERT(leafBB.size() == NumLeaves(), userErr,
|
||||
"must have the same number of updated boxes as original");
|
||||
// copy in leaf node Boxes
|
||||
strided_range<VecDH<Box>::Iter> leaves(nodeBBox_.begin(), nodeBBox_.end(), 2);
|
||||
strided_range<Vec<Box>::Iter> leaves(nodeBBox_.begin(), nodeBBox_.end(), 2);
|
||||
auto policy = autoPolicy(NumInternal());
|
||||
copy(policy, leafBB.cbegin(), leafBB.cend(), leaves.begin());
|
||||
// create global counters
|
||||
VecDH<int> counter(NumInternal(), 0);
|
||||
Vec<int> counter(NumInternal(), 0);
|
||||
// kernel over leaves to save internal Boxes
|
||||
for_each_n(
|
||||
policy, countAt(0), NumLeaves(),
|
||||
BuildInternalBoxes({nodeBBox_.ptrD(), counter.ptrD(), nodeParent_.cptrD(),
|
||||
internalChildren_.cptrD()}));
|
||||
BuildInternalBoxes({nodeBBox_, counter, nodeParent_, internalChildren_}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,12 +370,22 @@ bool Collider::Transform(glm::mat4x3 transform) {
|
||||
return axisAligned;
|
||||
}
|
||||
|
||||
template SparseIndices Collider::Collisions<true, Box>(const VecDH<Box>&) const;
|
||||
template SparseIndices Collider::Collisions<true, false, Box>(
|
||||
const VecView<const Box>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<false, Box>(
|
||||
const VecDH<Box>&) const;
|
||||
template SparseIndices Collider::Collisions<false, false, Box>(
|
||||
const VecView<const Box>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<false, glm::vec3>(
|
||||
const VecDH<glm::vec3>&) const;
|
||||
template SparseIndices Collider::Collisions<false, false, glm::vec3>(
|
||||
const VecView<const glm::vec3>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<true, true, Box>(
|
||||
const VecView<const Box>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<false, true, Box>(
|
||||
const VecView<const Box>&) const;
|
||||
|
||||
template SparseIndices Collider::Collisions<false, true, glm::vec3>(
|
||||
const VecView<const glm::vec3>&) const;
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (cross_section)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
@@ -26,3 +26,15 @@ target_link_libraries( ${PROJECT_NAME}
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT runtime
|
||||
)
|
||||
install(
|
||||
DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
|
||||
@@ -14,27 +14,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <clipper2/clipper.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "glm/ext/matrix_float3x2.hpp"
|
||||
#include "glm/ext/vector_float2.hpp"
|
||||
#include "public.h"
|
||||
|
||||
namespace C2 = Clipper2Lib;
|
||||
|
||||
namespace manifold {
|
||||
|
||||
class Rect;
|
||||
|
||||
/** @addtogroup Core
|
||||
* @{
|
||||
*/
|
||||
|
||||
struct PathImpl;
|
||||
|
||||
/**
|
||||
* Two-dimensional cross sections guaranteed to be without self-intersections,
|
||||
* or overlaps between polygons (from construction onwards). This class makes
|
||||
@@ -169,70 +164,10 @@ class CrossSection {
|
||||
///@}
|
||||
|
||||
private:
|
||||
mutable std::shared_ptr<const C2::PathsD> paths_;
|
||||
mutable std::shared_ptr<const PathImpl> paths_;
|
||||
mutable glm::mat3x2 transform_ = glm::mat3x2(1.0f);
|
||||
CrossSection(C2::PathsD paths);
|
||||
C2::PathsD GetPaths() const;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Connections
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Axis-aligned rectangular bounds.
|
||||
*/
|
||||
class Rect {
|
||||
public:
|
||||
glm::vec2 min = glm::vec2(0);
|
||||
glm::vec2 max = glm::vec2(0);
|
||||
|
||||
/** @name Creation
|
||||
* Constructors
|
||||
*/
|
||||
///@{
|
||||
Rect();
|
||||
~Rect();
|
||||
Rect(const Rect& other);
|
||||
Rect& operator=(const Rect& other);
|
||||
Rect(Rect&&) noexcept;
|
||||
Rect& operator=(Rect&&) noexcept;
|
||||
Rect(const glm::vec2 a, const glm::vec2 b);
|
||||
///@}
|
||||
|
||||
/** @name Information
|
||||
* Details of the rectangle
|
||||
*/
|
||||
///@{
|
||||
glm::vec2 Size() const;
|
||||
float Area() const;
|
||||
float Scale() const;
|
||||
glm::vec2 Center() const;
|
||||
bool Contains(const glm::vec2& pt) const;
|
||||
bool Contains(const Rect& other) const;
|
||||
bool DoesOverlap(const Rect& other) const;
|
||||
bool IsEmpty() const;
|
||||
bool IsFinite() const;
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
*/
|
||||
///@{
|
||||
void Union(const glm::vec2 p);
|
||||
Rect Union(const Rect& other) const;
|
||||
Rect operator+(const glm::vec2 shift) const;
|
||||
Rect& operator+=(const glm::vec2 shift);
|
||||
Rect operator*(const glm::vec2 scale) const;
|
||||
Rect& operator*=(const glm::vec2 scale);
|
||||
Rect Transform(const glm::mat3x2& m) const;
|
||||
///@}
|
||||
|
||||
/** @name Conversion
|
||||
*/
|
||||
///@{
|
||||
CrossSection AsCrossSection() const;
|
||||
///@}
|
||||
CrossSection(std::shared_ptr<const PathImpl> paths);
|
||||
std::shared_ptr<const PathImpl> GetPaths() const;
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
|
||||
@@ -14,8 +14,22 @@
|
||||
|
||||
#include "cross_section.h"
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
|
||||
namespace C2 = Clipper2Lib;
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace manifold {
|
||||
struct PathImpl {
|
||||
PathImpl(const C2::PathsD paths_) : paths_(paths_) {}
|
||||
operator const C2::PathsD&() const { return paths_; }
|
||||
const C2::PathsD paths_;
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
namespace {
|
||||
const int precision_ = 8;
|
||||
|
||||
@@ -96,8 +110,8 @@ C2::PathsD transform(const C2::PathsD ps, const glm::mat3x2 m) {
|
||||
return transformed;
|
||||
}
|
||||
|
||||
std::shared_ptr<const C2::PathsD> shared_paths(const C2::PathsD& ps) {
|
||||
return std::make_shared<const C2::PathsD>(ps);
|
||||
std::shared_ptr<const PathImpl> shared_paths(const C2::PathsD& ps) {
|
||||
return std::make_shared<const PathImpl>(ps);
|
||||
}
|
||||
|
||||
// forward declaration for mutual recursion
|
||||
@@ -191,7 +205,9 @@ namespace manifold {
|
||||
/**
|
||||
* The default constructor is an empty cross-section (containing no contours).
|
||||
*/
|
||||
CrossSection::CrossSection() { paths_ = shared_paths(C2::PathsD()); }
|
||||
CrossSection::CrossSection() {
|
||||
paths_ = std::make_shared<const PathImpl>(C2::PathsD());
|
||||
}
|
||||
|
||||
CrossSection::~CrossSection() = default;
|
||||
CrossSection::CrossSection(CrossSection&&) noexcept = default;
|
||||
@@ -218,7 +234,7 @@ CrossSection& CrossSection::operator=(const CrossSection& other) {
|
||||
};
|
||||
|
||||
// Private, skips unioning.
|
||||
CrossSection::CrossSection(C2::PathsD ps) { paths_ = shared_paths(ps); }
|
||||
CrossSection::CrossSection(std::shared_ptr<const PathImpl> ps) { paths_ = ps; }
|
||||
|
||||
/**
|
||||
* Create a 2d cross-section from a single contour. A boolean union operation
|
||||
@@ -271,13 +287,13 @@ CrossSection::CrossSection(const Rect& rect) {
|
||||
// Private
|
||||
// All access to paths_ should be done through the GetPaths() method, which
|
||||
// applies the accumulated transform_
|
||||
C2::PathsD CrossSection::GetPaths() const {
|
||||
std::shared_ptr<const PathImpl> CrossSection::GetPaths() const {
|
||||
if (transform_ == glm::mat3x2(1.0f)) {
|
||||
return *paths_;
|
||||
return paths_;
|
||||
}
|
||||
paths_ = shared_paths(transform(*paths_, transform_));
|
||||
paths_ = shared_paths(::transform(paths_->paths_, transform_));
|
||||
transform_ = glm::mat3x2(1.0f);
|
||||
return *paths_;
|
||||
return paths_;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,7 +325,7 @@ CrossSection CrossSection::Square(const glm::vec2 size, bool center) {
|
||||
p[2] = C2::PointD(x, y);
|
||||
p[3] = C2::PointD(0.0, y);
|
||||
}
|
||||
return CrossSection(C2::PathsD{p});
|
||||
return CrossSection(shared_paths(C2::PathsD{p}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -330,7 +346,7 @@ CrossSection CrossSection::Circle(float radius, int circularSegments) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[i] = C2::PointD(radius * cosd(dPhi * i), radius * sind(dPhi * i));
|
||||
}
|
||||
return CrossSection(C2::PathsD{circle});
|
||||
return CrossSection(shared_paths(C2::PathsD{circle}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -339,9 +355,9 @@ CrossSection CrossSection::Circle(float radius, int circularSegments) {
|
||||
CrossSection CrossSection::Boolean(const CrossSection& second,
|
||||
OpType op) const {
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths(),
|
||||
second.GetPaths(), precision_);
|
||||
return CrossSection(res);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, GetPaths()->paths_,
|
||||
second.GetPaths()->paths_, precision_);
|
||||
return CrossSection(shared_paths(res));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,19 +374,19 @@ CrossSection CrossSection::BatchBoolean(
|
||||
auto subjs = crossSections[0].GetPaths();
|
||||
int n_clips = 0;
|
||||
for (int i = 1; i < crossSections.size(); ++i) {
|
||||
n_clips += crossSections[i].GetPaths().size();
|
||||
n_clips += crossSections[i].GetPaths()->paths_.size();
|
||||
}
|
||||
auto clips = C2::PathsD();
|
||||
clips.reserve(n_clips);
|
||||
for (int i = 1; i < crossSections.size(); ++i) {
|
||||
auto ps = crossSections[i].GetPaths();
|
||||
clips.insert(clips.end(), ps.begin(), ps.end());
|
||||
clips.insert(clips.end(), ps->paths_.begin(), ps->paths_.end());
|
||||
}
|
||||
|
||||
auto ct = cliptype_of_op(op);
|
||||
auto res =
|
||||
C2::BooleanOp(ct, C2::FillRule::Positive, subjs, clips, precision_);
|
||||
return CrossSection(res);
|
||||
auto res = C2::BooleanOp(ct, C2::FillRule::Positive, subjs->paths_, clips,
|
||||
precision_);
|
||||
return CrossSection(shared_paths(res));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -441,7 +457,7 @@ std::vector<CrossSection> CrossSection::Decompose() const {
|
||||
}
|
||||
|
||||
C2::PolyTreeD tree;
|
||||
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths(),
|
||||
C2::BooleanOp(C2::ClipType::Union, C2::FillRule::Positive, GetPaths()->paths_,
|
||||
C2::PathsD(), tree, precision_);
|
||||
|
||||
auto polys = std::vector<C2::PathsD>();
|
||||
@@ -451,7 +467,7 @@ std::vector<CrossSection> CrossSection::Decompose() const {
|
||||
auto comps = std::vector<CrossSection>(n_polys);
|
||||
// reverse the stack while wrapping
|
||||
for (int i = 0; i < n_polys; ++i) {
|
||||
comps[n_polys - i - 1] = CrossSection(polys[i]);
|
||||
comps[n_polys - i - 1] = CrossSection(shared_paths(polys[i]));
|
||||
}
|
||||
|
||||
return comps;
|
||||
@@ -541,8 +557,8 @@ CrossSection CrossSection::Warp(
|
||||
std::function<void(glm::vec2&)> warpFunc) const {
|
||||
auto paths = GetPaths();
|
||||
auto warped = C2::PathsD();
|
||||
warped.reserve(paths.size());
|
||||
for (auto path : paths) {
|
||||
warped.reserve(paths->paths_.size());
|
||||
for (auto path : paths->paths_) {
|
||||
auto sz = path.size();
|
||||
auto s = C2::PathD(sz);
|
||||
for (int i = 0; i < sz; ++i) {
|
||||
@@ -552,7 +568,8 @@ CrossSection CrossSection::Warp(
|
||||
}
|
||||
warped.push_back(s);
|
||||
}
|
||||
return CrossSection(C2::Union(warped, C2::FillRule::Positive, precision_));
|
||||
return CrossSection(
|
||||
shared_paths(C2::Union(warped, C2::FillRule::Positive, precision_)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -568,8 +585,8 @@ CrossSection CrossSection::Warp(
|
||||
* offseting operations are to be performed, which would compound the issue.
|
||||
*/
|
||||
CrossSection CrossSection::Simplify(double epsilon) const {
|
||||
auto ps = SimplifyPaths(GetPaths(), epsilon, false);
|
||||
return CrossSection(ps);
|
||||
auto ps = SimplifyPaths(GetPaths()->paths_, epsilon, false);
|
||||
return CrossSection(shared_paths(ps));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -607,11 +624,17 @@ CrossSection CrossSection::Offset(double delta, JoinType jointype,
|
||||
arc_tol = (std::cos(Clipper2Lib::PI / n) - 1) * -scaled_delta;
|
||||
}
|
||||
auto ps =
|
||||
C2::InflatePaths(GetPaths(), delta, jt(jointype), C2::EndType::Polygon,
|
||||
miter_limit, precision_, arc_tol);
|
||||
return CrossSection(ps);
|
||||
C2::InflatePaths(GetPaths()->paths_, delta, jt(jointype),
|
||||
C2::EndType::Polygon, miter_limit, precision_, arc_tol);
|
||||
return CrossSection(shared_paths(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;
|
||||
@@ -619,24 +642,41 @@ CrossSection CrossSection::Hull(
|
||||
SimplePolygon pts;
|
||||
pts.reserve(n);
|
||||
for (auto cs : crossSections) {
|
||||
auto paths = cs.GetPaths();
|
||||
auto paths = cs.GetPaths()->paths_;
|
||||
for (auto path : paths) {
|
||||
for (auto p : path) {
|
||||
pts.push_back(v2_of_pd(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
return CrossSection(C2::PathsD{HullImpl(pts)});
|
||||
return CrossSection(shared_paths(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)});
|
||||
return CrossSection(shared_paths(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) {
|
||||
@@ -651,14 +691,14 @@ CrossSection CrossSection::Hull(const Polygons polys) {
|
||||
* Return the total area covered by complex polygons making up the
|
||||
* CrossSection.
|
||||
*/
|
||||
double CrossSection::Area() const { return C2::Area(GetPaths()); }
|
||||
double CrossSection::Area() const { return C2::Area(GetPaths()->paths_); }
|
||||
|
||||
/**
|
||||
* Return the number of vertices in the CrossSection.
|
||||
*/
|
||||
int CrossSection::NumVert() const {
|
||||
int n = 0;
|
||||
auto paths = GetPaths();
|
||||
auto paths = GetPaths()->paths_;
|
||||
for (auto p : paths) {
|
||||
n += p.size();
|
||||
}
|
||||
@@ -669,19 +709,19 @@ int CrossSection::NumVert() const {
|
||||
* Return the number of contours (both outer and inner paths) in the
|
||||
* CrossSection.
|
||||
*/
|
||||
int CrossSection::NumContour() const { return GetPaths().size(); }
|
||||
int CrossSection::NumContour() const { return GetPaths()->paths_.size(); }
|
||||
|
||||
/**
|
||||
* Does the CrossSection contain any contours?
|
||||
*/
|
||||
bool CrossSection::IsEmpty() const { return GetPaths().empty(); }
|
||||
bool CrossSection::IsEmpty() const { return GetPaths()->paths_.empty(); }
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding rectangle of all the CrossSections'
|
||||
* vertices.
|
||||
*/
|
||||
Rect CrossSection::Bounds() const {
|
||||
auto r = C2::GetBounds(GetPaths());
|
||||
auto r = C2::GetBounds(GetPaths()->paths_);
|
||||
return Rect({r.left, r.bottom}, {r.right, r.top});
|
||||
}
|
||||
|
||||
@@ -690,7 +730,7 @@ Rect CrossSection::Bounds() const {
|
||||
*/
|
||||
Polygons CrossSection::ToPolygons() const {
|
||||
auto polys = Polygons();
|
||||
auto paths = GetPaths();
|
||||
auto paths = GetPaths()->paths_;
|
||||
polys.reserve(paths.size());
|
||||
for (auto p : paths) {
|
||||
auto sp = SimplePolygon();
|
||||
@@ -702,167 +742,4 @@ Polygons CrossSection::ToPolygons() const {
|
||||
}
|
||||
return polys;
|
||||
}
|
||||
|
||||
// Rect
|
||||
|
||||
/**
|
||||
* Default constructor is an empty rectangle..
|
||||
*/
|
||||
Rect::Rect() {}
|
||||
|
||||
Rect::~Rect() = default;
|
||||
Rect::Rect(Rect&&) noexcept = default;
|
||||
Rect& Rect::operator=(Rect&&) noexcept = default;
|
||||
Rect::Rect(const Rect& other) {
|
||||
min = glm::vec2(other.min);
|
||||
max = glm::vec2(other.max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a rectangle that contains the two given points.
|
||||
*/
|
||||
Rect::Rect(const glm::vec2 a, const glm::vec2 b) {
|
||||
min = glm::min(a, b);
|
||||
max = glm::max(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the dimensions of the rectangle.
|
||||
*/
|
||||
glm::vec2 Rect::Size() const { return max - min; }
|
||||
|
||||
/**
|
||||
* Return the area of the rectangle.
|
||||
*/
|
||||
float Rect::Area() const {
|
||||
auto sz = Size();
|
||||
return sz.x * sz.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
float Rect::Scale() const {
|
||||
glm::vec2 absMax = glm::max(glm::abs(min), glm::abs(max));
|
||||
return glm::max(absMax.x, absMax.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center point of the rectangle.
|
||||
*/
|
||||
glm::vec2 Rect::Center() const { return 0.5f * (max + min); }
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes on border) the given point?
|
||||
*/
|
||||
bool Rect::Contains(const glm::vec2& p) const {
|
||||
return glm::all(glm::greaterThanEqual(p, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes equal) the given rectangle?
|
||||
*/
|
||||
bool Rect::Contains(const Rect& rect) const {
|
||||
return glm::all(glm::greaterThanEqual(rect.min, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, rect.max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle overlap the one given (including equality)?
|
||||
*/
|
||||
bool Rect::DoesOverlap(const Rect& rect) const {
|
||||
return min.x <= rect.max.x && min.y <= rect.max.y && max.x >= rect.min.x &&
|
||||
max.y >= rect.min.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the rectangle empty (containing no space)?
|
||||
*/
|
||||
bool Rect::IsEmpty() const { return max.y <= min.y || max.x <= min.x; };
|
||||
|
||||
/**
|
||||
* Does this recangle have finite bounds?
|
||||
*/
|
||||
bool Rect::IsFinite() const {
|
||||
return glm::all(glm::isfinite(min)) && glm::all(glm::isfinite(max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this rectangle (in place) to include the given point.
|
||||
*/
|
||||
void Rect::Union(const glm::vec2 p) {
|
||||
min = glm::min(min, p);
|
||||
max = glm::max(max, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this rectangle to include the given Rect.
|
||||
*/
|
||||
Rect Rect::Union(const Rect& rect) const {
|
||||
Rect out;
|
||||
out.min = glm::min(min, rect.min);
|
||||
out.max = glm::max(max, rect.max);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle by the given vector.
|
||||
*/
|
||||
Rect Rect::operator+(const glm::vec2 shift) const {
|
||||
Rect out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& Rect::operator+=(const glm::vec2 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle by the given vector.
|
||||
*/
|
||||
Rect Rect::operator*(const glm::vec2 scale) const {
|
||||
Rect out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& Rect::operator*=(const glm::vec2 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the rectangle by the given axis-aligned affine transform.
|
||||
*
|
||||
* Ensure the transform passed in is axis-aligned (rotations are all
|
||||
* multiples of 90 degrees), or else the resulting rectangle will no longer
|
||||
* bound properly.
|
||||
*/
|
||||
Rect Rect::Transform(const glm::mat3x2& m) const {
|
||||
Rect rect;
|
||||
rect.min = m * glm::vec3(min, 1);
|
||||
rect.max = m * glm::vec3(max, 1);
|
||||
return rect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a CrossSection with an outline defined by this axis-aligned
|
||||
* rectangle.
|
||||
*/
|
||||
CrossSection Rect::AsCrossSection() const { return CrossSection(*this); }
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -17,17 +17,26 @@ 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
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite Clipper2 quickhull
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} Clipper2 quickhull
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT runtime
|
||||
)
|
||||
install(
|
||||
DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
|
||||
+181
-175
@@ -27,7 +27,7 @@ namespace {
|
||||
// carefully designed to minimize rounding error and to eliminate it at edge
|
||||
// cases to ensure consistency.
|
||||
|
||||
__host__ __device__ glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
|
||||
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,10 +42,8 @@ __host__ __device__ glm::vec2 Interpolate(glm::vec3 pL, glm::vec3 pR, float x) {
|
||||
return yz;
|
||||
}
|
||||
|
||||
__host__ __device__ glm::vec4 Intersect(const glm::vec3 &pL,
|
||||
const glm::vec3 &pR,
|
||||
const glm::vec3 &qL,
|
||||
const glm::vec3 &qR) {
|
||||
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
|
||||
@@ -67,42 +65,43 @@ __host__ __device__ glm::vec4 Intersect(const glm::vec3 &pL,
|
||||
return xyzz;
|
||||
}
|
||||
|
||||
template <const bool inverted>
|
||||
struct CopyFaceEdges {
|
||||
const SparseIndices &p1q1;
|
||||
// const int *p1q1;
|
||||
// x can be either vert or edge (0 or 1).
|
||||
thrust::pair<int *, int *> pXq1;
|
||||
const Halfedge *halfedgesQ;
|
||||
SparseIndices &pXq1;
|
||||
VecView<const Halfedge> halfedgesQ;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
|
||||
void operator()(thrust::tuple<int, int> in) {
|
||||
int idx = 3 * thrust::get<0>(in);
|
||||
const int pX = thrust::get<1>(in);
|
||||
const int q2 = thrust::get<2>(in);
|
||||
int i = thrust::get<1>(in);
|
||||
int pX = p1q1.Get(i, inverted);
|
||||
int q2 = p1q1.Get(i, !inverted);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
pXq1.first[idx + i] = pX;
|
||||
const int q1 = 3 * q2 + i;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
const int q1 = 3 * q2 + j;
|
||||
const Halfedge edge = halfedgesQ[q1];
|
||||
pXq1.second[idx + i] = edge.IsForward() ? q1 : edge.pairedHalfedge;
|
||||
int a = pX;
|
||||
int b = edge.IsForward() ? q1 : edge.pairedHalfedge;
|
||||
if (inverted) std::swap(a, b);
|
||||
pXq1.Set(idx + j, a, b);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SparseIndices Filter11(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
const SparseIndices &p1q2, const SparseIndices &p2q1,
|
||||
ExecutionPolicy policy) {
|
||||
const SparseIndices &p1q2, const SparseIndices &p2q1) {
|
||||
SparseIndices p1q1(3 * p1q2.size() + 3 * p2q1.size());
|
||||
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);
|
||||
for_each_n(autoPolicy(p1q2.size()), zip(countAt(0), countAt(0)), p1q2.size(),
|
||||
CopyFaceEdges<false>({p1q2, p1q1, inQ.halfedge_}));
|
||||
for_each_n(autoPolicy(p2q1.size()), zip(countAt(p1q2.size()), countAt(0)),
|
||||
p2q1.size(), CopyFaceEdges<true>({p2q1, p1q1, inP.halfedge_}));
|
||||
p1q1.Unique();
|
||||
return p1q1;
|
||||
}
|
||||
|
||||
__host__ __device__ bool Shadows(float p, float q, float dir) {
|
||||
inline bool Shadows(float p, float q, float dir) {
|
||||
return p == q ? dir < 0 : p < q;
|
||||
}
|
||||
|
||||
@@ -117,10 +116,10 @@ __host__ __device__ bool Shadows(float p, float q, float dir) {
|
||||
* compiled function (they must agree on CPU or GPU). This is now taken care of
|
||||
* by the shared policy_ member.
|
||||
*/
|
||||
__host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
|
||||
const int p0, const int q1, const glm::vec3 *vertPosP,
|
||||
const glm::vec3 *vertPosQ, const Halfedge *halfedgeQ, const float expandP,
|
||||
const glm::vec3 *normalP, const bool reverse) {
|
||||
inline thrust::pair<int, glm::vec2> Shadow01(
|
||||
const int p0, const int q1, VecView<const glm::vec3> vertPosP,
|
||||
VecView<const glm::vec3> vertPosQ, VecView<const Halfedge> halfedgeQ,
|
||||
const float expandP, VecView<const glm::vec3> normalP, const bool reverse) {
|
||||
const int q1s = halfedgeQ[q1].startVert;
|
||||
const int q1e = halfedgeQ[q1].endVert;
|
||||
const float p0x = vertPosP[p0].x;
|
||||
@@ -148,43 +147,59 @@ __host__ __device__ thrust::pair<int, glm::vec2> Shadow01(
|
||||
return thrust::make_pair(s01, yz01);
|
||||
}
|
||||
|
||||
__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
|
||||
// https://github.com/scandum/binary_search/blob/master/README.md
|
||||
// much faster than standard binary search on large arrays
|
||||
int monobound_quaternary_search(VecView<const int64_t> array, int64_t key) {
|
||||
if (array.size() == 0) {
|
||||
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 {
|
||||
const glm::vec3 *vertPosP;
|
||||
const glm::vec3 *vertPosQ;
|
||||
const Halfedge *halfedgeP;
|
||||
const Halfedge *halfedgeQ;
|
||||
VecView<const glm::vec3> vertPosP;
|
||||
VecView<const glm::vec3> vertPosQ;
|
||||
VecView<const Halfedge> halfedgeP;
|
||||
VecView<const Halfedge> halfedgeQ;
|
||||
float expandP;
|
||||
const glm::vec3 *normalP;
|
||||
VecView<const glm::vec3> normalP;
|
||||
const SparseIndices &p1q1;
|
||||
|
||||
__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);
|
||||
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);
|
||||
|
||||
// For pRL[k], qRL[k], k==0 is the left and k==1 is the right.
|
||||
int k = 0;
|
||||
@@ -254,20 +269,17 @@ struct Kernel11 {
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<VecDH<int>, VecDH<glm::vec4>> Shadow11(SparseIndices &p1q1,
|
||||
const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
float expandP,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<int> s11(p1q1.size());
|
||||
VecDH<glm::vec4> xyzz11(p1q1.size());
|
||||
std::tuple<Vec<int>, Vec<glm::vec4>> Shadow11(SparseIndices &p1q1,
|
||||
const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
float expandP) {
|
||||
Vec<int> s11(p1q1.size());
|
||||
Vec<glm::vec4> xyzz11(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()}));
|
||||
for_each_n(autoPolicy(p1q1.size()),
|
||||
zip(countAt(0), xyzz11.begin(), s11.begin()), p1q1.size(),
|
||||
Kernel11({inP.vertPos_, inQ.vertPos_, inP.halfedge_, inQ.halfedge_,
|
||||
expandP, inP.vertNormal_, p1q1}));
|
||||
|
||||
p1q1.KeepFinite(xyzz11, s11);
|
||||
|
||||
@@ -275,19 +287,19 @@ std::tuple<VecDH<int>, VecDH<glm::vec4>> Shadow11(SparseIndices &p1q1,
|
||||
};
|
||||
|
||||
struct Kernel02 {
|
||||
const glm::vec3 *vertPosP;
|
||||
const Halfedge *halfedgeQ;
|
||||
const glm::vec3 *vertPosQ;
|
||||
const bool forward;
|
||||
VecView<const glm::vec3> vertPosP;
|
||||
VecView<const Halfedge> halfedgeQ;
|
||||
VecView<const glm::vec3> vertPosQ;
|
||||
const float expandP;
|
||||
const glm::vec3 *vertNormalP;
|
||||
VecView<const glm::vec3> vertNormalP;
|
||||
const SparseIndices &p0q2;
|
||||
const bool forward;
|
||||
|
||||
__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);
|
||||
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);
|
||||
|
||||
// For yzzLR[k], k==0 is the left and k==1 is the right.
|
||||
int k = 0;
|
||||
@@ -351,22 +363,18 @@ struct Kernel02 {
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<VecDH<int>, VecDH<float>> Shadow02(const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
SparseIndices &p0q2, bool forward,
|
||||
float expandP,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<int> s02(p0q2.size());
|
||||
VecDH<float> z02(p0q2.size());
|
||||
std::tuple<Vec<int>, Vec<float>> Shadow02(const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ,
|
||||
SparseIndices &p0q2, bool forward,
|
||||
float expandP) {
|
||||
Vec<int> s02(p0q2.size());
|
||||
Vec<float> z02(p0q2.size());
|
||||
|
||||
auto vertNormalP =
|
||||
forward ? inP.vertNormal_.cptrD() : inQ.vertNormal_.cptrD();
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(s02.begin(), z02.begin(), p0q2.begin(!forward), p0q2.begin(forward)),
|
||||
p0q2.size(),
|
||||
Kernel02({inP.vertPos_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), forward, expandP, vertNormalP}));
|
||||
auto vertNormalP = forward ? inP.vertNormal_ : inQ.vertNormal_;
|
||||
for_each_n(autoPolicy(p0q2.size()), zip(countAt(0), s02.begin(), z02.begin()),
|
||||
p0q2.size(),
|
||||
Kernel02({inP.vertPos_, inQ.halfedge_, inQ.vertPos_, expandP,
|
||||
vertNormalP, p0q2, forward}));
|
||||
|
||||
p0q2.KeepFinite(z02, s02);
|
||||
|
||||
@@ -374,25 +382,23 @@ std::tuple<VecDH<int>, VecDH<float>> Shadow02(const Manifold::Impl &inP,
|
||||
};
|
||||
|
||||
struct Kernel12 {
|
||||
const thrust::pair<const int *, const int *> p0q2;
|
||||
const int *s02;
|
||||
const float *z02;
|
||||
const int size02;
|
||||
const thrust::pair<const int *, const int *> p1q1;
|
||||
const int *s11;
|
||||
const glm::vec4 *xyzz11;
|
||||
const int size11;
|
||||
const Halfedge *halfedgesP;
|
||||
const Halfedge *halfedgesQ;
|
||||
const glm::vec3 *vertPosP;
|
||||
VecView<const int64_t> p0q2;
|
||||
VecView<const int> s02;
|
||||
VecView<const float> z02;
|
||||
VecView<const int64_t> p1q1;
|
||||
VecView<const int> s11;
|
||||
VecView<const glm::vec4> xyzz11;
|
||||
VecView<const Halfedge> halfedgesP;
|
||||
VecView<const Halfedge> halfedgesQ;
|
||||
VecView<const glm::vec3> vertPosP;
|
||||
const bool forward;
|
||||
const SparseIndices &p1q2;
|
||||
|
||||
__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);
|
||||
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);
|
||||
|
||||
// For xzyLR-[k], k==0 is the left and k==1 is the right.
|
||||
int k = 0;
|
||||
@@ -406,9 +412,9 @@ struct Kernel12 {
|
||||
const Halfedge edge = halfedgesP[p1];
|
||||
|
||||
for (int vert : {edge.startVert, edge.endVert}) {
|
||||
const auto key =
|
||||
forward ? thrust::make_pair(vert, q2) : thrust::make_pair(q2, vert);
|
||||
const int idx = BinarySearch(p0q2, size02, key);
|
||||
const int64_t key = forward ? SparseIndices::EncodePQ(vert, q2)
|
||||
: SparseIndices::EncodePQ(q2, vert);
|
||||
const int idx = monobound_quaternary_search(p0q2, key);
|
||||
if (idx != -1) {
|
||||
const int s = s02[idx];
|
||||
x12 += s * ((vert == edge.startVert) == forward ? 1 : -1);
|
||||
@@ -427,9 +433,9 @@ struct Kernel12 {
|
||||
const int q1 = 3 * q2 + i;
|
||||
const Halfedge edge = halfedgesQ[q1];
|
||||
const int q1F = edge.IsForward() ? q1 : edge.pairedHalfedge;
|
||||
const auto key =
|
||||
forward ? thrust::make_pair(p1, q1F) : thrust::make_pair(q1F, p1);
|
||||
const int idx = BinarySearch(p1q1, size11, key);
|
||||
const int64_t key = forward ? SparseIndices::EncodePQ(p1, q1F)
|
||||
: SparseIndices::EncodePQ(q1F, p1);
|
||||
const int idx = monobound_quaternary_search(p1q1, key);
|
||||
if (idx != -1) { // s is implicitly zero for anything not found
|
||||
const int s = s11[idx];
|
||||
x12 -= s * (edge.IsForward() ? 1 : -1);
|
||||
@@ -465,42 +471,44 @@ struct Kernel12 {
|
||||
}
|
||||
};
|
||||
|
||||
std::tuple<VecDH<int>, VecDH<glm::vec3>> Intersect12(
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, const VecDH<int> &s02,
|
||||
const SparseIndices &p0q2, const VecDH<int> &s11, const SparseIndices &p1q1,
|
||||
const VecDH<float> &z02, const VecDH<glm::vec4> &xyzz11,
|
||||
SparseIndices &p1q2, bool forward, ExecutionPolicy policy) {
|
||||
VecDH<int> x12(p1q2.size());
|
||||
VecDH<glm::vec3> v12(p1q2.size());
|
||||
std::tuple<Vec<int>, Vec<glm::vec3>> Intersect12(
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, const Vec<int> &s02,
|
||||
const SparseIndices &p0q2, const Vec<int> &s11, const SparseIndices &p1q1,
|
||||
const Vec<float> &z02, const Vec<glm::vec4> &xyzz11, SparseIndices &p1q2,
|
||||
bool forward) {
|
||||
Vec<int> x12(p1q2.size());
|
||||
Vec<glm::vec3> v12(p1q2.size());
|
||||
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(x12.begin(), v12.begin(), p1q2.begin(!forward), p1q2.begin(forward)),
|
||||
autoPolicy(p1q2.size()), zip(countAt(0), x12.begin(), v12.begin()),
|
||||
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}));
|
||||
Kernel12({p0q2.AsVec64(), s02, z02, p1q1.AsVec64(), s11, xyzz11,
|
||||
inP.halfedge_, inQ.halfedge_, inP.vertPos_, forward, p1q2}));
|
||||
|
||||
p1q2.KeepFinite(v12, x12);
|
||||
|
||||
return std::make_tuple(x12, v12);
|
||||
};
|
||||
|
||||
VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
|
||||
VecDH<int> &s02, bool reverse, ExecutionPolicy policy) {
|
||||
Vec<int> Winding03(const Manifold::Impl &inP, Vec<int> &vertices, Vec<int> &s02,
|
||||
bool reverse) {
|
||||
// verts that are not shadowed (not in p0q2) have winding number zero.
|
||||
VecDH<int> w03(inP.NumVert(), 0);
|
||||
|
||||
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());
|
||||
Vec<int> w03(inP.NumVert(), 0);
|
||||
// checking is slow, so just sort and reduce
|
||||
auto policy = autoPolicy(vertices.size());
|
||||
stable_sort(
|
||||
policy, zip(vertices.begin(), s02.begin()),
|
||||
zip(vertices.end(), s02.end()),
|
||||
[](const thrust::tuple<int, int> &a, const thrust::tuple<int, int> &b) {
|
||||
return thrust::get<0>(a) < thrust::get<0>(b);
|
||||
});
|
||||
Vec<int> w03val(w03.size());
|
||||
Vec<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, p0q2.begin(reverse), p0q2.end(reverse), s02.begin(),
|
||||
w03vert.begin(), w03val.begin());
|
||||
policy, vertices.begin(), vertices.end(), s02.begin(), w03vert.begin(),
|
||||
w03val.begin());
|
||||
scatter(policy, w03val.begin(), endPair.second, w03vert.begin(), w03.begin());
|
||||
|
||||
if (reverse)
|
||||
@@ -513,10 +521,7 @@ VecDH<int> Winding03(const Manifold::Impl &inP, SparseIndices &p0q2,
|
||||
namespace manifold {
|
||||
Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
OpType op)
|
||||
: inP_(inP),
|
||||
inQ_(inQ),
|
||||
expandP_(op == OpType::Add ? 1.0 : -1.0),
|
||||
policy_(autoPolicy(glm::max(inP.NumEdge(), inQ.NumEdge()))) {
|
||||
: inP_(inP), inQ_(inQ), expandP_(op == OpType::Add ? 1.0 : -1.0) {
|
||||
// Symbolic perturbation:
|
||||
// Union -> expand inP
|
||||
// Difference, Intersection -> contract inP
|
||||
@@ -536,29 +541,26 @@ 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_);
|
||||
p2q1_ = inP_.EdgeCollisions(inQ_, true); // inverted
|
||||
|
||||
policy_ = autoPolicy(glm::max(p1q2_.size(), p2q1_.size()));
|
||||
p1q2_.Sort(policy_);
|
||||
p1q2_.Sort();
|
||||
PRINT("p1q2 size = " << p1q2_.size());
|
||||
|
||||
p2q1_.SwapPQ();
|
||||
p2q1_.Sort(policy_);
|
||||
p2q1_.Sort();
|
||||
PRINT("p2q1 size = " << p2q1_.size());
|
||||
|
||||
// Level 2
|
||||
// Find vertices that overlap faces in XY-projection
|
||||
SparseIndices p0q2 = inQ.VertexCollisionsZ(inP.vertPos_);
|
||||
p0q2.Sort(policy_);
|
||||
p0q2.Sort();
|
||||
PRINT("p0q2 size = " << p0q2.size());
|
||||
|
||||
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_);
|
||||
p2q0.SwapPQ();
|
||||
p2q0.Sort(policy_);
|
||||
SparseIndices p2q0 = inP.VertexCollisionsZ(inQ.vertPos_, true); // inverted
|
||||
p2q0.Sort();
|
||||
PRINT("p2q0 size = " << p2q0.size());
|
||||
|
||||
// Find involved edge pairs from Level 3
|
||||
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_, policy_);
|
||||
SparseIndices p1q1 = Filter11(inP_, inQ_, p1q2_, p2q1_);
|
||||
PRINT("p1q1 size = " << p1q1.size());
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
@@ -570,39 +572,43 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
// Level 2
|
||||
// Build up XY-projection intersection of two edges, including the z-value for
|
||||
// each edge, keeping only those whose intersection exists.
|
||||
VecDH<int> s11;
|
||||
VecDH<glm::vec4> xyzz11;
|
||||
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_, policy_);
|
||||
Vec<int> s11;
|
||||
Vec<glm::vec4> xyzz11;
|
||||
std::tie(s11, xyzz11) = Shadow11(p1q1, inP, inQ, expandP_);
|
||||
PRINT("s11 size = " << s11.size());
|
||||
|
||||
// Build up Z-projection of vertices onto triangles, keeping only those that
|
||||
// fall inside the triangle.
|
||||
VecDH<int> s02;
|
||||
VecDH<float> z02;
|
||||
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_, policy_);
|
||||
Vec<int> s02;
|
||||
Vec<float> z02;
|
||||
std::tie(s02, z02) = Shadow02(inP, inQ, p0q2, true, expandP_);
|
||||
PRINT("s02 size = " << s02.size());
|
||||
|
||||
VecDH<int> s20;
|
||||
VecDH<float> z20;
|
||||
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_, policy_);
|
||||
Vec<int> s20;
|
||||
Vec<float> z20;
|
||||
std::tie(s20, z20) = Shadow02(inQ, inP, p2q0, false, expandP_);
|
||||
PRINT("s20 size = " << s20.size());
|
||||
|
||||
// Level 3
|
||||
// Build up the intersection of the edges and triangles, keeping only those
|
||||
// that intersect, and record the direction the edge is passing through the
|
||||
// triangle.
|
||||
std::tie(x12_, v12_) = Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02,
|
||||
xyzz11, p1q2_, true, policy_);
|
||||
std::tie(x12_, v12_) =
|
||||
Intersect12(inP, inQ, s02, p0q2, s11, p1q1, z02, xyzz11, p1q2_, true);
|
||||
PRINT("x12 size = " << x12_.size());
|
||||
|
||||
std::tie(x21_, v21_) = Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20,
|
||||
xyzz11, p2q1_, false, policy_);
|
||||
std::tie(x21_, v21_) =
|
||||
Intersect12(inQ, inP, s20, p2q0, s11, p1q1, z20, xyzz11, p2q1_, false);
|
||||
PRINT("x21 size = " << x21_.size());
|
||||
|
||||
Vec<int> p0 = p0q2.Copy(false);
|
||||
p0q2.Resize(0);
|
||||
Vec<int> q0 = p2q0.Copy(true);
|
||||
p2q0.Resize(0);
|
||||
// Sum up the winding numbers of all vertices.
|
||||
w03_ = Winding03(inP, p0q2, s02, false, policy_);
|
||||
w03_ = Winding03(inP, p0, s02, false);
|
||||
|
||||
w30_ = Winding03(inQ, p2q0, s20, true, policy_);
|
||||
w30_ = Winding03(inQ, q0, s20, true);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
intersections.Stop();
|
||||
|
||||
@@ -54,8 +54,7 @@ class Boolean3 {
|
||||
const Manifold::Impl &inP_, &inQ_;
|
||||
const float expandP_;
|
||||
SparseIndices p1q2_, p2q1_;
|
||||
VecDH<int> x12_, x21_, w03_, w30_;
|
||||
VecDH<glm::vec3> v12_, v21_;
|
||||
ExecutionPolicy policy_;
|
||||
Vec<int> x12_, x21_, w03_, w30_;
|
||||
Vec<glm::vec3> v12_, v21_;
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
+231
-172
@@ -13,8 +13,21 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <map>
|
||||
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/concurrent_map.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"
|
||||
@@ -22,16 +35,25 @@
|
||||
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 {
|
||||
|
||||
constexpr int kParallelThreshold = 128;
|
||||
|
||||
struct AbsSum : public thrust::binary_function<int, int, int> {
|
||||
__host__ __device__ int operator()(int a, int b) { return abs(a) + abs(b); }
|
||||
int operator()(int a, int b) { return abs(a) + abs(b); }
|
||||
};
|
||||
|
||||
struct DuplicateVerts {
|
||||
glm::vec3 *vertPosR;
|
||||
VecView<glm::vec3> vertPosR;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, int, glm::vec3> in) {
|
||||
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);
|
||||
@@ -43,23 +65,25 @@ struct DuplicateVerts {
|
||||
};
|
||||
|
||||
struct CountVerts {
|
||||
int *count;
|
||||
const int *inclusion;
|
||||
VecView<int> count;
|
||||
VecView<const int> inclusion;
|
||||
|
||||
__host__ __device__ void operator()(const Halfedge &edge) {
|
||||
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 Halfedge *halfedges;
|
||||
VecView<int> countP;
|
||||
VecView<int> countQ;
|
||||
const SparseIndices &pq;
|
||||
VecView<const Halfedge> halfedges;
|
||||
|
||||
__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));
|
||||
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));
|
||||
|
||||
AtomicAdd(countQ[faceQ], inclusion);
|
||||
const Halfedge half = halfedges[edgeP];
|
||||
@@ -69,59 +93,63 @@ struct CountNewVerts {
|
||||
};
|
||||
|
||||
struct NotZero : public thrust::unary_function<int, int> {
|
||||
__host__ __device__ int operator()(int x) const { return x > 0 ? 1 : 0; }
|
||||
int operator()(int x) const { return x > 0 ? 1 : 0; }
|
||||
};
|
||||
|
||||
std::tuple<VecDH<int>, VecDH<int>> SizeOutput(
|
||||
std::tuple<Vec<int>, Vec<int>> SizeOutput(
|
||||
Manifold::Impl &outR, const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
const VecDH<int> &i03, const VecDH<int> &i30, const VecDH<int> &i12,
|
||||
const VecDH<int> &i21, const SparseIndices &p1q2, const SparseIndices &p2q1,
|
||||
bool invertQ, ExecutionPolicy policy) {
|
||||
VecDH<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);
|
||||
auto sidesPerFaceP = sidesPerFacePQ.ptrD();
|
||||
auto sidesPerFaceQ = sidesPerFacePQ.ptrD() + inP.NumTri();
|
||||
const Vec<int> &i03, const Vec<int> &i30, const Vec<int> &i12,
|
||||
const Vec<int> &i21, const SparseIndices &p1q2, const SparseIndices &p2q1,
|
||||
bool invertQ) {
|
||||
Vec<int> sidesPerFacePQ(inP.NumTri() + inQ.NumTri(), 0);
|
||||
auto sidesPerFaceP = sidesPerFacePQ.view(0, inP.NumTri());
|
||||
auto sidesPerFaceQ = sidesPerFacePQ.view(inP.NumTri(), inQ.NumTri());
|
||||
|
||||
for_each(policy, inP.halfedge_.begin(), inP.halfedge_.end(),
|
||||
CountVerts({sidesPerFaceP, i03.cptrD()}));
|
||||
for_each(policy, inQ.halfedge_.begin(), inQ.halfedge_.end(),
|
||||
CountVerts({sidesPerFaceQ, i30.cptrD()}));
|
||||
for_each(autoPolicy(inP.halfedge_.size()), inP.halfedge_.begin(),
|
||||
inP.halfedge_.end(), CountVerts({sidesPerFaceP, i03}));
|
||||
for_each(autoPolicy(inP.halfedge_.size()), inQ.halfedge_.begin(),
|
||||
inQ.halfedge_.end(), CountVerts({sidesPerFaceQ, i30}));
|
||||
for_each_n(autoPolicy(i12.size()), zip(countAt(0), i12.begin()), i12.size(),
|
||||
CountNewVerts<false>(
|
||||
{sidesPerFaceP, sidesPerFaceQ, p1q2, inP.halfedge_}));
|
||||
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()}));
|
||||
autoPolicy(i21.size()), zip(countAt(0), i21.begin()), i21.size(),
|
||||
CountNewVerts<true>({sidesPerFaceQ, sidesPerFaceP, p2q1, inQ.halfedge_}));
|
||||
|
||||
VecDH<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);
|
||||
Vec<int> facePQ2R(inP.NumTri() + inQ.NumTri() + 1, 0);
|
||||
auto keepFace =
|
||||
thrust::make_transform_iterator(sidesPerFacePQ.begin(), NotZero());
|
||||
inclusive_scan(policy, keepFace, keepFace + sidesPerFacePQ.size(),
|
||||
facePQ2R.begin() + 1);
|
||||
inclusive_scan(autoPolicy(sidesPerFacePQ.size()), keepFace,
|
||||
keepFace + sidesPerFacePQ.size(), facePQ2R.begin() + 1);
|
||||
int numFaceR = facePQ2R.back();
|
||||
facePQ2R.resize(inP.NumTri() + inQ.NumTri());
|
||||
|
||||
outR.faceNormal_.resize(numFaceR);
|
||||
auto next = copy_if<decltype(outR.faceNormal_.begin())>(
|
||||
policy, inP.faceNormal_.begin(), inP.faceNormal_.end(), keepFace,
|
||||
outR.faceNormal_.begin(), thrust::identity<bool>());
|
||||
autoPolicy(inP.faceNormal_.size()), inP.faceNormal_.begin(),
|
||||
inP.faceNormal_.end(), keepFace, outR.faceNormal_.begin(),
|
||||
thrust::identity<bool>());
|
||||
if (invertQ) {
|
||||
auto start = thrust::make_transform_iterator(inQ.faceNormal_.begin(),
|
||||
thrust::negate<glm::vec3>());
|
||||
auto end = thrust::make_transform_iterator(inQ.faceNormal_.end(),
|
||||
thrust::negate<glm::vec3>());
|
||||
copy_if<decltype(inQ.faceNormal_.begin())>(policy, start, end,
|
||||
keepFace + inP.NumTri(), next,
|
||||
thrust::identity<bool>());
|
||||
copy_if<decltype(inQ.faceNormal_.begin())>(
|
||||
autoPolicy(inQ.faceNormal_.size()), start, end, keepFace + inP.NumTri(),
|
||||
next, thrust::identity<bool>());
|
||||
} else {
|
||||
copy_if<decltype(inQ.faceNormal_.begin())>(
|
||||
policy, inQ.faceNormal_.begin(), inQ.faceNormal_.end(),
|
||||
keepFace + inP.NumTri(), next, thrust::identity<bool>());
|
||||
autoPolicy(inQ.faceNormal_.size()), inQ.faceNormal_.begin(),
|
||||
inQ.faceNormal_.end(), keepFace + inP.NumTri(), next,
|
||||
thrust::identity<bool>());
|
||||
}
|
||||
|
||||
auto newEnd = remove<decltype(sidesPerFacePQ.begin())>(
|
||||
policy, sidesPerFacePQ.begin(), sidesPerFacePQ.end(), 0);
|
||||
VecDH<int> faceEdge(newEnd - sidesPerFacePQ.begin() + 1, 0);
|
||||
inclusive_scan(policy, sidesPerFacePQ.begin(), newEnd, faceEdge.begin() + 1);
|
||||
autoPolicy(sidesPerFacePQ.size()), sidesPerFacePQ.begin(),
|
||||
sidesPerFacePQ.end(), 0);
|
||||
Vec<int> faceEdge(newEnd - sidesPerFacePQ.begin() + 1, 0);
|
||||
inclusive_scan(autoPolicy(std::distance(sidesPerFacePQ.begin(), newEnd)),
|
||||
sidesPerFacePQ.begin(), newEnd, faceEdge.begin() + 1);
|
||||
outR.halfedge_.resize(faceEdge.back());
|
||||
|
||||
return std::make_tuple(faceEdge, facePQ2R);
|
||||
@@ -134,46 +162,71 @@ struct EdgePos {
|
||||
};
|
||||
|
||||
void AddNewEdgeVerts(
|
||||
std::map<int, std::vector<EdgePos>> &edgesP,
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
const SparseIndices &p1q2, const VecDH<int> &i12, const VecDH<int> &v12R,
|
||||
const VecDH<Halfedge> &halfedgeP, bool forward) {
|
||||
// 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,
|
||||
const SparseIndices &p1q2, const Vec<int> &i12, const Vec<int> &v12R,
|
||||
const Vec<Halfedge> &halfedgeP, bool forward) {
|
||||
// For each edge of P that intersects a face of Q (p1q2), add this vertex to
|
||||
// P's corresponding edge vector and to the two new edges, which are
|
||||
// intersections between the face of Q and the two faces of P attached to the
|
||||
// edge. The direction and duplicity are given by i12, while v12R remaps to
|
||||
// the output vert index. When forward is false, all is reversed.
|
||||
const 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];
|
||||
auto process = [&](std::function<void(size_t)> lock,
|
||||
std::function<void(size_t)> unlock, int i) {
|
||||
const int edgeP = p1q2.Get(i, !forward);
|
||||
const int faceQ = p1q2.Get(i, forward);
|
||||
const int vert = v12R[i];
|
||||
const int inclusion = i12[i];
|
||||
|
||||
auto &edgePosP = edgesP[edgeP];
|
||||
|
||||
Halfedge halfedge = halfedgeP[edgeP];
|
||||
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> keyRight = {halfedgeP[halfedge.pairedHalfedge].face,
|
||||
faceQ};
|
||||
if (!forward) std::swap(keyRight.first, keyRight.second);
|
||||
|
||||
key = {halfedge.face, faceQ};
|
||||
if (!forward) std::swap(key.first, key.second);
|
||||
auto &edgePosLeft = edgesNew[key];
|
||||
std::pair<int, int> keyLeft = {halfedge.face, faceQ};
|
||||
if (!forward) std::swap(keyLeft.first, keyLeft.second);
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
};
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/tbb.h>)
|
||||
// parallelize operations, requires concurrent_map so we can only enable this
|
||||
// with tbb
|
||||
if (!ManifoldParams().deterministic && p1q2.size() > kParallelThreshold) {
|
||||
// ideally we should have 1 mutex per key, but kParallelThreshold is enough
|
||||
// to avoid contention for most of the cases
|
||||
std::array<std::mutex, kParallelThreshold> 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) {
|
||||
@@ -189,28 +242,28 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
|
||||
[](EdgePos x) { return x.isStart; });
|
||||
ASSERT(middle - edgePos.begin() == nEdges, topologyErr, "Non-manifold edge!");
|
||||
auto cmp = [](EdgePos a, EdgePos b) { return a.edgePos < b.edgePos; };
|
||||
std::sort(edgePos.begin(), middle, cmp);
|
||||
std::sort(middle, edgePos.end(), cmp);
|
||||
std::stable_sort(edgePos.begin(), middle, cmp);
|
||||
std::stable_sort(middle, edgePos.end(), cmp);
|
||||
std::vector<Halfedge> edges;
|
||||
for (int i = 0; i < nEdges; ++i)
|
||||
edges.push_back({edgePos[i].vert, edgePos[i + nEdges].vert, -1, -1});
|
||||
return edges;
|
||||
}
|
||||
|
||||
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
VecDH<int> &facePtrR,
|
||||
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) {
|
||||
void AppendPartialEdges(Manifold::Impl &outR, Vec<char> &wholeHalfedgeP,
|
||||
Vec<int> &facePtrR,
|
||||
concurrent_map<int, std::vector<EdgePos>> &edgesP,
|
||||
Vec<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const Vec<int> &i03, const Vec<int> &vP2R,
|
||||
const Vec<int>::IterC faceP2R, bool forward) {
|
||||
// Each edge in the map is partially retained; for each of these, look up
|
||||
// their original verts and include them based on their winding number (i03),
|
||||
// while remapping them to the output using vP2R. Use the verts position
|
||||
// projected along the edge vector to pair them up, then distribute these
|
||||
// edges to their faces.
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
const VecDH<glm::vec3> &vertPosP = inP.vertPos_;
|
||||
const VecDH<Halfedge> &halfedgeP = inP.halfedge_;
|
||||
Vec<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
const Vec<glm::vec3> &vertPosP = inP.vertPos_;
|
||||
const Vec<Halfedge> &halfedgeP = inP.halfedge_;
|
||||
|
||||
for (auto &value : edgesP) {
|
||||
const int edgeP = value.first;
|
||||
@@ -281,13 +334,12 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
}
|
||||
|
||||
void AppendNewEdges(
|
||||
Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
VecDH<TriRef> &halfedgeRef, const VecDH<int> &facePQ2R,
|
||||
const int numFaceP) {
|
||||
Manifold::Impl &outR, Vec<int> &facePtrR,
|
||||
concurrent_map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
Vec<TriRef> &halfedgeRef, const Vec<int> &facePQ2R, const int numFaceP) {
|
||||
// Pair up each edge's verts and distribute to faces based on indices in key.
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
|
||||
Vec<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
Vec<glm::vec3> &vertPosR = outR.vertPos_;
|
||||
|
||||
for (auto &value : edgesNew) {
|
||||
const int faceP = value.first.first;
|
||||
@@ -334,16 +386,16 @@ void AppendNewEdges(
|
||||
}
|
||||
|
||||
struct DuplicateHalfedges {
|
||||
Halfedge *halfedgesR;
|
||||
TriRef *halfedgeRef;
|
||||
int *facePtr;
|
||||
const Halfedge *halfedgesP;
|
||||
const int *i03;
|
||||
const int *vP2R;
|
||||
const int *faceP2R;
|
||||
VecView<Halfedge> halfedgesR;
|
||||
VecView<TriRef> halfedgeRef;
|
||||
VecView<int> facePtr;
|
||||
VecView<const Halfedge> halfedgesP;
|
||||
VecView<const int> i03;
|
||||
VecView<const int> vP2R;
|
||||
VecView<const int> faceP2R;
|
||||
const bool forward;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<bool, Halfedge, int> in) {
|
||||
void operator()(thrust::tuple<bool, Halfedge, int> in) {
|
||||
if (!thrust::get<0>(in)) return;
|
||||
Halfedge halfedge = thrust::get<1>(in);
|
||||
if (!halfedge.IsForward()) return;
|
||||
@@ -385,25 +437,25 @@ struct DuplicateHalfedges {
|
||||
}
|
||||
};
|
||||
|
||||
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<char> wholeHalfedgeP, const VecDH<int> &i03,
|
||||
const VecDH<int> &vP2R, const int *faceP2R, bool forward,
|
||||
ExecutionPolicy policy) {
|
||||
for_each_n(policy,
|
||||
void AppendWholeEdges(Manifold::Impl &outR, Vec<int> &facePtrR,
|
||||
Vec<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const Vec<char> wholeHalfedgeP, const Vec<int> &i03,
|
||||
const Vec<int> &vP2R, VecView<const int> faceP2R,
|
||||
bool forward) {
|
||||
for_each_n(ManifoldParams().deterministic ? ExecutionPolicy::Seq
|
||||
: autoPolicy(inP.halfedge_.size()),
|
||||
zip(wholeHalfedgeP.begin(), inP.halfedge_.begin(), countAt(0)),
|
||||
inP.halfedge_.size(),
|
||||
DuplicateHalfedges({outR.halfedge_.ptrD(), halfedgeRef.ptrD(),
|
||||
facePtrR.ptrD(), inP.halfedge_.cptrD(),
|
||||
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
|
||||
DuplicateHalfedges({outR.halfedge_, halfedgeRef, facePtrR,
|
||||
inP.halfedge_, i03, vP2R, faceP2R, forward}));
|
||||
}
|
||||
|
||||
struct MapTriRef {
|
||||
const TriRef *triRefP;
|
||||
const TriRef *triRefQ;
|
||||
VecView<const TriRef> triRefP;
|
||||
VecView<const TriRef> triRefQ;
|
||||
const int offsetQ;
|
||||
|
||||
__host__ __device__ void operator()(TriRef &triRef) {
|
||||
void operator()(TriRef &triRef) {
|
||||
const int tri = triRef.tri;
|
||||
const bool PQ = triRef.meshID == 0;
|
||||
triRef = PQ ? triRefP[tri] : triRefQ[tri];
|
||||
@@ -411,14 +463,14 @@ struct MapTriRef {
|
||||
}
|
||||
};
|
||||
|
||||
VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ, bool invertQ,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<TriRef> refPQ = outR.meshRelation_.triRef;
|
||||
Vec<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ, bool invertQ) {
|
||||
Vec<TriRef> refPQ = outR.meshRelation_.triRef;
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
for_each_n(policy, outR.meshRelation_.triRef.begin(), outR.NumTri(),
|
||||
MapTriRef({inP.meshRelation_.triRef.cptrD(),
|
||||
inQ.meshRelation_.triRef.cptrD(), offsetQ}));
|
||||
for_each_n(
|
||||
autoPolicy(outR.NumTri()), outR.meshRelation_.triRef.begin(),
|
||||
outR.NumTri(),
|
||||
MapTriRef({inP.meshRelation_.triRef, inQ.meshRelation_.triRef, offsetQ}));
|
||||
|
||||
for (const auto &pair : inP.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
@@ -432,16 +484,16 @@ VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
glm::vec3 *uvw;
|
||||
const glm::vec3 *vertPosP;
|
||||
const glm::vec3 *vertPosQ;
|
||||
const glm::vec3 *vertPosR;
|
||||
const Halfedge *halfedgeP;
|
||||
const Halfedge *halfedgeQ;
|
||||
const Halfedge *halfedgeR;
|
||||
VecView<glm::vec3> uvw;
|
||||
VecView<const glm::vec3> vertPosP;
|
||||
VecView<const glm::vec3> vertPosQ;
|
||||
VecView<const glm::vec3> vertPosR;
|
||||
VecView<const Halfedge> halfedgeP;
|
||||
VecView<const Halfedge> halfedgeQ;
|
||||
VecView<const Halfedge> halfedgeR;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, TriRef> in) {
|
||||
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;
|
||||
@@ -462,9 +514,8 @@ struct Barycentric {
|
||||
}
|
||||
};
|
||||
|
||||
void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
ExecutionPolicy policy) {
|
||||
void CreateProperties(Manifold::Impl &outR, const Vec<TriRef> &refPQ,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ) {
|
||||
const int numPropP = inP.NumProp();
|
||||
const int numPropQ = inQ.NumProp();
|
||||
const int numProp = glm::max(numPropP, numPropQ);
|
||||
@@ -474,12 +525,11 @@ void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
const int numTri = outR.NumTri();
|
||||
outR.meshRelation_.triProperties.resize(numTri);
|
||||
|
||||
VecDH<glm::vec3> bary(outR.halfedge_.size());
|
||||
for_each_n(policy, zip(countAt(0), refPQ.cbegin()), numTri,
|
||||
Barycentric({bary.ptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), outR.vertPos_.cptrD(),
|
||||
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
outR.halfedge_.cptrD(), outR.precision_}));
|
||||
Vec<glm::vec3> bary(outR.halfedge_.size());
|
||||
for_each_n(autoPolicy(numTri), zip(countAt(0), refPQ.cbegin()), numTri,
|
||||
Barycentric({bary, inP.vertPos_, inQ.vertPos_, outR.vertPos_,
|
||||
inP.halfedge_, inQ.halfedge_, outR.halfedge_,
|
||||
outR.precision_}));
|
||||
|
||||
using Entry = std::pair<glm::ivec3, int>;
|
||||
int idMissProp = outR.NumVert();
|
||||
@@ -583,39 +633,44 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
const bool invertQ = op == OpType::Subtract;
|
||||
|
||||
// Convert winding numbers to inclusion values based on operation type.
|
||||
VecDH<int> i12(x12_.size());
|
||||
VecDH<int> i21(x21_.size());
|
||||
VecDH<int> i03(w03_.size());
|
||||
VecDH<int> i30(w30_.size());
|
||||
Vec<int> i12(x12_.size());
|
||||
Vec<int> i21(x21_.size());
|
||||
Vec<int> i03(w03_.size());
|
||||
Vec<int> i30(w30_.size());
|
||||
|
||||
transform(policy_, x12_.begin(), x12_.end(), i12.begin(), c3 * _1);
|
||||
transform(policy_, x21_.begin(), x21_.end(), i21.begin(), c3 * _1);
|
||||
transform(policy_, w03_.begin(), w03_.end(), i03.begin(), c1 + c3 * _1);
|
||||
transform(policy_, w30_.begin(), w30_.end(), i30.begin(), c2 + c3 * _1);
|
||||
transform(autoPolicy(x12_.size()), x12_.begin(), x12_.end(), i12.begin(),
|
||||
c3 * _1);
|
||||
transform(autoPolicy(x21_.size()), x21_.begin(), x21_.end(), i21.begin(),
|
||||
c3 * _1);
|
||||
transform(autoPolicy(w03_.size()), w03_.begin(), w03_.end(), i03.begin(),
|
||||
c1 + c3 * _1);
|
||||
transform(autoPolicy(w30_.size()), w30_.begin(), w30_.end(), i30.begin(),
|
||||
c2 + c3 * _1);
|
||||
|
||||
VecDH<int> vP2R(inP_.NumVert());
|
||||
exclusive_scan(policy_, i03.begin(), i03.end(), vP2R.begin(), 0, AbsSum());
|
||||
Vec<int> vP2R(inP_.NumVert());
|
||||
exclusive_scan(autoPolicy(i03.size()), i03.begin(), i03.end(), vP2R.begin(),
|
||||
0, AbsSum());
|
||||
int numVertR = AbsSum()(vP2R.back(), i03.back());
|
||||
const int nPv = numVertR;
|
||||
|
||||
VecDH<int> vQ2R(inQ_.NumVert());
|
||||
exclusive_scan(policy_, i30.begin(), i30.end(), vQ2R.begin(), numVertR,
|
||||
AbsSum());
|
||||
Vec<int> vQ2R(inQ_.NumVert());
|
||||
exclusive_scan(autoPolicy(i30.size()), i30.begin(), i30.end(), vQ2R.begin(),
|
||||
numVertR, AbsSum());
|
||||
numVertR = AbsSum()(vQ2R.back(), i30.back());
|
||||
const int nQv = numVertR - nPv;
|
||||
|
||||
VecDH<int> v12R(v12_.size());
|
||||
Vec<int> v12R(v12_.size());
|
||||
if (v12_.size() > 0) {
|
||||
exclusive_scan(policy_, i12.begin(), i12.end(), v12R.begin(), numVertR,
|
||||
AbsSum());
|
||||
exclusive_scan(autoPolicy(i12.size()), i12.begin(), i12.end(), v12R.begin(),
|
||||
numVertR, AbsSum());
|
||||
numVertR = AbsSum()(v12R.back(), i12.back());
|
||||
}
|
||||
const int n12 = numVertR - nPv - nQv;
|
||||
|
||||
VecDH<int> v21R(v21_.size());
|
||||
Vec<int> v21R(v21_.size());
|
||||
if (v21_.size() > 0) {
|
||||
exclusive_scan(policy_, i21.begin(), i21.end(), v21R.begin(), numVertR,
|
||||
AbsSum());
|
||||
exclusive_scan(autoPolicy(i21.size()), i21.begin(), i21.end(), v21R.begin(),
|
||||
numVertR, AbsSum());
|
||||
numVertR = AbsSum()(v21R.back(), i21.back());
|
||||
}
|
||||
const int n21 = numVertR - nPv - nQv - n12;
|
||||
@@ -630,15 +685,19 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
outR.vertPos_.resize(numVertR);
|
||||
// Add vertices, duplicating for inclusion numbers not in [-1, 1].
|
||||
// Retained vertices from P and Q:
|
||||
for_each_n(policy_, zip(i03.begin(), vP2R.begin(), inP_.vertPos_.begin()),
|
||||
inP_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(policy_, zip(i30.begin(), vQ2R.begin(), inQ_.vertPos_.begin()),
|
||||
inQ_.NumVert(), DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(autoPolicy(inP_.NumVert()),
|
||||
zip(i03.begin(), vP2R.begin(), inP_.vertPos_.begin()),
|
||||
inP_.NumVert(), DuplicateVerts({outR.vertPos_}));
|
||||
for_each_n(autoPolicy(inQ_.NumVert()),
|
||||
zip(i30.begin(), vQ2R.begin(), inQ_.vertPos_.begin()),
|
||||
inQ_.NumVert(), DuplicateVerts({outR.vertPos_}));
|
||||
// New vertices created from intersections:
|
||||
for_each_n(policy_, zip(i12.begin(), v12R.begin(), v12_.begin()), i12.size(),
|
||||
DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(policy_, zip(i21.begin(), v21R.begin(), v21_.begin()), i21.size(),
|
||||
DuplicateVerts({outR.vertPos_.ptrD()}));
|
||||
for_each_n(autoPolicy(i12.size()),
|
||||
zip(i12.begin(), v12R.begin(), v12_.begin()), i12.size(),
|
||||
DuplicateVerts({outR.vertPos_}));
|
||||
for_each_n(autoPolicy(i21.size()),
|
||||
zip(i21.begin(), v21R.begin(), v21_.begin()), i21.size(),
|
||||
DuplicateVerts({outR.vertPos_}));
|
||||
|
||||
PRINT(nPv << " verts from inP");
|
||||
PRINT(nQv << " verts from inQ");
|
||||
@@ -652,29 +711,29 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
|
||||
// This key is the forward halfedge index of P or Q. Only includes intersected
|
||||
// edges.
|
||||
std::map<int, std::vector<EdgePos>> edgesP, edgesQ;
|
||||
concurrent_map<int, std::vector<EdgePos>> edgesP, edgesQ;
|
||||
// This key is the face index of <P, Q>
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> edgesNew;
|
||||
concurrent_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);
|
||||
|
||||
// Level 4
|
||||
VecDH<int> faceEdge;
|
||||
VecDH<int> facePQ2R;
|
||||
std::tie(faceEdge, facePQ2R) = SizeOutput(
|
||||
outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ, policy_);
|
||||
Vec<int> faceEdge;
|
||||
Vec<int> facePQ2R;
|
||||
std::tie(faceEdge, facePQ2R) =
|
||||
SizeOutput(outR, inP_, inQ_, i03, i30, i12, i21, p1q2_, p2q1_, invertQ);
|
||||
|
||||
const int numFaceR = faceEdge.size() - 1;
|
||||
// This gets incremented for each halfedge that's added to a face so that the
|
||||
// next one knows where to slot in.
|
||||
VecDH<int> facePtrR = faceEdge;
|
||||
Vec<int> facePtrR = faceEdge;
|
||||
// Intersected halfedges are marked false.
|
||||
VecDH<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
VecDH<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
Vec<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
Vec<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
// The halfedgeRef contains the data that will become triRef once the faces
|
||||
// are triangulated.
|
||||
VecDH<TriRef> halfedgeRef(2 * outR.NumEdge());
|
||||
Vec<TriRef> halfedgeRef(2 * outR.NumEdge());
|
||||
|
||||
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
|
||||
i03, vP2R, facePQ2R.begin(), true);
|
||||
@@ -685,9 +744,9 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
inP_.NumTri());
|
||||
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inP_, wholeHalfedgeP, i03, vP2R,
|
||||
facePQ2R.cptrD(), true, policy_);
|
||||
facePQ2R.cview(0, inP_.NumTri()), true);
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
|
||||
facePQ2R.cptrD() + inP_.NumTri(), false, policy_);
|
||||
facePQ2R.cview(inP_.NumTri(), inQ_.NumTri()), false);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
assemble.Stop();
|
||||
@@ -711,11 +770,11 @@ Manifold::Impl Boolean3::Result(OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
|
||||
|
||||
VecDH<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ, policy_);
|
||||
Vec<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ);
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
CreateProperties(outR, refPQ, inP_, inQ_, policy_);
|
||||
CreateProperties(outR, refPQ, inP_, inQ_);
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "csg_tree.h"
|
||||
#include "graph.h"
|
||||
#include "impl.h"
|
||||
#include "par.h"
|
||||
#include "polygon.h"
|
||||
@@ -27,7 +26,7 @@ using namespace thrust::placeholders;
|
||||
|
||||
struct ToSphere {
|
||||
float length;
|
||||
__host__ __device__ void operator()(glm::vec3& v) {
|
||||
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,15 +35,15 @@ struct ToSphere {
|
||||
|
||||
struct Equals {
|
||||
int val;
|
||||
__host__ __device__ bool operator()(int x) { return x == val; }
|
||||
bool operator()(int x) { return x == val; }
|
||||
};
|
||||
|
||||
struct RemoveFace {
|
||||
const Halfedge* halfedge;
|
||||
const int* vertLabel;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const int> vertLabel;
|
||||
const int keepLabel;
|
||||
|
||||
__host__ __device__ bool operator()(int face) {
|
||||
bool operator()(int face) {
|
||||
return vertLabel[halfedge[3 * face].startVert] != keepLabel;
|
||||
}
|
||||
};
|
||||
@@ -247,7 +246,7 @@ Manifold Manifold::Extrude(const CrossSection& crossSection, float height,
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
++nDivisions;
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
VecDH<glm::ivec3> triVertsDH;
|
||||
Vec<glm::ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH;
|
||||
int nCrossSection = 0;
|
||||
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
|
||||
@@ -359,7 +358,7 @@ Manifold Manifold::Revolve(const CrossSection& crossSection,
|
||||
|
||||
auto pImpl_ = std::make_shared<Impl>();
|
||||
auto& vertPos = pImpl_->vertPos_;
|
||||
VecDH<glm::ivec3> triVertsDH;
|
||||
Vec<glm::ivec3> triVertsDH;
|
||||
auto& triVerts = triVertsDH;
|
||||
|
||||
std::vector<int> startPoses;
|
||||
@@ -465,24 +464,21 @@ Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
|
||||
* containing a copy of the original. It is the inverse operation of Compose().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
Graph graph;
|
||||
UnionFind<> uf(NumVert());
|
||||
// Graph graph;
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
for (int i = 0; i < NumVert(); ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (const Halfedge& halfedge : pImpl_->halfedge_) {
|
||||
if (halfedge.IsForward())
|
||||
graph.add_edge(halfedge.startVert, halfedge.endVert);
|
||||
if (halfedge.IsForward()) uf.unionXY(halfedge.startVert, halfedge.endVert);
|
||||
}
|
||||
std::vector<int> componentIndices;
|
||||
const int numComponents = ConnectedComponents(componentIndices, graph);
|
||||
const int numComponents = uf.connectedComponents(componentIndices);
|
||||
|
||||
if (numComponents == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
VecDH<int> vertLabel(componentIndices);
|
||||
Vec<int> vertLabel(componentIndices);
|
||||
|
||||
std::vector<Manifold> meshes;
|
||||
for (int i = 0; i < numComponents; ++i) {
|
||||
@@ -490,7 +486,7 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
// inherit original object's precision
|
||||
impl->precision_ = pImpl_->precision_;
|
||||
impl->vertPos_.resize(NumVert());
|
||||
VecDH<int> vertNew2Old(NumVert());
|
||||
Vec<int> vertNew2Old(NumVert());
|
||||
auto policy = autoPolicy(NumVert());
|
||||
auto start = zip(impl->vertPos_.begin(), vertNew2Old.begin());
|
||||
int nVert =
|
||||
@@ -501,14 +497,13 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
start;
|
||||
impl->vertPos_.resize(nVert);
|
||||
|
||||
VecDH<int> faceNew2Old(NumTri());
|
||||
Vec<int> faceNew2Old(NumTri());
|
||||
sequence(policy, faceNew2Old.begin(), faceNew2Old.end());
|
||||
|
||||
int nFace =
|
||||
remove_if<decltype(faceNew2Old.begin())>(
|
||||
policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
RemoveFace({pImpl_->halfedge_.cptrD(), vertLabel.cptrD(), i})) -
|
||||
faceNew2Old.begin();
|
||||
int nFace = remove_if<decltype(faceNew2Old.begin())>(
|
||||
policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
RemoveFace({pImpl_->halfedge_, vertLabel, i})) -
|
||||
faceNew2Old.begin();
|
||||
faceNew2Old.resize(nFace);
|
||||
|
||||
impl->GatherFaces(*pImpl_, faceNew2Old);
|
||||
|
||||
+114
-65
@@ -12,21 +12,29 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "csg_tree.h"
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/concurrent_priority_queue.h>)
|
||||
#include <tbb/tbb.h>
|
||||
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
|
||||
#include <tbb/concurrent_priority_queue.h>
|
||||
#endif
|
||||
|
||||
#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;
|
||||
|
||||
__host__ __device__ glm::vec3 operator()(glm::vec3 position) {
|
||||
glm::vec3 operator()(glm::vec3 position) {
|
||||
return transform * glm::vec4(position, 1.0f);
|
||||
}
|
||||
};
|
||||
@@ -36,7 +44,7 @@ struct UpdateHalfedge {
|
||||
const int nextEdge;
|
||||
const int nextFace;
|
||||
|
||||
__host__ __device__ Halfedge operator()(Halfedge edge) {
|
||||
Halfedge operator()(Halfedge edge) {
|
||||
edge.startVert += nextVert;
|
||||
edge.endVert += nextVert;
|
||||
edge.pairedHalfedge += nextEdge;
|
||||
@@ -48,7 +56,7 @@ struct UpdateHalfedge {
|
||||
struct UpdateTriProp {
|
||||
const int nextProp;
|
||||
|
||||
__host__ __device__ glm::ivec3 operator()(glm::ivec3 tri) {
|
||||
glm::ivec3 operator()(glm::ivec3 tri) {
|
||||
tri += nextProp;
|
||||
return tri;
|
||||
}
|
||||
@@ -57,19 +65,36 @@ struct UpdateTriProp {
|
||||
struct UpdateMeshIDs {
|
||||
const int offset;
|
||||
|
||||
__host__ __device__ TriRef operator()(TriRef ref) {
|
||||
TriRef operator()(TriRef ref) {
|
||||
ref.meshID += offset;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckOverlap {
|
||||
const Box *boxes;
|
||||
VecView<const Box> boxes;
|
||||
const size_t i;
|
||||
__host__ __device__ bool operator()(int j) {
|
||||
return boxes[i].DoesOverlap(boxes[j]);
|
||||
bool operator()(int j) { return boxes[i].DoesOverlap(boxes[j]); }
|
||||
};
|
||||
|
||||
using SharedImpl = std::variant<std::shared_ptr<const Manifold::Impl>,
|
||||
std::shared_ptr<Manifold::Impl>>;
|
||||
struct GetImplPtr {
|
||||
const Manifold::Impl *operator()(const SharedImpl &p) {
|
||||
if (std::holds_alternative<std::shared_ptr<const Manifold::Impl>>(p)) {
|
||||
return std::get_if<std::shared_ptr<const Manifold::Impl>>(&p)->get();
|
||||
} else {
|
||||
return std::get_if<std::shared_ptr<Manifold::Impl>>(&p)->get();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
struct MeshCompare {
|
||||
bool operator()(const SharedImpl &a, const SharedImpl &b) {
|
||||
return GetImplPtr()(a)->NumVert() < GetImplPtr()(b)->NumVert();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
namespace manifold {
|
||||
|
||||
@@ -197,12 +222,10 @@ 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 ||
|
||||
(policy == ExecutionPolicy::ParUnseq && !CudaEnabled())))
|
||||
if (nodes.size() > 1 && policy == ExecutionPolicy::Par)
|
||||
policy = ExecutionPolicy::Seq;
|
||||
|
||||
for_each_n_host(
|
||||
for_each_n(
|
||||
nodes.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
|
||||
countAt(0), nodes.size(),
|
||||
[&nodes, &vertIndices, &edgeIndices, &triIndices, &propVertIndices,
|
||||
@@ -229,9 +252,9 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
auto &oldProp = node->pImpl_->meshRelation_.properties;
|
||||
auto &newProp = combined.meshRelation_.properties;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
strided_range<VecDH<float>::IterC> oldRange(
|
||||
oldProp.begin() + p, oldProp.end(), numProp);
|
||||
strided_range<VecDH<float>::Iter> newRange(
|
||||
strided_range<Vec<float>::IterC> oldRange(oldProp.begin() + p,
|
||||
oldProp.end(), numProp);
|
||||
strided_range<Vec<float>::Iter> newRange(
|
||||
newProp.begin() + numPropOut * propVertIndices[i] + p,
|
||||
newProp.end(), numPropOut);
|
||||
copy(policy, oldRange.begin(), oldRange.end(), newRange.begin());
|
||||
@@ -271,14 +294,13 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
countAt(0)),
|
||||
node->pImpl_->halfedgeTangent_.size(),
|
||||
TransformTangents{glm::mat3(node->transform_), invert,
|
||||
node->pImpl_->halfedgeTangent_.cptrD(),
|
||||
node->pImpl_->halfedge_.cptrD()});
|
||||
node->pImpl_->halfedgeTangent_,
|
||||
node->pImpl_->halfedge_});
|
||||
if (invert)
|
||||
for_each_n(policy,
|
||||
zip(combined.meshRelation_.triRef.begin(),
|
||||
countAt(triIndices[i])),
|
||||
node->pImpl_->NumTri(),
|
||||
FlipTris({combined.halfedge_.ptrD()}));
|
||||
node->pImpl_->NumTri(), FlipTris({combined.halfedge_}));
|
||||
}
|
||||
// Since the nodes may be copies containing the same meshIDs, it is
|
||||
// important to add an offset so that each node instance gets
|
||||
@@ -432,17 +454,54 @@ 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) {
|
||||
auto getImplPtr = GetImplPtr();
|
||||
ASSERT(operation != OpType::Subtract, logicErr,
|
||||
"BatchBoolean doesn't support Difference.");
|
||||
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>)
|
||||
if (!ManifoldParams().deterministic) {
|
||||
tbb::task_group group;
|
||||
tbb::concurrent_priority_queue<SharedImpl, MeshCompare> queue(
|
||||
results.size());
|
||||
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_if<std::shared_ptr<Manifold::Impl>>(&r);
|
||||
}
|
||||
#endif
|
||||
// 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
|
||||
auto cmpFn = MeshCompare();
|
||||
std::make_heap(results.begin(), results.end(), cmpFn);
|
||||
while (results.size() > 1) {
|
||||
std::pop_heap(results.begin(), results.end(), cmpFn);
|
||||
@@ -460,10 +519,7 @@ std::shared_ptr<Manifold::Impl> CsgOpNode::BatchBoolean(
|
||||
std::make_shared<const Manifold::Impl>(boolean.Result(operation)));
|
||||
std::push_heap(results.begin(), results.end(), cmpFn);
|
||||
}
|
||||
if (results.size() == 1) {
|
||||
return std::make_shared<Manifold::Impl>(*results.front());
|
||||
}
|
||||
return std::make_shared<Manifold::Impl>();
|
||||
return std::make_shared<Manifold::Impl>(*results.front());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,22 +541,20 @@ void CsgOpNode::BatchUnion() const {
|
||||
const int start = (children_.size() > kMaxUnionSize)
|
||||
? (children_.size() - kMaxUnionSize)
|
||||
: 0;
|
||||
VecDH<Box> boxes;
|
||||
Vec<Box> boxes;
|
||||
boxes.reserve(children_.size() - start);
|
||||
for (int i = start; i < children_.size(); i++) {
|
||||
boxes.push_back(std::dynamic_pointer_cast<CsgLeafNode>(children_[i])
|
||||
->GetImpl()
|
||||
->bBox_);
|
||||
}
|
||||
const Box *boxesD = boxes.cptrD();
|
||||
// partition the children into a set of disjoint sets
|
||||
// each set contains a set of children that are pairwise disjoint
|
||||
std::vector<VecDH<size_t>> disjointSets;
|
||||
std::vector<Vec<size_t>> disjointSets;
|
||||
for (size_t i = 0; i < boxes.size(); i++) {
|
||||
auto lambda = [boxesD, i](const VecDH<size_t> &set) {
|
||||
return find_if<decltype(set.end())>(
|
||||
autoPolicy(set.size()), set.begin(), set.end(),
|
||||
CheckOverlap({boxesD, i})) == set.end();
|
||||
auto lambda = [&boxes, i](const Vec<size_t> &set) {
|
||||
return std::find_if(set.begin(), set.end(), CheckOverlap({boxes, i})) ==
|
||||
set.end();
|
||||
};
|
||||
auto it = std::find_if(disjointSets.begin(), disjointSets.end(), lambda);
|
||||
if (it == disjointSets.end()) {
|
||||
@@ -510,27 +564,22 @@ void CsgOpNode::BatchUnion() const {
|
||||
}
|
||||
}
|
||||
// compose each set of disjoint children
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls(
|
||||
disjointSets.size());
|
||||
for_each_n_host(
|
||||
disjointSets.size() > 1 ? ExecutionPolicy::Par : ExecutionPolicy::Seq,
|
||||
countAt(0), disjointSets.size(),
|
||||
[children_, &impls, &disjointSets, start](int i) {
|
||||
auto set = disjointSets[i];
|
||||
if (set.size() == 1) {
|
||||
impls[i] = std::dynamic_pointer_cast<CsgLeafNode>(
|
||||
children_[start + set[0]])
|
||||
->GetImpl();
|
||||
} else {
|
||||
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
|
||||
for (size_t j : set) {
|
||||
tmp.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + j]));
|
||||
}
|
||||
impls[i] = std::make_shared<const Manifold::Impl>(
|
||||
CsgLeafNode::Compose(tmp));
|
||||
}
|
||||
});
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls;
|
||||
for (auto &set : disjointSets) {
|
||||
if (set.size() == 1) {
|
||||
impls.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + set[0]])
|
||||
->GetImpl());
|
||||
} else {
|
||||
std::vector<std::shared_ptr<CsgLeafNode>> tmp;
|
||||
for (size_t j : set) {
|
||||
tmp.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(children_[start + j]));
|
||||
}
|
||||
impls.push_back(
|
||||
std::make_shared<const Manifold::Impl>(CsgLeafNode::Compose(tmp)));
|
||||
}
|
||||
}
|
||||
|
||||
children_.erase(children_.begin() + start, children_.end());
|
||||
children_.push_back(
|
||||
@@ -554,14 +603,14 @@ std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
|
||||
|
||||
if (forceToLeafNodes && !impl->forcedToLeafNodes_) {
|
||||
impl->forcedToLeafNodes_ = true;
|
||||
for_each_host(impl->children_.size() > 1 ? ExecutionPolicy::Par
|
||||
: ExecutionPolicy::Seq,
|
||||
impl->children_.begin(), impl->children_.end(),
|
||||
[](auto &child) {
|
||||
if (child->GetNodeType() != CsgNodeType::Leaf) {
|
||||
child = child->ToLeafNode();
|
||||
}
|
||||
});
|
||||
for_each(impl->children_.size() > 1 && !ManifoldParams().deterministic
|
||||
? ExecutionPolicy::Par
|
||||
: ExecutionPolicy::Seq,
|
||||
impl->children_.begin(), impl->children_.end(), [](auto &child) {
|
||||
if (child->GetNodeType() != CsgNodeType::Leaf) {
|
||||
child = child->ToLeafNode();
|
||||
}
|
||||
});
|
||||
}
|
||||
return impl->children_;
|
||||
}
|
||||
|
||||
+123
-54
@@ -18,7 +18,7 @@
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
__host__ __device__ glm::ivec3 TriOf(int edge) {
|
||||
glm::ivec3 TriOf(int edge) {
|
||||
glm::ivec3 triEdge;
|
||||
triEdge[0] = edge;
|
||||
triEdge[1] = NextHalfedge(triEdge[0]);
|
||||
@@ -26,7 +26,7 @@ __host__ __device__ glm::ivec3 TriOf(int edge) {
|
||||
return triEdge;
|
||||
}
|
||||
|
||||
__host__ __device__ bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
|
||||
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 @@ __host__ __device__ bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
|
||||
struct DuplicateEdge {
|
||||
const Halfedge* sortedHalfedge;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
bool operator()(int edge) {
|
||||
const Halfedge& halfedge = sortedHalfedge[edge];
|
||||
const Halfedge& nextHalfedge = sortedHalfedge[edge + 1];
|
||||
return halfedge.startVert == nextHalfedge.startVert &&
|
||||
@@ -45,11 +45,11 @@ struct DuplicateEdge {
|
||||
};
|
||||
|
||||
struct ShortEdge {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) const {
|
||||
bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag short edges
|
||||
const glm::vec3 delta =
|
||||
@@ -59,10 +59,10 @@ struct ShortEdge {
|
||||
};
|
||||
|
||||
struct FlagEdge {
|
||||
const Halfedge* halfedge;
|
||||
const TriRef* triRef;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const TriRef> triRef;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) const {
|
||||
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.
|
||||
@@ -82,12 +82,12 @@ struct FlagEdge {
|
||||
};
|
||||
|
||||
struct SwappableEdge {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const glm::vec3* triNormal;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
VecView<const glm::vec3> triNormal;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) const {
|
||||
bool operator()(int edge) const {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
|
||||
int tri = halfedge[edge].face;
|
||||
@@ -145,23 +145,25 @@ namespace manifold {
|
||||
void Manifold::Impl::SimplifyTopology() {
|
||||
if (!halfedge_.size()) return;
|
||||
|
||||
int nbEdges = halfedge_.size();
|
||||
const int nbEdges = halfedge_.size();
|
||||
auto policy = autoPolicy(nbEdges);
|
||||
int numFlagged = 0;
|
||||
VecDH<uint8_t> bflags(nbEdges);
|
||||
uint8_t* bflagsPtr = bflags.ptrD();
|
||||
Vec<uint8_t> bflags(nbEdges);
|
||||
|
||||
VecDH<SortEntry> entries(nbEdges);
|
||||
SortEntry* entriesPtr = entries.ptrD();
|
||||
// In the case of a very bad triangulation, it is possible to create pinched
|
||||
// verts. They must be removed before edge collapse.
|
||||
SplitPinchedVerts();
|
||||
|
||||
auto policy = autoPolicy(halfedge_.size());
|
||||
for_each_n(policy, countAt(0), nbEdges, [=] __host__ __device__(int i) {
|
||||
entriesPtr[i].start = halfedge_[i].startVert;
|
||||
entriesPtr[i].end = halfedge_[i].endVert;
|
||||
entriesPtr[i].index = i;
|
||||
});
|
||||
Vec<SortEntry> entries;
|
||||
entries.reserve(nbEdges / 2);
|
||||
for (int i = 0; i < nbEdges; ++i) {
|
||||
if (halfedge_[i].IsForward()) {
|
||||
entries.push_back({halfedge_[i].startVert, halfedge_[i].endVert, i});
|
||||
}
|
||||
}
|
||||
|
||||
sort(policy, entries.begin(), entries.end());
|
||||
for (int i = 0; i < nbEdges - 1; ++i) {
|
||||
stable_sort(policy, entries.begin(), entries.end());
|
||||
for (int i = 0; i < entries.size() - 1; ++i) {
|
||||
if (entries[i].start == entries[i + 1].start &&
|
||||
entries[i].end == entries[i + 1].end) {
|
||||
DedupeEdge(entries[i].index);
|
||||
@@ -176,14 +178,16 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<int> scratchBuffer;
|
||||
scratchBuffer.reserve(10);
|
||||
{
|
||||
numFlagged = 0;
|
||||
ShortEdge se{halfedge_.cptrD(), vertPos_.cptrD(), precision_};
|
||||
for_each_n(policy, countAt(0), nbEdges,
|
||||
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
|
||||
ShortEdge se{halfedge_, vertPos_, precision_};
|
||||
for_each_n(policy, countAt(0), nbEdges, [&](int i) { bflags[i] = se(i); });
|
||||
for (int i = 0; i < nbEdges; ++i) {
|
||||
if (bflags[i]) {
|
||||
CollapseEdge(i);
|
||||
CollapseEdge(i, scratchBuffer);
|
||||
scratchBuffer.resize(0);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
@@ -198,12 +202,12 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
|
||||
{
|
||||
numFlagged = 0;
|
||||
FlagEdge se{halfedge_.cptrD(), meshRelation_.triRef.cptrD()};
|
||||
for_each_n(policy, countAt(0), nbEdges,
|
||||
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
|
||||
FlagEdge se{halfedge_, meshRelation_.triRef};
|
||||
for_each_n(policy, countAt(0), nbEdges, [&](int i) { bflags[i] = se(i); });
|
||||
for (int i = 0; i < nbEdges; ++i) {
|
||||
if (bflags[i]) {
|
||||
CollapseEdge(i);
|
||||
CollapseEdge(i, scratchBuffer);
|
||||
scratchBuffer.resize(0);
|
||||
numFlagged++;
|
||||
}
|
||||
}
|
||||
@@ -218,14 +222,21 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
|
||||
{
|
||||
numFlagged = 0;
|
||||
SwappableEdge se{halfedge_.cptrD(), vertPos_.cptrD(), faceNormal_.cptrD(),
|
||||
precision_};
|
||||
for_each_n(policy, countAt(0), nbEdges,
|
||||
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
|
||||
SwappableEdge se{halfedge_, vertPos_, faceNormal_, precision_};
|
||||
for_each_n(policy, countAt(0), nbEdges, [&](int i) { bflags[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,6 +248,8 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Deduplicate the given 4-manifold edge by duplicating endVert, thus making the
|
||||
// edges distinct. Also duplicates startVert if it becomes pinched.
|
||||
void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
// Orbit endVert
|
||||
const int startVert = halfedge_[edge].startVert;
|
||||
@@ -245,6 +258,7 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
while (current != edge) {
|
||||
const int vert = halfedge_[current].startVert;
|
||||
if (vert == startVert) {
|
||||
// Single topological unit needs 2 faces added to be split
|
||||
const int newVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[endVert]);
|
||||
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
|
||||
@@ -291,6 +305,45 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
|
||||
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
|
||||
}
|
||||
|
||||
if (current == edge) {
|
||||
// Separate topological unit needs no new faces to be split
|
||||
const int newVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[endVert]);
|
||||
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
|
||||
|
||||
do {
|
||||
halfedge_[current].endVert = newVert;
|
||||
current = NextHalfedge(current);
|
||||
halfedge_[current].startVert = newVert;
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
} while (current != edge);
|
||||
}
|
||||
|
||||
// Orbit startVert
|
||||
const int pair = halfedge_[edge].pairedHalfedge;
|
||||
current = halfedge_[NextHalfedge(pair)].pairedHalfedge;
|
||||
while (current != pair) {
|
||||
const int vert = halfedge_[current].startVert;
|
||||
if (vert == endVert) {
|
||||
break; // Connected: not a pinched vert
|
||||
}
|
||||
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
|
||||
}
|
||||
|
||||
if (current == pair) {
|
||||
// Split the pinched vert the previous split created.
|
||||
const int newVert = vertPos_.size();
|
||||
vertPos_.push_back(vertPos_[endVert]);
|
||||
if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);
|
||||
|
||||
do {
|
||||
halfedge_[current].endVert = newVert;
|
||||
current = NextHalfedge(current);
|
||||
halfedge_[current].startVert = newVert;
|
||||
current = halfedge_[current].pairedHalfedge;
|
||||
} while (current != pair);
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::PairUp(int edge0, int edge1) {
|
||||
@@ -336,6 +389,7 @@ void Manifold::Impl::FormLoop(int current, int end) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseTri(const glm::ivec3& triEdge) {
|
||||
if (halfedge_[triEdge[1]].pairedHalfedge == -1) return;
|
||||
int pair1 = halfedge_[triEdge[1]].pairedHalfedge;
|
||||
int pair2 = halfedge_[triEdge[2]].pairedHalfedge;
|
||||
halfedge_[pair1].pairedHalfedge = pair2;
|
||||
@@ -348,6 +402,7 @@ void Manifold::Impl::CollapseTri(const glm::ivec3& triEdge) {
|
||||
void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
const glm::ivec3 tri0edge = TriOf(edge);
|
||||
const glm::ivec3 tri1edge = TriOf(halfedge_[edge].pairedHalfedge);
|
||||
if (halfedge_[tri0edge[1]].pairedHalfedge == -1) return;
|
||||
if (halfedge_[tri0edge[1]].endVert == halfedge_[tri1edge[1]].endVert) {
|
||||
if (halfedge_[tri0edge[1]].pairedHalfedge == tri1edge[2]) {
|
||||
if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {
|
||||
@@ -372,9 +427,13 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
}
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
// Collapses the given edge by removing startVert. May split the mesh
|
||||
// topologically if the collapse would have resulted in a 4-manifold edge. Do
|
||||
// not collapse an edge if startVert is pinched - the vert will be marked NaN,
|
||||
// but other edges may still be pointing to it.
|
||||
void Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
Vec<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
|
||||
const Halfedge toRemove = halfedge_[edge];
|
||||
if (toRemove.pairedHalfedge < 0) return;
|
||||
@@ -388,7 +447,6 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
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]) {
|
||||
@@ -483,13 +541,19 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
RemoveIfFolded(start);
|
||||
}
|
||||
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,
|
||||
std::vector<int>& visited,
|
||||
std::vector<int>& edgeSwapStack,
|
||||
std::vector<int>& edges) {
|
||||
Vec<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);
|
||||
@@ -530,8 +594,8 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
|
||||
// Update properties if applicable
|
||||
if (meshRelation_.properties.size() > 0) {
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
VecDH<float>& prop = meshRelation_.properties;
|
||||
Vec<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
Vec<float>& prop = meshRelation_.properties;
|
||||
triProp[tri0] = triProp[tri1];
|
||||
triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]];
|
||||
triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]];
|
||||
@@ -568,12 +632,14 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
SwapEdge();
|
||||
const glm::vec2 e23 = v[3] - v[2];
|
||||
if (glm::dot(e23, e23) < precision_ * precision_) {
|
||||
CollapseEdge(tri0edge[2]);
|
||||
tag++;
|
||||
CollapseEdge(tri0edge[2], edges);
|
||||
edges.resize(0);
|
||||
} else {
|
||||
RecursiveEdgeSwap(tri0edge[0]);
|
||||
RecursiveEdgeSwap(tri0edge[1]);
|
||||
RecursiveEdgeSwap(tri1edge[0]);
|
||||
RecursiveEdgeSwap(tri1edge[1]);
|
||||
visited[edge] = tag;
|
||||
visited[pair] = tag;
|
||||
edgeSwapStack.insert(edgeSwapStack.end(), {tri1edge[1], tri1edge[0],
|
||||
tri0edge[1], tri0edge[0]});
|
||||
}
|
||||
return;
|
||||
} else if (CCW(v[0], v[3], v[2], precision_) <= 0 ||
|
||||
@@ -582,8 +648,11 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
}
|
||||
// Normal path
|
||||
SwapEdge();
|
||||
RecursiveEdgeSwap(halfedge_[tri0edge[1]].pairedHalfedge);
|
||||
RecursiveEdgeSwap(halfedge_[tri1edge[0]].pairedHalfedge);
|
||||
visited[edge] = tag;
|
||||
visited[pair] = tag;
|
||||
edgeSwapStack.insert(edgeSwapStack.end(),
|
||||
{halfedge_[tri1edge[0]].pairedHalfedge,
|
||||
halfedge_[tri0edge[1]].pairedHalfedge});
|
||||
}
|
||||
|
||||
void Manifold::Impl::SplitPinchedVerts() {
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if MANIFOLD_PAR == 'T' && __has_include(<tbb/concurrent_map.h>)
|
||||
#include <tbb/tbb.h>
|
||||
#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1
|
||||
#include <tbb/concurrent_map.h>
|
||||
#endif
|
||||
#include <map>
|
||||
|
||||
#include "impl.h"
|
||||
@@ -19,6 +24,9 @@
|
||||
|
||||
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
|
||||
@@ -29,17 +37,14 @@ namespace manifold {
|
||||
* represents the mesh as a set of triangles as usual. In this process the
|
||||
* faceNormal_ values are retained, repeated as necessary.
|
||||
*/
|
||||
void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
const VecDH<TriRef>& halfedgeRef) {
|
||||
VecDH<glm::ivec3> triVerts;
|
||||
VecDH<glm::vec3> triNormal;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
void Manifold::Impl::Face2Tri(const Vec<int>& faceEdge,
|
||||
const Vec<TriRef>& halfedgeRef) {
|
||||
Vec<glm::ivec3> triVerts;
|
||||
Vec<glm::vec3> triNormal;
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
triRef.resize(0);
|
||||
triVerts.reserve(halfedge_.size());
|
||||
triNormal.reserve(halfedge_.size());
|
||||
triRef.reserve(halfedge_.size());
|
||||
|
||||
for (int face = 0; face < faceEdge.size() - 1; ++face) {
|
||||
auto processFace = [&](GeneralTriangulation general, AddTriangle addTri,
|
||||
int face) {
|
||||
const int firstEdge = faceEdge[face];
|
||||
const int lastEdge = faceEdge[face + 1];
|
||||
const int numEdge = lastEdge - firstEdge;
|
||||
@@ -63,9 +68,7 @@ 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!");
|
||||
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
addTri(face, tri, normal, halfedgeRef[firstEdge]);
|
||||
} else if (numEdge == 4) { // Pair of triangles
|
||||
int mapping[4] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -111,25 +114,78 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
}
|
||||
}
|
||||
|
||||
for (auto tri : {tri0, tri1}) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
for (const auto& tri : {tri0, tri1}) {
|
||||
addTri(face, tri, normal, 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]);
|
||||
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;
|
||||
Vec<int> triCount(faceEdge.size());
|
||||
triCount.back() = 0;
|
||||
// precompute number of triangles per face, and launch async tasks to
|
||||
// triangulate complex faces
|
||||
for_each(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(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) {
|
||||
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
|
||||
|
||||
faceNormal_ = std::move(triNormal);
|
||||
CreateHalfedges(triVerts);
|
||||
}
|
||||
@@ -139,7 +195,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
* projection of the vertices.
|
||||
*/
|
||||
PolygonsIdx Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const {
|
||||
const Vec<int>& faceEdge) const {
|
||||
const int firstEdge = faceEdge[face];
|
||||
const int lastEdge = faceEdge[face + 1];
|
||||
|
||||
|
||||
+113
-116
@@ -14,14 +14,11 @@
|
||||
|
||||
#include "impl.h"
|
||||
|
||||
#include <thrust/logical.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "graph.h"
|
||||
#include "hashtable.h"
|
||||
#include "mesh_fixes.h"
|
||||
#include "par.h"
|
||||
@@ -31,41 +28,36 @@ using namespace manifold;
|
||||
|
||||
constexpr uint64_t kRemove = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
__host__ __device__ void AtomicAddVec3(glm::vec3& target,
|
||||
const glm::vec3& add) {
|
||||
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 {
|
||||
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
|
||||
void operator()(glm::vec3& v) { v = SafeNormalize(v); }
|
||||
};
|
||||
|
||||
struct Transform4x3 {
|
||||
const glm::mat4x3 transform;
|
||||
|
||||
__host__ __device__ glm::vec3 operator()(glm::vec3 position) {
|
||||
glm::vec3 operator()(glm::vec3 position) {
|
||||
return transform * glm::vec4(position, 1.0f);
|
||||
}
|
||||
};
|
||||
|
||||
struct AssignNormals {
|
||||
glm::vec3* vertNormal;
|
||||
const glm::vec3* vertPos;
|
||||
const Halfedge* halfedges;
|
||||
VecView<glm::vec3> vertNormal;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
VecView<const Halfedge> halfedges;
|
||||
const float precision;
|
||||
const bool calculateTriNormal;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::vec3&, int> in) {
|
||||
void operator()(thrust::tuple<glm::vec3&, int> in) {
|
||||
glm::vec3& triNormal = thrust::get<0>(in);
|
||||
const int face = thrust::get<1>(in);
|
||||
|
||||
@@ -99,11 +91,10 @@ struct AssignNormals {
|
||||
};
|
||||
|
||||
struct Tri2Halfedges {
|
||||
Halfedge* halfedges;
|
||||
glm::uint64_t* edges;
|
||||
VecView<Halfedge> halfedges;
|
||||
VecView<glm::uint64_t> edges;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<int, const glm::ivec3&> in) {
|
||||
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}) {
|
||||
@@ -120,11 +111,11 @@ struct Tri2Halfedges {
|
||||
};
|
||||
|
||||
struct LinkHalfedges {
|
||||
Halfedge* halfedges;
|
||||
const int* ids;
|
||||
VecView<Halfedge> halfedges;
|
||||
VecView<const int> ids;
|
||||
const int numEdge;
|
||||
|
||||
__host__ __device__ void operator()(int i) {
|
||||
void operator()(int i) {
|
||||
const int pair0 = ids[i];
|
||||
const int pair1 = ids[i + numEdge];
|
||||
halfedges[pair0].pairedHalfedge = pair1;
|
||||
@@ -133,19 +124,20 @@ struct LinkHalfedges {
|
||||
};
|
||||
|
||||
struct MarkVerts {
|
||||
int* vert;
|
||||
VecView<int> vert;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3 triVerts) {
|
||||
void operator()(glm::ivec3 triVerts) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
vert[triVerts[i]] = 1;
|
||||
reinterpret_cast<std::atomic<int>*>(&vert[triVerts[i]])
|
||||
->store(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReindexTriVerts {
|
||||
const int* old2new;
|
||||
VecView<const int> old2new;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3& triVerts) {
|
||||
void operator()(glm::ivec3& triVerts) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
triVerts[i] = old2new[triVerts[i]];
|
||||
}
|
||||
@@ -154,9 +146,9 @@ struct ReindexTriVerts {
|
||||
|
||||
struct InitializeTriRef {
|
||||
const int meshID;
|
||||
const Halfedge* halfedge;
|
||||
VecView<const Halfedge> halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& baryRef = thrust::get<0>(inOut);
|
||||
int tri = thrust::get<1>(inOut);
|
||||
|
||||
@@ -169,23 +161,22 @@ struct InitializeTriRef {
|
||||
struct UpdateMeshID {
|
||||
const HashTableD<uint32_t> meshIDold2new;
|
||||
|
||||
__host__ __device__ void operator()(TriRef& ref) {
|
||||
ref.meshID = meshIDold2new[ref.meshID];
|
||||
}
|
||||
void operator()(TriRef& ref) { ref.meshID = meshIDold2new[ref.meshID]; }
|
||||
};
|
||||
|
||||
struct CoplanarEdge {
|
||||
float* triArea;
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const TriRef* triRef;
|
||||
const glm::ivec3* triProp;
|
||||
const float* prop;
|
||||
const float* propTol;
|
||||
VecView<float> triArea;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
VecView<const TriRef> triRef;
|
||||
VecView<const glm::ivec3> triProp;
|
||||
VecView<const float> prop;
|
||||
VecView<const float> propTol;
|
||||
const int numProp;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
// FIXME: race condition
|
||||
void operator()(
|
||||
thrust::tuple<thrust::pair<int, int>&, thrust::pair<int, int>&, int>
|
||||
inOut) {
|
||||
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
|
||||
@@ -277,14 +268,14 @@ struct CoplanarEdge {
|
||||
};
|
||||
|
||||
struct CheckCoplanarity {
|
||||
int* comp2tri;
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const int* components;
|
||||
VecView<int> comp2tri;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
std::vector<int>* components;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
const int component = components[tri];
|
||||
void operator()(int tri) {
|
||||
const int component = (*components)[tri];
|
||||
const int referenceTri = comp2tri[component];
|
||||
if (referenceTri < 0 || referenceTri == tri) return;
|
||||
|
||||
@@ -299,7 +290,8 @@ 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) {
|
||||
comp2tri[component] = -1;
|
||||
reinterpret_cast<std::atomic<int>*>(&comp2tri[component])
|
||||
->store(-1, std::memory_order_relaxed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -307,32 +299,27 @@ struct CheckCoplanarity {
|
||||
};
|
||||
|
||||
struct EdgeBox {
|
||||
const glm::vec3* vertPos;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<Box&, const TmpEdge&> inout) {
|
||||
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]);
|
||||
}
|
||||
};
|
||||
|
||||
int GetLabels(std::vector<int>& components,
|
||||
const VecDH<thrust::pair<int, int>>& edges, int numNodes) {
|
||||
Graph graph;
|
||||
for (int i = 0; i < numNodes; ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (int i = 0; i < edges.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = edges[i];
|
||||
if (edge.first < 0) continue;
|
||||
graph.add_edge(edge.first, edge.second);
|
||||
const Vec<thrust::pair<int, int>>& edges, int numNodes) {
|
||||
UnionFind<> uf(numNodes);
|
||||
for (auto edge : edges) {
|
||||
if (edge.first == -1 || edge.second == -1) continue;
|
||||
uf.unionXY(edge.first, edge.second);
|
||||
}
|
||||
|
||||
return ConnectedComponents(components, graph);
|
||||
return uf.connectedComponents(components);
|
||||
}
|
||||
|
||||
void DedupePropVerts(manifold::VecDH<glm::ivec3>& triProp,
|
||||
const VecDH<thrust::pair<int, int>>& vert2vert) {
|
||||
void DedupePropVerts(manifold::Vec<glm::ivec3>& triProp,
|
||||
const Vec<thrust::pair<int, int>>& vert2vert) {
|
||||
std::vector<int> vertLabels;
|
||||
const int numLabels = GetLabels(vertLabels, vert2vert, vert2vert.size());
|
||||
|
||||
@@ -494,7 +481,7 @@ Manifold::Impl::Impl(const Mesh& mesh, const MeshRelationD& relation,
|
||||
meshRelation_ = {relation.originalID, relation.numProp, relation.properties,
|
||||
relation.meshIDtransform};
|
||||
|
||||
VecDH<glm::ivec3> triVerts;
|
||||
Vec<glm::ivec3> triVerts;
|
||||
for (int i = 0; i < mesh.triVerts.size(); ++i) {
|
||||
const glm::ivec3 tri = mesh.triVerts[i];
|
||||
// Remove topological degenerates
|
||||
@@ -593,13 +580,13 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
CreateFaces();
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
VecDH<int> vertOld2New(NumVert() + 1, 0);
|
||||
void Manifold::Impl::RemoveUnreferencedVerts(Vec<glm::ivec3>& triVerts) {
|
||||
Vec<int> vertOld2New(NumVert() + 1, 0);
|
||||
auto policy = autoPolicy(NumVert());
|
||||
for_each(policy, triVerts.cbegin(), triVerts.cend(),
|
||||
MarkVerts({vertOld2New.ptrD() + 1}));
|
||||
MarkVerts({vertOld2New.view(1)}));
|
||||
|
||||
const VecDH<glm::vec3> oldVertPos = vertPos_;
|
||||
const Vec<glm::vec3> oldVertPos = vertPos_;
|
||||
vertPos_.resize(copy_if<decltype(vertPos_.begin())>(
|
||||
policy, oldVertPos.cbegin(), oldVertPos.cend(),
|
||||
vertOld2New.cbegin() + 1, vertPos_.begin(),
|
||||
@@ -610,7 +597,7 @@ void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
vertOld2New.begin() + 1);
|
||||
|
||||
for_each(policy, triVerts.begin(), triVerts.end(),
|
||||
ReindexTriVerts({vertOld2New.cptrD()}));
|
||||
ReindexTriVerts({vertOld2New}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::InitializeOriginal() {
|
||||
@@ -620,28 +607,25 @@ void Manifold::Impl::InitializeOriginal() {
|
||||
meshRelation_.triRef.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri()),
|
||||
zip(meshRelation_.triRef.begin(), countAt(0)), NumTri(),
|
||||
InitializeTriRef({meshID, halfedge_.cptrD()}));
|
||||
InitializeTriRef({meshID, halfedge_}));
|
||||
meshRelation_.meshIDtransform.clear();
|
||||
meshRelation_.meshIDtransform[meshID] = {meshID};
|
||||
}
|
||||
|
||||
void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
VecDH<float> propertyToleranceD =
|
||||
propertyTolerance.empty()
|
||||
? VecDH<float>(meshRelation_.numProp, kTolerance)
|
||||
: propertyTolerance;
|
||||
Vec<float> propertyToleranceD =
|
||||
propertyTolerance.empty() ? Vec<float>(meshRelation_.numProp, kTolerance)
|
||||
: propertyTolerance;
|
||||
|
||||
VecDH<thrust::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
||||
VecDH<thrust::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
||||
VecDH<float> triArea(NumTri());
|
||||
Vec<thrust::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
||||
Vec<thrust::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
||||
Vec<float> triArea(NumTri());
|
||||
for_each_n(
|
||||
autoPolicy(halfedge_.size()),
|
||||
zip(face2face.begin(), vert2vert.begin(), countAt(0)), halfedge_.size(),
|
||||
CoplanarEdge(
|
||||
{triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
meshRelation_.triRef.cptrD(), meshRelation_.triProperties.cptrD(),
|
||||
meshRelation_.properties.cptrD(), propertyToleranceD.cptrD(),
|
||||
meshRelation_.numProp, precision_}));
|
||||
CoplanarEdge({triArea, halfedge_, vertPos_, meshRelation_.triRef,
|
||||
meshRelation_.triProperties, meshRelation_.properties,
|
||||
propertyToleranceD, meshRelation_.numProp, precision_}));
|
||||
|
||||
if (meshRelation_.triProperties.size() > 0) {
|
||||
DedupePropVerts(meshRelation_.triProperties, vert2vert);
|
||||
@@ -650,7 +634,7 @@ void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
std::vector<int> components;
|
||||
const int numComponent = GetLabels(components, face2face, NumTri());
|
||||
|
||||
std::vector<int> comp2tri(numComponent, -1);
|
||||
Vec<int> comp2tri(numComponent, -1);
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int comp = components[tri];
|
||||
const int current = comp2tri[comp];
|
||||
@@ -660,16 +644,13 @@ void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
}
|
||||
}
|
||||
|
||||
VecDH<int> componentsD(components);
|
||||
VecDH<int> comp2triD(comp2tri);
|
||||
for_each_n(
|
||||
autoPolicy(halfedge_.size()), countAt(0), NumTri(),
|
||||
CheckCoplanarity({comp2triD.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
componentsD.cptrD(), precision_}));
|
||||
for_each_n(autoPolicy(halfedge_.size()), countAt(0), NumTri(),
|
||||
CheckCoplanarity(
|
||||
{comp2tri, halfedge_, vertPos_, &components, precision_}));
|
||||
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int referenceTri = comp2triD[components[tri]];
|
||||
const int referenceTri = comp2tri[components[tri]];
|
||||
if (referenceTri >= 0) {
|
||||
triRef[tri].tri = referenceTri;
|
||||
}
|
||||
@@ -679,29 +660,34 @@ void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
/**
|
||||
* Create the halfedge_ data structure from an input triVerts array like Mesh.
|
||||
*/
|
||||
void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
void Manifold::Impl::CreateHalfedges(const Vec<glm::ivec3>& triVerts) {
|
||||
const int numTri = triVerts.size();
|
||||
const int numHalfedge = 3 * numTri;
|
||||
// drop the old value first to avoid copy
|
||||
halfedge_.resize(0);
|
||||
halfedge_.resize(numHalfedge);
|
||||
VecDH<uint64_t> edge(numHalfedge);
|
||||
VecDH<int> ids(numHalfedge);
|
||||
Vec<uint64_t> edge(numHalfedge);
|
||||
Vec<int> ids(numHalfedge);
|
||||
auto policy = autoPolicy(numTri);
|
||||
sequence(policy, ids.begin(), ids.end());
|
||||
for_each_n(policy, zip(countAt(0), triVerts.begin()), numTri,
|
||||
Tri2Halfedges({halfedge_.ptrD(), edge.ptrD()}));
|
||||
Tri2Halfedges({halfedge_, edge}));
|
||||
// Stable sort is required here so that halfedges from the same face are
|
||||
// paired together (the triangles were created in face order). In some
|
||||
// degenerate situations the triangulator can add the same internal edge in
|
||||
// two different faces, causing this edge to not be 2-manifold. These are
|
||||
// fixed by duplicating verts in SimplifyTopology.
|
||||
stable_sort_by_key(policy, edge.begin(), edge.end(), ids.begin());
|
||||
stable_sort(policy, zip(edge.begin(), ids.begin()),
|
||||
zip(edge.end(), ids.end()),
|
||||
[](const thrust::tuple<uint64_t, int>& a,
|
||||
const thrust::tuple<uint64_t, int>& b) {
|
||||
return thrust::get<0>(a) < thrust::get<0>(b);
|
||||
});
|
||||
// Once sorted, the first half of the range is the forward halfedges, which
|
||||
// correspond to their backward pair at the same offset in the second half
|
||||
// of the range.
|
||||
for_each_n(policy, countAt(0), numHalfedge / 2,
|
||||
LinkHalfedges({halfedge_.ptrD(), ids.ptrD(), numHalfedge / 2}));
|
||||
LinkHalfedges({halfedge_, ids, numHalfedge / 2}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -710,8 +696,8 @@ void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
VecDH<Box> faceBox;
|
||||
VecDH<uint32_t> faceMorton;
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
collider_.UpdateBoxes(faceBox);
|
||||
}
|
||||
@@ -774,16 +760,15 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
const bool invert = glm::determinant(glm::mat3(transform_)) < 0;
|
||||
|
||||
if (halfedgeTangent_.size() > 0) {
|
||||
for_each_n(
|
||||
policy, zip(result.halfedgeTangent_.begin(), countAt(0)),
|
||||
halfedgeTangent_.size(),
|
||||
TransformTangents({glm::mat3(transform_), invert,
|
||||
halfedgeTangent_.cptrD(), halfedge_.cptrD()}));
|
||||
for_each_n(policy, zip(result.halfedgeTangent_.begin(), countAt(0)),
|
||||
halfedgeTangent_.size(),
|
||||
TransformTangents({glm::mat3(transform_), invert,
|
||||
halfedgeTangent_, halfedge_}));
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
for_each_n(policy, zip(result.meshRelation_.triRef.begin(), countAt(0)),
|
||||
result.NumTri(), FlipTris({result.halfedge_.ptrD()}));
|
||||
result.NumTri(), FlipTris({result.halfedge_}));
|
||||
}
|
||||
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
@@ -830,10 +815,9 @@ void Manifold::Impl::CalculateNormals() {
|
||||
faceNormal_.resize(NumTri());
|
||||
calculateTriNormal = true;
|
||||
}
|
||||
for_each_n(
|
||||
policy, zip(faceNormal_.begin(), countAt(0)), NumTri(),
|
||||
AssignNormals({vertNormal_.ptrD(), vertPos_.cptrD(), halfedge_.cptrD(),
|
||||
precision_, calculateTriNormal}));
|
||||
for_each_n(policy, zip(faceNormal_.begin(), countAt(0)), NumTri(),
|
||||
AssignNormals({vertNormal_, vertPos_, halfedge_, precision_,
|
||||
calculateTriNormal}));
|
||||
for_each(policy, vertNormal_.begin(), vertNormal_.end(), Normalize());
|
||||
}
|
||||
|
||||
@@ -863,17 +847,27 @@ 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) const {
|
||||
VecDH<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
|
||||
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q,
|
||||
bool inverted) const {
|
||||
Vec<TmpEdge> edges = CreateTmpEdges(Q.halfedge_);
|
||||
const int numEdge = edges.size();
|
||||
VecDH<Box> QedgeBB(numEdge);
|
||||
Vec<Box> QedgeBB(numEdge);
|
||||
auto policy = autoPolicy(numEdge);
|
||||
for_each_n(policy, zip(QedgeBB.begin(), edges.cbegin()), numEdge,
|
||||
EdgeBox({Q.vertPos_.cptrD()}));
|
||||
EdgeBox({Q.vertPos_}));
|
||||
|
||||
SparseIndices q1p2 = collider_.Collisions(QedgeBB);
|
||||
SparseIndices q1p2(0);
|
||||
if (inverted)
|
||||
q1p2 = collider_.Collisions<false, true>(QedgeBB.cview());
|
||||
else
|
||||
q1p2 = collider_.Collisions<false, false>(QedgeBB.cview());
|
||||
|
||||
for_each(policy, q1p2.begin(0), q1p2.end(0), ReindexEdge({edges.cptrD()}));
|
||||
if (inverted)
|
||||
for_each(policy, countAt(0), countAt(q1p2.size()),
|
||||
ReindexEdge<true>({edges, q1p2}));
|
||||
else
|
||||
for_each(policy, countAt(0), countAt(q1p2.size()),
|
||||
ReindexEdge<false>({edges, q1p2}));
|
||||
return q1p2;
|
||||
}
|
||||
|
||||
@@ -882,7 +876,10 @@ SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q) const {
|
||||
* bounding boxes of the faces of this manifold.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::VertexCollisionsZ(
|
||||
const VecDH<glm::vec3>& vertsIn) const {
|
||||
return collider_.Collisions(vertsIn);
|
||||
VecView<const glm::vec3> vertsIn, bool inverted) const {
|
||||
if (inverted)
|
||||
return collider_.Collisions<false, true>(vertsIn);
|
||||
else
|
||||
return collider_.Collisions<false, false>(vertsIn);
|
||||
}
|
||||
} // namespace manifold
|
||||
|
||||
+27
-24
@@ -22,7 +22,7 @@
|
||||
#include "shared.h"
|
||||
#include "sparse.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
@@ -37,20 +37,20 @@ struct Manifold::Impl {
|
||||
/// The originalID of this Manifold if it is an original; -1 otherwise.
|
||||
int originalID = -1;
|
||||
int numProp = 0;
|
||||
VecDH<float> properties;
|
||||
Vec<float> properties;
|
||||
std::map<int, Relation> meshIDtransform;
|
||||
VecDH<TriRef> triRef;
|
||||
VecDH<glm::ivec3> triProperties;
|
||||
Vec<TriRef> triRef;
|
||||
Vec<glm::ivec3> triProperties;
|
||||
};
|
||||
|
||||
Box bBox_;
|
||||
float precision_ = -1;
|
||||
Error status_ = Error::NoError;
|
||||
VecDH<glm::vec3> vertPos_;
|
||||
VecDH<Halfedge> halfedge_;
|
||||
VecDH<glm::vec3> vertNormal_;
|
||||
VecDH<glm::vec3> faceNormal_;
|
||||
VecDH<glm::vec4> halfedgeTangent_;
|
||||
Vec<glm::vec3> vertPos_;
|
||||
Vec<Halfedge> halfedge_;
|
||||
Vec<glm::vec3> vertNormal_;
|
||||
Vec<glm::vec3> faceNormal_;
|
||||
Vec<glm::vec4> halfedgeTangent_;
|
||||
MeshRelationD meshRelation_;
|
||||
Collider collider_;
|
||||
|
||||
@@ -67,9 +67,9 @@ struct Manifold::Impl {
|
||||
bool hasFaceIDs = false);
|
||||
|
||||
void CreateFaces(const std::vector<float>& propertyTolerance = {});
|
||||
void RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts);
|
||||
void RemoveUnreferencedVerts(Vec<glm::ivec3>& triVerts);
|
||||
void InitializeOriginal();
|
||||
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
|
||||
void CreateHalfedges(const Vec<glm::ivec3>& triVerts);
|
||||
void CalculateNormals();
|
||||
void IncrementMeshIDs();
|
||||
|
||||
@@ -77,8 +77,9 @@ 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) const;
|
||||
SparseIndices VertexCollisionsZ(const VecDH<glm::vec3>& vertsIn) const;
|
||||
SparseIndices EdgeCollisions(const Impl& B, bool inverted = false) const;
|
||||
SparseIndices VertexCollisionsZ(VecView<const glm::vec3> vertsIn,
|
||||
bool inverted = false) const;
|
||||
|
||||
bool IsEmpty() const { return NumVert() == 0; }
|
||||
int NumVert() const { return vertPos_.size(); }
|
||||
@@ -95,7 +96,7 @@ struct Manifold::Impl {
|
||||
void CalculateCurvature(int gaussianIdx, int meanIdx);
|
||||
void CalculateBBox();
|
||||
bool IsFinite() const;
|
||||
bool IsIndexInBounds(const VecDH<glm::ivec3>& triVerts) const;
|
||||
bool IsIndexInBounds(VecView<const glm::ivec3> triVerts) const;
|
||||
void SetPrecision(float minPrecision = -1);
|
||||
bool IsManifold() const;
|
||||
bool Is2Manifold() const;
|
||||
@@ -105,23 +106,25 @@ struct Manifold::Impl {
|
||||
// sort.cu
|
||||
void Finish();
|
||||
void SortVerts();
|
||||
void ReindexVerts(const VecDH<int>& vertNew2Old, int numOldVert);
|
||||
void ReindexVerts(const Vec<int>& vertNew2Old, int numOldVert);
|
||||
void CompactProps();
|
||||
void GetFaceBoxMorton(VecDH<Box>& faceBox, VecDH<uint32_t>& faceMorton) const;
|
||||
void SortFaces(VecDH<Box>& faceBox, VecDH<uint32_t>& faceMorton);
|
||||
void GatherFaces(const VecDH<int>& faceNew2Old);
|
||||
void GatherFaces(const Impl& old, const VecDH<int>& faceNew2Old);
|
||||
void GetFaceBoxMorton(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton) const;
|
||||
void SortFaces(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton);
|
||||
void GatherFaces(const Vec<int>& faceNew2Old);
|
||||
void GatherFaces(const Impl& old, const Vec<int>& faceNew2Old);
|
||||
|
||||
// face_op.cu
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<TriRef>& halfedgeRef);
|
||||
void Face2Tri(const Vec<int>& faceEdge, const Vec<TriRef>& halfedgeRef);
|
||||
PolygonsIdx Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
const Vec<int>& faceEdge) const;
|
||||
|
||||
// edge_op.cu
|
||||
void SimplifyTopology();
|
||||
void DedupeEdge(int edge);
|
||||
void CollapseEdge(int edge);
|
||||
void RecursiveEdgeSwap(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 RemoveIfFolded(int edge);
|
||||
void PairUp(int edge0, int edge1);
|
||||
void UpdateVert(int vert, int startEdge, int endEdge);
|
||||
@@ -131,7 +134,7 @@ struct Manifold::Impl {
|
||||
|
||||
// smoothing.cu
|
||||
void CreateTangents(const std::vector<Smoothness>&);
|
||||
VecDH<Barycentric> Subdivide(int n);
|
||||
Vec<Barycentric> Subdivide(int n);
|
||||
void Refine(int n);
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <QuickHull.hpp>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "QuickHull.hpp"
|
||||
#include "boolean3.h"
|
||||
#include "csg_tree.h"
|
||||
#include "impl.h"
|
||||
@@ -26,12 +26,12 @@ namespace {
|
||||
using namespace manifold;
|
||||
using namespace thrust::placeholders;
|
||||
|
||||
ExecutionParams params;
|
||||
ExecutionParams manifoldParams;
|
||||
|
||||
struct MakeTri {
|
||||
const Halfedge* halfedges;
|
||||
VecView<const Halfedge> halfedges;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
|
||||
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;
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
void operator()(int tri) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedges[3 * tri + i].startVert;
|
||||
const int propVert = triProperties[tri][i];
|
||||
@@ -170,7 +170,7 @@ Mesh Manifold::GetMesh() const {
|
||||
result.triVerts.resize(NumTri());
|
||||
// note that `triVerts` is `std::vector`, so we cannot use thrust::device
|
||||
thrust::for_each_n(thrust::host, zip(result.triVerts.begin(), countAt(0)),
|
||||
NumTri(), MakeTri({impl.halfedge_.cptrH()}));
|
||||
NumTri(), MakeTri({impl.halfedge_}));
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -218,7 +218,7 @@ MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
out.faceID.resize(numTri);
|
||||
std::vector<int> triNew2Old(numTri);
|
||||
std::iota(triNew2Old.begin(), triNew2Old.end(), 0);
|
||||
const TriRef* triRef = impl.meshRelation_.triRef.cptrD();
|
||||
VecView<const TriRef> triRef = impl.meshRelation_.triRef;
|
||||
// Don't sort originals - keep them in order
|
||||
if (!isOriginal) {
|
||||
std::sort(triNew2Old.begin(), triNew2Old.end(), [triRef](int a, int b) {
|
||||
@@ -260,7 +260,10 @@ MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
|
||||
|
||||
if (meshID != lastID) {
|
||||
addRun(out, runNormalTransform, tri, meshIDtransform.at(meshID));
|
||||
Impl::Relation rel;
|
||||
auto it = meshIDtransform.find(meshID);
|
||||
if (it != meshIDtransform.end()) rel = it->second;
|
||||
addRun(out, runNormalTransform, tri, rel);
|
||||
meshIDtransform.erase(meshID);
|
||||
lastID = meshID;
|
||||
}
|
||||
@@ -580,7 +583,7 @@ Manifold Manifold::SetProperties(
|
||||
propFunc) const {
|
||||
auto pImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
|
||||
const int oldNumProp = NumProp();
|
||||
const VecDH<float> oldProperties = pImpl->meshRelation_.properties;
|
||||
const Vec<float> oldProperties = pImpl->meshRelation_.properties;
|
||||
|
||||
auto& triProperties = pImpl->meshRelation_.triProperties;
|
||||
if (numProp == 0) {
|
||||
@@ -596,17 +599,16 @@ Manifold Manifold::SetProperties(
|
||||
triProperties[i][j] = idx++;
|
||||
}
|
||||
}
|
||||
pImpl->meshRelation_.properties = VecDH<float>(numProp * idx, 0);
|
||||
pImpl->meshRelation_.properties = Vec<float>(numProp * idx, 0);
|
||||
} else {
|
||||
pImpl->meshRelation_.properties =
|
||||
VecDH<float>(numProp * NumPropVert(), 0);
|
||||
pImpl->meshRelation_.properties = Vec<float>(numProp * NumPropVert(), 0);
|
||||
}
|
||||
thrust::for_each_n(
|
||||
thrust::host, countAt(0), NumTri(),
|
||||
UpdateProperties({pImpl->meshRelation_.properties.ptrH(), numProp,
|
||||
oldProperties.ptrH(), oldNumProp,
|
||||
pImpl->vertPos_.ptrH(), triProperties.ptrH(),
|
||||
pImpl->halfedge_.ptrH(), propFunc}));
|
||||
UpdateProperties({pImpl->meshRelation_.properties.data(), numProp,
|
||||
oldProperties.data(), oldNumProp,
|
||||
pImpl->vertPos_.data(), triProperties.data(),
|
||||
pImpl->halfedge_.data(), propFunc}));
|
||||
}
|
||||
|
||||
pImpl->meshRelation_.numProp = numProp;
|
||||
@@ -775,7 +777,7 @@ Manifold Manifold::TrimByPlane(glm::vec3 normal, float originOffset) const {
|
||||
return *this ^ Halfspace(BoundingBox(), normal, originOffset);
|
||||
}
|
||||
|
||||
ExecutionParams& ManifoldParams() { return params; }
|
||||
ExecutionParams& ManifoldParams() { return manifoldParams; }
|
||||
|
||||
/**
|
||||
* Compute the convex hull of a set of points. If the given points are fewer
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
__host__ __device__ inline int FlipHalfedge(int halfedge) {
|
||||
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 @@ __host__ __device__ inline int FlipHalfedge(int halfedge) {
|
||||
struct TransformNormals {
|
||||
const glm::mat3 transform;
|
||||
|
||||
__host__ __device__ glm::vec3 operator()(glm::vec3 normal) {
|
||||
glm::vec3 operator()(glm::vec3 normal) {
|
||||
normal = glm::normalize(transform * normal);
|
||||
if (isnan(normal.x)) normal = glm::vec3(0.0f);
|
||||
return normal;
|
||||
@@ -22,10 +22,10 @@ struct TransformNormals {
|
||||
struct TransformTangents {
|
||||
const glm::mat3 transform;
|
||||
const bool invert;
|
||||
const glm::vec4* oldTangents;
|
||||
const Halfedge* halfedge;
|
||||
VecView<const glm::vec4> oldTangents;
|
||||
VecView<const Halfedge> halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::vec4&, int> inOut) {
|
||||
void operator()(thrust::tuple<glm::vec4&, int> inOut) {
|
||||
glm::vec4& tangent = thrust::get<0>(inOut);
|
||||
int edge = thrust::get<1>(inOut);
|
||||
if (invert) {
|
||||
@@ -38,9 +38,9 @@ struct TransformTangents {
|
||||
};
|
||||
|
||||
struct FlipTris {
|
||||
Halfedge* halfedge;
|
||||
VecView<Halfedge> halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& bary = thrust::get<0>(inOut);
|
||||
const int tri = thrust::get<1>(inOut);
|
||||
|
||||
|
||||
@@ -12,10 +12,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <thrust/count.h>
|
||||
#include <thrust/logical.h>
|
||||
#include <thrust/transform_reduce.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "impl.h"
|
||||
@@ -25,11 +21,11 @@ namespace {
|
||||
using namespace manifold;
|
||||
|
||||
struct FaceAreaVolume {
|
||||
const Halfedge* halfedges;
|
||||
const glm::vec3* vertPos;
|
||||
VecView<const Halfedge> halfedges;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ thrust::pair<float, float> operator()(int face) {
|
||||
thrust::pair<float, float> operator()(int face) {
|
||||
float perimeter = 0;
|
||||
glm::vec3 edge[3];
|
||||
for (int i : {0, 1, 2}) {
|
||||
@@ -49,7 +45,7 @@ struct FaceAreaVolume {
|
||||
|
||||
struct PosMin
|
||||
: public thrust::binary_function<glm::vec3, glm::vec3, glm::vec3> {
|
||||
__host__ __device__ glm::vec3 operator()(glm::vec3 a, glm::vec3 b) {
|
||||
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 +54,7 @@ struct PosMin
|
||||
|
||||
struct PosMax
|
||||
: public thrust::binary_function<glm::vec3, glm::vec3, glm::vec3> {
|
||||
__host__ __device__ glm::vec3 operator()(glm::vec3 a, glm::vec3 b) {
|
||||
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,13 +62,11 @@ struct PosMax
|
||||
};
|
||||
|
||||
struct FiniteVert {
|
||||
__host__ __device__ bool operator()(glm::vec3 v) {
|
||||
return glm::all(glm::isfinite(v));
|
||||
}
|
||||
bool operator()(glm::vec3 v) { return glm::all(glm::isfinite(v)); }
|
||||
};
|
||||
|
||||
struct MakeMinMax {
|
||||
__host__ __device__ glm::ivec2 operator()(glm::ivec3 tri) {
|
||||
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])));
|
||||
}
|
||||
@@ -80,7 +74,7 @@ struct MakeMinMax {
|
||||
|
||||
struct MinMax
|
||||
: public thrust::binary_function<glm::ivec2, glm::ivec2, glm::ivec2> {
|
||||
__host__ __device__ glm::ivec2 operator()(glm::ivec2 a, glm::ivec2 b) {
|
||||
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;
|
||||
@@ -90,8 +84,8 @@ struct MinMax
|
||||
struct SumPair : public thrust::binary_function<thrust::pair<float, float>,
|
||||
thrust::pair<float, float>,
|
||||
thrust::pair<float, float>> {
|
||||
__host__ __device__ thrust::pair<float, float> operator()(
|
||||
thrust::pair<float, float> a, thrust::pair<float, float> b) {
|
||||
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;
|
||||
@@ -99,15 +93,15 @@ struct SumPair : public thrust::binary_function<thrust::pair<float, float>,
|
||||
};
|
||||
|
||||
struct CurvatureAngles {
|
||||
float* meanCurvature;
|
||||
float* gaussianCurvature;
|
||||
float* area;
|
||||
float* degree;
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const glm::vec3* triNormal;
|
||||
VecView<float> meanCurvature;
|
||||
VecView<float> gaussianCurvature;
|
||||
VecView<float> area;
|
||||
VecView<float> degree;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
VecView<const glm::vec3> triNormal;
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
void operator()(int tri) {
|
||||
glm::vec3 edge[3];
|
||||
glm::vec3 edgeLength(0.0);
|
||||
for (int i : {0, 1, 2}) {
|
||||
@@ -142,8 +136,7 @@ struct CurvatureAngles {
|
||||
};
|
||||
|
||||
struct NormalizeCurvature {
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<float&, float&, float, float> inOut) {
|
||||
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);
|
||||
@@ -155,18 +148,19 @@ struct NormalizeCurvature {
|
||||
};
|
||||
|
||||
struct UpdateProperties {
|
||||
float* properties;
|
||||
VecView<float> properties;
|
||||
|
||||
const float* oldProperties;
|
||||
const Halfedge* halfedge;
|
||||
const float* meanCurvature;
|
||||
const float* gaussianCurvature;
|
||||
VecView<const float> oldProperties;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const float> meanCurvature;
|
||||
VecView<const float> gaussianCurvature;
|
||||
const int oldNumProp;
|
||||
const int numProp;
|
||||
const int gaussianIdx;
|
||||
const int meanIdx;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
|
||||
// FIXME: race condition
|
||||
void operator()(thrust::tuple<glm::ivec3&, int> inOut) {
|
||||
glm::ivec3& triProp = thrust::get<0>(inOut);
|
||||
const int tri = thrust::get<1>(inOut);
|
||||
|
||||
@@ -193,13 +187,17 @@ struct UpdateProperties {
|
||||
};
|
||||
|
||||
struct CheckHalfedges {
|
||||
const Halfedge* halfedges;
|
||||
VecView<const Halfedge> halfedges;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
bool operator()(int edge) {
|
||||
const Halfedge halfedge = halfedges[edge];
|
||||
if (halfedge.startVert == -1 && halfedge.endVert == -1) return true;
|
||||
if (halfedge.startVert == -1 || halfedge.endVert == -1) return true;
|
||||
if (halfedge.pairedHalfedge == -1) return false;
|
||||
|
||||
if (!isfinite(vertPos[halfedge.startVert][0])) return false;
|
||||
if (!isfinite(vertPos[halfedge.endVert][0])) return false;
|
||||
|
||||
const Halfedge paired = halfedges[halfedge.pairedHalfedge];
|
||||
bool good = true;
|
||||
good &= paired.pairedHalfedge == edge;
|
||||
@@ -211,9 +209,9 @@ struct CheckHalfedges {
|
||||
};
|
||||
|
||||
struct NoDuplicates {
|
||||
const Halfedge* halfedges;
|
||||
VecView<const Halfedge> halfedges;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
bool operator()(int edge) {
|
||||
const Halfedge halfedge = halfedges[edge];
|
||||
if (halfedge.startVert == -1 && halfedge.endVert == -1 &&
|
||||
halfedge.pairedHalfedge == -1)
|
||||
@@ -224,12 +222,12 @@ struct NoDuplicates {
|
||||
};
|
||||
|
||||
struct CheckCCW {
|
||||
const Halfedge* halfedges;
|
||||
const glm::vec3* vertPos;
|
||||
const glm::vec3* triNormal;
|
||||
VecView<const Halfedge> halfedges;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
VecView<const glm::vec3> triNormal;
|
||||
const float tol;
|
||||
|
||||
__host__ __device__ bool operator()(int face) {
|
||||
bool operator()(int face) {
|
||||
if (halfedges[3 * face].pairedHalfedge < 0) return true;
|
||||
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(triNormal[face]);
|
||||
@@ -278,7 +276,7 @@ bool Manifold::Impl::IsManifold() const {
|
||||
auto policy = autoPolicy(halfedge_.size());
|
||||
|
||||
return all_of(policy, countAt(0), countAt(halfedge_.size()),
|
||||
CheckHalfedges({halfedge_.cptrD()}));
|
||||
CheckHalfedges({halfedge_, vertPos_}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,11 +289,11 @@ bool Manifold::Impl::Is2Manifold() const {
|
||||
|
||||
if (!IsManifold()) return false;
|
||||
|
||||
VecDH<Halfedge> halfedge(halfedge_);
|
||||
sort(policy, halfedge.begin(), halfedge.end());
|
||||
Vec<Halfedge> halfedge(halfedge_);
|
||||
stable_sort(policy, halfedge.begin(), halfedge.end());
|
||||
|
||||
return all_of(policy, countAt(0), countAt(2 * NumEdge() - 1),
|
||||
NoDuplicates({halfedge.cptrD()}));
|
||||
NoDuplicates({halfedge}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,8 +302,7 @@ bool Manifold::Impl::Is2Manifold() const {
|
||||
bool Manifold::Impl::MatchesTriNormals() const {
|
||||
if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;
|
||||
return all_of(autoPolicy(NumTri()), countAt(0), countAt(NumTri()),
|
||||
CheckCCW({halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
faceNormal_.cptrD(), 2 * precision_}));
|
||||
CheckCCW({halfedge_, vertPos_, faceNormal_, 2 * precision_}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,33 +310,45 @@ bool Manifold::Impl::MatchesTriNormals() const {
|
||||
*/
|
||||
int Manifold::Impl::NumDegenerateTris() const {
|
||||
if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;
|
||||
return count_if(autoPolicy(NumTri()), countAt(0), countAt(NumTri()),
|
||||
CheckCCW({halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
faceNormal_.cptrD(), -1 * precision_ / 2}));
|
||||
return count_if(
|
||||
autoPolicy(NumTri()), countAt(0), countAt(NumTri()),
|
||||
CheckCCW({halfedge_, vertPos_, faceNormal_, -1 * precision_ / 2}));
|
||||
}
|
||||
|
||||
Properties Manifold::Impl::GetProperties() const {
|
||||
if (IsEmpty()) return {0, 0};
|
||||
auto areaVolume = transform_reduce<thrust::pair<float, float>>(
|
||||
autoPolicy(NumTri()), countAt(0), countAt(NumTri()),
|
||||
FaceAreaVolume({halfedge_.cptrD(), vertPos_.cptrD(), precision_}),
|
||||
thrust::make_pair(0.0f, 0.0f), SumPair());
|
||||
return {areaVolume.first, areaVolume.second};
|
||||
// Kahan summation
|
||||
float area = 0;
|
||||
float volume = 0;
|
||||
float areaCompensation = 0;
|
||||
float volumeCompensation = 0;
|
||||
for (int i = 0; i < NumTri(); ++i) {
|
||||
auto [area1, volume1] =
|
||||
FaceAreaVolume({halfedge_, vertPos_, precision_})(i);
|
||||
const float t1 = area + area1;
|
||||
const float t2 = volume + volume1;
|
||||
areaCompensation += (area - t1) + area1;
|
||||
volumeCompensation += (volume - t2) + volume1;
|
||||
area = t1;
|
||||
volume = t2;
|
||||
}
|
||||
area += areaCompensation;
|
||||
volume += volumeCompensation;
|
||||
|
||||
return {area, volume};
|
||||
}
|
||||
|
||||
void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {
|
||||
if (IsEmpty()) return;
|
||||
if (gaussianIdx < 0 && meanIdx < 0) return;
|
||||
VecDH<float> vertMeanCurvature(NumVert(), 0);
|
||||
VecDH<float> vertGaussianCurvature(NumVert(), glm::two_pi<float>());
|
||||
VecDH<float> vertArea(NumVert(), 0);
|
||||
VecDH<float> degree(NumVert(), 0);
|
||||
Vec<float> vertMeanCurvature(NumVert(), 0);
|
||||
Vec<float> vertGaussianCurvature(NumVert(), glm::two_pi<float>());
|
||||
Vec<float> vertArea(NumVert(), 0);
|
||||
Vec<float> degree(NumVert(), 0);
|
||||
auto policy = autoPolicy(NumTri());
|
||||
for_each(
|
||||
policy, countAt(0), countAt(NumTri()),
|
||||
CurvatureAngles({vertMeanCurvature.ptrD(), vertGaussianCurvature.ptrD(),
|
||||
vertArea.ptrD(), degree.ptrD(), halfedge_.cptrD(),
|
||||
vertPos_.cptrD(), faceNormal_.cptrD()}));
|
||||
for_each(policy, countAt(0), countAt(NumTri()),
|
||||
CurvatureAngles({vertMeanCurvature, vertGaussianCurvature, vertArea,
|
||||
degree, halfedge_, vertPos_, faceNormal_}));
|
||||
for_each_n(policy,
|
||||
zip(vertMeanCurvature.begin(), vertGaussianCurvature.begin(),
|
||||
vertArea.begin(), degree.begin()),
|
||||
@@ -347,8 +356,8 @@ void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {
|
||||
|
||||
const int oldNumProp = NumProp();
|
||||
const int numProp = glm::max(oldNumProp, glm::max(gaussianIdx, meanIdx) + 1);
|
||||
const VecDH<float> oldProperties = meshRelation_.properties;
|
||||
meshRelation_.properties = VecDH<float>(numProp * NumPropVert(), 0);
|
||||
const Vec<float> oldProperties = meshRelation_.properties;
|
||||
meshRelation_.properties = Vec<float>(numProp * NumPropVert(), 0);
|
||||
meshRelation_.numProp = numProp;
|
||||
if (meshRelation_.triProperties.size() == 0) {
|
||||
meshRelation_.triProperties.resize(NumTri());
|
||||
@@ -356,10 +365,9 @@ void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {
|
||||
|
||||
for_each_n(
|
||||
policy, zip(meshRelation_.triProperties.begin(), countAt(0)), NumTri(),
|
||||
UpdateProperties({meshRelation_.properties.ptrD(), oldProperties.cptrD(),
|
||||
halfedge_.cptrD(), vertMeanCurvature.cptrD(),
|
||||
vertGaussianCurvature.cptrD(), oldNumProp, numProp,
|
||||
gaussianIdx, meanIdx}));
|
||||
UpdateProperties({meshRelation_.properties, oldProperties, halfedge_,
|
||||
vertMeanCurvature, vertGaussianCurvature, oldNumProp,
|
||||
numProp, gaussianIdx, meanIdx}));
|
||||
|
||||
CreateFaces();
|
||||
SimplifyTopology();
|
||||
@@ -396,7 +404,7 @@ bool Manifold::Impl::IsFinite() const {
|
||||
* Checks that the input triVerts array has all indices inside bounds of the
|
||||
* vertPos_ array.
|
||||
*/
|
||||
bool Manifold::Impl::IsIndexInBounds(const VecDH<glm::ivec3>& triVerts) const {
|
||||
bool Manifold::Impl::IsIndexInBounds(VecView<const glm::ivec3> triVerts) const {
|
||||
auto policy = autoPolicy(triVerts.size());
|
||||
glm::ivec2 minmax = transform_reduce<glm::ivec2>(
|
||||
policy, triVerts.begin(), triVerts.end(), MakeMinMax(),
|
||||
|
||||
+24
-28
@@ -15,33 +15,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "par.h"
|
||||
#include "sparse.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
__host__ __device__ inline glm::vec3 SafeNormalize(glm::vec3 v) {
|
||||
inline glm::vec3 SafeNormalize(glm::vec3 v) {
|
||||
v = glm::normalize(v);
|
||||
return glm::isfinite(v.x) ? v : glm::vec3(0);
|
||||
}
|
||||
|
||||
__host__ __device__ inline float MaxPrecision(float minPrecision,
|
||||
const Box& bBox) {
|
||||
inline float MaxPrecision(float minPrecision, const Box& bBox) {
|
||||
float precision = glm::max(minPrecision, kTolerance * bBox.Scale());
|
||||
return glm::isfinite(precision) ? precision : -1;
|
||||
}
|
||||
|
||||
__host__ __device__ inline int NextHalfedge(int current) {
|
||||
inline int NextHalfedge(int current) {
|
||||
++current;
|
||||
if (current % 3 == 0) current -= 3;
|
||||
return current;
|
||||
}
|
||||
|
||||
__host__ __device__ inline glm::vec3 UVW(int vert,
|
||||
const glm::vec3* barycentric) {
|
||||
inline glm::vec3 UVW(int vert, const glm::vec3* barycentric) {
|
||||
glm::vec3 uvw(0.0f);
|
||||
if (vert < 0) {
|
||||
uvw[vert + 3] = 1;
|
||||
@@ -51,8 +50,7 @@ __host__ __device__ inline glm::vec3 UVW(int vert,
|
||||
return uvw;
|
||||
}
|
||||
|
||||
__host__ __device__ inline glm::mat3 NormalTransform(
|
||||
const glm::mat4x3& transform) {
|
||||
inline glm::mat3 NormalTransform(const glm::mat4x3& transform) {
|
||||
return glm::inverse(glm::transpose(glm::mat3(transform)));
|
||||
}
|
||||
|
||||
@@ -60,8 +58,7 @@ __host__ __device__ inline glm::mat3 NormalTransform(
|
||||
* By using the closest axis-aligned projection to the normal instead of a
|
||||
* projection along the normal, we avoid introducing any rounding error.
|
||||
*/
|
||||
__host__ __device__ inline glm::mat3x2 GetAxisAlignedProjection(
|
||||
glm::vec3 normal) {
|
||||
inline glm::mat3x2 GetAxisAlignedProjection(glm::vec3 normal) {
|
||||
glm::vec3 absNormal = glm::abs(normal);
|
||||
float xyzMax;
|
||||
glm::mat2x3 projection;
|
||||
@@ -82,9 +79,8 @@ __host__ __device__ inline glm::mat3x2 GetAxisAlignedProjection(
|
||||
return glm::transpose(projection);
|
||||
}
|
||||
|
||||
__host__ __device__ inline glm::vec3 GetBarycentric(const glm::vec3& v,
|
||||
const glm::mat3& triPos,
|
||||
float precision) {
|
||||
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]),
|
||||
@@ -138,8 +134,8 @@ struct Halfedge {
|
||||
int startVert, endVert;
|
||||
int pairedHalfedge;
|
||||
int face;
|
||||
__host__ __device__ bool IsForward() const { return startVert < endVert; }
|
||||
__host__ __device__ bool operator<(const Halfedge& other) const {
|
||||
bool IsForward() const { return startVert < endVert; }
|
||||
bool operator<(const Halfedge& other) const {
|
||||
return startVert == other.startVert ? endVert < other.endVert
|
||||
: startVert < other.startVert;
|
||||
}
|
||||
@@ -170,22 +166,21 @@ struct TriRef {
|
||||
struct TmpEdge {
|
||||
int first, second, halfedgeIdx;
|
||||
|
||||
__host__ __device__ TmpEdge() {}
|
||||
__host__ __device__ TmpEdge(int start, int end, int idx) {
|
||||
TmpEdge() {}
|
||||
TmpEdge(int start, int end, int idx) {
|
||||
first = glm::min(start, end);
|
||||
second = glm::max(start, end);
|
||||
halfedgeIdx = idx;
|
||||
}
|
||||
|
||||
__host__ __device__ bool operator<(const TmpEdge& other) const {
|
||||
bool operator<(const TmpEdge& other) const {
|
||||
return first == other.first ? second < other.second : first < other.first;
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
struct Halfedge2Tmp {
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<TmpEdge&, const Halfedge&, int> inout) {
|
||||
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;
|
||||
@@ -195,13 +190,11 @@ struct Halfedge2Tmp {
|
||||
};
|
||||
|
||||
struct TmpInvalid {
|
||||
__host__ __device__ bool operator()(const TmpEdge& edge) {
|
||||
return edge.halfedgeIdx < 0;
|
||||
}
|
||||
bool operator()(const TmpEdge& edge) { return edge.halfedgeIdx < 0; }
|
||||
};
|
||||
|
||||
VecDH<TmpEdge> inline CreateTmpEdges(const VecDH<Halfedge>& halfedge) {
|
||||
VecDH<TmpEdge> edges(halfedge.size());
|
||||
Vec<TmpEdge> inline CreateTmpEdges(const Vec<Halfedge>& halfedge) {
|
||||
Vec<TmpEdge> edges(halfedge.size());
|
||||
for_each_n(autoPolicy(edges.size()),
|
||||
zip(edges.begin(), halfedge.begin(), countAt(0)), edges.size(),
|
||||
Halfedge2Tmp());
|
||||
@@ -214,10 +207,13 @@ VecDH<TmpEdge> inline CreateTmpEdges(const VecDH<Halfedge>& halfedge) {
|
||||
return edges;
|
||||
}
|
||||
|
||||
template <const bool inverted>
|
||||
struct ReindexEdge {
|
||||
const TmpEdge* edges;
|
||||
VecView<const TmpEdge> edges;
|
||||
SparseIndices& indices;
|
||||
|
||||
__host__ __device__ void operator()(int& edge) {
|
||||
void operator()(int i) {
|
||||
int& edge = indices.Get(i, inverted);
|
||||
edge = edges[edge].halfedgeIdx;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
__host__ __device__ glm::vec3 OrthogonalTo(glm::vec3 in, glm::vec3 ref) {
|
||||
glm::vec3 OrthogonalTo(glm::vec3 in, glm::vec3 ref) {
|
||||
in -= glm::dot(in, ref) * ref;
|
||||
return in;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ __host__ __device__ 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).
|
||||
*/
|
||||
__host__ __device__ int VertsPerTri(int edgeVerts) {
|
||||
int VertsPerTri(int edgeVerts) {
|
||||
return (edgeVerts * edgeVerts + edgeVerts) / 2;
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ __host__ __device__ int VertsPerTri(int edgeVerts) {
|
||||
* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU
|
||||
* version if desired.
|
||||
*/
|
||||
void FillRetainedVerts(VecDH<Barycentric>& vertBary,
|
||||
const VecDH<Halfedge>& halfedge_) {
|
||||
void FillRetainedVerts(Vec<Barycentric>& vertBary,
|
||||
const Vec<Halfedge>& halfedge_) {
|
||||
const int numTri = halfedge_.size() / 3;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
@@ -52,9 +52,9 @@ void FillRetainedVerts(VecDH<Barycentric>& vertBary,
|
||||
}
|
||||
|
||||
struct ReindexHalfedge {
|
||||
int* half2Edge;
|
||||
VecView<int> half2Edge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, TmpEdge> in) {
|
||||
void operator()(thrust::tuple<int, TmpEdge> in) {
|
||||
const int edge = thrust::get<0>(in);
|
||||
const int halfedge = thrust::get<1>(in).halfedgeIdx;
|
||||
|
||||
@@ -63,12 +63,12 @@ struct ReindexHalfedge {
|
||||
};
|
||||
|
||||
struct EdgeVerts {
|
||||
glm::vec3* vertPos;
|
||||
Barycentric* vertBary;
|
||||
VecView<glm::vec3> vertPos;
|
||||
VecView<Barycentric> vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, TmpEdge> in) {
|
||||
void operator()(thrust::tuple<int, TmpEdge> in) {
|
||||
int edge = thrust::get<0>(in);
|
||||
TmpEdge edgeVerts = thrust::get<1>(in);
|
||||
|
||||
@@ -91,13 +91,13 @@ struct EdgeVerts {
|
||||
};
|
||||
|
||||
struct InteriorVerts {
|
||||
glm::vec3* vertPos;
|
||||
Barycentric* vertBary;
|
||||
VecView<glm::vec3> vertPos;
|
||||
VecView<Barycentric> vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
const Halfedge* halfedge;
|
||||
VecView<const Halfedge> halfedge;
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
void operator()(int tri) {
|
||||
const float invTotal = 1.0f / n;
|
||||
int pos = startIdx + tri * VertsPerTri(n - 2);
|
||||
for (int i = 0; i <= n; ++i) {
|
||||
@@ -119,21 +119,21 @@ struct InteriorVerts {
|
||||
};
|
||||
|
||||
struct SplitTris {
|
||||
glm::ivec3* triVerts;
|
||||
const Halfedge* halfedge;
|
||||
const int* half2Edge;
|
||||
VecView<glm::ivec3> triVerts;
|
||||
VecView<Halfedge> halfedge;
|
||||
VecView<const int> half2Edge;
|
||||
const int edgeIdx;
|
||||
const int triIdx;
|
||||
const int n;
|
||||
|
||||
__host__ __device__ int EdgeVert(int i, int inHalfedge) const {
|
||||
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);
|
||||
}
|
||||
|
||||
__host__ __device__ int TriVert(int i, int j, int tri) const {
|
||||
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;
|
||||
}
|
||||
|
||||
__host__ __device__ int Vert(int i, int j, int tri) const {
|
||||
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);
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
void operator()(int tri) {
|
||||
int pos = n * n * tri;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int j = 0; j < n - i; ++j) {
|
||||
@@ -182,13 +182,12 @@ struct SplitTris {
|
||||
};
|
||||
|
||||
struct SmoothBezier {
|
||||
const glm::vec3* vertPos;
|
||||
const glm::vec3* triNormal;
|
||||
const glm::vec3* vertNormal;
|
||||
const Halfedge* halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
VecView<const glm::vec3> triNormal;
|
||||
VecView<const glm::vec3> vertNormal;
|
||||
VecView<const Halfedge> halfedge;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<glm::vec4&, Halfedge> inOut) {
|
||||
void operator()(thrust::tuple<glm::vec4&, Halfedge> inOut) {
|
||||
glm::vec4& tangent = thrust::get<0>(inOut);
|
||||
const Halfedge edge = thrust::get<1>(inOut);
|
||||
|
||||
@@ -213,33 +212,27 @@ struct SmoothBezier {
|
||||
};
|
||||
|
||||
struct InterpTri {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec4* halfedgeTangent;
|
||||
const glm::vec3* vertPos;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec4> halfedgeTangent;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
|
||||
__host__ __device__ glm::vec4 Homogeneous(glm::vec4 v) const {
|
||||
glm::vec4 Homogeneous(glm::vec4 v) const {
|
||||
v.x *= v.w;
|
||||
v.y *= v.w;
|
||||
v.z *= v.w;
|
||||
return v;
|
||||
}
|
||||
|
||||
__host__ __device__ glm::vec4 Homogeneous(glm::vec3 v) const {
|
||||
return glm::vec4(v, 1.0f);
|
||||
}
|
||||
glm::vec4 Homogeneous(glm::vec3 v) const { return glm::vec4(v, 1.0f); }
|
||||
|
||||
__host__ __device__ glm::vec3 HNormalize(glm::vec4 v) const {
|
||||
return glm::vec3(v) / v.w;
|
||||
}
|
||||
glm::vec3 HNormalize(glm::vec4 v) const { return glm::vec3(v) / v.w; }
|
||||
|
||||
__host__ __device__ glm::vec4 Bezier(glm::vec3 point,
|
||||
glm::vec4 tangent) const {
|
||||
glm::vec4 Bezier(glm::vec3 point, glm::vec4 tangent) const {
|
||||
return Homogeneous(glm::vec4(point, 0) + tangent);
|
||||
}
|
||||
|
||||
__host__ __device__ glm::mat2x4 CubicBezier2Linear(glm::vec4 p0, glm::vec4 p1,
|
||||
glm::vec4 p2, glm::vec4 p3,
|
||||
float x) const {
|
||||
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);
|
||||
@@ -247,16 +240,15 @@ struct InterpTri {
|
||||
return out;
|
||||
}
|
||||
|
||||
__host__ __device__ glm::vec3 BezierPoint(glm::mat2x4 points, float x) const {
|
||||
glm::vec3 BezierPoint(glm::mat2x4 points, float x) const {
|
||||
return HNormalize(glm::mix(points[0], points[1], x));
|
||||
}
|
||||
|
||||
__host__ __device__ glm::vec3 BezierTangent(glm::mat2x4 points) const {
|
||||
glm::vec3 BezierTangent(glm::mat2x4 points) const {
|
||||
return glm::normalize(HNormalize(points[1]) - HNormalize(points[0]));
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<glm::vec3&, Barycentric> inOut) {
|
||||
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;
|
||||
@@ -333,11 +325,10 @@ void Manifold::Impl::CreateTangents(
|
||||
|
||||
for_each_n(autoPolicy(numHalfedge),
|
||||
zip(halfedgeTangent_.begin(), halfedge_.cbegin()), numHalfedge,
|
||||
SmoothBezier({vertPos_.cptrD(), faceNormal_.cptrD(),
|
||||
vertNormal_.cptrD(), halfedge_.cptrD()}));
|
||||
SmoothBezier({vertPos_, faceNormal_, vertNormal_, halfedge_}));
|
||||
|
||||
if (!sharpenedEdges.empty()) {
|
||||
const VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
const Vec<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
// sharpenedEdges are referenced to the input Mesh, but the triangles have
|
||||
// been sorted in creating the Manifold, so the indices are converted using
|
||||
@@ -370,7 +361,7 @@ void Manifold::Impl::CreateTangents(
|
||||
{edge.second, edge.first});
|
||||
}
|
||||
|
||||
VecDH<glm::vec4>& tangent = halfedgeTangent_;
|
||||
Vec<glm::vec4>& tangent = halfedgeTangent_;
|
||||
for (const auto& value : vertTangents) {
|
||||
const std::vector<Pair>& vert = value.second;
|
||||
// Sharp edges that end are smooth at their terminal vert.
|
||||
@@ -431,8 +422,8 @@ void Manifold::Impl::CreateTangents(
|
||||
* run after the new vertices have moved, which is a likely scenario after
|
||||
* refinement (smoothing).
|
||||
*/
|
||||
VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return VecDH<Barycentric>();
|
||||
Vec<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return Vec<Barycentric>();
|
||||
faceNormal_.resize(0);
|
||||
vertNormal_.resize(0);
|
||||
const int numVert = NumVert();
|
||||
@@ -442,28 +433,27 @@ VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
const int vertsPerEdge = n - 1;
|
||||
const int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
vertPos_.resize(triVertStart + numTri * VertsPerTri(n - 2));
|
||||
VecDH<Barycentric> vertBary(vertPos_.size());
|
||||
Vec<Barycentric> vertBary(vertPos_.size());
|
||||
FillRetainedVerts(vertBary, halfedge_);
|
||||
|
||||
MeshRelationD oldMeshRelation = std::move(meshRelation_);
|
||||
meshRelation_.triRef.resize(n * n * numTri);
|
||||
meshRelation_.originalID = oldMeshRelation.originalID;
|
||||
|
||||
VecDH<TmpEdge> edges = CreateTmpEdges(halfedge_);
|
||||
VecDH<int> half2Edge(2 * numEdge);
|
||||
Vec<TmpEdge> edges = CreateTmpEdges(halfedge_);
|
||||
Vec<int> half2Edge(2 * numEdge);
|
||||
auto policy = autoPolicy(numEdge);
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
ReindexHalfedge({half2Edge.ptrD()}));
|
||||
ReindexHalfedge({half2Edge}));
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
EdgeVerts({vertPos_.ptrD(), vertBary.ptrD(), numVert, n}));
|
||||
EdgeVerts({vertPos_, vertBary, numVert, n}));
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), vertBary.ptrD(), triVertStart, n,
|
||||
halfedge_.cptrD()}));
|
||||
InteriorVerts({vertPos_, vertBary, triVertStart, n, halfedge_}));
|
||||
// Create sub-triangles
|
||||
VecDH<glm::ivec3> triVerts(n * n * numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
SplitTris({triVerts.ptrD(), halfedge_.cptrD(), half2Edge.cptrD(),
|
||||
numVert, triVertStart, n}));
|
||||
Vec<glm::ivec3> triVerts(n * n * numTri);
|
||||
for_each_n(
|
||||
policy, countAt(0), numTri,
|
||||
SplitTris({triVerts, halfedge_, half2Edge, numVert, triVertStart, n}));
|
||||
CreateHalfedges(triVerts);
|
||||
// Make original since the subdivided faces are intended to be warped into
|
||||
// being non-coplanar, and hence not being related to the original faces.
|
||||
@@ -483,14 +473,13 @@ VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
|
||||
void Manifold::Impl::Refine(int n) {
|
||||
Manifold::Impl old = *this;
|
||||
VecDH<Barycentric> vertBary = Subdivide(n);
|
||||
Vec<Barycentric> vertBary = Subdivide(n);
|
||||
if (vertBary.size() == 0) return;
|
||||
|
||||
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
|
||||
for_each_n(autoPolicy(NumTri()), zip(vertPos_.begin(), vertBary.begin()),
|
||||
NumVert(),
|
||||
InterpTri({old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
|
||||
old.vertPos_.cptrD()}));
|
||||
InterpTri({old.halfedge_, old.halfedgeTangent_, old.vertPos_}));
|
||||
}
|
||||
|
||||
halfedgeTangent_.resize(0);
|
||||
|
||||
+102
-110
@@ -12,12 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <thrust/sequence.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
|
||||
#include "graph.h"
|
||||
#include "impl.h"
|
||||
#include "par.h"
|
||||
|
||||
@@ -27,7 +25,7 @@ using namespace manifold;
|
||||
constexpr uint32_t kNoCode = 0xFFFFFFFFu;
|
||||
|
||||
struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
|
||||
__host__ __device__ void MakeForward(Halfedge& a) {
|
||||
void MakeForward(Halfedge& a) {
|
||||
if (!a.IsForward()) {
|
||||
int tmp = a.startVert;
|
||||
a.startVert = a.endVert;
|
||||
@@ -35,11 +33,11 @@ struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ int MaxOrMinus(int a, int b) {
|
||||
int MaxOrMinus(int a, int b) {
|
||||
return glm::min(a, b) < 0 ? -1 : glm::max(a, b);
|
||||
}
|
||||
|
||||
__host__ __device__ Halfedge operator()(Halfedge a, Halfedge b) {
|
||||
Halfedge operator()(Halfedge a, Halfedge b) {
|
||||
MakeForward(a);
|
||||
MakeForward(b);
|
||||
a.startVert = glm::min(a.startVert, b.startVert);
|
||||
@@ -50,7 +48,7 @@ struct Extrema : public thrust::binary_function<Halfedge, Halfedge, Halfedge> {
|
||||
}
|
||||
};
|
||||
|
||||
__host__ __device__ uint32_t SpreadBits3(uint32_t v) {
|
||||
uint32_t SpreadBits3(uint32_t v) {
|
||||
v = 0xFF0000FFu & (v * 0x00010001u);
|
||||
v = 0x0F00F00Fu & (v * 0x00000101u);
|
||||
v = 0xC30C30C3u & (v * 0x00000011u);
|
||||
@@ -58,7 +56,7 @@ __host__ __device__ uint32_t SpreadBits3(uint32_t v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
__host__ __device__ uint32_t MortonCode(glm::vec3 position, Box bBox) {
|
||||
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;
|
||||
@@ -74,20 +72,18 @@ __host__ __device__ uint32_t MortonCode(glm::vec3 position, Box bBox) {
|
||||
struct Morton {
|
||||
const Box bBox;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<uint32_t&, const glm::vec3&> inout) {
|
||||
void operator()(thrust::tuple<uint32_t&, const glm::vec3&> inout) {
|
||||
glm::vec3 position = thrust::get<1>(inout);
|
||||
thrust::get<0>(inout) = MortonCode(position, bBox);
|
||||
}
|
||||
};
|
||||
|
||||
struct FaceMortonBox {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
VecView<const Halfedge> halfedge;
|
||||
VecView<const glm::vec3> vertPos;
|
||||
const Box bBox;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<uint32_t&, Box&, int> inout) {
|
||||
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);
|
||||
@@ -114,9 +110,9 @@ struct FaceMortonBox {
|
||||
};
|
||||
|
||||
struct Reindex {
|
||||
const int* indexInv;
|
||||
VecView<const int> indexInv;
|
||||
|
||||
__host__ __device__ void operator()(Halfedge& edge) {
|
||||
void operator()(Halfedge& edge) {
|
||||
if (edge.startVert < 0) return;
|
||||
edge.startVert = indexInv[edge.startVert];
|
||||
edge.endVert = indexInv[edge.endVert];
|
||||
@@ -124,21 +120,22 @@ struct Reindex {
|
||||
};
|
||||
|
||||
struct MarkProp {
|
||||
int* keep;
|
||||
VecView<int> keep;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3 triProp) {
|
||||
void operator()(glm::ivec3 triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
keep[triProp[i]] = 1;
|
||||
reinterpret_cast<std::atomic<int>*>(&keep[triProp[i]])
|
||||
->store(1, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GatherProps {
|
||||
float* properties;
|
||||
const float* oldProperties;
|
||||
VecView<float> properties;
|
||||
VecView<const float> oldProperties;
|
||||
const int numProp;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
|
||||
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);
|
||||
@@ -150,9 +147,9 @@ struct GatherProps {
|
||||
};
|
||||
|
||||
struct ReindexProps {
|
||||
const int* old2new;
|
||||
VecView<const int> old2new;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3& triProp) {
|
||||
void operator()(glm::ivec3& triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triProp[i] = old2new[triProp[i]];
|
||||
}
|
||||
@@ -160,25 +157,25 @@ struct ReindexProps {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
VecDH<T> tmp(std::move(inOut));
|
||||
void Permute(Vec<T>& inOut, const Vec<int>& new2Old) {
|
||||
Vec<T> tmp(std::move(inOut));
|
||||
inOut.resize(new2Old.size());
|
||||
gather(autoPolicy(new2Old.size()), new2Old.begin(), new2Old.end(),
|
||||
tmp.begin(), inOut.begin());
|
||||
}
|
||||
|
||||
template void Permute<TriRef>(VecDH<TriRef>&, const VecDH<int>&);
|
||||
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
|
||||
template void Permute<TriRef>(Vec<TriRef>&, const Vec<int>&);
|
||||
template void Permute<glm::vec3>(Vec<glm::vec3>&, const Vec<int>&);
|
||||
|
||||
struct ReindexFace {
|
||||
Halfedge* halfedge;
|
||||
glm::vec4* halfedgeTangent;
|
||||
const Halfedge* oldHalfedge;
|
||||
const glm::vec4* oldHalfedgeTangent;
|
||||
const int* faceNew2Old;
|
||||
const int* faceOld2New;
|
||||
VecView<Halfedge> halfedge;
|
||||
VecView<glm::vec4> halfedgeTangent;
|
||||
VecView<const Halfedge> oldHalfedge;
|
||||
VecView<const glm::vec4> oldHalfedgeTangent;
|
||||
VecView<const int> faceNew2Old;
|
||||
VecView<const int> faceOld2New;
|
||||
|
||||
__host__ __device__ void operator()(int newFace) {
|
||||
void operator()(int newFace) {
|
||||
const int oldFace = faceNew2Old[newFace];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int oldEdge = 3 * oldFace + i;
|
||||
@@ -189,7 +186,7 @@ struct ReindexFace {
|
||||
edge.pairedHalfedge = 3 * faceOld2New[pairedFace] + offset;
|
||||
const int newEdge = 3 * newFace + i;
|
||||
halfedge[newEdge] = edge;
|
||||
if (oldHalfedgeTangent != nullptr) {
|
||||
if (!oldHalfedgeTangent.empty()) {
|
||||
halfedgeTangent[newEdge] = oldHalfedgeTangent[oldEdge];
|
||||
}
|
||||
}
|
||||
@@ -197,13 +194,12 @@ struct ReindexFace {
|
||||
};
|
||||
|
||||
struct VertMortonBox {
|
||||
const float* vertProperties;
|
||||
VecView<const float> vertProperties;
|
||||
const uint32_t numProp;
|
||||
const float tol;
|
||||
const Box bBox;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<uint32_t&, Box&, int> inout) {
|
||||
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);
|
||||
@@ -220,7 +216,7 @@ struct VertMortonBox {
|
||||
};
|
||||
|
||||
struct Duplicate {
|
||||
__host__ __device__ thrust::pair<float, float> operator()(float x) {
|
||||
thrust::pair<float, float> operator()(float x) {
|
||||
return thrust::make_pair(x, x);
|
||||
}
|
||||
};
|
||||
@@ -228,8 +224,8 @@ struct Duplicate {
|
||||
struct MinMax : public thrust::binary_function<thrust::pair<float, float>,
|
||||
thrust::pair<float, float>,
|
||||
thrust::pair<float, float>> {
|
||||
__host__ __device__ thrust::pair<float, float> operator()(
|
||||
thrust::pair<float, float> a, thrust::pair<float, float> b) {
|
||||
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));
|
||||
}
|
||||
@@ -255,8 +251,8 @@ void Manifold::Impl::Finish() {
|
||||
}
|
||||
|
||||
SortVerts();
|
||||
VecDH<Box> faceBox;
|
||||
VecDH<uint32_t> faceMorton;
|
||||
Vec<Box> faceBox;
|
||||
Vec<uint32_t> faceMorton;
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
@@ -305,24 +301,27 @@ void Manifold::Impl::Finish() {
|
||||
*/
|
||||
void Manifold::Impl::SortVerts() {
|
||||
const int numVert = NumVert();
|
||||
VecDH<uint32_t> vertMorton(numVert);
|
||||
Vec<uint32_t> vertMorton(numVert);
|
||||
auto policy = autoPolicy(numVert);
|
||||
for_each_n(policy, zip(vertMorton.begin(), vertPos_.cbegin()), numVert,
|
||||
Morton({bBox_}));
|
||||
|
||||
VecDH<int> vertNew2Old(numVert);
|
||||
Vec<int> vertNew2Old(numVert);
|
||||
sequence(policy, vertNew2Old.begin(), vertNew2Old.end());
|
||||
|
||||
sort_by_key(policy, vertMorton.begin(), vertMorton.end(),
|
||||
vertNew2Old.begin());
|
||||
stable_sort(policy, zip(vertMorton.begin(), vertNew2Old.begin()),
|
||||
zip(vertMorton.end(), vertNew2Old.end()),
|
||||
[](const thrust::tuple<uint32_t, int>& a,
|
||||
const thrust::tuple<uint32_t, int>& b) {
|
||||
return thrust::get<0>(a) < thrust::get<0>(b);
|
||||
});
|
||||
|
||||
ReindexVerts(vertNew2Old, numVert);
|
||||
|
||||
// Verts were flagged for removal with NaNs and assigned kNoCode to sort
|
||||
// them to the end, which allows them to be removed.
|
||||
const int newNumVert =
|
||||
lower_bound<decltype(vertMorton.begin())>(policy, vertMorton.begin(),
|
||||
vertMorton.end(), kNoCode) -
|
||||
std::lower_bound(vertMorton.begin(), vertMorton.end(), kNoCode) -
|
||||
vertMorton.begin();
|
||||
|
||||
vertNew2Old.resize(newNumVert);
|
||||
@@ -338,13 +337,12 @@ void Manifold::Impl::SortVerts() {
|
||||
* vertNew2Old. This may be a subset, so the total number of original verts is
|
||||
* also given.
|
||||
*/
|
||||
void Manifold::Impl::ReindexVerts(const VecDH<int>& vertNew2Old,
|
||||
int oldNumVert) {
|
||||
VecDH<int> vertOld2New(oldNumVert);
|
||||
void Manifold::Impl::ReindexVerts(const Vec<int>& vertNew2Old, int oldNumVert) {
|
||||
Vec<int> vertOld2New(oldNumVert);
|
||||
scatter(autoPolicy(oldNumVert), countAt(0), countAt(NumVert()),
|
||||
vertNew2Old.begin(), vertOld2New.begin());
|
||||
for_each(autoPolicy(oldNumVert), halfedge_.begin(), halfedge_.end(),
|
||||
Reindex({vertOld2New.cptrD()}));
|
||||
Reindex({vertOld2New}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,23 +352,22 @@ void Manifold::Impl::CompactProps() {
|
||||
if (meshRelation_.numProp == 0) return;
|
||||
|
||||
const int numVerts = meshRelation_.properties.size() / meshRelation_.numProp;
|
||||
VecDH<int> keep(numVerts, 0);
|
||||
Vec<int> keep(numVerts, 0);
|
||||
auto policy = autoPolicy(numVerts);
|
||||
|
||||
for_each(policy, meshRelation_.triProperties.cbegin(),
|
||||
meshRelation_.triProperties.cend(), MarkProp({keep.ptrD()}));
|
||||
VecDH<int> propOld2New(numVerts + 1, 0);
|
||||
meshRelation_.triProperties.cend(), MarkProp({keep}));
|
||||
Vec<int> propOld2New(numVerts + 1, 0);
|
||||
inclusive_scan(policy, keep.begin(), keep.end(), propOld2New.begin() + 1);
|
||||
|
||||
VecDH<float> oldProp = meshRelation_.properties;
|
||||
Vec<float> oldProp = meshRelation_.properties;
|
||||
const int numVertsNew = propOld2New[numVerts];
|
||||
meshRelation_.properties.resize(meshRelation_.numProp * numVertsNew);
|
||||
for_each_n(policy, zip(countAt(0), propOld2New.cbegin(), keep.cbegin()),
|
||||
numVerts,
|
||||
GatherProps({meshRelation_.properties.ptrD(), oldProp.cptrD(),
|
||||
meshRelation_.numProp}));
|
||||
for_each_n(
|
||||
policy, zip(countAt(0), propOld2New.cbegin(), keep.cbegin()), numVerts,
|
||||
GatherProps({meshRelation_.properties, oldProp, meshRelation_.numProp}));
|
||||
for_each_n(policy, meshRelation_.triProperties.begin(), NumTri(),
|
||||
ReindexProps({propOld2New.cptrD()}));
|
||||
ReindexProps({propOld2New}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,27 +375,30 @@ void Manifold::Impl::CompactProps() {
|
||||
* codes of the faces, respectively. The Morton code is based on the center of
|
||||
* the bounding box.
|
||||
*/
|
||||
void Manifold::Impl::GetFaceBoxMorton(VecDH<Box>& faceBox,
|
||||
VecDH<uint32_t>& faceMorton) const {
|
||||
void Manifold::Impl::GetFaceBoxMorton(Vec<Box>& faceBox,
|
||||
Vec<uint32_t>& faceMorton) const {
|
||||
faceBox.resize(NumTri());
|
||||
faceMorton.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri()),
|
||||
zip(faceMorton.begin(), faceBox.begin(), countAt(0)), NumTri(),
|
||||
FaceMortonBox({halfedge_.cptrD(), vertPos_.cptrD(), bBox_}));
|
||||
FaceMortonBox({halfedge_, vertPos_, bBox_}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the faces of this manifold according to their input Morton code. The
|
||||
* bounding box and Morton code arrays are also sorted accordingly.
|
||||
*/
|
||||
void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
|
||||
VecDH<uint32_t>& faceMorton) {
|
||||
VecDH<int> faceNew2Old(NumTri());
|
||||
void Manifold::Impl::SortFaces(Vec<Box>& faceBox, Vec<uint32_t>& faceMorton) {
|
||||
Vec<int> faceNew2Old(NumTri());
|
||||
auto policy = autoPolicy(faceNew2Old.size());
|
||||
sequence(policy, faceNew2Old.begin(), faceNew2Old.end());
|
||||
|
||||
sort_by_key(policy, faceMorton.begin(), faceMorton.end(),
|
||||
faceNew2Old.begin());
|
||||
stable_sort(policy, zip(faceMorton.begin(), faceNew2Old.begin()),
|
||||
zip(faceMorton.end(), faceNew2Old.end()),
|
||||
[](const thrust::tuple<uint32_t, int>& a,
|
||||
const thrust::tuple<uint32_t, int>& b) {
|
||||
return thrust::get<0>(a) < thrust::get<0>(b);
|
||||
});
|
||||
|
||||
// Tris were flagged for removal with pairedHalfedge = -1 and assigned kNoCode
|
||||
// to sort them to the end, which allows them to be removed.
|
||||
@@ -418,7 +418,7 @@ void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
|
||||
* another manifold, given by oldHalfedge. Input faceNew2Old defines the old
|
||||
* faces to gather into this.
|
||||
*/
|
||||
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
void Manifold::Impl::GatherFaces(const Vec<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
if (meshRelation_.triRef.size() == NumTri())
|
||||
Permute(meshRelation_.triRef, faceNew2Old);
|
||||
@@ -426,9 +426,9 @@ void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
Permute(meshRelation_.triProperties, faceNew2Old);
|
||||
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
|
||||
|
||||
VecDH<Halfedge> oldHalfedge(std::move(halfedge_));
|
||||
VecDH<glm::vec4> oldHalfedgeTangent(std::move(halfedgeTangent_));
|
||||
VecDH<int> faceOld2New(oldHalfedge.size() / 3);
|
||||
Vec<Halfedge> oldHalfedge(std::move(halfedge_));
|
||||
Vec<glm::vec4> oldHalfedgeTangent(std::move(halfedgeTangent_));
|
||||
Vec<int> faceOld2New(oldHalfedge.size() / 3);
|
||||
auto policy = autoPolicy(numTri);
|
||||
scatter(policy, countAt(0), countAt(numTri), faceNew2Old.begin(),
|
||||
faceOld2New.begin());
|
||||
@@ -436,13 +436,11 @@ void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
halfedge_.resize(3 * numTri);
|
||||
if (oldHalfedgeTangent.size() != 0) halfedgeTangent_.resize(3 * numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
ReindexFace({halfedge_.ptrD(), halfedgeTangent_.ptrD(),
|
||||
oldHalfedge.cptrD(), oldHalfedgeTangent.cptrD(),
|
||||
faceNew2Old.cptrD(), faceOld2New.cptrD()}));
|
||||
ReindexFace({halfedge_, halfedgeTangent_, oldHalfedge,
|
||||
oldHalfedgeTangent, faceNew2Old, faceOld2New}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::GatherFaces(const Impl& old,
|
||||
const VecDH<int>& faceNew2Old) {
|
||||
void Manifold::Impl::GatherFaces(const Impl& old, const Vec<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
@@ -468,16 +466,15 @@ void Manifold::Impl::GatherFaces(const Impl& old,
|
||||
old.faceNormal_.begin(), faceNormal_.begin());
|
||||
}
|
||||
|
||||
VecDH<int> faceOld2New(old.NumTri());
|
||||
Vec<int> faceOld2New(old.NumTri());
|
||||
scatter(policy, countAt(0), countAt(numTri), faceNew2Old.begin(),
|
||||
faceOld2New.begin());
|
||||
|
||||
halfedge_.resize(3 * numTri);
|
||||
if (old.halfedgeTangent_.size() != 0) halfedgeTangent_.resize(3 * numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
ReindexFace({halfedge_.ptrD(), halfedgeTangent_.ptrD(),
|
||||
old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
|
||||
faceNew2Old.cptrD(), faceOld2New.cptrD()}));
|
||||
ReindexFace({halfedge_, halfedgeTangent_, old.halfedge_,
|
||||
old.halfedgeTangent_, faceNew2Old, faceOld2New}));
|
||||
}
|
||||
|
||||
/// Constructs a position-only MeshGL from the input Mesh.
|
||||
@@ -543,18 +540,18 @@ bool MeshGL::Merge() {
|
||||
}
|
||||
|
||||
const int numOpenVert = openEdges.size();
|
||||
VecDH<int> openVerts(numOpenVert);
|
||||
Vec<int> openVerts(numOpenVert);
|
||||
int i = 0;
|
||||
for (const auto& edge : openEdges) {
|
||||
const int vert = edge.first;
|
||||
openVerts[i++] = vert;
|
||||
}
|
||||
|
||||
VecDH<float> vertPropD(vertProperties);
|
||||
Vec<float> vertPropD(vertProperties);
|
||||
Box bBox;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
strided_range<VecDH<float>::Iter> iPos(vertPropD.begin() + i,
|
||||
vertPropD.end(), numProp);
|
||||
strided_range<Vec<float>::Iter> iPos(vertPropD.begin() + i, vertPropD.end(),
|
||||
numProp);
|
||||
auto minMax = transform_reduce<thrust::pair<float, float>>(
|
||||
autoPolicy(numVert), iPos.begin(), iPos.end(), Duplicate(),
|
||||
thrust::make_pair(std::numeric_limits<float>::infinity(),
|
||||
@@ -567,43 +564,38 @@ bool MeshGL::Merge() {
|
||||
if (precision < 0) return false;
|
||||
|
||||
auto policy = autoPolicy(numOpenVert);
|
||||
VecDH<Box> vertBox(numOpenVert);
|
||||
VecDH<uint32_t> vertMorton(numOpenVert);
|
||||
Vec<Box> vertBox(numOpenVert);
|
||||
Vec<uint32_t> vertMorton(numOpenVert);
|
||||
|
||||
for_each_n(policy,
|
||||
zip(vertMorton.begin(), vertBox.begin(), openVerts.cbegin()),
|
||||
numOpenVert,
|
||||
VertMortonBox({vertPropD.cptrD(), numProp, precision, bBox}));
|
||||
numOpenVert, VertMortonBox({vertPropD, numProp, precision, bBox}));
|
||||
|
||||
sort_by_key(policy, vertMorton.begin(), vertMorton.end(),
|
||||
zip(vertBox.begin(), openVerts.begin()));
|
||||
stable_sort(policy,
|
||||
zip(vertMorton.begin(), vertBox.begin(), openVerts.begin()),
|
||||
zip(vertMorton.end(), vertBox.end(), openVerts.end()),
|
||||
[](const thrust::tuple<uint32_t, Box, int>& a,
|
||||
const thrust::tuple<uint32_t, Box, int>& b) {
|
||||
return thrust::get<0>(a) < thrust::get<0>(b);
|
||||
});
|
||||
|
||||
Collider collider(vertBox, vertMorton);
|
||||
SparseIndices toMerge = collider.Collisions<true>(vertBox);
|
||||
SparseIndices toMerge = collider.Collisions<true>(vertBox.cview());
|
||||
|
||||
Graph graph;
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
UnionFind<> uf(numVert);
|
||||
for (int i = 0; i < mergeFromVert.size(); ++i) {
|
||||
graph.add_edge(static_cast<int>(mergeFromVert[i]),
|
||||
static_cast<int>(mergeToVert[i]));
|
||||
uf.unionXY(static_cast<int>(mergeFromVert[i]),
|
||||
static_cast<int>(mergeToVert[i]));
|
||||
}
|
||||
for (int i = 0; i < toMerge.size(); ++i) {
|
||||
graph.add_edge(openVerts[toMerge.Get(0)[i]], openVerts[toMerge.Get(1)[i]]);
|
||||
}
|
||||
|
||||
std::vector<int> vertLabels;
|
||||
const int numLabels = ConnectedComponents(vertLabels, graph);
|
||||
std::vector<int> label2vert(numLabels);
|
||||
for (int v = 0; v < numVert; ++v) {
|
||||
label2vert[vertLabels[v]] = v;
|
||||
uf.unionXY(openVerts[toMerge.Get(i, false)],
|
||||
openVerts[toMerge.Get(i, true)]);
|
||||
}
|
||||
|
||||
mergeToVert.clear();
|
||||
mergeFromVert.clear();
|
||||
for (int v = 0; v < numVert; ++v) {
|
||||
const int mergeTo = label2vert[vertLabels[v]];
|
||||
const int mergeTo = uf.find(v);
|
||||
if (mergeTo != v) {
|
||||
mergeFromVert.push_back(v);
|
||||
mergeToVert.push_back(mergeTo);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (polygon)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
|
||||
+614
-845
File diff suppressed because it is too large
Load Diff
+17
-3
@@ -14,11 +14,25 @@
|
||||
|
||||
project(sdf)
|
||||
|
||||
add_library(${PROJECT_NAME} INTERFACE)
|
||||
add_library(${PROJECT_NAME} src/sdf.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
INTERFACE
|
||||
PUBLIC
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE utilities)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
|
||||
install(
|
||||
TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}
|
||||
COMPONENT runtime
|
||||
)
|
||||
install(
|
||||
DIRECTORY include/
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
|
||||
+5
-366
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -14,372 +14,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hashtable.h"
|
||||
#include "optional"
|
||||
#include "par.h"
|
||||
#include <functional>
|
||||
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
__host__ __device__ Uint64 identity(Uint64 x) { return x; }
|
||||
|
||||
__host__ __device__ glm::ivec3 TetTri0(int i) {
|
||||
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
|
||||
{0, 3, 4}, //
|
||||
{0, 1, 5}, //
|
||||
{1, 5, 3}, //
|
||||
{1, 4, 2}, //
|
||||
{1, 0, 3}, //
|
||||
{2, 5, 0}, //
|
||||
{5, 3, 2}, //
|
||||
{2, 3, 5}, //
|
||||
{0, 5, 2}, //
|
||||
{3, 0, 1}, //
|
||||
{2, 4, 1}, //
|
||||
{3, 5, 1}, //
|
||||
{5, 1, 0}, //
|
||||
{4, 3, 0}, //
|
||||
{-1, -1, -1}};
|
||||
return tetTri0[i];
|
||||
}
|
||||
|
||||
__host__ __device__ glm::ivec3 TetTri1(int i) {
|
||||
constexpr glm::ivec3 tetTri1[16] = {{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{3, 4, 1}, //
|
||||
{-1, -1, -1}, //
|
||||
{3, 2, 1}, //
|
||||
{0, 4, 2}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{2, 4, 0}, //
|
||||
{1, 2, 3}, //
|
||||
{-1, -1, -1}, //
|
||||
{1, 4, 3}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}};
|
||||
return tetTri1[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}, //
|
||||
{0, 0, 1, 0}, //
|
||||
{-1, 0, 0, 1}, //
|
||||
{0, -1, 0, 1}, //
|
||||
{0, 0, -1, 1}};
|
||||
return neighbors[i];
|
||||
}
|
||||
|
||||
__host__ __device__ Uint64 SpreadBits3(Uint64 v) {
|
||||
v = v & 0x1fffff;
|
||||
v = (v | v << 32) & 0x1f00000000ffff;
|
||||
v = (v | v << 16) & 0x1f0000ff0000ff;
|
||||
v = (v | v << 8) & 0x100f00f00f00f00f;
|
||||
v = (v | v << 4) & 0x10c30c30c30c30c3;
|
||||
v = (v | v << 2) & 0x1249249249249249;
|
||||
return v;
|
||||
}
|
||||
|
||||
__host__ __device__ Uint64 SqueezeBits3(Uint64 v) {
|
||||
v = v & 0x1249249249249249;
|
||||
v = (v ^ v >> 2) & 0x10c30c30c30c30c3;
|
||||
v = (v ^ v >> 4) & 0x100f00f00f00f00f;
|
||||
v = (v ^ v >> 8) & 0x1f0000ff0000ff;
|
||||
v = (v ^ v >> 16) & 0x1f00000000ffff;
|
||||
v = (v ^ v >> 32) & 0x1fffff;
|
||||
return 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.
|
||||
__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);
|
||||
}
|
||||
|
||||
__host__ __device__ glm::ivec4 DecodeMorton(Uint64 code) {
|
||||
glm::ivec4 index;
|
||||
index.x = SqueezeBits3(code >> 1);
|
||||
index.y = SqueezeBits3(code >> 2);
|
||||
index.z = SqueezeBits3(code >> 3);
|
||||
index.w = code & 0x1u;
|
||||
return index;
|
||||
}
|
||||
|
||||
struct GridVert {
|
||||
float distance = NAN;
|
||||
int edgeVerts[7] = {-1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
__host__ __device__ int Inside() const { return distance > 0 ? 1 : -1; }
|
||||
|
||||
__host__ __device__ int NeighborInside(int i) const {
|
||||
return Inside() * (edgeVerts[i] < 0 ? 1 : -1);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
struct ComputeVerts {
|
||||
glm::vec3* vertPos;
|
||||
int* vertIndex;
|
||||
HashTableD<GridVert, identity> gridVerts;
|
||||
const Func sdf;
|
||||
const glm::vec3 origin;
|
||||
const glm::ivec3 gridSize;
|
||||
const glm::vec3 spacing;
|
||||
const float level;
|
||||
|
||||
inline __host__ __device__ glm::vec3 Position(glm::ivec4 gridIndex) const {
|
||||
return origin +
|
||||
spacing * (glm::vec3(gridIndex) + (gridIndex.w == 1 ? 0.0f : -0.5f));
|
||||
}
|
||||
|
||||
inline __host__ __device__ float BoundedSDF(glm::ivec4 gridIndex) const {
|
||||
const float d = sdf(Position(gridIndex)) - level;
|
||||
|
||||
const glm::ivec3 xyz(gridIndex);
|
||||
const bool onLowerBound = glm::any(glm::lessThanEqual(xyz, glm::ivec3(0)));
|
||||
const bool onUpperBound = glm::any(glm::greaterThanEqual(xyz, gridSize));
|
||||
const bool onHalfBound =
|
||||
gridIndex.w == 1 && glm::any(glm::greaterThanEqual(xyz, gridSize - 1));
|
||||
if (onLowerBound || onUpperBound || onHalfBound) return glm::min(d, 0.0f);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
inline __host__ __device__ void operator()(Uint64 mortonCode) {
|
||||
if (gridVerts.Full()) return;
|
||||
|
||||
const glm::ivec4 gridIndex = DecodeMorton(mortonCode);
|
||||
|
||||
if (glm::any(glm::greaterThan(glm::ivec3(gridIndex), gridSize))) return;
|
||||
|
||||
const glm::vec3 position = Position(gridIndex);
|
||||
|
||||
GridVert gridVert;
|
||||
gridVert.distance = BoundedSDF(gridIndex);
|
||||
|
||||
bool keep = false;
|
||||
// These seven edges are uniquely owned by this gridVert; any of them
|
||||
// which intersect the surface create a vert.
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
glm::ivec4 neighborIndex = gridIndex + Neighbors(i);
|
||||
if (neighborIndex.w == 2) {
|
||||
neighborIndex += 1;
|
||||
neighborIndex.w = 0;
|
||||
}
|
||||
const float val = BoundedSDF(neighborIndex);
|
||||
if ((val > 0) == (gridVert.distance > 0)) continue;
|
||||
keep = true;
|
||||
|
||||
const int idx = AtomicAdd(*vertIndex, 1);
|
||||
vertPos[idx] =
|
||||
(val * position - gridVert.distance * Position(neighborIndex)) /
|
||||
(val - gridVert.distance);
|
||||
gridVert.edgeVerts[i] = idx;
|
||||
}
|
||||
|
||||
if (keep) gridVerts.Insert(mortonCode, gridVert);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildTris {
|
||||
glm::ivec3* triVerts;
|
||||
int* triIndex;
|
||||
const HashTableD<GridVert, identity> gridVerts;
|
||||
|
||||
__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]]};
|
||||
}
|
||||
|
||||
__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);
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(int idx) {
|
||||
Uint64 basekey = gridVerts.KeyAt(idx);
|
||||
if (basekey == kOpen) return;
|
||||
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
const glm::ivec4 baseIndex = DecodeMorton(basekey);
|
||||
|
||||
glm::ivec4 leadIndex = baseIndex;
|
||||
if (leadIndex.w == 0)
|
||||
leadIndex.w = 1;
|
||||
else {
|
||||
leadIndex += 1;
|
||||
leadIndex.w = 0;
|
||||
}
|
||||
|
||||
// This GridVert is in charge of the 6 tetrahedra surrounding its edge in
|
||||
// the (1,1,1) direction (edge 0).
|
||||
glm::ivec4 tet(base.NeighborInside(0), base.Inside(), -2, -2);
|
||||
glm::ivec4 thisIndex = baseIndex;
|
||||
thisIndex.x += 1;
|
||||
|
||||
GridVert thisVert = gridVerts[MortonCode(thisIndex)];
|
||||
|
||||
tet[2] = base.NeighborInside(1);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
thisIndex = leadIndex;
|
||||
--thisIndex[Prev3(i)];
|
||||
// MortonCodes take unsigned input, so check for negatives, given the
|
||||
// decrement.
|
||||
GridVert nextVert = thisIndex[Prev3(i)] < 0
|
||||
? GridVert()
|
||||
: gridVerts[MortonCode(thisIndex)];
|
||||
tet[3] = base.NeighborInside(Prev3(i) + 4);
|
||||
|
||||
const int edges1[6] = {base.edgeVerts[0],
|
||||
base.edgeVerts[i + 1],
|
||||
nextVert.edgeVerts[Next3(i) + 4],
|
||||
nextVert.edgeVerts[Prev3(i) + 1],
|
||||
thisVert.edgeVerts[i + 4],
|
||||
base.edgeVerts[Prev3(i) + 4]};
|
||||
thisVert = nextVert;
|
||||
CreateTris(tet, edges1);
|
||||
|
||||
thisIndex = baseIndex;
|
||||
++thisIndex[Next3(i)];
|
||||
nextVert = gridVerts[MortonCode(thisIndex)];
|
||||
tet[2] = tet[3];
|
||||
tet[3] = base.NeighborInside(Next3(i) + 1);
|
||||
|
||||
const int edges2[6] = {base.edgeVerts[0],
|
||||
edges1[5],
|
||||
thisVert.edgeVerts[i + 4],
|
||||
nextVert.edgeVerts[Next3(i) + 4],
|
||||
edges1[3],
|
||||
base.edgeVerts[Next3(i) + 1]};
|
||||
thisVert = nextVert;
|
||||
CreateTris(tet, edges2);
|
||||
|
||||
tet[2] = tet[3];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
void for_each_n_wrapper(ExecutionPolicy policy, int morton,
|
||||
ComputeVerts<Func> cv) {
|
||||
for_each_n(policy, countAt(0), morton, cv);
|
||||
}
|
||||
template <>
|
||||
void for_each_n_wrapper<std::function<float(glm::vec3)>>(
|
||||
ExecutionPolicy policy, int morton,
|
||||
ComputeVerts<std::function<float(glm::vec3)>> cv) {
|
||||
const auto pol =
|
||||
policy == ExecutionPolicy::ParUnseq ? ExecutionPolicy::Par : policy;
|
||||
for_each_n(pol, countAt(0), morton, cv);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Core
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance functor, containing this function signature:
|
||||
* `__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. 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
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
* @return Mesh This class does not depend on Manifold, so it just returns a
|
||||
* Mesh, but it is guaranteed to be manifold and so can always be used as
|
||||
* input to the Manifold constructor for further operations.
|
||||
*/
|
||||
template <typename Func>
|
||||
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
|
||||
std::optional<ExecutionPolicy> policy = std::nullopt) {
|
||||
Mesh out;
|
||||
|
||||
const glm::vec3 dim = bounds.Size();
|
||||
const float maxDim = std::max(dim[0], std::max(dim[1], dim[2]));
|
||||
const glm::ivec3 gridSize(dim / edgeLength);
|
||||
const glm::vec3 spacing = dim / (glm::vec3(gridSize));
|
||||
|
||||
const Uint64 maxMorton = MortonCode(glm::ivec4(gridSize + 1, 1));
|
||||
|
||||
// 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 (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)));
|
||||
HashTable<GridVert, identity> gridVerts(tableSize);
|
||||
VecDH<glm::vec3> vertPos(gridVerts.Size() * 7);
|
||||
|
||||
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,
|
||||
bounds.min, gridSize + 1, spacing, level}));
|
||||
|
||||
if (gridVerts.Full()) { // Resize HashTable
|
||||
const glm::vec3 lastVert = vertPos[index[0] - 1];
|
||||
const Uint64 lastMorton =
|
||||
MortonCode(glm::ivec4((lastVert - bounds.min) / spacing, 1));
|
||||
const float ratio = static_cast<float>(maxMorton) / lastMorton;
|
||||
if (ratio > 1000) // do not trust the ratio if it is too large
|
||||
tableSize *= 2;
|
||||
else
|
||||
tableSize *= ratio;
|
||||
gridVerts = HashTable<GridVert, identity>(tableSize);
|
||||
vertPos = VecDH<glm::vec3>(gridVerts.Size() * 7);
|
||||
} else { // Success
|
||||
vertPos.resize(index[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VecDH<glm::ivec3> triVerts(gridVerts.Entries() * 12); // worst case
|
||||
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(pol, countAt(0), gridVerts.Size(),
|
||||
BuildTris({triVerts.ptrD(), index.ptrD(), gridVerts.D()}));
|
||||
triVerts.resize(index[0]);
|
||||
|
||||
out.vertPos.insert(out.vertPos.end(), vertPos.begin(), vertPos.end());
|
||||
out.triVerts.insert(out.triVerts.end(), triVerts.begin(), triVerts.end());
|
||||
return out;
|
||||
Mesh LevelSet(std::function<float(glm::vec3)> sdf, Box bounds, float edgeLength,
|
||||
float level = 0, bool canParallel = true);
|
||||
}
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "sdf.h"
|
||||
|
||||
#include "hashtable.h"
|
||||
#include "par.h"
|
||||
#include "utils.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
Uint64 identity(Uint64 x) { return x; }
|
||||
|
||||
glm::ivec3 TetTri0(int i) {
|
||||
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
|
||||
{0, 3, 4}, //
|
||||
{0, 1, 5}, //
|
||||
{1, 5, 3}, //
|
||||
{1, 4, 2}, //
|
||||
{1, 0, 3}, //
|
||||
{2, 5, 0}, //
|
||||
{5, 3, 2}, //
|
||||
{2, 3, 5}, //
|
||||
{0, 5, 2}, //
|
||||
{3, 0, 1}, //
|
||||
{2, 4, 1}, //
|
||||
{3, 5, 1}, //
|
||||
{5, 1, 0}, //
|
||||
{4, 3, 0}, //
|
||||
{-1, -1, -1}};
|
||||
return tetTri0[i];
|
||||
}
|
||||
|
||||
glm::ivec3 TetTri1(int i) {
|
||||
constexpr glm::ivec3 tetTri1[16] = {{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{3, 4, 1}, //
|
||||
{-1, -1, -1}, //
|
||||
{3, 2, 1}, //
|
||||
{0, 4, 2}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{2, 4, 0}, //
|
||||
{1, 2, 3}, //
|
||||
{-1, -1, -1}, //
|
||||
{1, 4, 3}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}, //
|
||||
{-1, -1, -1}};
|
||||
return tetTri1[i];
|
||||
}
|
||||
|
||||
glm::ivec4 Neighbors(int i) {
|
||||
constexpr glm::ivec4 neighbors[7] = {{0, 0, 0, 1}, //
|
||||
{1, 0, 0, 0}, //
|
||||
{0, 1, 0, 0}, //
|
||||
{0, 0, 1, 0}, //
|
||||
{-1, 0, 0, 1}, //
|
||||
{0, -1, 0, 1}, //
|
||||
{0, 0, -1, 1}};
|
||||
return neighbors[i];
|
||||
}
|
||||
|
||||
Uint64 SpreadBits3(Uint64 v) {
|
||||
v = v & 0x1fffff;
|
||||
v = (v | v << 32) & 0x1f00000000ffff;
|
||||
v = (v | v << 16) & 0x1f0000ff0000ff;
|
||||
v = (v | v << 8) & 0x100f00f00f00f00f;
|
||||
v = (v | v << 4) & 0x10c30c30c30c30c3;
|
||||
v = (v | v << 2) & 0x1249249249249249;
|
||||
return v;
|
||||
}
|
||||
|
||||
Uint64 SqueezeBits3(Uint64 v) {
|
||||
v = v & 0x1249249249249249;
|
||||
v = (v ^ v >> 2) & 0x10c30c30c30c30c3;
|
||||
v = (v ^ v >> 4) & 0x100f00f00f00f00f;
|
||||
v = (v ^ v >> 8) & 0x1f0000ff0000ff;
|
||||
v = (v ^ v >> 16) & 0x1f00000000ffff;
|
||||
v = (v ^ v >> 32) & 0x1fffff;
|
||||
return 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) {
|
||||
return static_cast<Uint64>(index.w) | (SpreadBits3(index.x) << 1) |
|
||||
(SpreadBits3(index.y) << 2) | (SpreadBits3(index.z) << 3);
|
||||
}
|
||||
|
||||
glm::ivec4 DecodeMorton(Uint64 code) {
|
||||
glm::ivec4 index;
|
||||
index.x = SqueezeBits3(code >> 1);
|
||||
index.y = SqueezeBits3(code >> 2);
|
||||
index.z = SqueezeBits3(code >> 3);
|
||||
index.w = code & 0x1u;
|
||||
return index;
|
||||
}
|
||||
|
||||
struct GridVert {
|
||||
float distance = NAN;
|
||||
int edgeVerts[7] = {-1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
int Inside() const { return distance > 0 ? 1 : -1; }
|
||||
|
||||
int NeighborInside(int i) const {
|
||||
return Inside() * (edgeVerts[i] < 0 ? 1 : -1);
|
||||
}
|
||||
};
|
||||
|
||||
struct ComputeVerts {
|
||||
VecView<glm::vec3> vertPos;
|
||||
VecView<int> vertIndex;
|
||||
HashTableD<GridVert, identity> gridVerts;
|
||||
const std::function<float(glm::vec3)> sdf;
|
||||
const glm::vec3 origin;
|
||||
const glm::ivec3 gridSize;
|
||||
const glm::vec3 spacing;
|
||||
const float level;
|
||||
|
||||
inline 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 {
|
||||
const float d = sdf(Position(gridIndex)) - level;
|
||||
|
||||
const glm::ivec3 xyz(gridIndex);
|
||||
const bool onLowerBound = glm::any(glm::lessThanEqual(xyz, glm::ivec3(0)));
|
||||
const bool onUpperBound = glm::any(glm::greaterThanEqual(xyz, gridSize));
|
||||
const bool onHalfBound =
|
||||
gridIndex.w == 1 && glm::any(glm::greaterThanEqual(xyz, gridSize - 1));
|
||||
if (onLowerBound || onUpperBound || onHalfBound) return glm::min(d, 0.0f);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
inline void operator()(Uint64 mortonCode) {
|
||||
if (gridVerts.Full()) return;
|
||||
|
||||
const glm::ivec4 gridIndex = DecodeMorton(mortonCode);
|
||||
|
||||
if (glm::any(glm::greaterThan(glm::ivec3(gridIndex), gridSize))) return;
|
||||
|
||||
const glm::vec3 position = Position(gridIndex);
|
||||
|
||||
GridVert gridVert;
|
||||
gridVert.distance = BoundedSDF(gridIndex);
|
||||
|
||||
bool keep = false;
|
||||
// These seven edges are uniquely owned by this gridVert; any of them
|
||||
// which intersect the surface create a vert.
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
glm::ivec4 neighborIndex = gridIndex + Neighbors(i);
|
||||
if (neighborIndex.w == 2) {
|
||||
neighborIndex += 1;
|
||||
neighborIndex.w = 0;
|
||||
}
|
||||
const float val = BoundedSDF(neighborIndex);
|
||||
if ((val > 0) == (gridVert.distance > 0)) continue;
|
||||
keep = true;
|
||||
|
||||
const int idx = AtomicAdd(vertIndex[0], 1);
|
||||
vertPos[idx] =
|
||||
(val * position - gridVert.distance * Position(neighborIndex)) /
|
||||
(val - gridVert.distance);
|
||||
gridVert.edgeVerts[i] = idx;
|
||||
}
|
||||
|
||||
if (keep) gridVerts.Insert(mortonCode, gridVert);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildTris {
|
||||
VecView<glm::ivec3> triVerts;
|
||||
VecView<int> triIndex;
|
||||
const HashTableD<GridVert, identity> gridVerts;
|
||||
|
||||
void CreateTri(const glm::ivec3& tri, const int edges[6]) {
|
||||
if (tri[0] < 0) return;
|
||||
int idx = AtomicAdd(triIndex[0], 1);
|
||||
triVerts[idx] = {edges[tri[0]], edges[tri[1]], edges[tri[2]]};
|
||||
}
|
||||
|
||||
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) {
|
||||
Uint64 basekey = gridVerts.KeyAt(idx);
|
||||
if (basekey == kOpen) return;
|
||||
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
const glm::ivec4 baseIndex = DecodeMorton(basekey);
|
||||
|
||||
glm::ivec4 leadIndex = baseIndex;
|
||||
if (leadIndex.w == 0)
|
||||
leadIndex.w = 1;
|
||||
else {
|
||||
leadIndex += 1;
|
||||
leadIndex.w = 0;
|
||||
}
|
||||
|
||||
// This GridVert is in charge of the 6 tetrahedra surrounding its edge in
|
||||
// the (1,1,1) direction (edge 0).
|
||||
glm::ivec4 tet(base.NeighborInside(0), base.Inside(), -2, -2);
|
||||
glm::ivec4 thisIndex = baseIndex;
|
||||
thisIndex.x += 1;
|
||||
|
||||
GridVert thisVert = gridVerts[MortonCode(thisIndex)];
|
||||
|
||||
tet[2] = base.NeighborInside(1);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
thisIndex = leadIndex;
|
||||
--thisIndex[Prev3(i)];
|
||||
// MortonCodes take unsigned input, so check for negatives, given the
|
||||
// decrement.
|
||||
GridVert nextVert = thisIndex[Prev3(i)] < 0
|
||||
? GridVert()
|
||||
: gridVerts[MortonCode(thisIndex)];
|
||||
tet[3] = base.NeighborInside(Prev3(i) + 4);
|
||||
|
||||
const int edges1[6] = {base.edgeVerts[0],
|
||||
base.edgeVerts[i + 1],
|
||||
nextVert.edgeVerts[Next3(i) + 4],
|
||||
nextVert.edgeVerts[Prev3(i) + 1],
|
||||
thisVert.edgeVerts[i + 4],
|
||||
base.edgeVerts[Prev3(i) + 4]};
|
||||
thisVert = nextVert;
|
||||
CreateTris(tet, edges1);
|
||||
|
||||
thisIndex = baseIndex;
|
||||
++thisIndex[Next3(i)];
|
||||
nextVert = gridVerts[MortonCode(thisIndex)];
|
||||
tet[2] = tet[3];
|
||||
tet[3] = base.NeighborInside(Next3(i) + 1);
|
||||
|
||||
const int edges2[6] = {base.edgeVerts[0],
|
||||
edges1[5],
|
||||
thisVert.edgeVerts[i + 4],
|
||||
nextVert.edgeVerts[Next3(i) + 4],
|
||||
edges1[3],
|
||||
base.edgeVerts[Next3(i) + 1]};
|
||||
thisVert = nextVert;
|
||||
CreateTris(tet, edges2);
|
||||
|
||||
tet[2] = tet[3];
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Core
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance functor, containing this function signature:
|
||||
* `float operator()(glm::vec3 point)`, which returns the
|
||||
* signed distance of a given point in R^3. Positive values are inside,
|
||||
* negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
* @param canParallel 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.
|
||||
* @return Mesh This class does not depend on Manifold, so it just returns a
|
||||
* Mesh, but it is guaranteed to be manifold and so can always be used as
|
||||
* input to the Manifold constructor for further operations.
|
||||
*/
|
||||
Mesh LevelSet(std::function<float(glm::vec3)> sdf, Box bounds, float edgeLength,
|
||||
float level, bool canParallel) {
|
||||
Mesh out;
|
||||
|
||||
const glm::vec3 dim = bounds.Size();
|
||||
const float maxDim = std::max(dim[0], std::max(dim[1], dim[2]));
|
||||
const glm::ivec3 gridSize(dim / edgeLength);
|
||||
const glm::vec3 spacing = dim / (glm::vec3(gridSize));
|
||||
|
||||
const Uint64 maxMorton = MortonCode(glm::ivec4(gridSize + 1, 1));
|
||||
|
||||
// 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 = canParallel ? autoPolicy(maxMorton) : ExecutionPolicy::Seq;
|
||||
|
||||
int tableSize = glm::min(
|
||||
2 * maxMorton, static_cast<Uint64>(10 * glm::pow(maxMorton, 0.667)));
|
||||
HashTable<GridVert, identity> gridVerts(tableSize);
|
||||
Vec<glm::vec3> vertPos(gridVerts.Size() * 7);
|
||||
|
||||
while (1) {
|
||||
Vec<int> index(1, 0);
|
||||
for_each_n(pol, countAt(0), maxMorton + 1,
|
||||
ComputeVerts({vertPos, index, gridVerts.D(), sdf, bounds.min,
|
||||
gridSize + 1, spacing, level}));
|
||||
|
||||
if (gridVerts.Full()) { // Resize HashTable
|
||||
const glm::vec3 lastVert = vertPos[index[0] - 1];
|
||||
const Uint64 lastMorton =
|
||||
MortonCode(glm::ivec4((lastVert - bounds.min) / spacing, 1));
|
||||
const float ratio = static_cast<float>(maxMorton) / lastMorton;
|
||||
if (ratio > 1000) // do not trust the ratio if it is too large
|
||||
tableSize *= 2;
|
||||
else
|
||||
tableSize *= ratio;
|
||||
gridVerts = HashTable<GridVert, identity>(tableSize);
|
||||
vertPos = Vec<glm::vec3>(gridVerts.Size() * 7);
|
||||
} else { // Success
|
||||
vertPos.resize(index[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Vec<glm::ivec3> triVerts(gridVerts.Entries() * 12); // worst case
|
||||
|
||||
Vec<int> index(1, 0);
|
||||
for_each_n(pol, countAt(0), gridVerts.Size(),
|
||||
BuildTris({triVerts, index, gridVerts.D()}));
|
||||
triVerts.resize(index[0]);
|
||||
|
||||
out.vertPos.insert(out.vertPos.end(), vertPos.begin(), vertPos.end());
|
||||
out.triVerts.insert(out.triVerts.end(), triVerts.begin(), triVerts.end());
|
||||
return out;
|
||||
}
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
Vendored
-4
@@ -1,11 +1,7 @@
|
||||
add_subdirectory(glm)
|
||||
add_subdirectory(graphlite)
|
||||
|
||||
set(CLIPPER2_UTILS OFF)
|
||||
set(CLIPPER2_EXAMPLES OFF)
|
||||
set(CLIPPER2_TESTS OFF)
|
||||
set(CLIPPER2_USINGZ "OFF" CACHE STRING "Preempt cache default of USINGZ (we only use 2d)")
|
||||
add_definitions(-D_HAS_EXCEPTIONS=0) # disable exceptions for STL
|
||||
add_subdirectory(clipper2/CPP)
|
||||
|
||||
add_library(quickhull quickhull/QuickHull.cpp)
|
||||
|
||||
Vendored
+1
-1
Submodule src/third_party/clipper2 updated: 81b01d2acb...7cf615c279
Vendored
-1
Submodule src/third_party/glm deleted from bf71a83494
-10
@@ -1,10 +0,0 @@
|
||||
project (graphlite)
|
||||
|
||||
add_library(${PROJECT_NAME} OBJECT src/connected_components.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
Copyright 2021 Guohao Dou
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
#include "graph_lite.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
typedef typename graph_lite::Graph<
|
||||
int, void, void, graph_lite::EdgeDirection::UNDIRECTED,
|
||||
graph_lite::MultiEdge::DISALLOWED, graph_lite::SelfLoop::DISALLOWED,
|
||||
graph_lite::Map::UNORDERED_MAP, graph_lite::Container::VEC>
|
||||
Graph;
|
||||
|
||||
int ConnectedComponents(std::vector<int>& components, const Graph& g);
|
||||
} // namespace manifold
|
||||
-1621
File diff suppressed because it is too large
Load Diff
@@ -1,53 +0,0 @@
|
||||
// Copyright 2022 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "graph.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
int ConnectedComponents(std::vector<int>& components, const Graph& graph) {
|
||||
if (!graph.size()) {
|
||||
return 0;
|
||||
}
|
||||
components.resize(graph.size());
|
||||
std::fill(components.begin(), components.end(), -1);
|
||||
|
||||
std::deque<int> queue;
|
||||
int numComponent = 0;
|
||||
for (auto it = graph.begin(); it != graph.end(); ++it) {
|
||||
const int& root = *it;
|
||||
if (components[root] >= 0) continue; // skip visited nodes
|
||||
|
||||
// new component
|
||||
components[root] = numComponent;
|
||||
queue.emplace_back(root);
|
||||
// traverse all connected nodes
|
||||
while (!queue.empty()) {
|
||||
const auto [n_begin, n_end] = graph.neighbors(queue.front());
|
||||
queue.pop_front();
|
||||
for (auto n_it = n_begin; n_it != n_end; ++n_it) {
|
||||
const int& neighbor = *n_it;
|
||||
if (components[neighbor] < 0) { // unvisited
|
||||
components[neighbor] = numComponent;
|
||||
queue.emplace_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
++numComponent;
|
||||
}
|
||||
return numComponent;
|
||||
}
|
||||
} // namespace manifold
|
||||
Vendored
-1
Submodule src/third_party/thrust deleted from 3cd56842c9
@@ -14,53 +14,102 @@
|
||||
|
||||
project(utilities)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
message("CUDA Support: ${MANIFOLD_USE_CUDA}")
|
||||
add_library(${PROJECT_NAME} INTERFACE)
|
||||
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)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(glm
|
||||
GIT_REPOSITORY https://github.com/g-truc/glm.git
|
||||
GIT_TAG 0.9.9.8
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
FIND_PACKAGE_ARGS NAMES glm
|
||||
)
|
||||
FetchContent_MakeAvailable(glm)
|
||||
|
||||
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})
|
||||
FetchContent_Declare(Thrust
|
||||
GIT_REPOSITORY https://github.com/NVIDIA/thrust.git
|
||||
GIT_TAG 2.1.0
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
FIND_PACKAGE_ARGS NAMES Thrust
|
||||
)
|
||||
FetchContent_MakeAvailable(Thrust)
|
||||
|
||||
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)
|
||||
set_property(DIRECTORY ${TBB_SOURCE_DIR} PROPERTY EXCLUDE_FROM_ALL YES)
|
||||
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()
|
||||
elseif(MANIFOLD_PAR STREQUAL "NONE")
|
||||
set(MANIFOLD_PAR "CPP")
|
||||
else()
|
||||
message(FATAL_ERROR "Invalid value for MANIFOLD_PAR: ${MANIFOLD_PAR}. "
|
||||
"Should be one of \"TBB\", \"OMP\", \"NONE\"")
|
||||
"Should be \"TBB\" or \"NONE\"")
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${THRUST_INC_DIR})
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC glm)
|
||||
|
||||
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}>"
|
||||
)
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE glm::glm)
|
||||
if(DEFINED Thrust_SOURCE_DIR)
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE
|
||||
${Thrust_SOURCE_DIR}
|
||||
${Thrust_SOURCE_DIR}/dependencies/libcudacxx/include
|
||||
)
|
||||
else()
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PUBLIC ${MANIFOLD_FLAGS}
|
||||
-DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_${MANIFOLD_PAR}
|
||||
)
|
||||
target_include_directories(${PROJECT_NAME} INTERFACE
|
||||
${THRUST_ROOT}
|
||||
${THRUST_ROOT}/dependencies/libcudacxx/include
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_options(${PROJECT_NAME} INTERFACE
|
||||
-DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_${MANIFOLD_PAR}
|
||||
)
|
||||
|
||||
if(MANIFOLD_DEBUG)
|
||||
target_compile_options(${PROJECT_NAME}
|
||||
PUBLIC -DMANIFOLD_DEBUG)
|
||||
INTERFACE -DMANIFOLD_DEBUG)
|
||||
endif()
|
||||
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17)
|
||||
|
||||
install(
|
||||
FILES include/public.h
|
||||
DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/manifold
|
||||
COMPONENT devel
|
||||
)
|
||||
|
||||
|
||||
@@ -18,48 +18,36 @@
|
||||
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace {
|
||||
typedef unsigned long long int Uint64;
|
||||
typedef Uint64 (*hash_fun_t)(Uint64);
|
||||
constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
inline constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T AtomicCAS(T& target, T compare, T val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return atomicCAS(&target, compare, val);
|
||||
#else
|
||||
T AtomicCAS(T& target, T compare, T val) {
|
||||
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>
|
||||
__host__ __device__ void AtomicStore(T& target, T val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
target = val;
|
||||
#else
|
||||
void AtomicStore(T& target, T val) {
|
||||
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>
|
||||
__host__ __device__ T AtomicLoad(const T& target) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return target;
|
||||
#else
|
||||
T AtomicLoad(const T& target) {
|
||||
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
|
||||
__host__ __device__ inline Uint64 hash64bit(Uint64 x) {
|
||||
inline Uint64 hash64bit(Uint64 x) {
|
||||
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull;
|
||||
x = (x ^ (x >> 27)) * 0x94d049bb133111ebull;
|
||||
x = x ^ (x >> 31);
|
||||
@@ -75,17 +63,15 @@ namespace manifold {
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(VecDH<Uint64>& keys, VecDH<V>& values, VecDH<uint32_t>& used,
|
||||
HashTableD(Vec<Uint64>& keys, Vec<V>& values, Vec<uint32_t>& used,
|
||||
uint32_t step = 1)
|
||||
: step_{step}, keys_{keys}, values_{values}, used_{used} {}
|
||||
|
||||
__host__ __device__ int Size() const { return keys_.size(); }
|
||||
int Size() const { return keys_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const {
|
||||
return AtomicLoad(used_[0]) * 2 > Size();
|
||||
}
|
||||
bool Full() const { return AtomicLoad(used_[0]) * 2 > Size(); }
|
||||
|
||||
__host__ __device__ void Insert(Uint64 key, const V& val) {
|
||||
void Insert(Uint64 key, const V& val) {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
if (Full()) return;
|
||||
@@ -101,7 +87,7 @@ class HashTableD {
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ V& operator[](Uint64 key) const {
|
||||
V& operator[](Uint64 key) {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
const Uint64 k = AtomicLoad(keys_[idx]);
|
||||
@@ -112,16 +98,26 @@ class HashTableD {
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ Uint64 KeyAt(int idx) const {
|
||||
return AtomicLoad(keys_[idx]);
|
||||
const V& operator[](Uint64 key) const {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
const Uint64 k = AtomicLoad(keys_[idx]);
|
||||
if (k == key || k == kOpen) {
|
||||
return values_[idx];
|
||||
}
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
__host__ __device__ V& At(int idx) const { return values_[idx]; }
|
||||
|
||||
Uint64 KeyAt(int idx) const { return AtomicLoad(keys_[idx]); }
|
||||
V& At(int idx) { return values_[idx]; }
|
||||
const V& At(int idx) const { return values_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecD<Uint64> keys_;
|
||||
VecD<V> values_;
|
||||
VecD<uint32_t> used_;
|
||||
VecView<Uint64> keys_;
|
||||
VecView<V> values_;
|
||||
VecView<uint32_t> used_;
|
||||
};
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
@@ -144,14 +140,14 @@ class HashTable {
|
||||
return static_cast<float>(AtomicLoad(used_[0])) / Size();
|
||||
}
|
||||
|
||||
VecDH<V>& GetValueStore() { return values_; }
|
||||
Vec<V>& GetValueStore() { return values_; }
|
||||
|
||||
static Uint64 Open() { return kOpen; }
|
||||
|
||||
private:
|
||||
VecDH<Uint64> keys_;
|
||||
VecDH<V> values_;
|
||||
VecDH<uint32_t> used_ = VecDH<uint32_t>(1, 0);
|
||||
Vec<Uint64> keys_;
|
||||
Vec<V> values_;
|
||||
Vec<uint32_t> used_ = Vec<uint32_t>(1, 0);
|
||||
HashTableD<V, H> table_;
|
||||
};
|
||||
|
||||
|
||||
+106
-89
@@ -23,28 +23,26 @@
|
||||
#include <thrust/sort.h>
|
||||
#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 <algorithm>
|
||||
#include <numeric>
|
||||
#if MANIFOLD_PAR == 'T'
|
||||
#include <thrust/system/tbb/execution_policy.h>
|
||||
|
||||
#if MANIFOLD_PAR == 'T' && TBB_INTERFACE_VERSION >= 10000 && \
|
||||
__has_include(<pstl/glue_execution_defs.h>)
|
||||
#include <execution>
|
||||
#endif
|
||||
|
||||
#include "tbb/tbb.h"
|
||||
#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,
|
||||
};
|
||||
@@ -53,52 +51,18 @@ enum class ExecutionPolicy {
|
||||
// - Sequential for small workload,
|
||||
// - Parallel (CPU) for medium workload,
|
||||
// - GPU for large workload if available.
|
||||
inline ExecutionPolicy autoPolicy(int size) {
|
||||
inline constexpr ExecutionPolicy autoPolicy(int size) {
|
||||
// some random numbers
|
||||
if (size <= (1 << 12)) {
|
||||
return ExecutionPolicy::Seq;
|
||||
}
|
||||
if (size <= (1 << 16) || !CudaEnabled()) {
|
||||
return ExecutionPolicy::Par;
|
||||
}
|
||||
return ExecutionPolicy::ParUnseq;
|
||||
return ExecutionPolicy::Par;
|
||||
}
|
||||
|
||||
#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; \
|
||||
@@ -112,7 +76,6 @@ inline 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: \
|
||||
@@ -120,59 +83,113 @@ inline ExecutionPolicy autoPolicy(int size) {
|
||||
} \
|
||||
return thrust::NAME(thrust::cpp::par, args...); \
|
||||
}
|
||||
|
||||
#if MANIFOLD_PAR != 'T' || \
|
||||
(TBB_INTERFACE_VERSION >= 10000 && __has_include(<pstl/glue_execution_defs.h>))
|
||||
#if MANIFOLD_PAR == 'T'
|
||||
#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; \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
#define STL_DYNAMIC_BACKEND(NAME, RET) \
|
||||
template <typename Ret = RET, typename... Args> \
|
||||
Ret NAME(ExecutionPolicy policy, Args... args) { \
|
||||
return std::NAME(args...); \
|
||||
}
|
||||
#define STL_DYNAMIC_BACKEND_VOID(NAME) \
|
||||
template <typename... Args> \
|
||||
void NAME(ExecutionPolicy policy, Args... args) { \
|
||||
std::NAME(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: \
|
||||
break; \
|
||||
} \
|
||||
thrust::NAME(thrust::cpp::par, args...); \
|
||||
}
|
||||
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 MANIFOLD_PAR == 'T'
|
||||
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
|
||||
return std::copy_if(first, last, result, pred);
|
||||
#endif
|
||||
}
|
||||
|
||||
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each)
|
||||
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each_n)
|
||||
#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_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)
|
||||
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)
|
||||
STL_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)
|
||||
|
||||
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(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)
|
||||
THRUST_DYNAMIC_BACKEND(gather_if, void)
|
||||
THRUST_DYNAMIC_BACKEND(reduce_by_key, 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(remove_if, void)
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
+201
-37
@@ -34,12 +34,6 @@ namespace manifold {
|
||||
|
||||
constexpr float kTolerance = 1e-5;
|
||||
|
||||
#ifdef __CUDACC__
|
||||
#define HOST_DEVICE __host__ __device__
|
||||
#else
|
||||
#define HOST_DEVICE
|
||||
#endif
|
||||
|
||||
/** @defgroup Connections
|
||||
* @brief Move data in and out of the Manifold class.
|
||||
* @{
|
||||
@@ -50,7 +44,7 @@ constexpr float kTolerance = 1e-5;
|
||||
*
|
||||
* @param x Angle in degrees.
|
||||
*/
|
||||
inline HOST_DEVICE float sind(float x) {
|
||||
inline float sind(float x) {
|
||||
if (!std::isfinite(x)) return sin(x);
|
||||
if (x < 0.0f) return -sind(-x);
|
||||
int quo;
|
||||
@@ -73,7 +67,7 @@ inline HOST_DEVICE float sind(float x) {
|
||||
*
|
||||
* @param x Angle in degrees.
|
||||
*/
|
||||
inline HOST_DEVICE float cosd(float x) { return sind(x + 90.0f); }
|
||||
inline float cosd(float x) { return sind(x + 90.0f); }
|
||||
|
||||
/**
|
||||
* This 4x3 matrix can be used as an input to Manifold.Transform() to turn an
|
||||
@@ -81,7 +75,7 @@ inline HOST_DEVICE float cosd(float x) { return sind(x + 90.0f); }
|
||||
*
|
||||
* @param up The vector to be turned to point upwards. Length does not matter.
|
||||
*/
|
||||
inline HOST_DEVICE glm::mat4x3 RotateUp(glm::vec3 up) {
|
||||
inline glm::mat4x3 RotateUp(glm::vec3 up) {
|
||||
up = glm::normalize(up);
|
||||
glm::vec3 axis = glm::cross(up, {0, 0, 1});
|
||||
float angle = glm::asin(glm::length(axis));
|
||||
@@ -100,13 +94,12 @@ inline HOST_DEVICE glm::mat4x3 RotateUp(glm::vec3 up) {
|
||||
* @return int, like Signum, this returns 1 for CCW, -1 for CW, and 0 if within
|
||||
* tol of colinear.
|
||||
*/
|
||||
inline HOST_DEVICE int CCW(glm::vec2 p0, glm::vec2 p1, glm::vec2 p2,
|
||||
float tol) {
|
||||
inline int CCW(glm::vec2 p0, glm::vec2 p1, glm::vec2 p2, float tol) {
|
||||
glm::vec2 v1 = p1 - p0;
|
||||
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 <= base2 * tol * tol)
|
||||
if (area * area * 4 <= base2 * tol * tol)
|
||||
return 0;
|
||||
else
|
||||
return area > 0 ? 1 : -1;
|
||||
@@ -179,12 +172,12 @@ struct Box {
|
||||
/**
|
||||
* Default constructor is an infinite box that contains all space.
|
||||
*/
|
||||
HOST_DEVICE Box() {}
|
||||
Box() {}
|
||||
|
||||
/**
|
||||
* Creates a box that contains the two given points.
|
||||
*/
|
||||
HOST_DEVICE Box(const glm::vec3 p1, const glm::vec3 p2) {
|
||||
Box(const glm::vec3 p1, const glm::vec3 p2) {
|
||||
min = glm::min(p1, p2);
|
||||
max = glm::max(p1, p2);
|
||||
}
|
||||
@@ -192,18 +185,18 @@ struct Box {
|
||||
/**
|
||||
* Returns the dimensions of the Box.
|
||||
*/
|
||||
HOST_DEVICE glm::vec3 Size() const { return max - min; }
|
||||
glm::vec3 Size() const { return max - min; }
|
||||
|
||||
/**
|
||||
* Returns the center point of the Box.
|
||||
*/
|
||||
HOST_DEVICE glm::vec3 Center() const { return 0.5f * (max + min); }
|
||||
glm::vec3 Center() const { return 0.5f * (max + min); }
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
HOST_DEVICE float Scale() const {
|
||||
float Scale() const {
|
||||
glm::vec3 absMax = glm::max(glm::abs(min), glm::abs(max));
|
||||
return glm::max(absMax.x, glm::max(absMax.y, absMax.z));
|
||||
}
|
||||
@@ -211,7 +204,7 @@ struct Box {
|
||||
/**
|
||||
* Does this box contain (includes equal) the given point?
|
||||
*/
|
||||
HOST_DEVICE bool Contains(const glm::vec3& p) const {
|
||||
bool Contains(const glm::vec3& p) const {
|
||||
return glm::all(glm::greaterThanEqual(p, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, p));
|
||||
}
|
||||
@@ -219,7 +212,7 @@ struct Box {
|
||||
/**
|
||||
* Does this box contain (includes equal) the given box?
|
||||
*/
|
||||
HOST_DEVICE bool Contains(const Box& box) const {
|
||||
bool Contains(const Box& box) const {
|
||||
return glm::all(glm::greaterThanEqual(box.min, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, box.max));
|
||||
}
|
||||
@@ -227,7 +220,7 @@ struct Box {
|
||||
/**
|
||||
* Expand this box to include the given point.
|
||||
*/
|
||||
HOST_DEVICE void Union(const glm::vec3 p) {
|
||||
void Union(const glm::vec3 p) {
|
||||
min = glm::min(min, p);
|
||||
max = glm::max(max, p);
|
||||
}
|
||||
@@ -235,7 +228,7 @@ struct Box {
|
||||
/**
|
||||
* Expand this box to include the given box.
|
||||
*/
|
||||
HOST_DEVICE Box Union(const Box& box) const {
|
||||
Box Union(const Box& box) const {
|
||||
Box out;
|
||||
out.min = glm::min(min, box.min);
|
||||
out.max = glm::max(max, box.max);
|
||||
@@ -249,7 +242,7 @@ struct Box {
|
||||
* multiples of 90 degrees), or else the resulting bounding box will no longer
|
||||
* bound properly.
|
||||
*/
|
||||
HOST_DEVICE Box Transform(const glm::mat4x3& transform) const {
|
||||
Box Transform(const glm::mat4x3& transform) const {
|
||||
Box out;
|
||||
glm::vec3 minT = transform * glm::vec4(min, 1.0f);
|
||||
glm::vec3 maxT = transform * glm::vec4(max, 1.0f);
|
||||
@@ -261,7 +254,7 @@ struct Box {
|
||||
/**
|
||||
* Shift this box by the given vector.
|
||||
*/
|
||||
HOST_DEVICE Box operator+(glm::vec3 shift) const {
|
||||
Box operator+(glm::vec3 shift) const {
|
||||
Box out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
@@ -271,7 +264,7 @@ struct Box {
|
||||
/**
|
||||
* Shift this box in-place by the given vector.
|
||||
*/
|
||||
HOST_DEVICE Box& operator+=(glm::vec3 shift) {
|
||||
Box& operator+=(glm::vec3 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
@@ -280,7 +273,7 @@ struct Box {
|
||||
/**
|
||||
* Scale this box by the given vector.
|
||||
*/
|
||||
HOST_DEVICE Box operator*(glm::vec3 scale) const {
|
||||
Box operator*(glm::vec3 scale) const {
|
||||
Box out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
@@ -290,7 +283,7 @@ struct Box {
|
||||
/**
|
||||
* Scale this box in-place by the given vector.
|
||||
*/
|
||||
HOST_DEVICE Box& operator*=(glm::vec3 scale) {
|
||||
Box& operator*=(glm::vec3 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
@@ -299,7 +292,7 @@ struct Box {
|
||||
/**
|
||||
* Does this box overlap the one given (including equality)?
|
||||
*/
|
||||
HOST_DEVICE bool DoesOverlap(const Box& box) const {
|
||||
inline bool DoesOverlap(const Box& box) const {
|
||||
return min.x <= box.max.x && min.y <= box.max.y && min.z <= box.max.z &&
|
||||
max.x >= box.min.x && max.y >= box.min.y && max.z >= box.min.z;
|
||||
}
|
||||
@@ -308,17 +301,183 @@ struct Box {
|
||||
* Does the given point project within the XY extent of this box
|
||||
* (including equality)?
|
||||
*/
|
||||
HOST_DEVICE bool DoesOverlap(glm::vec3 p) const { // projected in z
|
||||
inline bool DoesOverlap(glm::vec3 p) const { // projected in z
|
||||
return p.x <= max.x && p.x >= min.x && p.y <= max.y && p.y >= min.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this box have finite bounds?
|
||||
*/
|
||||
HOST_DEVICE bool IsFinite() const {
|
||||
bool IsFinite() const {
|
||||
return glm::all(glm::isfinite(min)) && glm::all(glm::isfinite(max));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Axis-aligned rectangular bounds.
|
||||
*/
|
||||
struct Rect {
|
||||
glm::vec2 min = glm::vec2(std::numeric_limits<float>::infinity());
|
||||
glm::vec2 max = glm::vec2(-std::numeric_limits<float>::infinity());
|
||||
|
||||
/**
|
||||
* Default constructor is an empty rectangle..
|
||||
*/
|
||||
Rect() {}
|
||||
|
||||
/**
|
||||
* Create a rectangle that contains the two given points.
|
||||
*/
|
||||
Rect(const glm::vec2 a, const glm::vec2 b) {
|
||||
min = glm::min(a, b);
|
||||
max = glm::max(a, b);
|
||||
}
|
||||
|
||||
/** @name Information
|
||||
* Details of the rectangle
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* Return the dimensions of the rectangle.
|
||||
*/
|
||||
glm::vec2 Size() const { return max - min; }
|
||||
|
||||
/**
|
||||
* Return the area of the rectangle.
|
||||
*/
|
||||
float Area() const {
|
||||
auto sz = Size();
|
||||
return sz.x * sz.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute-largest coordinate value of any contained
|
||||
* point.
|
||||
*/
|
||||
float Scale() const {
|
||||
glm::vec2 absMax = glm::max(glm::abs(min), glm::abs(max));
|
||||
return glm::max(absMax.x, absMax.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the center point of the rectangle.
|
||||
*/
|
||||
glm::vec2 Center() const { return 0.5f * (max + min); }
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes on border) the given point?
|
||||
*/
|
||||
bool Contains(const glm::vec2& p) const {
|
||||
return glm::all(glm::greaterThanEqual(p, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle contain (includes equal) the given rectangle?
|
||||
*/
|
||||
bool Contains(const Rect& rect) const {
|
||||
return glm::all(glm::greaterThanEqual(rect.min, min)) &&
|
||||
glm::all(glm::greaterThanEqual(max, rect.max));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this rectangle overlap the one given (including equality)?
|
||||
*/
|
||||
bool DoesOverlap(const Rect& rect) const {
|
||||
return min.x <= rect.max.x && min.y <= rect.max.y && max.x >= rect.min.x &&
|
||||
max.y >= rect.min.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the rectangle empty (containing no space)?
|
||||
*/
|
||||
bool IsEmpty() const { return max.y <= min.y || max.x <= min.x; };
|
||||
|
||||
/**
|
||||
* Does this recangle have finite bounds?
|
||||
*/
|
||||
bool IsFinite() const {
|
||||
return glm::all(glm::isfinite(min)) && glm::all(glm::isfinite(max));
|
||||
}
|
||||
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
*/
|
||||
///@{
|
||||
|
||||
/**
|
||||
* Expand this rectangle (in place) to include the given point.
|
||||
*/
|
||||
void Union(const glm::vec2 p) {
|
||||
min = glm::min(min, p);
|
||||
max = glm::max(max, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand this rectangle to include the given Rect.
|
||||
*/
|
||||
Rect Union(const Rect& rect) const {
|
||||
Rect out;
|
||||
out.min = glm::min(min, rect.min);
|
||||
out.max = glm::max(max, rect.max);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle by the given vector.
|
||||
*/
|
||||
Rect operator+(const glm::vec2 shift) const {
|
||||
Rect out;
|
||||
out.min = min + shift;
|
||||
out.max = max + shift;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& operator+=(const glm::vec2 shift) {
|
||||
min += shift;
|
||||
max += shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle by the given vector.
|
||||
*/
|
||||
Rect operator*(const glm::vec2 scale) const {
|
||||
Rect out;
|
||||
out.min = min * scale;
|
||||
out.max = max * scale;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale this rectangle in-place by the given vector.
|
||||
*/
|
||||
Rect& operator*=(const glm::vec2 scale) {
|
||||
min *= scale;
|
||||
max *= scale;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the rectangle by the given axis-aligned affine transform.
|
||||
*
|
||||
* Ensure the transform passed in is axis-aligned (rotations are all
|
||||
* multiples of 90 degrees), or else the resulting rectangle will no longer
|
||||
* bound properly.
|
||||
*/
|
||||
Rect Transform(const glm::mat3x2& m) const {
|
||||
Rect rect;
|
||||
rect.min = m * glm::vec3(min, 1);
|
||||
rect.max = m * glm::vec3(max, 1);
|
||||
return rect;
|
||||
}
|
||||
///@}
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/** @addtogroup Core
|
||||
@@ -401,7 +560,7 @@ class Quality {
|
||||
int nSegL = 2.0f * radius * glm::pi<float>() / circularEdgeLength_;
|
||||
int nSeg = fmin(nSegA, nSegL) + 3;
|
||||
nSeg -= nSeg % 4;
|
||||
return nSeg;
|
||||
return std::max(nSeg, 3);
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
@@ -452,17 +611,12 @@ struct ExecutionParams {
|
||||
/// Suppresses printed errors regarding CW triangles. Has no effect if
|
||||
/// processOverlaps is true.
|
||||
bool suppressErrors = false;
|
||||
/// Deterministic outputs. Will disable some parallel optimizations.
|
||||
bool deterministic = false;
|
||||
};
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Box& box) {
|
||||
return stream << "min: " << box.min.x << ", " << box.min.y << ", "
|
||||
<< box.min.z << ", "
|
||||
<< "max: " << box.max.x << ", " << box.max.y << ", "
|
||||
<< box.max.z;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::ostream& operator<<(std::ostream& stream, const glm::tvec2<T>& v) {
|
||||
return stream << "x = " << v.x << ", y = " << v.y;
|
||||
@@ -486,6 +640,16 @@ inline std::ostream& operator<<(std::ostream& stream, const glm::mat4x3& mat) {
|
||||
<< tam[2] << std::endl;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Box& box) {
|
||||
return stream << "min: " << box.min << ", "
|
||||
<< "max: " << box.max;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Rect& box) {
|
||||
return stream << "min: " << box.min << ", "
|
||||
<< "max: " << box.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of this vector to standard output. Only exists if compiled
|
||||
* with MANIFOLD_DEBUG flag.
|
||||
|
||||
+131
-98
@@ -19,150 +19,183 @@
|
||||
#include "par.h"
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
class SparseIndices {
|
||||
// COO-style sparse matrix storage. Values corresponding to these indicies are
|
||||
// stored in vectors separate from this class, but having the same length.
|
||||
// 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
|
||||
public:
|
||||
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);
|
||||
#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(_WIN32)
|
||||
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;
|
||||
}
|
||||
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 = use_q ? q : p;
|
||||
|
||||
SparseIndices() = default;
|
||||
SparseIndices(size_t size) : data_(size * sizeof(int64_t)) {}
|
||||
|
||||
int size() const { return data_.size() / sizeof(int64_t); }
|
||||
|
||||
Vec<int> Copy(bool use_q) const {
|
||||
Vec<int> out(size());
|
||||
int offset = pOffset;
|
||||
if (use_q) offset = 1 - offset;
|
||||
const int* p = ptr();
|
||||
for_each(autoPolicy(out.size()), countAt(0), countAt((int)out.size()),
|
||||
[&](int i) { out[i] = p[i * 2 + offset]; });
|
||||
return out;
|
||||
}
|
||||
|
||||
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(); }
|
||||
|
||||
ZipC beginHpq() const { return zip(p.begin(), q.begin()); }
|
||||
ZipC endHpq() const { return zip(p.end(), q.end()); }
|
||||
|
||||
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);
|
||||
void Sort() {
|
||||
VecView<int64_t> view = AsVec64();
|
||||
stable_sort(autoPolicy(size()), view.begin(), view.end());
|
||||
}
|
||||
|
||||
void Unique(ExecutionPolicy policy) {
|
||||
Sort(policy);
|
||||
int newSize =
|
||||
unique<decltype(beginPQ())>(policy, beginPQ(), endPQ()) - beginPQ();
|
||||
void Resize(int size) { data_.resize(size * sizeof(int64_t), -1); }
|
||||
|
||||
inline int& Get(int i, bool use_q) {
|
||||
if (use_q)
|
||||
return ptr()[2 * i + 1 - pOffset];
|
||||
else
|
||||
return ptr()[2 * i + pOffset];
|
||||
}
|
||||
|
||||
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 {
|
||||
VecView<const int64_t> view = AsVec64();
|
||||
return view[i];
|
||||
}
|
||||
|
||||
inline void Set(int i, int p, int q) {
|
||||
VecView<int64_t> view = AsVec64();
|
||||
view[i] = EncodePQ(p, q);
|
||||
}
|
||||
|
||||
inline void SetPQ(int i, int64_t pq) {
|
||||
VecView<int64_t> view = AsVec64();
|
||||
view[i] = pq;
|
||||
}
|
||||
|
||||
VecView<int64_t> AsVec64() {
|
||||
return VecView<int64_t>(reinterpret_cast<int64_t*>(data_.data()),
|
||||
data_.size() / sizeof(int64_t));
|
||||
}
|
||||
|
||||
VecView<const int64_t> AsVec64() const {
|
||||
return VecView<const int64_t>(
|
||||
reinterpret_cast<const int64_t*>(data_.data()),
|
||||
data_.size() / sizeof(int64_t));
|
||||
}
|
||||
|
||||
VecView<int32_t> AsVec32() {
|
||||
return VecView<int32_t>(reinterpret_cast<int32_t*>(data_.data()),
|
||||
data_.size() / sizeof(int32_t));
|
||||
}
|
||||
|
||||
VecView<const int32_t> AsVec32() const {
|
||||
return VecView<const int32_t>(
|
||||
reinterpret_cast<const int32_t*>(data_.data()),
|
||||
data_.size() / sizeof(int32_t));
|
||||
}
|
||||
|
||||
inline void Add(int p, int q) {
|
||||
for (int i = 0; i < sizeof(int64_t); ++i) data_.push_back(-1);
|
||||
Set(size() - 1, p, q);
|
||||
}
|
||||
|
||||
void Unique() {
|
||||
Sort();
|
||||
VecView<int64_t> view = AsVec64();
|
||||
int newSize = std::unique(view.begin(), view.end()) - view.begin();
|
||||
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() == p.size(), userErr,
|
||||
size_t RemoveZeros(Vec<int>& S) {
|
||||
ASSERT(S.size() == size(), userErr,
|
||||
"Different number of values than indicies!");
|
||||
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;
|
||||
VecView<int64_t> view = AsVec64();
|
||||
auto zBegin = zip(S.begin(), view.begin());
|
||||
auto zEnd = zip(S.end(), view.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;
|
||||
S.resize(size, -1);
|
||||
p.resize(size, -1);
|
||||
q.resize(size, -1);
|
||||
Resize(size);
|
||||
return size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct firstNonFinite {
|
||||
__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 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 operator()(
|
||||
thrust::tuple<T, int, int, int> x) const {
|
||||
bool operator()(thrust::tuple<T, int, int64_t> x) const {
|
||||
bool result = NotFinite(thrust::get<0>(x));
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
size_t KeepFinite(VecDH<T>& v, VecDH<int>& x) {
|
||||
ASSERT(x.size() == p.size(), userErr,
|
||||
size_t KeepFinite(Vec<T>& v, Vec<int>& x) {
|
||||
ASSERT(x.size() == size(), userErr,
|
||||
"Different number of values than indicies!");
|
||||
auto zBegin = zip(v.begin(), x.begin(), begin(false), begin(true));
|
||||
auto zEnd = zip(v.end(), x.end(), end(false), end(true));
|
||||
VecView<int64_t> view = AsVec64();
|
||||
auto zBegin = zip(v.begin(), x.begin(), view.begin());
|
||||
auto zEnd = zip(v.end(), x.end(), view.end());
|
||||
size_t size = remove_if<decltype(zBegin)>(autoPolicy(v.size()), zBegin,
|
||||
zEnd, firstNonFinite<T>()) -
|
||||
zBegin;
|
||||
v.resize(size);
|
||||
x.resize(size, -1);
|
||||
p.resize(size, -1);
|
||||
q.resize(size, -1);
|
||||
x.resize(size);
|
||||
Resize(size);
|
||||
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 = " << p[i] << ", q = " << q[i] << std::endl;
|
||||
std::cout << i << ", p = " << Get(i, false) << ", q = " << Get(i, true)
|
||||
<< std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
VecDH<int> p, q;
|
||||
Vec<char> data_;
|
||||
inline int* ptr() { return reinterpret_cast<int32_t*>(data_.data()); }
|
||||
inline const int* ptr() const {
|
||||
return reinterpret_cast<const int32_t*>(data_.data());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -14,14 +14,9 @@
|
||||
// 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>
|
||||
#include <unordered_map>
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
#include <chrono>
|
||||
@@ -29,6 +24,7 @@
|
||||
#endif
|
||||
|
||||
#include "par.h"
|
||||
#include "vec.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
@@ -104,18 +100,18 @@ thrust::counting_iterator<T> countAt(T i) {
|
||||
return thrust::make_counting_iterator(i);
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Next3(int i) {
|
||||
inline int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Prev3(int i) {
|
||||
inline int Prev3(int i) {
|
||||
constexpr glm::ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T AtomicAdd(T& target, T add) {
|
||||
T AtomicAdd(T& target, T add) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
// required for synchronization
|
||||
__threadfence();
|
||||
@@ -131,7 +127,7 @@ __host__ __device__ T AtomicAdd(T& target, T add) {
|
||||
}
|
||||
|
||||
template <>
|
||||
inline __host__ __device__ int AtomicAdd(int& target, int add) {
|
||||
inline int AtomicAdd(int& target, int add) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
// required for synchronization
|
||||
__threadfence();
|
||||
@@ -156,8 +152,7 @@ class strided_range {
|
||||
|
||||
stride_functor(difference_type stride) : stride(stride) {}
|
||||
|
||||
__host__ __device__ difference_type
|
||||
operator()(const difference_type& i) const {
|
||||
difference_type operator()(const difference_type& i) const {
|
||||
return stride * i;
|
||||
}
|
||||
};
|
||||
@@ -220,5 +215,62 @@ class ConcurrentSharedPtr {
|
||||
std::shared_ptr<std::recursive_mutex> mutex =
|
||||
std::make_shared<std::recursive_mutex>();
|
||||
};
|
||||
|
||||
template <typename I = int, typename R = unsigned char>
|
||||
struct UnionFind {
|
||||
Vec<I> parents;
|
||||
// we do union by rank
|
||||
// note that we shift rank by 1, rank 0 means it is not connected to anything
|
||||
// else
|
||||
Vec<R> ranks;
|
||||
|
||||
UnionFind(I numNodes) : parents(numNodes), ranks(numNodes, 0) {
|
||||
sequence(autoPolicy(numNodes), parents.begin(), parents.end());
|
||||
}
|
||||
|
||||
I find(I x) {
|
||||
while (parents[x] != x) {
|
||||
parents[x] = parents[parents[x]];
|
||||
x = parents[x];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
void unionXY(I x, I y) {
|
||||
if (x == y) return;
|
||||
if (ranks[x] == 0) ranks[x] = 1;
|
||||
if (ranks[y] == 0) ranks[y] = 1;
|
||||
x = find(x);
|
||||
y = find(y);
|
||||
if (x == y) return;
|
||||
if (ranks[x] < ranks[y]) std::swap(x, y);
|
||||
if (ranks[x] == ranks[y]) ranks[x]++;
|
||||
parents[y] = x;
|
||||
}
|
||||
|
||||
I connectedComponents(std::vector<I>& components) {
|
||||
components.resize(parents.size());
|
||||
I lonelyNodes = 0;
|
||||
std::unordered_map<I, I> toLabel;
|
||||
for (size_t i = 0; i < parents.size(); ++i) {
|
||||
// we optimize for connected component of size 1
|
||||
// no need to put them into the hashmap
|
||||
if (ranks[i] == 0) {
|
||||
components[i] = static_cast<I>(toLabel.size()) + lonelyNodes++;
|
||||
continue;
|
||||
}
|
||||
parents[i] = find(i);
|
||||
auto iter = toLabel.find(parents[i]);
|
||||
if (iter == toLabel.end()) {
|
||||
I s = static_cast<I>(toLabel.size()) + lonelyNodes;
|
||||
toLabel.insert(std::make_pair(parents[i], s));
|
||||
components[i] = s;
|
||||
} else {
|
||||
components[i] = iter->second;
|
||||
}
|
||||
}
|
||||
return toLabel.size() + lonelyNodes;
|
||||
}
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#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
|
||||
#endif
|
||||
|
||||
// #include "optional_assert.h"
|
||||
#include "par.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
template <typename T>
|
||||
class Vec;
|
||||
|
||||
/**
|
||||
* View for Vec, can perform offset operation.
|
||||
* This will be invalidated when the original vector is dropped or changes
|
||||
* length.
|
||||
*/
|
||||
template <typename T>
|
||||
class VecView {
|
||||
public:
|
||||
using Iter = T *;
|
||||
using IterC = const T *;
|
||||
|
||||
VecView(T *ptr_, int size_) : ptr_(ptr_), size_(size_) {}
|
||||
|
||||
VecView(const VecView &other) {
|
||||
ptr_ = other.ptr_;
|
||||
size_ = other.size_;
|
||||
}
|
||||
|
||||
VecView &operator=(const VecView &other) {
|
||||
ptr_ = other.ptr_;
|
||||
size_ = other.size_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// allows conversion to a const VecView
|
||||
operator VecView<const T>() const { return {ptr_, size_}; }
|
||||
|
||||
inline const T &operator[](int i) const {
|
||||
if (i < 0 || i >= size_) throw std::out_of_range("Vec out of range");
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
inline T &operator[](int i) {
|
||||
if (i < 0 || i >= size_) throw std::out_of_range("Vec out of range");
|
||||
return ptr_[i];
|
||||
}
|
||||
|
||||
IterC cbegin() const { return ptr_; }
|
||||
IterC cend() const { return ptr_ + size_; }
|
||||
|
||||
IterC begin() const { return cbegin(); }
|
||||
IterC end() const { return cend(); }
|
||||
|
||||
Iter begin() { return ptr_; }
|
||||
Iter end() { return ptr_ + size_; }
|
||||
|
||||
const T &front() const {
|
||||
if (size_ == 0)
|
||||
throw std::out_of_range("attempt to take the front of an empty vector");
|
||||
return ptr_[0];
|
||||
}
|
||||
|
||||
const T &back() const {
|
||||
if (size_ == 0)
|
||||
throw std::out_of_range("attempt to take the back of an empty vector");
|
||||
return ptr_[size_ - 1];
|
||||
}
|
||||
|
||||
T &front() {
|
||||
if (size_ == 0)
|
||||
throw std::out_of_range("attempt to take the front of an empty vector");
|
||||
return ptr_[0];
|
||||
}
|
||||
|
||||
T &back() {
|
||||
if (size_ == 0)
|
||||
throw std::out_of_range("attempt to take the back of an empty vector");
|
||||
return ptr_[size_ - 1];
|
||||
}
|
||||
|
||||
int size() const { return size_; }
|
||||
|
||||
bool empty() const { return size_ == 0; }
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
void Dump() {
|
||||
std::cout << "Vec = " << std::endl;
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
std::cout << i << ", " << ptr_[i] << ", " << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
T *ptr_ = nullptr;
|
||||
int size_ = 0;
|
||||
|
||||
VecView() = default;
|
||||
friend class Vec<T>;
|
||||
friend class Vec<typename std::remove_const<T>::type>;
|
||||
friend class VecView<typename std::remove_const<T>::type>;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
template <typename T>
|
||||
class Vec : public VecView<T> {
|
||||
public:
|
||||
Vec() {}
|
||||
|
||||
// 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.
|
||||
Vec(int size) {
|
||||
reserve(size);
|
||||
this->size_ = size;
|
||||
}
|
||||
|
||||
Vec(int size, T val) { resize(size, val); }
|
||||
|
||||
Vec(const Vec<T> &vec) {
|
||||
this->size_ = vec.size();
|
||||
this->capacity_ = this->size_;
|
||||
auto policy = autoPolicy(this->size_);
|
||||
if (this->size_ != 0) {
|
||||
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
if (this->ptr_ == nullptr) throw std::bad_alloc();
|
||||
TracyAllocS(ptr_, size_ * sizeof(T), 3);
|
||||
uninitialized_copy(policy, vec.begin(), vec.end(), this->ptr_);
|
||||
}
|
||||
}
|
||||
|
||||
Vec(const std::vector<T> &vec) {
|
||||
this->size_ = vec.size();
|
||||
this->capacity_ = this->size_;
|
||||
auto policy = autoPolicy(this->size_);
|
||||
if (this->size_ != 0) {
|
||||
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
if (this->ptr_ == nullptr) throw std::bad_alloc();
|
||||
TracyAllocS(ptr_, size_ * sizeof(T), 3);
|
||||
uninitialized_copy(policy, vec.begin(), vec.end(), this->ptr_);
|
||||
}
|
||||
}
|
||||
|
||||
Vec(Vec<T> &&vec) {
|
||||
this->ptr_ = vec.ptr_;
|
||||
this->size_ = vec.size_;
|
||||
capacity_ = vec.capacity_;
|
||||
vec.ptr_ = nullptr;
|
||||
vec.size_ = 0;
|
||||
vec.capacity_ = 0;
|
||||
}
|
||||
|
||||
operator VecView<T>() { return {this->ptr_, this->size_}; }
|
||||
operator VecView<const T>() const { return {this->ptr_, this->size_}; }
|
||||
|
||||
~Vec() {
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->ptr_ = nullptr;
|
||||
this->size_ = 0;
|
||||
capacity_ = 0;
|
||||
}
|
||||
|
||||
Vec<T> &operator=(const Vec<T> &other) {
|
||||
if (&other == this) return *this;
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(this->ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->size_ = other.size_;
|
||||
capacity_ = other.size_;
|
||||
auto policy = autoPolicy(this->size_);
|
||||
if (this->size_ != 0) {
|
||||
this->ptr_ = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
if (this->ptr_ == nullptr) throw std::bad_alloc();
|
||||
TracyAllocS(ptr_, size_ * sizeof(T), 3);
|
||||
uninitialized_copy(policy, other.begin(), other.end(), this->ptr_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vec<T> &operator=(Vec<T> &&other) {
|
||||
if (&other == this) return *this;
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->size_ = other.size_;
|
||||
capacity_ = other.capacity_;
|
||||
this->ptr_ = other.ptr_;
|
||||
other.ptr_ = nullptr;
|
||||
other.size_ = 0;
|
||||
other.capacity_ = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator VecView<T>() const { return {this->ptr_, this->size_}; }
|
||||
|
||||
void swap(Vec<T> &other) {
|
||||
std::swap(this->ptr_, other.ptr_);
|
||||
std::swap(this->size_, other.size_);
|
||||
std::swap(capacity_, other.capacity_);
|
||||
}
|
||||
|
||||
inline void push_back(const T &val) {
|
||||
if (this->size_ >= capacity_) {
|
||||
// avoid dangling pointer in case val is a reference of our array
|
||||
T val_copy = val;
|
||||
reserve(capacity_ == 0 ? 128 : capacity_ * 2);
|
||||
this->ptr_[this->size_++] = val_copy;
|
||||
return;
|
||||
}
|
||||
this->ptr_[this->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 (this->size_ > 0)
|
||||
uninitialized_copy(autoPolicy(this->size_), this->ptr_,
|
||||
this->ptr_ + this->size_, newBuffer);
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->ptr_ = newBuffer;
|
||||
capacity_ = n;
|
||||
}
|
||||
}
|
||||
|
||||
void resize(int newSize, T val = T()) {
|
||||
bool shrink = this->size_ > 2 * newSize;
|
||||
reserve(newSize);
|
||||
if (this->size_ < newSize) {
|
||||
uninitialized_fill(autoPolicy(newSize - this->size_),
|
||||
this->ptr_ + this->size_, this->ptr_ + newSize, val);
|
||||
}
|
||||
this->size_ = newSize;
|
||||
if (shrink) shrink_to_fit();
|
||||
}
|
||||
|
||||
void shrink_to_fit() {
|
||||
T *newBuffer = nullptr;
|
||||
if (this->size_ > 0) {
|
||||
newBuffer = reinterpret_cast<T *>(malloc(this->size_ * sizeof(T)));
|
||||
if (newBuffer == nullptr) throw std::bad_alloc();
|
||||
TracyAllocS(newBuffer, size_ * sizeof(T), 3);
|
||||
uninitialized_copy(autoPolicy(this->size_), this->ptr_,
|
||||
this->ptr_ + this->size_, newBuffer);
|
||||
}
|
||||
if (this->ptr_ != nullptr) {
|
||||
TracyFreeS(ptr_, 3);
|
||||
free(this->ptr_);
|
||||
}
|
||||
this->ptr_ = newBuffer;
|
||||
capacity_ = this->size_;
|
||||
}
|
||||
|
||||
VecView<T> view(int offset = 0, int length = -1) {
|
||||
if (length == -1) {
|
||||
length = this->size_ - offset;
|
||||
if (length < 0) throw std::out_of_range("Vec::view out of range");
|
||||
} else if (offset + length > this->size_ || offset < 0) {
|
||||
throw std::out_of_range("Vec::view out of range");
|
||||
} else if (length < 0) {
|
||||
throw std::out_of_range("Vec::view negative length is not allowed");
|
||||
}
|
||||
return VecView<T>(this->ptr_ + offset, length);
|
||||
}
|
||||
|
||||
VecView<const T> cview(int offset = 0, int length = -1) const {
|
||||
if (length == -1) {
|
||||
length = this->size_ - offset;
|
||||
if (length < 0) throw std::out_of_range("Vec::cview out of range");
|
||||
} else if (offset + length > this->size_ || offset < 0) {
|
||||
throw std::out_of_range("Vec::cview out of range");
|
||||
} else if (length < 0) {
|
||||
throw std::out_of_range("Vec::cview negative length is not allowed");
|
||||
}
|
||||
return VecView<const T>(this->ptr_ + offset, length);
|
||||
}
|
||||
|
||||
VecView<const T> view(int offset = 0, int length = -1) const {
|
||||
return cview(offset, length);
|
||||
}
|
||||
|
||||
T *data() { return this->ptr_; }
|
||||
const T *data() const { return this->ptr_; }
|
||||
|
||||
private:
|
||||
int capacity_ = 0;
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -1,450 +0,0 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#ifdef MANIFOLD_USE_CUDA
|
||||
#include <cuda.h>
|
||||
#endif
|
||||
|
||||
// #include "optional_assert.h"
|
||||
#include "par.h"
|
||||
#include "public.h"
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
|
||||
// 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 ManagedVec {
|
||||
public:
|
||||
typedef T *Iter;
|
||||
typedef const T *IterC;
|
||||
|
||||
ManagedVec() {
|
||||
size_ = 0;
|
||||
capacity_ = 0;
|
||||
onHost = true;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
~ManagedVec() {
|
||||
if (ptr_ != nullptr) freeManaged(ptr_);
|
||||
ptr_ = nullptr;
|
||||
size_ = 0;
|
||||
capacity_ = 0;
|
||||
}
|
||||
|
||||
ManagedVec(const std::vector<T> &vec) {
|
||||
size_ = vec.size();
|
||||
capacity_ = size_;
|
||||
auto policy = autoPolicy(size_);
|
||||
onHost = policy != ExecutionPolicy::ParUnseq;
|
||||
if (size_ != 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void resize(size_t n) {
|
||||
reserve(n);
|
||||
size_ = n;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
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 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;
|
||||
impl_.resize(newSize, val);
|
||||
if (shrink) impl_.shrink_to_fit();
|
||||
}
|
||||
|
||||
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 < impl_.size(); ++i) {
|
||||
std::cout << i << ", " << impl_[i] << ", " << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
ManagedVec<T> impl_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class VecDc {
|
||||
public:
|
||||
VecDc(const VecDH<T> &vec) : ptr_(vec.ptrD()), size_(vec.size()) {}
|
||||
|
||||
__host__ __device__ const T &operator[](int i) const { return ptr_[i]; }
|
||||
__host__ __device__ int size() const { return size_; }
|
||||
|
||||
private:
|
||||
T const *const ptr_;
|
||||
const int size_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class VecD {
|
||||
public:
|
||||
VecD(VecDH<T> &vec) : ptr_(vec.ptrD()), size_(vec.size()) {}
|
||||
|
||||
__host__ __device__ T &operator[](int i) const { return ptr_[i]; }
|
||||
__host__ __device__ int size() const { return size_; }
|
||||
|
||||
private:
|
||||
T *ptr_;
|
||||
int size_;
|
||||
};
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -1,36 +0,0 @@
|
||||
// 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
|
||||
+20
-13
@@ -14,8 +14,6 @@
|
||||
|
||||
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)
|
||||
@@ -24,8 +22,27 @@ 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)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG v1.14.0
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
FIND_PACKAGE_ARGS NAMES GTest gtest
|
||||
)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
if(NOT TARGET GTest::gtest_main)
|
||||
add_library(GTest::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)
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::gtest_main manifold sdf samples cross_section)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
target_link_libraries(${PROJECT_NAME} manifoldc)
|
||||
@@ -36,16 +53,6 @@ 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)
|
||||
|
||||
|
||||
+7
-12
@@ -163,11 +163,9 @@ TEST(Boolean, Coplanar) {
|
||||
MeshGL cylinderGL = WithPositionColors(cylinder);
|
||||
cylinder = Manifold(cylinderGL);
|
||||
|
||||
Manifold cylinder2 = cylinder.Scale({0.5f, 0.5f, 1.0f})
|
||||
.Rotate(0, 0, 15)
|
||||
.Translate({0.25f, 0.25f, 0.0f});
|
||||
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 5);
|
||||
Manifold out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{32, 64, 3, 49}});
|
||||
ExpectMeshes(out, {{32, 64, 3, 48}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
@@ -189,11 +187,9 @@ TEST(Boolean, CoplanarProp) {
|
||||
MeshGL cylinderGL = WithIndexColors(cylinder.GetMesh());
|
||||
cylinder = Manifold(cylinderGL);
|
||||
|
||||
Manifold cylinder2 = cylinder.Scale({0.5f, 0.5f, 1.0f})
|
||||
.Rotate(0, 0, 15)
|
||||
.Translate({0.25f, 0.25f, 0.0f});
|
||||
Manifold cylinder2 = cylinder.Scale({0.8f, 0.8f, 1.0f}).Rotate(0, 0, 5);
|
||||
Manifold out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{42, 84, 3, 68}});
|
||||
ExpectMeshes(out, {{52, 104, 3, 88}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
@@ -636,9 +632,9 @@ TEST(Boolean, BooleanVolumes) {
|
||||
// m7 = m1 + m2 + m3
|
||||
auto m1 = Manifold::Cube({1, 1, 1});
|
||||
auto m2 = Manifold::Cube({2, 1, 1}).Transform(
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 0, 0)));
|
||||
glm::mat4x3(glm::translate(glm::mat4(1.0f), glm::vec3(1.0f, 0, 0))));
|
||||
auto m4 = Manifold::Cube({4, 1, 1}).Transform(
|
||||
glm::translate(glm::mat4(1.0f), glm::vec3(3.0f, 0, 0)));
|
||||
glm::mat4x3(glm::translate(glm::mat4(1.0f), glm::vec3(3.0f, 0, 0))));
|
||||
auto m3 = Manifold::Cube({3, 1, 1});
|
||||
auto m7 = Manifold::Cube({7, 1, 1});
|
||||
|
||||
@@ -879,7 +875,6 @@ TEST(Boolean, Sweep) {
|
||||
std::vector<Manifold> result;
|
||||
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
// std::cerr << i << std::endl;
|
||||
std::vector<Manifold> primitives =
|
||||
cutterPrimitives(pathPoints[i], pathPoints[(i + 1) % numPoints],
|
||||
pathPoints[(i + 2) % numPoints]);
|
||||
@@ -906,4 +901,4 @@ TEST(Boolean, Sweep) {
|
||||
#endif
|
||||
|
||||
PolygonParams().processOverlaps = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ TEST(CrossSection, Rect) {
|
||||
float w = 10;
|
||||
float h = 5;
|
||||
auto rect = Rect({0, 0}, {w, h});
|
||||
auto cross = rect.AsCrossSection();
|
||||
CrossSection cross(rect);
|
||||
auto area = rect.Area();
|
||||
|
||||
EXPECT_FLOAT_EQ(area, w * h);
|
||||
@@ -104,7 +104,7 @@ TEST(CrossSection, Transform) {
|
||||
0.0f, 3.0f, 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
|
||||
auto b = sq.Transform(trans * scale * rot);
|
||||
auto b = sq.Transform(glm::mat3x2(trans * scale * rot));
|
||||
auto b_copy = CrossSection(b);
|
||||
|
||||
auto ex_b = Manifold::Extrude(b, 1.).GetMesh();
|
||||
@@ -124,8 +124,8 @@ TEST(CrossSection, Warp) {
|
||||
|
||||
EXPECT_EQ(sq.NumVert(), 4);
|
||||
EXPECT_EQ(sq.NumContour(), 1);
|
||||
Identical(Manifold::Extrude(a, 1.).GetMesh(),
|
||||
Manifold::Extrude(b, 1.).GetMesh());
|
||||
// Identical(Manifold::Extrude(a, 1.).GetMesh(),
|
||||
// Manifold::Extrude(b, 1.).GetMesh());
|
||||
}
|
||||
|
||||
TEST(CrossSection, Decompose) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
#include "manifold.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "cross_section.h"
|
||||
#include "test.h"
|
||||
|
||||
@@ -642,7 +644,7 @@ TEST(Manifold, Invalid) {
|
||||
auto invalid = Manifold::Error::InvalidConstruction;
|
||||
auto circ = CrossSection::Circle(10.);
|
||||
auto empty_circ = CrossSection::Circle(-2.);
|
||||
auto empty_sq = CrossSection::Square(glm::vec3(0.0f));
|
||||
auto empty_sq = CrossSection::Square(glm::vec2(0.0f));
|
||||
|
||||
EXPECT_EQ(Manifold::Sphere(0).Status(), invalid);
|
||||
EXPECT_EQ(Manifold::Cylinder(0, 5).Status(), invalid);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user