Compare commits

..
Author SHA1 Message Date
Emmett Lalish 67a5b7140d basic example working 2022-11-16 00:01:08 +01:00
91 changed files with 3722 additions and 13797 deletions
-1
View File
@@ -26,7 +26,6 @@ jobs:
uses: dawidd6/action-download-artifact@v2
with:
workflow: manifold.yml
workflow_conclusion: completed
pr: ${{github.event.pull_request.number}}
name: wasm
path: ./public
+10 -46
View File
@@ -2,7 +2,7 @@
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
@@ -12,15 +12,14 @@ on:
jobs:
build:
timeout-minutes: 30
strategy:
matrix:
cuda_support: [ON, OFF]
parallel_backend: [NONE, OMP, TBB]
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
if: github.event.pull_request.draft == false
container:
image: docker://nvidia/cuda:12.0.1-devel-ubuntu22.04
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
steps:
- name: Install dependencies
run: |
@@ -36,7 +35,7 @@ jobs:
git apply thrust.diff
mkdir build
cd build
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
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
# note that the test for CUDA backend does not really test CUDA, as we
# don't have CUDA GPU on GitHub Action
@@ -53,7 +52,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_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
cd test
./manifold_test
@@ -68,37 +67,8 @@ 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:
timeout-minutes: 30
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
if: github.event.pull_request.draft == false
steps:
- name: Install dependencies
@@ -138,12 +108,9 @@ jobs:
build/bindings/wasm/manifold.js
build/bindings/wasm/manifold.wasm
build/bindings/wasm/manifold.d.ts
build/bindings/wasm/manifold-global-types.d.ts
build/bindings/wasm/manifold-encapsulated-types.d.ts
retention-days: 90
retention-days: 7
build_windows:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE]
@@ -177,7 +144,7 @@ jobs:
- name: Build ${{matrix.backend}}
shell: powershell
run: |
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
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
cd build
cmake --build . --target ALL_BUILD --config Release
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
@@ -187,15 +154,12 @@ 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
@@ -211,14 +175,13 @@ jobs:
git apply thrust.diff
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
- name: Test
run: |
cd build/test
./manifold_test
build_nix:
timeout-minutes: 30
strategy:
matrix:
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
@@ -228,3 +191,4 @@ jobs:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v15
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
-10
View File
@@ -118,14 +118,4 @@
},
"editor.tabSize": 2,
"editor.detectIndentation": false,
"cSpell.words": [
"Bary",
"csaszar",
"Gyroid",
"halfedge",
"halfedges",
"Tris",
"Verts"
],
"javascript.format.semicolons": "insert",
}
+7 -21
View File
@@ -18,13 +18,10 @@ 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_PYBIND ON)
option(MANIFOLD_CBIND OFF)
option(MANIFOLD_EXPORT off)
option(MANIFOLD_DEBUG off)
option(MANIFOLD_USE_CUDA off)
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
set(MANIFOLD_FLAGS -O3)
if(EMSCRIPTEN)
message("Building for Emscripten")
@@ -32,26 +29,15 @@ 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)
@@ -86,4 +72,4 @@ add_subdirectory(src)
add_subdirectory(samples)
add_subdirectory(test)
add_subdirectory(extras)
add_subdirectory(bindings)
add_subdirectory(bindings)
+1 -5
View File
@@ -12,11 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
add_subdirectory(c)
endif()
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
if(NOT EMSCRIPTEN)
add_subdirectory(python)
endif()
-18
View File
@@ -1,18 +0,0 @@
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)
-94
View File
@@ -1,94 +0,0 @@
#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(); }
-166
View File
@@ -1,166 +0,0 @@
#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;
}
-55
View File
@@ -1,55 +0,0 @@
#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;
}
-224
View File
@@ -1,224 +0,0 @@
#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
-68
View File
@@ -1,68 +0,0 @@
#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;
-462
View File
@@ -1,462 +0,0 @@
#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
-72
View File
@@ -1,72 +0,0 @@
#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
+2 -2
View File
@@ -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_17)
target_compile_features(pymanifold PUBLIC cxx_std_14)
target_include_directories(pymanifold
PRIVATE ${PYBIND11_DIR}/include
)
)
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
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
+53 -60
View File
@@ -40,16 +40,8 @@ PYBIND11_MODULE(pymanifold, m) {
.def(py::init<>())
.def(py::init([](std::vector<Manifold> &manifolds) {
Manifold result;
if (manifolds.size() >= 1) {
// for some reason using Manifold() as the initial object
// will cause failure for python specifically
// unable to reproduce with c++ directly
Manifold first = manifolds[0];
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
return first;
} else {
return Manifold();
}
for (Manifold &manifold : manifolds) result += manifold;
return result;
}),
"Construct manifold as the union of a set of manifolds.")
.def(py::self + py::self, "Boolean union.")
@@ -204,53 +196,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", [](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("smooth", Manifold::Smooth,
"Constructs a smooth version of the input mesh by creating "
"tangents; this\n"
"method will throw if you have supplied tangents with your "
"mesh already. The\n"
"actual triangle resolution is unchanged; use the Refine() "
"method to\n"
"interpolate to a higher-resolution curve.\n"
"\n"
"By default, every edge is calculated for maximum smoothness "
"(very much\n"
"approximately), attempting to minimize the maximum mean "
"Curvature magnitude.\n"
"No higher-order derivatives are considered, as the "
"interpolation is\n"
"independent per triangle, only sharing constraints on their "
"boundaries.\n"
"\n"
":param mesh: input Mesh.\n"
":param sharpenedEdges: If desired, you can supply a vector "
"of sharpened\n"
"halfedges, which should in general be a small subset of all "
"halfedges. Order\n"
"of entries doesn't matter, as each one specifies the "
"desired smoothness\n"
"(between zero and one, with one the default for all "
"unspecified halfedges)\n"
"and the halfedge index (3 * triangle index + [0,1,2] where "
"0 is the edge\n"
"between triVert 0 and 1, etc).\n"
"\n"
"At a smoothness value of zero, a sharp crease is made. The "
"smoothness is\n"
"interpolated along each edge, so the specified value should "
"be thought of as\n"
"an average. Where exactly two sharpened edges meet at a "
"vertex, their\n"
"tangents are rotated to be colinear so that the sharpened "
"edge can be\n"
"continuous. Vertices with only one sharpened edge are "
"completely smooth,\n"
"allowing sharpened edges to smoothly vanish at termination. "
"A single vertex\n"
"can be sharpened by sharping all edges that are incident on "
"it, allowing\n"
"cones to be formed.")
.def_static(
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
py::arg("mesh"))
@@ -351,10 +343,11 @@ 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<glm::vec2> vertices(polygons[i].size());
std::vector<PolyVert> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = {std::get<0>(polygons[i][j]),
std::get<1>(polygons[i][j])};
vertices[j] = {
{std::get<0>(polygons[i][j]), std::get<1>(polygons[i][j])},
j};
}
simplePolygons[i] = {vertices};
}
+3 -3
View File
@@ -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 -sEXPORT_ES6=1)
-sEXPORTED_RUNTIME_METHODS=addFunction,removeFunction -sMODULARIZE=1)
target_compile_features(manifoldjs PUBLIC cxx_std_17)
target_compile_features(manifoldjs PUBLIC cxx_std_14)
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
@@ -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/)
+46 -76
View File
@@ -15,8 +15,6 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <vector>
using namespace emscripten;
#include <manifold.h>
@@ -31,100 +29,65 @@ 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<glm::vec2> vertices(polygons[i].size());
std::vector<PolyVert> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = polygons[i][j];
vertices[j] = {polygons[i][j], j};
}
simplePolygons[i] = {vertices};
}
return simplePolygons;
}
val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
MeshGL mesh = manifold.GetMeshGL(normalIdx);
val GetMeshJS(const Manifold& manifold) {
MeshGL mesh = manifold.GetMeshGL();
val meshJS = val::object();
meshJS.set("numProp", mesh.numProp);
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertProperties",
val(typed_memory_view(mesh.vertProperties.size(),
mesh.vertProperties.data()))
.call<val>("slice"));
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
mesh.mergeFromVert.data()))
.call<val>("slice"));
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
mesh.mergeToVert.data()))
.call<val>("slice"));
meshJS.set("runIndex",
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
.call<val>("slice"));
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
mesh.runOriginalID.data()))
.call<val>("slice"));
meshJS.set("faceID",
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
meshJS.set("vertPos",
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
.call<val>("slice"));
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
mesh.vertNormal.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
mesh.runTransform.data()))
.call<val>("slice"));
return meshJS;
}
MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.numProp = mesh["numProp"].as<int>();
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertProperties =
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
if (mesh["mergeFromVert"] != val::undefined()) {
out.mergeFromVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
}
if (mesh["mergeToVert"] != val::undefined()) {
out.mergeToVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
}
if (mesh["runIndex"] != val::undefined()) {
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
}
if (mesh["runOriginalID"] != val::undefined()) {
out.runOriginalID =
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
}
if (mesh["faceID"] != val::undefined()) {
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
out.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
if (mesh["vertNormal"] != val::undefined()) {
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
if (mesh["runTransform"] != val::undefined()) {
out.runTransform =
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
}
return out;
}
@@ -146,11 +109,10 @@ Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
}
Manifold Transform(Manifold& manifold, const val& mat) {
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
Manifold Transform(Manifold& manifold, std::vector<float>& mat) {
glm::mat4x3 matrix;
for (const int col : {0, 1, 2, 3})
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 3; j++) matrix[i][j] = mat[i * 3 + j];
return manifold.Transform(matrix);
}
@@ -189,19 +151,17 @@ EMSCRIPTEN_BINDINGS(whatever) {
.field("w", &glm::vec4::w);
enum_<Manifold::Error>("status")
.value("NoError", Manifold::Error::NoError)
.value("NonFiniteVertex", Manifold::Error::NonFiniteVertex)
.value("NotManifold", Manifold::Error::NotManifold)
.value("VertexOutOfBounds", Manifold::Error::VertexOutOfBounds)
.value("PropertiesWrongLength", Manifold::Error::PropertiesWrongLength)
.value("MissingPositionProperties",
Manifold::Error::MissingPositionProperties)
.value("MergeVectorsDifferentLengths",
Manifold::Error::MergeVectorsDifferentLengths)
.value("MergeIndexOutOfBounds", Manifold::Error::MergeIndexOutOfBounds)
.value("TransformWrongLength", Manifold::Error::TransformWrongLength)
.value("RunIndexWrongLength", Manifold::Error::RunIndexWrongLength)
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength);
.value("NO_ERROR", Manifold::Error::NO_ERROR)
.value("NON_FINITE_VERTEX", Manifold::Error::NON_FINITE_VERTEX)
.value("NOT_MANIFOLD", Manifold::Error::NOT_MANIFOLD)
.value("VERTEX_INDEX_OUT_OF_BOUNDS",
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
.value("PROPERTIES_WRONG_LENGTH",
Manifold::Error::PROPERTIES_WRONG_LENGTH)
.value("TRI_PROPERTIES_WRONG_LENGTH",
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
@@ -213,6 +173,16 @@ 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)
@@ -228,6 +198,7 @@ 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")
@@ -242,8 +213,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
.function("_Translate", &Manifold::Translate)
.function("_Rotate", &Manifold::Rotate)
.function("_Scale", &Manifold::Scale)
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
&Manifold::Decompose))
.function("_Decompose", &Manifold::Decompose)
.function("isEmpty", &Manifold::IsEmpty)
.function("status", &Manifold::Status)
.function("numVert", &Manifold::NumVert)
@@ -255,7 +225,8 @@ EMSCRIPTEN_BINDINGS(whatever) {
.function("getProperties", &Manifold::GetProperties)
.function("_getCurvature", &Manifold::GetCurvature)
.function("originalID", &Manifold::OriginalID)
.function("asOriginal", &Manifold::AsOriginal);
.function("asOriginal", &Manifold::AsOriginal)
.function("_getMeshRelation", &Manifold::GetMeshRelation);
function("_Cube", &Manifold::Cube);
function("_Cylinder", &Manifold::Cylinder);
@@ -275,5 +246,4 @@ EMSCRIPTEN_BINDINGS(whatever) {
function("setMinCircularEdgeLength", &Manifold::SetMinCircularEdgeLength);
function("setCircularSegments", &Manifold::SetCircularSegments);
function("getCircularSegments", &Manifold::GetCircularSegments);
function("reserveIDs", &Manifold::ReserveIDs);
}
+54 -62
View File
@@ -75,6 +75,19 @@ 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));
};
@@ -110,27 +123,15 @@ Module.setup = function() {
class Mesh {
constructor({
numProp = 3,
triVerts = new Uint32Array(),
vertProperties = new Float32Array(),
mergeFromVert,
mergeToVert,
runIndex,
runOriginalID,
faceID,
halfedgeTangent,
runTransform
vertPos = new Float32Array(),
vertNormal,
halfedgeTangent
} = {}) {
this.numProp = numProp;
this.triVerts = triVerts;
this.vertProperties = vertProperties;
this.mergeFromVert = mergeFromVert;
this.mergeToVert = mergeToVert;
this.runIndex = runIndex;
this.runOriginalID = runOriginalID;
this.faceID = faceID;
this.vertPos = vertPos;
this.vertNormal = vertNormal;
this.halfedgeTangent = halfedgeTangent;
this.runTransform = runTransform;
}
get numTri() {
@@ -138,11 +139,7 @@ Module.setup = function() {
}
get numVert() {
return this.vertProperties.length / this.numProp;
}
get numRun() {
return this.runOriginalID.length;
return this.vertPos.length / 3;
}
verts(tri) {
@@ -150,36 +147,43 @@ Module.setup = function() {
}
position(vert) {
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
}
extras(vert) {
return this.vertProperties.subarray(
numProp * vert + 3, numProp * (vert + 1));
normal(vert) {
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
}
tangent(halfedge) {
return this.halfedgeTangent.subarray(4 * halfedge, 4 * (halfedge + 1));
}
transform(run) {
const mat4 = new Array(16);
for (const col of [0, 1, 2, 3]) {
for (const row of [0, 1, 2]) {
mat4[4 * col + row] = this.runTransform[12 * run + 3 * col + row];
}
}
mat4[15] = 1;
return mat4;
}
}
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function(normalIdx = [0, 0, 0]) {
if (normalIdx instanceof Array)
normalIdx = {0: normalIdx[0], 1: normalIdx[1], 2: normalIdx[2]};
return new Mesh(this._GetMeshJS(normalIdx));
Module.Manifold.prototype.getMesh = function() {
return new Mesh(this._GetMeshJS());
};
Module.Manifold.prototype.getMeshRelation = function() {
const result = this._getMeshRelation();
const oldBarycentric = result.barycentric;
const oldTriBary = result.triBary;
const conversion1 = v => [v.x, v.y, v.z];
const conversion2 = v => [v[0], v[1], v[2]];
const conversion3 = v => {
return {
meshID: v.meshID,
originalID: v.originalID,
tri: v.tri,
vertBary: conversion2(v.vertBary)
};
};
result.barycentric = fromVec(oldBarycentric, conversion1);
result.triBary = fromVec(oldTriBary, conversion3);
oldBarycentric.delete();
oldTriBary.delete();
return result;
};
Module.Manifold.prototype.boundingBox = function() {
@@ -193,35 +197,23 @@ Module.setup = function() {
Module.ManifoldError = function ManifoldError(code, ...args) {
let message = 'Unknown error';
switch (code) {
case Module.status.NonFiniteVertex.value:
case Module.status.NON_FINITE_VERTEX.value:
message = 'Non-finite vertex';
break;
case Module.status.NotManifold.value:
case Module.status.NOT_MANIFOLD.value:
message = 'Not manifold';
break;
case Module.status.VertexOutOfBounds.value:
case Module.status.VERTEX_INDEX_OUT_OF_BOUNDS.value:
message = 'Vertex index out of bounds';
break;
case Module.status.PropertiesWrongLength.value:
case Module.status.PROPERTIES_WRONG_LENGTH.value:
message = 'Properties have wrong length';
break;
case Module.status.MissingPositionProperties.value:
message = 'Less than three properties';
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
message = 'Tri properties have wrong length';
break;
case Module.status.MergeVectorsDifferentLengths.value:
message = 'Merge vectors have different lengths';
break;
case Module.status.MergeIndexOutOfBounds.value:
message = 'Merge index out of bounds';
break;
case Module.status.TransformWrongLength.value:
message = 'Transform vector has wrong length';
break;
case Module.status.RunIndexWrongLength.value:
message = 'Run index vector has wrong length';
break;
case Module.status.FaceIDWrongLength.value:
message = 'Face ID vector has wrong length';
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
message = 'Tri properties out of bounds';
}
const base = Error.apply(this, [message, ...args]);
@@ -332,7 +324,7 @@ Module.setup = function() {
const result = Module['_' + name + 'N'](v);
v.delete();
return result;
};
}
}
Module.union = batchbool('union');
-1
View File
@@ -1 +0,0 @@
manifold*
+1 -11
View File
@@ -128,7 +128,7 @@ model-viewer {
transform: translate3d(-50%, -50%, 0);
}
.header .margin {
.margin {
font-size: 16px;
margin: 1em;
}
@@ -186,16 +186,6 @@ 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);
}
-3220
View File
File diff suppressed because it is too large Load Diff
+8 -49
View File
@@ -12,13 +12,8 @@
// 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 ------------------------------------------------------------
@@ -120,7 +115,8 @@ function uniqueName(name) {
function addEdit(button) {
const label = button.firstChild;
const edit = addIcon(button);
edit.classList.add('edit');
edit.style.backgroundImage = 'url(icons/pencil.png)';
edit.style.right = '30px';
edit.onclick = function(event) {
event.stopPropagation();
@@ -160,7 +156,8 @@ function addEdit(button) {
};
const trash = addIcon(button);
trash.classList.add('trash');
trash.style.backgroundImage = 'url(icons/trash.png)';
trash.style.right = '0px';
let lastClick = 0;
trash.onclick = function(event) {
@@ -213,52 +210,14 @@ 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() {
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');
const content =
await fetch('manifold.d.ts').then(response => response.text());
monaco.languages.typescript.typescriptDefaults.addExtraLib(content);
editor = monaco.editor.create(
document.getElementById('editor'),
{language: 'typescript', automaticLayout: true});
@@ -350,7 +309,7 @@ let objectURL = null;
let manifoldWorker = null;
function createWorker() {
manifoldWorker = new Worker('worker.js', {type: 'module'});
manifoldWorker = new Worker('worker.js');
manifoldWorker.onmessage = function(e) {
if (e.data == null) {
if (tsWorker != null && !manifoldInitialized) {
+411 -405
View File
@@ -12,423 +12,429 @@
// See the License for the specific language governing permissions and
// limitations under the License.
export const examples = {
functions: {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the
// API docs. Type e.g. "box." to see the Manifold API. Type "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.
(function(exports) {
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
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.
// 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.
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
// All changes are automatically saved and restored between sessions.
// This 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.
// All changes are automatically saved and restored between sessions.
// This app is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// To share your code with another browser/device, simply copy the text to a
// file.
// See the script drop-down above ("Intro") for usage examples. 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
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions = 50; // Divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
const tet = 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);
const edge1 = union(corner, edge).rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
const edge2 = union(
union(edge1, edge1.rotate([0, 0, 180])),
edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
const edge4 = union(edge2, edge2.rotate([0, 0, 90])).translate([
0, 0, -edgeLength / 2
]);
return union(edge4, edge4.rotate([180, 0, 0]));
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
return result;
},
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function.
// This example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
const func = (v) => {
const x2 = v[0] * v[0];
const y2 = v[1] * v[1];
const z = v[2];
const z2 = z * z;
const a = x2 + 9 / 4 * y2 + z2;
const b = z * z2 * (x2 + 9 / 80 * y2);
const a2 = a * a;
const a3 = a * a2;
const step = (r) => {
const r2 = r * r;
const r4 = r2 * r2;
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df =
6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
let r = 1.5;
let dr = 1;
while (Math.abs(dr) > 0.0001) {
dr = step(r);
r -= dr;
}
// Update radius
v[0] *= r;
v[1] *= r;
v[2] *= r;
};
const ball = sphere(1, 200);
const heart = ball.warp(func);
const box = heart.boundingBox();
const result = heart.scale(100 / (box.max[0] - box.min[0]));
return result;
},
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with
// smooth() and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
const radius = 30;
const offset = 20;
const wiggles = 12;
const sharpness = 0.8;
const n = 50;
const positions = [];
const triangles = [];
positions.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
for (let i = 0; i < 2 * wiggles; ++i) {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
positions.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triangles.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triangles.push(0, 2 + i, 2 + j);
halfedge = triangles.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triangles.push(1, 2 + j, 2 + i);
}
const triVerts = Uint32Array.from(triangles);
const vertProperties = Float32Array.from(positions);
const scallop = new Mesh({numProp: 3, triVerts, vertProperties});
const 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) {
const {vec3} = glMatrix;
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
const kLoops = gcd(p, q);
p /= kLoops;
q /= kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * q * majorRadius / threadRadius;
const 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 func = (v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
vec3.set(
v, threadRadius * Math.cos(phi), 0, threadRadius * Math.sin(phi));
const center = vec3.fromValues(0, 0, 0);
const r = majorRadius + minorRadius * Math.cos(theta);
vec3.rotateX(v, v, center, -Math.atan2(p * minorRadius, q * r));
v[0] += minorRadius;
vec3.rotateY(v, v, center, theta);
v[0] += majorRadius;
vec3.rotateZ(v, v, center, psi);
};
let knot = 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;
}
// See the script drop-down above ("Intro") for usage examples.
return result;
},
functionBodies: new Map()
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as a
// puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions =
50; // Number of divisions (both ways) in the screw surface.
const scale = edgeLength / (2 * Math.sqrt(2));
const tet = tetrahedron().scale(scale);
const box = [];
box.push([2, -2], [2, 2]);
for (let i = 0; i <= nDivisions; ++i) {
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
}
const screw = extrude(box, 2, nDivisions, 270)
.rotate([0, 0, -45])
.translate([0, 0, -1])
.scale(scale);
const result = tet.intersect(screw);
return result;
},
RoundedFrame: function() {
// Demonstrates how at 90-degree intersections, the sphere and cylinder
// facets match up perfectly, for any choice of global resolution
// parameters.
function roundedFrame(edgeLength, radius, circularSegments = 0) {
const edge = cylinder(edgeLength, radius, -1, circularSegments);
const corner = sphere(radius, circularSegments);
let edge1 = union(corner, edge);
edge1 = edge1.rotate([-90, 0, 0]).translate([
-edgeLength / 2, -edgeLength / 2, 0
]);
let edge2 = edge1.rotate([0, 0, 180]);
edge2 = union(edge2, edge1);
edge2 =
union(edge2, edge.translate([-edgeLength / 2, -edgeLength / 2, 0]));
let edge4 = edge2.rotate([0, 0, 90]);
edge4 = union(edge4, edge2);
let frame = edge4.translate([0, 0, -edgeLength / 2]);
frame = union(frame, frame.rotate([180, 0, 0]));
return frame;
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
return result;
},
Heart: function() {
// Smooth, complex manifolds can be created using the warp() function. This
// example recreates the Exploitable Heart by Emmett Lalish:
// https://www.thingiverse.com/thing:6190
const func = (v) => {
const x2 = v[0] * v[0];
const y2 = v[1] * v[1];
const z = v[2];
const z2 = z * z;
const a = x2 + 9 / 4 * y2 + z2;
const b = z * z2 * (x2 + 9 / 80 * y2);
const a2 = a * a;
const a3 = a * a2;
const step = (r) => {
const r2 = r * r;
const r4 = r2 * r2;
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
// Derivative
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
return f / df;
};
// Newton's method for root finding
let r = 1.5;
let dr = 1;
while (Math.abs(dr) > 0.0001) {
dr = step(r);
r -= dr;
}
// Update radius
v[0] *= r;
v[1] *= r;
v[2] *= r;
};
const ball = sphere(1, 200);
const heart = ball.warp(func);
const box = heart.boundingBox();
const result = heart.scale(100 / (box.max[0] - box.min[0]));
return result;
},
Scallop: function() {
// A smoothed manifold demonstrating selective edge sharpening with smooth()
// and refine(), see more details at:
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
const height = 10;
const radius = 30;
const offset = 20;
const wiggles = 12;
const sharpness = 0.8;
const n = 50;
const vertPos = [];
const triVerts = [];
vertPos.push(-offset, 0, height, -offset, 0, -height);
const sharpenedEdges = [];
const delta = 3.14159 / wiggles;
for (let i = 0; i < 2 * wiggles; ++i) {
const theta = (i - wiggles) * delta;
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
vertPos.push(
radius * Math.cos(theta), radius * Math.sin(theta),
amp * (i % 2 == 0 ? 1 : -1));
let j = i + 1;
if (j == 2 * wiggles) j = 0;
const smoothness = 1 - sharpness * Math.cos((theta + delta / 2) / 2);
let halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(0, 2 + i, 2 + j);
halfedge = triVerts.length + 1;
sharpenedEdges.push({halfedge, smoothness});
triVerts.push(1, 2 + j, 2 + i);
}
const scallop = new Mesh();
scallop.triVerts = Uint32Array.from(triVerts);
scallop.vertPos = Float32Array.from(vertPos);
const result = smooth(scallop, sharpenedEdges).refine(n);
return result;
},
TorusKnot: function() {
// Creates a classic torus knot, defined as a string wrapping periodically
// around the surface of an imaginary donut. If p and q have a common factor
// then you will get multiple separate, interwoven knots. This is an example
// of using the warp() method, thus avoiding any direct handling of
// triangles.
// @param p The number of times the thread passes through the donut hole.
// @param q The number of times the thread circles the donut.
// @param majorRadius Radius of the interior of the imaginary donut.
// @param minorRadius Radius of the small cross-section of the imaginary
// donut.
// @param threadRadius Radius of the small cross-section of the actual
// object.
// @param circularSegments Number of linear segments making up the
// threadRadius circle. Default is getCircularSegments(threadRadius).
// @param linearSegments Number of segments along the length of the knot.
// Default makes roughly square facets.
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
const kLoops = gcd(p, q);
p /= kLoops;
q /= kLoops;
const n = circularSegments > 2 ? circularSegments :
getCircularSegments(threadRadius);
const m = linearSegments > 2 ? linearSegments :
n * q * majorRadius / threadRadius;
const circle = [];
const dPhi = 2 * 3.14159 / n;
const offset = 2;
for (let i = 0; i < n; ++i) {
circle.push([Math.cos(dPhi * i) + offset, Math.sin(dPhi * i)]);
}
const point = new THREE.Vector3();
const axis = new THREE.Vector3();
const func =
(v) => {
const psi = q * Math.atan2(v[0], v[1]);
const theta = psi * p / q;
const x1 = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
const phi = Math.atan2(x1 - offset, v[2]);
point.set(Math.cos(phi), 0, Math.sin(phi))
.multiplyScalar(threadRadius);
const r = majorRadius + minorRadius * Math.cos(theta);
point.applyAxisAngle(
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
point.x += minorRadius;
point.applyAxisAngle(axis.set(0, 1, 0), theta);
point.x += majorRadius;
point.applyAxisAngle(axis.set(0, 0, 1), psi);
v[0] = point.x;
v[1] = point.y;
v[2] = point.z;
}
let knot = revolve(circle, m).warp(func);
if (kLoops > 1) {
const knots = [];
for (let k = 0; k < kLoops; ++k) {
knots.push(knot.rotate([0, 0, 360 * (k / kLoops) * (q / p)]));
}
knot = compose(knots);
}
return knot;
}
// This recreates Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
const result = torusKnot(1, 3, 25, 10, 3.75);
return result;
},
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
function vec2add(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
if (depth == maxDepth) return;
const offsets = [
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
[0.0, -w]
];
for (let offset of offsets)
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
}
function mengerSponge(n) {
let result = cube([1, 1, 1], true);
const holes = [];
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
const hole = compose(holes);
result = difference(result, hole);
result = difference(result, hole.rotate([90, 0, 0]));
result = difference(result, hole.rotate([0, 90, 0]));
return result;
}
const result = mengerSponge(3).scale(100);
return result;
},
StretchyBracelet: function() {
// Recreates Stretchy Bracelet by Emmett Lalish:
// https://www.thingiverse.com/thing:13505
function base(
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
outerRadius, cut, nCut, nDivision) {
function rotate(v, theta) {
return [
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
];
}
let b = cylinder(width, radius + twistRadius / 2);
const circle = [];
const dPhiDeg = 180 / nDivision;
for (let i = 0; i < 2 * nDivision; ++i) {
circle.push([
decorRadius * Math.cos(dPhiDeg * i * Math.PI / 180) + twistRadius,
decorRadius * Math.sin(dPhiDeg * i * Math.PI / 180)
]);
}
let decor =
extrude(circle, width, nDivision, 180).scale([1, 0.5, 1]).translate([
0, radius, 0
]);
for (let i = 0; i < nDecor; i++)
b = b.add(decor.rotate([0, 0, (360.0 / nDecor) * i]));
const stretch = [];
const dPhiRad = 2 * Math.PI / nCut;
const p0 = [outerRadius, 0];
const p1 = [innerRadius, -cut];
const p2 = [innerRadius, cut];
for (let i = 0; i < nCut; ++i) {
stretch.push(rotate(p0, dPhiRad * i));
stretch.push(rotate(p1, dPhiRad * i));
stretch.push(rotate(p2, dPhiRad * i));
stretch.push(rotate(p0, dPhiRad * i));
}
b = intersection(extrude(stretch, width), b);
return b;
}
function stretchyBracelet(
radius = 30, height = 8, width = 15, thickness = 0.4, nDecor = 20,
nCut = 27, nDivision = 30) {
const twistRadius = Math.PI * radius / nDecor;
const decorRadius = twistRadius * 1.5;
const outerRadius = radius + (decorRadius + twistRadius) * 0.5;
const innerRadius = outerRadius - height;
const cut = 0.5 * (Math.PI * 2 * innerRadius / nCut - thickness);
const adjThickness = 0.5 * thickness * height / cut;
return difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
base(
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
const result = stretchyBracelet();
return result;
},
GyroidModule: function() {
// Recreates Modular Gyroid Puzzle by Emmett Lalish:
// https://www.thingiverse.com/thing:25477. This sample demonstrates the use
// of a Signed Distance Function (SDF) to create smooth, complex manifolds.
const size = 20;
const n = 20;
const pi = 3.14159;
function gyroid(p) {
const x = p[0] - pi / 4;
const y = p[1] - pi / 4;
const z = p[2] - pi / 4;
return Math.cos(x) * Math.sin(y) + Math.cos(y) * Math.sin(z) +
Math.cos(z) * Math.sin(x);
}
function gyroidOffset(level) {
const period = 2 * pi;
const box = {
min: [-period, -period, -period],
max: [period, period, period]
};
return levelSet(gyroid, box, period / n, level).scale(size / period);
};
function rhombicDodecahedron() {
const box = cube([1, 1, 2], true).scale(size * Math.sqrt(2));
const result =
box.rotate([90, 45, 0]).intersect(box.rotate([90, 45, 90]));
return result.intersect(box.rotate([0, 0, 45]));
}
let result = rhombicDodecahedron().intersect(gyroidOffset(-0.4))
result = result.subtract(gyroidOffset(0.4));
result = result.rotate([-45, 0, 90]).translate([0, 0, size / Math.sqrt(2)]);
return result;
}
};
for (const [func, code] of Object.entries(examples.functions)) {
exports.functionBodies = new Map();
for (const [func, code] of Object.entries(exports.functions)) {
const whole = code.toString();
const lines = whole.split('\n');
lines.splice(0, 1); // remove first line
lines.splice(-2, 2); // remove last two lines
// remove first six leading spaces
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
// remove first four leading spaces
const body = '\n' + lines.map(l => l.slice(4)).join('\n');
const name =
func.replace(/([a-z])([A-Z])/g, '$1 $2'); // Add spaces between words
examples.functionBodies.set(name, body);
};
exports.functionBodies.set(name, body);
};
})(typeof exports == 'undefined' ? this['examples'] = {} : exports);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 952 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

+13 -29
View File
@@ -5,36 +5,15 @@
<title>ManifoldCAD</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description"
content="Fast, reliable, parametric solid modeling web app. Programmatic 3D design with JavaScript, inspired by and improving upon OpenSCAD & JSCAD. Demonstrates a new GPU-parallel, open-source geometry kernel: Manifold.">
<meta name="theme-color" content="#A288B3">
<meta property="og:title" content="ManifoldCAD">
<meta property="og:type" content="website">
<meta property="og:url" content="https://manifoldcad.org/" />
<meta property="og:image" content="https://elalish.github.io/manifold/samples/models/mengerSpongeSquare.png">
<meta property="og:image:width" content="850">
<meta property="og:image:height" content="850">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png">
<link rel="manifest" href="/manifest.json">
<link type="text/css" href="editor.css" rel="stylesheet">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "ManifoldCAD",
"applicationCategory": "DesignApplication",
"offers": {
"@type": "Offer",
"price": "0"
}
}
</script>
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<link type="text/css" href="editor.css" rel="stylesheet" />
</head>
<body>
<div class="page">
<div class="container header" style="background-color: #A288B3;">
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
<div class="container" style="background-color: rgb(162 136 179);">
<p style="flex: 2;"><a href="https://github.com/elalish/manifold">Manifold</a>CAD
</p>
<div class="margin" style="position: relative;">
<button type="button" id="file" class="blue"><b class="uparrow"></b>&nbsp;&nbsp;<span
id="current">Intro</span></button>
@@ -51,7 +30,9 @@
<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>
@@ -59,14 +40,17 @@
</div>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css"
integrity="sha512-iQEIc0rsSDujsfjtD+lfyJ1W23Bh/lbgriubKDAym6VlEIDRj9rrbSIyJRyshOrl8s0yRcQ0+gyrZfSLyjJGWQ=="
crossorigin="anonymous" referrerpolicy="no-referrer">
<script type="module" src="editor.js"></script>
crossorigin="anonymous" referrerpolicy="no-referrer" />
<script src="examples.js"></script>
<script src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
</html>
-42
View File
@@ -1,42 +0,0 @@
{
"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"
}
]
}
+65 -205
View File
@@ -6,6 +6,7 @@
<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;
@@ -36,10 +37,9 @@
<body>
<p>This example demonstrates feeding Manifold's output into <a
href="https://modelviewer.dev/">&lt;model-viewer&gt;</a> via <a
href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>. It also shows how to pass mesh
properties like UV coordinates through Manifold and how to re-associate textures after operations. A more basic
three.js example is <a href="three.html">here</a>. </p>
href="https://modelviewer.dev/">&lt;model-viewer&gt;</a>
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
</p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
@@ -47,220 +47,80 @@
</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 src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/exporters/GLTFExporter.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
wasm.setup();
const mesh = new THREE.Mesh(undefined, new THREE.MeshStandardMaterial({
color: 'yellow',
metalness: 1,
roughness: 0.2
}));
const 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]);
}
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');
csg('difference');
});
const mv = document.querySelector('model-viewer');
let objectURL = null;
const exporter = new THREE.GLTFExporter();
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;
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 }
);
}
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);
// 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 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);
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
// most of three.js geometries aren't manifolds, so...
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
</script>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

-41
View File
@@ -1,41 +0,0 @@
// 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;
}))});
+119
View File
@@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<title>Manifold - three.js texture example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="https://elalish.github.io/manifold/samples/models/favicon.png" />
<style>
body {
margin: 0;
background-color: #f7f7f7;
font-family: 'Rubik', sans-serif;
font-size: 16px;
line-height: 24px;
color: rgba(0, 0, 0, .87);
font-weight: 400;
-webkit-font-smoothing: antialiased;
}
p {
max-width: 700px;
margin: 1em;
text-align: left;
}
canvas {
display: block;
width: 100vw;
height: 100vw;
max-width: 600px;
max-height: 600px;
}
</style>
</head>
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>.
</p>
<input type="checkbox"/><br />
<canvas id="output"></canvas>
</body>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="manifold.js"></script>
<script>
Module().then(function (wasm) {
wasm.setup();
const cube = new wasm.Mesh();
cube.vertPos = new Float32Array(24);
cube.triVerts = new Uint32Array(36);
cube.vertPos.set([
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1]);
cube.triVerts.set([
0, 2, 1, 0, 3, 2,
4, 5, 6, 4, 6, 7,
0, 1, 5, 0, 5, 4,
1, 2, 6, 1, 6, 5,
2, 3, 7, 2, 7, 6,
3, 0, 4, 3, 4, 7]);
const manifold = wasm.Manifold(cube);
const result = manifold.add(manifold.rotate([90, 0, 90]).translate([1, 1, 0]));
// we have a manifold, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 1, 100);
camera.position.z = 10;
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(mesh2geometry(result.getMesh()),
new THREE.MeshNormalMaterial({
flatShading: true
}));
const wireframe = new THREE.WireframeGeometry(mesh.geometry);
const lines = new THREE.LineSegments(wireframe);
scene.add(mesh);
scene.add(lines);
lines.visible = false;
const checkbox = document.querySelector('input');
checkbox.onchange = function (event) {
mesh.visible = !checkbox.checked;
lines.visible = checkbox.checked;
};
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
scene.rotation.x = time / 2000;
scene.rotation.y = time / 1000;
renderer.render(scene, camera);
});
});
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
</script>
</html>
+45 -46
View File
@@ -6,6 +6,7 @@
<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;
@@ -36,7 +37,8 @@
<body>
<p>This demonstrates the minimum code to connect Manifold to a three.js rendering. A slightly more complex example
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>. </p>
involving GLTF export and &lt;model-viewer&gt; can be found <a href="model-viewer.html">here</a>.
</p>
<select>
<option value="difference" selected>Difference</option>
<option value="intersection">Intersection</option>
@@ -44,67 +46,64 @@
</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';
const wasm = await Module();
wasm.setup();
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.144.0/examples/js/utils/BufferGeometryUtils.js"></script>
<script src="manifold.js"></script>
<script>
let wasm;
Module().then(function (tmp) {
wasm = tmp;
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
// we have manifold module, let's do some three.js
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 10);
camera.position.z = 1;
wasm.setup();
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const scene = new THREE.Scene();
const mesh = new THREE.Mesh(undefined, new THREE.MeshNormalMaterial({
flatShading: true
}));
scene.add(mesh);
const icosahedron = simplify(new THREE.IcosahedronGeometry(0.16));
const 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 manifold_1 = wasm.cube(0.2, 0.2, 0.2, true);
const manifold_2 = new wasm.Manifold(geometry2mesh(icosahedron));
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
const csg = function (operation) {
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
document.querySelector('select').onchange = function (event) {
csg(event.target.value);
};
csg('difference');
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);
const output = document.querySelector("#output");
const renderer = new THREE.WebGLRenderer({ canvas: output, antialias: true });
const dim = output.getBoundingClientRect();
renderer.setSize(dim.width, dim.height);
renderer.setAnimationLoop(function (time) {
mesh.rotation.x = time / 2000;
mesh.rotation.y = time / 1000;
renderer.render(scene, camera);
});
});
// functions to convert between three.js and wasm
function geometry2mesh(geometry) {
const vertProperties = geometry.attributes.position.array;
const vertPos = geometry.attributes.position.array;
const vertNormal = geometry.attributes.normal.array;
const triVerts = geometry.index.array;
return new wasm.Mesh({ numProp: 3, vertProperties, triVerts });
return new wasm.Mesh({vertPos, vertNormal, triVerts});
}
function mesh2geometry(mesh) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
@@ -113,7 +112,7 @@
function simplify(geometry) {
delete geometry.attributes.normal;
delete geometry.attributes.uv;
const simplified = mergeVertices(geometry);
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
simplified.computeVertexNormals();
return simplified;
}
+89 -176
View File
@@ -12,97 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
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;
}
const threePath = 'https://cdn.jsdelivr.net/npm/three@0.144.0/';
importScripts(
'manifold.js', threePath + 'build/three.js',
threePath + 'examples/js/exporters/GLTFExporter.js');
// manifold member functions that returns a new manifold
const memberFunctions = [
@@ -112,7 +25,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', 'show', 'only'
'difference', 'intersection', 'compose', 'levelSet', 'smooth'
];
const utils = [
'setMinCircularAngle', 'setMinCircularEdgeLength', 'setCircularSegments',
@@ -120,44 +33,49 @@ const utils = [
];
const exposedFunctions = constructors.concat(utils);
// 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 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.
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();
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;
}
}
manifoldRegistry.length = 0;
};
// Setup complete
postMessage(null);
for (const name of constructors) {
const originalFn = wasm[name];
wasm[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
wasm.cleanup =
function() {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
}
postMessage(null);
});
const oldLog = console.log;
console.log = function(...args) {
@@ -173,38 +91,34 @@ console.log = function(...args) {
oldLog(...args);
};
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();
}
};
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();
}
}
function outputMesh(indices, position, mesh) {
indices.setArray(mesh.triVerts);
// Export & Rendering
// ------------------------------------------------------------
const mesh = new THREE.Mesh(
undefined,
new THREE.MeshStandardMaterial(
{color: 'yellow', metalness: 1, roughness: 0.2}));
const rotation = new THREE.Matrix4();
rotation.set(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
mesh.setRotationFromMatrix(rotation); // Z-up -> Y-up
mesh.scale.setScalar(0.001); // mm -> m
const 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);
}
const exporter = new THREE.GLTFExporter();
async function exportGLB(manifold) {
function exportGLB(manifold) {
console.log(`Triangles: ${manifold.numTri().toLocaleString()}`);
const box = manifold.boundingBox();
const size = [0, 0, 0];
@@ -217,25 +131,24 @@ async function exportGLB(manifold) {
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
// From Z-up to Y-up (glTF)
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
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});
}
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)});
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;
}
-412
View File
@@ -1,412 +0,0 @@
// 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 &epsilon;
* defining
* [&epsilon;-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
precision(): number;
/**
* The genus is a topological property of the manifold, representing the
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
* a single mesh, so it is best to call Decompose() first.
*/
genus(): number;
/**
* Returns the surface area and volume of the manifold. These properties are
* clamped to zero for a given face if they are within the Precision(). This
* means degenerate manifolds can by identified by testing these properties as
* == 0.
*/
getProperties(): Properties;
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex (returned
* as vectors in the structure) and also returns their minimum and maximum
* values.
*/
getCurvature(): Curvature;
/**
* 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;
}
-65
View File
@@ -1,65 +0,0 @@
// 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[]
};
+434 -26
View File
@@ -1,4 +1,4 @@
// Copyright 2023 The Manifold Authors.
// 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.
@@ -12,33 +12,441 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import * as T from './manifold-encapsulated-types';
export * from './manifold-global-types';
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[],
};
export type Manifold = T.Manifold;
export type Mesh = T.Mesh;
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 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;
declare class Manifold {
/**
* Create a Manifold from a Mesh object.
*/
constructor(mesh: Mesh);
/**
* Transform this Manifold in space. The first three columns form a 3x3 matrix
* transform and the last is a translation vector. This operation can be
* chained. Transforms are combined and applied lazily.
*
* @param m The affine transform matrix to apply to all the vertices.
*/
transform(m: Matrix3x4): Manifold;
/**
* Move this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
* then Y, then Z, in degrees. We use degrees so that we can minimize rounding
* error, and eliminate it completely for any multiples of 90 degrees.
* Additionally, more efficient code paths are used to update the manifold
* when the transforms only rotate by multiples of 90 degrees. This operation
* can be chained. Transforms are combined and applied lazily.
*
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
* combined and applied lazily.
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3|number): Manifold;
/**
* Boolean union
*/
add(other: Manifold): Manifold;
/**
* Boolean difference
*/
subtract(other: Manifold): Manifold;
/**
* Boolean intersection
*/
intersect(other: Manifold): Manifold;
/**
* Increase the density of the mesh by splitting every edge into n pieces. For
* instance, with n = 2, each triangle will be split into 4 triangles. These
* will all be coplanar (and will not be immediately collapsed) unless the
* Mesh/Manifold has halfedgeTangents specified (e.g. from the Smooth()
* constructor), in which case the new vertices will be moved to the
* interpolated surface according to their barycentric coordinates.
*
* @param n The number of pieces to split every edge into. Must be > 1.
*/
refine(n: number): Manifold;
/**
* This function does not change the topology, but allows the vertices to be
* moved according to any arbitrary input function. It is easy to create a
* function that warps a geometrically valid object into one which overlaps,
* but that is not checked here, so it is up to the user to choose their
* function with discretion.
*
* @param warpFunc A function that modifies a given vertex position.
*/
warp(warpFunc: (vert: Vec3) => void): Manifold;
/**
* This operation returns a vector of Manifolds that are topologically
* disconnected. If everything is connected, the vector is length one,
* containing a copy of the original. It is the inverse operation of
* Compose().
*/
decompose(): Manifold[];
/**
* Does the Manifold have any triangles?
*/
isEmpty(): boolean;
/**
* The number of vertices in the Manifold.
*/
numVert(): number;
/**
* The number of triangles in the Manifold.
*/
numTri(): number;
/**
* The number of edges in the Manifold.
*/
numEdge(): number;
/**
* Returns the axis-aligned bounding box of all the Manifold's vertices.
*/
boundingBox(): Box;
/**
* Returns the precision of this Manifold's vertices, which tracks the
* approximate rounding error over all the transforms and operations that have
* led to this state. Any triangles that are colinear within this precision
* are considered degenerate and removed. This is the value of &epsilon;
* defining
* [&epsilon;-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
precision(): number;
/**
* The genus is a topological property of the manifold, representing the
* number of "handles". A sphere is 0, torus 1, etc. It is only meaningful for
* a single mesh, so it is best to call Decompose() first.
*/
genus(): number;
/**
* Returns the surface area and volume of the manifold. These properties are
* clamped to zero for a given face if they are within the Precision(). This
* means degenerate manifolds can by identified by testing these properties as
* == 0.
*/
getProperties(): Properties;
/**
* Curvature is the inverse of the radius of curvature, and signed such that
* positive is convex and negative is concave. There are two orthogonal
* principal curvatures at any point on a manifold, with one maximum and the
* other minimum. Gaussian curvature is their product, while mean
* curvature is their sum. This approximates them for every vertex (returned
* as vectors in the structure) and also returns their minimum and maximum
* values.
*/
getCurvature(): Curvature;
/**
* This returns a Mesh of simple vectors of vertices and triangles suitable
* for saving or other operations outside of the context of this library.
*/
getMesh(): Mesh;
/**
* Gets the relationship to the previous meshes, for the purpose of assigning
* properties like texture coordinates. The triBary vector is the same length
* as Mesh.triVerts: BaryRef.originalID indicates the source mesh and
* BaryRef.tri is that mesh's triangle index to which these barycentric
* coordinates refer. BaryRef.vertBary gives an index for each vertex into the
* barycentric vector if that index is >= 0, indicating it is a new vertex. If
* the index is < 0, this indicates it is an original vertex, the index + 3
* vert of the referenced triangle.
*
* BaryRef.meshID is a unique ID to the particular instance of a given mesh.
* For instance, if you want to convert the triangle mesh to a polygon mesh,
* all the triangles from a given face will have the same .meshID and .tri
* values.
*/
getMeshRelation(): MeshRelation;
/**
* If you copy a manifold, but you want this new copy to have new properties
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
* meaning it will now be referenced by its descendants instead of the meshes
* it was built from, allowing you to differentiate the copies when applying
* your properties to the final result.
*
* This function also condenses all coplanar faces in the relation, and
* collapses those edges. If you want to have inconsistent properties across
* these faces, meaning you want to preserve some of these edges, you should
* instead call GetMesh(), calculate your properties and use these to
* construct a new manifold.
*/
asOriginal(): Manifold;
/**
* If this mesh is an original, this returns its meshID that can be referenced
* by product manifolds' MeshRelation. If this manifold is a product, this
* returns -1.
*/
originalID(): number;
}
/**
* Constructs a unit cube (edge lengths all one), by default in the first
* octant, touching the origin.
*
* @param size The X, Y, and Z dimensions of the box.
* @param center Set to true to shift the center to the origin.
*/
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can also
* form cones if both radii are specified.
*
* @param height Z-extent
* @param radiusLow Radius of bottom circle. Must be positive.
* @param radiusHigh Radius of top circle. Can equal zero. Default is equal to
* radiusLow.
* @param circularSegments How many line segments to use around the circle.
* Default is calculated by the static Defaults.
* @param center Set to true to shift the center to the origin. Default is
* origin at the bottom.
*/
declare function cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
*
* @param radius Radius of the sphere. Must be positive.
* @param circularSegments Number of segments along its
* diameter. This number will always be rounded up to the nearest factor of
* four, as this sphere is constructed by refining an octahedron. This means
* there are a circle of vertices on all three of the axis planes. Default is
* calculated by the static Defaults.
*/
declare function sphere(radius: number, circularSegments?: number): Manifold;
/**
* Constructs a smooth version of the input mesh by creating tangents; this
* method will throw if you have supplied tangents with your mesh already. The
* actual triangle resolution is unchanged; use the Refine() method to
* interpolate to a higher-resolution curve.
*
* By default, every edge is calculated for maximum smoothness (very much
* approximately), attempting to minimize the maximum mean Curvature magnitude.
* No higher-order derivatives are considered, as the interpolation is
* independent per triangle, only sharing constraints on their boundaries.
*
* @param mesh input Mesh.
* @param sharpenedEdges If desired, you can supply a vector of sharpened
* halfedges, which should in general be a small subset of all halfedges. Order
* of entries doesn't matter, as each one specifies the desired smoothness
* (between zero and one, with one the default for all unspecified halfedges)
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
* between triVert 0 and 1, etc).
*
* At a smoothness value of zero, a sharp crease is made. The smoothness is
* interpolated along each edge, so the specified value should be thought of as
* an average. Where exactly two sharpened edges meet at a vertex, their
* tangents are rotated to be colinear so that the sharpened edge can be
* continuous. Vertices with only one sharpened edge are completely smooth,
* allowing sharpened edges to smoothly vanish at termination. A single vertex
* can be sharpened by sharping all edges that are incident on it, allowing
* cones to be formed.
*/
declare function smooth(mesh: Mesh, sharpenedEdges?: Smoothness[]): Manifold;
/**
* Constructs a tetrahedron centered at the origin with one vertex at (1,1,1)
* and the rest at similarly symmetric points.
*/
declare function tetrahedron(): Manifold;
/**
* Constructs a manifold from a set of polygons by extruding them along the
* Z-axis.
*
* @param crossSection A set of non-overlapping polygons to extrude.
* @param height Z-extent of extrusion.
* @param nDivisions Number of extra copies of the crossSection to insert into
* the shape vertically; especially useful in combination with twistDegrees to
* avoid interpolation artifacts. Default is none.
* @param twistDegrees Amount to twist the top crossSection relative to the
* bottom, interpolated linearly for the divisions in between.
* @param scaleTop Amount to scale the top (independently in X and Y). If the
* scale is {0, 0}, a pure cone is formed with only a single vertex at the top.
* Default {1, 1}.
*/
declare function extrude(
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
/**
* Constructs a manifold from a set of polygons by revolving this cross-section
* around its Y-axis and then setting this as the Z-axis of the resulting
* manifold. If the polygons cross the Y-axis, only the part on the positive X
* side is used. Geometrically valid input will result in geometrically valid
* output.
*
* @param crossSection A set of non-overlapping polygons to revolve.
* @param circularSegments Number of segments along its diameter. Default is
* calculated by the static Defaults.
*/
declare function revolve(
crossSection: Polygons, circularSegments?: number): Manifold;
declare function union(a: Manifold, b: Manifold): Manifold;
declare function difference(a: Manifold, b: Manifold): Manifold;
declare function intersection(a: Manifold, b: Manifold): Manifold;
declare function union(manifolds: Manifold[]): Manifold;
declare function difference(manifolds: Manifold[]): Manifold;
declare function intersection(manifolds: Manifold[]): Manifold;
/**
* Constructs a new manifold from a list of other manifolds. This is a purely
* topological operation, so care should be taken to avoid creating
* overlapping results. It is the inverse operation of Decompose().
*
* @param manifolds A list of Manifolds to lazy-union together.
*/
declare function compose(manifolds: Manifold[]): Manifold;
/**
* Constructs a level-set Mesh from the input Signed-Distance Function (SDF).
* This uses a form of Marching Tetrahedra (akin to Marching Cubes, but better
* for manifoldness). Instead of using a cubic grid, it uses a body-centered
* cubic grid (two shifted cubic grids). This means if your function's interior
* exceeds the given bounds, you will see a kind of egg-crate shape closing off
* the manifold, which is due to the underlying grid.
*
* @param sdf The signed-distance function which returns the signed distance of
* a given point in R^3. Positive values are inside, negative outside.
* @param bounds An axis-aligned box that defines the extent of the grid.
* @param edgeLength Approximate maximum edge length of the triangles in the
* final result. This affects grid spacing, and hence has a strong effect on
* performance.
* @param level You can inset your Mesh by using a positive value, or outset
* it with a negative value.
*/
declare function levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
/**
* @name Defaults
* These static properties control how circular shapes are quantized by
* default on construction. If circularSegments is specified, it takes
* precedence. If it is zero, then instead the minimum is used of the segments
* calculated based on edge length and angle, rounded up to the nearest
* multiple of four. To get numbers not divisible by four, circularSegments
* must be specified.
*/
///@{
declare function setMinCircularAngle(angle: number): void;
declare function setMinCircularEdgeLength(length: number): void;
declare function setCircularSegments(segments: number): void;
declare function getCircularSegments(radius: number): number;
///@}
/**
* Create a Manifold from a serialized Mesh object (MeshVec). Unlike the
* constructor, this method does not dispose the Mesh after using it.
*
* @param meshVec The serialized Mesh object to convert into a Manifold.
*/
declare function ManifoldFromMeshVec(meshVec: MeshVec): Manifold;
declare interface ManifoldStatic {
cube: typeof cube;
cylinder: typeof cylinder;
sphere: typeof sphere;
smooth: typeof smooth;
tetrahedron: typeof tetrahedron;
extrude: typeof extrude;
revolve: typeof revolve;
union: typeof union;
difference: typeof difference;
intersection: typeof intersection;
compose: typeof compose;
levelSet: typeof levelSet;
setMinCircularAngle: typeof setMinCircularAngle;
setMinCircularEdgeLength: typeof setMinCircularEdgeLength;
setCircularSegments: typeof setCircularSegments;
getCircularSegments: typeof getCircularSegments;
ManifoldFromMeshVec: typeof ManifoldFromMeshVec;
Manifold: typeof Manifold;
setup: () => void;
}
export default function Module(): Promise<ManifoldStatic>;
declare function Module(): Promise<ManifoldStatic>;
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "manifold-3d",
"version": "2.1.0",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "manifold-3d",
"version": "2.1.0",
"version": "1.0.0",
"license": "Apache-2.0",
"devDependencies": {
"chai": "*",
+2 -5
View File
@@ -1,20 +1,17 @@
{
"name": "manifold-3d",
"version": "2.1.0",
"version": "1.0.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"
@@ -44,6 +41,6 @@
"devDependencies": {
"chai": "*",
"mocha": "*",
"gl-matrix": "3.4.3"
"three": "^0.146.0"
}
}
+107 -115
View File
@@ -12,17 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
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);
const expect = require('chai').expect;
const wasm = require('../manifold.js');
const examples = require('../examples/examples');
const THREE = require('three');
// manifold member functions that returns a new manifold
const memberFunctions = [
@@ -40,122 +33,121 @@ const utils = [
];
const exposedFunctions = constructors.concat(utils);
// 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.
wasm().then(function(Module) {
Module.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
// Note that this only fixes memory leak across different runs: the memory
// will only be freed when the compilation finishes.
let manifoldRegistry = [];
for (const name of memberFunctions) {
const originalFn = 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();
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;
}
}
manifoldRegistry = [];
};
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();
for (const name of constructors) {
const originalFn = Module[name];
Module[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
}
}
}
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');
});
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 = [];
}
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');
});
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('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');
});
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('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('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('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('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('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('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('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('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('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('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('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('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');
});
});
});
+2 -2
View File
@@ -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_17)
target_compile_features(perfTest PUBLIC cxx_std_14)
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_17)
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
endif()
-1
View File
@@ -29,7 +29,6 @@
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"}"
];
+3 -2
View File
@@ -25,7 +25,7 @@ target_link_libraries(samples
)
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(samples PUBLIC cxx_std_17)
target_compile_features(samples PUBLIC cxx_std_14)
project(samplesGPU)
@@ -46,5 +46,6 @@ target_link_libraries(samplesGPU
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(samplesGPU
PUBLIC cxx_std_17
PUBLIC cxx_std_14
PRIVATE cxx_std_17
)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 KiB

+8 -7
View File
@@ -26,8 +26,10 @@ 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({decorRadius * cosd(dPhiDeg * i) + twistRadius,
decorRadius * sind(dPhiDeg * i)});
circle[0].push_back(
{glm::vec2(decorRadius * cosd(dPhiDeg * i) + twistRadius,
decorRadius * sind(dPhiDeg * i)),
0});
}
Manifold decor = Manifold::Extrude(circle, width, nDivision, 180)
.Scale({1.0f, 0.5f, 1.0f})
@@ -43,14 +45,13 @@ 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));
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));
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});
}
base = Manifold::Extrude(stretch, width) ^ base;
// Remove extra edges in coplanar faces
base = base.AsOriginal();
return base;
+1 -1
View File
@@ -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({cosd(dPhi * i) + 2.0f, sind(dPhi * i)});
circle[0].push_back({glm::vec2(cosd(dPhi * i) + 2.0f, sind(dPhi * i)), 0});
}
Manifold knot = Manifold::Revolve(circle, m);
+2 -2
View File
@@ -31,10 +31,10 @@ Manifold TetPuzzle(float edgeLength, float gap, int nDivisions) {
Manifold tet = Manifold::Tetrahedron().Scale(scale);
Polygons box;
box.push_back({{2, -2}, {2, 2}});
box.push_back({{glm::vec2(2, -2), 0}, {glm::vec2(2, 2), 1}});
for (int i = 0; i <= nDivisions; ++i) {
box[0].push_back({gap / 2, 2 - i * 4.0f / nDivisions});
box[0].push_back({glm::vec2(gap / 2, 2 - i * 4.0f / nDivisions), 2 + i});
}
Manifold screw = Manifold::Extrude(box, 2, nDivisions, 270)
+5 -3
View File
@@ -15,7 +15,7 @@
project (collider)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
add_library(${PROJECT_NAME} ${SOURCE_FILES})
if(MANIFOLD_USE_CUDA)
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
@@ -25,6 +25,8 @@ 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_17
target_compile_features(${PROJECT_NAME}
PUBLIC cxx_std_14
PRIVATE cxx_std_17
)
+6 -4
View File
@@ -18,16 +18,18 @@ 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_17
target_compile_features(${PROJECT_NAME}
PUBLIC cxx_std_14
PRIVATE cxx_std_17
)
+20 -28
View File
@@ -49,11 +49,12 @@ class Manifold {
Manifold(Manifold&&) noexcept;
Manifold& operator=(Manifold&&) noexcept;
Manifold(const MeshGL&, const std::vector<float>& propertyTolerance = {});
Manifold(const Mesh&);
Manifold(
const Mesh&,
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
const std::vector<float>& properties = std::vector<float>(),
const std::vector<float>& propertyTolerance = std::vector<float>());
static Manifold Smooth(const MeshGL&,
const std::vector<Smoothness>& sharpenedEdges = {});
static Manifold Smooth(const Mesh&,
const std::vector<Smoothness>& sharpenedEdges = {});
static Manifold Tetrahedron();
@@ -74,9 +75,6 @@ class Manifold {
*/
///@{
static Manifold Compose(const std::vector<Manifold>&);
Components GetComponents() const;
std::vector<Manifold> Decompose(Components components) const;
std::vector<Manifold> Decompose() const;
///@}
@@ -100,27 +98,21 @@ class Manifold {
*/
///@{
Mesh GetMesh() const;
MeshGL GetMeshGL(glm::ivec3 normalIdx = glm::ivec3(0)) const;
MeshGL GetMeshGL() const;
bool IsEmpty() const;
enum class Error {
NoError,
NonFiniteVertex,
NotManifold,
VertexOutOfBounds,
PropertiesWrongLength,
MissingPositionProperties,
MergeVectorsDifferentLengths,
MergeIndexOutOfBounds,
TransformWrongLength,
RunIndexWrongLength,
FaceIDWrongLength,
NO_ERROR,
NON_FINITE_VERTEX,
NOT_MANIFOLD,
VERTEX_INDEX_OUT_OF_BOUNDS,
PROPERTIES_WRONG_LENGTH,
TRI_PROPERTIES_WRONG_LENGTH,
TRI_PROPERTIES_OUT_OF_BOUNDS,
};
Error Status() const;
int NumVert() const;
int NumEdge() const;
int NumTri() const;
int NumProp() const;
int NumPropVert() const;
Box BoundingBox() const;
float Precision() const;
int Genus() const;
@@ -128,14 +120,14 @@ class Manifold {
Curvature GetCurvature() const;
///@}
/** @name Mesh ID
/** @name Relation
* 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
@@ -157,18 +149,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,
+1 -1
View File
@@ -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
+115 -146
View File
@@ -197,10 +197,17 @@ 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<TriRef> &halfedgeRef, const Manifold::Impl &inP,
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
const VecDH<int> &i03, const VecDH<int> &vP2R,
const VecDH<int>::IterC faceP2R, bool forward) {
// Each edge in the map is partially retained; for each of these, look up
@@ -259,8 +266,11 @@ 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 TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
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};
for (Halfedge e : edges) {
const int forwardEdge = facePtrR[faceLeft]++;
@@ -283,8 +293,7 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
void AppendNewEdges(
Manifold::Impl &outR, VecDH<int> &facePtrR,
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
VecDH<TriRef> &halfedgeRef, const VecDH<int> &facePQ2R,
const int numFaceP) {
VecDH<Ref> &halfedgeRef, const VecDH<int> &facePQ2R, const int numFaceP) {
// Pair up each edge's verts and distribute to faces based on indices in key.
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
@@ -313,8 +322,8 @@ void AppendNewEdges(
// add halfedges to result
const int faceLeft = facePQ2R[faceP];
const int faceRight = facePQ2R[numFaceP + faceQ];
const TriRef forwardRef = {0, -1, faceP};
const TriRef backwardRef = {1, -1, faceQ};
const Ref forwardRef = {0, faceP, -4};
const Ref backwardRef = {1, faceQ, -4};
for (Halfedge e : edges) {
const int forwardEdge = facePtrR[faceLeft]++;
const int backwardEdge = facePtrR[faceRight]++;
@@ -335,7 +344,7 @@ void AppendNewEdges(
struct DuplicateHalfedges {
Halfedge *halfedgesR;
TriRef *halfedgeRef;
Ref *halfedgeRef;
int *facePtr;
const Halfedge *halfedgesP;
const int *i03;
@@ -365,8 +374,11 @@ 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 TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
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};
for (int i = 0; i < glm::abs(inclusion); ++i) {
int forwardEdge = AtomicAdd(facePtr[halfedge.face], 1);
@@ -386,7 +398,7 @@ struct DuplicateHalfedges {
};
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
const VecDH<char> wholeHalfedgeP, const VecDH<int> &i03,
const VecDH<int> &vP2R, const int *faceP2R, bool forward,
ExecutionPolicy policy) {
@@ -398,142 +410,97 @@ void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
}
struct MapTriRef {
const TriRef *triRefP;
const TriRef *triRefQ;
struct CreateBarycentric {
glm::vec3 *barycentricR;
BaryRef *faceRef;
int *idx;
const int offsetQ;
__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 int firstNewVert;
const glm::vec3 *vertPosR;
const glm::vec3 *vertPosP;
const glm::vec3 *vertPosQ;
const glm::vec3 *vertPosR;
const Halfedge *halfedgeP;
const Halfedge *halfedgeQ;
const Halfedge *halfedgeR;
const BaryRef *triBaryP;
const BaryRef *triBaryQ;
const glm::vec3 *barycentricP;
const glm::vec3 *barycentricQ;
const bool invertQ;
const float precision;
__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;
__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);
const int triPQ = refPQ.tri;
const bool PQ = refPQ.meshID == 0;
const auto &vertPos = PQ ? vertPosP : vertPosQ;
const auto &halfedge = PQ ? halfedgeP : halfedgeQ;
const glm::vec3 *barycentric =
halfedgeRef.PQ == 0 ? barycentricP : barycentricQ;
const int tri = halfedgeRef.tri;
const BaryRef oldRef = halfedgeRef.PQ == 0 ? triBaryP[tri] : triBaryQ[tri];
glm::mat3 triPos;
for (const int j : {0, 1, 2})
triPos[j] = vertPos[halfedge[3 * triPQ + j].startVert];
faceRef[halfedgeR.face] = oldRef;
for (const int i : {0, 1, 2}) {
const int vert = halfedgeR[3 * tri + i].startVert;
uvw[3 * tri + i] = GetBarycentric(vertPosR[vert], triPos, precision);
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;
}
}
};
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;
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());
const int numTri = outR.NumTri();
outR.meshRelation_.triProperties.resize(numTri);
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]);
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));
}
}
}
return std::make_pair(faceRef, halfedgeBary);
}
} // namespace
@@ -545,25 +512,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());
@@ -655,9 +622,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 triRef once the faces
// The halfedgeRef contains the data that will become triBary once the faces
// are triangulated.
VecDH<TriRef> halfedgeRef(2 * outR.NumEdge());
VecDH<Ref> halfedgeRef(2 * outR.NumEdge());
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
i03, vP2R, facePQ2R.begin(), true);
@@ -672,6 +639,11 @@ 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;
@@ -683,7 +655,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
if (ManifoldParams().intermediateChecks)
ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
outR.Face2Tri(faceEdge, halfedgeRef);
outR.Face2Tri(faceEdge, faceRef, halfedgeBary);
#ifdef MANIFOLD_DEBUG
triangulate.Stop();
@@ -694,15 +666,13 @@ 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;
@@ -710,7 +680,6 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
#endif
outR.Finish();
outR.IncrementMeshIDs();
#ifdef MANIFOLD_DEBUG
sort.Stop();
+31 -90
View File
@@ -50,48 +50,6 @@ 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
@@ -126,8 +84,7 @@ Manifold Manifold::Smooth(const Mesh& mesh,
"when supplying tangents, the normal constructor should be used "
"rather than Smooth().");
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh, relation);
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh);
impl->CreateTangents(sharpenedEdges);
return Manifold(impl);
}
@@ -137,7 +94,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));
}
/**
@@ -148,10 +105,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.AsOriginal();
return cube;
}
/**
@@ -175,13 +132,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)));
circle[0].push_back(
{radiusLow * glm::vec2(cosd(dPhi * i), sind(dPhi * i)), 0});
}
Manifold cylinder =
Manifold::Extrude(circle, height, 0, 0.0f, glm::vec2(scale));
if (center)
cylinder =
cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f)).AsOriginal();
cylinder = cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f));
return cylinder;
}
@@ -198,13 +155,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_->InitializeOriginal();
pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1));
return Manifold(pImpl_);
}
@@ -236,15 +193,12 @@ 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();
SimplePolygonIdx simpleIndexed;
for (const glm::vec2& polyVert : poly) {
vertPos.push_back({polyVert.x, polyVert.y, 0.0f});
simpleIndexed.push_back({polyVert, idx++});
for (PolyVert& polyVert : poly) {
vertPos.push_back({polyVert.pos.x, polyVert.pos.y, 0.0f});
polyVert.idx = idx++;
}
crossSectionIndexed.push_back(simpleIndexed);
}
for (int i = 1; i < nDivisions + 1; ++i) {
float alpha = i / float(nDivisions);
@@ -263,7 +217,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];
glm::vec2 pos = transform * poly[vert].pos;
vertPos.push_back({pos.x, pos.y, height * alpha});
triVerts.push_back({thisVert, lastVert, thisVert - nCrossSection});
triVerts.push_back(
@@ -277,7 +231,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(crossSectionIndexed);
std::vector<glm::ivec3> top = Triangulate(crossSection);
for (const glm::ivec3& tri : top) {
triVerts.push_back({tri[0], tri[2], tri[1]});
if (!isCone) triVerts.push_back(tri + nCrossSection * nDivisions);
@@ -285,9 +239,7 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
pImpl_->CreateHalfedges(triVertsDH);
pImpl_->Finish();
pImpl_->meshRelation_.originalID = ReserveIDs(1);
pImpl_->InitializeOriginal();
pImpl_->CreateFaces();
pImpl_->InitializeNewReference();
return Manifold(pImpl_);
}
@@ -306,7 +258,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.x);
radius = fmax(radius, vert.pos.x);
}
}
int nDivisions =
@@ -319,7 +271,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].x <= 0) {
if (poly[polyVert].pos.x <= 0) {
start = polyVert;
break;
}
@@ -333,7 +285,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];
glm::vec2 pos = poly[polyVert].pos;
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
triVerts.push_back({startVert + slice, startVert + lastSlice,
lastStart + lastSlice});
@@ -343,11 +295,11 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
}
} else { // poly crosses zero
int polyVert = start;
glm::vec2 pos = poly[polyVert];
glm::vec2 pos = poly[polyVert].pos;
do {
glm::vec2 lastPos = pos;
polyVert = (polyVert + 1) % poly.size();
pos = poly[polyVert];
pos = poly[polyVert].pos;
if (pos.x > 0) {
if (lastPos.x <= 0) {
float a = pos.x / (pos.x - lastPos.x);
@@ -357,7 +309,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];
glm::vec2 pos = poly[polyVert].pos;
vertPos.push_back({pos.x * cosd(phi), pos.x * sind(phi), pos.y});
if (lastPos.x > 0) {
triVerts.push_back({startVert + slice, startVert + lastSlice,
@@ -386,9 +338,7 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
pImpl_->CreateHalfedges(triVertsDH);
pImpl_->Finish();
pImpl_->meshRelation_.originalID = ReserveIDs(1);
pImpl_->InitializeOriginal();
pImpl_->CreateFaces();
pImpl_->InitializeNewReference();
return Manifold(pImpl_);
}
@@ -407,7 +357,12 @@ Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
return Manifold(std::make_shared<Impl>(CsgLeafNode::Compose(children)));
}
Components Manifold::GetComponents() const {
/**
* 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 {
Graph graph;
auto pImpl_ = GetCsgLeafNode().GetImpl();
for (int i = 0; i < NumVert(); ++i) {
@@ -419,21 +374,16 @@ Components Manifold::GetComponents() const {
}
std::vector<int> components;
const int numLabel = ConnectedComponents(components, graph);
return {components, numLabel};
}
std::vector<Manifold> Manifold::Decompose(Components components) const {
auto pImpl_ = GetCsgLeafNode().GetImpl();
if (components.numComponents == 1) {
if (numLabel == 1) {
std::vector<Manifold> meshes(1);
meshes[0] = *this;
return meshes;
}
VecDH<int> vertLabel(components.indices);
VecDH<int> vertLabel(components);
std::vector<Manifold> meshes;
for (int i = 0; i < components.numComponents; ++i) {
for (int i = 0; i < numLabel; ++i) {
auto impl = std::make_shared<Impl>();
// inherit original object's precision
impl->precision_ = pImpl_->precision_;
@@ -468,13 +418,4 @@ std::vector<Manifold> Manifold::Decompose(Components components) 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
+45 -41
View File
@@ -40,6 +40,16 @@ 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;
@@ -54,15 +64,6 @@ 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;
@@ -127,7 +128,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.
@@ -138,6 +139,7 @@ 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 =
@@ -151,6 +153,7 @@ 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;
@@ -159,13 +162,14 @@ Manifold::Impl CsgLeafNode::Compose(
combined.halfedge_.resize(2 * numEdge);
combined.faceNormal_.resize(numTri);
combined.halfedgeTangent_.resize(2 * numEdge);
combined.meshRelation_.triRef.resize(numTri);
combined.meshRelation_.barycentric.resize(numBary);
combined.meshRelation_.triBary.resize(numTri);
auto policy = autoPolicy(numTri);
int nextVert = 0;
int nextEdge = 0;
int nextTri = 0;
int i = 0;
int nextBary = 0;
for (auto &node : nodes) {
if (node->transform_ == glm::mat4x3(1.0f)) {
copy(policy, node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),
@@ -191,31 +195,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 add an offset so that each node instance gets
// important to increment them separately so that each node instance gets
// unique meshIDs.
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;
}
combined.IncrementMeshIDs(nextTri, node->pImpl_->NumTri());
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;
}
@@ -254,21 +258,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());
@@ -277,12 +281,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;
}
@@ -301,7 +305,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) {
@@ -387,7 +391,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
@@ -424,15 +428,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_;
@@ -440,14 +444,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;
}
}
+1 -1
View File
@@ -17,7 +17,7 @@
namespace manifold {
enum class CsgNodeType { Union, Intersection, Difference, Leaf };
enum class CsgNodeType { UNION, INTERSECTION, DIFFERENCE, LEAF };
class CsgLeafNode;
+39 -71
View File
@@ -60,19 +60,19 @@ struct ShortEdge {
struct FlagEdge {
const Halfedge* halfedge;
const TriRef* triRef;
const BaryRef* triBary;
__host__ __device__ bool operator()(int edge) {
if (halfedge[edge].pairedHalfedge < 0) return false;
// Flag redundant edges - those where the startVert is surrounded by only
// two original triangles.
const TriRef ref0 = triRef[edge / 3];
const BaryRef ref0 = triBary[edge / 3];
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
const TriRef ref1 = triRef[current / 3];
const BaryRef ref1 = triBary[current / 3];
while (current != edge) {
current = NextHalfedge(halfedge[current].pairedHalfedge);
int tri = current / 3;
const TriRef ref = triRef[tri];
const BaryRef ref = triBary[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_.triRef.cptrD()})) -
FlagEdge({halfedge_.cptrD(), meshRelation_.triBary.cptrD()})) -
flaggedEdges.begin();
flaggedEdges.resize(numFlagged);
@@ -213,11 +213,8 @@ 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_.triRef.size() > 0)
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
if (meshRelation_.triProperties.size() > 0)
meshRelation_.triProperties.push_back(
meshRelation_.triProperties[oldFace]);
if (meshRelation_.triBary.size() > 0)
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
newHalfedge += 3;
@@ -230,11 +227,8 @@ void Manifold::Impl::DedupeEdge(const int edge) {
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
PairUp(newHalfedge + 1, opposite);
PairUp(newHalfedge, newHalfedge - 3);
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 (meshRelation_.triBary.size() > 0)
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
break;
@@ -307,8 +301,7 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
}
void Manifold::Impl::CollapseEdge(const int edge) {
VecDH<TriRef>& triRef = meshRelation_.triRef;
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
VecDH<BaryRef>& triBary = meshRelation_.triBary;
const Halfedge toRemove = halfedge_[edge];
if (toRemove.pairedHalfedge < 0) return;
@@ -333,32 +326,24 @@ 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 TriRef ref = triRef[tri];
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
const BaryRef ref = triBary[tri];
// Don't collapse if the edge is not redundant (this may have changed due
// to the collapse of neighbors).
if (ref.meshID != 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;
}
}
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
return;
// Don't collapse edge if it would cause a triangle to invert.
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
if (CCW(projection * pNext, projection * pLast, projection * pNew,
precision_) < 0)
return;
@@ -373,30 +358,18 @@ 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) {
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];
}
}
// Update the shifted triangles to the vertBary of endVert
const int tri = current / 3;
const int vIdx = current - 3 * tri;
triBary[tri].vertBary[vIdx] =
(ref0.meshID == triBary[tri].meshID && ref0.tri == triBary[tri].tri)
? ref0.vertBary[(edge + 1) % 3]
: ref1.vertBary[toRemove.pairedHalfedge % 3];
}
const int vert = halfedge_[current].endVert;
@@ -418,7 +391,7 @@ void Manifold::Impl::CollapseEdge(const int edge) {
}
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
VecDH<TriRef>& triRef = meshRelation_.triRef;
VecDH<BaryRef>& triBary = meshRelation_.triBary;
if (edge < 0) return;
const int pair = halfedge_[edge].pairedHalfedge;
@@ -458,28 +431,23 @@ 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];
triRef[tri0] = triRef[tri1];
triBary[tri0] = triBary[tri1];
triBary[tri0].vertBary[perm0[1]] = triBary[tri1].vertBary[perm1[0]];
triBary[tri0].vertBary[perm0[0]] = triBary[tri1].vertBary[perm1[2]];
// Calculate a new barycentric coordinate for the split triangle.
const glm::vec3 uvw0 = UVW(triBary[tri1].vertBary[perm1[0]],
meshRelation_.barycentric.cptrH());
const glm::vec3 uvw1 = UVW(triBary[tri1].vertBary[perm1[1]],
meshRelation_.barycentric.cptrH());
const float l01 = glm::length(v[1] - v[0]);
const float l02 = glm::length(v[2] - v[0]);
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
// 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;
}
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;
// if the new edge already exists, duplicate the verts and split the mesh.
int current = halfedge_[tri1edge[0]].pairedHalfedge;
+33 -11
View File
@@ -30,14 +30,15 @@ namespace manifold {
* faceNormal_ values are retained, repeated as necessary.
*/
void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
const VecDH<TriRef>& halfedgeRef) {
const VecDH<BaryRef>& faceRef,
const VecDH<int>& halfedgeBary) {
VecDH<glm::ivec3> triVerts;
VecDH<glm::vec3> triNormal;
VecDH<TriRef>& triRef = meshRelation_.triRef;
triRef.resize(0);
VecDH<BaryRef>& triBary = meshRelation_.triBary;
triBary.resize(0);
triVerts.reserve(halfedge_.size());
triNormal.reserve(halfedge_.size());
triRef.reserve(halfedge_.size());
triBary.reserve(halfedge_.size());
for (int face = 0; face < faceEdge.size() - 1; ++face) {
const int firstEdge = faceEdge[face];
@@ -46,6 +47,12 @@ 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,
@@ -65,7 +72,11 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
triVerts.push_back(tri);
triNormal.push_back(normal);
triRef.push_back(halfedgeRef[firstEdge]);
triBary.push_back(faceRef[face]);
for (int k : {0, 1, 2}) {
int index = linearSearch(mapping, tri[k]);
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
}
} else if (numEdge == 4) { // Pair of triangles
int mapping[4] = {halfedge_[firstEdge].startVert,
halfedge_[firstEdge + 1].startVert,
@@ -114,19 +125,30 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
for (auto tri : {tri0, tri1}) {
triVerts.push_back(tri);
triNormal.push_back(normal);
triRef.push_back(halfedgeRef[firstEdge]);
triBary.push_back(faceRef[face]);
for (int k : {0, 1, 2}) {
int index = linearSearch(mapping, tri[k]);
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
}
}
} else { // General triangulation
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
const PolygonsIdx polys = Face2Polygons(face, projection, faceEdge);
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);
std::vector<glm::ivec3> newTris = Triangulate(polys, precision_);
for (auto tri : newTris) {
triVerts.push_back(tri);
triNormal.push_back(normal);
triRef.push_back(halfedgeRef[firstEdge]);
triBary.push_back(faceRef[face]);
for (int k : {0, 1, 2}) {
triBary.back().vertBary[k] = vertBary[tri[k]];
}
}
}
}
@@ -138,8 +160,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.
*/
PolygonsIdx Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
const VecDH<int>& faceEdge) const {
Polygons Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
const VecDH<int>& faceEdge) const {
const int firstEdge = faceEdge[face];
const int lastEdge = faceEdge[face + 1];
@@ -151,7 +173,7 @@ PolygonsIdx Manifold::Impl::Face2Polygons(int face, glm::mat3x2 projection,
ASSERT(inserted, topologyErr, "face has duplicate vertices.");
}
PolygonsIdx polys;
Polygons polys;
int startEdge = 0;
int thisEdge = startEdge;
while (1) {
+182 -337
View File
@@ -19,10 +19,8 @@
#include <algorithm>
#include <atomic>
#include <map>
#include <numeric>
#include "graph.h"
#include "hashtable.h"
#include "par.h"
namespace {
@@ -43,12 +41,6 @@ __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); }
};
@@ -71,42 +63,6 @@ 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;
@@ -201,25 +157,45 @@ struct ReindexTriVerts {
}
};
struct InitializeTriRef {
struct InitializeBaryRef {
const int meshID;
const Halfedge* halfedge;
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
TriRef& baryRef = thrust::get<0>(inOut);
__host__ __device__ void operator()(thrust::tuple<BaryRef&, int> inOut) {
BaryRef& 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 HashTableD<uint32_t> meshIDold2new;
const int* meshIDold2new;
const int meshIDoffset;
__host__ __device__ void operator()(TriRef& ref) {
ref.meshID = meshIDold2new[ref.meshID];
__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;
}
};
@@ -227,7 +203,6 @@ struct CoplanarEdge {
float* triArea;
const Halfedge* halfedge;
const glm::vec3* vertPos;
const TriRef* triRef;
const glm::ivec3* triProp;
const float* prop;
const float* propTol;
@@ -235,17 +210,13 @@ struct CoplanarEdge {
const float precision;
__host__ __device__ void operator()(
thrust::tuple<thrust::pair<int, int>&, thrust::pair<int, int>&, int>
inOut) {
thrust::tuple<thrust::pair<int, int>&, int> inOut) {
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
thrust::pair<int, int>& vert2vert = thrust::get<1>(inOut);
const int edgeIdx = thrust::get<2>(inOut);
const int edgeIdx = thrust::get<1>(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;
@@ -253,23 +224,6 @@ 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;
@@ -282,8 +236,6 @@ 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;
@@ -311,14 +263,16 @@ struct CoplanarEdge {
pairVec + normal * scale * (pairProp - baseProp);
glm::vec3 cross = glm::cross(iJointVec, iEdgeVec);
const float areaP = glm::max(
const float area = glm::max(
glm::length(cross), glm::length(glm::cross(iPairVec, iJointVec)));
const float volumeP = glm::abs(glm::dot(cross, iPairVec));
const float volume = glm::abs(glm::dot(cross, iPairVec));
// Only operate on consistent triangles
if (volumeP > areaP * precision) return;
if (volume > area * precision) return;
}
}
triArea[edge.face] = area;
triArea[pair.face] = areaPair;
face2face.first = edge.face;
face2face.second = pair.face;
}
@@ -334,207 +288,44 @@ 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<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;
}
std::atomic<int> Manifold::Impl::meshIDCounter_(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 MeshRelationD& relation,
const std::vector<float>& propertyTolerance,
bool hasFaceIDs)
: vertPos_(mesh.vertPos),
halfedgeTangent_(mesh.halfedgeTangent),
meshRelation_(relation) {
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) {
VecDH<glm::ivec3> triVerts = mesh.triVerts;
if (!IsIndexInBounds(triVerts)) {
MarkFailure(Error::VertexOutOfBounds);
MarkFailure(Error::VERTEX_INDEX_OUT_OF_BOUNDS);
return;
}
RemoveUnreferencedVerts(triVerts);
CalculateBBox();
if (!IsFinite()) {
MarkFailure(Error::NonFiniteVertex);
MarkFailure(Error::NON_FINITE_VERTEX);
return;
}
SetPrecision();
CreateHalfedges(triVerts);
if (!IsManifold()) {
MarkFailure(Error::NotManifold);
MarkFailure(Error::NOT_MANIFOLD);
return;
}
CalculateNormals();
InitializeOriginal();
if (!hasFaceIDs) {
CreateFaces(propertyTolerance);
}
InitializeNewReference(triProperties, properties, propertyTolerance);
if (status_ != Error::NO_ERROR) return;
SimplifyTopology();
Finish();
@@ -548,14 +339,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}, //
@@ -571,7 +362,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}, //
@@ -587,9 +378,7 @@ Manifold::Impl::Impl(Shape shape) {
vertPos_ = vertPos;
CreateHalfedges(triVerts);
Finish();
meshRelation_.originalID = ReserveIDs(1);
InitializeOriginal();
CreateFaces();
InitializeNewReference();
}
void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
@@ -612,42 +401,67 @@ void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
ReindexTriVerts({vertOld2New.cptrD()}));
}
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());
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.
for_each_n(autoPolicy(NumTri()),
zip(meshRelation_.triRef.begin(), countAt(0)), NumTri(),
InitializeTriRef({meshID, halfedge_.cptrD()}));
meshRelation_.meshIDtransform.clear();
meshRelation_.meshIDtransform[meshID] = {meshID};
zip(meshRelation_.triBary.begin(), countAt(0)), NumTri(),
InitializeBaryRef({meshID, halfedge_.cptrD()}));
meshRelation_.originalID = meshID;
}
void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
VecDH<float> propertyToleranceD =
propertyTolerance.empty()
? VecDH<float>(meshRelation_.numProp, kTolerance)
: propertyTolerance;
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;
};
}
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(), 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_}));
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_}));
if (meshRelation_.triProperties.size() > 0) {
DedupePropVerts(meshRelation_.triProperties, vert2vert);
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);
}
std::vector<int> components;
const int numComponent = GetLabels(components, face2face, NumTri());
const int numComponent = ConnectedComponents(components, graph);
std::vector<int> comp2tri(numComponent, -1);
for (int tri = 0; tri < NumTri(); ++tri) {
@@ -659,9 +473,51 @@ void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
}
}
VecDH<TriRef>& triRef = meshRelation_.triRef;
for (int tri = 0; tri < NumTri(); ++tri)
triRef[tri].tri = comp2tri[components[tri]];
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;
}
/**
@@ -693,8 +549,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();
@@ -724,12 +580,7 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
result.precision_ = precision_;
result.bBox_ = bBox_;
result.halfedge_ = halfedge_;
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.halfedgeTangent_ = halfedgeTangent_;
result.vertPos_.resize(NumVert());
result.faceNormal_.resize(faceNormal_.size());
@@ -737,27 +588,12 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(),
Transform4x3({transform_}));
glm::mat3 normalTransform = NormalTransform(transform_);
glm::mat3 normalTransform =
glm::inverse(glm::transpose(glm::mat3(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();
@@ -774,8 +610,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());
@@ -785,13 +621,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() {
@@ -814,26 +650,35 @@ void Manifold::Impl::CalculateNormals() {
* Remaps all the contained meshIDs to new unique values to represent new
* instances of these meshes.
*/
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;
}
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);
const int numTri = NumTri();
for_each_n(autoPolicy(numTri), meshRelation_.triRef.begin(), numTri,
UpdateMeshID({meshIDold2new.D()}));
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}));
}
/**
* 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 {
+22 -35
View File
@@ -13,12 +13,9 @@
// 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"
@@ -28,24 +25,16 @@ namespace manifold {
/** @ingroup Private */
struct Manifold::Impl {
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.
VecDH<glm::vec3> barycentric;
VecDH<BaryRef> triBary;
/// The meshID 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::NoError;
Error status_ = Error::NO_ERROR;
VecDH<glm::vec3> vertPos_;
VecDH<Halfedge> halfedge_;
VecDH<glm::vec3> vertNormal_;
@@ -54,24 +43,27 @@ struct Manifold::Impl {
MeshRelationD meshRelation_;
Collider collider_;
static std::atomic<uint32_t> meshIDCounter_;
static uint32_t ReserveIDs(uint32_t);
static std::atomic<int> meshIDCounter_;
Impl() {}
enum class Shape { Tetrahedron, Cube, Octahedron };
enum class Shape { TETRAHEDRON, CUBE, OCTAHEDRON };
Impl(Shape);
Impl(const MeshGL&, std::vector<float> propertyTolerance = {});
Impl(const Mesh&, const MeshRelationD& relation,
const std::vector<float>& propertyTolerance = {},
bool hasFaceIDs = false);
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>());
void CreateFaces(const std::vector<float>& propertyTolerance = {});
void RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts);
void InitializeOriginal();
void ReinitializeReference(int meshID);
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
void CalculateNormals();
void IncrementMeshIDs();
void IncrementMeshIDs(int start, int length);
void Update();
void MarkFailure(Error status);
@@ -83,11 +75,6 @@ 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;
@@ -105,16 +92,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<TriRef>& halfedgeRef);
PolygonsIdx Face2Polygons(int face, glm::mat3x2 projection,
const VecDH<int>& faceEdge) const;
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;
// edge_op.cu
void SimplifyTopology();
@@ -129,7 +116,7 @@ struct Manifold::Impl {
// smoothing.cu
void CreateTangents(const std::vector<Smoothness>&);
VecDH<Barycentric> Subdivide(int n);
MeshRelationD Subdivide(int n);
void Refine(int n);
};
} // namespace manifold
+87 -211
View File
@@ -12,10 +12,6 @@
// 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"
@@ -79,49 +75,44 @@ 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) {
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
pNode_ =
std::make_shared<CsgLeafNode>(std::make_shared<Impl>(mesh, relation));
}
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))) {}
/**
* This returns a Mesh of simple vectors of vertices and triangles suitable for
@@ -147,34 +138,29 @@ Mesh Manifold::GetMesh() const {
return result;
}
/**
* 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 {
MeshGL Manifold::GetMeshGL() const {
const Impl& impl = *GetCsgLeafNode().GetImpl();
const int numProp = NumProp();
const int numVert = NumPropVert();
const int numVert = NumVert();
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.numProp = 3 + numProp;
out.vertPos.resize(3 * numVert);
out.vertNormal.resize(3 * numVert);
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) {
@@ -185,124 +171,6 @@ MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) 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;
}
@@ -373,9 +241,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 NoError,
* combined into a new Manifold via operations, the status reverts to NO_ERROR,
* simply processing the problem mesh as empty. Likewise, empty meshes may still
* show NoError, for instance if they are small enough relative to their
* show NO_ERROR, for instance if they are small enough relative to their
* precision to be collapsed to nothing.
*/
Manifold::Error Manifold::Status() const {
@@ -393,18 +261,6 @@ 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.
@@ -454,6 +310,30 @@ 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
@@ -464,30 +344,26 @@ int Manifold::OriginalID() const {
}
/**
* 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.
* 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.
*/
Manifold Manifold::AsOriginal() const {
auto newImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
newImpl->meshRelation_.originalID = ReserveIDs(1);
newImpl->InitializeOriginal();
newImpl->CreateFaces();
newImpl->InitializeNewReference();
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.
*/
@@ -648,7 +524,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);
}
/**
@@ -663,7 +539,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);
}
/**
@@ -678,7 +554,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);
}
/**
@@ -700,11 +576,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));
}
+18 -59
View File
@@ -45,11 +45,6 @@ __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.
@@ -83,43 +78,34 @@ __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]));
const int longSide = d2[0] > d2[1] && d2[0] > d2[2] ? 0
: d2[1] > d2[2] ? 1
: 2;
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);
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
if (d2[longside] < tol2) { // point
return glm::vec3(1, 0, 0);
} 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);
} 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);
}
uvw /= (uvw[0] + uvw[1] + uvw[2]);
return uvw;
} else { // line
const int nextV = Next3(longSide);
int nextside = longside + 1;
if (nextside > 2) nextside -= 3;
const float 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;
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;
return uvw;
}
}
@@ -139,24 +125,6 @@ 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.
@@ -223,14 +191,5 @@ 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
+99 -73
View File
@@ -33,23 +33,10 @@ __host__ __device__ int VertsPerTri(int edgeVerts) {
return (edgeVerts * edgeVerts + edgeVerts) / 2;
}
/**
* 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 Barycentric {
int tri;
glm::vec3 uvw;
};
struct ReindexHalfedge {
int* half2Edge;
@@ -64,7 +51,6 @@ struct ReindexHalfedge {
struct EdgeVerts {
glm::vec3* vertPos;
Barycentric* vertBary;
const int startIdx;
const int n;
@@ -73,32 +59,35 @@ struct EdgeVerts {
TmpEdge edgeVerts = thrust::get<1>(in);
float invTotal = 1.0f / n;
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};
}
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;
}
};
struct InteriorVerts {
glm::vec3* vertPos;
Barycentric* vertBary;
glm::vec3* uvw;
BaryRef* triBary;
glm::vec3* uvwNew;
BaryRef* triBaryNew;
const glm::vec3* uvwOld;
const int startIdx;
const int n;
const Halfedge* halfedge;
__host__ __device__ void operator()(int tri) {
__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);
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) {
@@ -106,13 +95,34 @@ struct InteriorVerts {
const float u = invTotal * j;
const float v = invTotal * k;
const float w = invTotal * i;
if (i == 0 || j == 0 || k == 0 || j == n - i) continue;
const int first = posBary;
uvw[posBary] = {u, v, w};
uvwNew[posBary] = uvwOldTri * uvw[posBary];
++posBary;
if (j == n - i) continue;
vertPos[pos] = u * vertPos[halfedge[3 * tri].startVert] + //
v * vertPos[halfedge[3 * tri + 1].startVert] + //
w * vertPos[halfedge[3 * tri + 2].startVert];
// 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};
}
vertBary[pos++] = {tri, {u, v, w}};
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];
}
}
}
@@ -212,6 +222,24 @@ 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;
@@ -337,14 +365,14 @@ void Manifold::Impl::CreateTangents(
vertNormal_.cptrD(), halfedge_.cptrD()}));
if (!sharpenedEdges.empty()) {
const VecDH<TriRef>& triRef = meshRelation_.triRef;
const VecDH<BaryRef>& triBary = meshRelation_.triBary;
// 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 = triRef[tri].tri;
int oldTri = triBary[tri].tri;
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
}
@@ -431,22 +459,24 @@ void Manifold::Impl::CreateTangents(
* run after the new vertices have moved, which is a likely scenario after
* refinement (smoothing).
*/
VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
if (n < 2) return VecDH<Barycentric>();
Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
if (n < 2) return meshRelation_;
faceNormal_.resize(0);
vertNormal_.resize(0);
const int numVert = NumVert();
const int numEdge = NumEdge();
const int numTri = NumTri();
int numVert = NumVert();
int numEdge = NumEdge();
int numTri = NumTri();
// Append new verts
const int vertsPerEdge = n - 1;
const int triVertStart = numVert + numEdge * vertsPerEdge;
int vertsPerEdge = n - 1;
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_.triRef.resize(n * n * numTri);
meshRelation_.barycentric.resize(relation.barycentric.size());
meshRelation_.triBary.resize(relation.triBary.size());
meshRelation_.originalID = oldMeshRelation.originalID;
VecDH<TmpEdge> edges = CreateTmpEdges(halfedge_);
@@ -455,40 +485,36 @@ VecDH<Barycentric> 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(), vertBary.ptrD(), numVert, n}));
for_each_n(policy, countAt(0), numTri,
InteriorVerts({vertPos_.ptrD(), vertBary.ptrD(), triVertStart, n,
halfedge_.cptrD()}));
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()}));
// 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);
// 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;
return relation;
}
void Manifold::Impl::Refine(int n) {
Manifold::Impl old = *this;
VecDH<Barycentric> vertBary = Subdivide(n);
if (vertBary.size() == 0) return;
MeshRelationD relation = Subdivide(n);
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
for_each_n(autoPolicy(NumTri()), zip(vertPos_.begin(), vertBary.begin()),
NumVert(),
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(),
InterpTri({old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
old.vertPos_.cptrD()}));
}
+10 -86
View File
@@ -119,42 +119,6 @@ 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));
@@ -163,7 +127,7 @@ void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
tmp.begin(), inOut.begin());
}
template void Permute<TriRef>(VecDH<TriRef>&, const VecDH<int>&);
template void Permute<BaryRef>(VecDH<BaryRef>&, const VecDH<int>&);
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
struct ReindexFace {
@@ -208,7 +172,7 @@ void Manifold::Impl::Finish() {
SetPrecision(precision_);
if (!bBox_.IsFinite()) {
// Decimated out of existence - early out.
MarkFailure(Error::NoError);
MarkFailure(Error::NO_ERROR);
return;
}
@@ -218,7 +182,6 @@ 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!");
@@ -239,8 +202,8 @@ void Manifold::Impl::Finish() {
"Halfedge index is negative!");
ASSERT(extrema.pairedHalfedge < 2 * NumEdge(), topologyErr,
"Halfedge index exceeds number of halfedges!");
ASSERT(meshRelation_.triRef.size() == NumTri() ||
meshRelation_.triRef.size() == 0,
ASSERT(meshRelation_.triBary.size() == NumTri() ||
meshRelation_.triBary.size() == 0,
logicErr, "Mesh Relation doesn't fit!");
ASSERT(faceNormal_.size() == NumTri() || faceNormal_.size() == 0, logicErr,
"faceNormal size = " + std::to_string(faceNormal_.size()) +
@@ -300,32 +263,6 @@ 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
@@ -373,10 +310,9 @@ void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
*/
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
const int numTri = faceNew2Old.size();
if (meshRelation_.triRef.size() == NumTri())
Permute(meshRelation_.triRef, faceNew2Old);
if (meshRelation_.triProperties.size() == NumTri())
Permute(meshRelation_.triProperties, faceNew2Old);
if (meshRelation_.triBary.size() == NumTri())
Permute(meshRelation_.triBary, faceNew2Old);
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
VecDH<Halfedge> oldHalfedge(std::move(halfedge_));
@@ -397,23 +333,11 @@ 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_.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;
}
old.meshRelation_.triBary.begin(), meshRelation_.triBary.begin());
meshRelation_.barycentric = old.meshRelation_.barycentric;
if (old.faceNormal_.size() == old.NumTri()) {
faceNormal_.resize(numTri);
+2 -2
View File
@@ -15,7 +15,7 @@
project (polygon)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
add_library(${PROJECT_NAME} ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME}
PUBLIC ${PROJECT_SOURCE_DIR}/include
@@ -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_17)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
+1 -15
View File
@@ -22,21 +22,7 @@ namespace manifold {
/** @addtogroup Private
* @{
*/
/**
* 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,
std::vector<glm::ivec3> Triangulate(const Polygons &polys,
float precision = -1);
ExecutionParams &PolygonParams();
+67 -64
View File
@@ -29,14 +29,17 @@ 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) {
ASSERT(condition, geometryErr, msg);
if (!params.processOverlaps && !condition) {
std::ostringstream output;
output << "Error in file: " << file << " (" << line << "): \'" << cond
<< "\' is false: " << msg;
throw geometryErr(output.str());
}
return condition;
}
@@ -63,8 +66,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 PolygonsIdx &polys) {
std::vector<PolyEdge> halfedges;
std::vector<Polyedge> Polygons2Edges(const Polygons &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});
@@ -74,9 +77,9 @@ std::vector<PolyEdge> Polygons2Edges(const PolygonsIdx &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]});
@@ -85,26 +88,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);
};
@@ -126,17 +129,17 @@ void CheckTopology(const std::vector<PolyEdge> &halfedges) {
}
void CheckTopology(const std::vector<glm::ivec3> &triangles,
const PolygonsIdx &polys) {
std::vector<PolyEdge> halfedges = Triangles2Edges(triangles);
std::vector<PolyEdge> openEdges = Polygons2Edges(polys);
for (PolyEdge e : openEdges) {
const Polygons &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 PolygonsIdx &polys, float precision) {
const Polygons &polys, float precision) {
std::map<int, glm::vec2> vertPos;
for (const auto &poly : polys) {
for (int i = 0; i < poly.size(); ++i) {
@@ -151,7 +154,7 @@ void CheckGeometry(const std::vector<glm::ivec3> &triangles,
geometryErr, "triangulation is not entirely CCW!");
}
void Dump(const PolygonsIdx &polys) {
void Dump(const Polygons &polys) {
for (auto poly : polys) {
std::cout << "polys.push_back({" << std::setprecision(9) << std::endl;
for (auto v : poly) {
@@ -169,7 +172,7 @@ void Dump(const PolygonsIdx &polys) {
}
}
void PrintFailure(const std::exception &e, const PolygonsIdx &polys,
void PrintFailure(const std::exception &e, const Polygons &polys,
std::vector<glm::ivec3> &triangles, float precision) {
std::cout << "-----------------------------------" << std::endl;
std::cout << "Triangulation failed! Precision = " << precision << std::endl;
@@ -193,10 +196,10 @@ void PrintFailure(const std::exception &e, const PolygonsIdx &polys,
*/
class Monotones {
public:
Monotones(const PolygonsIdx &polys, float precision) : precision_(precision) {
Monotones(const Polygons &polys, float precision) : precision_(precision) {
VertItr start, last, current;
float bound = 0;
for (const SimplePolygonIdx &poly : polys) {
for (const SimplePolygon &poly : polys) {
for (int i = 0; i < poly.size(); ++i) {
monotones_.push_back({poly[i].pos, //
poly[i].idx, //
@@ -272,7 +275,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});
@@ -306,7 +309,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
@@ -486,8 +489,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 {
@@ -515,46 +518,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;
}
}
}
@@ -570,12 +573,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 line.
* a polyline.
*/
int IsHole(VertItr vert) const {
VertItr left = vert->left;
@@ -819,7 +822,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});
@@ -836,13 +839,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(
@@ -865,23 +868,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;
}
@@ -944,29 +947,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.
@@ -996,7 +999,7 @@ class Monotones {
}
break;
}
case Skip:
case SKIP:
break;
}
@@ -1042,7 +1045,7 @@ namespace manifold {
* @return std::vector<glm::ivec3> The triangles, referencing the original
* vertex indicies.
*/
std::vector<glm::ivec3> Triangulate(const PolygonsIdx &polys, float precision) {
std::vector<glm::ivec3> Triangulate(const Polygons &polys, float precision) {
std::vector<glm::ivec3> triangles;
try {
Monotones monotones(polys, precision);
+1 -1
View File
@@ -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)
+103 -45
View File
@@ -14,16 +14,37 @@
#pragma once
#include "hashtable.h"
#include "optional"
#include "par.h"
#include "public.h"
#include "utils.h"
#include "vec_dh.h"
namespace {
using namespace manifold;
__host__ __device__ Uint64 identity(Uint64 x) { return x; }
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__ glm::ivec3 TetTri0(int i) {
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
@@ -114,6 +135,7 @@ __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};
@@ -124,11 +146,73 @@ 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<GridVert, identity> gridVerts;
HashTableD gridVerts;
const Func sdf;
const glm::vec3 origin;
const glm::ivec3 gridSize;
@@ -163,6 +247,7 @@ struct ComputeVerts {
const glm::vec3 position = Position(gridIndex);
GridVert gridVert;
gridVert.key = mortonCode;
gridVert.distance = BoundedSDF(gridIndex);
bool keep = false;
@@ -185,14 +270,14 @@ struct ComputeVerts {
gridVert.edgeVerts[i] = idx;
}
if (keep) gridVerts.Insert(mortonCode, gridVert);
if (keep) gridVerts.Insert(gridVert);
}
};
struct BuildTris {
glm::ivec3* triVerts;
int* triIndex;
const HashTableD<GridVert, identity> gridVerts;
const HashTableD gridVerts;
__host__ __device__ void CreateTri(const glm::ivec3& tri,
const int edges[6]) {
@@ -210,11 +295,10 @@ struct BuildTris {
}
__host__ __device__ void operator()(int idx) {
Uint64 basekey = gridVerts.KeyAt(idx);
if (basekey == kOpen) return;
const GridVert& base = gridVerts.At(idx);
const glm::ivec4 baseIndex = DecodeMorton(basekey);
if (base.key == kOpen) return;
const glm::ivec4 baseIndex = DecodeMorton(base.key);
glm::ivec4 leadIndex = baseIndex;
if (leadIndex.w == 0)
@@ -271,21 +355,6 @@ 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 {
@@ -319,8 +388,7 @@ 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,
std::optional<ExecutionPolicy> policy = std::nullopt) {
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
Mesh out;
const glm::vec3 dim = bounds.Size();
@@ -329,27 +397,17 @@ 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));
// 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();
const auto policy = autoPolicy(maxMorton);
int tableSize = glm::min(
2 * maxMorton, static_cast<Uint64>(10 * glm::pow(maxMorton, 0.667)));
HashTable<GridVert, identity> gridVerts(tableSize);
HashTable gridVerts(tableSize);
VecDH<glm::vec3> vertPos(gridVerts.Size() * 7);
while (1) {
VecDH<int> index(1, 0);
// avoid handing dynamic function pointers to CUDA
for_each_n_wrapper<Func>(
pol, maxMorton + 1,
for_each_n(
policy, countAt(0), maxMorton + 1,
ComputeVerts<Func>({vertPos.ptrD(), index.ptrD(), gridVerts.D(), sdf,
bounds.min, gridSize + 1, spacing, level}));
@@ -362,7 +420,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
tableSize *= 2;
else
tableSize *= ratio;
gridVerts = HashTable<GridVert, identity>(tableSize);
gridVerts = HashTable(tableSize);
vertPos = VecDH<glm::vec3>(gridVerts.Size() * 7);
} else { // Success
vertPos.resize(index[0]);
@@ -373,7 +431,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(pol, countAt(0), gridVerts.Size(),
for_each_n(policy, countAt(0), gridVerts.Size(),
BuildTris({triVerts.ptrD(), index.ptrD(), gridVerts.D()}));
triVerts.resize(index[0]);
@@ -382,4 +440,4 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
return out;
}
/** @} */
} // namespace manifold
} // namespace manifold
+2 -2
View File
@@ -1,10 +1,10 @@
project (graphlite)
add_library(${PROJECT_NAME} OBJECT src/connected_components.cpp)
add_library(${PROJECT_NAME} 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)
+1 -1
View File
@@ -15,7 +15,7 @@
project(utilities)
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
add_library(${PROJECT_NAME} ${SOURCE_FILES})
message("CUDA Support: ${MANIFOLD_USE_CUDA}")
message("Parallel Backend: ${MANIFOLD_PAR}")
-159
View File
@@ -1,159 +0,0 @@
// Copyright 2022 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <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
+7 -3
View File
@@ -49,6 +49,10 @@ 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,
@@ -56,12 +60,12 @@ enum class ExecutionPolicy {
inline ExecutionPolicy autoPolicy(int size) {
// some random numbers
if (size <= (1 << 12)) {
return ExecutionPolicy::Seq;
return Seq;
}
if (size <= (1 << 16) || !CudaEnabled()) {
return ExecutionPolicy::Par;
return Par;
}
return ExecutionPolicy::ParUnseq;
return ParUnseq;
}
#ifdef MANIFOLD_USE_CUDA
+123 -78
View File
@@ -89,6 +89,11 @@ 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.
@@ -117,12 +122,22 @@ 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
* [&epsilon;-valid](https://github.com/elalish/manifold/wiki/Manifold-Library#definition-of-%CE%B5-valid).
*/
using SimplePolygon = std::vector<glm::vec2>;
using SimplePolygon = std::vector<PolyVert>;
/**
* Set of polygons with holes. Order of contours is arbitrary. Can contain any
@@ -133,6 +148,22 @@ using SimplePolygon = std::vector<glm::vec2>;
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.
*/
@@ -151,81 +182,45 @@ 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;
};
/**
* 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() = 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_) {}
/// 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];
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);
}
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 < 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]};
}
}
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];
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]};
}
}
}
};
@@ -261,15 +256,59 @@ struct Curvature {
};
/**
* Connected component indices and number of discrete components. Intermediate
* step for Manifold.Decompose(), completed by Manifold.GetComponents()
* Part of MeshRelation - represents a single triangle relation to an original
* Mesh.
*/
struct Components {
std::vector<int> indices;
int numComponents;
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;
};
/** @} */
/**
* @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
@@ -464,7 +503,7 @@ struct ExecutionParams {
bool verbose = false;
/// If processOverlaps is false, a geometric check will be performed to assert
/// all triangles are CCW.
bool processOverlaps = true;
bool processOverlaps = false;
/// Suppresses printed errors regarding CW triangles. Has no effect if
/// processOverlaps is true.
bool suppressErrors = false;
@@ -502,6 +541,12 @@ 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.
-10
View File
@@ -103,16 +103,6 @@ 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__
+4 -18
View File
@@ -18,18 +18,9 @@ add_subdirectory(third_party)
enable_testing()
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()
set(SOURCE_FILES polygon_test.cpp mesh_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold sdf samples samplesGPU)
if(MANIFOLD_CBIND)
target_link_libraries(${PROJECT_NAME} manifoldc)
endif()
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold samples samplesGPU)
if(MANIFOLD_EXPORT)
add_subdirectory(meshIO)
@@ -38,17 +29,12 @@ if(MANIFOLD_EXPORT)
endif()
if(MANIFOLD_USE_CUDA)
set(CUDA_FILES test_main.cpp sdf_test.cpp)
if(MANIFOLD_CBIND)
list(APPEND CUDA_FILES manifoldc_test.cpp)
endif()
set_source_files_properties(${CUDA_FILES} PROPERTIES LANGUAGE CUDA)
set_source_files_properties(mesh_test.cpp sdf_test.cpp PROPERTIES LANGUAGE CUDA)
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
endif()
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
add_test(test_all ${PROJECT_NAME})
-591
View File
@@ -1,591 +0,0 @@
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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);
}
-492
View File
@@ -1,492 +0,0 @@
// Copyright 2021 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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);
}
-154
View File
@@ -1,154 +0,0 @@
#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]);
}
}
+1 -1
View File
@@ -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_17)
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
+1 -10
View File
@@ -41,14 +41,8 @@ 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. Only used with Mesh export, not MeshGL.
/// linear, not sRGB.
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);
};
/**
@@ -66,9 +60,6 @@ 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
+45 -158
View File
@@ -19,75 +19,11 @@
#include "assimp/Exporter.hpp"
#include "assimp/Importer.hpp"
#include "assimp/material.h"
#include "assimp/pbrmaterial.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 {
/**
@@ -155,95 +91,6 @@ 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.
@@ -267,11 +114,38 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
return;
}
std::string type = GetType(filename);
const bool isYup = type == "glb2" || type == "gltf2";
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;
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];
@@ -312,7 +186,20 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[i][j];
}
ExportScene(scene, 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, ext, filename);
delete scene;
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
}
} // namespace manifold
+1086
View File
File diff suppressed because it is too large Load Diff
+63 -90
View File
@@ -24,22 +24,22 @@ namespace {
using namespace manifold;
void StandardizePoly(SimplePolygonIdx &p) {
void StandardizePoly(SimplePolygon &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(PolygonsIdx &polys) {
void StandardizePolys(Polygons &polys) {
for (auto &p : polys) StandardizePoly(p);
std::sort(polys.begin(), polys.end(),
[](SimplePolygonIdx &p1, SimplePolygonIdx &p2) {
[](SimplePolygon &p1, SimplePolygon &p2) {
return p1[0].idx < p2[0].idx;
});
}
void Identical(PolygonsIdx p1, PolygonsIdx p2) {
void Identical(Polygons p1, Polygons p2) {
ASSERT_EQ(p1.size(), p2.size());
StandardizePolys(p1);
StandardizePolys(p2);
@@ -51,8 +51,8 @@ void Identical(PolygonsIdx p1, PolygonsIdx p2) {
}
}
PolygonsIdx Turn180(PolygonsIdx polys) {
for (SimplePolygonIdx &poly : polys) {
Polygons Turn180(Polygons polys) {
for (SimplePolygon &poly : polys) {
for (PolyVert &vert : poly) {
vert.pos *= -1;
}
@@ -60,11 +60,11 @@ PolygonsIdx Turn180(PolygonsIdx polys) {
return polys;
}
PolygonsIdx Duplicate(PolygonsIdx polys) {
Polygons Duplicate(Polygons polys) {
float xMin = std::numeric_limits<float>::infinity();
float xMax = -std::numeric_limits<float>::infinity();
int indexMax = 0;
for (SimplePolygonIdx &poly : polys) {
for (SimplePolygon &poly : polys) {
for (PolyVert &vert : poly) {
xMin = std::min(xMin, vert.pos.x);
xMax = std::max(xMax, vert.pos.x);
@@ -76,7 +76,7 @@ PolygonsIdx Duplicate(PolygonsIdx polys) {
const int nPolys = polys.size();
for (int i = 0; i < nPolys; ++i) {
SimplePolygonIdx poly = polys[i];
SimplePolygon poly = polys[i];
for (PolyVert &vert : poly) {
vert.pos.x += shift;
vert.idx += indexMax;
@@ -86,7 +86,7 @@ PolygonsIdx Duplicate(PolygonsIdx polys) {
return polys;
}
void TestPoly(const PolygonsIdx &polys, int expectedNumTri,
void TestPoly(const Polygons &polys, int expectedNumTri,
float precision = -1.0f) {
PolygonParams().verbose = options.params.verbose;
@@ -94,11 +94,11 @@ void TestPoly(const PolygonsIdx &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 PolygonsIdx &polys, int expectedNumTri,
* tolerance.
*/
TEST(Polygon, SimpleHole) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0, -2), 0}, //
{glm::vec2(2, 2), 1}, //
@@ -126,7 +126,7 @@ TEST(Polygon, SimpleHole) {
}
TEST(Polygon, SimpleHole2) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-7, 0), 0}, //
{glm::vec2(-6, 3), 1}, //
@@ -163,7 +163,7 @@ TEST(Polygon, MultiMerge) {
}
TEST(Polygon, Colinear) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0, 0), 0}, //
{glm::vec2(1, 1), 1}, //
@@ -333,7 +333,7 @@ TEST(Polygon, ColinearY) {
}
TEST(Polygon, Concave) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(4, 0), 1}, //
{glm::vec2(3, 2), 3}, //
@@ -368,7 +368,7 @@ TEST(Polygon, Concave2) {
}
TEST(Polygon, Sliver) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons 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) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-60, 0), 19}, //
{glm::vec2(-50, 0), 21}, //
@@ -504,7 +504,7 @@ TEST(Polygon, Sliver5) {
}
TEST(Polygon, Sliver6) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(10, 0), 5}, //
{glm::vec2(0, 10), 9}, //
@@ -517,7 +517,7 @@ TEST(Polygon, Sliver6) {
}
TEST(Polygon, Sliver7) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(50, -10), 0}, //
{glm::vec2(60, 0), 25}, //
@@ -531,35 +531,8 @@ 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) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(11.7864399, -7.4572401), 4176}, //
{glm::vec2(11.6818037, -7.30982304), 24873}, //
@@ -572,7 +545,7 @@ TEST(Polygon, Colinear2) {
}
TEST(Polygon, Split) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-0.707106769, -0.707106769), 1}, //
{glm::vec2(1, 0), 14}, //
@@ -588,7 +561,7 @@ TEST(Polygon, Split) {
}
TEST(Polygon, Duplicates) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-15, -8.10255623), 1648}, //
{glm::vec2(-15, -9.02439785), 1650}, //
@@ -601,7 +574,7 @@ TEST(Polygon, Duplicates) {
}
TEST(Polygon, Simple1) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(4.04059982, -4.01843977), 2872}, //
{glm::vec2(3.95867562, -4.25263977), 24604}, //
@@ -613,7 +586,7 @@ TEST(Polygon, Simple1) {
}
TEST(Polygon, Simple2) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-1, -1), 1}, //
{glm::vec2(-0.5, -0.5), 9}, //
@@ -627,7 +600,7 @@ TEST(Polygon, Simple2) {
}
TEST(Polygon, Simple3) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(19.7193489, 6.15445995), 19798}, //
{glm::vec2(20.2308197, 5.64299059), 31187}, //
@@ -640,7 +613,7 @@ TEST(Polygon, Simple3) {
}
TEST(Polygon, Simple4) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(15, -12.7135563), 287}, //
{glm::vec2(15, -10.6843739), 288}, //
@@ -656,7 +629,7 @@ TEST(Polygon, Simple4) {
}
TEST(Polygon, Simple5) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-27.3845081, 0.375669748), 364}, //
{glm::vec2(-27.6389656, 0), 365}, //
@@ -671,7 +644,7 @@ TEST(Polygon, Simple5) {
}
TEST(Polygon, Simple6) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-7.99813318, 12.8888826), 25009}, //
{glm::vec2(-7.85714436, 12.9125195), 25006}, //
@@ -683,7 +656,7 @@ TEST(Polygon, Simple6) {
}
TEST(Polygon, TouchingHole) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-2, -1), 0}, //
{glm::vec2(2, -1), 1}, //
@@ -700,7 +673,7 @@ TEST(Polygon, TouchingHole) {
}
TEST(Polygon, Degenerate) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(1, -1), 0}, //
{glm::vec2(1, 1), 1}, //
@@ -714,7 +687,7 @@ TEST(Polygon, Degenerate) {
}
TEST(Polygon, Degenerate2) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0.0740740597, -0.314814836), 4829}, //
{glm::vec2(0.0925925896, -0.314814806), 4828}, //
@@ -725,7 +698,7 @@ TEST(Polygon, Degenerate2) {
}
TEST(Polygon, Degenerate3) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-0.413580239, -0.216049403), 49696}, //
{glm::vec2(-0.42592591, -0.216049403), 49690}, //
@@ -739,7 +712,7 @@ TEST(Polygon, Degenerate3) {
}
TEST(Polygon, Degenerate4) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0, 10), 213}, //
{glm::vec2(-0.0696326792, 9.99390793), 360}, //
@@ -750,7 +723,7 @@ TEST(Polygon, Degenerate4) {
}
TEST(Polygon, Degenerate5) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(1, 0), 3}, //
{glm::vec2(1, 1), 7}, //
@@ -765,7 +738,7 @@ TEST(Polygon, Degenerate5) {
}
TEST(Polygon, Degenerate6) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(4.37113897e-07, -4.37113897e-07), 0}, //
{glm::vec2(4.37113897e-07, 0), 18}, //
@@ -779,7 +752,7 @@ TEST(Polygon, Degenerate6) {
}
TEST(Polygon, Tricky) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(1, 0), 0}, //
{glm::vec2(2, 1), 1}, //
@@ -795,7 +768,7 @@ TEST(Polygon, Tricky) {
}
TEST(Polygon, Tricky2) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(1, 0), 0}, //
{glm::vec2(3, 1), 1}, //
@@ -812,7 +785,7 @@ TEST(Polygon, Tricky2) {
}
TEST(Polygon, Slit) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(27.7069321, 13.5144091), 286}, //
{glm::vec2(28.664566, 10.8102894), 267}, //
@@ -834,7 +807,7 @@ TEST(Polygon, Slit) {
}
TEST(Polygon, SharedEdge) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0.265432119, 0.0061728349), 61185}, //
{glm::vec2(0.277777791, -3.7252903e-09), 61180}, //
@@ -847,7 +820,7 @@ TEST(Polygon, SharedEdge) {
}
TEST(Polygon, Precision) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-0.98486793, -0.492948532), 0}, //
{glm::vec2(-0.984859049, -0.492013603), 1}, //
@@ -865,7 +838,7 @@ TEST(Polygon, Precision2) {
const bool intermediateChecks = PolygonParams().intermediateChecks;
PolygonParams().intermediateChecks = false;
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(4.98093176, -0.247938812), 11113}, //
{glm::vec2(4.94630527, -0.0826399028), 22736}, //
@@ -893,7 +866,7 @@ TEST(Polygon, Precision2) {
};
TEST(Polygon, Comb) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0.462962955, -0.427297652), 18}, //
{glm::vec2(0.5, -0.42592591), 17}, //
@@ -939,7 +912,7 @@ TEST(Polygon, Comb) {
}
TEST(Polygon, Comb2) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-0.5, -0.462962955), 2}, //
{glm::vec2(-0.462962955, -0.461591214), 4}, //
@@ -985,7 +958,7 @@ TEST(Polygon, Comb2) {
}
TEST(Polygon, PointPoly) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0, -15.9780979), 32}, //
{glm::vec2(5.08144999, -14.2678728), 244}, //
@@ -1018,7 +991,7 @@ TEST(Polygon, PointPoly) {
};
TEST(Polygon, KissingZigzag) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(37.4667358, 0), 0}, //
{glm::vec2(27.8904877, -3.04520559), 1}, //
@@ -1133,7 +1106,7 @@ TEST(Polygon, KissingZigzag) {
}
TEST(Polygon, Sponge) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(-0.5, -0.5), 22}, //
{glm::vec2(-0.388888896, -0.388888896), 23}, //
@@ -1191,7 +1164,7 @@ TEST(Polygon, Sponge) {
}
TEST(Polygon, SquareHoles) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0.388888896, -0.277777791), 10}, //
{glm::vec2(0.388888896, -0.388888896), 8}, //
@@ -1256,7 +1229,7 @@ TEST(Polygon, SquareHoles) {
}
TEST(Polygon, BigSponge) {
PolygonsIdx polys;
Polygons polys;
polys.push_back({
{glm::vec2(0.5, 0.5), 1}, //
{glm::vec2(0.487654328, 0.487654328), 13834}, //
+8 -11
View File
@@ -23,6 +23,14 @@
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);
@@ -54,7 +62,6 @@ 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.
@@ -72,7 +79,6 @@ 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) {
@@ -108,7 +114,6 @@ 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) {
@@ -132,8 +137,6 @@ 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}));
@@ -150,7 +153,6 @@ 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(), {});
@@ -162,7 +164,6 @@ 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
@@ -175,7 +176,6 @@ 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,7 +187,6 @@ 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();
@@ -205,7 +204,6 @@ 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(), {});
@@ -223,7 +221,6 @@ 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());
+3 -3
View File
@@ -66,7 +66,7 @@ TEST(SDF, Bounds) {
if (options.exportModels) ExportMesh("cubeVoid.gltf", levelSet, {});
#endif
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NoError);
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NO_ERROR);
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::NoError);
EXPECT_EQ(cubeVoid.Status(), Manifold::Error::NO_ERROR);
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::NoError);
EXPECT_EQ(layers.Status(), Manifold::Error::NO_ERROR);
EXPECT_EQ(layers.Genus(), -8);
}
+1 -27
View File
@@ -14,37 +14,11 @@
#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;
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);
extern Options options;
+1 -308
View File
@@ -14,11 +14,8 @@
#include "manifold.h"
#include "polygon.h"
#include "sdf.h"
#include "test.h"
using namespace manifold;
Options options;
void print_usage() {
@@ -31,7 +28,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++) {
@@ -65,310 +62,6 @@ 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());
}