Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c8ce3c935 | ||
|
|
4d434e4f8c | ||
|
|
9e9a22d66b | ||
|
|
893a4f8ac3 | ||
|
|
78c03f2611 | ||
|
|
2915f7aa90 | ||
|
|
95daa54591 | ||
|
|
2c597d1eef | ||
|
|
e2a8e94906 | ||
|
|
eae9653745 | ||
|
|
feb7fb6edd | ||
|
|
32f887d321 | ||
|
|
3a962cfa53 | ||
|
|
36ca7acf6c | ||
|
|
0704f62eaa | ||
|
|
5041c38424 | ||
|
|
c4bf392f66 | ||
|
|
3f42e5e6d7 | ||
|
|
376a7c676f | ||
|
|
e6bb87b2bb | ||
|
|
9477deaa9c | ||
|
|
02affd5247 | ||
|
|
171fe5466e | ||
|
|
5331b68485 | ||
|
|
20d7ef96fb | ||
|
|
3560bbf679 | ||
|
|
4415cfce46 | ||
|
|
bbb1fabcac | ||
|
|
f43dded234 | ||
|
|
05f9ceb999 | ||
|
|
422c7f26d9 | ||
|
|
a701d2be8e | ||
|
|
5b5f56674a | ||
|
|
1b7b365c3a | ||
|
|
812b056179 | ||
|
|
bd46bfd1cf | ||
|
|
a7b457db74 | ||
|
|
87059508e7 |
@@ -26,6 +26,7 @@ jobs:
|
||||
uses: dawidd6/action-download-artifact@v2
|
||||
with:
|
||||
workflow: manifold.yml
|
||||
workflow_conclusion: completed
|
||||
pr: ${{github.event.pull_request.number}}
|
||||
name: wasm
|
||||
path: ./public
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
@@ -12,14 +12,15 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
|
||||
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_EXPORT=ON -DMANIFOLD_PYBIND=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
|
||||
@@ -52,7 +53,7 @@ jobs:
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
run: |
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
@@ -67,8 +68,37 @@ jobs:
|
||||
fail_ci_if_error: false
|
||||
name: ${{matrix.parallel_backend}}-${{matrix.cuda_support}}
|
||||
|
||||
build_cbind:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get -y update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libomp-dev libassimp-dev git libtbb-dev pkg-config libpython3-dev python3 python3-distutils python3-pip
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: jwlawson/actions-setup-cmake@v1.12
|
||||
- name: Build C bindings with OMP and CUDA
|
||||
run: |
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_CBIND=ON -DMANIFOLD_PAR=OMP -DMANIFOLD_USE_CUDA=ON .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test --gtest_filter=CBIND.*
|
||||
|
||||
build_wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
@@ -108,9 +138,12 @@ jobs:
|
||||
build/bindings/wasm/manifold.js
|
||||
build/bindings/wasm/manifold.wasm
|
||||
build/bindings/wasm/manifold.d.ts
|
||||
retention-days: 7
|
||||
build/bindings/wasm/manifold-global-types.d.ts
|
||||
build/bindings/wasm/manifold-encapsulated-types.d.ts
|
||||
retention-days: 90
|
||||
|
||||
build_windows:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
@@ -144,7 +177,7 @@ jobs:
|
||||
- name: Build ${{matrix.backend}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
@@ -154,12 +187,15 @@ jobs:
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mac:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
runs-on: macos-latest
|
||||
if: github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Install common dependencies
|
||||
run: brew install pkg-config
|
||||
- name: Install OpenMP
|
||||
if: matrix.parallel_backend == 'OMP'
|
||||
run: brew install libomp
|
||||
@@ -175,13 +211,14 @@ jobs:
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
|
||||
@@ -191,4 +228,3 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
|
||||
@@ -118,4 +118,14 @@
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
}
|
||||
@@ -18,10 +18,13 @@ project(manifold LANGUAGES CXX)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
option(MANIFOLD_EXPORT off)
|
||||
option(MANIFOLD_DEBUG off)
|
||||
option(MANIFOLD_USE_CUDA off)
|
||||
option(MANIFOLD_EXPORT OFF)
|
||||
option(MANIFOLD_DEBUG OFF)
|
||||
option(MANIFOLD_USE_CUDA OFF)
|
||||
option(MANIFOLD_PYBIND ON)
|
||||
option(MANIFOLD_CBIND OFF)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
set(MANIFOLD_FLAGS -O3)
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
message("Building for Emscripten")
|
||||
@@ -29,15 +32,26 @@ if(EMSCRIPTEN)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
endif()
|
||||
|
||||
option(PYBIND11_FINDPYTHON on)
|
||||
option(PYBIND11_FINDPYTHON ON)
|
||||
|
||||
option(BUILD_TEST_CGAL off)
|
||||
option(BUILD_TEST_CGAL OFF)
|
||||
|
||||
option(BUILD_SHARED_LIBS off)
|
||||
option(BUILD_SHARED_LIBS OFF)
|
||||
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
set(MANIFOLD_EXPORT ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
|
||||
# Allow shared libraries to be relocatable (add Place Independent Code flag).
|
||||
# Also include when statically linking C bindings to avoid issues with bundling
|
||||
# artefacts in host languages using them for FFI.
|
||||
add_compile_options(-fPIC)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
enable_language(CUDA)
|
||||
find_package(CUDA REQUIRED)
|
||||
@@ -72,4 +86,4 @@ add_subdirectory(src)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
add_subdirectory(bindings)
|
||||
add_subdirectory(bindings)
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
project(manifoldc)
|
||||
|
||||
|
||||
add_library(manifoldc manifoldc.cpp conv.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET manifoldc PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
manifoldc
|
||||
PRIVATE manifold sdf meshIO graphlite
|
||||
)
|
||||
|
||||
target_include_directories(manifoldc PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(manifoldc PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(manifoldc PRIVATE cxx_std_17)
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2) {
|
||||
auto p1 = glm::vec3(x1, y1, z1);
|
||||
auto p2 = glm::vec3(x2, y2, z2);
|
||||
auto box = new (mem) Box(p1, p2);
|
||||
return to_c(box);
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
|
||||
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
|
||||
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
auto v = box.Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
auto v = box.Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
return box.Scale();
|
||||
}
|
||||
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return box.Contains(p);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
auto box = (*from_c(a)).Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = (*from_c(b)).Transform(mat);
|
||||
return to_c(new (mem) Box(transformed));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto scaled = (*from_c(b)) * p;
|
||||
return to_c(new (mem) Box(scaled));
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return box.DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return (*from_c(a)).DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return (*from_c(b)).IsFinite(); }
|
||||
@@ -0,0 +1,166 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include "include/types.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
return reinterpret_cast<ManifoldManifold *>(m);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon *>(m);
|
||||
}
|
||||
|
||||
ManifoldPolygons *to_c(manifold::Polygons *m) {
|
||||
return reinterpret_cast<ManifoldPolygons *>(m);
|
||||
}
|
||||
|
||||
ManifoldMesh *to_c(manifold::Mesh *m) {
|
||||
return reinterpret_cast<ManifoldMesh *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m) {
|
||||
return reinterpret_cast<ManifoldCurvature *>(m);
|
||||
}
|
||||
|
||||
ManifoldComponents *to_c(manifold::Components *components) {
|
||||
return reinterpret_cast<ManifoldComponents *>(components);
|
||||
}
|
||||
|
||||
ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
ManifoldError e = MANIFOLD_NO_ERROR;
|
||||
switch (error) {
|
||||
case Manifold::Error::NoError:
|
||||
e = MANIFOLD_NO_ERROR;
|
||||
break;
|
||||
case Manifold::Error::NonFiniteVertex:
|
||||
e = MANIFOLD_NON_FINITE_VERTEX;
|
||||
break;
|
||||
case Manifold::Error::NotManifold:
|
||||
e = MANIFOLD_NOT_MANIFOLD;
|
||||
break;
|
||||
case Manifold::Error::VertexOutOfBounds:
|
||||
e = MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::PropertiesWrongLength:
|
||||
e = MANIFOLD_PROPERTIES_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::MissingPositionProperties:
|
||||
e = MANIFOLD_MISSING_POSITION_PROPERTIES;
|
||||
break;
|
||||
case Manifold::Error::MergeVectorsDifferentLengths:
|
||||
e = MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS;
|
||||
break;
|
||||
case Manifold::Error::MergeIndexOutOfBounds:
|
||||
e = MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS;
|
||||
break;
|
||||
case Manifold::Error::TransformWrongLength:
|
||||
e = MANIFOLD_TRANSFORM_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::RunIndexWrongLength:
|
||||
e = MANIFOLD_RUN_INDEX_WRONG_LENGTH;
|
||||
break;
|
||||
case Manifold::Error::FaceIDWrongLength:
|
||||
e = MANIFOLD_FACE_ID_WRONG_LENGTH;
|
||||
break;
|
||||
};
|
||||
return e;
|
||||
}
|
||||
|
||||
ManifoldBox *to_c(manifold::Box *m) {
|
||||
return reinterpret_cast<ManifoldBox *>(m);
|
||||
}
|
||||
|
||||
ManifoldMaterial *to_c(manifold::Material *m) {
|
||||
return reinterpret_cast<ManifoldMaterial *>(m);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m) {
|
||||
return reinterpret_cast<ManifoldExportOptions *>(m);
|
||||
}
|
||||
|
||||
ManifoldVec3 to_c(glm::vec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldIVec3 to_c(glm::ivec3 v) { return {v.x, v.y, v.z}; }
|
||||
|
||||
ManifoldProperties to_c(manifold::Properties p) {
|
||||
return {p.surfaceArea, p.volume};
|
||||
}
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m) {
|
||||
return reinterpret_cast<manifold::Manifold const *>(m);
|
||||
}
|
||||
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m) {
|
||||
return reinterpret_cast<manifold::SimplePolygon const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m) {
|
||||
return reinterpret_cast<manifold::Polygons const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m) {
|
||||
return reinterpret_cast<manifold::Mesh const *>(m);
|
||||
}
|
||||
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m) {
|
||||
return reinterpret_cast<manifold::MeshGL const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *m) {
|
||||
return reinterpret_cast<manifold::Curvature const *>(m);
|
||||
}
|
||||
|
||||
const manifold::Components *from_c(ManifoldComponents *components) {
|
||||
return reinterpret_cast<manifold::Components *>(components);
|
||||
}
|
||||
|
||||
const manifold::Box *from_c(ManifoldBox *m) {
|
||||
return reinterpret_cast<manifold::Box const *>(m);
|
||||
}
|
||||
|
||||
manifold::Material *from_c(ManifoldMaterial *mat) {
|
||||
return reinterpret_cast<manifold::Material *>(mat);
|
||||
}
|
||||
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options) {
|
||||
return reinterpret_cast<manifold::ExportOptions *>(options);
|
||||
}
|
||||
|
||||
glm::vec3 from_c(ManifoldVec3 v) { return glm::vec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::ivec3 from_c(ManifoldIVec3 v) { return glm::ivec3(v.x, v.y, v.z); }
|
||||
|
||||
glm::vec4 from_c(ManifoldVec4 v) { return glm::vec4(v.x, v.y, v.z, v.w); }
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::ivec3>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length) {
|
||||
auto vec = std::vector<glm::vec4>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(from_c(vs[i]));
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include <types.h>
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m);
|
||||
ManifoldComponents *to_c(manifold::Components *components);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldMaterial *to_c(manifold::Material *m);
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *m);
|
||||
const manifold::Components *from_c(ManifoldComponents *components);
|
||||
manifold::Material *from_c(ManifoldMaterial *mat);
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector_of_array(T *ts, size_t length) {
|
||||
auto vec = std::vector<T>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *copy_data(void *mem, std::vector<T> v) {
|
||||
T *ts = reinterpret_cast<T *>(mem);
|
||||
memcpy(ts, v.data(), sizeof(T) * v.size());
|
||||
return ts;
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Polygons
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length);
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length);
|
||||
|
||||
// Mesh Construction
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris);
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent);
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m);
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// SDF
|
||||
// By default, the execution policy (sequential or parallel) of
|
||||
// manifold_level_set will be chosen automatically depending on the size of the
|
||||
// job and whether Manifold has been compiled with a PAR backend. If you are
|
||||
// using these bindings from a language that has a runtime lock preventing the
|
||||
// parallel execution of closures, then you should use manifold_level_set_seq to
|
||||
// force sequential execution.
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level);
|
||||
|
||||
// Manifold Booleans
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b);
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset);
|
||||
|
||||
// Manifold Transformations
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z);
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float));
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine);
|
||||
|
||||
// Manifold Shapes / Constructors
|
||||
ManifoldManifold *manifold_empty(void *mem);
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m);
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem);
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center);
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center);
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh);
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_idxs);
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldPolygons *polygons,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y);
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldPolygons *polygons,
|
||||
int circular_segments);
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifold **ms,
|
||||
size_t length);
|
||||
size_t manifold_decompose_length(ManifoldManifold *m);
|
||||
ManifoldComponents *manifold_get_components(void *mem, ManifoldManifold *m);
|
||||
size_t manifold_components_length(ManifoldComponents *components);
|
||||
ManifoldManifold **manifold_decompose(void **mem, ManifoldManifold *m,
|
||||
ManifoldComponents *cs);
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m);
|
||||
|
||||
// Manifold Info
|
||||
int manifold_is_empty(ManifoldManifold *m);
|
||||
ManifoldError manifold_status(ManifoldManifold *m);
|
||||
int manifold_num_vert(ManifoldManifold *m);
|
||||
int manifold_num_edge(ManifoldManifold *m);
|
||||
int manifold_num_tri(ManifoldManifold *m);
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m);
|
||||
float manifold_precision(ManifoldManifold *m);
|
||||
int manifold_genus(ManifoldManifold *m);
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m);
|
||||
ManifoldCurvature *manifold_get_curvature(void *mem, ManifoldManifold *m);
|
||||
ManifoldCurvatureBounds manifold_curvature_bounds(ManifoldCurvature *curv);
|
||||
size_t manifold_curvature_vert_length(ManifoldCurvature *curv);
|
||||
float *manifold_curvature_vert_mean(void *mem, ManifoldCurvature *curv);
|
||||
float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv);
|
||||
int manifold_get_circular_segments(float radius);
|
||||
int manifold_original_id(ManifoldManifold *m);
|
||||
|
||||
// Bounding Box
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2);
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b);
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b);
|
||||
float manifold_box_scale(ManifoldBox *b);
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b);
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z);
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b);
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4);
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z);
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z);
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b);
|
||||
int manifold_box_is_finite(ManifoldBox *b);
|
||||
|
||||
// Static Quality Globals
|
||||
void manifold_set_min_circular_angle(float degrees);
|
||||
void manifold_set_min_circular_edge_length(float length);
|
||||
void manifold_set_circular_segments(int number);
|
||||
|
||||
// Manifold Mesh Extraction
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m);
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m);
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m);
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
|
||||
|
||||
// MeshIO / Export
|
||||
ManifoldMaterial *manifold_material(void *mem);
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness);
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color);
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert);
|
||||
ManifoldExportOptions *manifold_export_options(void *mem);
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted);
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat);
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options);
|
||||
|
||||
// memory size
|
||||
size_t manifold_simple_polygon_size();
|
||||
size_t manifold_polygons_size();
|
||||
size_t manifold_manifold_size();
|
||||
size_t manifold_manifold_pair_size();
|
||||
size_t manifold_meshgl_size();
|
||||
size_t manifold_box_size();
|
||||
size_t manifold_curvature_size();
|
||||
size_t manifold_components_size();
|
||||
size_t manifold_material_size();
|
||||
size_t manifold_export_options_size();
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p);
|
||||
void manifold_destruct_manifold(ManifoldManifold *m);
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_destruct_box(ManifoldBox *b);
|
||||
void manifold_destruct_curvature(ManifoldCurvature *c);
|
||||
void manifold_destruct_components(ManifoldComponents *c);
|
||||
void manifold_destruct_material(ManifoldMaterial *m);
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *options);
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p);
|
||||
void manifold_delete_polygons(ManifoldPolygons *p);
|
||||
void manifold_delete_manifold(ManifoldManifold *m);
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m);
|
||||
void manifold_delete_box(ManifoldBox *b);
|
||||
void manifold_delete_curvature(ManifoldCurvature *c);
|
||||
void manifold_delete_components(ManifoldComponents *c);
|
||||
void manifold_delete_material(ManifoldMaterial *m);
|
||||
void manifold_delete_export_options(ManifoldExportOptions *options);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct ManifoldManifold ManifoldManifold;
|
||||
typedef struct ManifoldSimplePolygon ManifoldSimplePolygon;
|
||||
typedef struct ManifoldPolygons ManifoldPolygons;
|
||||
typedef struct ManifoldMesh ManifoldMesh;
|
||||
typedef struct ManifoldMeshGL ManifoldMeshGL;
|
||||
typedef struct ManifoldCurvature ManifoldCurvature;
|
||||
typedef struct ManifoldComponents ManifoldComponents;
|
||||
typedef struct ManifoldBox ManifoldBox;
|
||||
typedef struct ManifoldMaterial ManifoldMaterial;
|
||||
typedef struct ManifoldExportOptions ManifoldExportOptions;
|
||||
|
||||
typedef struct ManifoldManifoldPair {
|
||||
ManifoldManifold* first;
|
||||
ManifoldManifold* second;
|
||||
} ManifoldManifoldPair;
|
||||
|
||||
typedef struct ManifoldVec2 {
|
||||
float x;
|
||||
float y;
|
||||
} ManifoldVec2;
|
||||
|
||||
typedef struct ManifoldVec3 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
} ManifoldVec3;
|
||||
|
||||
typedef struct ManifoldIVec3 {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} ManifoldIVec3;
|
||||
|
||||
typedef struct ManifoldVec4 {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
} ManifoldVec4;
|
||||
|
||||
typedef struct ManifoldProperties {
|
||||
float surface_area;
|
||||
float volume;
|
||||
} ManifoldProperties;
|
||||
|
||||
typedef struct ManifoldCurvatureBounds {
|
||||
float max_mean_curvature;
|
||||
float min_mean_curvature;
|
||||
float max_gaussian_curvature;
|
||||
float min_gaussian_curvature;
|
||||
} ManifoldCurvatureBounds;
|
||||
|
||||
typedef enum ManifoldError {
|
||||
MANIFOLD_NO_ERROR,
|
||||
MANIFOLD_NON_FINITE_VERTEX,
|
||||
MANIFOLD_NOT_MANIFOLD,
|
||||
MANIFOLD_VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_PROPERTIES_WRONG_LENGTH,
|
||||
MANIFOLD_MISSING_POSITION_PROPERTIES,
|
||||
MANIFOLD_MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MANIFOLD_MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
MANIFOLD_TRANSFORM_WRONG_LENGTH,
|
||||
MANIFOLD_RUN_INDEX_WRONG_LENGTH,
|
||||
MANIFOLD_FACE_ID_WRONG_LENGTH,
|
||||
} ManifoldError;
|
||||
@@ -0,0 +1,462 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <manifoldc.h>
|
||||
#include <meshIO.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include "box.cpp"
|
||||
#include "include/conv.h"
|
||||
#include "include/types.h"
|
||||
#include "meshio.cpp"
|
||||
#include "types.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
namespace {
|
||||
ManifoldMeshGL *level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length, float level,
|
||||
bool seq) {
|
||||
// typing with std::function rather than auto compiles when CUDA is on,
|
||||
// passing it into GPU (and crashing) is avoided dynamically in `sdf.h`
|
||||
std::function<float(glm::vec3)> fun = [sdf](glm::vec3 v) {
|
||||
return (sdf(v.x, v.y, v.z));
|
||||
};
|
||||
auto pol = seq ? std::make_optional(ExecutionPolicy::Seq) : std::nullopt;
|
||||
auto mesh = LevelSet(fun, *from_c(bounds), edge_length, level, pol);
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<glm::vec2>;
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back({ps[i].x, ps[i].y});
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
|
||||
size_t length) {
|
||||
auto vec = new (mem) std::vector<SimplePolygon>;
|
||||
auto polys = reinterpret_cast<SimplePolygon **>(ps);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec->push_back(*polys[i]);
|
||||
}
|
||||
return to_c(vec);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_union(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) + (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
ManifoldManifold *manifold_difference(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) - (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
ManifoldManifold *manifold_intersection(void *mem, ManifoldManifold *a,
|
||||
ManifoldManifold *b) {
|
||||
auto m = (*from_c(a)) ^ (*from_c(b));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *a, ManifoldManifold *b) {
|
||||
auto pair = from_c(a)->Split(*from_c(b));
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifoldPair manifold_split_by_plane(void *mem_first, void *mem_second,
|
||||
ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto pair = from_c(m)->SplitByPlane(normal, offset);
|
||||
auto first = new (mem_first) Manifold(pair.first);
|
||||
auto second = new (mem_second) Manifold(pair.second);
|
||||
return {to_c(first), to_c(second)};
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
|
||||
float normal_x, float normal_y,
|
||||
float normal_z, float offset) {
|
||||
auto normal = glm::vec3(normal_x, normal_y, normal_z);
|
||||
auto trimmed = from_c(m)->TrimByPlane(normal, offset);
|
||||
return to_c(new (mem) Manifold(trimmed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto v = glm::vec3(x, y, z);
|
||||
auto translated = from_c(m)->Translate(v);
|
||||
return to_c(new (mem) Manifold(translated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto rotated = from_c(m)->Rotate(x, y, z);
|
||||
return to_c(new (mem) Manifold(rotated));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_scale(void *mem, ManifoldManifold *m, float x,
|
||||
float y, float z) {
|
||||
auto s = glm::vec3(x, y, z);
|
||||
auto scaled = from_c(m)->Scale(s);
|
||||
return to_c(new (mem) Manifold(scaled));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_transform(void *mem, ManifoldManifold *m, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = from_c(m)->Transform(mat);
|
||||
return to_c(new (mem) Manifold(transformed));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
|
||||
ManifoldVec3 (*fun)(float, float, float)) {
|
||||
std::function<void(glm::vec3 & v)> warp = [fun](glm::vec3 &v) {
|
||||
v = from_c(fun(v.x, v.y, v.z));
|
||||
};
|
||||
auto warped = from_c(m)->Warp(warp);
|
||||
return to_c(new (mem) Manifold(warped));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set(void *mem, float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, false);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_level_set_seq(void *mem,
|
||||
float (*sdf)(float, float, float),
|
||||
ManifoldBox *bounds, float edge_length,
|
||||
float level) {
|
||||
return level_set(mem, sdf, bounds, edge_length, level, true);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_refine(void *mem, ManifoldManifold *m, int refine) {
|
||||
auto refined = from_c(m)->Refine(refine);
|
||||
return to_c(new (mem) Manifold(refined));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_empty(void *mem) {
|
||||
return to_c(new (mem) Manifold());
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_copy(void *mem, ManifoldManifold *m) {
|
||||
return to_c(new (mem) Manifold(*from_c(m)));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_tetrahedron(void *mem) {
|
||||
auto m = Manifold::Tetrahedron();
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cube(void *mem, float x, float y, float z,
|
||||
int center) {
|
||||
auto size = glm::vec3(x, y, z);
|
||||
auto m = Manifold::Cube(size, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_cylinder(void *mem, float height, float radius_low,
|
||||
float radius_high, int circular_segments,
|
||||
int center) {
|
||||
auto m = Manifold::Cylinder(height, radius_low, radius_high,
|
||||
circular_segments, center);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_sphere(void *mem, float radius,
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Sphere(radius, circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl(void *mem, float *vert_props, size_t n_verts,
|
||||
size_t n_props, uint32_t *tri_verts,
|
||||
size_t n_tris) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_w_tangents(void *mem, float *vert_props,
|
||||
size_t n_verts, size_t n_props,
|
||||
uint32_t *tri_verts, size_t n_tris,
|
||||
float *halfedge_tangent) {
|
||||
auto mesh = new (mem) MeshGL();
|
||||
mesh->numProp = n_props;
|
||||
mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
|
||||
mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
|
||||
mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
|
||||
return to_c(mesh);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_smooth(void *mem, ManifoldMeshGL *mesh,
|
||||
int *half_edges, float *smoothness,
|
||||
int n_edges) {
|
||||
auto smooth = std::vector<Smoothness>();
|
||||
for (int i = 0; i < n_edges; ++i) {
|
||||
smooth.push_back({half_edges[i], smoothness[i]});
|
||||
}
|
||||
auto m = Manifold::Smooth(*from_c(mesh), smooth);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_of_meshgl(void *mem, ManifoldMeshGL *mesh) {
|
||||
auto m = Manifold(*from_c(mesh));
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_extrude(void *mem, ManifoldPolygons *polygons,
|
||||
float height, int slices,
|
||||
float twist_degrees, float scale_x,
|
||||
float scale_y) {
|
||||
auto scale = glm::vec2(scale_x, scale_y);
|
||||
auto m = Manifold::Extrude(*from_c(polygons), height, slices, twist_degrees,
|
||||
scale);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_revolve(void *mem, ManifoldPolygons *polygons,
|
||||
|
||||
int circular_segments) {
|
||||
auto m = Manifold::Revolve(*from_c(polygons), circular_segments);
|
||||
return to_c(new (mem) Manifold(m));
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_compose(void *mem, ManifoldManifold **ms,
|
||||
size_t length) {
|
||||
auto vec = std::vector<Manifold>();
|
||||
auto manifolds = reinterpret_cast<Manifold **>(ms);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(*manifolds[i]);
|
||||
}
|
||||
auto composed = Manifold::Compose(vec);
|
||||
return to_c(new (mem) Manifold(composed));
|
||||
}
|
||||
|
||||
ManifoldComponents *manifold_get_components(void *mem, ManifoldManifold *m) {
|
||||
return to_c(new (mem) Components(from_c(m)->GetComponents()));
|
||||
}
|
||||
|
||||
size_t manifold_components_length(ManifoldComponents *components) {
|
||||
return from_c(components)->numComponents;
|
||||
}
|
||||
|
||||
ManifoldManifold **manifold_decompose(void **mem, ManifoldManifold *m,
|
||||
ManifoldComponents *cs) {
|
||||
auto components = *from_c(cs);
|
||||
auto manifolds = from_c(m)->Decompose(components);
|
||||
ManifoldManifold **ms = reinterpret_cast<ManifoldManifold **>(mem);
|
||||
for (int i = 0; i < components.numComponents; ++i) {
|
||||
ms[i] = to_c(new (mem[i]) Manifold(manifolds[i]));
|
||||
}
|
||||
return ms;
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_get_meshgl(void *mem, ManifoldManifold *m) {
|
||||
auto mesh = from_c(m)->GetMeshGL();
|
||||
return to_c(new (mem) MeshGL(mesh));
|
||||
}
|
||||
|
||||
ManifoldMeshGL *manifold_meshgl_copy(void *mem, ManifoldMeshGL *m) {
|
||||
return to_c(new (mem) MeshGL(*from_c(m)));
|
||||
}
|
||||
|
||||
int manifold_meshgl_num_prop(ManifoldMeshGL *m) { return from_c(m)->numProp; }
|
||||
int manifold_meshgl_num_vert(ManifoldMeshGL *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_meshgl_num_tri(ManifoldMeshGL *m) { return from_c(m)->NumTri(); }
|
||||
|
||||
size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->vertProperties.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tri_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->triVerts.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_merge_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->mergeFromVert.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_index_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runIndex.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runOriginalID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_run_transform_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->runTransform.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_face_id_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->faceID.size();
|
||||
}
|
||||
|
||||
size_t manifold_meshgl_tangent_length(ManifoldMeshGL *m) {
|
||||
return from_c(m)->halfedgeTangent.size();
|
||||
}
|
||||
|
||||
float *manifold_meshgl_vert_properties(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->vertProperties);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_tri_verts(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->triVerts);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_from_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeFromVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_merge_to_vert(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->mergeToVert);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_index(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runIndex);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_run_original_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runOriginalID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_run_transform(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->runTransform);
|
||||
}
|
||||
|
||||
uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->faceID);
|
||||
}
|
||||
|
||||
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m) {
|
||||
return copy_data(mem, from_c(m)->halfedgeTangent);
|
||||
}
|
||||
|
||||
ManifoldManifold *manifold_as_original(void *mem, ManifoldManifold *m) {
|
||||
auto orig = from_c(m)->AsOriginal();
|
||||
return to_c(new (mem) Manifold(orig));
|
||||
}
|
||||
|
||||
int manifold_original_id(ManifoldManifold *m) {
|
||||
return from_c(m)->OriginalID();
|
||||
}
|
||||
|
||||
int manifold_is_empty(ManifoldManifold *m) { return from_c(m)->IsEmpty(); }
|
||||
|
||||
ManifoldError manifold_status(ManifoldManifold *m) {
|
||||
auto error = from_c(m)->Status();
|
||||
return to_c(error);
|
||||
}
|
||||
|
||||
int manifold_num_vert(ManifoldManifold *m) { return from_c(m)->NumVert(); }
|
||||
int manifold_num_edge(ManifoldManifold *m) { return from_c(m)->NumEdge(); }
|
||||
int manifold_num_tri(ManifoldManifold *m) { return from_c(m)->NumTri(); }
|
||||
int manifold_genus(ManifoldManifold *m) { return from_c(m)->Genus(); }
|
||||
|
||||
ManifoldProperties manifold_get_properties(ManifoldManifold *m) {
|
||||
return to_c(from_c(m)->GetProperties());
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_bounding_box(void *mem, ManifoldManifold *m) {
|
||||
auto box = from_c(m)->BoundingBox();
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
float manifold_precision(ManifoldManifold *m) { return from_c(m)->Precision(); }
|
||||
ManifoldCurvature *manifold_get_curvature(void *mem, ManifoldManifold *m) {
|
||||
auto curv = from_c(m)->GetCurvature();
|
||||
return to_c(new (mem) Curvature(curv));
|
||||
}
|
||||
|
||||
ManifoldCurvatureBounds manifold_curvature_bounds(ManifoldCurvature *curv) {
|
||||
auto c = *from_c(curv);
|
||||
return {c.maxMeanCurvature, c.minMeanCurvature, c.maxGaussianCurvature,
|
||||
c.minGaussianCurvature};
|
||||
}
|
||||
|
||||
size_t manifold_curvature_vert_length(ManifoldCurvature *curv) {
|
||||
return from_c(curv)->vertMeanCurvature.size();
|
||||
}
|
||||
|
||||
float *manifold_curvature_vert_mean(void *mem, ManifoldCurvature *curv) {
|
||||
return copy_data(mem, from_c(curv)->vertMeanCurvature);
|
||||
}
|
||||
|
||||
float *manifold_curvature_vert_gaussian(void *mem, ManifoldCurvature *curv) {
|
||||
return copy_data(mem, from_c(curv)->vertGaussianCurvature);
|
||||
}
|
||||
|
||||
// Static Quality Globals
|
||||
void manifold_set_min_circular_angle(float degrees) {
|
||||
Manifold::SetMinCircularAngle(degrees);
|
||||
}
|
||||
|
||||
void manifold_set_min_circular_edge_length(float length) {
|
||||
Manifold::SetMinCircularEdgeLength(length);
|
||||
}
|
||||
|
||||
void manifold_set_circular_segments(int number) {
|
||||
Manifold::SetCircularSegments(number);
|
||||
}
|
||||
|
||||
int manifold_get_circular_segments(float radius) {
|
||||
return Manifold::GetCircularSegments(radius);
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
|
||||
size_t manifold_polygons_size() { return sizeof(Polygons); }
|
||||
size_t manifold_manifold_size() { return sizeof(Manifold); }
|
||||
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
|
||||
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
|
||||
size_t manifold_box_size() { return sizeof(Box); }
|
||||
size_t manifold_curvature_size() { return sizeof(Curvature); }
|
||||
size_t manifold_components_size() { return sizeof(Components); }
|
||||
|
||||
// pointer free + destruction
|
||||
void manifold_delete_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
delete from_c(p);
|
||||
}
|
||||
void manifold_delete_polygons(ManifoldPolygons *p) { delete from_c(p); }
|
||||
void manifold_delete_manifold(ManifoldManifold *m) { delete from_c(m); }
|
||||
void manifold_delete_meshgl(ManifoldMeshGL *m) { delete from_c(m); }
|
||||
void manifold_delete_box(ManifoldBox *b) { delete from_c(b); }
|
||||
void manifold_delete_curvature(ManifoldCurvature *c) { delete from_c(c); }
|
||||
void manifold_delete_components(ManifoldComponents *c) { delete from_c(c); }
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_simple_polygon(ManifoldSimplePolygon *p) {
|
||||
from_c(p)->~SimplePolygon();
|
||||
}
|
||||
void manifold_destruct_polygons(ManifoldPolygons *p) { from_c(p)->~Polygons(); }
|
||||
void manifold_destruct_manifold(ManifoldManifold *m) { from_c(m)->~Manifold(); }
|
||||
void manifold_destruct_meshgl(ManifoldMeshGL *m) { from_c(m)->~MeshGL(); }
|
||||
void manifold_destruct_box(ManifoldBox *b) { from_c(b)->~Box(); }
|
||||
void manifold_destruct_curvature(ManifoldCurvature *c) {
|
||||
from_c(c)->~Curvature();
|
||||
}
|
||||
void manifold_destruct_components(ManifoldComponents *c) {
|
||||
from_c(c)->~Components();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "meshIO.h"
|
||||
|
||||
#include <conv.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldMaterial *manifold_material(void *mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert) {
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions *m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -18,7 +18,7 @@ add_subdirectory(third_party)
|
||||
pybind11_add_module(pymanifold pymanifold.cpp)
|
||||
target_link_libraries(pymanifold PRIVATE manifold)
|
||||
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_14)
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_17)
|
||||
target_include_directories(pymanifold
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from pymanifold import Manifold
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
|
||||
return obj
|
||||
@@ -40,8 +40,16 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
for (Manifold &manifold : manifolds) result += manifold;
|
||||
return 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.")
|
||||
@@ -196,53 +204,53 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def("to_mesh", &Manifold::GetMesh)
|
||||
.def_static("smooth", Manifold::Smooth,
|
||||
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_static(
|
||||
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
|
||||
py::arg("mesh"))
|
||||
@@ -343,11 +351,10 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
.def(py::init([](std::vector<std::vector<Float2>> &polygons) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
std::vector<PolyVert> vertices(polygons[i].size());
|
||||
std::vector<glm::vec2> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {
|
||||
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
|
||||
j};
|
||||
vertices[j] = {std::get<0>(polygons[i][j]),
|
||||
std::get<1>(polygons[i][j])};
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@ set_source_files_properties(bindings.cpp OBJECT_DEPENDS
|
||||
target_link_libraries(manifoldjs manifold sdf)
|
||||
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS} -fexceptions)
|
||||
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1)
|
||||
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1 -sEXPORT_ES6=1)
|
||||
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_14)
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -34,5 +34,5 @@ add_custom_command(
|
||||
TARGET manifoldjs POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold-*.d.ts
|
||||
${CMAKE_CURRENT_BINARY_DIR}/examples/)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
#include <manifold.h>
|
||||
@@ -29,65 +31,100 @@ Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }
|
||||
Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }
|
||||
|
||||
Manifold UnionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::ADD);
|
||||
;
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::Add);
|
||||
}
|
||||
|
||||
Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::SUBTRACT);
|
||||
;
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::Subtract);
|
||||
}
|
||||
|
||||
Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::INTERSECT);
|
||||
;
|
||||
return Manifold::BatchBoolean(manifolds, Manifold::OpType::Intersect);
|
||||
}
|
||||
|
||||
std::vector<SimplePolygon> ToPolygon(
|
||||
std::vector<std::vector<glm::vec2>>& polygons) {
|
||||
std::vector<SimplePolygon> simplePolygons(polygons.size());
|
||||
for (int i = 0; i < polygons.size(); i++) {
|
||||
std::vector<PolyVert> vertices(polygons[i].size());
|
||||
std::vector<glm::vec2> vertices(polygons[i].size());
|
||||
for (int j = 0; j < polygons[i].size(); j++) {
|
||||
vertices[j] = {polygons[i][j], j};
|
||||
vertices[j] = polygons[i][j];
|
||||
}
|
||||
simplePolygons[i] = {vertices};
|
||||
}
|
||||
return simplePolygons;
|
||||
}
|
||||
|
||||
val GetMeshJS(const Manifold& manifold) {
|
||||
MeshGL mesh = manifold.GetMeshGL();
|
||||
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
|
||||
MeshGL mesh = manifold.GetMeshGL(normalIdx);
|
||||
val meshJS = val::object();
|
||||
|
||||
meshJS.set("numProp", mesh.numProp);
|
||||
meshJS.set("triVerts",
|
||||
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertPos",
|
||||
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
|
||||
meshJS.set("vertProperties",
|
||||
val(typed_memory_view(mesh.vertProperties.size(),
|
||||
mesh.vertProperties.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
|
||||
mesh.mergeFromVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
|
||||
mesh.mergeToVert.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runIndex",
|
||||
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
|
||||
mesh.runOriginalID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("faceID",
|
||||
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
|
||||
mesh.vertNormal.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("halfedgeTangent",
|
||||
val(typed_memory_view(mesh.halfedgeTangent.size(),
|
||||
mesh.halfedgeTangent.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
|
||||
mesh.runTransform.data()))
|
||||
.call<val>("slice"));
|
||||
|
||||
return meshJS;
|
||||
}
|
||||
|
||||
MeshGL MeshJS2GL(const val& mesh) {
|
||||
MeshGL out;
|
||||
out.numProp = mesh["numProp"].as<int>();
|
||||
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
|
||||
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
|
||||
if (mesh["vertNormal"] != val::undefined()) {
|
||||
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
|
||||
out.vertProperties =
|
||||
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
|
||||
if (mesh["mergeFromVert"] != val::undefined()) {
|
||||
out.mergeFromVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
|
||||
}
|
||||
if (mesh["mergeToVert"] != val::undefined()) {
|
||||
out.mergeToVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
|
||||
}
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["runOriginalID"] != val::undefined()) {
|
||||
out.runOriginalID =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
|
||||
}
|
||||
if (mesh["runTransform"] != val::undefined()) {
|
||||
out.runTransform =
|
||||
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -109,10 +146,11 @@ Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
|
||||
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
|
||||
}
|
||||
|
||||
Manifold Transform(Manifold& manifold, std::vector<float>& mat) {
|
||||
Manifold Transform(Manifold& manifold, const val& mat) {
|
||||
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
|
||||
glm::mat4x3 matrix;
|
||||
for (int i = 0; i < 4; i++)
|
||||
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
|
||||
for (const int col : {0, 1, 2, 3})
|
||||
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
|
||||
return manifold.Transform(matrix);
|
||||
}
|
||||
|
||||
@@ -151,17 +189,19 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("w", &glm::vec4::w);
|
||||
|
||||
enum_<Manifold::Error>("status")
|
||||
.value("NO_ERROR", Manifold::Error::NO_ERROR)
|
||||
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
|
||||
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
|
||||
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
|
||||
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
|
||||
.value("PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
|
||||
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
.value("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_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
@@ -173,16 +213,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
value_object<BaryRef>("baryRef")
|
||||
.field("meshID", &BaryRef::meshID)
|
||||
.field("originalID", &BaryRef::originalID)
|
||||
.field("tri", &BaryRef::tri)
|
||||
.field("vertBary", &BaryRef::vertBary);
|
||||
|
||||
value_object<MeshRelation>("meshRelation")
|
||||
.field("barycentric", &MeshRelation::barycentric)
|
||||
.field("triBary", &MeshRelation::triBary);
|
||||
|
||||
value_object<Curvature>("curvature")
|
||||
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
|
||||
.field("minMeanCurvature", &Curvature::minMeanCurvature)
|
||||
@@ -198,7 +228,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<BaryRef>("Vector_baryRef");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
@@ -213,7 +242,8 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
.function("_Rotate", &Manifold::Rotate)
|
||||
.function("_Scale", &Manifold::Scale)
|
||||
.function("_Decompose", &Manifold::Decompose)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
@@ -225,8 +255,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("_getCurvature", &Manifold::GetCurvature)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal)
|
||||
.function("_getMeshRelation", &Manifold::GetMeshRelation);
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
function("_Cube", &Manifold::Cube);
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
@@ -246,4 +275,5 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
|
||||
function("setCircularSegments", &Manifold::SetCircularSegments);
|
||||
function("getCircularSegments", &Manifold::GetCircularSegments);
|
||||
function("reserveIDs", &Manifold::ReserveIDs);
|
||||
}
|
||||
|
||||
@@ -75,19 +75,6 @@ Module.setup = function() {
|
||||
return out;
|
||||
};
|
||||
|
||||
// note that the matrix is using column major (same as glm)
|
||||
Module.Manifold.prototype.transform = function(mat) {
|
||||
console.assert(mat.length == 4, 'expects a 3x4 matrix');
|
||||
const vec = new Module.Vector_f32();
|
||||
for (let col of mat) {
|
||||
console.assert(col.length == 3, 'expects a 3x4 matrix');
|
||||
for (let x of col) mat.push_back(x);
|
||||
}
|
||||
const result = this._Transform(vec);
|
||||
vec.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.translate = function(...vec) {
|
||||
return this._Translate(vararg2vec(vec));
|
||||
};
|
||||
@@ -123,15 +110,27 @@ Module.setup = function() {
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
triVerts = new Uint32Array(),
|
||||
vertPos = new Float32Array(),
|
||||
vertNormal,
|
||||
halfedgeTangent
|
||||
vertProperties = new Float32Array(),
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
runOriginalID,
|
||||
faceID,
|
||||
halfedgeTangent,
|
||||
runTransform
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
this.vertPos = vertPos;
|
||||
this.vertNormal = vertNormal;
|
||||
this.vertProperties = vertProperties;
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.runOriginalID = runOriginalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
this.runTransform = runTransform;
|
||||
}
|
||||
|
||||
get numTri() {
|
||||
@@ -139,7 +138,11 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertPos.length / 3;
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
get numRun() {
|
||||
return this.runOriginalID.length;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
@@ -147,43 +150,36 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
|
||||
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
|
||||
}
|
||||
|
||||
normal(vert) {
|
||||
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(
|
||||
numProp * vert + 3, numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
|
||||
}
|
||||
|
||||
transform(run) {
|
||||
const mat4 = new Array(16);
|
||||
for (const col of [0, 1, 2, 3]) {
|
||||
for (const row of [0, 1, 2]) {
|
||||
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
|
||||
}
|
||||
}
|
||||
mat4[15] = 1;
|
||||
return mat4;
|
||||
}
|
||||
}
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function() {
|
||||
return new Mesh(this._GetMeshJS());
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getMeshRelation = function() {
|
||||
const result = this._getMeshRelation();
|
||||
const oldBarycentric = result.barycentric;
|
||||
const oldTriBary = result.triBary;
|
||||
const conversion1 = v => [v.x, v.y, v.z];
|
||||
const conversion2 = v => [v[0], v[1], v[2]];
|
||||
const conversion3 = v => {
|
||||
return {
|
||||
meshID: v.meshID,
|
||||
originalID: v.originalID,
|
||||
tri: v.tri,
|
||||
vertBary: conversion2(v.vertBary)
|
||||
};
|
||||
};
|
||||
result.barycentric = fromVec(oldBarycentric, conversion1);
|
||||
result.triBary = fromVec(oldTriBary, conversion3);
|
||||
oldBarycentric.delete();
|
||||
oldTriBary.delete();
|
||||
return result;
|
||||
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
|
||||
if (normalIdx instanceof Array)
|
||||
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
|
||||
return new Mesh(this._GetMeshJS(normalIdx));
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
@@ -197,23 +193,35 @@ Module.setup = function() {
|
||||
Module.ManifoldError = function ManifoldError(code, ...args) {
|
||||
let message = 'Unknown error';
|
||||
switch (code) {
|
||||
case Module.status.NON_FINITE_VERTEX.value:
|
||||
case Module.status.NonFiniteVertex.value:
|
||||
message = 'Non-finite vertex';
|
||||
break;
|
||||
case Module.status.NOT_MANIFOLD.value:
|
||||
case Module.status.NotManifold.value:
|
||||
message = 'Not manifold';
|
||||
break;
|
||||
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
|
||||
case Module.status.VertexOutOfBounds.value:
|
||||
message = 'Vertex index out of bounds';
|
||||
break;
|
||||
case Module.status.PROPERTIES_WRONG_LENGTH.value:
|
||||
case Module.status.PropertiesWrongLength.value:
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Tri properties have wrong length';
|
||||
case Module.status.MissingPositionProperties.value:
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
|
||||
message = 'Tri properties out of bounds';
|
||||
case Module.status.MergeVectorsDifferentLengths.value:
|
||||
message = 'Merge vectors have different lengths';
|
||||
break;
|
||||
case Module.status.MergeIndexOutOfBounds.value:
|
||||
message = 'Merge index out of bounds';
|
||||
break;
|
||||
case Module.status.TransformWrongLength.value:
|
||||
message = 'Transform vector has wrong length';
|
||||
break;
|
||||
case Module.status.RunIndexWrongLength.value:
|
||||
message = 'Run index vector has wrong length';
|
||||
break;
|
||||
case Module.status.FaceIDWrongLength.value:
|
||||
message = 'Face ID vector has wrong length';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -324,7 +332,7 @@ Module.setup = function() {
|
||||
const result = Module['_' + name + 'N'](v);
|
||||
v.delete();
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Module.union = batchbool('union');
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
manifold*
|
||||
@@ -128,7 +128,7 @@ model-viewer {
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.margin {
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
@@ -186,6 +186,16 @@ model-viewer {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.edit {
|
||||
background-image: url(icons/pencil.png);
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.trash {
|
||||
background-image: url(icons/trash.png);
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {examples} from './examples.js';
|
||||
const exampleFunctions = examples.functionBodies;
|
||||
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.register('service-worker.js');
|
||||
}
|
||||
|
||||
let editor = undefined;
|
||||
|
||||
// File UI ------------------------------------------------------------
|
||||
@@ -115,8 +120,7 @@ function uniqueName(name) {
|
||||
function addEdit(button) {
|
||||
const label = button.firstChild;
|
||||
const edit = addIcon(button);
|
||||
edit.style.backgroundImage = 'url(icons/pencil.png)';
|
||||
edit.style.right = '30px';
|
||||
edit.classList.add('edit');
|
||||
|
||||
edit.onclick = function(event) {
|
||||
event.stopPropagation();
|
||||
@@ -156,8 +160,7 @@ function addEdit(button) {
|
||||
};
|
||||
|
||||
const trash = addIcon(button);
|
||||
trash.style.backgroundImage = 'url(icons/trash.png)';
|
||||
trash.style.right = '0px';
|
||||
trash.classList.add('trash');
|
||||
let lastClick = 0;
|
||||
|
||||
trash.onclick = function(event) {
|
||||
@@ -210,14 +213,52 @@ function initializeRun() {
|
||||
|
||||
// Editor ------------------------------------------------------------
|
||||
let tsWorker = undefined;
|
||||
|
||||
async function getManifoldDTS() {
|
||||
const global = await fetch('manifold-global-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
const encapsulated = await fetch('manifold-encapsulated-types.d.ts')
|
||||
.then(response => response.text());
|
||||
|
||||
return `
|
||||
${global.replaceAll('export', '')}
|
||||
${encapsulated.replace(/^import.*$/gm, '').replaceAll('export', 'declare')}
|
||||
declare interface ManifoldStatic {
|
||||
cube: typeof cube;
|
||||
cylinder: typeof cylinder;
|
||||
sphere: typeof sphere;
|
||||
smooth: typeof smooth;
|
||||
tetrahedron: typeof tetrahedron;
|
||||
extrude: typeof extrude;
|
||||
revolve: typeof revolve;
|
||||
union: typeof union;
|
||||
difference: typeof difference;
|
||||
intersection: typeof intersection;
|
||||
compose: typeof compose;
|
||||
levelSet: typeof levelSet;
|
||||
setMinCircularAngle: typeof setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof setCircularSegments;
|
||||
getCircularSegments: typeof getCircularSegments;
|
||||
reserveIDs: typeof reserveIDs;
|
||||
}
|
||||
declare const module: ManifoldStatic;
|
||||
`;
|
||||
}
|
||||
|
||||
require.config({
|
||||
paths:
|
||||
{vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs'}
|
||||
});
|
||||
require(['vs/editor/editor.main'], async function() {
|
||||
const content =
|
||||
await fetch('manifold.d.ts').then(response => response.text());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
|
||||
async function addTypes(uri) {
|
||||
const content = await fetch(uri).then(response => response.text());
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
|
||||
}
|
||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||
await getManifoldDTS());
|
||||
await addTypes('editor.d.ts');
|
||||
editor = monaco.editor.create(
|
||||
document.getElementById('editor'),
|
||||
{language: 'typescript', automaticLayout: true});
|
||||
@@ -309,7 +350,7 @@ let objectURL = null;
|
||||
let manifoldWorker = null;
|
||||
|
||||
function createWorker() {
|
||||
manifoldWorker = new Worker('worker.js');
|
||||
manifoldWorker = new Worker('worker.js', {type: 'module'});
|
||||
manifoldWorker.onmessage = function(e) {
|
||||
if (e.data == null) {
|
||||
if (tsWorker != null && !manifoldInitialized) {
|
||||
|
||||
@@ -12,429 +12,423 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
(function(exports) {
|
||||
export const examples = {
|
||||
functions: {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the
|
||||
// API docs. Type e.g. "box." to see the Manifold API. Type "module." to
|
||||
// see the static API - these functions can also be used bare. Use
|
||||
// console.log() to print output (lower-right). This editor defines Z as
|
||||
// up and units of mm.
|
||||
|
||||
exports.functions = {
|
||||
Intro: function() {
|
||||
// Write code in JavaScript or TypeScript and this editor will show the API
|
||||
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
|
||||
// static API - these functions can also be used bare. Use console.log() to
|
||||
// print output (lower-right). This editor defines Z as up and units of mm.
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
|
||||
const box = cube([100, 100, 100], true);
|
||||
const ball = sphere(60, 100);
|
||||
// You must name your final output "result".
|
||||
const result = box.subtract(ball);
|
||||
// For visual debug, wrap any shape with show() and it and all of its
|
||||
// copies will be shown in transparent red, akin to # in OpenSCAD. Or try
|
||||
// only() to ghost out everything else, akin to * in OpenSCAD.
|
||||
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This app is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// To share your code with another browser/device, simply copy the text to a
|
||||
// file.
|
||||
// All changes are automatically saved and restored between sessions.
|
||||
// This PWA is purely local - there is no server communication.
|
||||
// This means it will work equally well offline once loaded.
|
||||
// Consider installing it (icon in the search bar) for easy access.
|
||||
|
||||
// See the script drop-down above ("Intro") for usage examples.
|
||||
return result;
|
||||
},
|
||||
// See the script drop-down above ("Intro") for usage examples. The
|
||||
// gl-matrix package from npm is automatically imported for convenience -
|
||||
// its API is available in the top-level glMatrix object.
|
||||
return result;
|
||||
},
|
||||
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as a
|
||||
// puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
TetrahedronPuzzle: function() {
|
||||
// A tetrahedron cut into two identical halves that can screw together as
|
||||
// a puzzle. This only outputs one of the halves. This demonstrates how
|
||||
// redundant points along a polygon can be used to make twisted extrusions
|
||||
// smoother. Based on the screw puzzle by George Hart:
|
||||
// https://www.thingiverse.com/thing:186372
|
||||
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions =
|
||||
50; // Number of divisions (both ways) in the screw surface.
|
||||
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
|
||||
const gap = 0.2; // Spacing between the two halves to allow sliding.
|
||||
const nDivisions = 50; // Divisions (both ways) in the screw surface.
|
||||
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
const scale = edgeLength / (2 * Math.sqrt(2));
|
||||
|
||||
const tet = tetrahedron().scale(scale);
|
||||
const tet = tetrahedron().scale(scale);
|
||||
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
let edge1 = union(corner, edge);
|
||||
edge1 = edge1.rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
let edge2 = edge1.rotate([0, 0, 180]);
|
||||
edge2 = union(edge2, edge1);
|
||||
edge2 =
|
||||
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
let edge4 = edge2.rotate([0, 0, 90]);
|
||||
edge4 = union(edge4, edge2);
|
||||
|
||||
let frame = edge4.translate([0, 0, -edgeLength / 2]);
|
||||
frame = union(frame, frame.rotate([180, 0, 0]));
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function. This
|
||||
// example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with smooth()
|
||||
// and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
vertPos.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triVerts.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triVerts.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
const scallop = new Mesh();
|
||||
scallop.triVerts = Uint32Array.from(triVerts);
|
||||
scallop.vertPos = Float32Array.from(vertPos);
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common factor
|
||||
// then you will get multiple separate, interwoven knots. This is an example
|
||||
// of using the warp() method, thus avoiding any direct handling of
|
||||
// triangles.
|
||||
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
const box = [];
|
||||
box.push([2, -2], [2, 2]);
|
||||
for (let i = 0; i <= nDivisions; ++i) {
|
||||
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
|
||||
}
|
||||
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
const screw = extrude(box, 2, nDivisions, 270)
|
||||
.rotate([0, 0, -45])
|
||||
.translate([0, 0, -1])
|
||||
.scale(scale);
|
||||
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
const offset = 2;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
const result = tet.intersect(screw);
|
||||
return result;
|
||||
},
|
||||
|
||||
RoundedFrame: function() {
|
||||
// Demonstrates how at 90-degree intersections, the sphere and cylinder
|
||||
// facets match up perfectly, for any choice of global resolution
|
||||
// parameters.
|
||||
|
||||
function roundedFrame(edgeLength, radius, circularSegments = 0) {
|
||||
const edge = cylinder(edgeLength, radius, -1, circularSegments);
|
||||
const corner = sphere(radius, circularSegments);
|
||||
|
||||
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
|
||||
-edgeLength / 2, -edgeLength / 2, 0
|
||||
]);
|
||||
|
||||
const edge2 = union(
|
||||
union(edge1, edge1.rotate([0, 0, 180])),
|
||||
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
|
||||
|
||||
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
|
||||
0, 0, -edgeLength / 2
|
||||
]);
|
||||
|
||||
return union(edge4, edge4.rotate([180, 0, 0]));
|
||||
}
|
||||
|
||||
const point = new THREE.Vector3();
|
||||
const axis = new THREE.Vector3();
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
const func =
|
||||
(v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
point.set(Math.cos(phi), 0, Math.sin(phi))
|
||||
.multiplyScalar(threadRadius);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
point.applyAxisAngle(
|
||||
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
|
||||
point.x += minorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 1, 0), theta);
|
||||
point.x += majorRadius;
|
||||
point.applyAxisAngle(axis.set(0, 0, 1), psi);
|
||||
v[0] = point.x;
|
||||
v[1] = point.y;
|
||||
v[2] = point.z;
|
||||
}
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function.
|
||||
// This example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df =
|
||||
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
}
|
||||
knot = compose(knots);
|
||||
// Update radius
|
||||
v[0] *= r;
|
||||
v[1] *= r;
|
||||
v[2] *= r;
|
||||
};
|
||||
|
||||
const ball = sphere(1, 200);
|
||||
const heart = ball.warp(func);
|
||||
const box = heart.boundingBox();
|
||||
const result = heart.scale(100 / (box.max[0] - box.min[0]));
|
||||
return result;
|
||||
},
|
||||
|
||||
Scallop: function() {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with
|
||||
// smooth() and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const positions = [];
|
||||
const triangles = [];
|
||||
positions.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
positions.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
|
||||
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
|
||||
let halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(0, 2 + i, 2 + j);
|
||||
|
||||
halfedge = triangles.length + 1;
|
||||
sharpenedEdges.push({halfedge, smoothness});
|
||||
triangles.push(1, 2 + j, 2 + i);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
const triVerts = Uint32Array.from(triangles);
|
||||
const vertProperties = Float32Array.from(positions);
|
||||
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
TorusKnot: function() {
|
||||
// Creates a classic torus knot, defined as a string wrapping periodically
|
||||
// around the surface of an imaginary donut. If p and q have a common
|
||||
// factor then you will get multiple separate, interwoven knots. This is
|
||||
// an example of using the warp() method, thus avoiding any direct
|
||||
// handling of triangles.
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
// @param p The number of times the thread passes through the donut hole.
|
||||
// @param q The number of times the thread circles the donut.
|
||||
// @param majorRadius Radius of the interior of the imaginary donut.
|
||||
// @param minorRadius Radius of the small cross-section of the imaginary
|
||||
// donut.
|
||||
// @param threadRadius Radius of the small cross-section of the actual
|
||||
// object.
|
||||
// @param circularSegments Number of linear segments making up the
|
||||
// threadRadius circle. Default is getCircularSegments(threadRadius).
|
||||
// @param linearSegments Number of segments along the length of the knot.
|
||||
// Default makes roughly square facets.
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
function torusKnot(
|
||||
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
|
||||
linearSegments = 0) {
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
function gcd(a, b) {
|
||||
return b == 0 ? a : gcd(b, a % b);
|
||||
}
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
|
||||
[0.0, -w]
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
|
||||
}
|
||||
const kLoops = gcd(p, q);
|
||||
p /= kLoops;
|
||||
q /= kLoops;
|
||||
const n = circularSegments > 2 ? circularSegments :
|
||||
getCircularSegments(threadRadius);
|
||||
const m = linearSegments > 2 ? linearSegments :
|
||||
n * q * majorRadius / threadRadius;
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
const circle = [];
|
||||
const dPhi = 2 * 3.14159 / n;
|
||||
const offset = 2;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
|
||||
}
|
||||
|
||||
const hole = compose(holes);
|
||||
const func = (v) => {
|
||||
const psi = q * Math.atan2(v[0], v[1]);
|
||||
const theta = psi * p / q;
|
||||
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
const phi = Math.atan2(x1 - offset, v[2]);
|
||||
vec3.set(
|
||||
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
|
||||
const center = vec3.fromValues(0, 0, 0);
|
||||
const r = majorRadius + minorRadius * Math.cos(theta);
|
||||
vec3.rotateX(v, v, center, -Math.atan2(p * minorRadius, q * r));
|
||||
v[0] += minorRadius;
|
||||
vec3.rotateY(v, v, center, theta);
|
||||
v[0] += majorRadius;
|
||||
vec3.rotateZ(v, v, center, psi);
|
||||
};
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
let knot = revolve(circle, m).warp(func);
|
||||
|
||||
if (kLoops > 1) {
|
||||
const knots = [];
|
||||
for (let k = 0; k < kLoops; ++k) {
|
||||
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
|
||||
}
|
||||
knot = compose(knots);
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
vec2.fromValues(-w, -w), vec2.fromValues(-w, 0.0),
|
||||
vec2.fromValues(-w, w), vec2.fromValues(0.0, w),
|
||||
vec2.fromValues(w, w), vec2.fromValues(w, 0.0),
|
||||
vec2.fromValues(w, -w), vec2.fromValues(0.0, -w)
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(
|
||||
holes, hole, w, vec2.add(offset, position, offset), depth + 1,
|
||||
maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
const {vec2} = glMatrix;
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor = extrude(circle, width, nDivision, 180)
|
||||
.scale([1, 0.5, 1])
|
||||
.translate([0, radius, 0]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const o = vec2.fromValues(0, 0);
|
||||
const p0 = vec2.fromValues(outerRadius, 0);
|
||||
const p1 = vec2.fromValues(innerRadius, -cut);
|
||||
const p2 = vec2.fromValues(innerRadius, cut);
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p1, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p2, o, dPhiRad * i));
|
||||
stretch.push(vec2.rotate([0, 0], p0, o, dPhiRad * i));
|
||||
}
|
||||
b = intersection(extrude(stretch, width), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the
|
||||
// use of a Signed Distance Function (SDF) to create smooth, complex
|
||||
// manifolds.
|
||||
const {vec3} = glMatrix;
|
||||
|
||||
const size = 20;
|
||||
const n = 20;
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: vec3.fromValues(-period, -period, -period),
|
||||
max: vec3.fromValues(period, period, period)
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4));
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result =
|
||||
result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
function rotate(v, theta) {
|
||||
return [
|
||||
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
|
||||
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
|
||||
];
|
||||
}
|
||||
|
||||
let b = cylinder(width, radius + twistRadius / 2);
|
||||
const circle = [];
|
||||
const dPhiDeg = 180 / nDivision;
|
||||
for (let i = 0; i < 2 * nDivision; ++i) {
|
||||
circle.push([
|
||||
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
|
||||
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
|
||||
]);
|
||||
}
|
||||
let decor =
|
||||
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
|
||||
0, radius, 0
|
||||
]);
|
||||
for (let i = 0; i < nDecor; i++)
|
||||
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
|
||||
const stretch = [];
|
||||
const dPhiRad = 2 * Math.PI / nCut;
|
||||
|
||||
const p0 = [outerRadius, 0];
|
||||
const p1 = [innerRadius, -cut];
|
||||
const p2 = [innerRadius, cut];
|
||||
for (let i = 0; i < nCut; ++i) {
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
stretch.push(rotate(p1, dPhiRad * i));
|
||||
stretch.push(rotate(p2, dPhiRad * i));
|
||||
stretch.push(rotate(p0, dPhiRad * i));
|
||||
}
|
||||
b = intersection(extrude(stretch, width), b);
|
||||
return b;
|
||||
}
|
||||
|
||||
function stretchyBracelet(
|
||||
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
|
||||
nCut = 27, nDivision = 30) {
|
||||
const twistRadius = Math.PI * radius / nDecor;
|
||||
const decorRadius = twistRadius * 1.5;
|
||||
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
|
||||
const innerRadius = outerRadius - height;
|
||||
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
|
||||
const adjThickness = 0.5 * thickness * height / cut;
|
||||
|
||||
return difference(
|
||||
base(
|
||||
width, radius, decorRadius, twistRadius, nDecor,
|
||||
innerRadius + thickness, outerRadius + adjThickness,
|
||||
cut - adjThickness, nCut, nDivision),
|
||||
base(
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
|
||||
GyroidModule: function() {
|
||||
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
|
||||
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
|
||||
|
||||
const size = 20;
|
||||
const n = 20;
|
||||
const pi = 3.14159;
|
||||
|
||||
function gyroid(p) {
|
||||
const x = p[0] - pi / 4;
|
||||
const y = p[1] - pi / 4;
|
||||
const z = p[2] - pi / 4;
|
||||
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
|
||||
Math.cos(z) * Math.sin(x);
|
||||
}
|
||||
|
||||
function gyroidOffset(level) {
|
||||
const period = 2 * pi;
|
||||
const box = {
|
||||
min: [-period, -period, -period],
|
||||
max: [period, period, period]
|
||||
};
|
||||
return levelSet(gyroid, box, period / n, level).scale(size / period);
|
||||
};
|
||||
|
||||
function rhombicDodecahedron() {
|
||||
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
|
||||
const result =
|
||||
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
|
||||
return result.intersect(box.rotate([0, 0, 45]));
|
||||
}
|
||||
|
||||
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
|
||||
result = result.subtract(gyroidOffset(0.4));
|
||||
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
|
||||
return result;
|
||||
}
|
||||
functionBodies: new Map()
|
||||
};
|
||||
|
||||
exports.functionBodies = new Map();
|
||||
|
||||
for (const [func, code] of Object.entries(exports.functions)) {
|
||||
for (const [func, code] of Object.entries(examples.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first four leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
const name =
|
||||
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
|
||||
exports.functionBodies.set(name, body);
|
||||
};
|
||||
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
|
||||
examples.functionBodies.set(name, body);
|
||||
};
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 952 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 271 KiB |
@@ -5,15 +5,36 @@
|
||||
<title>ManifoldCAD</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
<link type="text/css" href="editor.css" rel="stylesheet" />
|
||||
<meta name="description"
|
||||
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
|
||||
<meta name="theme-color" content="#A288B3">
|
||||
<meta property="og:title" content="ManifoldCAD">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://manifoldcad.org/" />
|
||||
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
|
||||
<meta property="og:image:width" content="850">
|
||||
<meta property="og:image:height" content="850">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link type="text/css" href="editor.css" rel="stylesheet">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebApplication",
|
||||
"name": "ManifoldCAD",
|
||||
"applicationCategory": "DesignApplication",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="background-color: rgb(162 136 179);">
|
||||
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
|
||||
</p>
|
||||
<div class="container header" style="background-color: #A288B3;">
|
||||
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
|
||||
<div class="margin" style="position: relative;">
|
||||
<button type="button" id="file" class="blue"><b class="uparrow"></b> <span
|
||||
id="current">Intro</span></button>
|
||||
@@ -30,9 +51,7 @@
|
||||
<model-viewer camera-controls min-camera-orbit="auto 0deg auto" max-camera-orbit="auto 180deg auto"
|
||||
shadow-intensity="1" interaction-prompt="none" alt="Editor 3D output">
|
||||
<div class="center" slot="poster">
|
||||
<p id="poster" style="text-align: center; line-height: 36px;">
|
||||
Loading...
|
||||
</p>
|
||||
<p id="poster" style="text-align: center; line-height: 36px;"> Loading... </p>
|
||||
</div>
|
||||
</model-viewer>
|
||||
<div id="console"></div>
|
||||
@@ -40,17 +59,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
|
||||
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
|
||||
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<script src="examples.js"></script>
|
||||
<script src="editor.js"></script>
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<script type="module" src="editor.js"></script>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "ManifoldCAD",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icons/mengerSponge192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/icons/mengerSponge512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"background_color": "#FFFFFF",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"scope": "/",
|
||||
"theme_color": "#A288B3",
|
||||
"description": "Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.",
|
||||
"categories": [
|
||||
"design",
|
||||
"developer",
|
||||
"education",
|
||||
"graphics",
|
||||
"productivity"
|
||||
],
|
||||
"screenshots": [
|
||||
{
|
||||
"src": "/icons/manifoldCAD.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
},
|
||||
{
|
||||
"src": "/icons/manifoldCADonly.png",
|
||||
"type": "image/png",
|
||||
"sizes": "2906x1980"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -37,9 +36,10 @@
|
||||
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
href="https://modelviewer.dev/"><model-viewer></a>
|
||||
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
|
||||
</p>
|
||||
href="https://modelviewer.dev/"><model-viewer></a> via <a
|
||||
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
|
||||
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
|
||||
three.js example is <a href="three.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
@@ -47,80 +47,220 @@
|
||||
</select>
|
||||
<model-viewer camera-controls shadow-intensity="1" alt="Output of mesh Boolean operation"></model-viewer>
|
||||
</body>
|
||||
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
|
||||
<script type="module">
|
||||
import { Accessor, Document, WebIO } from 'https://cdn.skypack.dev/pin/@gltf-transform/core@v3.0.0-SfbIFhNPTRdr1UE2VSan/mode=imports,min/optimized/@gltf-transform/core.js';
|
||||
import Module from '../manifold.js';
|
||||
|
||||
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
|
||||
<script src="https://unpkg.com/three@0.144.0/build/three.js"></script>
|
||||
<script src="https://unpkg.com/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="https://unpkg.com/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
|
||||
color: 'yellow',
|
||||
metalness: 1,
|
||||
roughness: 0.2
|
||||
}));
|
||||
const io = new WebIO();
|
||||
const doc = new Document();
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const buffer = doc.createBuffer();
|
||||
const position = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const uv = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC2);
|
||||
const moonIndices = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const spaceIndices = doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const moonTexture = doc.createTexture('moon');
|
||||
const spaceTexture = doc.createTexture('space');
|
||||
const moonMaterial = doc.createMaterial('moon').setBaseColorTexture(moonTexture);
|
||||
const spaceMaterial = doc.createMaterial('moon').setBaseColorTexture(spaceTexture);
|
||||
const moonPrimitive = doc.createPrimitive().setMaterial(moonMaterial)
|
||||
.setIndices(moonIndices).setAttribute('POSITION', position).setAttribute('TEXCOORD_0', uv);
|
||||
const spacePrimitive = doc.createPrimitive().setMaterial(spaceMaterial)
|
||||
.setIndices(spaceIndices).setAttribute('POSITION', position).setAttribute('TEXCOORD_0', uv);
|
||||
const mesh = doc.createMesh('result').addPrimitive(moonPrimitive).addPrimitive(spacePrimitive);
|
||||
const node = doc.createNode().setMesh(mesh);
|
||||
const scene = doc.createScene().addChild(node);
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
push2MV(mesh);
|
||||
};
|
||||
async function loadTexture(texture, uri) {
|
||||
const response = await fetch(uri);
|
||||
const blob = await response.blob();
|
||||
texture.setMimeType(blob.type);
|
||||
texture.setImage(new Uint8Array(await blob.arrayBuffer()));
|
||||
}
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
async function loadTextures() {
|
||||
const moonLoads = loadTexture(moonTexture, './moonAltitude.jpg');
|
||||
const spaceLoads = loadTexture(spaceTexture, './hubbleDeepField.jpg');
|
||||
await Promise.all([moonLoads, spaceLoads]);
|
||||
}
|
||||
|
||||
csg('difference');
|
||||
});
|
||||
const texturesLoad = loadTextures();
|
||||
const materialMap = new Map();
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
const firstID = wasm.reserveIDs(2);
|
||||
const space = cubeUV(firstID).scale(50);
|
||||
const moon = sphereUV(firstID + 1).scale(60);
|
||||
materialMap.set(firstID, spaceIndices);
|
||||
materialMap.set(firstID + 1, moonIndices);
|
||||
|
||||
const csg = function (operation) {
|
||||
push2MV(wasm[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
|
||||
function push2MV(mesh) {
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
},
|
||||
() => console.log('GLTF export failed!'),
|
||||
{ binary: true }
|
||||
);
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
for (const [i, id] of mesh.runOriginalID.entries()) {
|
||||
const indices = materialMap.get(id);
|
||||
indices.setArray(mesh.triVerts.subarray(mesh.runIndex[i], mesh.runIndex[i + 1]));
|
||||
}
|
||||
|
||||
const numVert = mesh.numVert;
|
||||
const numProp = mesh.numProp;
|
||||
const posArray = new Float32Array(3 * numVert);
|
||||
const uvArray = new Float32Array(2 * numVert);
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
posArray[3 * i] = mesh.vertProperties[numProp * i];
|
||||
posArray[3 * i + 1] = mesh.vertProperties[numProp * i + 1];
|
||||
posArray[3 * i + 2] = mesh.vertProperties[numProp * i + 2];
|
||||
uvArray[2 * i] = mesh.vertProperties[numProp * i + 3];
|
||||
uvArray[2 * i + 1] = mesh.vertProperties[numProp * i + 4];
|
||||
}
|
||||
position.setArray(posArray);
|
||||
uv.setArray(uvArray);
|
||||
|
||||
await texturesLoad;
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
}
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
function cubeUV(id) {
|
||||
const numProp = 5;
|
||||
const numTri = 12;
|
||||
const numVert = 8;
|
||||
const numPropVert = 24;
|
||||
const vertPos =
|
||||
[-1, -1, -1,
|
||||
1, -1, -1,
|
||||
1, 1, -1,
|
||||
-1, 1, -1,
|
||||
-1, -1, 1,
|
||||
1, -1, 1,
|
||||
1, 1, 1,
|
||||
-1, 1, 1];
|
||||
const uvs = [0, 0, 0, 1, 1, 1, 1, 0];
|
||||
const triVerts = new Uint32Array(
|
||||
[0, 3, 2, 0, 2, 1,
|
||||
4, 5, 6, 4, 6, 7,
|
||||
0, 1, 5, 0, 5, 4,
|
||||
1, 2, 6, 1, 6, 5,
|
||||
2, 3, 7, 2, 7, 6,
|
||||
3, 0, 4, 3, 4, 7]);
|
||||
const v4 = [0, 1, 2, 5];
|
||||
const v6 = [0, 1, 2, 0, 2, 3];
|
||||
const vertProperties = new Float32Array(numProp * numPropVert);
|
||||
const mergeFromVert = new Uint32Array(numPropVert - numVert);
|
||||
const mergeToVert = new Uint32Array(numPropVert - numVert);
|
||||
const runOriginalID = new Uint32Array(1);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
|
||||
runOriginalID[0] = id;
|
||||
const vert2vert = [-1, -1, -1, -1, -1, -1, -1, -1];
|
||||
let idx = 0;
|
||||
for (let face = 0; face < 6; ++face) {
|
||||
for (let v = 0; v < 4; ++v) {
|
||||
const vIdx = 4 * face + v;
|
||||
const vOld = triVerts[6 * face + v4[v]];
|
||||
if (vert2vert[vOld] === -1) {
|
||||
vert2vert[vOld] = vIdx;
|
||||
} else {
|
||||
mergeFromVert[idx] = vIdx;
|
||||
mergeToVert[idx] = vert2vert[vOld];
|
||||
++idx;
|
||||
}
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
vertProperties[numProp * vIdx + i] = vertPos[3 * vOld + i];
|
||||
}
|
||||
vertProperties[numProp * vIdx + 3] = uvs[2 * v];
|
||||
vertProperties[numProp * vIdx + 4] = uvs[2 * v + 1];
|
||||
}
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
triVerts[6 * face + i] = 4 * face + v6[i];
|
||||
}
|
||||
}
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
function sphereUV(id) {
|
||||
const n = 100;
|
||||
const tol = 1 / (2 * n);
|
||||
const mesh = wasm.sphere(1, n).getMesh();
|
||||
const nMerge = n / 2 - 1;
|
||||
const numVert = mesh.numVert;
|
||||
const numTri = mesh.numTri;
|
||||
const numPropIn = mesh.numProp;
|
||||
const numProp = 5;
|
||||
const triVerts = new Uint32Array(3 * numTri);
|
||||
const vertProperties = new Float32Array(numProp * (numVert + nMerge));
|
||||
const mergeFromVert = new Uint32Array(nMerge);
|
||||
const mergeToVert = new Uint32Array(nMerge);
|
||||
const runOriginalID = new Uint32Array(1);
|
||||
const meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert, runOriginalID });
|
||||
runOriginalID[0] = id;
|
||||
const vert2vert = new Uint32Array(numVert);
|
||||
let idx = 0;
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
const x = mesh.vertProperties[numPropIn * i];
|
||||
const y = mesh.vertProperties[numPropIn * i + 1];
|
||||
const z = mesh.vertProperties[numPropIn * i + 2];
|
||||
vertProperties[numProp * i] = x;
|
||||
vertProperties[numProp * i + 1] = y;
|
||||
vertProperties[numProp * i + 2] = z;
|
||||
const u = Math.atan2(y, x) / (2 * Math.PI) + 0.5;
|
||||
const v = Math.acos(z) / Math.PI;
|
||||
vertProperties[numProp * i + 4] = v;
|
||||
if (Math.abs(y) < tol && x < -tol) {// Seam
|
||||
const iNew = numVert + idx;
|
||||
mergeFromVert[idx] = iNew;
|
||||
mergeToVert[idx] = i;
|
||||
vert2vert[i] = iNew;
|
||||
vertProperties[numProp * iNew] = x;
|
||||
vertProperties[numProp * iNew + 1] = y;
|
||||
vertProperties[numProp * iNew + 2] = z;
|
||||
vertProperties[numProp * iNew + 3] = 1;
|
||||
vertProperties[numProp * iNew + 4] = v;
|
||||
vertProperties[numProp * i + 3] = 0;
|
||||
++idx;
|
||||
} else {
|
||||
vert2vert[i] = i;
|
||||
vertProperties[numProp * i + 3] = u;
|
||||
}
|
||||
}
|
||||
for (let tri = 0; tri < numTri; ++tri) {
|
||||
const v0 = mesh.triVerts[3 * tri];
|
||||
const v1 = mesh.triVerts[3 * tri + 1];
|
||||
const v2 = mesh.triVerts[3 * tri + 2];
|
||||
if ((vert2vert[v0] !== v0 || vert2vert[v1] !== v1 || vert2vert[v2] !== v2) &&
|
||||
(vertProperties[numProp * v0 + 1] > tol || vertProperties[numProp * v1 + 1] > tol ||
|
||||
vertProperties[numProp * v2 + 1] > tol)) {
|
||||
triVerts[3 * tri] = vert2vert[v0];
|
||||
triVerts[3 * tri + 1] = vert2vert[v1];
|
||||
triVerts[3 * tri + 2] = vert2vert[v2];
|
||||
} else {
|
||||
triVerts[3 * tri] = v0;
|
||||
triVerts[3 * tri + 1] = v1;
|
||||
triVerts[3 * tri + 2] = v2;
|
||||
}
|
||||
}
|
||||
return wasm.Manifold(meshUV);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 109 KiB |
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Increment version when updating CDN URLs to clean up cache.
|
||||
const cacheName = 'manifoldCAD-cache-v1';
|
||||
|
||||
self.addEventListener('activate', e => {
|
||||
e.waitUntil(caches.keys().then((keyList) => {
|
||||
return Promise.all(keyList.map((key) => {
|
||||
if (key !== cacheName) {
|
||||
return caches.delete(key);
|
||||
}
|
||||
}));
|
||||
}));
|
||||
});
|
||||
|
||||
// Serves from cache, then updates the cache in the background from the network,
|
||||
// if available. Update available on refresh.
|
||||
self.addEventListener(
|
||||
'fetch',
|
||||
e => {e.respondWith(caches.match(e.request).then(cachedResponse => {
|
||||
const networkFetch = fetch(e.request).then(response => {
|
||||
const clone = response.clone();
|
||||
caches.open(cacheName).then(cache => {
|
||||
cache.put(e.request, clone);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
return cachedResponse || networkFetch;
|
||||
}))});
|
||||
@@ -6,7 +6,6 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
@@ -37,8 +36,7 @@
|
||||
|
||||
<body>
|
||||
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>.
|
||||
</p>
|
||||
involving GLTF export and <model-viewer> can be found <a href="model-viewer.html">here</a>. </p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
<option value="intersection">Intersection</option>
|
||||
@@ -46,64 +44,67 @@
|
||||
</select><br />
|
||||
<canvas id="output"></canvas>
|
||||
</body>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module">
|
||||
import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.149.0/build/three.module.js';
|
||||
import { mergeVertices } from 'https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/utils/BufferGeometryUtils.js';
|
||||
import Module from '../manifold.js';
|
||||
|
||||
<script src="https://unpkg.com/three@0.142.0/build/three.js"></script>
|
||||
<script src="https://unpkg.com/three@0.142.0/examples/js/utils/BufferGeometryUtils.js"></script>
|
||||
<script src="manifold.js"></script>
|
||||
<script>
|
||||
let wasm;
|
||||
Module().then(function (tmp) {
|
||||
wasm = tmp;
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
wasm.setup();
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
// we have manifold module, let's do some three.js
|
||||
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
|
||||
camera.position.z = 1;
|
||||
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
const scene = new THREE.Scene();
|
||||
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
|
||||
flatShading: true
|
||||
}));
|
||||
scene.add(mesh);
|
||||
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
const manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
|
||||
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
};
|
||||
|
||||
csg('difference');
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
csg(event.target.value);
|
||||
};
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
csg('difference');
|
||||
|
||||
const output = document.querySelector("#output");
|
||||
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
|
||||
const dim = output.getBoundingClientRect();
|
||||
renderer.setSize(dim.width, dim.height);
|
||||
renderer.setAnimationLoop(function (time) {
|
||||
mesh.rotation.x = time / 2000;
|
||||
mesh.rotation.y = time / 1000;
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
return new wasm.Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
@@ -112,7 +113,7 @@
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
const simplified = mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
}
|
||||
|
||||
@@ -12,10 +12,97 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
|
||||
importScripts(
|
||||
'manifold.js', threePath + 'build/three.js',
|
||||
threePath + 'examples/js/exporters/GLTFExporter.js');
|
||||
import * as glMatrix from 'https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/+esm';
|
||||
import {Accessor, Document, Material, WebIO} from 'https://cdn.skypack.dev/pin/@gltf-transform/core@v3.0.0-SfbIFhNPTRdr1UE2VSan/mode=imports,min/optimized/@gltf-transform/core.js';
|
||||
|
||||
import Module from '../manifold.js';
|
||||
|
||||
const module = await Module();
|
||||
module.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
glMatrix.glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
// Scene setup
|
||||
const io = new WebIO();
|
||||
const doc = new Document();
|
||||
|
||||
const buffer = doc.createBuffer();
|
||||
const position =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const indices =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
const resultMaterial = doc.createMaterial()
|
||||
.setBaseColorFactor([1, 1, 0, 1])
|
||||
.setMetallicFactor(1)
|
||||
.setRoughnessFactor(0.2);
|
||||
const resultPrimitive = doc.createPrimitive()
|
||||
.setMaterial(resultMaterial)
|
||||
.setIndices(indices)
|
||||
.setAttribute('POSITION', position);
|
||||
const mesh = doc.createMesh().addPrimitive(resultPrimitive);
|
||||
const node = doc.createNode('result').setMesh(mesh);
|
||||
const scene = doc.createScene().addChild(node);
|
||||
|
||||
// Debug setup to show source meshes
|
||||
const shown = new Map();
|
||||
let ghost = false;
|
||||
const debugMaterial = doc.createMaterial()
|
||||
.setBaseColorFactor([1, 0, 0, 0.25])
|
||||
.setAlphaMode(Material.AlphaMode.BLEND)
|
||||
.setDoubleSided(true)
|
||||
.setMetallicFactor(0);
|
||||
const ghostMaterial = doc.createMaterial()
|
||||
.setBaseColorFactor([0.5, 0.5, 0.5, 0.25])
|
||||
.setAlphaMode(Material.AlphaMode.BLEND)
|
||||
.setDoubleSided(true)
|
||||
.setMetallicFactor(0);
|
||||
|
||||
function debug(manifold, material) {
|
||||
const position =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.VEC3);
|
||||
const indices =
|
||||
doc.createAccessor().setBuffer(buffer).setType(Accessor.Type.SCALAR);
|
||||
|
||||
const primitive = doc.createPrimitive()
|
||||
.setMaterial(material)
|
||||
.setIndices(indices)
|
||||
.setAttribute('POSITION', position);
|
||||
const mesh = doc.createMesh('debug').addPrimitive(primitive);
|
||||
|
||||
const result = manifold.asOriginal();
|
||||
outputMesh(indices, position, result.getMesh());
|
||||
|
||||
shown.set(result.originalID(), mesh);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.show = (manifold) => {
|
||||
return debug(manifold, debugMaterial);
|
||||
};
|
||||
|
||||
module.only = (manifold) => {
|
||||
ghost = true;
|
||||
return debug(manifold, resultMaterial);
|
||||
};
|
||||
|
||||
function debugCleanup() {
|
||||
for (const [id, mesh] of shown) {
|
||||
const primitive = mesh.listPrimitives()[0];
|
||||
primitive.getAttribute('POSITION').dispose();
|
||||
primitive.getIndices().dispose();
|
||||
primitive.dispose();
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
scene.traverse((node) => {
|
||||
if (node.getName() == 'debug') {
|
||||
node.dispose();
|
||||
}
|
||||
});
|
||||
shown.clear();
|
||||
ghost = false;
|
||||
}
|
||||
|
||||
// manifold member functions that returns a new manifold
|
||||
const memberFunctions = [
|
||||
@@ -25,7 +112,7 @@ const memberFunctions = [
|
||||
// top level functions that constructs a new manifold
|
||||
const constructors = [
|
||||
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'union',
|
||||
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
|
||||
'difference', 'intersection', 'compose', 'levelSet', 'smooth', 'show', 'only'
|
||||
];
|
||||
const utils = [
|
||||
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
|
||||
@@ -33,49 +120,44 @@ const utils = [
|
||||
];
|
||||
const exposedFunctions = constructors.concat(utils);
|
||||
|
||||
let wasm;
|
||||
Module().then(function(tmp) {
|
||||
wasm = tmp;
|
||||
wasm.setup();
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = wasm.Manifold.prototype[name];
|
||||
wasm.Manifold.prototype['_' + name] = originalFn;
|
||||
wasm.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
const manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = module.Manifold.prototype[name];
|
||||
module.Manifold.prototype['_' + name] = originalFn;
|
||||
module.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = module[name];
|
||||
module[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
module.cleanup = function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry.length = 0;
|
||||
};
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = wasm[name];
|
||||
wasm[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
wasm.cleanup =
|
||||
function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry = [];
|
||||
}
|
||||
|
||||
postMessage(null);
|
||||
});
|
||||
// Setup complete
|
||||
postMessage(null);
|
||||
|
||||
const oldLog = console.log;
|
||||
console.log = function(...args) {
|
||||
@@ -91,34 +173,38 @@ console.log = function(...args) {
|
||||
oldLog(...args);
|
||||
};
|
||||
|
||||
onmessage =
|
||||
(e) => {
|
||||
const content = e.data + '\nexportGLB(result);\n';
|
||||
try {
|
||||
const f = new Function(...exposedFunctions, content);
|
||||
f(...exposedFunctions.map(name => wasm[name]));
|
||||
} catch (error) {
|
||||
console.log(error.toString());
|
||||
postMessage({objectURL: null});
|
||||
} finally {
|
||||
wasm.cleanup();
|
||||
}
|
||||
}
|
||||
onmessage = async (e) => {
|
||||
const content = e.data + '\nreturn exportGLB(result);\n';
|
||||
try {
|
||||
const f = new Function(
|
||||
'exportGLB', 'glMatrix', 'module', ...exposedFunctions, content);
|
||||
await f(
|
||||
exportGLB, glMatrix, module,
|
||||
...exposedFunctions.map(name => module[name]));
|
||||
} catch (error) {
|
||||
console.log(error.toString());
|
||||
postMessage({objectURL: null});
|
||||
} finally {
|
||||
module.cleanup();
|
||||
debugCleanup();
|
||||
}
|
||||
};
|
||||
|
||||
// Export & Rendering
|
||||
// ------------------------------------------------------------
|
||||
const mesh = new THREE.Mesh(
|
||||
undefined,
|
||||
new THREE.MeshStandardMaterial(
|
||||
{color: 'yellow', metalness: 1, roughness: 0.2}));
|
||||
const rotation = new THREE.Matrix4();
|
||||
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
|
||||
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
|
||||
mesh.scale.setScalar(0.001); // mm -> m
|
||||
function outputMesh(indices, position, mesh) {
|
||||
indices.setArray(mesh.triVerts);
|
||||
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
const numVert = mesh.numVert;
|
||||
const numProp = mesh.numProp;
|
||||
const posArray = new Float32Array(3 * numVert);
|
||||
for (let i = 0; i < numVert; ++i) {
|
||||
posArray[3 * i] = mesh.vertProperties[numProp * i];
|
||||
posArray[3 * i + 1] = mesh.vertProperties[numProp * i + 1];
|
||||
posArray[3 * i + 2] = mesh.vertProperties[numProp * i + 2];
|
||||
}
|
||||
position.setArray(posArray);
|
||||
}
|
||||
|
||||
function exportGLB(manifold) {
|
||||
async function exportGLB(manifold) {
|
||||
console.log(`Triangles: ${manifold.numTri().toLocaleString()}`);
|
||||
const box = manifold.boundingBox();
|
||||
const size = [0, 0, 0];
|
||||
@@ -131,24 +217,25 @@ function exportGLB(manifold) {
|
||||
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
|
||||
(volume / 100).toLocaleString()} cm^3`);
|
||||
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(manifold.getMesh());
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], {type: 'application/octet-stream'});
|
||||
postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
},
|
||||
() => {
|
||||
console.log('glTF export failed!');
|
||||
postMessage({objectURL: null});
|
||||
},
|
||||
{binary: true});
|
||||
}
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
outputMesh(indices, position, mesh);
|
||||
|
||||
for (const [run, id] of mesh.runOriginalID.entries()) {
|
||||
const outMesh = shown.get(id);
|
||||
if (outMesh == null) {
|
||||
continue;
|
||||
}
|
||||
const node =
|
||||
doc.createNode('debug').setMesh(outMesh).setMatrix(mesh.transform(run));
|
||||
scene.addChild(node);
|
||||
}
|
||||
|
||||
resultPrimitive.setMaterial(ghost ? ghostMaterial : resultMaterial);
|
||||
|
||||
const glb = await io.writeBinary(doc);
|
||||
|
||||
const blob = new Blob([glb], {type: 'application/octet-stream'});
|
||||
postMessage({objectURL: URL.createObjectURL(blob)});
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Box, Curvature, Mat4, Polygons, Properties, SealedFloat32Array, SealedUint32Array, Smoothness, Vec2, Vec3 } from './manifold-global-types';
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
export function cube(size?: Vec3|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
export function cylinder(
|
||||
height: number, radiusLow: number, radiusHigh?: number,
|
||||
circularSegments?: number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
export function sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param mesh input Mesh.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
export function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
export function tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
export function extrude(
|
||||
crossSection: Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by revolving this cross-section
|
||||
* around its Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
export function revolve(
|
||||
crossSection: Polygons, circularSegments?: number): Manifold;
|
||||
|
||||
export function union(a: Manifold, b: Manifold): Manifold;
|
||||
export function difference(a: Manifold, b: Manifold): Manifold;
|
||||
export function intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
export function union(manifolds: Manifold[]): Manifold;
|
||||
export function difference(manifolds: Manifold[]): Manifold;
|
||||
export function intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a list of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A list of Manifolds to lazy-union together.
|
||||
*/
|
||||
export function compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance function which returns the signed distance of
|
||||
* a given point in R^3. Positive values are inside, negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
*/
|
||||
export function levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
export function setMinCircularAngle(angle: number): void;
|
||||
export function setMinCircularEdgeLength(length: number): void;
|
||||
export function setCircularSegments(segments: number): void;
|
||||
export function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
/**
|
||||
* Returns the first of n sequential new unique mesh IDs for marking sets of
|
||||
* triangles that can be looked up after further operations. Assign to
|
||||
* Mesh.runOriginalID vector.
|
||||
*/
|
||||
export function reserveIDs(count: number): number;
|
||||
|
||||
export class Manifold {
|
||||
/**
|
||||
* Convert a Mesh into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will throw an error if the
|
||||
* result is not an oriented 2-manifold. Will collapse degenerate triangles
|
||||
* and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless
|
||||
* round-trip of data from getMesh(). For multi-material input, use
|
||||
* reserveIDs() to set a unique originalID for each material, and sort the
|
||||
* materials into triangle runs.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
/**
|
||||
* Transform this Manifold in space. Stored in column-major order. This
|
||||
* operation can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transformation matrix to apply to all the vertices. The
|
||||
* last row is ignored.
|
||||
*/
|
||||
transform(m: Mat4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
* will all be coplanar (and will not be immediately collapsed) unless the
|
||||
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
|
||||
* constructor), in which case the new vertices will be moved to the
|
||||
* interpolated surface according to their barycentric coordinates.
|
||||
*
|
||||
* @param n The number of pieces to split every edge into. Must be > 1.
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
* function that warps a geometrically valid object into one which overlaps,
|
||||
* but that is not checked here, so it is up to the user to choose their
|
||||
* function with discretion.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. This approximates them for every vertex (returned
|
||||
* as vectors in the structure) and also returns their minimum and maximum
|
||||
* values.
|
||||
*/
|
||||
getCurvature(): Curvature;
|
||||
|
||||
/**
|
||||
* Returns a Mesh that is designed to easily push into a renderer, including
|
||||
* all interleaved vertex properties that may have been input. It also
|
||||
* includes relations to all the input meshes that form a part of this result
|
||||
* and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original Mesh inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output Mesh to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* Meshes must use the same channels for their normals.
|
||||
*/
|
||||
getMesh(normalIdx?: Vec3): Mesh;
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its IDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its ID that can be referenced
|
||||
* by product manifolds. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
}
|
||||
|
||||
export class Mesh {
|
||||
constructor(options: {
|
||||
numProp: number,
|
||||
vertProperties: Float32Array,
|
||||
triVerts: Uint32Array,
|
||||
mergeFromVert?: Uint32Array,
|
||||
mergeToVert?: Uint32Array,
|
||||
runIndex?: Uint32Array,
|
||||
runOriginalID?: Uint32Array,
|
||||
runTransform?: Float32Array,
|
||||
faceID?: Uint32Array,
|
||||
halfedgeTangent?: Float32Array
|
||||
});
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
mergeFromVert?: Uint32Array;
|
||||
mergeToVert?: Uint32Array;
|
||||
runIndex?: Uint32Array;
|
||||
runOriginalID?: Uint32Array;
|
||||
runTransform?: Float32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
get numRun(): number;
|
||||
verts(tri: number): SealedUint32Array<3>;
|
||||
position(vert: number): SealedFloat32Array<3>;
|
||||
extras(vert: number): Float32Array;
|
||||
tangent(halfedge: number): SealedFloat32Array<4>;
|
||||
transform(run: number): Mat4;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export interface SealedUint32Array<N extends number> extends Uint32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export interface SealedFloat32Array<N extends number> extends Float32Array {
|
||||
length: N;
|
||||
}
|
||||
|
||||
export type Vec2 = [number, number];
|
||||
export type Vec3 = [number, number, number];
|
||||
// 4x4 matrix stored in column-major order
|
||||
export type Mat4 = [
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
number,
|
||||
];
|
||||
export type SimplePolygon = Vec2[];
|
||||
export type Polygons = SimplePolygon|SimplePolygon[];
|
||||
export type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
export type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
export type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
export type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
@@ -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.
|
||||
@@ -12,441 +12,33 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
type Vec2 = [number, number];
|
||||
type Vec3 = [number, number, number];
|
||||
type Vec4 = [number, number, number, number];
|
||||
type Matrix3x4 = [Vec3, Vec3, Vec3, Vec3];
|
||||
type SimplePolygon = Vec2[];
|
||||
type Polygons = SimplePolygon|SimplePolygon[];
|
||||
type Box = {
|
||||
min: Vec3,
|
||||
max: Vec3
|
||||
};
|
||||
type Smoothness = {
|
||||
halfedge: number,
|
||||
smoothness: number
|
||||
};
|
||||
type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
type BaryRef = {
|
||||
meshID: number,
|
||||
originalID: number,
|
||||
tri: number,
|
||||
vertBary: Vec3
|
||||
};
|
||||
type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
maxGaussianCurvature: number,
|
||||
minGaussianCurvature: number,
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
type MeshRelation = {
|
||||
barycentric: Vec3[],
|
||||
triBary: BaryRef[],
|
||||
};
|
||||
import * as T from './manifold-encapsulated-types';
|
||||
export * from './manifold-global-types';
|
||||
|
||||
declare class Mesh {
|
||||
vertPos: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
vertNormal?: Float32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
verts(tri: number): Uint32Array<3>;
|
||||
position(vert: number): Float32Array<3>;
|
||||
normal(vert: number): Float32Array<3>;
|
||||
tangent(halfedge: number): Float32Array<4>;
|
||||
}
|
||||
export type Manifold = T.Manifold;
|
||||
export type Mesh = T.Mesh;
|
||||
|
||||
declare class Manifold {
|
||||
/**
|
||||
* Create a Manifold from a Mesh object.
|
||||
*/
|
||||
constructor(mesh: Mesh);
|
||||
/**
|
||||
* Transform this Manifold in space. The first three columns form a 3x3 matrix
|
||||
* transform and the last is a translation vector. This operation can be
|
||||
* chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param m The affine transform matrix to apply to all the vertices.
|
||||
*/
|
||||
transform(m: Matrix3x4): Manifold;
|
||||
|
||||
/**
|
||||
* Move this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to add to every vertex.
|
||||
*/
|
||||
translate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Applies an Euler angle rotation to the manifold, first about the X axis,
|
||||
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
|
||||
* error, and eliminate it completely for any multiples of 90 degrees.
|
||||
* Additionally, more efficient code paths are used to update the manifold
|
||||
* when the transforms only rotate by multiples of 90 degrees. This operation
|
||||
* can be chained. Transforms are combined and applied lazily.
|
||||
*
|
||||
* @param v [X, Y, Z] rotation in degrees.
|
||||
*/
|
||||
rotate(v: Vec3): Manifold;
|
||||
|
||||
/**
|
||||
* Scale this Manifold in space. This operation can be chained. Transforms are
|
||||
* combined and applied lazily.
|
||||
*
|
||||
* @param v The vector to multiply every vertex by per component.
|
||||
*/
|
||||
scale(v: Vec3|number): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean union
|
||||
*/
|
||||
add(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean difference
|
||||
*/
|
||||
subtract(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Boolean intersection
|
||||
*/
|
||||
intersect(other: Manifold): Manifold;
|
||||
|
||||
/**
|
||||
* Increase the density of the mesh by splitting every edge into n pieces. For
|
||||
* instance, with n = 2, each triangle will be split into 4 triangles. These
|
||||
* will all be coplanar (and will not be immediately collapsed) unless the
|
||||
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
|
||||
* constructor), in which case the new vertices will be moved to the
|
||||
* interpolated surface according to their barycentric coordinates.
|
||||
*
|
||||
* @param n The number of pieces to split every edge into. Must be > 1.
|
||||
*/
|
||||
refine(n: number): Manifold;
|
||||
|
||||
/**
|
||||
* This function does not change the topology, but allows the vertices to be
|
||||
* moved according to any arbitrary input function. It is easy to create a
|
||||
* function that warps a geometrically valid object into one which overlaps,
|
||||
* but that is not checked here, so it is up to the user to choose their
|
||||
* function with discretion.
|
||||
*
|
||||
* @param warpFunc A function that modifies a given vertex position.
|
||||
*/
|
||||
warp(warpFunc: (vert: Vec3) => void): Manifold;
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of
|
||||
* Compose().
|
||||
*/
|
||||
decompose(): Manifold[];
|
||||
|
||||
/**
|
||||
* Does the Manifold have any triangles?
|
||||
*/
|
||||
isEmpty(): boolean;
|
||||
|
||||
/**
|
||||
* The number of vertices in the Manifold.
|
||||
*/
|
||||
numVert(): number;
|
||||
|
||||
/**
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
numTri(): number;
|
||||
|
||||
/**
|
||||
* The number of edges in the Manifold.
|
||||
*/
|
||||
numEdge(): number;
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
*/
|
||||
boundingBox(): Box;
|
||||
|
||||
/**
|
||||
* Returns the precision of this Manifold's vertices, which tracks the
|
||||
* approximate rounding error over all the transforms and operations that have
|
||||
* led to this state. Any triangles that are colinear within this precision
|
||||
* are considered degenerate and removed. This is the value of ε
|
||||
* defining
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
precision(): number;
|
||||
|
||||
/**
|
||||
* The genus is a topological property of the manifold, representing the
|
||||
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
|
||||
* a single mesh, so it is best to call Decompose() first.
|
||||
*/
|
||||
genus(): number;
|
||||
|
||||
/**
|
||||
* Returns the surface area and volume of the manifold. These properties are
|
||||
* clamped to zero for a given face if they are within the Precision(). This
|
||||
* means degenerate manifolds can by identified by testing these properties as
|
||||
* == 0.
|
||||
*/
|
||||
getProperties(): Properties;
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. This approximates them for every vertex (returned
|
||||
* as vectors in the structure) and also returns their minimum and maximum
|
||||
* values.
|
||||
*/
|
||||
getCurvature(): Curvature;
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable
|
||||
* for saving or other operations outside of the context of this library.
|
||||
*/
|
||||
getMesh(): Mesh;
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous meshes, for the purpose of assigning
|
||||
* properties like texture coordinates. The triBary vector is the same length
|
||||
* as Mesh.triVerts: BaryRef.originalID indicates the source mesh and
|
||||
* BaryRef.tri is that mesh's triangle index to which these barycentric
|
||||
* coordinates refer. BaryRef.vertBary gives an index for each vertex into the
|
||||
* barycentric vector if that index is >= 0, indicating it is a new vertex. If
|
||||
* the index is < 0, this indicates it is an original vertex, the index + 3
|
||||
* vert of the referenced triangle.
|
||||
*
|
||||
* BaryRef.meshID is a unique ID to the particular instance of a given mesh.
|
||||
* For instance, if you want to convert the triangle mesh to a polygon mesh,
|
||||
* all the triangles from a given face will have the same .meshID and .tri
|
||||
* values.
|
||||
*/
|
||||
getMeshRelation(): MeshRelation;
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to
|
||||
* construct a new manifold.
|
||||
*/
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a unit cube (edge lengths all one), by default in the first
|
||||
* octant, touching the origin.
|
||||
*
|
||||
* @param size The X, Y, and Z dimensions of the box.
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* A convenience constructor for the common case of extruding a circle. Can also
|
||||
* form cones if both radii are specified.
|
||||
*
|
||||
* @param height Z-extent
|
||||
* @param radiusLow Radius of bottom circle. Must be positive.
|
||||
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
|
||||
* radiusLow.
|
||||
* @param circularSegments How many line segments to use around the circle.
|
||||
* Default is calculated by the static Defaults.
|
||||
* @param center Set to true to shift the center to the origin. Default is
|
||||
* origin at the bottom.
|
||||
*/
|
||||
declare function cylinder(
|
||||
height: number, radiusLow: number, radiusHigh?: number,
|
||||
circularSegments?: number, center?: boolean): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a geodesic sphere of a given radius.
|
||||
*
|
||||
* @param radius Radius of the sphere. Must be positive.
|
||||
* @param circularSegments Number of segments along its
|
||||
* diameter. This number will always be rounded up to the nearest factor of
|
||||
* four, as this sphere is constructed by refining an octahedron. This means
|
||||
* there are a circle of vertices on all three of the axis planes. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
declare function sphere(radius: number, circularSegments?: number): Manifold;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param mesh input Mesh.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
declare function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
declare function tetrahedron(): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by extruding them along the
|
||||
* Z-axis.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to extrude.
|
||||
* @param height Z-extent of extrusion.
|
||||
* @param nDivisions Number of extra copies of the crossSection to insert into
|
||||
* the shape vertically; especially useful in combination with twistDegrees to
|
||||
* avoid interpolation artifacts. Default is none.
|
||||
* @param twistDegrees Amount to twist the top crossSection relative to the
|
||||
* bottom, interpolated linearly for the divisions in between.
|
||||
* @param scaleTop Amount to scale the top (independently in X and Y). If the
|
||||
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
|
||||
* Default {1, 1}.
|
||||
*/
|
||||
declare function extrude(
|
||||
crossSection: Polygons, height: number, nDivisions?: number,
|
||||
twistDegrees?: number, scaleTop?: Vec2): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a manifold from a set of polygons by revolving this cross-section
|
||||
* around its Y-axis and then setting this as the Z-axis of the resulting
|
||||
* manifold. If the polygons cross the Y-axis, only the part on the positive X
|
||||
* side is used. Geometrically valid input will result in geometrically valid
|
||||
* output.
|
||||
*
|
||||
* @param crossSection A set of non-overlapping polygons to revolve.
|
||||
* @param circularSegments Number of segments along its diameter. Default is
|
||||
* calculated by the static Defaults.
|
||||
*/
|
||||
declare function revolve(
|
||||
crossSection: Polygons, circularSegments?: number): Manifold;
|
||||
|
||||
declare function union(a: Manifold, b: Manifold): Manifold;
|
||||
declare function difference(a: Manifold, b: Manifold): Manifold;
|
||||
declare function intersection(a: Manifold, b: Manifold): Manifold;
|
||||
|
||||
declare function union(manifolds: Manifold[]): Manifold;
|
||||
declare function difference(manifolds: Manifold[]): Manifold;
|
||||
declare function intersection(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a new manifold from a list of other manifolds. This is a purely
|
||||
* topological operation, so care should be taken to avoid creating
|
||||
* overlapping results. It is the inverse operation of Decompose().
|
||||
*
|
||||
* @param manifolds A list of Manifolds to lazy-union together.
|
||||
*/
|
||||
declare function compose(manifolds: Manifold[]): Manifold;
|
||||
|
||||
/**
|
||||
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
|
||||
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
|
||||
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
|
||||
* cubic grid (two shifted cubic grids). This means if your function's interior
|
||||
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
|
||||
* the manifold, which is due to the underlying grid.
|
||||
*
|
||||
* @param sdf The signed-distance function which returns the signed distance of
|
||||
* a given point in R^3. Positive values are inside, negative outside.
|
||||
* @param bounds An axis-aligned box that defines the extent of the grid.
|
||||
* @param edgeLength Approximate maximum edge length of the triangles in the
|
||||
* final result. This affects grid spacing, and hence has a strong effect on
|
||||
* performance.
|
||||
* @param level You can inset your Mesh by using a positive value, or outset
|
||||
* it with a negative value.
|
||||
*/
|
||||
declare function levelSet(
|
||||
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
|
||||
level?: number): Manifold;
|
||||
|
||||
/**
|
||||
* @name Defaults
|
||||
* These static properties control how circular shapes are quantized by
|
||||
* default on construction. If circularSegments is specified, it takes
|
||||
* precedence. If it is zero, then instead the minimum is used of the segments
|
||||
* calculated based on edge length and angle, rounded up to the nearest
|
||||
* multiple of four. To get numbers not divisible by four, circularSegments
|
||||
* must be specified.
|
||||
*/
|
||||
///@{
|
||||
declare function setMinCircularAngle(angle: number): void;
|
||||
declare function setMinCircularEdgeLength(length: number): void;
|
||||
declare function setCircularSegments(segments: number): void;
|
||||
declare function getCircularSegments(radius: number): number;
|
||||
///@}
|
||||
|
||||
/**
|
||||
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
|
||||
* constructor, this method does not dispose the Mesh after using it.
|
||||
*
|
||||
* @param meshVec The serialized Mesh object to convert into a Manifold.
|
||||
*/
|
||||
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
|
||||
|
||||
declare interface ManifoldStatic {
|
||||
cube: typeof cube;
|
||||
cylinder: typeof cylinder;
|
||||
sphere: typeof sphere;
|
||||
smooth: typeof smooth;
|
||||
tetrahedron: typeof tetrahedron;
|
||||
extrude: typeof extrude;
|
||||
revolve: typeof revolve;
|
||||
union: typeof union;
|
||||
difference: typeof difference;
|
||||
intersection: typeof intersection;
|
||||
compose: typeof compose;
|
||||
levelSet: typeof levelSet;
|
||||
setMinCircularAngle: typeof setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof setCircularSegments;
|
||||
getCircularSegments: typeof getCircularSegments;
|
||||
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
|
||||
Manifold: typeof Manifold;
|
||||
export interface ManifoldStatic {
|
||||
cube: typeof T.cube;
|
||||
cylinder: typeof T.cylinder;
|
||||
sphere: typeof T.sphere;
|
||||
smooth: typeof T.smooth;
|
||||
tetrahedron: typeof T.tetrahedron;
|
||||
extrude: typeof T.extrude;
|
||||
revolve: typeof T.revolve;
|
||||
union: typeof T.union;
|
||||
difference: typeof T.difference;
|
||||
intersection: typeof T.intersection;
|
||||
compose: typeof T.compose;
|
||||
levelSet: typeof T.levelSet;
|
||||
setMinCircularAngle: typeof T.setMinCircularAngle;
|
||||
setMinCircularEdgeLength: typeof T.setMinCircularEdgeLength;
|
||||
setCircularSegments: typeof T.setCircularSegments;
|
||||
getCircularSegments: typeof T.getCircularSegments;
|
||||
reserveIDs: typeof T.reserveIDs;
|
||||
Manifold: typeof T.Manifold;
|
||||
Mesh: typeof T.Mesh;
|
||||
setup: () => void;
|
||||
}
|
||||
|
||||
declare function Module(): Promise<ManifoldStatic>;
|
||||
export default function Module(): Promise<ManifoldStatic>;
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"chai": "*",
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
{
|
||||
"name": "manifold-3d",
|
||||
"version": "1.0.0",
|
||||
"version": "2.1.0",
|
||||
"description": "Geometry library for topological robustness",
|
||||
"main": "manifold.js",
|
||||
"files": [
|
||||
"manifold.js",
|
||||
"manifold.wasm",
|
||||
"manifold.d.ts",
|
||||
"manifold-encapsulated-types.d.ts",
|
||||
"manifold-global-types.d.ts",
|
||||
"examples/examples.js",
|
||||
"test/test.js"
|
||||
],
|
||||
"typings": "manifold.d.ts",
|
||||
"types": "manifold.d.ts",
|
||||
"type": "module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/elalish/manifold.git"
|
||||
@@ -41,6 +44,6 @@
|
||||
"devDependencies": {
|
||||
"chai": "*",
|
||||
"mocha": "*",
|
||||
"three": "^0.146.0"
|
||||
"gl-matrix": "3.4.3"
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
const expect = require('chai').expect;
|
||||
const wasm = require('../manifold.js');
|
||||
const examples = require('../examples/examples');
|
||||
const THREE = require('three');
|
||||
import {expect} from 'chai';
|
||||
import glMatrix from 'gl-matrix';
|
||||
|
||||
import {examples} from '../examples/examples.js';
|
||||
import Module from '../manifold.js';
|
||||
|
||||
const wasm = await Module();
|
||||
wasm.setup();
|
||||
|
||||
// Faster on modern browsers than Float32Array
|
||||
glMatrix.glMatrix.setMatrixArrayType(Array);
|
||||
|
||||
// manifold member functions that returns a new manifold
|
||||
const memberFunctions = [
|
||||
@@ -33,121 +40,122 @@ const utils = [
|
||||
];
|
||||
const exposedFunctions = constructors.concat(utils);
|
||||
|
||||
wasm().then(function(Module) {
|
||||
Module.setup();
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
// Setup memory management, such that users don't have to care about
|
||||
// calling `delete` manually.
|
||||
// Note that this only fixes memory leak across different runs: the memory
|
||||
// will only be freed when the compilation finishes.
|
||||
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = Module.Manifold.prototype[name];
|
||||
Module.Manifold.prototype['_' + name] = originalFn;
|
||||
Module.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
let manifoldRegistry = [];
|
||||
for (const name of memberFunctions) {
|
||||
const originalFn = wasm.Manifold.prototype[name];
|
||||
wasm.Manifold.prototype['_' + name] = originalFn;
|
||||
wasm.Manifold.prototype[name] = function(...args) {
|
||||
const result = this['_' + name](...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = wasm[name];
|
||||
wasm[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
wasm.cleanup = function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry = [];
|
||||
};
|
||||
|
||||
for (const name of constructors) {
|
||||
const originalFn = Module[name];
|
||||
Module[name] = function(...args) {
|
||||
const result = originalFn(...args);
|
||||
manifoldRegistry.push(result);
|
||||
return result;
|
||||
}
|
||||
function runExample(name) {
|
||||
try {
|
||||
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
|
||||
const f = new Function(...exposedFunctions, 'glMatrix', content);
|
||||
const manifold = f(...exposedFunctions.map(name => wasm[name]), glMatrix);
|
||||
|
||||
const mesh = manifold.getMesh();
|
||||
expect(mesh.mergeFromVert.length).to.equal(mesh.mergeToVert.length);
|
||||
expect(mesh.mergeFromVert.length)
|
||||
.to.equal(mesh.numVert - manifold.numVert());
|
||||
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
return {...prop, genus};
|
||||
} finally {
|
||||
wasm.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
Module.cleanup =
|
||||
function() {
|
||||
for (const obj of manifoldRegistry) {
|
||||
// decompose result is an array of manifolds
|
||||
if (obj instanceof Array)
|
||||
for (const elem of obj) elem.delete();
|
||||
else
|
||||
obj.delete();
|
||||
}
|
||||
manifoldRegistry = [];
|
||||
}
|
||||
suite('Examples', () => {
|
||||
test('Intro', () => {
|
||||
const result = runExample('Intro');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
function runExample(name) {
|
||||
try {
|
||||
const content = examples.functionBodies.get(name) + '\nreturn result;\n';
|
||||
;
|
||||
const f = new Function(...exposedFunctions, 'THREE', content);
|
||||
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
|
||||
const prop = manifold.getProperties();
|
||||
const genus = manifold.genus();
|
||||
return {...prop, genus};
|
||||
} finally {
|
||||
Module.cleanup();
|
||||
}
|
||||
}
|
||||
test('Tetrahedron Puzzle', () => {
|
||||
const result = runExample('Tetrahedron Puzzle');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
suite('Examples', () => {
|
||||
test('Intro', () => {
|
||||
const result = runExample('Intro');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(203164, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(62046, 1, 'Surface Area');
|
||||
});
|
||||
test('Rounded Frame', () => {
|
||||
const result = runExample('Rounded Frame');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(353706, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Tetrahedron Puzzle', () => {
|
||||
const result = runExample('Tetrahedron Puzzle');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(7297, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(3303, 1, 'Surface Area');
|
||||
});
|
||||
test('Heart', () => {
|
||||
const result = runExample('Heart');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(282743, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Rounded Frame', () => {
|
||||
const result = runExample('Rounded Frame');
|
||||
expect(result.genus).to.equal(5, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(353706, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(68454, 1, 'Surface Area');
|
||||
});
|
||||
test('Scallop', () => {
|
||||
const result = runExample('Scallop');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Heart', () => {
|
||||
const result = runExample('Heart');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(282743, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
|
||||
});
|
||||
test('Torus Knot', () => {
|
||||
const result = runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Scallop', () => {
|
||||
const result = runExample('Scallop');
|
||||
expect(result.genus).to.equal(0, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(41284, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(7810, 1, 'Surface Area');
|
||||
});
|
||||
test('Menger Sponge', () => {
|
||||
const result = runExample('Menger Sponge');
|
||||
expect(result.genus).to.equal(1409, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(406457, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Torus Knot', () => {
|
||||
const result = runExample('Torus Knot');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(20960, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(11202, 1, 'Surface Area');
|
||||
});
|
||||
test('Stretchy Bracelet', () => {
|
||||
const result = runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Menger Sponge', () => {
|
||||
const result = runExample('Menger Sponge');
|
||||
expect(result.genus).to.equal(1409, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(406457, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', () => {
|
||||
const result = runExample('Stretchy Bracelet');
|
||||
expect(result.genus).to.equal(1, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(3992, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(22267, 1, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Gyroid Module', () => {
|
||||
const result = runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
test('Gyroid Module', () => {
|
||||
const result = runExample('Gyroid Module');
|
||||
expect(result.genus).to.equal(15, 'Genus');
|
||||
expect(result.volume).to.be.closeTo(4167, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(5642, 1, 'Surface Area');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ add_executable(perfTest perf_test.cpp)
|
||||
target_link_libraries(perfTest manifold)
|
||||
|
||||
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTest PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTest PUBLIC cxx_std_17)
|
||||
|
||||
if(BUILD_TEST_CGAL)
|
||||
add_executable(perfTestCGAL perf_test_cgal.cpp)
|
||||
@@ -29,5 +29,5 @@ if(BUILD_TEST_CGAL)
|
||||
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
|
||||
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages(ps: with ps; [trimesh])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PYBIND=ON"
|
||||
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
|
||||
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ target_link_libraries(samples
|
||||
)
|
||||
|
||||
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samples PUBLIC cxx_std_14)
|
||||
target_compile_features(samples PUBLIC cxx_std_17)
|
||||
|
||||
project(samplesGPU)
|
||||
|
||||
@@ -46,6 +46,5 @@ target_link_libraries(samplesGPU
|
||||
|
||||
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samplesGPU
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
After Width: | Height: | Size: 428 KiB |
@@ -26,10 +26,8 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
Polygons circle(1);
|
||||
float dPhiDeg = 180.0f / nDivision;
|
||||
for (int i = 0; i < 2 * nDivision; ++i) {
|
||||
circle[0].push_back(
|
||||
{glm::vec2(decorRadius * cosd(dPhiDeg * i) + twistRadius,
|
||||
decorRadius * sind(dPhiDeg * i)),
|
||||
0});
|
||||
circle[0].push_back({decorRadius * cosd(dPhiDeg * i) + twistRadius,
|
||||
decorRadius * sind(dPhiDeg * i)});
|
||||
}
|
||||
Manifold decor = Manifold::Extrude(circle, width, nDivision, 180)
|
||||
.Scale({1.0f, 0.5f, 1.0f})
|
||||
@@ -45,13 +43,14 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
|
||||
glm::vec2 p1(innerRadius, -cut);
|
||||
glm::vec2 p2(innerRadius, cut);
|
||||
for (int i = 0; i < nCut; ++i) {
|
||||
stretch[0].push_back({glm::rotate(p0, dPhiRad * i), 0});
|
||||
stretch[0].push_back({glm::rotate(p1, dPhiRad * i), 0});
|
||||
stretch[0].push_back({glm::rotate(p2, dPhiRad * i), 0});
|
||||
stretch[0].push_back({glm::rotate(p0, dPhiRad * i), 0});
|
||||
stretch[0].push_back(glm::rotate(p0, dPhiRad * i));
|
||||
stretch[0].push_back(glm::rotate(p1, dPhiRad * i));
|
||||
stretch[0].push_back(glm::rotate(p2, dPhiRad * i));
|
||||
stretch[0].push_back(glm::rotate(p0, dPhiRad * i));
|
||||
}
|
||||
|
||||
base = Manifold::Extrude(stretch, width) ^ base;
|
||||
// Remove extra edges in coplanar faces
|
||||
base = base.AsOriginal();
|
||||
|
||||
return base;
|
||||
|
||||
@@ -51,7 +51,7 @@ Manifold TorusKnot(int p, int q, float majorRadius, float minorRadius,
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back({glm::vec2(cosd(dPhi * i) + 2.0f, sind(dPhi * i)), 0});
|
||||
circle[0].push_back({cosd(dPhi * i) + 2.0f, sind(dPhi * i)});
|
||||
}
|
||||
|
||||
Manifold knot = Manifold::Revolve(circle, m);
|
||||
|
||||
@@ -31,10 +31,10 @@ Manifold TetPuzzle(float edgeLength, float gap, int nDivisions) {
|
||||
Manifold tet = Manifold::Tetrahedron().Scale(scale);
|
||||
|
||||
Polygons box;
|
||||
box.push_back({{glm::vec2(2, -2), 0}, {glm::vec2(2, 2), 1}});
|
||||
box.push_back({{2, -2}, {2, 2}});
|
||||
|
||||
for (int i = 0; i <= nDivisions; ++i) {
|
||||
box[0].push_back({glm::vec2(gap / 2, 2 - i * 4.0f / nDivisions), 2 + i});
|
||||
box[0].push_back({gap / 2, 2 - i * 4.0f / nDivisions});
|
||||
}
|
||||
|
||||
Manifold screw = Manifold::Extrude(box, 2, nDivisions, 270)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (collider)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
@@ -25,8 +25,6 @@ endif()
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
||||
@@ -18,18 +18,16 @@ 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_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_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
||||
@@ -49,12 +49,11 @@ class Manifold {
|
||||
Manifold(Manifold&&) noexcept;
|
||||
Manifold& operator=(Manifold&&) noexcept;
|
||||
|
||||
Manifold(
|
||||
const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
Manifold(const MeshGL&, const std::vector<float>& propertyTolerance = {});
|
||||
Manifold(const Mesh&);
|
||||
|
||||
static Manifold Smooth(const MeshGL&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Smooth(const Mesh&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Tetrahedron();
|
||||
@@ -75,6 +74,9 @@ class Manifold {
|
||||
*/
|
||||
///@{
|
||||
static Manifold Compose(const std::vector<Manifold>&);
|
||||
|
||||
Components GetComponents() const;
|
||||
std::vector<Manifold> Decompose(Components components) const;
|
||||
std::vector<Manifold> Decompose() const;
|
||||
///@}
|
||||
|
||||
@@ -98,21 +100,27 @@ class Manifold {
|
||||
*/
|
||||
///@{
|
||||
Mesh GetMesh() const;
|
||||
MeshGL GetMeshGL() const;
|
||||
MeshGL GetMeshGL(glm::ivec3 normalIdx = glm::ivec3(0)) const;
|
||||
bool IsEmpty() const;
|
||||
enum class Error {
|
||||
NO_ERROR,
|
||||
NON_FINITE_VERTEX,
|
||||
NOT_MANIFOLD,
|
||||
VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_OUT_OF_BOUNDS,
|
||||
NoError,
|
||||
NonFiniteVertex,
|
||||
NotManifold,
|
||||
VertexOutOfBounds,
|
||||
PropertiesWrongLength,
|
||||
MissingPositionProperties,
|
||||
MergeVectorsDifferentLengths,
|
||||
MergeIndexOutOfBounds,
|
||||
TransformWrongLength,
|
||||
RunIndexWrongLength,
|
||||
FaceIDWrongLength,
|
||||
};
|
||||
Error Status() const;
|
||||
int NumVert() const;
|
||||
int NumEdge() const;
|
||||
int NumTri() const;
|
||||
int NumProp() const;
|
||||
int NumPropVert() const;
|
||||
Box BoundingBox() const;
|
||||
float Precision() const;
|
||||
int Genus() const;
|
||||
@@ -120,14 +128,14 @@ class Manifold {
|
||||
Curvature GetCurvature() const;
|
||||
///@}
|
||||
|
||||
/** @name Relation
|
||||
/** @name Mesh ID
|
||||
* Details of the manifold's relation to its input meshes, for the purposes
|
||||
* of reapplying mesh properties.
|
||||
*/
|
||||
///@{
|
||||
MeshRelation GetMeshRelation() const;
|
||||
int OriginalID() const;
|
||||
Manifold AsOriginal() const;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
@@ -149,18 +157,18 @@ class Manifold {
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* Boolean operation type: ADD (Union), SUBTRACT (Difference), and INTERSECT.
|
||||
* Boolean operation type: Add (Union), Subtract (Difference), and Intersect.
|
||||
*/
|
||||
enum class OpType { ADD, SUBTRACT, INTERSECT };
|
||||
enum class OpType { Add, Subtract, Intersect };
|
||||
Manifold Boolean(const Manifold& second, OpType op) const;
|
||||
static Manifold BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
OpType op);
|
||||
// Boolean operation shorthand
|
||||
Manifold operator+(const Manifold&) const; // ADD (Union)
|
||||
Manifold operator+(const Manifold&) const; // Add (Union)
|
||||
Manifold& operator+=(const Manifold&);
|
||||
Manifold operator-(const Manifold&) const; // SUBTRACT (Difference)
|
||||
Manifold operator-(const Manifold&) const; // Subtract (Difference)
|
||||
Manifold& operator-=(const Manifold&);
|
||||
Manifold operator^(const Manifold&) const; // INTERSECT
|
||||
Manifold operator^(const Manifold&) const; // Intersect
|
||||
Manifold& operator^=(const Manifold&);
|
||||
std::pair<Manifold, Manifold> Split(const Manifold&) const;
|
||||
std::pair<Manifold, Manifold> SplitByPlane(glm::vec3 normal,
|
||||
|
||||
@@ -515,7 +515,7 @@ Boolean3::Boolean3(const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
Manifold::OpType op)
|
||||
: inP_(inP),
|
||||
inQ_(inQ),
|
||||
expandP_(op == Manifold::OpType::ADD ? 1.0 : -1.0),
|
||||
expandP_(op == Manifold::OpType::Add ? 1.0 : -1.0),
|
||||
policy_(autoPolicy(glm::max(inP.NumEdge(), inQ.NumEdge()))) {
|
||||
// Symbolic perturbation:
|
||||
// Union -> expand inP
|
||||
|
||||
@@ -197,17 +197,10 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
|
||||
return edges;
|
||||
}
|
||||
|
||||
// A Ref carries the reference of a halfedge's startVert back to the input
|
||||
// manifolds. PQ is 0 if the halfedge comes from triangle tri of P, and 1 for Q.
|
||||
// vert is 0, 1, or 2 to denote which vertex of tri it is, and -1 if it is new.
|
||||
struct Ref {
|
||||
int PQ, tri, vert;
|
||||
};
|
||||
|
||||
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
VecDH<int> &facePtrR,
|
||||
std::map<int, std::vector<EdgePos>> &edgesP,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<int> &i03, const VecDH<int> &vP2R,
|
||||
const VecDH<int>::IterC faceP2R, bool forward) {
|
||||
// Each edge in the map is partially retained; for each of these, look up
|
||||
@@ -266,11 +259,8 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW. This is only valid if this is a retained vert; it
|
||||
// will be ignored later if the vert is new.
|
||||
const Ref forwardRef = {forward ? 0 : 1, faceLeftP,
|
||||
(edgeP + (reversed ? 1 : 0)) % 3};
|
||||
const Ref backwardRef = {
|
||||
forward ? 0 : 1, faceRightP,
|
||||
(halfedge.pairedHalfedge + (reversed ? 1 : 0)) % 3};
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
@@ -293,7 +283,8 @@ 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<Ref> &halfedgeRef, const VecDH<int> &facePQ2R, const int numFaceP) {
|
||||
VecDH<TriRef> &halfedgeRef, const VecDH<int> &facePQ2R,
|
||||
const int numFaceP) {
|
||||
// Pair up each edge's verts and distribute to faces based on indices in key.
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
|
||||
@@ -322,8 +313,8 @@ void AppendNewEdges(
|
||||
// add halfedges to result
|
||||
const int faceLeft = facePQ2R[faceP];
|
||||
const int faceRight = facePQ2R[numFaceP + faceQ];
|
||||
const Ref forwardRef = {0, faceP, -4};
|
||||
const Ref backwardRef = {1, faceQ, -4};
|
||||
const TriRef forwardRef = {0, -1, faceP};
|
||||
const TriRef backwardRef = {1, -1, faceQ};
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
const int backwardEdge = facePtrR[faceRight]++;
|
||||
@@ -344,7 +335,7 @@ void AppendNewEdges(
|
||||
|
||||
struct DuplicateHalfedges {
|
||||
Halfedge *halfedgesR;
|
||||
Ref *halfedgeRef;
|
||||
TriRef *halfedgeRef;
|
||||
int *facePtr;
|
||||
const Halfedge *halfedgesP;
|
||||
const int *i03;
|
||||
@@ -374,11 +365,8 @@ struct DuplicateHalfedges {
|
||||
// Negative inclusion means the halfedges are reversed, which means our
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW.
|
||||
const Ref forwardRef = {forward ? 0 : 1, faceLeftP,
|
||||
(edgeP + (inclusion < 0 ? 1 : 0)) % 3};
|
||||
const Ref backwardRef = {
|
||||
forward ? 0 : 1, faceRightP,
|
||||
(halfedge.pairedHalfedge + (inclusion < 0 ? 1 : 0)) % 3};
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (int i = 0; i < glm::abs(inclusion); ++i) {
|
||||
int forwardEdge = AtomicAdd(facePtr[halfedge.face], 1);
|
||||
@@ -398,7 +386,7 @@ struct DuplicateHalfedges {
|
||||
};
|
||||
|
||||
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
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) {
|
||||
@@ -410,97 +398,142 @@ void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
|
||||
}
|
||||
|
||||
struct CreateBarycentric {
|
||||
glm::vec3 *barycentricR;
|
||||
BaryRef *faceRef;
|
||||
int *idx;
|
||||
|
||||
struct MapTriRef {
|
||||
const TriRef *triRefP;
|
||||
const TriRef *triRefQ;
|
||||
const int offsetQ;
|
||||
const int firstNewVert;
|
||||
const glm::vec3 *vertPosR;
|
||||
|
||||
__host__ __device__ void operator()(TriRef &triRef) {
|
||||
const int tri = triRef.tri;
|
||||
const bool PQ = triRef.meshID == 0;
|
||||
triRef = PQ ? triRefP[tri] : triRefQ[tri];
|
||||
if (!PQ) triRef.meshID += offsetQ;
|
||||
}
|
||||
};
|
||||
|
||||
VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ, bool invertQ,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<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 (const auto &pair : inP.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
for (const auto &pair : inQ.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ] = pair.second;
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
|
||||
invertQ;
|
||||
}
|
||||
return refPQ;
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
glm::vec3 *uvw;
|
||||
const glm::vec3 *vertPosP;
|
||||
const glm::vec3 *vertPosQ;
|
||||
const glm::vec3 *vertPosR;
|
||||
const Halfedge *halfedgeP;
|
||||
const Halfedge *halfedgeQ;
|
||||
const BaryRef *triBaryP;
|
||||
const BaryRef *triBaryQ;
|
||||
const glm::vec3 *barycentricP;
|
||||
const glm::vec3 *barycentricQ;
|
||||
const bool invertQ;
|
||||
const Halfedge *halfedgeR;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<int &, Ref, Halfedge> inOut) {
|
||||
int &halfedgeBary = thrust::get<0>(inOut);
|
||||
const Ref halfedgeRef = thrust::get<1>(inOut);
|
||||
const Halfedge halfedgeR = thrust::get<2>(inOut);
|
||||
__host__ __device__ void operator()(thrust::tuple<int, TriRef> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const TriRef refPQ = thrust::get<1>(in);
|
||||
if (halfedgeR[3 * tri].startVert < 0) return;
|
||||
|
||||
const glm::vec3 *barycentric =
|
||||
halfedgeRef.PQ == 0 ? barycentricP : barycentricQ;
|
||||
const int tri = halfedgeRef.tri;
|
||||
const BaryRef oldRef = halfedgeRef.PQ == 0 ? triBaryP[tri] : triBaryQ[tri];
|
||||
const int triPQ = refPQ.tri;
|
||||
const bool PQ = refPQ.meshID == 0;
|
||||
const auto &vertPos = PQ ? vertPosP : vertPosQ;
|
||||
const auto &halfedge = PQ ? halfedgeP : halfedgeQ;
|
||||
|
||||
faceRef[halfedgeR.face] = oldRef;
|
||||
glm::mat3 triPos;
|
||||
for (const int j : {0, 1, 2})
|
||||
triPos[j] = vertPos[halfedge[3 * triPQ + j].startVert];
|
||||
|
||||
if (halfedgeRef.PQ == 1) {
|
||||
// Mark the meshID as coming from Q
|
||||
faceRef[halfedgeR.face].meshID += offsetQ;
|
||||
}
|
||||
|
||||
if (halfedgeR.startVert < firstNewVert) { // retained vert
|
||||
int i = halfedgeRef.vert;
|
||||
const int bary = oldRef.vertBary[i];
|
||||
if (bary < 0) {
|
||||
halfedgeBary = bary;
|
||||
} else {
|
||||
halfedgeBary = AtomicAdd(*idx, 1);
|
||||
barycentricR[halfedgeBary] = barycentric[bary];
|
||||
}
|
||||
} else { // new vert
|
||||
halfedgeBary = AtomicAdd(*idx, 1);
|
||||
|
||||
const glm::vec3 *vertPos = halfedgeRef.PQ == 0 ? vertPosP : vertPosQ;
|
||||
const Halfedge *halfedge = halfedgeRef.PQ == 0 ? halfedgeP : halfedgeQ;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2})
|
||||
triPos[i] = vertPos[halfedge[3 * tri + i].startVert];
|
||||
|
||||
glm::mat3 uvwOldTri;
|
||||
for (int i : {0, 1, 2})
|
||||
uvwOldTri[i] = UVW(oldRef.vertBary[i], barycentric);
|
||||
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPosR[halfedgeR.startVert], triPos, precision);
|
||||
barycentricR[halfedgeBary] = uvwOldTri * uvw;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = halfedgeR[3 * tri + i].startVert;
|
||||
uvw[3 * tri + i] = GetBarycentric(vertPosR[vert], triPos, precision);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
|
||||
Manifold::Impl &outR, const VecDH<Ref> &halfedgeRef,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, int firstNewVert,
|
||||
int numFaceR, bool invertQ, ExecutionPolicy policy) {
|
||||
outR.meshRelation_.barycentric.resize(outR.halfedge_.size());
|
||||
VecDH<BaryRef> faceRef(numFaceR);
|
||||
VecDH<int> halfedgeBary(halfedgeRef.size());
|
||||
void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
ExecutionPolicy policy) {
|
||||
const int numPropP = inP.NumProp();
|
||||
const int numPropQ = inQ.NumProp();
|
||||
const int numProp = glm::max(numPropP, numPropQ);
|
||||
outR.meshRelation_.numProp = numProp;
|
||||
if (numProp == 0) return;
|
||||
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
VecDH<int> idx(1, 0);
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(halfedgeBary.begin(), halfedgeRef.begin(), outR.halfedge_.cbegin()),
|
||||
halfedgeRef.size(),
|
||||
CreateBarycentric(
|
||||
{outR.meshRelation_.barycentric.ptrD(), faceRef.ptrD(), idx.ptrD(),
|
||||
offsetQ, firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
inP.meshRelation_.triBary.cptrD(), inQ.meshRelation_.triBary.cptrD(),
|
||||
inP.meshRelation_.barycentric.cptrD(),
|
||||
inQ.meshRelation_.barycentric.cptrD(), invertQ, outR.precision_}));
|
||||
outR.meshRelation_.barycentric.resize(idx[0]);
|
||||
const int numTri = outR.NumTri();
|
||||
outR.meshRelation_.triProperties.resize(numTri);
|
||||
|
||||
return std::make_pair(faceRef, halfedgeBary);
|
||||
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_}));
|
||||
|
||||
std::map<std::tuple<int, int, int, int>, int> propIdx;
|
||||
int idx = 0;
|
||||
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
// Skip collapsed triangles
|
||||
if (outR.halfedge_[3 * tri].startVert < 0) continue;
|
||||
|
||||
const int triPQ = refPQ[tri].tri;
|
||||
const bool PQ = refPQ[tri].meshID == 0;
|
||||
const auto &properties =
|
||||
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties;
|
||||
const auto &triProp = PQ ? inP.meshRelation_.triProperties[triPQ]
|
||||
: inQ.meshRelation_.triProperties[triPQ];
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = outR.halfedge_[3 * tri + i].startVert;
|
||||
const glm::vec3 uvw = bary[3 * tri + i];
|
||||
|
||||
auto key = std::make_tuple(PQ, vert, -1, -1);
|
||||
int edge = -1;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (uvw[j] == 1) {
|
||||
// On a retained vert, the propVert must also match
|
||||
std::get<2>(key) = triProp[j];
|
||||
edge = -1;
|
||||
break;
|
||||
}
|
||||
if (uvw[j] == 0) edge = j;
|
||||
}
|
||||
if (edge >= 0) {
|
||||
// On an edge, both propVerts must match
|
||||
const int p0 = triProp[Next3(edge)];
|
||||
const int p1 = triProp[Prev3(edge)];
|
||||
std::get<2>(key) = glm::min(p0, p1);
|
||||
std::get<3>(key) = glm::max(p0, p1);
|
||||
}
|
||||
|
||||
const auto it = propIdx.find(key);
|
||||
if (it != propIdx.end()) {
|
||||
outR.meshRelation_.triProperties[tri][i] = it->second;
|
||||
continue;
|
||||
}
|
||||
outR.meshRelation_.triProperties[tri][i] = idx;
|
||||
propIdx.insert({key, idx++});
|
||||
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
glm::vec3 oldProps;
|
||||
for (const int j : {0, 1, 2})
|
||||
oldProps[j] = properties[numProp * triProp[j] + p];
|
||||
outR.meshRelation_.properties.push_back(glm::dot(uvw, oldProps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -512,25 +545,25 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
assemble.Start();
|
||||
#endif
|
||||
|
||||
ASSERT((expandP_ > 0) == (op == Manifold::OpType::ADD), logicErr,
|
||||
ASSERT((expandP_ > 0) == (op == Manifold::OpType::Add), logicErr,
|
||||
"Result op type not compatible with constructor op type.");
|
||||
const int c1 = op == Manifold::OpType::INTERSECT ? 0 : 1;
|
||||
const int c2 = op == Manifold::OpType::ADD ? 1 : 0;
|
||||
const int c3 = op == Manifold::OpType::INTERSECT ? 1 : -1;
|
||||
const int c1 = op == Manifold::OpType::Intersect ? 0 : 1;
|
||||
const int c2 = op == Manifold::OpType::Add ? 1 : 0;
|
||||
const int c3 = op == Manifold::OpType::Intersect ? 1 : -1;
|
||||
|
||||
if (w03_.size() == 0) {
|
||||
if (w30_.size() != 0 && op == Manifold::OpType::ADD) {
|
||||
if (w30_.size() != 0 && op == Manifold::OpType::Add) {
|
||||
return inQ_;
|
||||
}
|
||||
return Manifold::Impl();
|
||||
} else if (w30_.size() == 0) {
|
||||
if (op == Manifold::OpType::INTERSECT) {
|
||||
if (op == Manifold::OpType::Intersect) {
|
||||
return Manifold::Impl();
|
||||
}
|
||||
return inP_;
|
||||
}
|
||||
|
||||
const bool invertQ = op == Manifold::OpType::SUBTRACT;
|
||||
const bool invertQ = op == Manifold::OpType::Subtract;
|
||||
|
||||
// Convert winding numbers to inclusion values based on operation type.
|
||||
VecDH<int> i12(x12_.size());
|
||||
@@ -622,9 +655,9 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
// Intersected halfedges are marked false.
|
||||
VecDH<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
VecDH<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
// The halfedgeRef contains the data that will become triBary once the faces
|
||||
// The halfedgeRef contains the data that will become triRef once the faces
|
||||
// are triangulated.
|
||||
VecDH<Ref> halfedgeRef(2 * outR.NumEdge());
|
||||
VecDH<TriRef> halfedgeRef(2 * outR.NumEdge());
|
||||
|
||||
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
|
||||
i03, vP2R, facePQ2R.begin(), true);
|
||||
@@ -639,11 +672,6 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
|
||||
facePQ2R.cptrD() + inP_.NumTri(), false, policy_);
|
||||
|
||||
VecDH<BaryRef> faceRef;
|
||||
VecDH<int> halfedgeBary;
|
||||
std::tie(faceRef, halfedgeBary) = CalculateMeshRelation(
|
||||
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ, policy_);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
assemble.Stop();
|
||||
Timer triangulate;
|
||||
@@ -655,7 +683,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
|
||||
|
||||
outR.Face2Tri(faceEdge, faceRef, halfedgeBary);
|
||||
outR.Face2Tri(faceEdge, halfedgeRef);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
triangulate.Stop();
|
||||
@@ -666,13 +694,15 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
|
||||
|
||||
VecDH<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ, policy_);
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
CreateProperties(outR, refPQ, inP_, inQ_, policy_);
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
|
||||
|
||||
outR.IncrementMeshIDs(0, outR.NumTri());
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
simplify.Stop();
|
||||
Timer sort;
|
||||
@@ -680,6 +710,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
#endif
|
||||
|
||||
outR.Finish();
|
||||
outR.IncrementMeshIDs();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
sort.Stop();
|
||||
|
||||
@@ -50,6 +50,48 @@ struct RemoveFace {
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param meshGL input MeshGL.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
Manifold Manifold::Smooth(const MeshGL& meshGL,
|
||||
const std::vector<Smoothness>& sharpenedEdges) {
|
||||
ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
// Don't allow any triangle merging.
|
||||
std::vector<float> propertyTolerance(meshGL.numProp - 3, -1);
|
||||
std::shared_ptr<Impl> impl =
|
||||
std::make_shared<Impl>(meshGL, propertyTolerance);
|
||||
impl->CreateTangents(sharpenedEdges);
|
||||
return Manifold(impl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
@@ -84,7 +126,8 @@ Manifold Manifold::Smooth(const Mesh& mesh,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh);
|
||||
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh, relation);
|
||||
impl->CreateTangents(sharpenedEdges);
|
||||
return Manifold(impl);
|
||||
}
|
||||
@@ -94,7 +137,7 @@ Manifold Manifold::Smooth(const Mesh& mesh,
|
||||
* and the rest at similarly symmetric points.
|
||||
*/
|
||||
Manifold Manifold::Tetrahedron() {
|
||||
return Manifold(std::make_shared<Impl>(Impl::Shape::TETRAHEDRON));
|
||||
return Manifold(std::make_shared<Impl>(Impl::Shape::Tetrahedron));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,10 +148,10 @@ Manifold Manifold::Tetrahedron() {
|
||||
* @param center Set to true to shift the center to the origin.
|
||||
*/
|
||||
Manifold Manifold::Cube(glm::vec3 size, bool center) {
|
||||
auto cube = Manifold(std::make_shared<Impl>(Impl::Shape::CUBE));
|
||||
auto cube = Manifold(std::make_shared<Impl>(Impl::Shape::Cube));
|
||||
cube = cube.Scale(size);
|
||||
if (center) cube = cube.Translate(-size / 2.0f);
|
||||
return cube;
|
||||
return cube.AsOriginal();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,13 +175,13 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
Polygons circle(1);
|
||||
float dPhi = 360.0f / n;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
circle[0].push_back(
|
||||
{radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)), 0});
|
||||
circle[0].push_back(radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)));
|
||||
}
|
||||
Manifold cylinder =
|
||||
Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale));
|
||||
if (center)
|
||||
cylinder = cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f));
|
||||
cylinder =
|
||||
cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f)).AsOriginal();
|
||||
return cylinder;
|
||||
}
|
||||
|
||||
@@ -155,13 +198,13 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
|
||||
Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
int n = circularSegments > 0 ? (circularSegments + 3) / 4
|
||||
: GetCircularSegments(radius) / 4;
|
||||
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::OCTAHEDRON);
|
||||
auto pImpl_ = std::make_shared<Impl>(Impl::Shape::Octahedron);
|
||||
pImpl_->Subdivide(n);
|
||||
for_each_n(autoPolicy(pImpl_->NumVert()), pImpl_->vertPos_.begin(),
|
||||
pImpl_->NumVert(), ToSphere({radius}));
|
||||
pImpl_->Finish();
|
||||
// Ignore preceding octahedron.
|
||||
pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1));
|
||||
pImpl_->InitializeOriginal();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -193,12 +236,15 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
int nCrossSection = 0;
|
||||
bool isCone = scaleTop.x == 0.0 && scaleTop.y == 0.0;
|
||||
int idx = 0;
|
||||
PolygonsIdx crossSectionIndexed;
|
||||
for (auto& poly : crossSection) {
|
||||
nCrossSection += poly.size();
|
||||
for (PolyVert& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.pos.x, polyVert.pos.y, 0.0f});
|
||||
polyVert.idx = idx++;
|
||||
SimplePolygonIdx simpleIndexed;
|
||||
for (const glm::vec2& polyVert : poly) {
|
||||
vertPos.push_back({polyVert.x, polyVert.y, 0.0f});
|
||||
simpleIndexed.push_back({polyVert, idx++});
|
||||
}
|
||||
crossSectionIndexed.push_back(simpleIndexed);
|
||||
}
|
||||
for (int i = 1; i < nDivisions + 1; ++i) {
|
||||
float alpha = i / float(nDivisions);
|
||||
@@ -217,7 +263,7 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
triVerts.push_back({nCrossSection * i + j, lastVert - nCrossSection,
|
||||
thisVert - nCrossSection});
|
||||
} else {
|
||||
glm::vec2 pos = transform * poly[vert].pos;
|
||||
glm::vec2 pos = transform * poly[vert];
|
||||
vertPos.push_back({pos.x, pos.y, height * alpha});
|
||||
triVerts.push_back({thisVert, lastVert, thisVert - nCrossSection});
|
||||
triVerts.push_back(
|
||||
@@ -231,7 +277,7 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
if (isCone)
|
||||
for (int j = 0; j < crossSection.size(); ++j) // Duplicate vertex for Genus
|
||||
vertPos.push_back({0.0f, 0.0f, height});
|
||||
std::vector<glm::ivec3> top = Triangulate(crossSection);
|
||||
std::vector<glm::ivec3> top = Triangulate(crossSectionIndexed);
|
||||
for (const glm::ivec3& tri : top) {
|
||||
triVerts.push_back({tri[0], tri[2], tri[1]});
|
||||
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
|
||||
@@ -239,7 +285,9 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeNewReference();
|
||||
pImpl_->meshRelation_.originalID = ReserveIDs(1);
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -258,7 +306,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
float radius = 0.0f;
|
||||
for (const auto& poly : crossSection) {
|
||||
for (const auto& vert : poly) {
|
||||
radius = fmax(radius, vert.pos.x);
|
||||
radius = fmax(radius, vert.x);
|
||||
}
|
||||
}
|
||||
int nDivisions =
|
||||
@@ -271,7 +319,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
for (const auto& poly : crossSection) {
|
||||
int start = -1;
|
||||
for (int polyVert = 0; polyVert < poly.size(); ++polyVert) {
|
||||
if (poly[polyVert].pos.x <= 0) {
|
||||
if (poly[polyVert].x <= 0) {
|
||||
start = polyVert;
|
||||
break;
|
||||
}
|
||||
@@ -285,7 +333,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
|
||||
triVerts.push_back({startVert + slice, startVert + lastSlice,
|
||||
lastStart + lastSlice});
|
||||
@@ -295,11 +343,11 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
}
|
||||
} else { // poly crosses zero
|
||||
int polyVert = start;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
do {
|
||||
glm::vec2 lastPos = pos;
|
||||
polyVert = (polyVert + 1) % poly.size();
|
||||
pos = poly[polyVert].pos;
|
||||
pos = poly[polyVert];
|
||||
if (pos.x > 0) {
|
||||
if (lastPos.x <= 0) {
|
||||
float a = pos.x / (pos.x - lastPos.x);
|
||||
@@ -309,7 +357,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
for (int slice = 0; slice < nDivisions; ++slice) {
|
||||
int lastSlice = (slice == 0 ? nDivisions : slice) - 1;
|
||||
float phi = slice * dPhi;
|
||||
glm::vec2 pos = poly[polyVert].pos;
|
||||
glm::vec2 pos = poly[polyVert];
|
||||
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
|
||||
if (lastPos.x > 0) {
|
||||
triVerts.push_back({startVert + slice, startVert + lastSlice,
|
||||
@@ -338,7 +386,9 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeNewReference();
|
||||
pImpl_->meshRelation_.originalID = ReserveIDs(1);
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -357,12 +407,7 @@ Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold(std::make_shared<Impl>(CsgLeafNode::Compose(children)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of Compose().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
Components Manifold::GetComponents() const {
|
||||
Graph graph;
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
for (int i = 0; i < NumVert(); ++i) {
|
||||
@@ -374,16 +419,21 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
}
|
||||
std::vector<int> components;
|
||||
const int numLabel = ConnectedComponents(components, graph);
|
||||
return {components, numLabel};
|
||||
}
|
||||
|
||||
if (numLabel == 1) {
|
||||
std::vector<Manifold> Manifold::Decompose(Components components) const {
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
|
||||
if (components.numComponents == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
VecDH<int> vertLabel(components);
|
||||
VecDH<int> vertLabel(components.indices);
|
||||
|
||||
std::vector<Manifold> meshes;
|
||||
for (int i = 0; i < numLabel; ++i) {
|
||||
for (int i = 0; i < components.numComponents; ++i) {
|
||||
auto impl = std::make_shared<Impl>();
|
||||
// inherit original object's precision
|
||||
impl->precision_ = pImpl_->precision_;
|
||||
@@ -418,4 +468,13 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of Compose().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
return Decompose(GetComponents());
|
||||
}
|
||||
} // namespace manifold
|
||||
|
||||
@@ -40,16 +40,6 @@ struct TransformNormals {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateTriBary {
|
||||
const int nextBary;
|
||||
|
||||
__host__ __device__ BaryRef operator()(BaryRef ref) {
|
||||
for (int i : {0, 1, 2})
|
||||
if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateHalfedge {
|
||||
const int nextVert;
|
||||
const int nextEdge;
|
||||
@@ -64,6 +54,15 @@ struct UpdateHalfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshIDs {
|
||||
const int offset;
|
||||
|
||||
__host__ __device__ TriRef operator()(TriRef ref) {
|
||||
ref.meshID += offset;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckOverlap {
|
||||
const Box *boxes;
|
||||
const size_t i;
|
||||
@@ -128,7 +127,7 @@ std::shared_ptr<CsgNode> CsgLeafNode::Transform(const glm::mat4x3 &m) const {
|
||||
return std::make_shared<CsgLeafNode>(pImpl_, m * glm::mat4(transform_));
|
||||
}
|
||||
|
||||
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::LEAF; }
|
||||
CsgNodeType CsgLeafNode::GetNodeType() const { return CsgNodeType::Leaf; }
|
||||
|
||||
/**
|
||||
* Efficient union of a set of pairwise disjoint meshes.
|
||||
@@ -139,7 +138,6 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 0;
|
||||
int numBary = 0;
|
||||
for (auto &node : nodes) {
|
||||
float nodeOldScale = node->pImpl_->bBox_.Scale();
|
||||
float nodeNewScale =
|
||||
@@ -153,7 +151,6 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
numVert += node->pImpl_->NumVert();
|
||||
numEdge += node->pImpl_->NumEdge();
|
||||
numTri += node->pImpl_->NumTri();
|
||||
numBary += node->pImpl_->meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
Manifold::Impl combined;
|
||||
@@ -162,14 +159,13 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
combined.halfedge_.resize(2 * numEdge);
|
||||
combined.faceNormal_.resize(numTri);
|
||||
combined.halfedgeTangent_.resize(2 * numEdge);
|
||||
combined.meshRelation_.barycentric.resize(numBary);
|
||||
combined.meshRelation_.triBary.resize(numTri);
|
||||
combined.meshRelation_.triRef.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
int nextVert = 0;
|
||||
int nextEdge = 0;
|
||||
int nextTri = 0;
|
||||
int nextBary = 0;
|
||||
int i = 0;
|
||||
for (auto &node : nodes) {
|
||||
if (node->transform_ == glm::mat4x3(1.0f)) {
|
||||
copy(policy, node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),
|
||||
@@ -195,31 +191,31 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
copy(policy, node->pImpl_->halfedgeTangent_.begin(),
|
||||
node->pImpl_->halfedgeTangent_.end(),
|
||||
combined.halfedgeTangent_.begin() + nextEdge);
|
||||
copy(policy, node->pImpl_->meshRelation_.barycentric.begin(),
|
||||
node->pImpl_->meshRelation_.barycentric.end(),
|
||||
combined.meshRelation_.barycentric.begin() + nextBary);
|
||||
transform(policy, node->pImpl_->meshRelation_.triBary.begin(),
|
||||
node->pImpl_->meshRelation_.triBary.end(),
|
||||
combined.meshRelation_.triBary.begin() + nextTri,
|
||||
UpdateTriBary({nextBary}));
|
||||
transform(policy, node->pImpl_->halfedge_.begin(),
|
||||
node->pImpl_->halfedge_.end(),
|
||||
combined.halfedge_.begin() + nextEdge,
|
||||
UpdateHalfedge({nextVert, nextEdge, nextTri}));
|
||||
|
||||
// Since the nodes may be copies containing the same meshIDs, it is
|
||||
// important to increment them separately so that each node instance gets
|
||||
// important to add an offset so that each node instance gets
|
||||
// unique meshIDs.
|
||||
combined.IncrementMeshIDs(nextTri, node->pImpl_->NumTri());
|
||||
const int offset = i++ * Manifold::Impl::meshIDCounter_;
|
||||
transform(policy, node->pImpl_->meshRelation_.triRef.begin(),
|
||||
node->pImpl_->meshRelation_.triRef.end(),
|
||||
combined.meshRelation_.triRef.begin() + nextTri,
|
||||
UpdateMeshIDs({offset}));
|
||||
for (const auto pair : node->pImpl_->meshRelation_.meshIDtransform) {
|
||||
combined.meshRelation_.meshIDtransform[pair.first + offset] = pair.second;
|
||||
}
|
||||
|
||||
nextVert += node->pImpl_->NumVert();
|
||||
nextEdge += 2 * node->pImpl_->NumEdge();
|
||||
nextTri += node->pImpl_->NumTri();
|
||||
nextBary += node->pImpl_->meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
// required to remove parts that are smaller than the precision
|
||||
combined.SimplifyTopology();
|
||||
combined.Finish();
|
||||
combined.IncrementMeshIDs();
|
||||
return combined;
|
||||
}
|
||||
|
||||
@@ -258,21 +254,21 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
auto &children_ = impl_->children_;
|
||||
if (children_.size() > 1) {
|
||||
switch (impl_->op_) {
|
||||
case CsgNodeType::UNION:
|
||||
case CsgNodeType::Union:
|
||||
BatchUnion();
|
||||
break;
|
||||
case CsgNodeType::INTERSECTION: {
|
||||
case CsgNodeType::Intersection: {
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> impls;
|
||||
for (auto &child : children_) {
|
||||
impls.push_back(
|
||||
std::dynamic_pointer_cast<CsgLeafNode>(child)->GetImpl());
|
||||
}
|
||||
BatchBoolean(Manifold::OpType::INTERSECT, impls);
|
||||
BatchBoolean(Manifold::OpType::Intersect, impls);
|
||||
children_.clear();
|
||||
children_.push_back(std::make_shared<CsgLeafNode>(impls.front()));
|
||||
break;
|
||||
};
|
||||
case CsgNodeType::DIFFERENCE: {
|
||||
case CsgNodeType::Difference: {
|
||||
// take the lhs out and treat the remaining nodes as the rhs, perform
|
||||
// union optimization for them
|
||||
auto lhs = std::dynamic_pointer_cast<CsgLeafNode>(children_.front());
|
||||
@@ -281,12 +277,12 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
auto rhs = std::dynamic_pointer_cast<CsgLeafNode>(children_.front());
|
||||
children_.clear();
|
||||
Boolean3 boolean(*lhs->GetImpl(), *rhs->GetImpl(),
|
||||
Manifold::OpType::SUBTRACT);
|
||||
Manifold::OpType::Subtract);
|
||||
children_.push_back(
|
||||
std::make_shared<CsgLeafNode>(std::make_shared<Manifold::Impl>(
|
||||
boolean.Result(Manifold::OpType::SUBTRACT))));
|
||||
boolean.Result(Manifold::OpType::Subtract))));
|
||||
};
|
||||
case CsgNodeType::LEAF:
|
||||
case CsgNodeType::Leaf:
|
||||
// unreachable
|
||||
break;
|
||||
}
|
||||
@@ -305,7 +301,7 @@ std::shared_ptr<CsgLeafNode> CsgOpNode::ToLeafNode() const {
|
||||
void CsgOpNode::BatchBoolean(
|
||||
Manifold::OpType operation,
|
||||
std::vector<std::shared_ptr<const Manifold::Impl>> &results) {
|
||||
ASSERT(operation != Manifold::OpType::SUBTRACT, logicErr,
|
||||
ASSERT(operation != Manifold::OpType::Subtract, logicErr,
|
||||
"BatchBoolean doesn't support Difference.");
|
||||
auto cmpFn = [](std::shared_ptr<const Manifold::Impl> a,
|
||||
std::shared_ptr<const Manifold::Impl> b) {
|
||||
@@ -391,7 +387,7 @@ void CsgOpNode::BatchUnion() const {
|
||||
std::make_shared<const Manifold::Impl>(CsgLeafNode::Compose(tmp)));
|
||||
}
|
||||
}
|
||||
BatchBoolean(Manifold::OpType::ADD, impls);
|
||||
BatchBoolean(Manifold::OpType::Add, impls);
|
||||
children_.erase(children_.begin() + start, children_.end());
|
||||
children_.push_back(std::make_shared<CsgLeafNode>(impls.back()));
|
||||
// move it to the front as we process from the back, and the newly added
|
||||
@@ -428,15 +424,15 @@ std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
|
||||
newChildren.push_back(grandchild->Transform(child->GetTransform()));
|
||||
}
|
||||
} else {
|
||||
if (!finalize || child->GetNodeType() == CsgNodeType::LEAF) {
|
||||
if (!finalize || child->GetNodeType() == CsgNodeType::Leaf) {
|
||||
newChildren.push_back(child);
|
||||
} else {
|
||||
newChildren.push_back(child->ToLeafNode());
|
||||
}
|
||||
}
|
||||
// special handling for difference: we treat it as first - (second + third +
|
||||
// ...) so op = UNION after the first node
|
||||
if (op == CsgNodeType::DIFFERENCE) op = CsgNodeType::UNION;
|
||||
// ...) so op = Union after the first node
|
||||
if (op == CsgNodeType::Difference) op = CsgNodeType::Union;
|
||||
}
|
||||
children_ = newChildren;
|
||||
return children_;
|
||||
@@ -444,14 +440,14 @@ std::vector<std::shared_ptr<CsgNode>> &CsgOpNode::GetChildren(
|
||||
|
||||
void CsgOpNode::SetOp(Manifold::OpType op) {
|
||||
switch (op) {
|
||||
case Manifold::OpType::ADD:
|
||||
impl_->op_ = CsgNodeType::UNION;
|
||||
case Manifold::OpType::Add:
|
||||
impl_->op_ = CsgNodeType::Union;
|
||||
break;
|
||||
case Manifold::OpType::SUBTRACT:
|
||||
impl_->op_ = CsgNodeType::DIFFERENCE;
|
||||
case Manifold::OpType::Subtract:
|
||||
impl_->op_ = CsgNodeType::Difference;
|
||||
break;
|
||||
case Manifold::OpType::INTERSECT:
|
||||
impl_->op_ = CsgNodeType::INTERSECTION;
|
||||
case Manifold::OpType::Intersect:
|
||||
impl_->op_ = CsgNodeType::Intersection;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
namespace manifold {
|
||||
|
||||
enum class CsgNodeType { UNION, INTERSECTION, DIFFERENCE, LEAF };
|
||||
enum class CsgNodeType { Union, Intersection, Difference, Leaf };
|
||||
|
||||
class CsgLeafNode;
|
||||
|
||||
|
||||
@@ -60,19 +60,19 @@ struct ShortEdge {
|
||||
|
||||
struct FlagEdge {
|
||||
const Halfedge* halfedge;
|
||||
const BaryRef* triBary;
|
||||
const TriRef* triRef;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag redundant edges - those where the startVert is surrounded by only
|
||||
// two original triangles.
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const TriRef ref0 = triRef[edge / 3];
|
||||
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
|
||||
const BaryRef ref1 = triBary[current / 3];
|
||||
const TriRef ref1 = triRef[current / 3];
|
||||
while (current != edge) {
|
||||
current = NextHalfedge(halfedge[current].pairedHalfedge);
|
||||
int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
const TriRef ref = triRef[tri];
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return false;
|
||||
@@ -168,7 +168,7 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triBary.cptrD()})) -
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triRef.cptrD()})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
@@ -213,8 +213,11 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
halfedge_.push_back({outsideVert, endVert, -1, newFace});
|
||||
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, current);
|
||||
if (meshRelation_.triBary.size() > 0)
|
||||
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
newHalfedge += 3;
|
||||
@@ -227,8 +230,11 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, opposite);
|
||||
PairUp(newHalfedge, newHalfedge - 3);
|
||||
if (meshRelation_.triBary.size() > 0)
|
||||
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
break;
|
||||
@@ -301,7 +307,8 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
|
||||
const Halfedge toRemove = halfedge_[edge];
|
||||
if (toRemove.pairedHalfedge < 0) return;
|
||||
@@ -326,24 +333,32 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
|
||||
// Orbit startVert
|
||||
int start = halfedge_[tri1edge[1]].pairedHalfedge;
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const BaryRef ref1 = triBary[toRemove.pairedHalfedge / 3];
|
||||
if (!shortEdge) {
|
||||
current = start;
|
||||
TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];
|
||||
glm::vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
glm::vec3 pNext = vertPos_[halfedge_[current].endVert];
|
||||
const int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
const TriRef ref = triRef[tri];
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
// Don't collapse if the edge is not redundant (this may have changed due
|
||||
// to the collapse of neighbors).
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return;
|
||||
if (ref.meshID != refCheck.meshID || ref.tri != refCheck.tri) {
|
||||
refCheck = triRef[edge / 3];
|
||||
if (ref.meshID != refCheck.meshID || ref.tri != refCheck.tri) {
|
||||
return;
|
||||
} else {
|
||||
// Don't collapse if the edges separating the faces are not colinear
|
||||
// (can happen when the two faces are coplanar).
|
||||
if (CCW(projection * pOld, projection * pLast, projection * pNew,
|
||||
precision_) != 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't collapse edge if it would cause a triangle to invert.
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
if (CCW(projection * pNext, projection * pLast, projection * pNew,
|
||||
precision_) < 0)
|
||||
return;
|
||||
@@ -358,18 +373,30 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
CollapseTri(tri1edge);
|
||||
|
||||
// Orbit startVert
|
||||
const int tri0 = edge / 3;
|
||||
const int tri1 = toRemove.pairedHalfedge / 3;
|
||||
const int triVert0 = (edge + 1) % 3;
|
||||
const int triVert1 = toRemove.pairedHalfedge % 3;
|
||||
const int prop0 = triProp.size() > 0 ? triProp[tri0][edge % 3] : -1;
|
||||
const int prop1 = triProp.size() > 0
|
||||
? triProp[tri1][(toRemove.pairedHalfedge + 1) % 3]
|
||||
: -1;
|
||||
current = start;
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
|
||||
if (!shortEdge) {
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
triBary[tri].vertBary[vIdx] =
|
||||
(ref0.meshID == triBary[tri].meshID && ref0.tri == triBary[tri].tri)
|
||||
? ref0.vertBary[(edge + 1) % 3]
|
||||
: ref1.vertBary[toRemove.pairedHalfedge % 3];
|
||||
if (triProp.size() > 0) {
|
||||
if (triProp[tri][vIdx] == prop0) {
|
||||
triProp[tri][vIdx] = triProp[tri0][triVert0];
|
||||
} else if (triProp[tri][vIdx] == prop1) {
|
||||
triProp[tri][vIdx] = triProp[tri1][triVert1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int vert = halfedge_[current].endVert;
|
||||
@@ -391,7 +418,7 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
if (edge < 0) return;
|
||||
const int pair = halfedge_[edge].pairedHalfedge;
|
||||
@@ -431,23 +458,28 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
const int tri0 = halfedge_[tri0edge[0]].face;
|
||||
const int tri1 = halfedge_[tri1edge[0]].face;
|
||||
faceNormal_[tri0] = faceNormal_[tri1];
|
||||
triBary[tri0] = triBary[tri1];
|
||||
triBary[tri0].vertBary[perm0[1]] = triBary[tri1].vertBary[perm1[0]];
|
||||
triBary[tri0].vertBary[perm0[0]] = triBary[tri1].vertBary[perm1[2]];
|
||||
// Calculate a new barycentric coordinate for the split triangle.
|
||||
const glm::vec3 uvw0 = UVW(triBary[tri1].vertBary[perm1[0]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
const glm::vec3 uvw1 = UVW(triBary[tri1].vertBary[perm1[1]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
triRef[tri0] = triRef[tri1];
|
||||
const float l01 = glm::length(v[1] - v[0]);
|
||||
const float l02 = glm::length(v[2] - v[0]);
|
||||
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
|
||||
const glm::vec3 uvw2 = a * uvw0 + (1 - a) * uvw1;
|
||||
// And assign it.
|
||||
const int newBary = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.push_back(uvw2);
|
||||
triBary[tri1].vertBary[perm1[0]] = newBary;
|
||||
triBary[tri0].vertBary[perm0[2]] = newBary;
|
||||
// Update properties if applicable
|
||||
if (meshRelation_.properties.size() > 0) {
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
VecDH<float>& prop = meshRelation_.properties;
|
||||
triProp[tri0] = triProp[tri1];
|
||||
triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]];
|
||||
triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]];
|
||||
const int numProp = NumProp();
|
||||
const int newProp = prop.size() / numProp;
|
||||
const int propIdx0 = triProp[tri1][perm1[0]];
|
||||
const int propIdx1 = triProp[tri1][perm1[1]];
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
prop.push_back(a * prop[numProp * propIdx0 + p] +
|
||||
(1 - a) * prop[numProp * propIdx1 + p]);
|
||||
}
|
||||
triProp[tri1][perm1[0]] = newProp;
|
||||
triProp[tri0][perm0[2]] = newProp;
|
||||
}
|
||||
|
||||
// if the new edge already exists, duplicate the verts and split the mesh.
|
||||
int current = halfedge_[tri1edge[0]].pairedHalfedge;
|
||||
|
||||
@@ -30,15 +30,14 @@ namespace manifold {
|
||||
* faceNormal_ values are retained, repeated as necessary.
|
||||
*/
|
||||
void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
const VecDH<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary) {
|
||||
const VecDH<TriRef>& halfedgeRef) {
|
||||
VecDH<glm::ivec3> triVerts;
|
||||
VecDH<glm::vec3> triNormal;
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
triBary.resize(0);
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
triRef.resize(0);
|
||||
triVerts.reserve(halfedge_.size());
|
||||
triNormal.reserve(halfedge_.size());
|
||||
triBary.reserve(halfedge_.size());
|
||||
triRef.reserve(halfedge_.size());
|
||||
|
||||
for (int face = 0; face < faceEdge.size() - 1; ++face) {
|
||||
const int firstEdge = faceEdge[face];
|
||||
@@ -47,12 +46,6 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
ASSERT(numEdge >= 3, topologyErr, "face has less than three edges.");
|
||||
const glm::vec3 normal = faceNormal_[face];
|
||||
|
||||
auto linearSearch = [](const int* mapping, int value) {
|
||||
int i = 0;
|
||||
while (mapping[i] != value) ++i;
|
||||
return i;
|
||||
};
|
||||
|
||||
if (numEdge == 3) { // Single triangle
|
||||
int mapping[3] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -72,11 +65,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
int index = linearSearch(mapping, tri[k]);
|
||||
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
} else if (numEdge == 4) { // Pair of triangles
|
||||
int mapping[4] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -125,30 +114,19 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
for (auto tri : {tri0, tri1}) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
int index = linearSearch(mapping, tri[k]);
|
||||
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
}
|
||||
} else { // General triangulation
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
|
||||
|
||||
std::map<int, int> vertBary;
|
||||
for (int j = firstEdge; j < lastEdge; ++j)
|
||||
vertBary[halfedge_[j].startVert] = halfedgeBary[j];
|
||||
|
||||
const Polygons polys = Face2Polygons(face, projection, faceEdge);
|
||||
const PolygonsIdx polys = Face2Polygons(face, projection, faceEdge);
|
||||
|
||||
std::vector<glm::ivec3> newTris = Triangulate(polys, precision_);
|
||||
|
||||
for (auto tri : newTris) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
triBary.back().vertBary[k] = vertBary[tri[k]];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,8 +138,8 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
* For the input face index, return a set of 2D polygons formed by the input
|
||||
* projection of the vertices.
|
||||
*/
|
||||
Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const {
|
||||
PolygonsIdx Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const {
|
||||
const int firstEdge = faceEdge[face];
|
||||
const int lastEdge = faceEdge[face + 1];
|
||||
|
||||
@@ -173,7 +151,7 @@ Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
|
||||
ASSERT(inserted, topologyErr, "face has duplicate vertices.");
|
||||
}
|
||||
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
int startEdge = 0;
|
||||
int thisEdge = startEdge;
|
||||
while (1) {
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "graph.h"
|
||||
#include "hashtable.h"
|
||||
#include "par.h"
|
||||
|
||||
namespace {
|
||||
@@ -41,6 +43,12 @@ __host__ __device__ void AtomicAddVec3(glm::vec3& target,
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ int FlipHalfedge(int halfedge) {
|
||||
const int tri = halfedge / 3;
|
||||
const int vert = 2 - (halfedge - 3 * tri);
|
||||
return 3 * tri + vert;
|
||||
}
|
||||
|
||||
struct Normalize {
|
||||
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
|
||||
};
|
||||
@@ -63,6 +71,42 @@ struct TransformNormals {
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformTangents {
|
||||
const glm::mat3 transform;
|
||||
const bool invert;
|
||||
const glm::vec4* oldTangents;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::vec4&, int> inOut) {
|
||||
glm::vec4& tangent = thrust::get<0>(inOut);
|
||||
int edge = thrust::get<1>(inOut);
|
||||
if (invert) {
|
||||
edge = halfedge[FlipHalfedge(edge)].pairedHalfedge;
|
||||
}
|
||||
|
||||
tangent = glm::vec4(transform * glm::vec3(oldTangents[edge]),
|
||||
oldTangents[edge].w);
|
||||
}
|
||||
};
|
||||
|
||||
struct FlipTris {
|
||||
Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& bary = thrust::get<0>(inOut);
|
||||
const int tri = thrust::get<1>(inOut);
|
||||
|
||||
thrust::swap(halfedge[3 * tri], halfedge[3 * tri + 2]);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
thrust::swap(halfedge[3 * tri + i].startVert,
|
||||
halfedge[3 * tri + i].endVert);
|
||||
halfedge[3 * tri + i].pairedHalfedge =
|
||||
FlipHalfedge(halfedge[3 * tri + i].pairedHalfedge);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AssignNormals {
|
||||
glm::vec3* vertNormal;
|
||||
const glm::vec3* vertPos;
|
||||
@@ -157,45 +201,25 @@ struct ReindexTriVerts {
|
||||
}
|
||||
};
|
||||
|
||||
struct InitializeBaryRef {
|
||||
struct InitializeTriRef {
|
||||
const int meshID;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef&, int> inOut) {
|
||||
BaryRef& baryRef = thrust::get<0>(inOut);
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& baryRef = thrust::get<0>(inOut);
|
||||
int tri = thrust::get<1>(inOut);
|
||||
|
||||
baryRef.meshID = meshID;
|
||||
baryRef.originalID = meshID;
|
||||
baryRef.tri = tri;
|
||||
baryRef.vertBary = {-3, -2, -1};
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkMeshID {
|
||||
int* includesMeshID;
|
||||
|
||||
__host__ __device__ void operator()(BaryRef& ref) {
|
||||
includesMeshID[ref.meshID] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshID {
|
||||
const int* meshIDold2new;
|
||||
const int meshIDoffset;
|
||||
const HashTableD<uint32_t> meshIDold2new;
|
||||
|
||||
__host__ __device__ void operator()(BaryRef& ref) {
|
||||
ref.meshID = meshIDold2new[ref.meshID] + meshIDoffset;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckProperties {
|
||||
const int numSets;
|
||||
|
||||
__host__ __device__ bool operator()(glm::ivec3 triProp) {
|
||||
bool good = true;
|
||||
for (int i : {0, 1, 2}) good &= (triProp[i] >= 0 && triProp[i] < numSets);
|
||||
return good;
|
||||
__host__ __device__ void operator()(TriRef& ref) {
|
||||
ref.meshID = meshIDold2new[ref.meshID];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,6 +227,7 @@ struct CoplanarEdge {
|
||||
float* triArea;
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec3* vertPos;
|
||||
const TriRef* triRef;
|
||||
const glm::ivec3* triProp;
|
||||
const float* prop;
|
||||
const float* propTol;
|
||||
@@ -210,13 +235,17 @@ struct CoplanarEdge {
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<thrust::pair<int, int>&, int> inOut) {
|
||||
thrust::tuple<thrust::pair<int, int>&, thrust::pair<int, int>&, int>
|
||||
inOut) {
|
||||
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
|
||||
const int edgeIdx = thrust::get<1>(inOut);
|
||||
thrust::pair<int, int>& vert2vert = thrust::get<1>(inOut);
|
||||
const int edgeIdx = thrust::get<2>(inOut);
|
||||
|
||||
const Halfedge edge = halfedge[edgeIdx];
|
||||
if (!edge.IsForward()) return;
|
||||
const Halfedge pair = halfedge[edge.pairedHalfedge];
|
||||
if (triRef[edge.face].meshID != triRef[pair.face].meshID) return;
|
||||
|
||||
const glm::vec3 base = vertPos[edge.startVert];
|
||||
|
||||
const int baseNum = edgeIdx - 3 * edge.face;
|
||||
@@ -224,6 +253,23 @@ struct CoplanarEdge {
|
||||
const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
|
||||
const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
|
||||
|
||||
if (numProp > 0) {
|
||||
const int prop0 = triProp[edge.face][baseNum];
|
||||
const int prop1 = triProp[edge.face][edgeNum];
|
||||
bool propEqual = true;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
if (glm::abs(prop[numProp * prop0 + p] - prop[numProp * prop1 + p]) >
|
||||
propTol[p]) {
|
||||
propEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (propEqual) {
|
||||
vert2vert.first = prop0;
|
||||
vert2vert.second = prop1;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3 jointVec = vertPos[pair.startVert] - base;
|
||||
const glm::vec3 edgeVec =
|
||||
vertPos[halfedge[3 * edge.face + edgeNum].startVert] - base;
|
||||
@@ -236,6 +282,8 @@ struct CoplanarEdge {
|
||||
glm::vec3 normal = glm::cross(jointVec, edgeVec);
|
||||
const float area = glm::length(normal);
|
||||
const float areaPair = glm::length(glm::cross(pairVec, jointVec));
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
// Don't link degenerate triangles
|
||||
if (area < length * precision || areaPair < lengthPair * precision) return;
|
||||
|
||||
@@ -263,16 +311,14 @@ struct CoplanarEdge {
|
||||
pairVec + normal * scale * (pairProp - baseProp);
|
||||
|
||||
glm::vec3 cross = glm::cross(iJointVec, iEdgeVec);
|
||||
const float area = glm::max(
|
||||
const float areaP = glm::max(
|
||||
glm::length(cross), glm::length(glm::cross(iPairVec, iJointVec)));
|
||||
const float volume = glm::abs(glm::dot(cross, iPairVec));
|
||||
const float volumeP = glm::abs(glm::dot(cross, iPairVec));
|
||||
// Only operate on consistent triangles
|
||||
if (volume > area * precision) return;
|
||||
if (volumeP > areaP * precision) return;
|
||||
}
|
||||
}
|
||||
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
face2face.first = edge.face;
|
||||
face2face.second = pair.face;
|
||||
}
|
||||
@@ -288,44 +334,207 @@ struct EdgeBox {
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return ConnectedComponents(components, graph);
|
||||
}
|
||||
|
||||
void DedupePropVerts(manifold::VecDH<glm::ivec3>& triProp,
|
||||
const VecDH<thrust::pair<int, int>>& vert2vert) {
|
||||
std::vector<int> vertLabels;
|
||||
const int numLabels = GetLabels(vertLabels, vert2vert, vert2vert.size());
|
||||
|
||||
std::vector<int> label2vert(numLabels);
|
||||
for (int v = 0; v < vert2vert.size(); ++v) {
|
||||
label2vert[vertLabels[v]] = v;
|
||||
}
|
||||
for (int tri = 0; tri < triProp.size(); ++tri) {
|
||||
for (int i : {0, 1, 2})
|
||||
triProp[tri][i] = label2vert[vertLabels[triProp[tri][i]]];
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
std::atomic<int> Manifold::Impl::meshIDCounter_(1);
|
||||
std::atomic<uint32_t> Manifold::Impl::meshIDCounter_(1);
|
||||
|
||||
uint32_t Manifold::Impl::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::meshIDCounter_.fetch_add(n, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
std::vector<float> propertyTolerance) {
|
||||
Mesh mesh;
|
||||
const int numVert = meshGL.NumVert();
|
||||
const int numTri = meshGL.NumTri();
|
||||
|
||||
if (meshGL.numProp < 3) {
|
||||
MarkFailure(Error::MissingPositionProperties);
|
||||
return;
|
||||
}
|
||||
|
||||
mesh.triVerts.resize(numTri);
|
||||
if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) {
|
||||
MarkFailure(Error::MergeVectorsDifferentLengths);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.runTransform.empty() &&
|
||||
12 * meshGL.runOriginalID.size() != meshGL.runTransform.size()) {
|
||||
MarkFailure(Error::TransformWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.runOriginalID.empty() && !meshGL.runIndex.empty() &&
|
||||
meshGL.runOriginalID.size() + 1 != meshGL.runIndex.size()) {
|
||||
MarkFailure(Error::RunIndexWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) {
|
||||
MarkFailure(Error::FaceIDWrongLength);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<int> prop2vert(numVert);
|
||||
std::iota(prop2vert.begin(), prop2vert.end(), 0);
|
||||
for (int i = 0; i < meshGL.mergeFromVert.size(); ++i) {
|
||||
const int from = meshGL.mergeFromVert[i];
|
||||
const int to = meshGL.mergeToVert[i];
|
||||
if (from >= numVert || to >= numVert) {
|
||||
MarkFailure(Error::MergeIndexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
prop2vert[from] = to;
|
||||
}
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
const int vert = meshGL.triVerts[3 * i + j];
|
||||
if (vert < 0 || vert >= numVert) {
|
||||
MarkFailure(Error::VertexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
mesh.triVerts[i][j] = prop2vert[vert];
|
||||
}
|
||||
}
|
||||
|
||||
MeshRelationD relation;
|
||||
|
||||
if (meshGL.numProp > 3) {
|
||||
relation.triProperties.resize(numTri);
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
relation.triProperties[i][j] = meshGL.triVerts[3 * i + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int numProp = meshGL.numProp - 3;
|
||||
relation.numProp = numProp;
|
||||
relation.properties.resize(meshGL.NumVert() * numProp);
|
||||
// This will have unreferenced duplicate positions that will be removed by
|
||||
// Impl::RemoveUnreferencedVerts().
|
||||
mesh.vertPos.resize(meshGL.NumVert());
|
||||
|
||||
for (int i = 0; i < meshGL.NumVert(); ++i) {
|
||||
for (const int j : {0, 1, 2})
|
||||
mesh.vertPos[i][j] = meshGL.vertProperties[meshGL.numProp * i + j];
|
||||
for (int j = 0; j < numProp; ++j)
|
||||
relation.properties[i * numProp + j] =
|
||||
meshGL.vertProperties[meshGL.numProp * i + 3 + j];
|
||||
}
|
||||
|
||||
mesh.halfedgeTangent.resize(meshGL.halfedgeTangent.size() / 4);
|
||||
for (int i = 0; i < mesh.halfedgeTangent.size(); ++i) {
|
||||
for (const int j : {0, 1, 2, 3})
|
||||
mesh.halfedgeTangent[i][j] = meshGL.halfedgeTangent[4 * i + j];
|
||||
}
|
||||
|
||||
if (meshGL.runOriginalID.empty()) {
|
||||
relation.originalID = Impl::ReserveIDs(1);
|
||||
} else {
|
||||
std::vector<uint32_t> runIndex = meshGL.runIndex;
|
||||
if (runIndex.empty()) {
|
||||
runIndex = {0, 3 * meshGL.NumTri()};
|
||||
}
|
||||
relation.triRef.resize(meshGL.NumTri());
|
||||
const int startID = Impl::ReserveIDs(meshGL.runOriginalID.size());
|
||||
for (int i = 0; i < meshGL.runOriginalID.size(); ++i) {
|
||||
const int meshID = startID + i;
|
||||
const int originalID = meshGL.runOriginalID[i];
|
||||
for (int tri = runIndex[i] / 3; tri < runIndex[i + 1] / 3; ++tri) {
|
||||
TriRef& ref = relation.triRef[tri];
|
||||
ref.meshID = meshID;
|
||||
ref.originalID = originalID;
|
||||
ref.tri = meshGL.faceID.empty() ? tri : meshGL.faceID[tri];
|
||||
}
|
||||
|
||||
if (meshGL.runTransform.empty()) {
|
||||
relation.meshIDtransform[meshID] = {originalID};
|
||||
} else {
|
||||
const float* m = meshGL.runTransform.data() + 12 * i;
|
||||
relation.meshIDtransform[meshID] = {
|
||||
originalID,
|
||||
{m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10],
|
||||
m[11]}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*this = Impl(mesh, relation, propertyTolerance, !meshGL.faceID.empty());
|
||||
|
||||
// A Manifold created from an input mesh is never an original - the input is
|
||||
// the original.
|
||||
meshRelation_.originalID = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a manifold from an input triangle Mesh. Will return an empty Manifold
|
||||
* and set an Error Status if the Mesh is not manifold or otherwise invalid.
|
||||
* TODO: update halfedgeTangent during SimplifyTopology.
|
||||
*/
|
||||
Manifold::Impl::Impl(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: vertPos_(mesh.vertPos), halfedgeTangent_(mesh.halfedgeTangent) {
|
||||
Manifold::Impl::Impl(const Mesh& mesh, const MeshRelationD& relation,
|
||||
const std::vector<float>& propertyTolerance,
|
||||
bool hasFaceIDs)
|
||||
: vertPos_(mesh.vertPos),
|
||||
halfedgeTangent_(mesh.halfedgeTangent),
|
||||
meshRelation_(relation) {
|
||||
VecDH<glm::ivec3> triVerts = mesh.triVerts;
|
||||
if (!IsIndexInBounds(triVerts)) {
|
||||
MarkFailure(Error::VERTEX_INDEX_OUT_OF_BOUNDS);
|
||||
MarkFailure(Error::VertexOutOfBounds);
|
||||
return;
|
||||
}
|
||||
RemoveUnreferencedVerts(triVerts);
|
||||
|
||||
CalculateBBox();
|
||||
if (!IsFinite()) {
|
||||
MarkFailure(Error::NON_FINITE_VERTEX);
|
||||
MarkFailure(Error::NonFiniteVertex);
|
||||
return;
|
||||
}
|
||||
SetPrecision();
|
||||
|
||||
CreateHalfedges(triVerts);
|
||||
if (!IsManifold()) {
|
||||
MarkFailure(Error::NOT_MANIFOLD);
|
||||
MarkFailure(Error::NotManifold);
|
||||
return;
|
||||
}
|
||||
CalculateNormals();
|
||||
InitializeNewReference(triProperties, properties, propertyTolerance);
|
||||
if (status_ != Error::NO_ERROR) return;
|
||||
|
||||
InitializeOriginal();
|
||||
if (!hasFaceIDs) {
|
||||
CreateFaces(propertyTolerance);
|
||||
}
|
||||
|
||||
SimplifyTopology();
|
||||
Finish();
|
||||
@@ -339,14 +548,14 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
std::vector<glm::vec3> vertPos;
|
||||
std::vector<glm::ivec3> triVerts;
|
||||
switch (shape) {
|
||||
case Shape::TETRAHEDRON:
|
||||
case Shape::Tetrahedron:
|
||||
vertPos = {{-1.0f, -1.0f, 1.0f},
|
||||
{-1.0f, 1.0f, -1.0f},
|
||||
{1.0f, -1.0f, -1.0f},
|
||||
{1.0f, 1.0f, 1.0f}};
|
||||
triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
break;
|
||||
case Shape::CUBE:
|
||||
case Shape::Cube:
|
||||
vertPos = {{0.0f, 0.0f, 0.0f}, //
|
||||
{1.0f, 0.0f, 0.0f}, //
|
||||
{1.0f, 1.0f, 0.0f}, //
|
||||
@@ -362,7 +571,7 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
{2, 3, 7}, {2, 7, 6}, //
|
||||
{3, 0, 4}, {3, 4, 7}};
|
||||
break;
|
||||
case Shape::OCTAHEDRON:
|
||||
case Shape::Octahedron:
|
||||
vertPos = {{1.0f, 0.0f, 0.0f}, //
|
||||
{-1.0f, 0.0f, 0.0f}, //
|
||||
{0.0f, 1.0f, 0.0f}, //
|
||||
@@ -378,7 +587,9 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
vertPos_ = vertPos;
|
||||
CreateHalfedges(triVerts);
|
||||
Finish();
|
||||
InitializeNewReference();
|
||||
meshRelation_.originalID = ReserveIDs(1);
|
||||
InitializeOriginal();
|
||||
CreateFaces();
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
@@ -401,67 +612,42 @@ void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
ReindexTriVerts({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::ReinitializeReference(int meshID) {
|
||||
// instead of storing the meshID, we store 0 and set the mapping to
|
||||
// 0 -> meshID, because the meshID after boolean operation also starts from 0.
|
||||
void Manifold::Impl::InitializeOriginal() {
|
||||
const int meshID = meshRelation_.originalID;
|
||||
// Don't initialize if it's not an original
|
||||
if (meshID < 0) return;
|
||||
meshRelation_.triRef.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri()),
|
||||
zip(meshRelation_.triBary.begin(), countAt(0)), NumTri(),
|
||||
InitializeBaryRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.originalID = meshID;
|
||||
zip(meshRelation_.triRef.begin(), countAt(0)), NumTri(),
|
||||
InitializeTriRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.meshIDtransform.clear();
|
||||
meshRelation_.meshIDtransform[meshID] = {meshID};
|
||||
}
|
||||
|
||||
int Manifold::Impl::InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance) {
|
||||
meshRelation_.triBary.resize(NumTri());
|
||||
const int nextMeshID = meshIDCounter_.fetch_add(1, std::memory_order_relaxed);
|
||||
ReinitializeReference(nextMeshID);
|
||||
|
||||
const int numProps = propertyTolerance.size();
|
||||
|
||||
VecDH<glm::ivec3> triPropertiesD(triProperties);
|
||||
VecDH<float> propertiesD(properties);
|
||||
VecDH<float> propertyToleranceD(propertyTolerance);
|
||||
|
||||
if (numProps > 0) {
|
||||
if (triProperties.size() != NumTri() && triProperties.size() != 0) {
|
||||
MarkFailure(Error::TRI_PROPERTIES_WRONG_LENGTH);
|
||||
return nextMeshID;
|
||||
};
|
||||
if (properties.size() % numProps != 0) {
|
||||
MarkFailure(Error::PROPERTIES_WRONG_LENGTH);
|
||||
return nextMeshID;
|
||||
};
|
||||
|
||||
const int numSets = properties.size() / numProps;
|
||||
if (!all_of(autoPolicy(triProperties.size()), triPropertiesD.begin(),
|
||||
triPropertiesD.end(), CheckProperties({numSets}))) {
|
||||
MarkFailure(Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
return nextMeshID;
|
||||
};
|
||||
}
|
||||
void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
VecDH<float> propertyToleranceD =
|
||||
propertyTolerance.empty()
|
||||
? VecDH<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());
|
||||
for_each_n(autoPolicy(halfedge_.size()), zip(face2face.begin(), countAt(0)),
|
||||
halfedge_.size(),
|
||||
CoplanarEdge({triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
triPropertiesD.cptrD(), propertiesD.cptrD(),
|
||||
propertyToleranceD.cptrD(), numProps, precision_}));
|
||||
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_}));
|
||||
|
||||
Graph graph;
|
||||
for (int i = 0; i < NumTri(); ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (int i = 0; i < face2face.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = face2face[i];
|
||||
if (edge.first < 0) continue;
|
||||
graph.add_edge(edge.first, edge.second);
|
||||
if (meshRelation_.triProperties.size() > 0) {
|
||||
DedupePropVerts(meshRelation_.triProperties, vert2vert);
|
||||
}
|
||||
|
||||
std::vector<int> components;
|
||||
const int numComponent = ConnectedComponents(components, graph);
|
||||
const int numComponent = GetLabels(components, face2face, NumTri());
|
||||
|
||||
std::vector<int> comp2tri(numComponent, -1);
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
@@ -473,51 +659,9 @@ int Manifold::Impl::InitializeNewReference(
|
||||
}
|
||||
}
|
||||
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
std::map<std::pair<int, int>, int> triVert2bary;
|
||||
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int refTri = comp2tri[components[tri]];
|
||||
if (refTri == tri) continue;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_[3 * refTri + i].startVert;
|
||||
triPos[i] = vertPos_[vert];
|
||||
triVert2bary[{refTri, vert}] = i - 3;
|
||||
}
|
||||
|
||||
glm::ivec3 vertBary;
|
||||
bool coplanar = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_[3 * tri + i].startVert;
|
||||
if (triVert2bary.find({refTri, vert}) == triVert2bary.end()) {
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPos_[vert], triPos, precision_);
|
||||
if (isnan(uvw[0])) {
|
||||
coplanar = false;
|
||||
triVert2bary[{refTri, vert}] = -4;
|
||||
break;
|
||||
}
|
||||
triVert2bary[{refTri, vert}] = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.push_back(uvw);
|
||||
}
|
||||
const int bary = triVert2bary[{refTri, vert}];
|
||||
if (bary < -3) {
|
||||
coplanar = false;
|
||||
break;
|
||||
}
|
||||
vertBary[i] = bary;
|
||||
}
|
||||
|
||||
if (coplanar) {
|
||||
BaryRef& ref = triBary[tri];
|
||||
ref.tri = refTri;
|
||||
ref.vertBary = vertBary;
|
||||
}
|
||||
}
|
||||
|
||||
return nextMeshID;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
for (int tri = 0; tri < NumTri(); ++tri)
|
||||
triRef[tri].tri = comp2tri[components[tri]];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,8 +693,8 @@ void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full recalculation of the face bounding boxes, including updating the
|
||||
* collider, but does not resort the faces.
|
||||
* Does a full recalculation of the face bounding boxes, including updating
|
||||
* the collider, but does not resort the faces.
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
@@ -580,7 +724,12 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
result.precision_ = precision_;
|
||||
result.bBox_ = bBox_;
|
||||
result.halfedge_ = halfedge_;
|
||||
result.halfedgeTangent_ = halfedgeTangent_;
|
||||
result.halfedgeTangent_.resize(halfedgeTangent_.size());
|
||||
|
||||
result.meshRelation_.originalID = -1;
|
||||
for (auto& m : result.meshRelation_.meshIDtransform) {
|
||||
m.second.transform = transform_ * glm::mat4(m.second.transform);
|
||||
}
|
||||
|
||||
result.vertPos_.resize(NumVert());
|
||||
result.faceNormal_.resize(faceNormal_.size());
|
||||
@@ -588,12 +737,27 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(),
|
||||
Transform4x3({transform_}));
|
||||
|
||||
glm::mat3 normalTransform =
|
||||
glm::inverse(glm::transpose(glm::mat3(transform_)));
|
||||
glm::mat3 normalTransform = NormalTransform(transform_);
|
||||
transform(policy, faceNormal_.begin(), faceNormal_.end(),
|
||||
result.faceNormal_.begin(), TransformNormals({normalTransform}));
|
||||
transform(policy, vertNormal_.begin(), vertNormal_.end(),
|
||||
result.vertNormal_.begin(), TransformNormals({normalTransform}));
|
||||
|
||||
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()}));
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
for_each_n(policy, zip(result.meshRelation_.triRef.begin(), countAt(0)),
|
||||
result.NumTri(), FlipTris({result.halfedge_.ptrD()}));
|
||||
}
|
||||
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
// axis-aligned.
|
||||
if (!result.collider_.Transform(transform_)) result.Update();
|
||||
@@ -610,8 +774,8 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the precision based on the bounding box, and limits its minimum value by
|
||||
* the optional input.
|
||||
* Sets the precision based on the bounding box, and limits its minimum value
|
||||
* by the optional input.
|
||||
*/
|
||||
void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
precision_ = glm::max(minPrecision, kTolerance * bBox_.Scale());
|
||||
@@ -621,13 +785,13 @@ void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
/**
|
||||
* If face normals are already present, this function uses them to compute
|
||||
* vertex normals (angle-weighted pseudo-normals); otherwise it also computes
|
||||
* the face normals. Face normals are only calculated when needed because nearly
|
||||
* degenerate faces will accrue rounding error, while the Boolean can retain
|
||||
* their original normal, which is more accurate and can help with merging
|
||||
* coplanar faces.
|
||||
* the face normals. Face normals are only calculated when needed because
|
||||
* nearly degenerate faces will accrue rounding error, while the Boolean can
|
||||
* retain their original normal, which is more accurate and can help with
|
||||
* merging coplanar faces.
|
||||
*
|
||||
* If the face normals have been invalidated by an operation like Warp(), ensure
|
||||
* you do faceNormal_.resize(0) before calling this function to force
|
||||
* If the face normals have been invalidated by an operation like Warp(),
|
||||
* ensure you do faceNormal_.resize(0) before calling this function to force
|
||||
* recalculation.
|
||||
*/
|
||||
void Manifold::Impl::CalculateNormals() {
|
||||
@@ -650,35 +814,26 @@ void Manifold::Impl::CalculateNormals() {
|
||||
* Remaps all the contained meshIDs to new unique values to represent new
|
||||
* instances of these meshes.
|
||||
*/
|
||||
void Manifold::Impl::IncrementMeshIDs(int start, int length) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
ASSERT(start >= 0 && length >= 0 && start + length <= triBary.size(),
|
||||
logicErr, "out of bounds");
|
||||
const auto policy = autoPolicy(length);
|
||||
// Use double the space since the Boolean has P and Q instances.
|
||||
VecDH<int> includesMeshID(2 * Manifold::Impl::meshIDCounter_, 0);
|
||||
void Manifold::Impl::IncrementMeshIDs() {
|
||||
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
|
||||
// Update keys of the transform map
|
||||
std::map<int, Relation> oldTransforms;
|
||||
std::swap(meshRelation_.meshIDtransform, oldTransforms);
|
||||
const int numMeshIDs = oldTransforms.size();
|
||||
int nextMeshID = ReserveIDs(numMeshIDs);
|
||||
for (const auto& pair : oldTransforms) {
|
||||
meshIDold2new.D().Insert(pair.first, nextMeshID);
|
||||
meshRelation_.meshIDtransform[nextMeshID++] = pair.second;
|
||||
}
|
||||
|
||||
auto begin = triBary.begin() + start;
|
||||
auto end = begin + length;
|
||||
|
||||
for_each(policy, begin, end, MarkMeshID({includesMeshID.ptrD()}));
|
||||
|
||||
inclusive_scan(autoPolicy(includesMeshID.size()), includesMeshID.begin(),
|
||||
includesMeshID.end(), includesMeshID.begin());
|
||||
|
||||
const int numMeshIDs = includesMeshID[includesMeshID.size() - 1];
|
||||
const int meshIDstart = Manifold::Impl::meshIDCounter_.fetch_add(
|
||||
numMeshIDs, std::memory_order_relaxed);
|
||||
|
||||
// We do start - 1 because the inclusive scan makes our first index 1 instead
|
||||
// of 0.
|
||||
for_each(policy, begin, end,
|
||||
UpdateMeshID({includesMeshID.cptrD(), meshIDstart - 1}));
|
||||
const int numTri = NumTri();
|
||||
for_each_n(autoPolicy(numTri), meshRelation_.triRef.begin(), numTri,
|
||||
UpdateMeshID({meshIDold2new.D()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sparse array of the bounding box overlaps between the edges of the
|
||||
* input manifold, Q and the faces of this manifold. Returned indices only
|
||||
* Returns a sparse array of the bounding box overlaps between the edges of
|
||||
* the input manifold, Q and the faces of this manifold. Returned indices only
|
||||
* point to forward halfedges.
|
||||
*/
|
||||
SparseIndices Manifold::Impl::EdgeCollisions(const Impl& Q) const {
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "collider.h"
|
||||
#include "manifold.h"
|
||||
#include "optional_assert.h"
|
||||
#include "polygon.h"
|
||||
#include "shared.h"
|
||||
#include "sparse.h"
|
||||
#include "utils.h"
|
||||
@@ -25,16 +28,24 @@ namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
struct Manifold::Impl {
|
||||
struct MeshRelationD {
|
||||
VecDH<glm::vec3> barycentric;
|
||||
VecDH<BaryRef> triBary;
|
||||
/// The meshID of this Manifold if it is an original; -1 otherwise.
|
||||
struct Relation {
|
||||
int originalID = -1;
|
||||
glm::mat4x3 transform = glm::mat4x3(1);
|
||||
bool backSide = false;
|
||||
};
|
||||
struct MeshRelationD {
|
||||
/// The originalID of this Manifold if it is an original; -1 otherwise.
|
||||
int originalID = -1;
|
||||
int numProp = 0;
|
||||
VecDH<TriRef> triRef;
|
||||
VecDH<glm::ivec3> triProperties;
|
||||
VecDH<float> properties;
|
||||
std::map<int, Relation> meshIDtransform;
|
||||
};
|
||||
|
||||
Box bBox_;
|
||||
float precision_ = -1;
|
||||
Error status_ = Error::NO_ERROR;
|
||||
Error status_ = Error::NoError;
|
||||
VecDH<glm::vec3> vertPos_;
|
||||
VecDH<Halfedge> halfedge_;
|
||||
VecDH<glm::vec3> vertNormal_;
|
||||
@@ -43,27 +54,24 @@ struct Manifold::Impl {
|
||||
MeshRelationD meshRelation_;
|
||||
Collider collider_;
|
||||
|
||||
static std::atomic<int> meshIDCounter_;
|
||||
static std::atomic<uint32_t> meshIDCounter_;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
|
||||
Impl() {}
|
||||
enum class Shape { TETRAHEDRON, CUBE, OCTAHEDRON };
|
||||
enum class Shape { Tetrahedron, Cube, Octahedron };
|
||||
Impl(Shape);
|
||||
|
||||
Impl(const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
|
||||
int InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
Impl(const MeshGL&, std::vector<float> propertyTolerance = {});
|
||||
Impl(const Mesh&, const MeshRelationD& relation,
|
||||
const std::vector<float>& propertyTolerance = {},
|
||||
bool hasFaceIDs = false);
|
||||
|
||||
void CreateFaces(const std::vector<float>& propertyTolerance = {});
|
||||
void RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts);
|
||||
void ReinitializeReference(int meshID);
|
||||
void InitializeOriginal();
|
||||
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
|
||||
void CalculateNormals();
|
||||
void IncrementMeshIDs(int start, int length);
|
||||
void IncrementMeshIDs();
|
||||
|
||||
void Update();
|
||||
void MarkFailure(Error status);
|
||||
@@ -75,6 +83,11 @@ struct Manifold::Impl {
|
||||
int NumVert() const { return vertPos_.size(); }
|
||||
int NumEdge() const { return halfedge_.size() / 2; }
|
||||
int NumTri() const { return halfedge_.size() / 3; }
|
||||
int NumProp() const { return meshRelation_.numProp; }
|
||||
int NumPropVert() const {
|
||||
return NumProp() == 0 ? NumVert()
|
||||
: meshRelation_.properties.size() / NumProp();
|
||||
}
|
||||
|
||||
// properties.cu
|
||||
Properties GetProperties() const;
|
||||
@@ -92,16 +105,16 @@ struct Manifold::Impl {
|
||||
void Finish();
|
||||
void SortVerts();
|
||||
void ReindexVerts(const VecDH<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);
|
||||
|
||||
// face_op.cu
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary);
|
||||
Polygons Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<TriRef>& halfedgeRef);
|
||||
PolygonsIdx Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
|
||||
// edge_op.cu
|
||||
void SimplifyTopology();
|
||||
@@ -116,7 +129,7 @@ struct Manifold::Impl {
|
||||
|
||||
// smoothing.cu
|
||||
void CreateTangents(const std::vector<Smoothness>&);
|
||||
MeshRelationD Subdivide(int n);
|
||||
VecDH<Barycentric> Subdivide(int n);
|
||||
void Refine(int n);
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "boolean3.h"
|
||||
#include "csg_tree.h"
|
||||
#include "impl.h"
|
||||
@@ -75,44 +79,49 @@ Manifold& Manifold::operator=(const Manifold& other) {
|
||||
}
|
||||
|
||||
CsgLeafNode& Manifold::GetCsgLeafNode() const {
|
||||
if (pNode_->GetNodeType() != CsgNodeType::LEAF) {
|
||||
if (pNode_->GetNodeType() != CsgNodeType::Leaf) {
|
||||
pNode_ = pNode_->ToLeafNode();
|
||||
}
|
||||
return *std::static_pointer_cast<CsgLeafNode>(pNode_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a MeshGL into a Manifold, retaining its properties and merging only
|
||||
* the positions according to the merge vectors. Will return an empty Manifold
|
||||
* and set an Error Status if the result is not an oriented 2-manifold. Will
|
||||
* collapse degenerate triangles and unnecessary vertices.
|
||||
*
|
||||
* All fields are read, making this structure suitable for a lossless round-trip
|
||||
* of data from GetMeshGL. For multi-material input, use ReserveIDs to set a
|
||||
* unique originalID for each material, and sort the materials into triangle
|
||||
* runs.
|
||||
*
|
||||
* @param meshGL The input MeshGL.
|
||||
* @param propertyTolerance A vector of precision values for each property
|
||||
* beyond position. If specified, the propertyTolerance vector must have size =
|
||||
* numProp - 3. This is the amount of interpolation error allowed before two
|
||||
* neighboring triangles are considered to be on a property boundary edge.
|
||||
* Property boundary edges will be retained across operations even if the
|
||||
* triangles are coplanar. Defaults to 1e-5, which works well for most
|
||||
* properties in the [-1, 1] range.
|
||||
*/
|
||||
Manifold::Manifold(const MeshGL& meshGL,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: pNode_(std::make_shared<CsgLeafNode>(
|
||||
std::make_shared<Impl>(meshGL, propertyTolerance))) {}
|
||||
|
||||
/**
|
||||
* Convert a Mesh into a Manifold. Will return an empty Manifold
|
||||
* and set an Error Status if the Mesh is not an oriented 2-manifold. Will
|
||||
* collapse degenerate triangles and unnecessary vertices.
|
||||
*
|
||||
* The three optional inputs should all be specified if any are. These define
|
||||
* any properties you may have on this mesh. These properties are not saved in
|
||||
* the Manifold, but rather used to determine which coplanar triangles can be
|
||||
* safely merged due to all properties being colinear. Any edges that define
|
||||
* property boundaries will be retained in the output of arbitrary Boolean
|
||||
* operations so that these properties can be properly reapplied to the result
|
||||
* using the MeshRelation.
|
||||
*
|
||||
* @param mesh The input Mesh.
|
||||
* @param triProperties A vector of the same length as triVerts, filled with
|
||||
* references to the properties index. Note the same vertex can have different
|
||||
* properties in different triangles.
|
||||
* @param properties A vector whose length is the largest index in
|
||||
* triProperties times the length of propertyTolerance (the number of
|
||||
* properties). Think of it as a property matrix indexed as [index *
|
||||
* numProperties + propertyNum].
|
||||
* @param propertyTolerance A vector of precision values for each property.
|
||||
* This is the amount of interpolation error allowed before two neighboring
|
||||
* triangles are considered not coplanar. A good place to start is 1e-5 times
|
||||
* the largest value you expect this property to take.
|
||||
*/
|
||||
Manifold::Manifold(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(
|
||||
mesh, triProperties, properties, propertyTolerance))) {}
|
||||
Manifold::Manifold(const Mesh& mesh) {
|
||||
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
|
||||
pNode_ =
|
||||
std::make_shared<CsgLeafNode>(std::make_shared<Impl>(mesh, relation));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable for
|
||||
@@ -138,29 +147,34 @@ Mesh Manifold::GetMesh() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
MeshGL Manifold::GetMeshGL() const {
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
const Impl& impl = *GetCsgLeafNode().GetImpl();
|
||||
|
||||
const int numVert = NumVert();
|
||||
const int numProp = NumProp();
|
||||
const int numVert = NumPropVert();
|
||||
const int numTri = NumTri();
|
||||
|
||||
const bool isOriginal = impl.meshRelation_.originalID >= 0;
|
||||
const bool updateNormals =
|
||||
!isOriginal && glm::all(glm::greaterThan(normalIdx, glm::ivec3(2)));
|
||||
|
||||
MeshGL out;
|
||||
out.vertPos.resize(3 * numVert);
|
||||
out.vertNormal.resize(3 * numVert);
|
||||
out.numProp = 3 + numProp;
|
||||
out.triVerts.resize(3 * numTri);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
const glm::vec3 v = impl.vertPos_[i];
|
||||
out.vertPos[3 * i] = v.x;
|
||||
out.vertPos[3 * i + 1] = v.y;
|
||||
out.vertPos[3 * i + 2] = v.z;
|
||||
const glm::vec3 n = impl.vertNormal_[i];
|
||||
out.vertNormal[3 * i] = n.x;
|
||||
out.vertNormal[3 * i + 1] = n.y;
|
||||
out.vertNormal[3 * i + 2] = n.z;
|
||||
}
|
||||
for (int i = 0; i < numTri * 3; ++i) {
|
||||
out.triVerts[i] = impl.halfedge_[i].startVert;
|
||||
}
|
||||
|
||||
const int numHalfedge = impl.halfedgeTangent_.size();
|
||||
out.halfedgeTangent.resize(4 * numHalfedge);
|
||||
for (int i = 0; i < numHalfedge; ++i) {
|
||||
@@ -171,6 +185,124 @@ MeshGL Manifold::GetMeshGL() const {
|
||||
out.halfedgeTangent[4 * i + 3] = t.w;
|
||||
}
|
||||
|
||||
// Sort the triangles into runs
|
||||
out.faceID.resize(numTri);
|
||||
std::vector<int> triNew2Old(numTri);
|
||||
std::iota(triNew2Old.begin(), triNew2Old.end(), 0);
|
||||
const TriRef* triRef = impl.meshRelation_.triRef.cptrD();
|
||||
// Don't sort originals - keep them in order
|
||||
if (!isOriginal) {
|
||||
std::sort(triNew2Old.begin(), triNew2Old.end(), [triRef](int a, int b) {
|
||||
return triRef[a].originalID == triRef[b].originalID
|
||||
? triRef[a].meshID < triRef[b].meshID
|
||||
: triRef[a].originalID < triRef[b].originalID;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<glm::mat3> runNormalTransform;
|
||||
|
||||
auto addRun = [updateNormals, isOriginal](
|
||||
MeshGL& out, std::vector<glm::mat3>& runNormalTransform,
|
||||
int tri, const Impl::Relation& rel) {
|
||||
out.runIndex.push_back(3 * tri);
|
||||
out.runOriginalID.push_back(rel.originalID);
|
||||
if (updateNormals) {
|
||||
runNormalTransform.push_back(NormalTransform(rel.transform) *
|
||||
(rel.backSide ? -1.0f : 1.0f));
|
||||
}
|
||||
if (!isOriginal) {
|
||||
for (const int col : {0, 1, 2, 3}) {
|
||||
for (const int row : {0, 1, 2}) {
|
||||
out.runTransform.push_back(rel.transform[col][row]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto meshIDtransform = impl.meshRelation_.meshIDtransform;
|
||||
int lastID = -1;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
const int oldTri = triNew2Old[tri];
|
||||
const auto ref = triRef[oldTri];
|
||||
const int meshID = ref.meshID;
|
||||
|
||||
out.faceID[tri] = ref.tri;
|
||||
for (const int i : {0, 1, 2})
|
||||
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
|
||||
|
||||
if (meshID != lastID) {
|
||||
addRun(out, runNormalTransform, tri, meshIDtransform.at(meshID));
|
||||
meshIDtransform.erase(meshID);
|
||||
lastID = meshID;
|
||||
}
|
||||
}
|
||||
// Add runs for originals that did not contribute any faces to the output
|
||||
for (const auto& pair : meshIDtransform) {
|
||||
addRun(out, runNormalTransform, numTri, pair.second);
|
||||
}
|
||||
out.runIndex.push_back(3 * numTri);
|
||||
|
||||
// Early return for no props
|
||||
if (numProp == 0) {
|
||||
out.vertProperties.resize(3 * numVert);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
const glm::vec3 v = impl.vertPos_[i];
|
||||
out.vertProperties[3 * i] = v.x;
|
||||
out.vertProperties[3 * i + 1] = v.y;
|
||||
out.vertProperties[3 * i + 2] = v.z;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Duplicate verts with different props
|
||||
std::vector<int> vert2idx(impl.NumVert(), -1);
|
||||
std::map<std::pair<int, int>, int> vertPropPair;
|
||||
for (int run = 0; run < out.runOriginalID.size(); ++run) {
|
||||
for (int tri = out.runIndex[run] / 3; tri < out.runIndex[run + 1] / 3;
|
||||
++tri) {
|
||||
const glm::ivec3 triProp =
|
||||
impl.meshRelation_.triProperties[triNew2Old[tri]];
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int prop = triProp[i];
|
||||
const int vert = out.triVerts[3 * tri + i];
|
||||
|
||||
const auto it = vertPropPair.find({vert, prop});
|
||||
if (it != vertPropPair.end()) {
|
||||
out.triVerts[3 * tri + i] = it->second;
|
||||
continue;
|
||||
}
|
||||
const int idx = out.vertProperties.size() / out.numProp;
|
||||
vertPropPair.insert({{vert, prop}, idx});
|
||||
out.triVerts[3 * tri + i] = idx;
|
||||
|
||||
for (int p : {0, 1, 2}) {
|
||||
out.vertProperties.push_back(impl.vertPos_[vert][p]);
|
||||
}
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
out.vertProperties.push_back(
|
||||
impl.meshRelation_.properties[prop * numProp + p]);
|
||||
}
|
||||
if (updateNormals) {
|
||||
glm::vec3 normal;
|
||||
const int start = out.vertProperties.size() - out.numProp;
|
||||
for (int i : {0, 1, 2}) {
|
||||
normal[i] = out.vertProperties[start + normalIdx[i]];
|
||||
}
|
||||
normal = glm::normalize(runNormalTransform[run] * normal);
|
||||
for (int i : {0, 1, 2}) {
|
||||
out.vertProperties[start + normalIdx[i]] = normal[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (vert2idx[vert] == -1) {
|
||||
vert2idx[vert] = idx;
|
||||
} else {
|
||||
out.mergeFromVert.push_back(idx);
|
||||
out.mergeToVert.push_back(vert2idx[vert]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -241,9 +373,9 @@ bool Manifold::IsEmpty() const { return GetCsgLeafNode().GetImpl()->IsEmpty(); }
|
||||
/**
|
||||
* 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 NO_ERROR,
|
||||
* 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 NO_ERROR, for instance if they are small enough relative to their
|
||||
* show NoError, for instance if they are small enough relative to their
|
||||
* precision to be collapsed to nothing.
|
||||
*/
|
||||
Manifold::Error Manifold::Status() const {
|
||||
@@ -261,6 +393,18 @@ int Manifold::NumEdge() const { return GetCsgLeafNode().GetImpl()->NumEdge(); }
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
int Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }
|
||||
/**
|
||||
* The number of properties per vertex in the Manifold.
|
||||
*/
|
||||
int Manifold::NumProp() const { return GetCsgLeafNode().GetImpl()->NumProp(); }
|
||||
/**
|
||||
* The number of property vertices in the Manifold. This will always be >=
|
||||
* NumVert, as some physical vertices may be duplicated to account for different
|
||||
* properties on different neighboring triangles.
|
||||
*/
|
||||
int Manifold::NumPropVert() const {
|
||||
return GetCsgLeafNode().GetImpl()->NumPropVert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
@@ -310,30 +454,6 @@ Curvature Manifold::GetCurvature() const {
|
||||
return GetCsgLeafNode().GetImpl()->GetCurvature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous meshes, for the purpose of assigning
|
||||
* properties like texture coordinates. The triBary vector is the same length as
|
||||
* Mesh.triVerts: BaryRef.originalID indicates the source mesh and BaryRef.tri
|
||||
* is that mesh's triangle index to which these barycentric coordinates refer.
|
||||
* BaryRef.vertBary gives an index for each vertex into the barycentric vector
|
||||
* if that index is >= 0, indicating it is a new vertex. If the index is < 0,
|
||||
* this indicates it is an original vertex, the index + 3 vert of the referenced
|
||||
* triangle.
|
||||
*
|
||||
* BaryRef.meshID is a unique ID to the particular instance of a given mesh. For
|
||||
* instance, if you want to convert the triangle mesh to a polygon mesh, all the
|
||||
* triangles from a given face will have the same .meshID and .tri values.
|
||||
*/
|
||||
MeshRelation Manifold::GetMeshRelation() const {
|
||||
MeshRelation out;
|
||||
const auto& relation = GetCsgLeafNode().GetImpl()->meshRelation_;
|
||||
out.triBary.insert(out.triBary.end(), relation.triBary.begin(),
|
||||
relation.triBary.end());
|
||||
out.barycentric.insert(out.barycentric.end(), relation.barycentric.begin(),
|
||||
relation.barycentric.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
@@ -344,26 +464,30 @@ int Manifold::OriginalID() const {
|
||||
}
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
|
||||
* meaning it will now be referenced by its descendents instead of the meshes it
|
||||
* was built from, allowing you to differentiate the copies when applying your
|
||||
* properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to construct
|
||||
* a new manifold.
|
||||
* This function condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. In the process the relation to ancestor meshes is lost
|
||||
* and this new Manifold is marked an original. Properties are preserved, so if
|
||||
* they do not match across an edge, that edge will be kept.
|
||||
*/
|
||||
Manifold Manifold::AsOriginal() const {
|
||||
auto newImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
|
||||
newImpl->InitializeNewReference();
|
||||
newImpl->meshRelation_.originalID = ReserveIDs(1);
|
||||
newImpl->InitializeOriginal();
|
||||
newImpl->CreateFaces();
|
||||
newImpl->SimplifyTopology();
|
||||
newImpl->Finish();
|
||||
return Manifold(std::make_shared<CsgLeafNode>(newImpl));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
uint32_t Manifold::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::ReserveIDs(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should always be true. Also checks saneness of the internal data structures.
|
||||
*/
|
||||
@@ -524,7 +648,7 @@ Manifold Manifold::BatchBoolean(const std::vector<Manifold>& manifolds,
|
||||
* Shorthand for Boolean Union.
|
||||
*/
|
||||
Manifold Manifold::operator+(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::ADD);
|
||||
return Boolean(Q, OpType::Add);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,7 +663,7 @@ Manifold& Manifold::operator+=(const Manifold& Q) {
|
||||
* Shorthand for Boolean Difference.
|
||||
*/
|
||||
Manifold Manifold::operator-(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::SUBTRACT);
|
||||
return Boolean(Q, OpType::Subtract);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,7 +678,7 @@ Manifold& Manifold::operator-=(const Manifold& Q) {
|
||||
* Shorthand for Boolean Intersection.
|
||||
*/
|
||||
Manifold Manifold::operator^(const Manifold& Q) const {
|
||||
return Boolean(Q, OpType::INTERSECT);
|
||||
return Boolean(Q, OpType::Intersect);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,11 +700,11 @@ std::pair<Manifold, Manifold> Manifold::Split(const Manifold& cutter) const {
|
||||
auto impl1 = GetCsgLeafNode().GetImpl();
|
||||
auto impl2 = cutter.GetCsgLeafNode().GetImpl();
|
||||
|
||||
Boolean3 boolean(*impl1, *impl2, OpType::SUBTRACT);
|
||||
Boolean3 boolean(*impl1, *impl2, OpType::Subtract);
|
||||
auto result1 = std::make_shared<CsgLeafNode>(
|
||||
std::make_unique<Impl>(boolean.Result(OpType::INTERSECT)));
|
||||
std::make_unique<Impl>(boolean.Result(OpType::Intersect)));
|
||||
auto result2 = std::make_shared<CsgLeafNode>(
|
||||
std::make_unique<Impl>(boolean.Result(OpType::SUBTRACT)));
|
||||
std::make_unique<Impl>(boolean.Result(OpType::Subtract)));
|
||||
return std::make_pair(Manifold(result1), Manifold(result2));
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ __host__ __device__ inline glm::vec3 UVW(int vert,
|
||||
return uvw;
|
||||
}
|
||||
|
||||
__host__ __device__ inline glm::mat3 NormalTransform(
|
||||
const glm::mat4x3& transform) {
|
||||
return glm::inverse(glm::transpose(glm::mat3(transform)));
|
||||
}
|
||||
|
||||
/**
|
||||
* By using the closest axis-aligned projection to the normal instead of a
|
||||
* projection along the normal, we avoid introducing any rounding error.
|
||||
@@ -78,34 +83,43 @@ __host__ __device__ inline glm::vec3 GetBarycentric(const glm::vec3& v,
|
||||
triPos[1] - triPos[0]);
|
||||
const glm::vec3 d2(glm::dot(edges[0], edges[0]), glm::dot(edges[1], edges[1]),
|
||||
glm::dot(edges[2], edges[2]));
|
||||
int longside = d2[0] > d2[1] && d2[0] > d2[2] ? 0 : d2[1] > d2[2] ? 1 : 2;
|
||||
const int longSide = d2[0] > d2[1] && d2[0] > d2[2] ? 0
|
||||
: d2[1] > d2[2] ? 1
|
||||
: 2;
|
||||
const glm::vec3 crossP = glm::cross(edges[0], edges[1]);
|
||||
const float area2 = glm::dot(crossP, crossP);
|
||||
const float tol2 = precision * precision;
|
||||
const float vol = glm::dot(crossP, v - triPos[2]);
|
||||
if (vol * vol > area2 * tol2) return glm::vec3(NAN);
|
||||
|
||||
if (d2[longside] < tol2) { // point
|
||||
glm::vec3 uvw(0);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const glm::vec3 dv = v - triPos[i];
|
||||
if (glm::dot(dv, dv) < tol2) {
|
||||
// Return exactly equal if within tolerance of vert.
|
||||
uvw[i] = 1;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
|
||||
if (d2[longSide] < tol2) { // point
|
||||
return glm::vec3(1, 0, 0);
|
||||
} else if (area2 > d2[longside] * tol2) { // triangle
|
||||
glm::vec3 uvw(0);
|
||||
for (int i : {0, 1, 2}) {
|
||||
int j = i + 1;
|
||||
if (j > 2) j -= 3;
|
||||
uvw[i] = glm::dot(glm::cross(edges[i], v - triPos[j]), crossP);
|
||||
} else if (area2 > d2[longSide] * tol2) { // triangle
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int j = Next3(i);
|
||||
const glm::vec3 crossPv = glm::cross(edges[i], v - triPos[j]);
|
||||
const float area2v = glm::dot(crossPv, crossPv);
|
||||
// Return exactly equal if within tolerance of edge.
|
||||
uvw[i] = area2v < d2[i] * tol2 ? 0 : glm::dot(crossPv, crossP);
|
||||
}
|
||||
uvw /= (uvw[0] + uvw[1] + uvw[2]);
|
||||
return uvw;
|
||||
} else { // line
|
||||
int nextside = longside + 1;
|
||||
if (nextside > 2) nextside -= 3;
|
||||
const int nextV = Next3(longSide);
|
||||
const float alpha =
|
||||
glm::dot(v - triPos[nextside], edges[longside]) / d2[longside];
|
||||
glm::vec3 uvw(0);
|
||||
uvw[longside] = 0;
|
||||
uvw[nextside++] = 1 - alpha;
|
||||
if (nextside > 2) nextside -= 3;
|
||||
uvw[nextside] = alpha;
|
||||
glm::dot(v - triPos[nextV], edges[longSide]) / d2[longSide];
|
||||
uvw[longSide] = 0;
|
||||
uvw[nextV] = 1 - alpha;
|
||||
const int lastV = Next3(nextV);
|
||||
uvw[lastV] = alpha;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
@@ -125,6 +139,24 @@ struct Halfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
glm::vec3 uvw;
|
||||
};
|
||||
|
||||
struct TriRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// The triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri].
|
||||
int tri;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a temporary edge structure which only stores edges forward and
|
||||
* references the halfedge it was created from.
|
||||
@@ -191,5 +223,14 @@ inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) {
|
||||
<< ", pairedHalfedge = " << edge.pairedHalfedge
|
||||
<< ", face = " << edge.face;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) {
|
||||
return stream << "tri = " << bary.tri << ", uvw = " << bary.uvw;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const TriRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri;
|
||||
}
|
||||
#endif
|
||||
} // namespace manifold
|
||||
|
||||
@@ -33,10 +33,23 @@ __host__ __device__ int VertsPerTri(int edgeVerts) {
|
||||
return (edgeVerts * edgeVerts + edgeVerts) / 2;
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
glm::vec3 uvw;
|
||||
};
|
||||
/**
|
||||
* Retained verts are part of several triangles, and it doesn't matter which one
|
||||
* the vertBary refers to. Here, whichever is last will win and it's done on the
|
||||
* 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_) {
|
||||
const int numTri = halfedge_.size() / 3;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
glm::vec3 uvw(0);
|
||||
uvw[i] = 1;
|
||||
vertBary[halfedge_[3 * tri + i].startVert] = {tri, uvw};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReindexHalfedge {
|
||||
int* half2Edge;
|
||||
@@ -51,6 +64,7 @@ struct ReindexHalfedge {
|
||||
|
||||
struct EdgeVerts {
|
||||
glm::vec3* vertPos;
|
||||
Barycentric* vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
|
||||
@@ -59,35 +73,32 @@ struct EdgeVerts {
|
||||
TmpEdge edgeVerts = thrust::get<1>(in);
|
||||
|
||||
float invTotal = 1.0f / n;
|
||||
for (int i = 1; i < n; ++i)
|
||||
vertPos[startIdx + (n - 1) * edge + i - 1] =
|
||||
(float(n - i) * vertPos[edgeVerts.first] +
|
||||
float(i) * vertPos[edgeVerts.second]) *
|
||||
invTotal;
|
||||
for (int i = 1; i < n; ++i) {
|
||||
const int vert = startIdx + (n - 1) * edge + i - 1;
|
||||
const float v = i * invTotal;
|
||||
const float u = 1 - v;
|
||||
vertPos[vert] =
|
||||
u * vertPos[edgeVerts.first] + v * vertPos[edgeVerts.second];
|
||||
|
||||
const int tri = edgeVerts.halfedgeIdx / 3;
|
||||
const int idx = edgeVerts.halfedgeIdx - 3 * tri;
|
||||
glm::vec3 uvw(0);
|
||||
uvw[idx] = u;
|
||||
uvw[Next3(idx)] = v;
|
||||
vertBary[vert] = {tri, uvw};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InteriorVerts {
|
||||
glm::vec3* vertPos;
|
||||
glm::vec3* uvw;
|
||||
BaryRef* triBary;
|
||||
glm::vec3* uvwNew;
|
||||
BaryRef* triBaryNew;
|
||||
const glm::vec3* uvwOld;
|
||||
Barycentric* vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, BaryRef> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const BaryRef baryOld = thrust::get<1>(in);
|
||||
|
||||
glm::mat3 uvwOldTri;
|
||||
for (int i : {0, 1, 2}) uvwOldTri[i] = UVW(baryOld.vertBary[i], uvwOld);
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
const float invTotal = 1.0f / n;
|
||||
int posTri = tri * n * n;
|
||||
int posBary = tri * VertsPerTri(n + 1);
|
||||
int pos = startIdx + tri * VertsPerTri(n - 2);
|
||||
for (int i = 0; i <= n; ++i) {
|
||||
for (int j = 0; j <= n - i; ++j) {
|
||||
@@ -95,34 +106,13 @@ struct InteriorVerts {
|
||||
const float u = invTotal * j;
|
||||
const float v = invTotal * k;
|
||||
const float w = invTotal * i;
|
||||
const int first = posBary;
|
||||
uvw[posBary] = {u, v, w};
|
||||
uvwNew[posBary] = uvwOldTri * uvw[posBary];
|
||||
++posBary;
|
||||
if (j == n - i) continue;
|
||||
if (i == 0 || j == 0 || k == 0 || j == n - i) continue;
|
||||
|
||||
// The three retained verts are denoted by their index - 3. uvw entries
|
||||
// are added for them out of laziness of indexing only.
|
||||
const int a = (k == n) ? -2 : first;
|
||||
const int b = (i == n - 1) ? -1 : first + n - i + 1;
|
||||
const int c = (j == n - 1) ? -3 : first + 1;
|
||||
glm::ivec3 vertBary(c, a, b);
|
||||
triBary[posTri] = {-1, -1, tri, vertBary};
|
||||
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID, baryOld.tri,
|
||||
vertBary};
|
||||
if (j < n - 1 - i) {
|
||||
int d = b + 1; // d cannot be a retained vert
|
||||
vertBary = {b, d, c};
|
||||
triBary[posTri] = {-1, -1, tri, vertBary};
|
||||
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID,
|
||||
baryOld.tri, vertBary};
|
||||
}
|
||||
vertPos[pos] = u * vertPos[halfedge[3 * tri].startVert] + //
|
||||
v * vertPos[halfedge[3 * tri + 1].startVert] + //
|
||||
w * vertPos[halfedge[3 * tri + 2].startVert];
|
||||
|
||||
if (i == 0 || j == 0 || k == 0) continue;
|
||||
|
||||
vertPos[pos++] = u * vertPos[halfedge[3 * tri].startVert] + //
|
||||
v * vertPos[halfedge[3 * tri + 1].startVert] + //
|
||||
w * vertPos[halfedge[3 * tri + 2].startVert];
|
||||
vertBary[pos++] = {tri, {u, v, w}};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,24 +212,6 @@ struct SmoothBezier {
|
||||
}
|
||||
};
|
||||
|
||||
struct TriBary2Vert {
|
||||
Barycentric* vertBary;
|
||||
int* lock;
|
||||
const glm::vec3* uvw;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef, int> in) {
|
||||
const BaryRef baryRef = thrust::get<0>(in);
|
||||
const int tri = thrust::get<1>(in);
|
||||
|
||||
for (int i : {0, 1, 2}) {
|
||||
int vert = halfedge[3 * tri + i].startVert;
|
||||
if (AtomicAdd(lock[vert], 1) != 0) continue;
|
||||
vertBary[vert] = {baryRef.tri, UVW(baryRef.vertBary[i], uvw)};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InterpTri {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec4* halfedgeTangent;
|
||||
@@ -365,14 +337,14 @@ void Manifold::Impl::CreateTangents(
|
||||
vertNormal_.cptrD(), halfedge_.cptrD()}));
|
||||
|
||||
if (!sharpenedEdges.empty()) {
|
||||
const VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
const VecDH<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
|
||||
// meshRelation_.
|
||||
std::vector<int> oldHalfedge2New(halfedge_.size());
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
int oldTri = triBary[tri].tri;
|
||||
int oldTri = triRef[tri].tri;
|
||||
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
|
||||
}
|
||||
|
||||
@@ -459,24 +431,22 @@ void Manifold::Impl::CreateTangents(
|
||||
* run after the new vertices have moved, which is a likely scenario after
|
||||
* refinement (smoothing).
|
||||
*/
|
||||
Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return meshRelation_;
|
||||
VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return VecDH<Barycentric>();
|
||||
faceNormal_.resize(0);
|
||||
vertNormal_.resize(0);
|
||||
int numVert = NumVert();
|
||||
int numEdge = NumEdge();
|
||||
int numTri = NumTri();
|
||||
const int numVert = NumVert();
|
||||
const int numEdge = NumEdge();
|
||||
const int numTri = NumTri();
|
||||
// Append new verts
|
||||
int vertsPerEdge = n - 1;
|
||||
int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
const int vertsPerEdge = n - 1;
|
||||
const int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
vertPos_.resize(triVertStart + numTri * VertsPerTri(n - 2));
|
||||
VecDH<Barycentric> vertBary(vertPos_.size());
|
||||
FillRetainedVerts(vertBary, halfedge_);
|
||||
|
||||
MeshRelationD relation;
|
||||
relation.barycentric.resize(numTri * VertsPerTri(n + 1));
|
||||
relation.triBary.resize(n * n * numTri);
|
||||
MeshRelationD oldMeshRelation = std::move(meshRelation_);
|
||||
meshRelation_.barycentric.resize(relation.barycentric.size());
|
||||
meshRelation_.triBary.resize(relation.triBary.size());
|
||||
meshRelation_.triRef.resize(n * n * numTri);
|
||||
meshRelation_.originalID = oldMeshRelation.originalID;
|
||||
|
||||
VecDH<TmpEdge> edges = CreateTmpEdges(halfedge_);
|
||||
@@ -485,36 +455,40 @@ Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
ReindexHalfedge({half2Edge.ptrD()}));
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
EdgeVerts({vertPos_.ptrD(), numVert, n}));
|
||||
for_each_n(
|
||||
policy, zip(countAt(0), oldMeshRelation.triBary.begin()), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), relation.barycentric.ptrD(),
|
||||
relation.triBary.ptrD(), meshRelation_.barycentric.ptrD(),
|
||||
meshRelation_.triBary.ptrD(),
|
||||
oldMeshRelation.barycentric.cptrD(), triVertStart, n,
|
||||
halfedge_.ptrD()}));
|
||||
EdgeVerts({vertPos_.ptrD(), vertBary.ptrD(), numVert, n}));
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), vertBary.ptrD(), triVertStart, n,
|
||||
halfedge_.cptrD()}));
|
||||
// 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}));
|
||||
CreateHalfedges(triVerts);
|
||||
return relation;
|
||||
// Make original since the subdivided faces are intended to be warped into
|
||||
// being non-coplanar, and hence not being related to the original faces.
|
||||
meshRelation_.originalID = ReserveIDs(1);
|
||||
InitializeOriginal();
|
||||
|
||||
if (meshRelation_.numProp > 0) {
|
||||
meshRelation_.properties.resize(meshRelation_.numProp * numVert);
|
||||
meshRelation_.triProperties.resize(meshRelation_.triRef.size());
|
||||
// Fill properties according to barycentric.
|
||||
// Set triProp to share properties on continuous edges.
|
||||
// Duplicate properties will be removed during sorting.
|
||||
}
|
||||
|
||||
return vertBary;
|
||||
}
|
||||
|
||||
void Manifold::Impl::Refine(int n) {
|
||||
Manifold::Impl old = *this;
|
||||
MeshRelationD relation = Subdivide(n);
|
||||
VecDH<Barycentric> vertBary = Subdivide(n);
|
||||
if (vertBary.size() == 0) return;
|
||||
|
||||
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
|
||||
VecDH<Barycentric> vertBary(NumVert());
|
||||
VecDH<int> lock(NumVert(), 0);
|
||||
auto policy = autoPolicy(NumTri());
|
||||
for_each_n(policy, zip(relation.triBary.begin(), countAt(0)), NumTri(),
|
||||
TriBary2Vert({vertBary.ptrD(), lock.ptrD(),
|
||||
relation.barycentric.cptrD(), halfedge_.cptrD()}));
|
||||
|
||||
for_each_n(policy, zip(vertPos_.begin(), vertBary.begin()), NumVert(),
|
||||
for_each_n(autoPolicy(NumTri()), zip(vertPos_.begin(), vertBary.begin()),
|
||||
NumVert(),
|
||||
InterpTri({old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
|
||||
old.vertPos_.cptrD()}));
|
||||
}
|
||||
|
||||
@@ -119,6 +119,42 @@ struct Reindex {
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkProp {
|
||||
int* keep;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3 triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
keep[triProp[i]] = 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GatherProps {
|
||||
float* properties;
|
||||
const float* oldProperties;
|
||||
const int numProp;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
|
||||
const int oldIdx = thrust::get<0>(in);
|
||||
const int newIdx = thrust::get<1>(in);
|
||||
const int keep = thrust::get<2>(in);
|
||||
if (keep == 0) return;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
properties[newIdx * numProp + p] = oldProperties[oldIdx * numProp + p];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReindexProps {
|
||||
const int* old2new;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3& triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triProp[i] = old2new[triProp[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
VecDH<T> tmp(std::move(inOut));
|
||||
@@ -127,7 +163,7 @@ void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
tmp.begin(), inOut.begin());
|
||||
}
|
||||
|
||||
template void Permute<BaryRef>(VecDH<BaryRef>&, const VecDH<int>&);
|
||||
template void Permute<TriRef>(VecDH<TriRef>&, const VecDH<int>&);
|
||||
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
|
||||
|
||||
struct ReindexFace {
|
||||
@@ -172,7 +208,7 @@ void Manifold::Impl::Finish() {
|
||||
SetPrecision(precision_);
|
||||
if (!bBox_.IsFinite()) {
|
||||
// Decimated out of existence - early out.
|
||||
MarkFailure(Error::NO_ERROR);
|
||||
MarkFailure(Error::NoError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -182,6 +218,7 @@ void Manifold::Impl::Finish() {
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
CompactProps();
|
||||
|
||||
ASSERT(halfedge_.size() % 6 == 0, topologyErr,
|
||||
"Not an even number of faces after sorting faces!");
|
||||
@@ -202,8 +239,8 @@ void Manifold::Impl::Finish() {
|
||||
"Halfedge index is negative!");
|
||||
ASSERT(extrema.pairedHalfedge < 2 * NumEdge(), topologyErr,
|
||||
"Halfedge index exceeds number of halfedges!");
|
||||
ASSERT(meshRelation_.triBary.size() == NumTri() ||
|
||||
meshRelation_.triBary.size() == 0,
|
||||
ASSERT(meshRelation_.triRef.size() == NumTri() ||
|
||||
meshRelation_.triRef.size() == 0,
|
||||
logicErr, "Mesh Relation doesn't fit!");
|
||||
ASSERT(faceNormal_.size() == NumTri() || faceNormal_.size() == 0, logicErr,
|
||||
"faceNormal size = " + std::to_string(faceNormal_.size()) +
|
||||
@@ -263,6 +300,32 @@ void Manifold::Impl::ReindexVerts(const VecDH<int>& vertNew2Old,
|
||||
Reindex({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unreferenced property verts and reindexes triProperties.
|
||||
*/
|
||||
void Manifold::Impl::CompactProps() {
|
||||
if (meshRelation_.numProp == 0) return;
|
||||
|
||||
const int numVerts = meshRelation_.properties.size() / meshRelation_.numProp;
|
||||
VecDH<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);
|
||||
inclusive_scan(policy, keep.begin(), keep.end(), propOld2New.begin() + 1);
|
||||
|
||||
VecDH<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, meshRelation_.triProperties.begin(), NumTri(),
|
||||
ReindexProps({propOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the faceBox and faceMorton input with the bounding boxes and Morton
|
||||
* codes of the faces, respectively. The Morton code is based on the center of
|
||||
@@ -310,9 +373,10 @@ void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
|
||||
*/
|
||||
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
if (meshRelation_.triBary.size() == NumTri())
|
||||
Permute(meshRelation_.triBary, faceNew2Old);
|
||||
|
||||
if (meshRelation_.triRef.size() == NumTri())
|
||||
Permute(meshRelation_.triRef, faceNew2Old);
|
||||
if (meshRelation_.triProperties.size() == NumTri())
|
||||
Permute(meshRelation_.triProperties, faceNew2Old);
|
||||
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
|
||||
|
||||
VecDH<Halfedge> oldHalfedge(std::move(halfedge_));
|
||||
@@ -333,11 +397,23 @@ void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
void Manifold::Impl::GatherFaces(const Impl& old,
|
||||
const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
meshRelation_.triBary.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
meshRelation_.triRef.resize(numTri);
|
||||
gather(policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triBary.begin(), meshRelation_.triBary.begin());
|
||||
meshRelation_.barycentric = old.meshRelation_.barycentric;
|
||||
old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin());
|
||||
|
||||
for (const auto& pair : old.meshRelation_.meshIDtransform) {
|
||||
meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
if (old.meshRelation_.triProperties.size() > 0) {
|
||||
meshRelation_.triProperties.resize(numTri);
|
||||
gather(policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triProperties.begin(),
|
||||
meshRelation_.triProperties.begin());
|
||||
meshRelation_.properties = old.meshRelation_.properties;
|
||||
}
|
||||
|
||||
if (old.faceNormal_.size() == old.NumTri()) {
|
||||
faceNormal_.resize(numTri);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (polygon)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
@@ -25,4 +25,4 @@ target_link_libraries( ${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
@@ -22,7 +22,21 @@ namespace manifold {
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
std::vector<glm::ivec3> Triangulate(const Polygons &polys,
|
||||
|
||||
/**
|
||||
* Polygon vertex.
|
||||
*/
|
||||
struct PolyVert {
|
||||
/// X-Y position
|
||||
glm::vec2 pos;
|
||||
/// ID or index into another vertex vector
|
||||
int idx;
|
||||
};
|
||||
|
||||
using SimplePolygonIdx = std::vector<PolyVert>;
|
||||
using PolygonsIdx = std::vector<SimplePolygonIdx>;
|
||||
|
||||
std::vector<glm::ivec3> Triangulate(const PolygonsIdx &polys,
|
||||
float precision = -1);
|
||||
|
||||
ExecutionParams &PolygonParams();
|
||||
|
||||
@@ -29,17 +29,14 @@ using namespace manifold;
|
||||
ExecutionParams params;
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
struct Polyedge {
|
||||
struct PolyEdge {
|
||||
int startVert, endVert;
|
||||
};
|
||||
|
||||
bool OverlapAssert(bool condition, const char *file, int line,
|
||||
const std::string &cond, const std::string &msg) {
|
||||
if (!params.processOverlaps && !condition) {
|
||||
std::ostringstream output;
|
||||
output << "Error in file: " << file << " (" << line << "): \'" << cond
|
||||
<< "\' is false: " << msg;
|
||||
throw geometryErr(output.str());
|
||||
if (!params.processOverlaps) {
|
||||
ASSERT(condition, geometryErr, msg);
|
||||
}
|
||||
return condition;
|
||||
}
|
||||
@@ -66,8 +63,8 @@ bool OverlapAssert(bool condition, const char *file, int line,
|
||||
#define PRINT(msg) \
|
||||
if (params.verbose) std::cout << msg << std::endl;
|
||||
|
||||
std::vector<Polyedge> Polygons2Edges(const Polygons &polys) {
|
||||
std::vector<Polyedge> halfedges;
|
||||
std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx &polys) {
|
||||
std::vector<PolyEdge> halfedges;
|
||||
for (const auto &poly : polys) {
|
||||
for (int i = 1; i < poly.size(); ++i) {
|
||||
halfedges.push_back({poly[i - 1].idx, poly[i].idx});
|
||||
@@ -77,9 +74,9 @@ std::vector<Polyedge> Polygons2Edges(const Polygons &polys) {
|
||||
return halfedges;
|
||||
}
|
||||
|
||||
std::vector<Polyedge> Triangles2Edges(
|
||||
std::vector<PolyEdge> Triangles2Edges(
|
||||
const std::vector<glm::ivec3> &triangles) {
|
||||
std::vector<Polyedge> halfedges;
|
||||
std::vector<PolyEdge> halfedges;
|
||||
for (const glm::ivec3 &tri : triangles) {
|
||||
halfedges.push_back({tri[0], tri[1]});
|
||||
halfedges.push_back({tri[1], tri[2]});
|
||||
@@ -88,26 +85,26 @@ std::vector<Polyedge> Triangles2Edges(
|
||||
return halfedges;
|
||||
}
|
||||
|
||||
void CheckTopology(const std::vector<Polyedge> &halfedges) {
|
||||
void CheckTopology(const std::vector<PolyEdge> &halfedges) {
|
||||
ASSERT(halfedges.size() % 2 == 0, topologyErr, "Odd number of halfedges.");
|
||||
size_t n_edges = halfedges.size() / 2;
|
||||
std::vector<Polyedge> forward(halfedges.size()), backward(halfedges.size());
|
||||
std::vector<PolyEdge> forward(halfedges.size()), backward(halfedges.size());
|
||||
|
||||
auto end = std::copy_if(halfedges.begin(), halfedges.end(), forward.begin(),
|
||||
[](Polyedge e) { return e.endVert > e.startVert; });
|
||||
[](PolyEdge e) { return e.endVert > e.startVert; });
|
||||
ASSERT(std::distance(forward.begin(), end) == n_edges, topologyErr,
|
||||
"Half of halfedges should be forward.");
|
||||
forward.resize(n_edges);
|
||||
|
||||
end = std::copy_if(halfedges.begin(), halfedges.end(), backward.begin(),
|
||||
[](Polyedge e) { return e.endVert < e.startVert; });
|
||||
[](PolyEdge e) { return e.endVert < e.startVert; });
|
||||
ASSERT(std::distance(backward.begin(), end) == n_edges, topologyErr,
|
||||
"Half of halfedges should be backward.");
|
||||
backward.resize(n_edges);
|
||||
|
||||
std::for_each(backward.begin(), backward.end(),
|
||||
[](Polyedge &e) { std::swap(e.startVert, e.endVert); });
|
||||
auto cmp = [](const Polyedge &a, const Polyedge &b) {
|
||||
[](PolyEdge &e) { std::swap(e.startVert, e.endVert); });
|
||||
auto cmp = [](const PolyEdge &a, const PolyEdge &b) {
|
||||
return a.startVert < b.startVert ||
|
||||
(a.startVert == b.startVert && a.endVert < b.endVert);
|
||||
};
|
||||
@@ -129,17 +126,17 @@ void CheckTopology(const std::vector<Polyedge> &halfedges) {
|
||||
}
|
||||
|
||||
void CheckTopology(const std::vector<glm::ivec3> &triangles,
|
||||
const Polygons &polys) {
|
||||
std::vector<Polyedge> halfedges = Triangles2Edges(triangles);
|
||||
std::vector<Polyedge> openEdges = Polygons2Edges(polys);
|
||||
for (Polyedge e : openEdges) {
|
||||
const PolygonsIdx &polys) {
|
||||
std::vector<PolyEdge> halfedges = Triangles2Edges(triangles);
|
||||
std::vector<PolyEdge> openEdges = Polygons2Edges(polys);
|
||||
for (PolyEdge e : openEdges) {
|
||||
halfedges.push_back({e.endVert, e.startVert});
|
||||
}
|
||||
CheckTopology(halfedges);
|
||||
}
|
||||
|
||||
void CheckGeometry(const std::vector<glm::ivec3> &triangles,
|
||||
const Polygons &polys, float precision) {
|
||||
const PolygonsIdx &polys, float precision) {
|
||||
std::map<int, glm::vec2> vertPos;
|
||||
for (const auto &poly : polys) {
|
||||
for (int i = 0; i < poly.size(); ++i) {
|
||||
@@ -154,7 +151,7 @@ void CheckGeometry(const std::vector<glm::ivec3> &triangles,
|
||||
geometryErr, "triangulation is not entirely CCW!");
|
||||
}
|
||||
|
||||
void Dump(const Polygons &polys) {
|
||||
void Dump(const PolygonsIdx &polys) {
|
||||
for (auto poly : polys) {
|
||||
std::cout << "polys.push_back({" << std::setprecision(9) << std::endl;
|
||||
for (auto v : poly) {
|
||||
@@ -172,7 +169,7 @@ void Dump(const Polygons &polys) {
|
||||
}
|
||||
}
|
||||
|
||||
void PrintFailure(const std::exception &e, const Polygons &polys,
|
||||
void PrintFailure(const std::exception &e, const PolygonsIdx &polys,
|
||||
std::vector<glm::ivec3> &triangles, float precision) {
|
||||
std::cout << "-----------------------------------" << std::endl;
|
||||
std::cout << "Triangulation failed! Precision = " << precision << std::endl;
|
||||
@@ -196,10 +193,10 @@ void PrintFailure(const std::exception &e, const Polygons &polys,
|
||||
*/
|
||||
class Monotones {
|
||||
public:
|
||||
Monotones(const Polygons &polys, float precision) : precision_(precision) {
|
||||
Monotones(const PolygonsIdx &polys, float precision) : precision_(precision) {
|
||||
VertItr start, last, current;
|
||||
float bound = 0;
|
||||
for (const SimplePolygon &poly : polys) {
|
||||
for (const SimplePolygonIdx &poly : polys) {
|
||||
for (int i = 0; i < poly.size(); ++i) {
|
||||
monotones_.push_back({poly[i].pos, //
|
||||
poly[i].idx, //
|
||||
@@ -275,7 +272,7 @@ class Monotones {
|
||||
void Check() {
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
if (!params.intermediateChecks) return;
|
||||
std::vector<Polyedge> edges;
|
||||
std::vector<PolyEdge> edges;
|
||||
for (VertItr vert = monotones_.begin(); vert != monotones_.end(); vert++) {
|
||||
vert->SetProcessed(false);
|
||||
edges.push_back({vert->mesh_idx, vert->right->mesh_idx});
|
||||
@@ -309,7 +306,7 @@ class Monotones {
|
||||
typedef std::list<VertAdj>::iterator VertItr;
|
||||
struct EdgePair;
|
||||
typedef std::list<EdgePair>::iterator PairItr;
|
||||
enum VertType { START, WESTSIDE, EASTSIDE, MERGE, END, SKIP };
|
||||
enum VertType { Start, WestSide, EastSide, Merge, End, Skip };
|
||||
|
||||
std::list<VertAdj> monotones_; // sweep-line list of verts
|
||||
std::list<EdgePair> activePairs_; // west to east list of monotone edge pairs
|
||||
@@ -489,8 +486,8 @@ class Monotones {
|
||||
}
|
||||
|
||||
PairItr GetPair(VertItr vert, VertType type) const {
|
||||
// MERGE returns westPair, as this is the one that will be removed.
|
||||
return type == WESTSIDE ? vert->eastPair : vert->westPair;
|
||||
// Merge returns westPair, as this is the one that will be removed.
|
||||
return type == WestSide ? vert->eastPair : vert->westPair;
|
||||
}
|
||||
|
||||
bool Coincident(glm::vec2 p0, glm::vec2 p1) const {
|
||||
@@ -518,46 +515,46 @@ class Monotones {
|
||||
if (vert->left->Processed()) {
|
||||
if (westPair == eastPair) {
|
||||
// facing in
|
||||
PRINT("END");
|
||||
PRINT("End");
|
||||
CloseEnd(vert);
|
||||
return END;
|
||||
return End;
|
||||
} else if (westPair != activePairs_.end() &&
|
||||
std::next(westPair) == eastPair) {
|
||||
// facing out
|
||||
PRINT("MERGE");
|
||||
PRINT("Merge");
|
||||
CloseEnd(vert);
|
||||
// westPair will be removed and eastPair takes over.
|
||||
SetVWest(eastPair, westPair->vWest);
|
||||
return MERGE;
|
||||
return Merge;
|
||||
} else { // not neighbors
|
||||
PRINT("SKIP");
|
||||
return SKIP;
|
||||
PRINT("Skip");
|
||||
return Skip;
|
||||
}
|
||||
} else {
|
||||
if (!eastPair->vEast->right->IsPast(vert, precision_) &&
|
||||
vert->IsPast(eastPair->vEast, precision_) &&
|
||||
vert->pos.x > eastPair->vEast->right->pos.x + precision_) {
|
||||
PRINT("SKIP WEST");
|
||||
return SKIP;
|
||||
PRINT("Skip WEST");
|
||||
return Skip;
|
||||
}
|
||||
SetVWest(eastPair, vert);
|
||||
PRINT("WESTSIDE");
|
||||
return WESTSIDE;
|
||||
PRINT("WestSide");
|
||||
return WestSide;
|
||||
}
|
||||
} else {
|
||||
if (vert->left->Processed()) {
|
||||
if (!westPair->vWest->left->IsPast(vert, precision_) &&
|
||||
vert->IsPast(westPair->vWest, precision_) &&
|
||||
vert->pos.x < westPair->vWest->left->pos.x - precision_) {
|
||||
PRINT("SKIP EAST");
|
||||
return SKIP;
|
||||
PRINT("Skip EAST");
|
||||
return Skip;
|
||||
}
|
||||
SetVEast(westPair, vert);
|
||||
PRINT("EASTSIDE");
|
||||
return EASTSIDE;
|
||||
PRINT("EastSide");
|
||||
return EastSide;
|
||||
} else {
|
||||
PRINT("START");
|
||||
return START;
|
||||
PRINT("Start");
|
||||
return Start;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,12 +570,12 @@ class Monotones {
|
||||
}
|
||||
|
||||
/**
|
||||
* When vert is a START, this determines if it is backwards (forming a void or
|
||||
* When vert is a Start, this determines if it is backwards (forming a void or
|
||||
* hole). Usually the first return is adequate, but if it is degenerate, the
|
||||
* function will continue to search up the neighbors until the degeneracy is
|
||||
* broken and a certain answer is returned. Like CCW, this function returns 1
|
||||
* for a hole, -1 for a start, and 0 only if the entire polygon degenerates to
|
||||
* a polyline.
|
||||
* a line.
|
||||
*/
|
||||
int IsHole(VertItr vert) const {
|
||||
VertItr left = vert->left;
|
||||
@@ -822,7 +819,7 @@ class Monotones {
|
||||
|
||||
PairItr newPair = activePairs_.end();
|
||||
bool isHole = false;
|
||||
if (type == START) {
|
||||
if (type == Start) {
|
||||
newPair = activePairs_.insert(
|
||||
activePairs_.begin(), {vert, vert, monotones_.end(),
|
||||
activePairs_.end(), false, false, false});
|
||||
@@ -839,13 +836,13 @@ class Monotones {
|
||||
}
|
||||
|
||||
const PairItr pair = GetPair(vert, type);
|
||||
OVERLAP_ASSERT(type == SKIP || pair != activePairs_.end(),
|
||||
OVERLAP_ASSERT(type == Skip || pair != activePairs_.end(),
|
||||
"No active pair!");
|
||||
|
||||
if (type != SKIP && ShiftEast(vert, pair, isHole)) type = SKIP;
|
||||
if (type != SKIP && ShiftWest(vert, pair, isHole)) type = SKIP;
|
||||
if (type != Skip && ShiftEast(vert, pair, isHole)) type = Skip;
|
||||
if (type != Skip && ShiftWest(vert, pair, isHole)) type = Skip;
|
||||
|
||||
if (type == SKIP) {
|
||||
if (type == Skip) {
|
||||
OVERLAP_ASSERT(std::next(insertAt) != monotones_.end(),
|
||||
"Not Geometrically Valid! Tried to skip final vert.");
|
||||
OVERLAP_ASSERT(
|
||||
@@ -868,23 +865,23 @@ class Monotones {
|
||||
monotones_.splice(insertAt, monotones_, vert);
|
||||
|
||||
switch (type) {
|
||||
case WESTSIDE:
|
||||
case WestSide:
|
||||
nextAttached.push(vert->left);
|
||||
break;
|
||||
case EASTSIDE:
|
||||
case EastSide:
|
||||
nextAttached.push(vert->right);
|
||||
break;
|
||||
case START:
|
||||
case Start:
|
||||
nextAttached.push(vert->left);
|
||||
nextAttached.push(vert->right);
|
||||
break;
|
||||
case MERGE:
|
||||
case Merge:
|
||||
// Mark merge as hole for sweep-back.
|
||||
pair->vMerge = vert;
|
||||
case END:
|
||||
case End:
|
||||
RemovePair(pair);
|
||||
break;
|
||||
case SKIP:
|
||||
case Skip:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -947,29 +944,29 @@ class Monotones {
|
||||
if (vert->Processed()) continue;
|
||||
|
||||
VertType type = ProcessVert(vert);
|
||||
OVERLAP_ASSERT(type != SKIP, "SKIP should not happen on reverse sweep!");
|
||||
OVERLAP_ASSERT(type != Skip, "Skip should not happen on reverse sweep!");
|
||||
|
||||
PairItr westPair = GetPair(vert, type);
|
||||
OVERLAP_ASSERT(westPair != activePairs_.end(), "No active pair!");
|
||||
|
||||
switch (type) {
|
||||
case MERGE: {
|
||||
case Merge: {
|
||||
PairItr eastPair = std::next(westPair);
|
||||
if (eastPair->vMerge != monotones_.end())
|
||||
vert = SplitVerts(vert, eastPair->vMerge);
|
||||
eastPair->vMerge = vert;
|
||||
}
|
||||
case END:
|
||||
case End:
|
||||
RemovePair(westPair);
|
||||
case WESTSIDE:
|
||||
case EASTSIDE:
|
||||
case WestSide:
|
||||
case EastSide:
|
||||
if (westPair->vMerge != monotones_.end()) {
|
||||
VertItr eastVert = SplitVerts(vert, westPair->vMerge);
|
||||
if (type == WESTSIDE) westPair->vWest = eastVert;
|
||||
if (type == WestSide) westPair->vWest = eastVert;
|
||||
westPair->vMerge = monotones_.end();
|
||||
}
|
||||
break;
|
||||
case START: {
|
||||
case Start: {
|
||||
// Due to sweeping in the opposite direction, east and west are
|
||||
// swapped and what was the next pair is now the previous pair and
|
||||
// begin and end are swapped.
|
||||
@@ -999,7 +996,7 @@ class Monotones {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SKIP:
|
||||
case Skip:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1045,7 +1042,7 @@ namespace manifold {
|
||||
* @return std::vector<glm::ivec3> The triangles, referencing the original
|
||||
* vertex indicies.
|
||||
*/
|
||||
std::vector<glm::ivec3> Triangulate(const Polygons &polys, float precision) {
|
||||
std::vector<glm::ivec3> Triangulate(const PolygonsIdx &polys, float precision) {
|
||||
std::vector<glm::ivec3> triangles;
|
||||
try {
|
||||
Monotones monotones(polys, precision);
|
||||
|
||||
@@ -21,4 +21,4 @@ target_include_directories(${PROJECT_NAME}
|
||||
${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE utilities)
|
||||
target_link_libraries(${PROJECT_NAME} INTERFACE utilities)
|
||||
|
||||
@@ -14,37 +14,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hashtable.h"
|
||||
#include "optional"
|
||||
#include "par.h"
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
typedef unsigned long long int Uint64;
|
||||
|
||||
constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
|
||||
__host__ __device__ Uint64 AtomicCAS(Uint64& target, Uint64 compare,
|
||||
Uint64 val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return atomicCAS(&target, compare, val);
|
||||
#else
|
||||
std::atomic<Uint64>& tar = reinterpret_cast<std::atomic<Uint64>&>(target);
|
||||
tar.compare_exchange_strong(compare, val);
|
||||
return compare;
|
||||
#endif
|
||||
}
|
||||
|
||||
__host__ __device__ int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
__host__ __device__ int Prev3(int i) {
|
||||
constexpr glm::ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
__host__ __device__ Uint64 identity(Uint64 x) { return x; }
|
||||
|
||||
__host__ __device__ glm::ivec3 TetTri0(int i) {
|
||||
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
|
||||
@@ -135,7 +114,6 @@ __host__ __device__ glm::ivec4 DecodeMorton(Uint64 code) {
|
||||
}
|
||||
|
||||
struct GridVert {
|
||||
Uint64 key = kOpen;
|
||||
float distance = NAN;
|
||||
int edgeVerts[7] = {-1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
@@ -146,73 +124,11 @@ struct GridVert {
|
||||
}
|
||||
};
|
||||
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(VecDH<GridVert>& alloc, VecDH<uint32_t>& used, uint32_t step = 127)
|
||||
: step_{step}, table_{alloc}, used_{used} {}
|
||||
|
||||
__host__ __device__ int Size() const { return table_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const { return used_[0] * 2 > Size(); }
|
||||
|
||||
__host__ __device__ void Insert(const GridVert& vert) {
|
||||
uint32_t idx = vert.key & (Size() - 1);
|
||||
while (1) {
|
||||
Uint64& key = table_[idx].key;
|
||||
const Uint64 found = AtomicCAS(key, kOpen, vert.key);
|
||||
if (found == kOpen) {
|
||||
AtomicAdd(used_[0], 0x1u);
|
||||
table_[idx] = vert;
|
||||
return;
|
||||
}
|
||||
if (found == vert.key) return;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ GridVert operator[](Uint64 key) const {
|
||||
uint32_t idx = key & (Size() - 1);
|
||||
while (1) {
|
||||
const GridVert found = table_[idx];
|
||||
if (found.key == key || found.key == kOpen) return found;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ GridVert At(int idx) const { return table_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecD<GridVert> table_;
|
||||
VecD<uint32_t> used_;
|
||||
};
|
||||
|
||||
class HashTable {
|
||||
public:
|
||||
HashTable(uint32_t size, uint32_t step = 127)
|
||||
: alloc_{1 << (int)ceil(log2(size)), {}}, table_{alloc_, used_, step} {}
|
||||
|
||||
HashTableD D() { return table_; }
|
||||
|
||||
int Entries() const { return used_[0]; }
|
||||
|
||||
int Size() const { return table_.Size(); }
|
||||
|
||||
bool Full() const { return used_[0] * 2 > Size(); }
|
||||
|
||||
float FilledFraction() const { return static_cast<float>(used_[0]) / Size(); }
|
||||
|
||||
private:
|
||||
VecDH<GridVert> alloc_;
|
||||
VecDH<uint32_t> used_ = VecDH<uint32_t>(1, 0);
|
||||
HashTableD table_;
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
struct ComputeVerts {
|
||||
glm::vec3* vertPos;
|
||||
int* vertIndex;
|
||||
HashTableD gridVerts;
|
||||
HashTableD<GridVert, identity> gridVerts;
|
||||
const Func sdf;
|
||||
const glm::vec3 origin;
|
||||
const glm::ivec3 gridSize;
|
||||
@@ -247,7 +163,6 @@ struct ComputeVerts {
|
||||
const glm::vec3 position = Position(gridIndex);
|
||||
|
||||
GridVert gridVert;
|
||||
gridVert.key = mortonCode;
|
||||
gridVert.distance = BoundedSDF(gridIndex);
|
||||
|
||||
bool keep = false;
|
||||
@@ -270,14 +185,14 @@ struct ComputeVerts {
|
||||
gridVert.edgeVerts[i] = idx;
|
||||
}
|
||||
|
||||
if (keep) gridVerts.Insert(gridVert);
|
||||
if (keep) gridVerts.Insert(mortonCode, gridVert);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildTris {
|
||||
glm::ivec3* triVerts;
|
||||
int* triIndex;
|
||||
const HashTableD gridVerts;
|
||||
const HashTableD<GridVert, identity> gridVerts;
|
||||
|
||||
__host__ __device__ void CreateTri(const glm::ivec3& tri,
|
||||
const int edges[6]) {
|
||||
@@ -295,10 +210,11 @@ struct BuildTris {
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(int idx) {
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
if (base.key == kOpen) return;
|
||||
Uint64 basekey = gridVerts.KeyAt(idx);
|
||||
if (basekey == kOpen) return;
|
||||
|
||||
const glm::ivec4 baseIndex = DecodeMorton(base.key);
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
const glm::ivec4 baseIndex = DecodeMorton(basekey);
|
||||
|
||||
glm::ivec4 leadIndex = baseIndex;
|
||||
if (leadIndex.w == 0)
|
||||
@@ -355,6 +271,21 @@ struct BuildTris {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -388,7 +319,8 @@ namespace manifold {
|
||||
* input to the Manifold constructor for further operations.
|
||||
*/
|
||||
template <typename Func>
|
||||
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
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();
|
||||
@@ -397,17 +329,27 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
const glm::vec3 spacing = dim / (glm::vec3(gridSize));
|
||||
|
||||
const Uint64 maxMorton = MortonCode(glm::ivec4(gridSize + 1, 1));
|
||||
const auto policy = autoPolicy(maxMorton);
|
||||
|
||||
// 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 gridVerts(tableSize);
|
||||
HashTable<GridVert, identity> gridVerts(tableSize);
|
||||
VecDH<glm::vec3> vertPos(gridVerts.Size() * 7);
|
||||
|
||||
while (1) {
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(
|
||||
policy, countAt(0), maxMorton + 1,
|
||||
// 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}));
|
||||
|
||||
@@ -420,7 +362,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
tableSize *= 2;
|
||||
else
|
||||
tableSize *= ratio;
|
||||
gridVerts = HashTable(tableSize);
|
||||
gridVerts = HashTable<GridVert, identity>(tableSize);
|
||||
vertPos = VecDH<glm::vec3>(gridVerts.Size() * 7);
|
||||
} else { // Success
|
||||
vertPos.resize(index[0]);
|
||||
@@ -431,7 +373,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
VecDH<glm::ivec3> triVerts(gridVerts.Entries() * 12); // worst case
|
||||
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(policy, countAt(0), gridVerts.Size(),
|
||||
for_each_n(pol, countAt(0), gridVerts.Size(),
|
||||
BuildTris({triVerts.ptrD(), index.ptrD(), gridVerts.D()}));
|
||||
triVerts.resize(index[0]);
|
||||
|
||||
@@ -440,4 +382,4 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
return out;
|
||||
}
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
project (graphlite)
|
||||
|
||||
add_library(${PROJECT_NAME} src/connected_components.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT src/connected_components.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project(utilities)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
message("CUDA Support: ${MANIFOLD_USE_CUDA}")
|
||||
message("Parallel Backend: ${MANIFOLD_PAR}")
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
// 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 <stdint.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace {
|
||||
typedef unsigned long long int Uint64;
|
||||
typedef Uint64 (*hash_fun_t)(Uint64);
|
||||
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
|
||||
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
|
||||
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
|
||||
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) {
|
||||
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ull;
|
||||
x = (x ^ (x >> 27)) * 0x94d049bb133111ebull;
|
||||
x = x ^ (x >> 31);
|
||||
return x;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
/** @addtogroup Private
|
||||
* @{
|
||||
*/
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(VecDH<Uint64>& keys, VecDH<V>& values, VecDH<uint32_t>& used,
|
||||
uint32_t step = 1)
|
||||
: step_{step}, keys_{keys}, values_{values}, used_{used} {}
|
||||
|
||||
__host__ __device__ int Size() const { return keys_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const {
|
||||
return AtomicLoad(used_[0]) * 2 > Size();
|
||||
}
|
||||
|
||||
__host__ __device__ void Insert(Uint64 key, const V& val) {
|
||||
uint32_t idx = H(key) & (Size() - 1);
|
||||
while (1) {
|
||||
if (Full()) return;
|
||||
Uint64& k = keys_[idx];
|
||||
const Uint64 found = AtomicCAS(k, kOpen, key);
|
||||
if (found == kOpen) {
|
||||
AtomicAdd(used_[0], 0x1u);
|
||||
values_[idx] = val;
|
||||
return;
|
||||
}
|
||||
if (found == key) return;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ 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__ Uint64 KeyAt(int idx) const {
|
||||
return AtomicLoad(keys_[idx]);
|
||||
}
|
||||
__host__ __device__ V& At(int idx) const { return values_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecD<Uint64> keys_;
|
||||
VecD<V> values_;
|
||||
VecD<uint32_t> used_;
|
||||
};
|
||||
|
||||
template <typename V, hash_fun_t H = hash64bit>
|
||||
class HashTable {
|
||||
public:
|
||||
HashTable(uint32_t size, uint32_t step = 1)
|
||||
: keys_{1 << (int)ceil(log2(size)), kOpen},
|
||||
values_{1 << (int)ceil(log2(size)), {}},
|
||||
table_{keys_, values_, used_, step} {}
|
||||
|
||||
HashTableD<V, H> D() { return table_; }
|
||||
|
||||
int Entries() const { return AtomicLoad(used_[0]); }
|
||||
|
||||
int Size() const { return table_.Size(); }
|
||||
|
||||
bool Full() const { return AtomicLoad(used_[0]) * 2 > Size(); }
|
||||
|
||||
float FilledFraction() const {
|
||||
return static_cast<float>(AtomicLoad(used_[0])) / Size();
|
||||
}
|
||||
|
||||
VecDH<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);
|
||||
HashTableD<V, H> table_;
|
||||
};
|
||||
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -49,10 +49,6 @@ enum class ExecutionPolicy {
|
||||
Seq,
|
||||
};
|
||||
|
||||
constexpr ExecutionPolicy ParUnseq = ExecutionPolicy::ParUnseq;
|
||||
constexpr ExecutionPolicy Par = ExecutionPolicy::Par;
|
||||
constexpr ExecutionPolicy Seq = ExecutionPolicy::Seq;
|
||||
|
||||
// ExecutionPolicy:
|
||||
// - Sequential for small workload,
|
||||
// - Parallel (CPU) for medium workload,
|
||||
@@ -60,12 +56,12 @@ constexpr ExecutionPolicy Seq = ExecutionPolicy::Seq;
|
||||
inline ExecutionPolicy autoPolicy(int size) {
|
||||
// some random numbers
|
||||
if (size <= (1 << 12)) {
|
||||
return Seq;
|
||||
return ExecutionPolicy::Seq;
|
||||
}
|
||||
if (size <= (1 << 16) || !CudaEnabled()) {
|
||||
return Par;
|
||||
return ExecutionPolicy::Par;
|
||||
}
|
||||
return ParUnseq;
|
||||
return ExecutionPolicy::ParUnseq;
|
||||
}
|
||||
|
||||
#ifdef MANIFOLD_USE_CUDA
|
||||
|
||||
@@ -89,11 +89,6 @@ inline HOST_DEVICE glm::mat4x3 RotateUp(glm::vec3 up) {
|
||||
return glm::mat4x3(glm::rotate(glm::mat4(1), angle, axis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 for positive values, -1 for negative, and 0 for exactly zero.
|
||||
*/
|
||||
inline HOST_DEVICE int Signum(float val) { return (val > 0) - (val < 0); }
|
||||
|
||||
/**
|
||||
* Determines if the three points are wound counter-clockwise, clockwise, or
|
||||
* colinear within the specified tolerance.
|
||||
@@ -122,22 +117,12 @@ inline HOST_DEVICE int CCW(glm::vec2 p0, glm::vec2 p1, glm::vec2 p2,
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Polygon vertex.
|
||||
*/
|
||||
struct PolyVert {
|
||||
/// X-Y position
|
||||
glm::vec2 pos;
|
||||
/// ID or index into another vertex vector
|
||||
int idx;
|
||||
};
|
||||
|
||||
/**
|
||||
* Single polygon contour, wound CCW. First and last point are implicitly
|
||||
* connected. Should ensure all input is
|
||||
* [ε-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
|
||||
*/
|
||||
using SimplePolygon = std::vector<PolyVert>;
|
||||
using SimplePolygon = std::vector<glm::vec2>;
|
||||
|
||||
/**
|
||||
* Set of polygons with holes. Order of contours is arbitrary. Can contain any
|
||||
@@ -148,22 +133,6 @@ using SimplePolygon = std::vector<PolyVert>;
|
||||
using Polygons = std::vector<SimplePolygon>;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of vertices
|
||||
int NumVert() const { return this->vertPos.size() / 3; };
|
||||
/// Number of triangles
|
||||
int NumTri() const { return this->triVerts.size() / 3; };
|
||||
|
||||
std::vector<float> vertPos;
|
||||
std::vector<float> vertNormal;
|
||||
std::vector<uint32_t> triVerts;
|
||||
std::vector<float> halfedgeTangent;
|
||||
};
|
||||
|
||||
/**
|
||||
* The triangle-mesh input and output of this library.
|
||||
*/
|
||||
@@ -182,45 +151,81 @@ struct Mesh {
|
||||
/// as 3 * tri + i, representing the tangent from Mesh.triVerts[tri][i] along
|
||||
/// the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<glm::vec4> halfedgeTangent;
|
||||
};
|
||||
|
||||
Mesh() = default;
|
||||
Mesh(const std::vector<glm::vec3>& vertPos_,
|
||||
const std::vector<glm::ivec3>& triVerts_,
|
||||
const std::vector<glm::vec3>& vertNormal_ = {},
|
||||
const std::vector<glm::vec4>& halfedgeTangent_ = {})
|
||||
: vertPos(vertPos_),
|
||||
triVerts(triVerts_),
|
||||
vertNormal(vertNormal_),
|
||||
halfedgeTangent(halfedgeTangent_) {}
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly. This may not be manifold since the verts are duplicated
|
||||
* along property boundaries that do not match. The additional merge vectors
|
||||
* store this missing information, allowing the manifold to be reconstructed.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of property vertices
|
||||
uint32_t NumVert() const { return vertProperties.size() / numProp; };
|
||||
/// Number of triangles
|
||||
uint32_t NumTri() const { return triVerts.size() / 3; };
|
||||
|
||||
Mesh(const MeshGL& in) {
|
||||
const int numTri = in.NumTri();
|
||||
const int numVert = in.NumVert();
|
||||
triVerts.resize(numTri);
|
||||
vertPos.resize(numVert);
|
||||
if (!in.vertNormal.empty()) {
|
||||
vertNormal.resize(numVert);
|
||||
/// Number of properties per vertex, always >= 3.
|
||||
uint32_t numProp = 3;
|
||||
/// Flat, GL-style interleaved list of all vertex properties: propVal =
|
||||
/// vertProperties[vert * numProp + propIdx]. The first three properties are
|
||||
/// always the position x, y, z.
|
||||
std::vector<float> vertProperties;
|
||||
/// The vertex indices of the three triangle corners in CCW (from the outside)
|
||||
/// order, for each triangle.
|
||||
std::vector<uint32_t> triVerts;
|
||||
/// Optional: A list of only the vertex indicies that need to be merged to
|
||||
/// reconstruct the manifold.
|
||||
std::vector<uint32_t> mergeFromVert;
|
||||
/// Optional: The same length as mergeFromVert, and the corresponding value
|
||||
/// contains the vertex to merge with. It will have an identical position, but
|
||||
/// the other properties may differ.
|
||||
std::vector<uint32_t> mergeToVert;
|
||||
/// Optional: Indicates runs of triangles that correspond to a particular
|
||||
/// input mesh instance. The runs encompass all of triVerts and are sorted
|
||||
/// by runOriginalID. Run i begins at triVerts[runIndex[i]] and ends at
|
||||
/// triVerts[runIndex[i+1]]. All runIndex values are divisible by 3.
|
||||
std::vector<uint32_t> runIndex;
|
||||
/// Optional: The OriginalID of the mesh this triangle run came from. This ID
|
||||
/// is ideal for reapplying materials to the output mesh. Multiple runs may
|
||||
/// have the same ID, e.g. representing different copies of the same input
|
||||
/// mesh. If you create an input MeshGL that you want to be able to reference
|
||||
/// as one or more originals, be sure to set unique values from ReserveIDs().
|
||||
std::vector<uint32_t> runOriginalID;
|
||||
/// Optional: For each run, a 3x4 transform is stored representing how the
|
||||
/// corresponding original mesh was transformed to create this triangle run.
|
||||
/// This matrix is stored in column-major order and the length of the overall
|
||||
/// vector is 12 * runOriginalID.size().
|
||||
std::vector<float> runTransform;
|
||||
/// Optional: Length NumTri, contains an ID of the source face this triangle
|
||||
/// comes from. When auto-generated, this ID will be a triangle index into the
|
||||
/// original mesh. All neighboring coplanar triangles from that input mesh
|
||||
/// will refer to a single triangle of that group as the faceID. When
|
||||
/// supplying faceIDs, ensure that triangles with the same ID are in fact
|
||||
/// coplanar and have consistent properties (within some tolerance) or the
|
||||
/// output will be surprising.
|
||||
std::vector<uint32_t> faceID;
|
||||
/// Optional: The X-Y-Z-W weighted tangent vectors for smooth Refine(). If
|
||||
/// non-empty, must be exactly four times as long as Mesh.triVerts. Indexed
|
||||
/// as 4 * (3 * tri + i) + j, i < 3, j < 4, representing the tangent value
|
||||
/// Mesh.triVerts[tri][i] along the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<float> halfedgeTangent;
|
||||
|
||||
MeshGL() = default;
|
||||
MeshGL(const Mesh& mesh) {
|
||||
numProp = 3;
|
||||
vertProperties.resize(numProp * mesh.vertPos.size());
|
||||
for (int i = 0; i < mesh.vertPos.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) vertProperties[3 * i + j] = mesh.vertPos[i][j];
|
||||
}
|
||||
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
vertPos[i] = {in.vertPos[3 * i], in.vertPos[3 * i + 1],
|
||||
in.vertPos[3 * i + 2]};
|
||||
if (!in.vertNormal.empty()) {
|
||||
vertNormal[i] = {in.vertNormal[3 * i], in.vertNormal[3 * i + 1],
|
||||
in.vertNormal[3 * i + 2]};
|
||||
}
|
||||
triVerts.resize(3 * mesh.triVerts.size());
|
||||
for (int i = 0; i < mesh.triVerts.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) triVerts[3 * i + j] = mesh.triVerts[i][j];
|
||||
}
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
triVerts[i] = {in.triVerts[3 * i], in.triVerts[3 * i + 1],
|
||||
in.triVerts[3 * i + 2]};
|
||||
if (!in.halfedgeTangent.empty()) {
|
||||
for (const int j : {0, 1, 2})
|
||||
halfedgeTangent[3 * i + j] = {
|
||||
in.halfedgeTangent[4 * (3 * i + j)],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 1],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 2],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 3]};
|
||||
}
|
||||
halfedgeTangent.resize(4 * mesh.halfedgeTangent.size());
|
||||
for (int i = 0; i < mesh.halfedgeTangent.size(); ++i) {
|
||||
for (int j : {0, 1, 2, 3})
|
||||
halfedgeTangent[4 * i + j] = mesh.halfedgeTangent[i][j];
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -256,59 +261,15 @@ struct Curvature {
|
||||
};
|
||||
|
||||
/**
|
||||
* Part of MeshRelation - represents a single triangle relation to an original
|
||||
* Mesh.
|
||||
* Connected component indices and number of discrete components. Intermediate
|
||||
* step for Manifold.Decompose(), completed by Manifold.GetComponents()
|
||||
*/
|
||||
struct BaryRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// The triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri].
|
||||
int tri;
|
||||
/// For the three corners of the output triangle, new (intersection) vertices
|
||||
/// store an index to the MeshRelation.barycentric vector, while original
|
||||
/// vertices have negative values referring to Mesh.triVerts[tri][i + 3].
|
||||
glm::ivec3 vertBary;
|
||||
struct Components {
|
||||
std::vector<int> indices;
|
||||
int numComponents;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @ingroup Connections
|
||||
* Represents the relationship of this output Mesh to all input Meshes that
|
||||
* eventually led to it, see Manifold.GetMeshRelation().
|
||||
*/
|
||||
struct MeshRelation {
|
||||
/// A vector of shared barycentric coordinates representing the position of a
|
||||
/// vertex relative to its original triangle.
|
||||
std::vector<glm::vec3> barycentric;
|
||||
/// A vector matching Mesh.triVerts that contains the relation of each output
|
||||
/// triangle to a single input triangle.
|
||||
std::vector<BaryRef> triBary;
|
||||
|
||||
/**
|
||||
* A convenience function to get the barycentric coordinates of a given
|
||||
* corner.
|
||||
*
|
||||
* @param tri A valid triangle index of Mesh.triVerts.
|
||||
* @param vert The corner of the triangle: 0, 1, or 2.
|
||||
*/
|
||||
inline glm::vec3 UVW(int tri, int vert) {
|
||||
glm::vec3 uvw(0.0f);
|
||||
const int idx = triBary[tri].vertBary[vert];
|
||||
if (idx < 0) {
|
||||
uvw[idx + 3] = 1;
|
||||
} else {
|
||||
uvw = barycentric[idx];
|
||||
}
|
||||
return uvw;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup Connections
|
||||
* Axis-aligned bounding box
|
||||
@@ -503,7 +464,7 @@ struct ExecutionParams {
|
||||
bool verbose = false;
|
||||
/// If processOverlaps is false, a geometric check will be performed to assert
|
||||
/// all triangles are CCW.
|
||||
bool processOverlaps = false;
|
||||
bool processOverlaps = true;
|
||||
/// Suppresses printed errors regarding CW triangles. Has no effect if
|
||||
/// processOverlaps is true.
|
||||
bool suppressErrors = false;
|
||||
@@ -541,12 +502,6 @@ inline std::ostream& operator<<(std::ostream& stream, const glm::mat4x3& mat) {
|
||||
<< tam[2] << std::endl;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const BaryRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri
|
||||
<< ", uvw idx: " << ref.vertBary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of this vector to standard output. Only exists if compiled
|
||||
* with MANIFOLD_DEBUG flag.
|
||||
|
||||
@@ -103,6 +103,16 @@ thrust::counting_iterator<T> countAt(T i) {
|
||||
return thrust::make_counting_iterator(i);
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
inline __host__ __device__ 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) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
|
||||
@@ -18,9 +18,18 @@ add_subdirectory(third_party)
|
||||
|
||||
enable_testing()
|
||||
|
||||
set(SOURCE_FILES polygon_test.cpp mesh_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
|
||||
set(SOURCE_FILES polygon_test.cpp manifold_test.cpp boolean_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
list(APPEND SOURCE_FILES manifoldc_test.cpp)
|
||||
endif()
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold samples samplesGPU)
|
||||
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold sdf samples samplesGPU)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
target_link_libraries(${PROJECT_NAME} manifoldc)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_EXPORT)
|
||||
add_subdirectory(meshIO)
|
||||
@@ -29,12 +38,17 @@ if(MANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(mesh_test.cpp sdf_test.cpp PROPERTIES LANGUAGE 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_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
add_test(test_all ${PROJECT_NAME})
|
||||
|
||||
|
||||
@@ -0,0 +1,591 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "test.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
/**
|
||||
* The very simplest Boolean operation test.
|
||||
*/
|
||||
TEST(Boolean, Tetra) {
|
||||
Manifold tetra = Manifold::Tetrahedron();
|
||||
MeshGL tetraGL = WithPositionColors(tetra);
|
||||
tetra = Manifold(tetraGL);
|
||||
EXPECT_TRUE(!tetra.IsEmpty());
|
||||
|
||||
Manifold tetra2 = tetra.Translate(glm::vec3(0.5f));
|
||||
Manifold result = tetra2 - tetra;
|
||||
|
||||
ExpectMeshes(result, {{8, 12, 3, 11}});
|
||||
|
||||
RelatedGL(result, {tetraGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MeshGLRoundTrip) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2));
|
||||
ASSERT_GE(cube.OriginalID(), 0);
|
||||
const MeshGL original = cube.GetMeshGL();
|
||||
|
||||
Manifold result = cube + cube.Translate({1, 1, 0});
|
||||
|
||||
ASSERT_LT(result.OriginalID(), 0);
|
||||
ExpectMeshes(result, {{18, 32}});
|
||||
RelatedGL(result, {original});
|
||||
|
||||
MeshGL inGL = result.GetMeshGL();
|
||||
ASSERT_EQ(inGL.runOriginalID.size(), 2);
|
||||
const Manifold result2(inGL);
|
||||
|
||||
ASSERT_LT(result2.OriginalID(), 0);
|
||||
ExpectMeshes(result2, {{18, 32}});
|
||||
RelatedGL(result2, {original});
|
||||
|
||||
const MeshGL outGL = result2.GetMeshGL();
|
||||
ASSERT_EQ(outGL.runOriginalID.size(), 2);
|
||||
}
|
||||
|
||||
TEST(Boolean, Normals) {
|
||||
const MeshGL boxGL = WithNormals(Manifold::Cube({200, 200, 100}, true));
|
||||
const Manifold box(boxGL);
|
||||
const MeshGL sphereGL = WithNormals(Manifold::Sphere(60));
|
||||
const Manifold sphere(sphereGL);
|
||||
|
||||
Manifold cube = box ^ box.Rotate(90) ^ box.Rotate(0, 90);
|
||||
|
||||
Manifold result =
|
||||
cube - (sphere.Rotate(180) -
|
||||
sphere.Scale(glm::vec3(0.5)).Rotate(90).Translate({40, 40, 40}));
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.faceted = false;
|
||||
opt.mat.roughness = 0;
|
||||
opt.mat.normalChannels = {3, 4, 5};
|
||||
if (options.exportModels)
|
||||
ExportMesh("normals.glb", result.GetMeshGL({3, 4, 5}), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(result, {boxGL, sphereGL}, true);
|
||||
}
|
||||
|
||||
TEST(Boolean, EmptyOriginal) {
|
||||
const Manifold cube = Manifold::Cube();
|
||||
const Manifold tet = Manifold::Tetrahedron();
|
||||
const Manifold result = tet - cube.Translate({3, 4, 5});
|
||||
const MeshGL mesh = result.GetMeshGL();
|
||||
ASSERT_EQ(mesh.runIndex.size(), 3);
|
||||
EXPECT_EQ(mesh.runIndex[0], 0);
|
||||
EXPECT_EQ(mesh.runIndex[1], mesh.triVerts.size());
|
||||
EXPECT_EQ(mesh.runIndex[2], mesh.triVerts.size());
|
||||
ASSERT_EQ(mesh.runOriginalID.size(), 2);
|
||||
EXPECT_EQ(mesh.runOriginalID[0], tet.OriginalID());
|
||||
EXPECT_EQ(mesh.runOriginalID[1], cube.OriginalID());
|
||||
ASSERT_EQ(mesh.runTransform.size(), 24);
|
||||
EXPECT_EQ(mesh.runTransform[9], 0);
|
||||
EXPECT_EQ(mesh.runTransform[10], 0);
|
||||
EXPECT_EQ(mesh.runTransform[11], 0);
|
||||
EXPECT_EQ(mesh.runTransform[12 + 9], 3);
|
||||
EXPECT_EQ(mesh.runTransform[12 + 10], 4);
|
||||
EXPECT_EQ(mesh.runTransform[12 + 11], 5);
|
||||
}
|
||||
|
||||
TEST(Boolean, Mirrored) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(1)).Scale({1, -1, 1});
|
||||
EXPECT_TRUE(cube.IsManifold());
|
||||
EXPECT_TRUE(cube.MatchesTriNormals());
|
||||
|
||||
Manifold cube2 = Manifold::Cube(glm::vec3(1)).Scale({0.5, -1, 0.5});
|
||||
Manifold result = cube - cube2;
|
||||
|
||||
ExpectMeshes(result, {{12, 20}});
|
||||
|
||||
auto prop = result.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 0.75);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 5.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests check Boolean operations on coplanar faces.
|
||||
*/
|
||||
TEST(Boolean, SelfSubtract) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
Manifold empty = cube - cube;
|
||||
EXPECT_TRUE(empty.IsManifold());
|
||||
EXPECT_TRUE(empty.IsEmpty());
|
||||
|
||||
auto prop = empty.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 0.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 0.0f);
|
||||
}
|
||||
|
||||
TEST(Boolean, Perturb) {
|
||||
Mesh tmp;
|
||||
tmp.vertPos = {{0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 1.0f, 0.0f},
|
||||
{1.0f, 0.0f, 0.0f},
|
||||
{0.0f, 0.0f, 1.0f}};
|
||||
tmp.triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
Manifold corner(tmp);
|
||||
Manifold empty = corner - corner;
|
||||
EXPECT_TRUE(empty.IsManifold());
|
||||
EXPECT_TRUE(empty.IsEmpty());
|
||||
|
||||
auto prop = empty.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 0.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 0.0f);
|
||||
}
|
||||
|
||||
TEST(Boolean, Coplanar) {
|
||||
Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f);
|
||||
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 out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{32, 64, 3, 49}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(out, {cylinderGL});
|
||||
}
|
||||
|
||||
/**
|
||||
* Colinear edges are not collapsed like above due to non-coplanar properties.
|
||||
*/
|
||||
TEST(Boolean, CoplanarProp) {
|
||||
Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f);
|
||||
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 out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{42, 84, 3, 68}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(out, {cylinderGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MultiCoplanar) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
Manifold first = cube - cube.Translate({0.3f, 0.3f, 0.0f});
|
||||
cube = cube.Translate({-0.3f, -0.3f, 0.0f});
|
||||
Manifold out = first - cube;
|
||||
CheckStrictly(out);
|
||||
EXPECT_EQ(out.Genus(), -1);
|
||||
auto prop = out.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 0.18, 1e-5);
|
||||
EXPECT_NEAR(prop.surfaceArea, 2.76, 1e-5);
|
||||
}
|
||||
|
||||
TEST(Boolean, FaceUnion) {
|
||||
Manifold cubes = Manifold::Cube();
|
||||
cubes += cubes.Translate({1, 0, 0});
|
||||
EXPECT_EQ(cubes.Genus(), 0);
|
||||
ExpectMeshes(cubes, {{12, 20}});
|
||||
auto prop = cubes.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 2, 1e-5);
|
||||
EXPECT_NEAR(prop.surfaceArea, 10, 1e-5);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("faceUnion.glb", cubes.GetMesh(), {});
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Boolean, EdgeUnion) {
|
||||
Manifold cubes = Manifold::Cube();
|
||||
cubes += cubes.Translate({1, 1, 0});
|
||||
ExpectMeshes(cubes, {{8, 12}, {8, 12}});
|
||||
}
|
||||
|
||||
TEST(Boolean, EdgeUnion2) {
|
||||
Manifold tets = Manifold::Tetrahedron();
|
||||
Manifold cube2 = tets;
|
||||
tets = tets.Translate({0, 0, -1});
|
||||
tets += cube2.Translate({0, 0, 1}).Rotate(0, 0, 90);
|
||||
ExpectMeshes(tets, {{4, 4}, {4, 4}});
|
||||
}
|
||||
|
||||
TEST(Boolean, CornerUnion) {
|
||||
Manifold cubes = Manifold::Cube();
|
||||
cubes += cubes.Translate({1, 1, 1});
|
||||
ExpectMeshes(cubes, {{8, 12}, {8, 12}});
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests verify that the spliting helper functions return meshes with
|
||||
* volumes that make sense.
|
||||
*/
|
||||
TEST(Boolean, Split) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2.0f), true);
|
||||
Manifold oct = Manifold::Sphere(1, 4).Translate(glm::vec3(0.0f, 0.0f, 1.0f));
|
||||
std::pair<Manifold, Manifold> splits = cube.Split(oct);
|
||||
CheckStrictly(splits.first);
|
||||
CheckStrictly(splits.second);
|
||||
EXPECT_FLOAT_EQ(splits.first.GetProperties().volume +
|
||||
splits.second.GetProperties().volume,
|
||||
cube.GetProperties().volume);
|
||||
}
|
||||
|
||||
TEST(Boolean, SplitByPlane) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2.0f), true);
|
||||
cube = cube.Translate({0.0f, 1.0f, 0.0f});
|
||||
cube = cube.Rotate(90.0f, 0.0f, 0.0f);
|
||||
std::pair<Manifold, Manifold> splits =
|
||||
cube.SplitByPlane({0.0f, 0.0f, 1.0f}, 1.0f);
|
||||
CheckStrictly(splits.first);
|
||||
CheckStrictly(splits.second);
|
||||
EXPECT_NEAR(splits.first.GetProperties().volume,
|
||||
splits.second.GetProperties().volume, 1e-5);
|
||||
|
||||
Manifold first = cube.TrimByPlane({0.0f, 0.0f, 1.0f}, 1.0f);
|
||||
// Verify trim returns the same result as the first split by checking that
|
||||
// their bounding boxes contain each other, thus they are equal.
|
||||
EXPECT_TRUE(splits.first.BoundingBox().Contains(first.BoundingBox()));
|
||||
EXPECT_TRUE(first.BoundingBox().Contains(splits.first.BoundingBox()));
|
||||
}
|
||||
|
||||
TEST(Boolean, SplitByPlane60) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(2.0f), true);
|
||||
cube = cube.Translate({0.0f, 1.0f, 0.0f});
|
||||
cube = cube.Rotate(0.0f, 0.0f, -60.0f);
|
||||
cube = cube.Translate({2.0f, 0.0f, 0.0f});
|
||||
float phi = 30.0f;
|
||||
std::pair<Manifold, Manifold> splits =
|
||||
cube.SplitByPlane({sind(phi), -cosd(phi), 0.0f}, 1.0f);
|
||||
CheckStrictly(splits.first);
|
||||
CheckStrictly(splits.second);
|
||||
EXPECT_NEAR(splits.first.GetProperties().volume,
|
||||
splits.second.GetProperties().volume, 1e-5);
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests that non-intersecting geometry is properly retained.
|
||||
*/
|
||||
TEST(Boolean, Vug) {
|
||||
Manifold cube = Manifold::Cube(glm::vec3(4.0f), true);
|
||||
Manifold vug = cube - Manifold::Cube();
|
||||
|
||||
EXPECT_EQ(vug.Genus(), -1);
|
||||
|
||||
Manifold half = vug.SplitByPlane({0.0f, 0.0f, 1.0f}, -1.0f).first;
|
||||
CheckStrictly(half);
|
||||
EXPECT_EQ(half.Genus(), -1);
|
||||
|
||||
auto prop = half.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 4.0 * 4.0 * 3.0 - 1.0);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 16.0 * 2 + 12.0 * 4 + 6.0);
|
||||
}
|
||||
|
||||
TEST(Boolean, Empty) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
float cubeVol = cube.GetProperties().volume;
|
||||
Manifold empty;
|
||||
|
||||
EXPECT_EQ((cube + empty).GetProperties().volume, cubeVol);
|
||||
EXPECT_EQ((cube - empty).GetProperties().volume, cubeVol);
|
||||
EXPECT_TRUE((empty - cube).IsEmpty());
|
||||
EXPECT_TRUE((cube ^ empty).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(Boolean, Winding) {
|
||||
std::vector<Manifold> cubes;
|
||||
cubes.emplace_back(Manifold::Cube(glm::vec3(3.0f), true));
|
||||
cubes.emplace_back(Manifold::Cube(glm::vec3(2.0f), true));
|
||||
Manifold doubled = Manifold::Compose(cubes);
|
||||
|
||||
Manifold cube = Manifold::Cube(glm::vec3(1.0f), true);
|
||||
EXPECT_TRUE((cube ^= doubled).IsManifold());
|
||||
}
|
||||
|
||||
TEST(Boolean, NonIntersecting) {
|
||||
Manifold cube1 = Manifold::Cube();
|
||||
float vol1 = cube1.GetProperties().volume;
|
||||
Manifold cube2 = cube1.Scale(glm::vec3(2)).Translate({3, 0, 0});
|
||||
float vol2 = cube2.GetProperties().volume;
|
||||
|
||||
EXPECT_EQ((cube1 + cube2).GetProperties().volume, vol1 + vol2);
|
||||
EXPECT_EQ((cube1 - cube2).GetProperties().volume, vol1);
|
||||
EXPECT_TRUE((cube1 ^ cube2).IsEmpty());
|
||||
}
|
||||
|
||||
TEST(Boolean, Precision) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
Manifold cube2 = cube;
|
||||
Manifold cube3 = cube;
|
||||
float distance = 100;
|
||||
float scale = distance * kTolerance;
|
||||
cube2 = cube2.Scale(glm::vec3(scale)).Translate({distance, 0, 0});
|
||||
|
||||
cube += cube2;
|
||||
ExpectMeshes(cube, {{8, 12}});
|
||||
|
||||
cube3 = cube3.Scale(glm::vec3(2 * scale)).Translate({distance, 0, 0});
|
||||
cube += cube3;
|
||||
ExpectMeshes(cube, {{8, 12}, {8, 12}});
|
||||
}
|
||||
|
||||
TEST(Boolean, Precision2) {
|
||||
float scale = 1000;
|
||||
Manifold cube = Manifold::Cube(glm::vec3(scale));
|
||||
Manifold cube2 = cube;
|
||||
float distance = scale * (1 - kTolerance / 2);
|
||||
|
||||
cube2 = cube2.Translate(glm::vec3(-distance));
|
||||
EXPECT_TRUE((cube ^ cube2).IsEmpty());
|
||||
|
||||
cube2 = cube2.Translate(glm::vec3(scale * kTolerance));
|
||||
EXPECT_FALSE((cube ^ cube2).IsEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests verify correct topology and geometry for complex boolean
|
||||
* operations between valid shapes with many faces.
|
||||
*/
|
||||
TEST(Boolean, Sphere) {
|
||||
Manifold sphere = Manifold::Sphere(1.0f, 12);
|
||||
MeshGL sphereGL = WithPositionColors(sphere);
|
||||
sphere = Manifold(sphereGL);
|
||||
|
||||
Manifold sphere2 = sphere.Translate(glm::vec3(0.5));
|
||||
Manifold result = sphere - sphere2;
|
||||
|
||||
ExpectMeshes(result, {{74, 144, 3, 110}});
|
||||
EXPECT_EQ(result.NumDegenerateTris(), 0);
|
||||
|
||||
RelatedGL(result, {sphereGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MeshRelation) {
|
||||
Mesh gyroidMesh = Gyroid();
|
||||
MeshGL gyroidMeshGL = WithPositionColors(gyroidMesh);
|
||||
Manifold gyroid(gyroidMeshGL);
|
||||
|
||||
Manifold gyroid2 = gyroid.Translate(glm::vec3(2.0f));
|
||||
|
||||
EXPECT_FALSE(gyroid.IsEmpty());
|
||||
EXPECT_TRUE(gyroid.IsManifold());
|
||||
EXPECT_TRUE(gyroid.MatchesTriNormals());
|
||||
EXPECT_LE(gyroid.NumDegenerateTris(), 0);
|
||||
Manifold result = gyroid + gyroid2;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels)
|
||||
ExportMesh("gyroidUnion.glb", result.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
EXPECT_TRUE(result.MatchesTriNormals());
|
||||
EXPECT_LE(result.NumDegenerateTris(), 1);
|
||||
EXPECT_EQ(result.Decompose().size(), 1);
|
||||
auto prop = result.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 226, 1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 387, 1);
|
||||
|
||||
RelatedGL(result, {gyroidMeshGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, Cylinders) {
|
||||
Manifold rod = Manifold::Cylinder(1.0, 0.4, -1.0, 12);
|
||||
float arrays1[][12] = {
|
||||
{0, 0, 1, 3, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 6}, //
|
||||
{0, 0, 1, 2, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 8}, //
|
||||
|
||||
{0, 0, 1, 1, //
|
||||
-1, 0, 0, 2, //
|
||||
0, -1, 0, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 2, //
|
||||
0, 0, 1, 6}, //
|
||||
{0, 0, 1, 3, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 7}, //
|
||||
{0, 0, 1, 1, //
|
||||
-1, 0, 0, 3, //
|
||||
0, -1, 0, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 0, 1, 4, //
|
||||
0, -1, 0, 6}, //
|
||||
{1, 0, 0, 4, //
|
||||
0, 0, 1, 4, //
|
||||
0, -1, 0, 6}, //
|
||||
};
|
||||
float arrays2[][12] = {
|
||||
{1, 0, 0, 3, //
|
||||
0, 0, 1, 2, //
|
||||
0, -1, 0, 6}, //
|
||||
{1, 0, 0, 4, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 6}, //
|
||||
|
||||
{0, 0, 1, 2, //
|
||||
-1, 0, 0, 2, //
|
||||
0, -1, 0, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 2, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 1, //
|
||||
0, 1, 0, 3, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 4, //
|
||||
0, 0, 1, 7}, //
|
||||
{1, 0, 0, 3, //
|
||||
0, 1, 0, 5, //
|
||||
0, 0, 1, 6}, //
|
||||
{0, 0, 1, 3, //
|
||||
-1, 0, 0, 4, //
|
||||
0, -1, 0, 6}, //
|
||||
};
|
||||
|
||||
Manifold m1;
|
||||
for (auto& array : arrays1) {
|
||||
glm::mat4x3 mat;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
mat[i][j] = array[j * 4 + i];
|
||||
}
|
||||
}
|
||||
m1 += rod.Transform(mat);
|
||||
}
|
||||
|
||||
Manifold m2;
|
||||
for (auto& array : arrays2) {
|
||||
glm::mat4x3 mat;
|
||||
for (const int i : {0, 1, 2, 3}) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
mat[i][j] = array[j * 4 + i];
|
||||
}
|
||||
}
|
||||
m2 += rod.Transform(mat);
|
||||
}
|
||||
m1 += m2;
|
||||
|
||||
EXPECT_TRUE(m1.IsManifold());
|
||||
EXPECT_TRUE(m1.MatchesTriNormals());
|
||||
EXPECT_LE(m1.NumDegenerateTris(), 12);
|
||||
}
|
||||
|
||||
TEST(Boolean, Cubes) {
|
||||
Manifold result = Manifold::Cube({1.2, 1, 1}, true).Translate({0, -0.5, 0.5});
|
||||
result += Manifold::Cube({1, 0.8, 0.5}).Translate({-0.5, 0, 0.5});
|
||||
result += Manifold::Cube({1.2, 0.1, 0.5}).Translate({-0.6, -0.1, 0});
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
EXPECT_TRUE(result.MatchesTriNormals());
|
||||
EXPECT_LE(result.NumDegenerateTris(), 0);
|
||||
auto prop = result.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 1.6, 0.001);
|
||||
EXPECT_NEAR(prop.surfaceArea, 9.2, 0.01);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("cubes.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Boolean, Subtract) {
|
||||
Mesh firstMesh;
|
||||
firstMesh.vertPos = {{0, 0, 0}, {1540, 0, 0},
|
||||
{1540, 70, 0}, {0, 70, 0},
|
||||
{0, 0, -278.282}, {1540, 70, -278.282},
|
||||
{1540, 0, -278.282}, {0, 70, -278.282}};
|
||||
firstMesh.triVerts = {
|
||||
{0, 1, 2}, {2, 3, 0}, {4, 5, 6}, {5, 4, 7}, {6, 2, 1}, {6, 5, 2},
|
||||
{5, 3, 2}, {5, 7, 3}, {7, 0, 3}, {7, 4, 0}, {4, 1, 0}, {4, 6, 1},
|
||||
};
|
||||
|
||||
Mesh secondMesh;
|
||||
secondMesh.vertPos = {
|
||||
{2.04636e-12, 70, 50000}, {2.04636e-12, -1.27898e-13, 50000},
|
||||
{1470, -1.27898e-13, 50000}, {1540, 70, 50000},
|
||||
{2.04636e-12, 70, -28.2818}, {1470, -1.27898e-13, 0},
|
||||
{2.04636e-12, -1.27898e-13, 0}, {1540, 70, -28.2818}};
|
||||
secondMesh.triVerts = {{0, 1, 2}, {2, 3, 0}, {4, 5, 6}, {5, 4, 7},
|
||||
{6, 2, 1}, {6, 5, 2}, {5, 3, 2}, {5, 7, 3},
|
||||
{7, 0, 3}, {7, 4, 0}, {4, 1, 0}, {4, 6, 1}};
|
||||
|
||||
Manifold first(firstMesh);
|
||||
Manifold second(secondMesh);
|
||||
|
||||
first -= second;
|
||||
first.GetMesh();
|
||||
}
|
||||
|
||||
// FIXME: test is failing on Mac CI (passing on others)
|
||||
TEST(Boolean, Close) {
|
||||
PolygonParams().processOverlaps = true;
|
||||
|
||||
const float r = 10;
|
||||
Manifold a = Manifold::Sphere(r, 256);
|
||||
Manifold result = a;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// std::cout << i << std::endl;
|
||||
result ^= a.Translate({a.Precision() / 10 * i, 0.0, 0.0});
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
}
|
||||
auto prop = result.GetProperties();
|
||||
const float tol = 0.004;
|
||||
EXPECT_NEAR(prop.volume, (4.0f / 3.0f) * glm::pi<float>() * r * r * r,
|
||||
tol * r * r * r);
|
||||
EXPECT_NEAR(prop.surfaceArea, 4 * glm::pi<float>() * r * r, tol * r * r);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("close.glb", result.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
PolygonParams().processOverlaps = false;
|
||||
}
|
||||
|
||||
TEST(Boolean, UnionDifference) {
|
||||
Manifold block = Manifold::Cube({1, 1, 1}, true) - Manifold::Cylinder(1, 0.5);
|
||||
Manifold result = block + block.Translate({0, 0, 1});
|
||||
float resultsize = result.GetProperties().volume;
|
||||
float blocksize = block.GetProperties().volume;
|
||||
EXPECT_NEAR(resultsize, blocksize * 2, 0.0001);
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
// Copyright 2021 The Manifold Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "manifold.h"
|
||||
|
||||
#include "test.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
template <typename T>
|
||||
int NumUnique(const std::vector<T>& in) {
|
||||
std::set<int> unique;
|
||||
for (const T& v : in) {
|
||||
unique.emplace(v);
|
||||
}
|
||||
return unique.size();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* This tests that turning a mesh into a manifold and returning it to a mesh
|
||||
* produces a consistent result.
|
||||
*/
|
||||
TEST(Manifold, GetMesh) {
|
||||
Manifold manifold = Manifold::Sphere(1);
|
||||
Mesh mesh_out = manifold.GetMesh();
|
||||
Manifold manifold2(mesh_out);
|
||||
Mesh mesh_out2 = manifold2.GetMesh();
|
||||
Identical(mesh_out, mesh_out2);
|
||||
}
|
||||
|
||||
TEST(Manifold, GetMeshGL) {
|
||||
Manifold manifold = Manifold::Sphere(1);
|
||||
Mesh mesh_out = manifold.GetMesh();
|
||||
MeshGL meshGL_out = manifold.GetMeshGL();
|
||||
ASSERT_EQ(meshGL_out.NumVert(), mesh_out.vertPos.size());
|
||||
ASSERT_EQ(meshGL_out.NumTri(), mesh_out.triVerts.size());
|
||||
for (int i = 0; i < meshGL_out.NumVert(); ++i) {
|
||||
for (const int j : {0, 1, 2}) {
|
||||
ASSERT_EQ(meshGL_out.vertProperties[3 * i + j], mesh_out.vertPos[i][j]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < meshGL_out.NumTri(); ++i) {
|
||||
for (const int j : {0, 1, 2})
|
||||
ASSERT_EQ(meshGL_out.triVerts[3 * i + j], mesh_out.triVerts[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Manifold, Empty) {
|
||||
Mesh emptyMesh;
|
||||
Manifold empty(emptyMesh);
|
||||
|
||||
EXPECT_TRUE(empty.IsEmpty());
|
||||
EXPECT_EQ(empty.Status(), Manifold::Error::NoError);
|
||||
EXPECT_TRUE(empty.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, ValidInput) {
|
||||
std::vector<float> propTol = {0.1, 0.2};
|
||||
MeshGL tetGL = TetGL();
|
||||
Manifold tet(tetGL, propTol);
|
||||
EXPECT_FALSE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NoError);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput1) {
|
||||
Mesh in = Tet();
|
||||
in.vertPos[2][1] = NAN;
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NonFiniteVertex);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput2) {
|
||||
Mesh in = Tet();
|
||||
std::swap(in.triVerts[2][1], in.triVerts[2][2]);
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NotManifold);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput3) {
|
||||
Mesh in = Tet();
|
||||
for (glm::ivec3& tri : in.triVerts) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
if (tri[i] == 2) tri[i] = -2;
|
||||
}
|
||||
}
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput4) {
|
||||
Mesh in = Tet();
|
||||
for (glm::ivec3& tri : in.triVerts) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
if (tri[i] == 2) tri[i] = 4;
|
||||
}
|
||||
}
|
||||
Manifold tet(in);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput5) {
|
||||
MeshGL tetGL = TetGL();
|
||||
tetGL.mergeFromVert[tetGL.mergeFromVert.size() - 1] = 7;
|
||||
Manifold tet(tetGL);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::MergeIndexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput7) {
|
||||
MeshGL tetGL = TetGL();
|
||||
tetGL.triVerts[tetGL.triVerts.size() - 1] = 7;
|
||||
Manifold tet(tetGL);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VertexOutOfBounds);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
/**
|
||||
* ExpectMeshes performs a decomposition, so this test ensures that compose and
|
||||
* decompose are inverse operations.
|
||||
*/
|
||||
TEST(Manifold, Decompose) {
|
||||
std::vector<Manifold> manifoldList;
|
||||
manifoldList.emplace_back(Manifold::Tetrahedron());
|
||||
manifoldList.emplace_back(Manifold::Cube());
|
||||
manifoldList.emplace_back(Manifold::Sphere(1, 4));
|
||||
Manifold manifolds = Manifold::Compose(manifoldList);
|
||||
|
||||
ExpectMeshes(manifolds, {{8, 12}, {6, 8}, {4, 4}});
|
||||
|
||||
std::vector<MeshGL> input;
|
||||
|
||||
for (const Manifold& manifold : manifoldList) {
|
||||
EXPECT_GE(manifold.OriginalID(), 0);
|
||||
input.emplace_back(manifold.GetMeshGL());
|
||||
}
|
||||
|
||||
RelatedGL(manifolds, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests check the various manifold constructors.
|
||||
*/
|
||||
TEST(Manifold, Sphere) {
|
||||
int n = 25;
|
||||
Manifold sphere = Manifold::Sphere(1.0f, 4 * n);
|
||||
EXPECT_TRUE(sphere.IsManifold());
|
||||
EXPECT_EQ(sphere.NumTri(), n * n * 8);
|
||||
}
|
||||
|
||||
TEST(Manifold, Normals) {
|
||||
Mesh cube = Manifold::Cube(glm::vec3(1), true).GetMesh();
|
||||
const int nVert = cube.vertPos.size();
|
||||
for (int i = 0; i < nVert; ++i) {
|
||||
glm::vec3 v = glm::normalize(cube.vertPos[i]);
|
||||
glm::vec3& n = cube.vertNormal[i];
|
||||
EXPECT_FLOAT_EQ(v.x, n.x);
|
||||
EXPECT_FLOAT_EQ(v.y, n.y);
|
||||
EXPECT_FLOAT_EQ(v.z, n.z);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Manifold, Extrude) {
|
||||
Polygons polys = SquareHole();
|
||||
Manifold donut = Manifold::Extrude(polys, 1.0f, 3);
|
||||
EXPECT_TRUE(donut.IsManifold());
|
||||
EXPECT_EQ(donut.Genus(), 1);
|
||||
auto prop = donut.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 12.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 48.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, ExtrudeCone) {
|
||||
Polygons polys = SquareHole();
|
||||
Manifold donut = Manifold::Extrude(polys, 1.0f, 0, 0, glm::vec2(0.0f));
|
||||
EXPECT_TRUE(donut.IsManifold());
|
||||
EXPECT_EQ(donut.Genus(), 0);
|
||||
EXPECT_FLOAT_EQ(donut.GetProperties().volume, 4.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Revolve) {
|
||||
Polygons polys = SquareHole();
|
||||
Manifold vug = Manifold::Revolve(polys, 48);
|
||||
EXPECT_TRUE(vug.IsManifold());
|
||||
EXPECT_EQ(vug.Genus(), -1);
|
||||
auto prop = vug.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 14.0f * glm::pi<float>(), 0.2f);
|
||||
EXPECT_NEAR(prop.surfaceArea, 30.0f * glm::pi<float>(), 0.2f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Revolve2) {
|
||||
Polygons polys = SquareHole(2.0f);
|
||||
Manifold donutHole = Manifold::Revolve(polys, 48);
|
||||
EXPECT_TRUE(donutHole.IsManifold());
|
||||
EXPECT_EQ(donutHole.Genus(), 0);
|
||||
auto prop = donutHole.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 48.0f * glm::pi<float>(), 1.0f);
|
||||
EXPECT_NEAR(prop.surfaceArea, 96.0f * glm::pi<float>(), 1.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Smooth) {
|
||||
Manifold tet = Manifold::Tetrahedron();
|
||||
Manifold smooth = Manifold::Smooth(tet.GetMesh());
|
||||
smooth = smooth.Refine(100);
|
||||
ExpectMeshes(smooth, {{20002, 40000}});
|
||||
auto prop = smooth.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 17.38, 0.1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 33.38, 0.1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("smoothTet.glb", smooth.GetMesh(), {});
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Manifold, SmoothSphere) {
|
||||
int n[5] = {4, 8, 16, 32, 64};
|
||||
float precision[5] = {0.03, 0.003, 0.003, 0.0005, 0.00006};
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Manifold sphere = Manifold::Sphere(1, n[i]);
|
||||
// Refine(odd) puts a center point in the triangle, which is the worst case.
|
||||
Manifold smoothed = Manifold::Smooth(sphere.GetMesh()).Refine(7);
|
||||
Mesh out = smoothed.GetMesh();
|
||||
auto bounds =
|
||||
std::minmax_element(out.vertPos.begin(), out.vertPos.end(),
|
||||
[](const glm::vec3& a, const glm::vec3& b) {
|
||||
return glm::dot(a, a) < glm::dot(b, b);
|
||||
});
|
||||
float min = glm::length(*bounds.first);
|
||||
float max = glm::length(*bounds.second);
|
||||
EXPECT_NEAR(min, 1, precision[i]);
|
||||
EXPECT_NEAR(max, 1, precision[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Manifold, ManualSmooth) {
|
||||
// Unit Octahedron
|
||||
const Mesh oct = Manifold::Sphere(1, 4).GetMesh();
|
||||
Mesh smooth = Manifold::Smooth(oct).GetMesh();
|
||||
// Sharpen the edge from vert 4 to 5
|
||||
smooth.halfedgeTangent[6] = {0, 0, 0, 1};
|
||||
smooth.halfedgeTangent[22] = {0, 0, 0, 1};
|
||||
smooth.halfedgeTangent[16] = {0, 0, 0, 1};
|
||||
smooth.halfedgeTangent[18] = {0, 0, 0, 1};
|
||||
Manifold interp(smooth);
|
||||
interp = interp.Refine(100);
|
||||
|
||||
ExpectMeshes(interp, {{40002, 80000}});
|
||||
auto prop = interp.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 3.53, 0.01);
|
||||
EXPECT_NEAR(prop.surfaceArea, 11.39, 0.01);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) {
|
||||
const Mesh out = interp.GetMesh();
|
||||
ExportOptions options;
|
||||
options.faceted = false;
|
||||
options.mat.roughness = 0.1;
|
||||
|
||||
options.mat.vertColor.resize(interp.NumVert());
|
||||
const glm::vec4 red(1, 0, 0, 1);
|
||||
const glm::vec4 purple(1, 0, 1, 1);
|
||||
for (int tri = 0; tri < interp.NumTri(); ++tri) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
const glm::vec3& uvw = {0.5, 0.5, 0.0};
|
||||
const float alpha = glm::min(uvw[0], glm::min(uvw[1], uvw[2]));
|
||||
options.mat.vertColor[out.triVerts[tri][i]] =
|
||||
glm::mix(purple, red, glm::smoothstep(0.0f, 0.2f, alpha));
|
||||
}
|
||||
}
|
||||
ExportMesh("sharpenedSphere.glb", out, options);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(Manifold, SmoothMirrored) {
|
||||
const Mesh tet = Manifold::Tetrahedron().GetMesh();
|
||||
Manifold smooth = Manifold::Smooth(tet);
|
||||
Manifold mirror = smooth.Scale({-1, 1, 2}).Refine(10);
|
||||
smooth = smooth.Refine(10).Scale({1, 1, 2});
|
||||
|
||||
auto prop0 = smooth.GetProperties();
|
||||
auto prop1 = mirror.GetProperties();
|
||||
EXPECT_NEAR(prop0.volume, prop1.volume, 0.1);
|
||||
EXPECT_NEAR(prop0.surfaceArea, prop1.surfaceArea, 0.1);
|
||||
}
|
||||
|
||||
TEST(Manifold, Csaszar) {
|
||||
Manifold csaszar = Manifold::Smooth(Csaszar());
|
||||
csaszar = csaszar.Refine(100);
|
||||
ExpectMeshes(csaszar, {{70000, 140000}});
|
||||
auto prop = csaszar.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 84699, 10);
|
||||
EXPECT_NEAR(prop.surfaceArea, 14796, 10);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) {
|
||||
const Mesh out = csaszar.GetMesh();
|
||||
ExportOptions options;
|
||||
options.faceted = false;
|
||||
options.mat.roughness = 0.1;
|
||||
|
||||
options.mat.vertColor.resize(csaszar.NumVert());
|
||||
const glm::vec4 blue(0, 0, 1, 1);
|
||||
const glm::vec4 yellow(1, 1, 0, 1);
|
||||
for (int tri = 0; tri < csaszar.NumTri(); ++tri) {
|
||||
for (int i : {0, 1, 2}) {
|
||||
const glm::vec3& uvw = {0.5, 0.5, 0.0};
|
||||
const float alpha = glm::min(uvw[0], glm::min(uvw[1], uvw[2]));
|
||||
options.mat.vertColor[out.triVerts[tri][i]] =
|
||||
glm::mix(yellow, blue, glm::smoothstep(0.0f, 0.2f, alpha));
|
||||
}
|
||||
}
|
||||
ExportMesh("smoothCsaszar.glb", out, options);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* These tests verify the calculation of a manifold's geometric properties.
|
||||
*/
|
||||
TEST(Manifold, GetProperties) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
EXPECT_TRUE(cube.IsManifold());
|
||||
auto prop = cube.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 1.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f);
|
||||
|
||||
cube = cube.Scale(glm::vec3(-1.0f));
|
||||
prop = cube.GetProperties();
|
||||
EXPECT_FLOAT_EQ(prop.volume, 1.0f);
|
||||
EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f);
|
||||
}
|
||||
|
||||
TEST(Manifold, Precision) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), kTolerance);
|
||||
cube = cube.Scale({0.1, 1, 10});
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), 10 * kTolerance);
|
||||
cube = cube.Translate({-100, -10, -1});
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), 100 * kTolerance);
|
||||
}
|
||||
|
||||
TEST(Manifold, Precision2) {
|
||||
Manifold cube = Manifold::Cube();
|
||||
cube = cube.Translate({-0.5, 0, 0}).Scale({2, 1, 1});
|
||||
EXPECT_FLOAT_EQ(cube.Precision(), 2 * kTolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Curvature is the inverse of the radius of curvature, and signed such that
|
||||
* positive is convex and negative is concave. There are two orthogonal
|
||||
* principal curvatures at any point on a manifold, with one maximum and the
|
||||
* other minimum. Gaussian curvature is their product, while mean
|
||||
* curvature is their sum. Here we check our discrete approximations calculated
|
||||
* at each vertex against the constant expected values of spheres of different
|
||||
* radii and at different mesh resolutions.
|
||||
*/
|
||||
TEST(Manifold, GetCurvature) {
|
||||
const float precision = 0.015;
|
||||
for (int n = 4; n < 100; n *= 2) {
|
||||
Manifold sphere = Manifold::Sphere(1, 64);
|
||||
Curvature curvature = sphere.GetCurvature();
|
||||
EXPECT_NEAR(curvature.minMeanCurvature, 2, 2 * precision);
|
||||
EXPECT_NEAR(curvature.maxMeanCurvature, 2, 2 * precision);
|
||||
EXPECT_NEAR(curvature.minGaussianCurvature, 1, precision);
|
||||
EXPECT_NEAR(curvature.maxGaussianCurvature, 1, precision);
|
||||
|
||||
sphere = sphere.Scale(glm::vec3(2.0f));
|
||||
curvature = sphere.GetCurvature();
|
||||
EXPECT_NEAR(curvature.minMeanCurvature, 1, precision);
|
||||
EXPECT_NEAR(curvature.maxMeanCurvature, 1, precision);
|
||||
EXPECT_NEAR(curvature.minGaussianCurvature, 0.25, 0.25 * precision);
|
||||
EXPECT_NEAR(curvature.maxGaussianCurvature, 0.25, 0.25 * precision);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing more advanced Manifold operations.
|
||||
*/
|
||||
|
||||
TEST(Manifold, Transform) {
|
||||
Manifold cube = Manifold::Cube({1, 2, 3});
|
||||
Manifold cube2 = cube;
|
||||
cube = cube.Rotate(30, 40, 50).Scale({6, 5, 4}).Translate({1, 2, 3});
|
||||
|
||||
glm::mat3 rX(1.0f, 0.0f, 0.0f, //
|
||||
0.0f, cosd(30), sind(30), //
|
||||
0.0f, -sind(30), cosd(30));
|
||||
glm::mat3 rY(cosd(40), 0.0f, -sind(40), //
|
||||
0.0f, 1.0f, 0.0f, //
|
||||
sind(40), 0.0f, cosd(40));
|
||||
glm::mat3 rZ(cosd(50), sind(50), 0.0f, //
|
||||
-sind(50), cosd(50), 0.0f, //
|
||||
0.0f, 0.0f, 1.0f);
|
||||
glm::mat3 s = glm::mat3(1.0f);
|
||||
s[0][0] = 6;
|
||||
s[1][1] = 5;
|
||||
s[2][2] = 4;
|
||||
glm::mat4x3 transform = glm::mat4x3(s * rZ * rY * rX);
|
||||
transform[3] = glm::vec3(1, 2, 3);
|
||||
cube2 = cube2.Transform(transform);
|
||||
|
||||
Identical(cube.GetMesh(), cube2.GetMesh());
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelation) {
|
||||
Mesh gyroidMesh = Gyroid();
|
||||
MeshGL gyroidMeshGL = WithIndexColors(gyroidMesh);
|
||||
Manifold gyroid(gyroidMeshGL);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("gyroid.glb", gyroid.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(gyroid, {gyroidMeshGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelationTransform) {
|
||||
const Manifold cube = Manifold::Cube();
|
||||
const MeshGL cubeGL = cube.GetMeshGL();
|
||||
const Manifold turned = cube.Rotate(45, 90);
|
||||
|
||||
RelatedGL(turned, {cubeGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelationRefine) {
|
||||
const Mesh in = Csaszar();
|
||||
MeshGL inGL = WithIndexColors(in);
|
||||
Manifold csaszar(inGL);
|
||||
|
||||
RelatedGL(csaszar, {inGL});
|
||||
csaszar.Refine(4);
|
||||
RelatedGL(csaszar, {inGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshGLRoundTrip) {
|
||||
const Manifold cylinder = Manifold::Cylinder(2, 1);
|
||||
ASSERT_GE(cylinder.OriginalID(), 0);
|
||||
MeshGL inGL = cylinder.GetMeshGL();
|
||||
const Manifold cylinder2(inGL);
|
||||
const MeshGL outGL = cylinder2.GetMeshGL();
|
||||
|
||||
ASSERT_EQ(inGL.runOriginalID.size(), 1);
|
||||
ASSERT_EQ(outGL.runOriginalID.size(), 1);
|
||||
ASSERT_EQ(outGL.runOriginalID[0], inGL.runOriginalID[0]);
|
||||
|
||||
RelatedGL(cylinder2, {inGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, FaceIDRoundTrip) {
|
||||
const Manifold cube = Manifold::Cube();
|
||||
ASSERT_GE(cube.OriginalID(), 0);
|
||||
MeshGL inGL = cube.GetMeshGL();
|
||||
ASSERT_EQ(NumUnique(inGL.faceID), 6);
|
||||
inGL.faceID = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
|
||||
|
||||
const Manifold cube2(inGL);
|
||||
const MeshGL outGL = cube2.GetMeshGL();
|
||||
ASSERT_EQ(NumUnique(outGL.faceID), 12);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "manifoldc.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
float eps = 0.000001;
|
||||
|
||||
bool approx_float_array(float *a, float *b, size_t len) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (abs(a[i] - b[i]) > eps) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(CBIND, sphere) {
|
||||
int n = 25;
|
||||
size_t sz = manifold_manifold_size();
|
||||
ManifoldManifold *sphere = manifold_sphere(malloc(sz), 1.0f, 4 * n);
|
||||
|
||||
EXPECT_EQ(manifold_status(sphere), MANIFOLD_NO_ERROR);
|
||||
EXPECT_EQ(manifold_num_tri(sphere), n * n * 8);
|
||||
|
||||
manifold_delete_manifold(sphere);
|
||||
}
|
||||
|
||||
TEST(CBIND, warp_translation) {
|
||||
size_t sz = manifold_manifold_size();
|
||||
ManifoldVec3 (*warp)(float, float, float) = [](float x, float y, float z) {
|
||||
ManifoldVec3 v = {x + 15.0f, y, z};
|
||||
return v;
|
||||
};
|
||||
ManifoldManifold *sphere = manifold_sphere(malloc(sz), 1.0f, 100);
|
||||
ManifoldManifold *trans = manifold_translate(malloc(sz), sphere, 15., 0., 0.);
|
||||
ManifoldManifold *warped = manifold_warp(malloc(sz), sphere, warp);
|
||||
|
||||
ManifoldMeshGL *trans_mesh =
|
||||
manifold_get_meshgl(malloc(manifold_meshgl_size()), trans);
|
||||
ManifoldMeshGL *warped_mesh =
|
||||
manifold_get_meshgl(malloc(manifold_meshgl_size()), warped);
|
||||
|
||||
size_t len_props = manifold_meshgl_vert_properties_length(trans_mesh);
|
||||
float *trans_props = manifold_meshgl_vert_properties(
|
||||
malloc(sizeof(float) * len_props), trans_mesh);
|
||||
float *warped_props = manifold_meshgl_vert_properties(
|
||||
malloc(sizeof(float) * len_props), warped_mesh);
|
||||
|
||||
EXPECT_TRUE(approx_float_array(trans_props, warped_props, len_props));
|
||||
|
||||
manifold_delete_manifold(sphere);
|
||||
manifold_delete_manifold(trans);
|
||||
manifold_delete_manifold(warped);
|
||||
manifold_delete_meshgl(trans_mesh);
|
||||
manifold_delete_meshgl(warped_mesh);
|
||||
delete trans_props;
|
||||
delete warped_props;
|
||||
}
|
||||
|
||||
TEST(CBIND, level_set) {
|
||||
size_t sz = manifold_manifold_size();
|
||||
// can't convert lambda with captures to funptr
|
||||
float (*sdf)(float, float, float) = [](float x, float y, float z) {
|
||||
const float radius = 15;
|
||||
const float xscale = 3;
|
||||
const float yscale = 1;
|
||||
const float zscale = 1;
|
||||
float xs = x / xscale;
|
||||
float ys = y / yscale;
|
||||
float zs = z / zscale;
|
||||
return radius - sqrtf(xs * xs + ys * ys + zs * zs);
|
||||
};
|
||||
|
||||
const float bb = 30; // (radius * 2)
|
||||
// bounding box scaled according to factors used in *sdf
|
||||
ManifoldBox *bounds = manifold_box(malloc(manifold_box_size()), -bb * 3,
|
||||
-bb * 1, -bb * 1, bb * 3, bb * 1, bb * 1);
|
||||
ManifoldMeshGL *sdf_mesh =
|
||||
manifold_level_set(malloc(manifold_meshgl_size()), sdf, bounds, 0.5, 0);
|
||||
ManifoldManifold *sdf_man = manifold_of_meshgl(malloc(sz), sdf_mesh);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ManifoldExportOptions *options =
|
||||
manifold_export_options(malloc(manifold_export_options_size()));
|
||||
const char *name = "cbind_sdf_test.stl";
|
||||
manifold_export_meshgl(name, sdf_mesh, options);
|
||||
manifold_delete_export_options(options);
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(manifold_status(sdf_man), MANIFOLD_NO_ERROR);
|
||||
|
||||
manifold_delete_meshgl(sdf_mesh);
|
||||
manifold_delete_manifold(sdf_man);
|
||||
manifold_delete_box(bounds);
|
||||
}
|
||||
|
||||
TEST(CBIND, extrude) {
|
||||
size_t sz = manifold_manifold_size();
|
||||
|
||||
ManifoldVec2 pts[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
|
||||
ManifoldSimplePolygon *sq[] = {manifold_simple_polygon(
|
||||
malloc(manifold_simple_polygon_size()), &pts[0], 4)};
|
||||
ManifoldPolygons *polys =
|
||||
manifold_polygons(malloc(manifold_polygons_size()), sq, 1);
|
||||
|
||||
ManifoldManifold *cube = manifold_cube(malloc(sz), 1., 1., 1., 0);
|
||||
ManifoldManifold *extrusion =
|
||||
manifold_extrude(malloc(sz), polys, 1, 0, 0, 1, 1);
|
||||
|
||||
ManifoldManifold *diff = manifold_difference(malloc(sz), cube, extrusion);
|
||||
ManifoldProperties props = manifold_get_properties(diff);
|
||||
|
||||
EXPECT_TRUE(props.volume < eps);
|
||||
|
||||
manifold_delete_manifold(cube);
|
||||
manifold_delete_manifold(extrusion);
|
||||
manifold_delete_manifold(diff);
|
||||
manifold_delete_simple_polygon(sq[0]);
|
||||
manifold_delete_polygons(polys);
|
||||
}
|
||||
|
||||
TEST(CBIND, compose_decompose) {
|
||||
size_t sz = manifold_manifold_size();
|
||||
|
||||
ManifoldManifold *s1 = manifold_sphere(malloc(sz), 1.0f, 100);
|
||||
ManifoldManifold *s2 = manifold_translate(malloc(sz), s1, 2., 2., 2.);
|
||||
ManifoldManifold *ss[] = {s1, s2};
|
||||
ManifoldManifold *composed = manifold_compose(malloc(sz), ss, 2);
|
||||
|
||||
ManifoldComponents *cs =
|
||||
manifold_get_components(malloc(manifold_components_size()), composed);
|
||||
size_t len = manifold_components_length(cs);
|
||||
void *bufs[len];
|
||||
for (int i = 0; i < len; ++i) {
|
||||
bufs[i] = malloc(sz);
|
||||
}
|
||||
ManifoldManifold **decomposed = manifold_decompose(bufs, composed, cs);
|
||||
|
||||
EXPECT_EQ(len, 2);
|
||||
|
||||
manifold_delete_manifold(s1);
|
||||
manifold_delete_manifold(s2);
|
||||
manifold_delete_manifold(composed);
|
||||
manifold_delete_components(cs);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
manifold_delete_manifold(decomposed[i]);
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ target_link_libraries(${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
@@ -41,8 +41,14 @@ struct Material {
|
||||
glm::vec4 color = glm::vec4(1.0f);
|
||||
/// Optional: If non-empty, must match Mesh.vertPos. Provides an RGBA color
|
||||
/// for each vertex, linearly interpolated across triangles. Colors are
|
||||
/// linear, not sRGB.
|
||||
/// linear, not sRGB. Only used with Mesh export, not MeshGL.
|
||||
std::vector<glm::vec4> vertColor;
|
||||
/// For MeshGL export, gives the property indicies where the normal channels
|
||||
/// can be found. Must be >= 3, since the first three are position.
|
||||
glm::ivec3 normalChannels = glm::ivec3(-1);
|
||||
/// For MeshGL export, gives the property indicies where the color channels
|
||||
/// can be found. Any index < 0 will output all 1.0 for that channel.
|
||||
glm::ivec4 colorChannels = glm::ivec4(-1);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,6 +66,9 @@ Mesh ImportMesh(const std::string& filename, bool forceCleanup = false);
|
||||
|
||||
void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
const ExportOptions& options);
|
||||
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options);
|
||||
/** @} */
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
@@ -19,11 +19,75 @@
|
||||
|
||||
#include "assimp/Exporter.hpp"
|
||||
#include "assimp/Importer.hpp"
|
||||
#include "assimp/pbrmaterial.h"
|
||||
#include "assimp/material.h"
|
||||
#include "assimp/postprocess.h"
|
||||
#include "assimp/scene.h"
|
||||
#include "optional_assert.h"
|
||||
|
||||
#ifndef AI_MATKEY_ROUGHNESS_FACTOR
|
||||
#include "assimp/pbrmaterial.h"
|
||||
#define AI_MATKEY_METALLIC_FACTOR \
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
|
||||
#define AI_MATKEY_ROUGHNESS_FACTOR \
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
aiScene* CreateScene(const ExportOptions& options) {
|
||||
aiScene* scene = new aiScene();
|
||||
|
||||
scene->mNumMaterials = 1;
|
||||
scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
|
||||
scene->mMaterials[0] = new aiMaterial();
|
||||
|
||||
aiMaterial* material = scene->mMaterials[0];
|
||||
material->AddProperty(&options.mat.roughness, 1, AI_MATKEY_ROUGHNESS_FACTOR);
|
||||
material->AddProperty(&options.mat.metalness, 1, AI_MATKEY_METALLIC_FACTOR);
|
||||
const glm::vec4& color = options.mat.color;
|
||||
aiColor4D baseColor(color.r, color.g, color.b, color.a);
|
||||
material->AddProperty(&baseColor, 1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
scene->mNumMeshes = 1;
|
||||
scene->mMeshes = new aiMesh*[scene->mNumMeshes];
|
||||
scene->mMeshes[0] = new aiMesh();
|
||||
scene->mMeshes[0]->mMaterialIndex = 0;
|
||||
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->mNumMeshes = 1;
|
||||
scene->mRootNode->mMeshes = new glm::uint[scene->mRootNode->mNumMeshes];
|
||||
scene->mRootNode->mMeshes[0] = 0;
|
||||
|
||||
scene->mMeshes[0]->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
return scene;
|
||||
}
|
||||
|
||||
std::string GetType(const std::string& filename) {
|
||||
std::string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
if (ext == "glb") ext = "glb2";
|
||||
if (ext == "gltf") ext = "gltf2";
|
||||
return ext;
|
||||
}
|
||||
|
||||
void ExportScene(aiScene* scene, const std::string& filename) {
|
||||
Assimp::Exporter exporter;
|
||||
|
||||
// int n = exporter.GetExportFormatCount();
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// auto desc = exporter.GetExportFormatDescription(i);
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
auto result = exporter.Export(scene, GetType(filename), filename);
|
||||
|
||||
delete scene;
|
||||
|
||||
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
@@ -91,6 +155,95 @@ Mesh ImportMesh(const std::string& filename, bool forceCleanup) {
|
||||
return mesh_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Mesh to the desired file type, determined from the extension
|
||||
* specified. In the case of .glb/.gltf, this will save in version 2.0.
|
||||
*
|
||||
* This is a very simple export function and is intended primarily as a
|
||||
* demonstration. Generally users of this library will need to modify this to
|
||||
* write all the important properties for their application and read any custom
|
||||
* data structures.
|
||||
*
|
||||
* @param filename The file extension must be one that Assimp supports for
|
||||
* export. GLB & 3MF are recommended.
|
||||
* @param mesh The mesh to export, likely from Manifold.GetMeshGL().
|
||||
* @param options The options currently only affect an exported GLB's material.
|
||||
* Pass {} for defaults.
|
||||
*/
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options) {
|
||||
if (mesh.triVerts.size() == 0) {
|
||||
std::cout << filename << " was not saved because the input mesh was empty."
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string type = GetType(filename);
|
||||
const bool isYup = type == "glb2" || type == "gltf2";
|
||||
|
||||
aiScene* scene = CreateScene(options);
|
||||
aiMesh* mesh_out = scene->mMeshes[0];
|
||||
|
||||
mesh_out->mNumVertices = mesh.NumVert();
|
||||
mesh_out->mVertices = new aiVector3D[mesh_out->mNumVertices];
|
||||
if (!options.faceted) {
|
||||
bool validChannels = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
int c = options.mat.normalChannels[i];
|
||||
validChannels &= c >= 3 && c < (int)mesh.numProp;
|
||||
}
|
||||
ASSERT(validChannels, userErr,
|
||||
"When faceted is false, valid normalChannels must be supplied.");
|
||||
mesh_out->mNormals = new aiVector3D[mesh_out->mNumVertices];
|
||||
}
|
||||
|
||||
bool validChannels = true;
|
||||
bool hasColor = false;
|
||||
for (int i : {0, 1, 2, 3}) {
|
||||
int c = options.mat.colorChannels[i];
|
||||
validChannels &= c < (int)mesh.numProp;
|
||||
hasColor |= c >= 0;
|
||||
}
|
||||
ASSERT(validChannels, userErr, "Invalid colorChannels.");
|
||||
if (hasColor) mesh_out->mColors[0] = new aiColor4D[mesh_out->mNumVertices];
|
||||
|
||||
for (int i = 0; i < mesh_out->mNumVertices; ++i) {
|
||||
glm::vec3 v;
|
||||
for (int j : {0, 1, 2}) v[j] = mesh.vertProperties[i * mesh.numProp + j];
|
||||
mesh_out->mVertices[i] =
|
||||
isYup ? aiVector3D(v.y, v.z, v.x) : aiVector3D(v.x, v.y, v.z);
|
||||
if (!options.faceted) {
|
||||
glm::vec3 n;
|
||||
for (int j : {0, 1, 2})
|
||||
n[j] = mesh.vertProperties[i * mesh.numProp +
|
||||
options.mat.normalChannels[j]];
|
||||
mesh_out->mNormals[i] =
|
||||
isYup ? aiVector3D(n.y, n.z, n.x) : aiVector3D(n.x, n.y, n.z);
|
||||
}
|
||||
if (hasColor) {
|
||||
glm::vec4 c;
|
||||
for (int j : {0, 1, 2, 3})
|
||||
c[j] = options.mat.colorChannels[j] < 0
|
||||
? 1
|
||||
: mesh.vertProperties[i * mesh.numProp +
|
||||
options.mat.colorChannels[j]];
|
||||
mesh_out->mColors[0][i] = aiColor4D(c.r, c.g, c.b, c.a);
|
||||
}
|
||||
}
|
||||
|
||||
mesh_out->mNumFaces = mesh.NumTri();
|
||||
mesh_out->mFaces = new aiFace[mesh_out->mNumFaces];
|
||||
|
||||
for (int i = 0; i < mesh_out->mNumFaces; ++i) {
|
||||
aiFace& face = mesh_out->mFaces[i];
|
||||
face.mNumIndices = 3;
|
||||
face.mIndices = new glm::uint[face.mNumIndices];
|
||||
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[3 * i + j];
|
||||
}
|
||||
|
||||
ExportScene(scene, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Mesh to the desired file type, determined from the extension
|
||||
* specified. In the case of .glb/.gltf, this will save in version 2.0.
|
||||
@@ -114,38 +267,11 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
return;
|
||||
}
|
||||
|
||||
std::string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
const bool isYup = ext == "glb" || ext == "gltf";
|
||||
if (ext == "glb") ext = "glb2";
|
||||
if (ext == "gltf") ext = "gltf2";
|
||||
|
||||
aiScene* scene = new aiScene();
|
||||
|
||||
scene->mNumMaterials = 1;
|
||||
scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
|
||||
scene->mMaterials[0] = new aiMaterial();
|
||||
|
||||
aiMaterial* material = scene->mMaterials[0];
|
||||
material->AddProperty(&options.mat.roughness, 1,
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR);
|
||||
material->AddProperty(&options.mat.metalness, 1,
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR);
|
||||
const glm::vec4& color = options.mat.color;
|
||||
aiColor4D baseColor(color.r, color.g, color.b, color.a);
|
||||
material->AddProperty(&baseColor, 1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
scene->mNumMeshes = 1;
|
||||
scene->mMeshes = new aiMesh*[scene->mNumMeshes];
|
||||
scene->mMeshes[0] = new aiMesh();
|
||||
scene->mMeshes[0]->mMaterialIndex = 0;
|
||||
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->mNumMeshes = 1;
|
||||
scene->mRootNode->mMeshes = new glm::uint[scene->mRootNode->mNumMeshes];
|
||||
scene->mRootNode->mMeshes[0] = 0;
|
||||
std::string type = GetType(filename);
|
||||
const bool isYup = type == "glb2" || type == "gltf2";
|
||||
|
||||
aiScene* scene = CreateScene(options);
|
||||
aiMesh* mesh_out = scene->mMeshes[0];
|
||||
mesh_out->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
|
||||
mesh_out->mNumVertices = mesh.vertPos.size();
|
||||
mesh_out->mVertices = new aiVector3D[mesh_out->mNumVertices];
|
||||
@@ -186,20 +312,7 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[i][j];
|
||||
}
|
||||
|
||||
Assimp::Exporter exporter;
|
||||
|
||||
// int n = exporter.GetExportFormatCount();
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// auto desc = exporter.GetExportFormatDescription(i);
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
auto result = exporter.Export(scene, ext, filename);
|
||||
|
||||
delete scene;
|
||||
|
||||
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
|
||||
ExportScene(scene, filename);
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
@@ -24,22 +24,22 @@ namespace {
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
void StandardizePoly(SimplePolygon &p) {
|
||||
void StandardizePoly(SimplePolygonIdx &p) {
|
||||
auto start = std::min_element(
|
||||
p.begin(), p.end(),
|
||||
[](const PolyVert &v1, const PolyVert &v2) { return v1.idx < v2.idx; });
|
||||
std::rotate(p.begin(), start, p.end());
|
||||
}
|
||||
|
||||
void StandardizePolys(Polygons &polys) {
|
||||
void StandardizePolys(PolygonsIdx &polys) {
|
||||
for (auto &p : polys) StandardizePoly(p);
|
||||
std::sort(polys.begin(), polys.end(),
|
||||
[](SimplePolygon &p1, SimplePolygon &p2) {
|
||||
[](SimplePolygonIdx &p1, SimplePolygonIdx &p2) {
|
||||
return p1[0].idx < p2[0].idx;
|
||||
});
|
||||
}
|
||||
|
||||
void Identical(Polygons p1, Polygons p2) {
|
||||
void Identical(PolygonsIdx p1, PolygonsIdx p2) {
|
||||
ASSERT_EQ(p1.size(), p2.size());
|
||||
StandardizePolys(p1);
|
||||
StandardizePolys(p2);
|
||||
@@ -51,8 +51,8 @@ void Identical(Polygons p1, Polygons p2) {
|
||||
}
|
||||
}
|
||||
|
||||
Polygons Turn180(Polygons polys) {
|
||||
for (SimplePolygon &poly : polys) {
|
||||
PolygonsIdx Turn180(PolygonsIdx polys) {
|
||||
for (SimplePolygonIdx &poly : polys) {
|
||||
for (PolyVert &vert : poly) {
|
||||
vert.pos *= -1;
|
||||
}
|
||||
@@ -60,11 +60,11 @@ Polygons Turn180(Polygons polys) {
|
||||
return polys;
|
||||
}
|
||||
|
||||
Polygons Duplicate(Polygons polys) {
|
||||
PolygonsIdx Duplicate(PolygonsIdx polys) {
|
||||
float xMin = std::numeric_limits<float>::infinity();
|
||||
float xMax = -std::numeric_limits<float>::infinity();
|
||||
int indexMax = 0;
|
||||
for (SimplePolygon &poly : polys) {
|
||||
for (SimplePolygonIdx &poly : polys) {
|
||||
for (PolyVert &vert : poly) {
|
||||
xMin = std::min(xMin, vert.pos.x);
|
||||
xMax = std::max(xMax, vert.pos.x);
|
||||
@@ -76,7 +76,7 @@ Polygons Duplicate(Polygons polys) {
|
||||
|
||||
const int nPolys = polys.size();
|
||||
for (int i = 0; i < nPolys; ++i) {
|
||||
SimplePolygon poly = polys[i];
|
||||
SimplePolygonIdx poly = polys[i];
|
||||
for (PolyVert &vert : poly) {
|
||||
vert.pos.x += shift;
|
||||
vert.idx += indexMax;
|
||||
@@ -86,7 +86,7 @@ Polygons Duplicate(Polygons polys) {
|
||||
return polys;
|
||||
}
|
||||
|
||||
void TestPoly(const Polygons &polys, int expectedNumTri,
|
||||
void TestPoly(const PolygonsIdx &polys, int expectedNumTri,
|
||||
float precision = -1.0f) {
|
||||
PolygonParams().verbose = options.params.verbose;
|
||||
|
||||
@@ -94,11 +94,11 @@ void TestPoly(const Polygons &polys, int expectedNumTri,
|
||||
EXPECT_NO_THROW(triangles = Triangulate(polys, precision));
|
||||
EXPECT_EQ(triangles.size(), expectedNumTri) << "Basic";
|
||||
|
||||
// EXPECT_NO_THROW(triangles = Triangulate(Turn180(polys), precision));
|
||||
// EXPECT_EQ(triangles.size(), expectedNumTri) << "Turn 180";
|
||||
EXPECT_NO_THROW(triangles = Triangulate(Turn180(polys), precision));
|
||||
EXPECT_EQ(triangles.size(), expectedNumTri) << "Turn 180";
|
||||
|
||||
// EXPECT_NO_THROW(triangles = Triangulate(Duplicate(polys), precision));
|
||||
// EXPECT_EQ(triangles.size(), 2 * expectedNumTri) << "Duplicate";
|
||||
EXPECT_NO_THROW(triangles = Triangulate(Duplicate(polys), precision));
|
||||
EXPECT_EQ(triangles.size(), 2 * expectedNumTri) << "Duplicate";
|
||||
|
||||
PolygonParams().verbose = false;
|
||||
}
|
||||
@@ -110,7 +110,7 @@ void TestPoly(const Polygons &polys, int expectedNumTri,
|
||||
* tolerance.
|
||||
*/
|
||||
TEST(Polygon, SimpleHole) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, -2), 0}, //
|
||||
{glm::vec2(2, 2), 1}, //
|
||||
@@ -126,7 +126,7 @@ TEST(Polygon, SimpleHole) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SimpleHole2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, 1.63299), 0}, //
|
||||
{glm::vec2(-1.41421, -0.816496), 1}, //
|
||||
@@ -141,7 +141,7 @@ TEST(Polygon, SimpleHole2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, MultiMerge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-7, 0), 0}, //
|
||||
{glm::vec2(-6, 3), 1}, //
|
||||
@@ -163,7 +163,7 @@ TEST(Polygon, MultiMerge) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Colinear) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-5.48368, -3.73905), 0}, //
|
||||
{glm::vec2(-4.9881, -4.51552), 1}, //
|
||||
@@ -196,7 +196,7 @@ TEST(Polygon, Colinear) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Merges) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-3.22039, 10.2769), 0}, //
|
||||
{glm::vec2(-3.12437, 10.4147), 1}, //
|
||||
@@ -214,7 +214,7 @@ TEST(Polygon, Merges) {
|
||||
}
|
||||
|
||||
TEST(Polygon, ExtraTriangle) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1.23141634, -0.493547261), 0}, //
|
||||
{glm::vec2(1.23142254, -0.493540883), 1}, //
|
||||
@@ -253,7 +253,7 @@ TEST(Polygon, ExtraTriangle) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SpongeThin) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.5, -0.475308657), 11}, //
|
||||
{glm::vec2(-0.487654328, -0.475461066), 12}, //
|
||||
@@ -308,7 +308,7 @@ TEST(Polygon, SpongeThin) {
|
||||
}
|
||||
|
||||
TEST(Polygon, ColinearY) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, 0), 0}, //
|
||||
{glm::vec2(1, 1), 1}, //
|
||||
@@ -333,7 +333,7 @@ TEST(Polygon, ColinearY) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Concave) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.707107008, -0.707107008), 0}, //
|
||||
{glm::vec2(1, 0), 1}, //
|
||||
@@ -349,7 +349,7 @@ TEST(Polygon, Concave) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Concave2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4, 0), 1}, //
|
||||
{glm::vec2(3, 2), 3}, //
|
||||
@@ -368,7 +368,7 @@ TEST(Polygon, Concave2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(2.82002997, 0), 0}, //
|
||||
{glm::vec2(2.82002997, 0), 1}, //
|
||||
@@ -384,7 +384,7 @@ TEST(Polygon, Sliver) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Duplicate) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-32.0774002, -10.4309998), 0}, //
|
||||
{glm::vec2(-31.7346992, -6.10348988), 1}, //
|
||||
@@ -413,7 +413,7 @@ TEST(Polygon, Duplicate) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Folded) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(2.82002997, 0), 0}, //
|
||||
{glm::vec2(1.23706996, 4.20994997), 1}, //
|
||||
@@ -431,7 +431,7 @@ TEST(Polygon, Folded) {
|
||||
}
|
||||
|
||||
TEST(Polygon, NearlyLinear) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(2.82002997, -8.22814036e-05), 0}, //
|
||||
{glm::vec2(2.82002997, -8.22814036e-05), 1}, //
|
||||
@@ -445,7 +445,7 @@ TEST(Polygon, NearlyLinear) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(27.4996014, 8.6873703), 74}, //
|
||||
{glm::vec2(28.27701, 9.52887344), 76}, //
|
||||
@@ -458,7 +458,7 @@ TEST(Polygon, Sliver2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver3) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, -2.65168381), 369}, //
|
||||
{glm::vec2(0, -0.792692184), 1889}, //
|
||||
@@ -471,7 +471,7 @@ TEST(Polygon, Sliver3) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver4) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.375669807, 8.90489388), 7474}, //
|
||||
{glm::vec2(0, 8.39722729), 7459}, //
|
||||
@@ -486,7 +486,7 @@ TEST(Polygon, Sliver4) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver5) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-60, 0), 19}, //
|
||||
{glm::vec2(-50, 0), 21}, //
|
||||
@@ -504,7 +504,7 @@ TEST(Polygon, Sliver5) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver6) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(10, 0), 5}, //
|
||||
{glm::vec2(0, 10), 9}, //
|
||||
@@ -517,7 +517,7 @@ TEST(Polygon, Sliver6) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sliver7) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(50, -10), 0}, //
|
||||
{glm::vec2(60, 0), 25}, //
|
||||
@@ -531,8 +531,35 @@ TEST(Polygon, Sliver7) {
|
||||
TestPoly(polys, 6);
|
||||
}
|
||||
|
||||
TEST(Polygon, DISABLED_Sliver8) {
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(27.9279995, 4.9749999), 4}, //
|
||||
{glm::vec2(31.0610008, 2.32299995), 6}, //
|
||||
{glm::vec2(31.0610008, 2.32299995), 18}, //
|
||||
{glm::vec2(27.9279995, 4.9749999), 16}, //
|
||||
{glm::vec2(27.9279995, 4.9749999), 15}, //
|
||||
{glm::vec2(31.0610008, 2.32299995), 28}, //
|
||||
{glm::vec2(30.4400005, 5.34100008), 17}, //
|
||||
});
|
||||
TestPoly(polys, 5);
|
||||
}
|
||||
|
||||
TEST(Polygon, DISABLED_Sliver9) {
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1.49183154, -0.479596376), 16194}, //
|
||||
{glm::vec2(1.71669781, -0.246418983), 16195}, //
|
||||
{glm::vec2(1.70233643, -0.246410117), 16196}, //
|
||||
{glm::vec2(1.47253978, -0.246277586), 16197}, //
|
||||
{glm::vec2(1.47254002, -0.246287003), 17997}, //
|
||||
{glm::vec2(1.4869014, -0.246295869), 17996}, //
|
||||
});
|
||||
TestPoly(polys, 4);
|
||||
}
|
||||
|
||||
TEST(Polygon, Colinear2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(11.7864399, -7.4572401), 4176}, //
|
||||
{glm::vec2(11.6818037, -7.30982304), 24873}, //
|
||||
@@ -545,7 +572,7 @@ TEST(Polygon, Colinear2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Split) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.707106769, -0.707106769), 1}, //
|
||||
{glm::vec2(1, 0), 14}, //
|
||||
@@ -561,7 +588,7 @@ TEST(Polygon, Split) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Duplicates) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-15, -8.10255623), 1648}, //
|
||||
{glm::vec2(-15, -9.02439785), 1650}, //
|
||||
@@ -574,7 +601,7 @@ TEST(Polygon, Duplicates) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple1) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4.04059982, -4.01843977), 2872}, //
|
||||
{glm::vec2(3.95867562, -4.25263977), 24604}, //
|
||||
@@ -586,7 +613,7 @@ TEST(Polygon, Simple1) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-1, -1), 1}, //
|
||||
{glm::vec2(-0.5, -0.5), 9}, //
|
||||
@@ -600,7 +627,7 @@ TEST(Polygon, Simple2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple3) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(19.7193489, 6.15445995), 19798}, //
|
||||
{glm::vec2(20.2308197, 5.64299059), 31187}, //
|
||||
@@ -613,7 +640,7 @@ TEST(Polygon, Simple3) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple4) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(15, -12.7135563), 287}, //
|
||||
{glm::vec2(15, -10.6843739), 288}, //
|
||||
@@ -629,7 +656,7 @@ TEST(Polygon, Simple4) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple5) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-27.3845081, 0.375669748), 364}, //
|
||||
{glm::vec2(-27.6389656, 0), 365}, //
|
||||
@@ -644,7 +671,7 @@ TEST(Polygon, Simple5) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Simple6) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-7.99813318, 12.8888826), 25009}, //
|
||||
{glm::vec2(-7.85714436, 12.9125195), 25006}, //
|
||||
@@ -656,7 +683,7 @@ TEST(Polygon, Simple6) {
|
||||
}
|
||||
|
||||
TEST(Polygon, TouchingHole) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-2, -1), 0}, //
|
||||
{glm::vec2(2, -1), 1}, //
|
||||
@@ -673,7 +700,7 @@ TEST(Polygon, TouchingHole) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, -1), 0}, //
|
||||
{glm::vec2(1, 1), 1}, //
|
||||
@@ -687,7 +714,7 @@ TEST(Polygon, Degenerate) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.0740740597, -0.314814836), 4829}, //
|
||||
{glm::vec2(0.0925925896, -0.314814806), 4828}, //
|
||||
@@ -698,7 +725,7 @@ TEST(Polygon, Degenerate2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate3) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.413580239, -0.216049403), 49696}, //
|
||||
{glm::vec2(-0.42592591, -0.216049403), 49690}, //
|
||||
@@ -712,7 +739,7 @@ TEST(Polygon, Degenerate3) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate4) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, 10), 213}, //
|
||||
{glm::vec2(-0.0696326792, 9.99390793), 360}, //
|
||||
@@ -723,7 +750,7 @@ TEST(Polygon, Degenerate4) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate5) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, 0), 3}, //
|
||||
{glm::vec2(1, 1), 7}, //
|
||||
@@ -738,7 +765,7 @@ TEST(Polygon, Degenerate5) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Degenerate6) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4.37113897e-07, -4.37113897e-07), 0}, //
|
||||
{glm::vec2(4.37113897e-07, 0), 18}, //
|
||||
@@ -752,7 +779,7 @@ TEST(Polygon, Degenerate6) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Tricky) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, 0), 0}, //
|
||||
{glm::vec2(2, 1), 1}, //
|
||||
@@ -768,7 +795,7 @@ TEST(Polygon, Tricky) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Tricky2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(1, 0), 0}, //
|
||||
{glm::vec2(3, 1), 1}, //
|
||||
@@ -785,7 +812,7 @@ TEST(Polygon, Tricky2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Slit) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(27.7069321, 13.5144091), 286}, //
|
||||
{glm::vec2(28.664566, 10.8102894), 267}, //
|
||||
@@ -807,7 +834,7 @@ TEST(Polygon, Slit) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SharedEdge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.265432119, 0.0061728349), 61185}, //
|
||||
{glm::vec2(0.277777791, -3.7252903e-09), 61180}, //
|
||||
@@ -820,7 +847,7 @@ TEST(Polygon, SharedEdge) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Precision) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.98486793, -0.492948532), 0}, //
|
||||
{glm::vec2(-0.984859049, -0.492013603), 1}, //
|
||||
@@ -838,7 +865,7 @@ TEST(Polygon, Precision2) {
|
||||
const bool intermediateChecks = PolygonParams().intermediateChecks;
|
||||
PolygonParams().intermediateChecks = false;
|
||||
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(4.98093176, -0.247938812), 11113}, //
|
||||
{glm::vec2(4.94630527, -0.0826399028), 22736}, //
|
||||
@@ -866,7 +893,7 @@ TEST(Polygon, Precision2) {
|
||||
};
|
||||
|
||||
TEST(Polygon, Comb) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.462962955, -0.427297652), 18}, //
|
||||
{glm::vec2(0.5, -0.42592591), 17}, //
|
||||
@@ -912,7 +939,7 @@ TEST(Polygon, Comb) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Comb2) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.5, -0.462962955), 2}, //
|
||||
{glm::vec2(-0.462962955, -0.461591214), 4}, //
|
||||
@@ -958,7 +985,7 @@ TEST(Polygon, Comb2) {
|
||||
}
|
||||
|
||||
TEST(Polygon, PointPoly) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0, -15.9780979), 32}, //
|
||||
{glm::vec2(5.08144999, -14.2678728), 244}, //
|
||||
@@ -991,7 +1018,7 @@ TEST(Polygon, PointPoly) {
|
||||
};
|
||||
|
||||
TEST(Polygon, KissingZigzag) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(37.4667358, 0), 0}, //
|
||||
{glm::vec2(27.8904877, -3.04520559), 1}, //
|
||||
@@ -1106,7 +1133,7 @@ TEST(Polygon, KissingZigzag) {
|
||||
}
|
||||
|
||||
TEST(Polygon, Sponge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(-0.5, -0.5), 22}, //
|
||||
{glm::vec2(-0.388888896, -0.388888896), 23}, //
|
||||
@@ -1164,7 +1191,7 @@ TEST(Polygon, Sponge) {
|
||||
}
|
||||
|
||||
TEST(Polygon, SquareHoles) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.388888896, -0.277777791), 10}, //
|
||||
{glm::vec2(0.388888896, -0.388888896), 8}, //
|
||||
@@ -1229,7 +1256,7 @@ TEST(Polygon, SquareHoles) {
|
||||
}
|
||||
|
||||
TEST(Polygon, BigSponge) {
|
||||
Polygons polys;
|
||||
PolygonsIdx polys;
|
||||
polys.push_back({
|
||||
{glm::vec2(0.5, 0.5), 1}, //
|
||||
{glm::vec2(0.487654328, 0.487654328), 13834}, //
|
||||
|
||||
@@ -23,14 +23,6 @@
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
void CheckManifold(const Manifold& manifold) {
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
for (const glm::vec3& normal : manifold.GetMesh().vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> EdgePairs(const Mesh in) {
|
||||
const int numHalfedge = 3 * in.triVerts.size();
|
||||
std::vector<int> edgePair(numHalfedge);
|
||||
@@ -62,6 +54,7 @@ TEST(Samples, Knot13) {
|
||||
auto prop = knot13.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 20786, 1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 11177, 1);
|
||||
CheckGL(knot13);
|
||||
}
|
||||
|
||||
// This creates two interlinked knots.
|
||||
@@ -79,6 +72,7 @@ TEST(Samples, Knot42) {
|
||||
auto prop1 = knots[1].GetProperties();
|
||||
EXPECT_NEAR(prop0.volume, prop1.volume, 1);
|
||||
EXPECT_NEAR(prop0.surfaceArea, prop1.surfaceArea, 1);
|
||||
CheckGL(knot42);
|
||||
}
|
||||
|
||||
TEST(Samples, Scallop) {
|
||||
@@ -114,6 +108,7 @@ TEST(Samples, Scallop) {
|
||||
auto prop = scallop.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 41.3, 0.1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 78.1, 0.1);
|
||||
CheckGL(scallop);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) {
|
||||
@@ -137,6 +132,8 @@ TEST(Samples, TetPuzzle) {
|
||||
Manifold puzzle = TetPuzzle(50, 0.2, 50);
|
||||
CheckManifold(puzzle);
|
||||
EXPECT_LE(puzzle.NumDegenerateTris(), 2);
|
||||
CheckGL(puzzle);
|
||||
|
||||
Manifold puzzle2 = puzzle.Rotate(0, 0, 180);
|
||||
EXPECT_TRUE((puzzle ^ puzzle2).IsEmpty());
|
||||
puzzle = puzzle.Transform(RotateUp({1, -1, -1}));
|
||||
@@ -153,6 +150,7 @@ TEST(Samples, FrameReduced) {
|
||||
auto prop = frame.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 227333, 10);
|
||||
EXPECT_NEAR(prop.surfaceArea, 62635, 1);
|
||||
CheckGL(frame);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("roundedFrameReduced.glb", frame.GetMesh(), {});
|
||||
@@ -164,6 +162,7 @@ TEST(Samples, Frame) {
|
||||
CheckManifold(frame);
|
||||
EXPECT_EQ(frame.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(frame.Genus(), 5);
|
||||
CheckGL(frame);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("roundedFrame.glb", frame.GetMesh(), {});
|
||||
#endif
|
||||
@@ -176,6 +175,7 @@ TEST(Samples, Bracelet) {
|
||||
CheckManifold(bracelet);
|
||||
EXPECT_LE(bracelet.NumDegenerateTris(), 22);
|
||||
EXPECT_EQ(bracelet.Genus(), 1);
|
||||
CheckGL(bracelet);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("bracelet.glb", bracelet.GetMesh(), {});
|
||||
#endif
|
||||
@@ -187,6 +187,7 @@ TEST(Samples, GyroidModule) {
|
||||
CheckManifold(gyroid);
|
||||
EXPECT_LE(gyroid.NumDegenerateTris(), 2);
|
||||
EXPECT_EQ(gyroid.Genus(), 15);
|
||||
CheckGL(gyroid);
|
||||
|
||||
const Box bounds = gyroid.BoundingBox();
|
||||
const float precision = gyroid.Precision();
|
||||
@@ -204,6 +205,7 @@ TEST(Samples, Sponge1) {
|
||||
EXPECT_EQ(sponge.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(sponge.NumVert(), 40);
|
||||
EXPECT_EQ(sponge.Genus(), 5);
|
||||
CheckGL(sponge);
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels)
|
||||
ExportMesh("mengerSponge1.glb", sponge.GetMesh(), {});
|
||||
@@ -221,6 +223,7 @@ TEST(Samples, Sponge4) {
|
||||
// FIXME: limit NumDegenerateTris
|
||||
EXPECT_LE(sponge.NumDegenerateTris(), 40000);
|
||||
EXPECT_EQ(sponge.Genus(), 26433); // should be 1:5, 2:81, 3:1409, 4:26433
|
||||
CheckGL(sponge);
|
||||
|
||||
std::pair<Manifold, Manifold> cutSponge = sponge.SplitByPlane({1, 1, 1}, 0);
|
||||
EXPECT_TRUE(cutSponge.first.IsManifold());
|
||||
|
||||
@@ -66,7 +66,7 @@ TEST(SDF, Bounds) {
|
||||
if (options.exportModels) ExportMesh("cubeVoid.gltf", levelSet, {});
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NoError);
|
||||
EXPECT_EQ(cubeVoid.Genus(), -1);
|
||||
const float outerBound = size / 2 + edgeLength / 2;
|
||||
EXPECT_NEAR(bounds.min.x, -outerBound, precision);
|
||||
@@ -92,7 +92,7 @@ TEST(SDF, Surface) {
|
||||
if (options.exportModels) ExportMesh("cube.gltf", cube.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NoError);
|
||||
EXPECT_EQ(cube.Genus(), 0);
|
||||
auto prop = cube.GetProperties();
|
||||
EXPECT_NEAR(prop.volume, 8, 0.001);
|
||||
@@ -112,6 +112,6 @@ TEST(SDF, Resize) {
|
||||
if (options.exportModels) ExportMesh("layers.gltf", layers.GetMesh(), {});
|
||||
#endif
|
||||
|
||||
EXPECT_EQ(layers.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_EQ(layers.Status(), Manifold::Error::NoError);
|
||||
EXPECT_EQ(layers.Genus(), -8);
|
||||
}
|
||||
@@ -14,11 +14,37 @@
|
||||
|
||||
#pragma once
|
||||
#include "gtest/gtest.h"
|
||||
#include "manifold.h"
|
||||
#include "public.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
struct Options {
|
||||
bool exportModels = false;
|
||||
manifold::ExecutionParams params = {};
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
extern Options options;
|
||||
|
||||
struct MeshSize {
|
||||
int numVert, numTri;
|
||||
int numProp = 0;
|
||||
int numPropVert = numVert;
|
||||
};
|
||||
|
||||
Polygons SquareHole(float xOffset = 0.0);
|
||||
Mesh Csaszar();
|
||||
Mesh Gyroid();
|
||||
Mesh Tet();
|
||||
MeshGL TetGL();
|
||||
MeshGL WithIndexColors(const Mesh& in);
|
||||
MeshGL WithPositionColors(const Manifold& in);
|
||||
MeshGL WithNormals(const Manifold& in);
|
||||
void Identical(const Mesh& mesh1, const Mesh& mesh2);
|
||||
void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
|
||||
bool checkNormals = false);
|
||||
void ExpectMeshes(const Manifold& manifold,
|
||||
const std::vector<MeshSize>& meshSize);
|
||||
void CheckManifold(const Manifold& manifold);
|
||||
void CheckStrictly(const Manifold& manifold);
|
||||
void CheckGL(const Manifold& manifold);
|
||||
|
||||
@@ -14,8 +14,11 @@
|
||||
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
#include "test.h"
|
||||
|
||||
using namespace manifold;
|
||||
|
||||
Options options;
|
||||
|
||||
void print_usage() {
|
||||
@@ -28,7 +31,7 @@ void print_usage() {
|
||||
"flag)\n");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
@@ -62,6 +65,310 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
manifold::PolygonParams().intermediateChecks = true;
|
||||
manifold::PolygonParams().processOverlaps = false;
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
Polygons SquareHole(float xOffset) {
|
||||
Polygons polys;
|
||||
polys.push_back({
|
||||
{2 + xOffset, 2}, //
|
||||
{-2 + xOffset, 2}, //
|
||||
{-2 + xOffset, -2}, //
|
||||
{2 + xOffset, -2}, //
|
||||
});
|
||||
polys.push_back({
|
||||
{-1 + xOffset, 1}, //
|
||||
{1 + xOffset, 1}, //
|
||||
{1 + xOffset, -1}, //
|
||||
{-1 + xOffset, -1}, //
|
||||
});
|
||||
return polys;
|
||||
}
|
||||
|
||||
Mesh Csaszar() {
|
||||
Mesh csaszar;
|
||||
csaszar.vertPos = {{-20, -20, -10}, //
|
||||
{-20, 20, -15}, //
|
||||
{-5, -8, 8}, //
|
||||
{0, 0, 30}, //
|
||||
{5, 8, 8}, //
|
||||
{20, -20, -15}, //
|
||||
{20, 20, -10}};
|
||||
csaszar.triVerts = {{1, 3, 6}, //
|
||||
{1, 6, 5}, //
|
||||
{2, 5, 6}, //
|
||||
{0, 2, 6}, //
|
||||
{0, 6, 4}, //
|
||||
{3, 4, 6}, //
|
||||
{1, 2, 3}, //
|
||||
{1, 4, 2}, //
|
||||
{1, 0, 4}, //
|
||||
{1, 5, 0}, //
|
||||
{3, 5, 4}, //
|
||||
{0, 5, 3}, //
|
||||
{0, 3, 2}, //
|
||||
{2, 4, 5}};
|
||||
return csaszar;
|
||||
}
|
||||
|
||||
struct GyroidSDF {
|
||||
__host__ __device__ float operator()(glm::vec3 p) const {
|
||||
const glm::vec3 min = p;
|
||||
const glm::vec3 max = glm::vec3(glm::two_pi<float>()) - p;
|
||||
const float min3 = glm::min(min.x, glm::min(min.y, min.z));
|
||||
const float max3 = glm::min(max.x, glm::min(max.y, max.z));
|
||||
const float bound = glm::min(min3, max3);
|
||||
const float gyroid =
|
||||
cos(p.x) * sin(p.y) + cos(p.y) * sin(p.z) + cos(p.z) * sin(p.x);
|
||||
return glm::min(gyroid, bound);
|
||||
}
|
||||
};
|
||||
|
||||
Mesh Gyroid() {
|
||||
const float period = glm::two_pi<float>();
|
||||
return LevelSet(GyroidSDF(), {glm::vec3(0), glm::vec3(period)}, 0.5);
|
||||
}
|
||||
|
||||
Mesh Tet() {
|
||||
Mesh tet;
|
||||
tet.vertPos = {{-1.0f, -1.0f, 1.0f},
|
||||
{-1.0f, 1.0f, -1.0f},
|
||||
{1.0f, -1.0f, -1.0f},
|
||||
{1.0f, 1.0f, 1.0f}};
|
||||
tet.triVerts = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {3, 2, 1}};
|
||||
return tet;
|
||||
}
|
||||
|
||||
MeshGL TetGL() {
|
||||
MeshGL tet;
|
||||
tet.numProp = 5;
|
||||
tet.vertProperties = {-1, -1, 1, 0, 0, //
|
||||
-1, 1, -1, 1, -1, //
|
||||
1, -1, -1, 2, -2, //
|
||||
1, 1, 1, 3, -3, //
|
||||
-1, 1, -1, 4, -4, //
|
||||
1, -1, -1, 5, -5, //
|
||||
1, 1, 1, 6, -6};
|
||||
tet.triVerts = {2, 0, 1, 0, 3, 1, 2, 3, 0, 6, 5, 4};
|
||||
tet.mergeFromVert = {4, 5, 6};
|
||||
tet.mergeToVert = {1, 2, 3};
|
||||
return tet;
|
||||
}
|
||||
|
||||
MeshGL WithIndexColors(const Mesh& in) {
|
||||
MeshGL inGL(in);
|
||||
inGL.runOriginalID = {Manifold::ReserveIDs(1)};
|
||||
const int numVert = in.vertPos.size();
|
||||
inGL.numProp = 6;
|
||||
inGL.vertProperties.resize(6 * numVert);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
for (int j : {0, 1, 2}) inGL.vertProperties[6 * i + j] = in.vertPos[i][j];
|
||||
// vertex colors
|
||||
double a;
|
||||
inGL.vertProperties[6 * i + 3] = powf(modf(i * sqrt(2.0), &a), 2.2);
|
||||
inGL.vertProperties[6 * i + 4] = powf(modf(i * sqrt(3.0), &a), 2.2);
|
||||
inGL.vertProperties[6 * i + 5] = powf(modf(i * sqrt(5.0), &a), 2.2);
|
||||
}
|
||||
return inGL;
|
||||
}
|
||||
|
||||
MeshGL WithPositionColors(const Manifold& in) {
|
||||
MeshGL inGL = in.GetMeshGL();
|
||||
inGL.runIndex.clear();
|
||||
inGL.runOriginalID.clear();
|
||||
inGL.runTransform.clear();
|
||||
inGL.faceID.clear();
|
||||
inGL.runOriginalID = {Manifold::ReserveIDs(1)};
|
||||
const int numVert = in.NumVert();
|
||||
const Box bbox = in.BoundingBox();
|
||||
const glm::vec3 size = bbox.Size();
|
||||
const std::vector<float> oldProp = inGL.vertProperties;
|
||||
inGL.numProp = 6;
|
||||
inGL.vertProperties.resize(6 * numVert);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
for (int j : {0, 1, 2}) inGL.vertProperties[6 * i + j] = oldProp[3 * i + j];
|
||||
// vertex colors
|
||||
inGL.vertProperties[6 * i + 3] = (oldProp[3 * i] - bbox.min.x) / size.x;
|
||||
inGL.vertProperties[6 * i + 4] = (oldProp[3 * i + 1] - bbox.min.y) / size.y;
|
||||
inGL.vertProperties[6 * i + 5] = (oldProp[3 * i + 2] - bbox.min.z) / size.z;
|
||||
}
|
||||
return inGL;
|
||||
}
|
||||
|
||||
MeshGL WithNormals(const Manifold& in) {
|
||||
const Mesh mesh = in.GetMesh();
|
||||
MeshGL out;
|
||||
out.runOriginalID = {Manifold::ReserveIDs(1)};
|
||||
out.numProp = 6;
|
||||
out.vertProperties.resize(out.numProp * mesh.vertPos.size());
|
||||
for (int i = 0; i < mesh.vertPos.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) {
|
||||
out.vertProperties[6 * i + j] = mesh.vertPos[i][j];
|
||||
out.vertProperties[6 * i + 3 + j] = mesh.vertNormal[i][j];
|
||||
}
|
||||
}
|
||||
out.triVerts.resize(3 * mesh.triVerts.size());
|
||||
for (int i = 0; i < mesh.triVerts.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) out.triVerts[3 * i + j] = mesh.triVerts[i][j];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void Identical(const Mesh& mesh1, const Mesh& mesh2) {
|
||||
ASSERT_EQ(mesh1.vertPos.size(), mesh2.vertPos.size());
|
||||
for (int i = 0; i < mesh1.vertPos.size(); ++i)
|
||||
for (int j : {0, 1, 2})
|
||||
ASSERT_NEAR(mesh1.vertPos[i][j], mesh2.vertPos[i][j], 0.0001);
|
||||
|
||||
ASSERT_EQ(mesh1.triVerts.size(), mesh2.triVerts.size());
|
||||
for (int i = 0; i < mesh1.triVerts.size(); ++i)
|
||||
ASSERT_EQ(mesh1.triVerts[i], mesh2.triVerts[i]);
|
||||
}
|
||||
|
||||
void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
|
||||
bool checkNormals) {
|
||||
ASSERT_FALSE(out.IsEmpty());
|
||||
const glm::ivec3 normalIdx =
|
||||
checkNormals ? glm::ivec3(3, 4, 5) : glm::ivec3(0);
|
||||
MeshGL output = out.GetMeshGL(normalIdx);
|
||||
for (int run = 0; run < output.runOriginalID.size(); ++run) {
|
||||
const float* m = output.runTransform.data() + 12 * run;
|
||||
const glm::mat4x3 transform =
|
||||
output.runTransform.empty()
|
||||
? glm::mat4x3(1.0f)
|
||||
: glm::mat4x3(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8],
|
||||
m[9], m[10], m[11]);
|
||||
int i = 0;
|
||||
for (; i < originals.size(); ++i) {
|
||||
ASSERT_EQ(originals[i].runOriginalID.size(), 1);
|
||||
if (originals[i].runOriginalID[0] == output.runOriginalID[run]) break;
|
||||
}
|
||||
ASSERT_LT(i, originals.size());
|
||||
const MeshGL& inMesh = originals[i];
|
||||
for (int tri = output.runIndex[run] / 3; tri < output.runIndex[run + 1] / 3;
|
||||
++tri) {
|
||||
if (!output.faceID.empty()) {
|
||||
ASSERT_LT(tri, output.faceID.size());
|
||||
}
|
||||
const int inTri = output.faceID.empty() ? tri : output.faceID[tri];
|
||||
ASSERT_LT(inTri, inMesh.triVerts.size() / 3);
|
||||
glm::ivec3 inTriangle = {inMesh.triVerts[3 * inTri],
|
||||
inMesh.triVerts[3 * inTri + 1],
|
||||
inMesh.triVerts[3 * inTri + 2]};
|
||||
inTriangle *= inMesh.numProp;
|
||||
|
||||
glm::mat3 inTriPos;
|
||||
glm::mat3 outTriPos;
|
||||
for (int j : {0, 1, 2}) {
|
||||
const int vert = output.triVerts[3 * tri + j];
|
||||
glm::vec4 pos;
|
||||
for (int k : {0, 1, 2}) {
|
||||
pos[k] = inMesh.vertProperties[inTriangle[j] + k];
|
||||
outTriPos[j][k] = output.vertProperties[vert * output.numProp + k];
|
||||
}
|
||||
pos[3] = 1;
|
||||
inTriPos[j] = transform * pos;
|
||||
}
|
||||
glm::vec3 outNormal =
|
||||
glm::cross(outTriPos[1] - outTriPos[0], outTriPos[2] - outTriPos[0]);
|
||||
glm::vec3 inNormal =
|
||||
glm::cross(inTriPos[1] - inTriPos[0], inTriPos[2] - inTriPos[0]);
|
||||
const float area = glm::length(inNormal);
|
||||
if (area == 0) continue;
|
||||
inNormal /= area;
|
||||
|
||||
for (int j : {0, 1, 2}) {
|
||||
const int vert = output.triVerts[3 * tri + j];
|
||||
glm::vec3 edges[3];
|
||||
for (int k : {0, 1, 2}) edges[k] = inTriPos[k] - outTriPos[j];
|
||||
const float volume = glm::dot(edges[0], glm::cross(edges[1], edges[2]));
|
||||
ASSERT_LE(volume, area * 100 * out.Precision());
|
||||
|
||||
if (checkNormals) {
|
||||
glm::vec3 normal;
|
||||
for (int k : {0, 1, 2})
|
||||
normal[k] =
|
||||
output.vertProperties[vert * output.numProp + normalIdx[k]];
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
ASSERT_GT(glm::dot(normal, outNormal), 0);
|
||||
} else {
|
||||
for (int p = 3; p < output.numProp; ++p) {
|
||||
const float propOut =
|
||||
output.vertProperties[vert * output.numProp + p];
|
||||
|
||||
glm::vec3 inProp = {inMesh.vertProperties[inTriangle[0] + p],
|
||||
inMesh.vertProperties[inTriangle[1] + p],
|
||||
inMesh.vertProperties[inTriangle[2] + p]};
|
||||
glm::vec3 edgesP[3];
|
||||
for (int k : {0, 1, 2}) {
|
||||
edgesP[k] = edges[k] + inNormal * inProp[k] - inNormal * propOut;
|
||||
}
|
||||
const float volumeP =
|
||||
glm::dot(edgesP[0], glm::cross(edgesP[1], edgesP[2]));
|
||||
|
||||
ASSERT_LE(volumeP, area * 100 * out.Precision());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExpectMeshes(const Manifold& manifold,
|
||||
const std::vector<MeshSize>& meshSize) {
|
||||
EXPECT_FALSE(manifold.IsEmpty());
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
std::vector<Manifold> manifolds = manifold.Decompose();
|
||||
ASSERT_EQ(manifolds.size(), meshSize.size());
|
||||
std::sort(manifolds.begin(), manifolds.end(),
|
||||
[](const Manifold& a, const Manifold& b) {
|
||||
return a.NumVert() != b.NumVert() ? a.NumVert() > b.NumVert()
|
||||
: a.NumTri() > b.NumTri();
|
||||
});
|
||||
for (int i = 0; i < manifolds.size(); ++i) {
|
||||
EXPECT_TRUE(manifolds[i].IsManifold());
|
||||
EXPECT_EQ(manifolds[i].NumVert(), meshSize[i].numVert);
|
||||
EXPECT_EQ(manifolds[i].NumTri(), meshSize[i].numTri);
|
||||
EXPECT_EQ(manifolds[i].NumProp(), meshSize[i].numProp);
|
||||
EXPECT_EQ(manifolds[i].NumPropVert(), meshSize[i].numPropVert);
|
||||
const MeshGL meshGL = manifolds[i].GetMeshGL();
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.mergeToVert.size());
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(),
|
||||
meshGL.NumVert() - manifolds[i].NumVert());
|
||||
const Mesh mesh = manifolds[i].GetMesh();
|
||||
for (const glm::vec3& normal : mesh.vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckManifold(const Manifold& manifold) {
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
for (const glm::vec3& normal : manifold.GetMesh().vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckStrictly(const Manifold& manifold) {
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
EXPECT_EQ(manifold.NumDegenerateTris(), 0);
|
||||
}
|
||||
|
||||
void CheckGL(const Manifold& manifold) {
|
||||
ASSERT_FALSE(manifold.IsEmpty());
|
||||
const MeshGL meshGL = manifold.GetMeshGL();
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.mergeToVert.size());
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.NumVert() - manifold.NumVert());
|
||||
EXPECT_EQ(meshGL.runIndex.size(), meshGL.runOriginalID.size() + 1);
|
||||
EXPECT_EQ(meshGL.runIndex.front(), 0);
|
||||
EXPECT_EQ(meshGL.runIndex.back(), 3 * meshGL.NumTri());
|
||||
if (!meshGL.runTransform.empty()) {
|
||||
EXPECT_EQ(meshGL.runTransform.size(), 12 * meshGL.runOriginalID.size());
|
||||
}
|
||||
EXPECT_EQ(meshGL.faceID.size(), meshGL.NumTri());
|
||||
}
|
||||