Compare commits

..
1 Commits
Author SHA1 Message Date
Emmett Lalish 67a5b7140d basic example working 2022-11-16 00:01:08 +01:00
61 changed files with 1840 additions and 4177 deletions
+7 -39
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,12 +12,11 @@ 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:11.6.0-devel-ubuntu20.04
@@ -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_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,36 +67,7 @@ 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:11.6.0-devel-ubuntu20.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-20.04
if: github.event.pull_request.draft == false
steps:
@@ -141,7 +111,6 @@ jobs:
retention-days: 7
build_windows:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE]
@@ -175,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}}
@@ -185,7 +154,6 @@ jobs:
./Release/manifold_test.exe
build_mac:
timeout-minutes: 30
strategy:
matrix:
parallel_backend: [NONE, TBB]
@@ -207,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]
@@ -224,3 +191,4 @@ jobs:
- uses: actions/checkout@v3
- uses: cachix/install-nix-action@v15
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
+1 -11
View File
@@ -104,7 +104,7 @@
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DBUILD_SHARED_LIBS=ON",
"-DMANIFOLD_EXPORT=ON",
"-DMANIFOLD_EXPORT=OFF",
"-DMANIFOLD_DEBUG=ON",
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE",
@@ -118,14 +118,4 @@
},
"editor.tabSize": 2,
"editor.detectIndentation": false,
"cSpell.words": [
"Bary",
"csaszar",
"Gyroid",
"halfedge",
"halfedges",
"Tris",
"Verts"
],
"javascript.format.semicolons": "insert",
}
+7 -20
View File
@@ -18,11 +18,9 @@ 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\"")
if(EMSCRIPTEN)
@@ -31,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)
@@ -85,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 = NO_ERROR;
switch (error) {
case Manifold::Error::NO_ERROR:
e = NO_ERROR;
break;
case Manifold::Error::NON_FINITE_VERTEX:
e = NON_FINITE_VERTEX;
break;
case Manifold::Error::NOT_MANIFOLD:
e = NOT_MANIFOLD;
break;
case Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS:
e = VERTEX_INDEX_OUT_OF_BOUNDS;
break;
case Manifold::Error::PROPERTIES_WRONG_LENGTH:
e = PROPERTIES_WRONG_LENGTH;
break;
case Manifold::Error::MISSING_POSITION_PROPERTIES:
e = MISSING_POSITION_PROPERTIES;
break;
case Manifold::Error::MERGE_VECTORS_DIFFERENT_LENGTHS:
e = MERGE_VECTORS_DIFFERENT_LENGTHS;
break;
case Manifold::Error::MERGE_INDEX_OUT_OF_BOUNDS:
e = MERGE_INDEX_OUT_OF_BOUNDS;
break;
case Manifold::Error::TRANSFORM_WRONG_LENGTH:
e = TRANSFORM_WRONG_LENGTH;
break;
case Manifold::Error::RUN_INDEX_WRONG_LENGTH:
e = RUN_INDEX_WRONG_LENGTH;
break;
case Manifold::Error::FACE_ID_WRONG_LENGTH:
e = 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;
}
-222
View File
@@ -1,222 +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_original_id_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_original_id(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
-74
View File
@@ -1,74 +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 ManifoldPolyVert {
ManifoldVec2 pos;
int idx;
} ManifoldPolyVert;
typedef struct ManifoldCurvatureBounds {
float max_mean_curvature;
float min_mean_curvature;
float max_gaussian_curvature;
float min_gaussian_curvature;
} ManifoldCurvatureBounds;
typedef enum ManifoldError {
NO_ERROR,
NON_FINITE_VERTEX,
NOT_MANIFOLD,
VERTEX_INDEX_OUT_OF_BOUNDS,
PROPERTIES_WRONG_LENGTH,
MISSING_POSITION_PROPERTIES,
MERGE_VECTORS_DIFFERENT_LENGTHS,
MERGE_INDEX_OUT_OF_BOUNDS,
TRANSFORM_WRONG_LENGTH,
RUN_INDEX_WRONG_LENGTH,
FACE_ID_WRONG_LENGTH,
} ManifoldError;
-454
View File
@@ -1,454 +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(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<PolyVert>;
for (int i = 0; i < length; ++i) {
vec->push_back({{ps[i].x, ps[i].y}, i});
}
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_original_id_length(ManifoldMeshGL *m) {
return from_c(m)->originalID.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_original_id(void *mem, ManifoldMeshGL *m) {
return copy_data(mem, from_c(m)->originalID);
}
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
)
)
@@ -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
+49 -57
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"))
+2 -1
View File
@@ -24,7 +24,7 @@ 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)
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})
@@ -35,3 +35,4 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_BINARY_DIR}/examples/)
+25 -51
View File
@@ -15,8 +15,6 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <vector>
using namespace emscripten;
#include <manifold.h>
@@ -62,29 +60,15 @@ 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()))
meshJS.set("vertPos",
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.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("originalID", val(typed_memory_view(mesh.originalID.size(),
mesh.originalID.data()))
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
mesh.vertNormal.data()))
.call<val>("slice"));
meshJS.set("faceID",
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
@@ -95,26 +79,10 @@ val GetMeshJS(const Manifold& manifold) {
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["originalID"] != val::undefined()) {
out.originalID = convertJSArrayToNumberVector<uint32_t>(mesh["originalID"]);
}
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 =
@@ -190,15 +158,10 @@ EMSCRIPTEN_BINDINGS(whatever) {
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
.value("PROPERTIES_WRONG_LENGTH",
Manifold::Error::PROPERTIES_WRONG_LENGTH)
.value("MISSING_POSITION_PROPERTIES",
Manifold::Error::MISSING_POSITION_PROPERTIES)
.value("MERGE_VECTORS_DIFFERENT_LENGTHS",
Manifold::Error::MERGE_VECTORS_DIFFERENT_LENGTHS)
.value("MERGE_INDEX_OUT_OF_BOUNDS",
Manifold::Error::MERGE_INDEX_OUT_OF_BOUNDS)
.value("TRANSFORM_WRONG_LENGTH", Manifold::Error::TRANSFORM_WRONG_LENGTH)
.value("RUN_INDEX_WRONG_LENGTH", Manifold::Error::RUN_INDEX_WRONG_LENGTH)
.value("FACE_ID_WRONG_LENGTH", Manifold::Error::FACE_ID_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);
@@ -210,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)
@@ -225,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")
@@ -239,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)
@@ -252,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);
+76 -77
View File
@@ -13,7 +13,7 @@
// limitations under the License.
var _ManifoldInitialized = false;
Module.setup = function () {
Module.setup = function() {
if (_ManifoldInitialized) return;
_ManifoldInitialized = true;
@@ -38,11 +38,11 @@ Module.setup = function () {
polygons = [polygons];
}
return toVec(
new Module.Vector2_vec2(), polygons,
poly => toVec(new Module.Vector_vec2(), poly, p => {
if (p instanceof Array) return { x: p[0], y: p[1] };
return p;
}));
new Module.Vector2_vec2(), polygons,
poly => toVec(new Module.Vector_vec2(), poly, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
}));
}
function disposePolygons(polygonsVec) {
@@ -52,15 +52,15 @@ Module.setup = function () {
function vararg2vec(vec) {
if (vec[0] instanceof Array)
return { x: vec[0][0], y: vec[0][1], z: vec[0][2] };
return {x: vec[0][0], y: vec[0][1], z: vec[0][2]};
if (typeof (vec[0]) == 'number')
// default to 0
return { x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0 };
return {x: vec[0] || 0, y: vec[1] || 0, z: vec[2] || 0};
return vec[0];
}
Module.Manifold.prototype.warp = function (func) {
const wasmFuncPtr = addFunction(function (vec3Ptr) {
Module.Manifold.prototype.warp = function(func) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
@@ -76,7 +76,7 @@ Module.setup = function () {
};
// note that the matrix is using column major (same as glm)
Module.Manifold.prototype.transform = function (mat) {
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) {
@@ -88,29 +88,29 @@ Module.setup = function () {
return result;
};
Module.Manifold.prototype.translate = function (...vec) {
Module.Manifold.prototype.translate = function(...vec) {
return this._Translate(vararg2vec(vec));
};
Module.Manifold.prototype.rotate = function (vec) {
Module.Manifold.prototype.rotate = function(vec) {
return this._Rotate(...vec);
};
Module.Manifold.prototype.scale = function (vec) {
Module.Manifold.prototype.scale = function(vec) {
if (typeof vec == 'number') {
return this._Scale({ x: vec, y: vec, z: vec });
return this._Scale({x: vec, y: vec, z: vec});
}
return this._Scale(vararg2vec([vec]));
};
Module.Manifold.prototype.decompose = function () {
Module.Manifold.prototype.decompose = function() {
const vec = this._Decompose();
const result = fromVec(vec);
vec.delete();
return result;
};
Module.Manifold.prototype.getCurvature = function () {
Module.Manifold.prototype.getCurvature = function() {
const result = this._getCurvature();
const oldMeanCurvature = result.vertMeanCurvature;
const oldGaussianCurvature = result.vertGaussianCurvature;
@@ -123,24 +123,14 @@ Module.setup = function () {
class Mesh {
constructor({
numProp = 3,
triVerts = new Uint32Array(),
vertProperties = new Float32Array(),
mergeFromVert,
mergeToVert,
runIndex,
originalID,
faceID,
vertPos = new Float32Array(),
vertNormal,
halfedgeTangent
} = {}) {
this.numProp = numProp;
this.triVerts = triVerts;
this.vertProperties = vertProperties;
this.mergeFromVert = mergeFromVert;
this.mergeToVert = mergeToVert;
this.runIndex = runIndex;
this.originalID = originalID;
this.faceID = faceID;
this.vertPos = vertPos;
this.vertNormal = vertNormal;
this.halfedgeTangent = halfedgeTangent;
}
@@ -149,7 +139,7 @@ Module.setup = function () {
}
get numVert() {
return this.vertProperties.length / this.numProp;
return this.vertPos.length / 3;
}
verts(tri) {
@@ -157,11 +147,11 @@ 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) {
@@ -171,11 +161,32 @@ Module.setup = function () {
Module.Mesh = Mesh;
Module.Manifold.prototype.getMesh = function () {
Module.Manifold.prototype.getMesh = function() {
return new Mesh(this._GetMeshJS());
};
Module.Manifold.prototype.boundingBox = function () {
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() {
const result = this._boundingBox();
return {
min: ['x', 'y', 'z'].map(f => result.min[f]),
@@ -198,23 +209,11 @@ Module.setup = function () {
case Module.status.PROPERTIES_WRONG_LENGTH.value:
message = 'Properties have wrong length';
break;
case Module.status.MISSING_POSITION_PROPERTIES.value:
message = 'Less than three properties';
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
message = 'Tri properties have wrong length';
break;
case Module.status.MERGE_VECTORS_DIFFERENT_LENGTHS.value:
message = 'Merge vectors have different lengths';
break;
case Module.status.MERGE_INDEX_OUT_OF_BOUNDS.value:
message = 'Merge index out of bounds';
break;
case Module.status.TRANSFORM_WRONG_LENGTH.value:
message = 'Transform vector has wrong length';
break;
case Module.status.RUN_INDEX_WRONG_LENGTH.value:
message = 'Run index vector has wrong length';
break;
case Module.status.FACE_ID_WRONG_LENGTH.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]);
@@ -226,11 +225,11 @@ Module.setup = function () {
Module.ManifoldError.prototype = Object.create(Error.prototype, {
constructor:
{ value: Module.ManifoldError, writable: true, configurable: true }
{value: Module.ManifoldError, writable: true, configurable: true}
});
const ManifoldCtor = Module.Manifold;
Module.Manifold = function (mesh) {
Module.Manifold = function(mesh) {
const manifold = new ManifoldCtor(mesh);
const status = manifold.status();
@@ -243,30 +242,30 @@ Module.setup = function () {
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
Module.cube = function (...args) {
Module.cube = function(...args) {
let size = undefined;
if (args.length == 0)
size = { x: 1, y: 1, z: 1 };
size = {x: 1, y: 1, z: 1};
else if (typeof args[0] == 'number')
size = { x: args[0], y: args[0], z: args[0] };
size = {x: args[0], y: args[0], z: args[0]};
else
size = vararg2vec(args);
const center = args[1] || false;
return Module._Cube(size, center);
};
Module.cylinder = function (
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
Module.cylinder = function(
height, radiusLow, radiusHigh = -1.0, circularSegments = 0,
center = false) {
return Module._Cylinder(
height, radiusLow, radiusHigh, circularSegments, center);
height, radiusLow, radiusHigh, circularSegments, center);
};
Module.sphere = function (radius, circularSegments = 0) {
Module.sphere = function(radius, circularSegments = 0) {
return Module._Sphere(radius, circularSegments);
};
Module.smooth = function (mesh, sharpenedEdges = []) {
Module.smooth = function(mesh, sharpenedEdges = []) {
const sharp = new Module.Vector_smoothness();
toVec(sharp, sharpenedEdges);
const result = Module._Smooth(mesh, sharp);
@@ -274,25 +273,25 @@ Module.setup = function () {
return result;
};
Module.extrude = function (
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0]) {
if (scaleTop instanceof Array) scaleTop = { x: scaleTop[0], y: scaleTop[1] };
Module.extrude = function(
polygons, height, nDivisions = 0, twistDegrees = 0.0,
scaleTop = [1.0, 1.0]) {
if (scaleTop instanceof Array) scaleTop = {x: scaleTop[0], y: scaleTop[1]};
const polygonsVec = polygons2vec(polygons);
const result = Module._Extrude(
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
polygonsVec, height, nDivisions, twistDegrees, scaleTop);
disposePolygons(polygonsVec);
return result;
};
Module.revolve = function (polygons, circularSegments = 0) {
Module.revolve = function(polygons, circularSegments = 0) {
const polygonsVec = polygons2vec(polygons);
const result = Module._Revolve(polygonsVec, circularSegments);
disposePolygons(polygonsVec);
return result;
};
Module.compose = function (manifolds) {
Module.compose = function(manifolds) {
const vec = new Module.Vector_manifold();
toVec(vec, manifolds);
const result = Module._Compose(vec);
@@ -300,12 +299,12 @@ Module.setup = function () {
return result;
};
Module.levelSet = function (sdf, bounds, edgeLength, level = 0) {
Module.levelSet = function(sdf, bounds, edgeLength, level = 0) {
const bounds2 = {
min: { x: bounds.min[0], y: bounds.min[1], z: bounds.min[2] },
max: { x: bounds.max[0], y: bounds.max[1], z: bounds.max[2] },
min: {x: bounds.min[0], y: bounds.min[1], z: bounds.min[2]},
max: {x: bounds.max[0], y: bounds.max[1], z: bounds.max[2]},
};
const wasmFuncPtr = addFunction(function (vec3Ptr) {
const wasmFuncPtr = addFunction(function(vec3Ptr) {
const x = getValue(vec3Ptr, 'float');
const y = getValue(vec3Ptr + 4, 'float');
const z = getValue(vec3Ptr + 8, 'float');
@@ -318,14 +317,14 @@ Module.setup = function () {
};
function batchbool(name) {
return function (...args) {
return function(...args) {
if (args.length == 1) args = args[0];
const v = new Module.Vector_manifold();
for (const m of args) v.push_back(m);
const result = Module['_' + name + 'N'](v);
v.delete();
return result;
};
}
}
Module.union = batchbool('union');
+2 -2
View File
@@ -128,7 +128,7 @@ model-viewer {
transform: translate3d(-50%, -50%, 0);
}
.header .margin {
.margin {
font-size: 16px;
margin: 1em;
}
@@ -211,4 +211,4 @@ model-viewer {
.col {
width: 100%;
}
}
}
+330 -330
View File
@@ -12,239 +12,239 @@
// See the License for the specific language governing permissions and
// limitations under the License.
(function (exports) {
(function(exports) {
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.
exports.functions = {
Intro: function() {
// Write code in JavaScript or TypeScript and this editor will show the API
// docs. Type e.g. "box." to see the Manifold API. Type "Module." to see the
// static API - these functions can also be used bare. Use console.log() to
// print output (lower-right). This editor defines Z as up and units of mm.
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
const box = cube([100, 100, 100], true);
const ball = sphere(60, 100);
// You must name your final output "result".
const result = box.subtract(ball);
// All changes are automatically saved and restored between sessions.
// This app is purely local - there is no server communication.
// This means it will work equally well offline once loaded.
// To share your code with another browser/device, simply copy the text to a
// file.
// All changes are automatically saved and restored between sessions.
// This 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.
return result;
},
// See the script drop-down above ("Intro") for usage examples.
return result;
},
TetrahedronPuzzle: function () {
// A tetrahedron cut into two identical halves that can screw together as a
// puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
TetrahedronPuzzle: function() {
// A tetrahedron cut into two identical halves that can screw together as a
// puzzle. This only outputs one of the halves. This demonstrates how
// redundant points along a polygon can be used to make twisted extrusions
// smoother. Based on the screw puzzle by George Hart:
// https://www.thingiverse.com/thing:186372
const edgeLength = 50; // Length of each edge of the overall tetrahedron.
const gap = 0.2; // Spacing between the two halves to allow sliding.
const nDivisions =
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 scale = edgeLength / (2 * Math.sqrt(2));
const tet = tetrahedron().scale(scale);
const tet = tetrahedron().scale(scale);
const box = [];
box.push([2, -2], [2, 2]);
for (let i = 0; i <= nDivisions; ++i) {
box.push([gap / (2 * scale), 2 - i * 4 / nDivisions]);
}
const 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 screw = extrude(box, 2, nDivisions, 270)
.rotate([0, 0, -45])
.translate([0, 0, -1])
.scale(scale);
const result = tet.intersect(screw);
return result;
},
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.
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);
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 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 =
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 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]));
let frame = edge4.translate([0, 0, -edgeLength / 2]);
frame = union(frame, frame.rotate([180, 0, 0]));
return frame;
}
return frame;
}
setMinCircularAngle(3);
setMinCircularEdgeLength(0.5);
const result = roundedFrame(100, 10);
return result;
},
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
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 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 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;
},
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
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 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 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);
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(
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;
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);
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);
}
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.vertProperties = Float32Array.from(vertPos);
const result = smooth(scallop, sharpenedEdges).refine(n);
return result;
},
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.
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.
// @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(
function torusKnot(
p, q, majorRadius, minorRadius, threadRadius, circularSegments = 0,
linearSegments = 0) {
function gcd(a, b) {
return b == 0 ? a : gcd(b, a % b);
}
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 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 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 point = new THREE.Vector3();
const axis = new THREE.Vector3();
const func =
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);
.multiplyScalar(threadRadius);
const r = majorRadius + minorRadius * Math.cos(theta);
point.applyAxisAngle(
axis.set(1, 0, 0), -Math.atan2(p * minorRadius, q * r));
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;
@@ -252,189 +252,189 @@
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);
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)]));
}
return knot;
knot = compose(knots);
}
// This recreates Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
return knot;
}
const result = torusKnot(1, 3, 25, 10, 3.75);
return result;
},
// This recreates Matlab Knot by Emmett Lalish:
// https://www.thingiverse.com/thing:7080
MengerSponge: function () {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
const result = torusKnot(1, 3, 25, 10, 3.75);
return result;
},
function vec2add(a, b) {
return [a[0] + b[0], a[1] + b[1]];
}
MengerSponge: function() {
// This example demonstrates how symbolic perturbation correctly creates
// holes even though the subtracted objects are exactly coplanar.
function fractal(holes, hole, w, position, depth, maxDepth) {
w /= 3;
holes.push(
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);
}
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);
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);
const hole = compose(holes);
result = difference(result, hole);
result = difference(result, hole.rotate([90, 0, 0]));
result = difference(result, hole.rotate([0, 90, 0]));
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 result = mengerSponge(3).scale(100);
return result;
},
function base(
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)
];
}
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 =
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;
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;
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(
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;
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(
return difference(
base(
width, radius, decorRadius, twistRadius, nDecor,
innerRadius + thickness, outerRadius + adjThickness,
cut - adjThickness, nCut, nDivision),
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;
width, radius - thickness, decorRadius, twistRadius, nDecor,
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
nDivision));
}
};
exports.functionBodies = new Map();
const result = stretchyBracelet();
return result;
},
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');
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 name =
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;
}
};
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 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
exports.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

+2 -2
View File
@@ -11,7 +11,7 @@
<body>
<div class="page">
<div class="container header" style="background-color: rgb(162 136 179);">
<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;">
@@ -53,4 +53,4 @@
<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>
</html>
+49 -191
View File
@@ -38,10 +38,7 @@
<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>.
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>
@@ -52,58 +49,29 @@
</body>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/2.0.1/model-viewer.min.js"></script>
<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 type="module">
import { Accessor, Document, WebIO } from 'https://cdn.skypack.dev/@gltf-transform/core';
const io = new WebIO();
const doc = new Document();
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);
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()));
}
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();
<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 space = cubeUV().scale(50);
const moon = sphereUV().scale(60);
materialMap.set(space.originalID(), spaceIndices);
materialMap.set(moon.originalID(), moonIndices);
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 csg = function (operation) {
push2MV(wasm[operation](space, moon));
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
push2MV(mesh);
};
document.querySelector('select').onchange = function (event) {
@@ -115,154 +83,44 @@
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.originalID.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() {
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 meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert });
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() {
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 meshUV = new wasm.Mesh({ numProp, triVerts, vertProperties, mergeFromVert, mergeToVert });
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

+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>
+5 -3
View File
@@ -94,14 +94,16 @@
// 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;
}
+48 -49
View File
@@ -14,8 +14,8 @@
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.js', threePath + 'build/three.js',
threePath + 'examples/js/exporters/GLTFExporter.js');
// manifold member functions that returns a new manifold
const memberFunctions = [
@@ -34,7 +34,7 @@ const utils = [
const exposedFunctions = constructors.concat(utils);
let wasm;
Module().then(function (tmp) {
Module().then(function(tmp) {
wasm = tmp;
wasm.setup();
// Setup memory management, such that users don't have to care about
@@ -46,39 +46,39 @@ Module().then(function (tmp) {
for (const name of memberFunctions) {
const originalFn = wasm.Manifold.prototype[name];
wasm.Manifold.prototype['_' + name] = originalFn;
wasm.Manifold.prototype[name] = function (...args) {
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) {
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 = [];
};
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) {
console.log = function(...args) {
let message = '';
for (const arg of args) {
if (typeof arg == 'object') {
@@ -87,30 +87,30 @@ console.log = function (...args) {
message += arg.toString();
}
}
postMessage({ log: message });
postMessage({log: message});
oldLog(...args);
};
onmessage =
(e) => {
const content = e.data + '\nexportGLB(result);\n';
try {
const f = new Function(...exposedFunctions, content);
f(...exposedFunctions.map(name => wasm[name]));
} catch (error) {
console.log(error.toString());
postMessage({ objectURL: null });
} finally {
wasm.cleanup();
(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();
}
}
};
// Export & Rendering
// ------------------------------------------------------------
const mesh = new THREE.Mesh(
undefined,
new THREE.MeshStandardMaterial(
{ color: 'yellow', metalness: 1, roughness: 0.2 }));
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
@@ -125,31 +125,30 @@ function exportGLB(manifold) {
for (let i = 0; i < 3; i++) {
size[i] = Math.round((box.max[i] - box.min[i]) * 10) / 10;
}
console.log(`Bounding Box: X = ${size[0].toLocaleString()
} mm, Y = ${size[1].toLocaleString()
} mm, Z = ${size[2].toLocaleString()} mm`);
console.log(`Bounding Box: X = ${size[0].toLocaleString()} mm, Y = ${
size[1].toLocaleString()} mm, Z = ${size[2].toLocaleString()} mm`);
const volume = Math.round(manifold.getProperties().volume / 10);
console.log(`Genus: ${manifold.genus().toLocaleString()
}, Volume: ${(volume / 100).toLocaleString()} cm^3`);
console.log(`Genus: ${manifold.genus().toLocaleString()}, Volume: ${
(volume / 100).toLocaleString()} cm^3`);
mesh.geometry?.dispose();
mesh.geometry = mesh2geometry(manifold.getMesh());
exporter.parse(
mesh,
(gltf) => {
const blob = new Blob([gltf], { type: 'application/octet-stream' });
postMessage({ objectURL: URL.createObjectURL(blob) });
},
() => {
console.log('glTF export failed!');
postMessage({ objectURL: null });
},
{ binary: true });
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});
}
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.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
return geometry;
}
+43 -21
View File
@@ -17,7 +17,7 @@ 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 Polygons = SimplePolygon|SimplePolygon[];
type Box = {
min: Vec3,
max: Vec3
@@ -30,6 +30,12 @@ type Properties = {
surfaceArea: number,
volume: number
};
type BaryRef = {
meshID: number,
originalID: number,
tri: number,
vertBary: Vec3
};
type Curvature = {
maxMeanCurvature: number,
minMeanCurvature: number,
@@ -38,22 +44,21 @@ type Curvature = {
vertMeanCurvature: number[],
vertGaussianCurvature: number[]
};
type MeshRelation = {
barycentric: Vec3[],
triBary: BaryRef[],
};
declare class Mesh {
numProp: number;
vertProperties: Float32Array;
vertPos: Float32Array;
triVerts: Uint32Array;
mergeFromVert?: Uint32Array;
mergeToVert?: Uint32Array;
runIndex?: Uint32Array;
originalID?: Uint32Array;
faceID?: Uint32Array;
vertNormal?: Float32Array;
halfedgeTangent?: Float32Array;
get numTri(): number;
get numVert(): number;
verts(tri: number): Uint32Array<3>;
position(vert: number): Float32Array<3>;
extras(vert: number): Float32Array;
normal(vert: number): Float32Array<3>;
tangent(halfedge: number): Float32Array<4>;
}
@@ -97,7 +102,7 @@ declare class Manifold {
*
* @param v The vector to multiply every vertex by per component.
*/
scale(v: Vec3 | number): Manifold;
scale(v: Vec3|number): Manifold;
/**
* Boolean union
@@ -212,9 +217,26 @@ declare class Manifold {
*/
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 IDs to a new original,
* (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.
@@ -228,8 +250,8 @@ declare class 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
* 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;
@@ -242,7 +264,7 @@ declare class Manifold {
* @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;
declare function cube(size?: Vec3|number, center?: boolean): Manifold;
/**
* A convenience constructor for the common case of extruding a circle. Can also
@@ -258,8 +280,8 @@ declare function cube(size?: Vec3 | number, center?: boolean): Manifold;
* origin at the bottom.
*/
declare function cylinder(
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
height: number, radiusLow: number, radiusHigh?: number,
circularSegments?: number, center?: boolean): Manifold;
/**
* Constructs a geodesic sphere of a given radius.
@@ -326,8 +348,8 @@ declare function tetrahedron(): Manifold;
* Default {1, 1}.
*/
declare function extrude(
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
crossSection: Polygons, height: number, nDivisions?: number,
twistDegrees?: number, scaleTop?: Vec2): Manifold;
/**
* Constructs a manifold from a set of polygons by revolving this cross-section
@@ -341,7 +363,7 @@ declare function extrude(
* calculated by the static Defaults.
*/
declare function revolve(
crossSection: Polygons, circularSegments?: number): Manifold;
crossSection: Polygons, circularSegments?: number): Manifold;
declare function union(a: Manifold, b: Manifold): Manifold;
declare function difference(a: Manifold, b: Manifold): Manifold;
@@ -378,8 +400,8 @@ declare function compose(manifolds: Manifold[]): Manifold;
* it with a negative value.
*/
declare function levelSet(
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
sdf: (point: Vec3) => number, bounds: Box, edgeLength: number,
level?: number): Manifold;
/**
* @name Defaults
+20 -20
View File
@@ -33,7 +33,7 @@ const utils = [
];
const exposedFunctions = constructors.concat(utils);
wasm().then(function (Module) {
wasm().then(function(Module) {
Module.setup();
// Setup memory management, such that users don't have to care about
// calling `delete` manually.
@@ -44,33 +44,33 @@ wasm().then(function (Module) {
for (const name of memberFunctions) {
const originalFn = Module.Manifold.prototype[name];
Module.Manifold.prototype['_' + name] = originalFn;
Module.Manifold.prototype[name] = function (...args) {
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) {
Module[name] = function(...args) {
const result = originalFn(...args);
manifoldRegistry.push(result);
return result;
};
}
}
Module.cleanup =
function () {
for (const obj of manifoldRegistry) {
// decompose result is an array of manifolds
if (obj instanceof Array)
for (const elem of obj) elem.delete();
else
obj.delete();
}
manifoldRegistry = [];
};
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 = [];
}
function runExample(name) {
try {
@@ -80,7 +80,7 @@ wasm().then(function (Module) {
const manifold = f(...exposedFunctions.map(name => Module[name]), THREE);
const prop = manifold.getProperties();
const genus = manifold.genus();
return { ...prop, genus };
return {...prop, genus};
} finally {
Module.cleanup();
}
@@ -104,14 +104,14 @@ wasm().then(function (Module) {
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.volume).to.be.closeTo(353706, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(68454, 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.volume).to.be.closeTo(282743, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(22187, 1, 'Surface Area');
});
@@ -132,8 +132,8 @@ wasm().then(function (Module) {
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');
expect(result.volume).to.be.closeTo(406457, 1, 'Volume');
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
});
test('Stretchy Bracelet', () => {
+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
)
-1
View File
@@ -52,7 +52,6 @@ Manifold Base(float width, float radius, float decorRadius, float twistRadius,
}
base = Manifold::Extrude(stretch, width) ^ base;
// Remove extra edges in coplanar faces
base = base.AsOriginal();
return base;
+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
)
+10 -18
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,7 +98,7 @@ class Manifold {
*/
///@{
Mesh GetMesh() const;
MeshGL GetMeshGL(glm::ivec3 normalIdx = glm::ivec3(0)) const;
MeshGL GetMeshGL() const;
bool IsEmpty() const;
enum class Error {
NO_ERROR,
@@ -108,19 +106,13 @@ class Manifold {
NOT_MANIFOLD,
VERTEX_INDEX_OUT_OF_BOUNDS,
PROPERTIES_WRONG_LENGTH,
MISSING_POSITION_PROPERTIES,
MERGE_VECTORS_DIFFERENT_LENGTHS,
MERGE_INDEX_OUT_OF_BOUNDS,
TRANSFORM_WRONG_LENGTH,
RUN_INDEX_WRONG_LENGTH,
FACE_ID_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
+108 -139
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
@@ -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();
+15 -72
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);
}
@@ -151,7 +108,7 @@ Manifold Manifold::Cube(glm::vec3 size, bool center) {
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;
}
/**
@@ -181,8 +138,7 @@ Manifold Manifold::Cylinder(float height, float radiusLow, float radiusHigh,
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;
}
@@ -205,7 +161,7 @@ Manifold Manifold::Sphere(float radius, int circularSegments) {
pImpl_->NumVert(), ToSphere({radius}));
pImpl_->Finish();
// Ignore preceding octahedron.
pImpl_->InitializeOriginal();
pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1));
return Manifold(pImpl_);
}
@@ -283,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_);
}
@@ -384,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_);
}
@@ -405,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) {
@@ -417,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_;
@@ -466,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
+26 -19
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;
@@ -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,28 +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}));
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;
}
+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;
+29 -7
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,11 +125,19 @@ 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);
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_);
@@ -126,7 +145,10 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
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]];
}
}
}
}
+169 -349
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,43 +157,52 @@ 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 {
HashTableD<uint32_t> table;
int* includesMeshID;
__host__ __device__ void operator()(TriRef& ref) {
if (table.Full()) return;
table.Insert(ref.meshID, 1);
__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) {
__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;
}
};
struct CoplanarEdge {
float* triArea;
const Halfedge* halfedge;
const glm::vec3* vertPos;
const TriRef* triRef;
const glm::ivec3* triProp;
const float* prop;
const float* propTol;
@@ -245,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;
@@ -263,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;
@@ -292,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;
@@ -321,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;
}
@@ -344,181 +288,22 @@ 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::MISSING_POSITION_PROPERTIES);
return;
}
mesh.triVerts.resize(numTri);
if (meshGL.mergeFromVert.size() != meshGL.mergeToVert.size()) {
MarkFailure(Error::MERGE_VECTORS_DIFFERENT_LENGTHS);
return;
}
if (!meshGL.transform.empty() &&
12 * meshGL.originalID.size() != meshGL.transform.size()) {
MarkFailure(Error::TRANSFORM_WRONG_LENGTH);
return;
}
if (!meshGL.originalID.empty() && !meshGL.runIndex.empty() &&
meshGL.originalID.size() + 1 != meshGL.runIndex.size()) {
MarkFailure(Error::RUN_INDEX_WRONG_LENGTH);
return;
}
if (!meshGL.faceID.empty() && meshGL.faceID.size() != meshGL.NumTri()) {
MarkFailure(Error::FACE_ID_WRONG_LENGTH);
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::MERGE_INDEX_OUT_OF_BOUNDS);
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::VERTEX_INDEX_OUT_OF_BOUNDS);
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.originalID.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.originalID.size());
for (int i = 0; i < meshGL.originalID.size(); ++i) {
const int meshID = startID + i;
const int originalID = meshGL.originalID[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.transform.empty()) {
relation.meshIDtransform[meshID] = {};
} else {
const float* m = meshGL.transform.data() + 12 * i;
relation.meshIDtransform[meshID] = {{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::VERTEX_INDEX_OUT_OF_BOUNDS);
@@ -539,11 +324,8 @@ Manifold::Impl::Impl(const Mesh& mesh, const MeshRelationD& relation,
return;
}
CalculateNormals();
InitializeOriginal();
if (!hasFaceIDs) {
CreateFaces(propertyTolerance);
}
InitializeNewReference(triProperties, properties, propertyTolerance);
if (status_ != Error::NO_ERROR) return;
SimplifyTopology();
Finish();
@@ -596,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) {
@@ -621,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] = {};
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) {
@@ -668,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;
}
/**
@@ -702,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();
@@ -733,11 +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());
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());
@@ -745,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();
@@ -782,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());
@@ -793,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() {
@@ -822,43 +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() {
const int numTri = NumTri();
const auto policy = autoPolicy(numTri);
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
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);
while (1) {
for_each_n(policy, meshRelation_.triRef.begin(), numTri,
MarkMeshID({meshIDold2new.D()}));
if (!meshIDold2new.Full()) break;
meshIDold2new = HashTable<uint32_t>(meshIDold2new.Size() * 2);
}
inclusive_scan(autoPolicy(meshIDold2new.Size()),
meshIDold2new.GetValueStore().begin(),
meshIDold2new.GetValueStore().end(),
meshIDold2new.GetValueStore().begin());
const int numMeshIDs = meshIDold2new.GetValueStore().back();
const int meshIDstart = ReserveIDs(numMeshIDs);
// We do start - 1 because the inclusive scan makes our first index 1
// instead of 0.
for_each_n(policy, meshRelation_.triRef.begin(), numTri,
UpdateMeshID({meshIDold2new.D(), meshIDstart}));
// Update keys of the transform map
std::map<int, Relation> oldTransforms;
std::swap(meshRelation_.meshIDtransform, oldTransforms);
const int tableSize = meshIDold2new.Size();
for (int i = 0; i < tableSize; ++i) {
const auto oldID = meshIDold2new.D().KeyAt(i);
if (oldID != HashTable<uint32_t>::Open()) {
meshRelation_.meshIDtransform[meshIDold2new.D().At(i) + meshIDstart] =
oldTransforms[oldID];
}
}
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 {
+18 -29
View File
@@ -13,8 +13,6 @@
// limitations under the License.
#pragma once
#include <map>
#include "collider.h"
#include "manifold.h"
#include "optional_assert.h"
@@ -27,18 +25,11 @@ namespace manifold {
/** @ingroup Private */
struct Manifold::Impl {
struct Relation {
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_;
@@ -52,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 };
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);
@@ -81,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;
@@ -103,14 +92,14 @@ 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);
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;
@@ -127,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
+78 -187
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"
@@ -85,43 +81,38 @@ CsgLeafNode& Manifold::GetCsgLeafNode() const {
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,33 +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 updateNormals =
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) {
@@ -184,110 +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 (impl.meshRelation_.originalID < 0) {
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;
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;
if (meshID != lastID) {
out.runIndex.push_back(3 * tri);
out.originalID.push_back(ref.originalID);
const Impl::Relation& m = impl.meshRelation_.meshIDtransform.at(meshID);
if (updateNormals) {
runNormalTransform.push_back(NormalTransform(m.transform) *
(m.backSide ? -1.0f : 1.0f));
}
if (impl.meshRelation_.originalID < 0) {
for (const int col : {0, 1, 2, 3}) {
for (const int row : {0, 1, 2}) {
out.transform.push_back(m.transform[col][row]);
}
}
}
lastID = meshID;
}
out.faceID[tri] = ref.tri;
for (const int i : {0, 1, 2})
out.triVerts[3 * tri + i] = impl.halfedge_[3 * oldTri + i].startVert;
}
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.originalID.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;
}
@@ -378,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.
@@ -439,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
@@ -449,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.originalID 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.
*/
+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()}));
}
+9 -85
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 {
@@ -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 -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 -43
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,20 +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 == ParUnseq ? Par : policy;
for_each_n(pol, countAt(0), morton, cv);
}
} // namespace
namespace manifold {
@@ -318,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();
@@ -328,26 +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() == 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}));
@@ -360,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]);
@@ -371,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]);
@@ -380,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}")
-127
View File
@@ -1,127 +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);
return compare;
#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 used_[0] * 2 > Size(); }
__host__ __device__ void Insert(Uint64 key, const V& val) {
uint32_t idx = H(key) & (Size() - 1);
while (1) {
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) {
if (keys_[idx] == key || keys_[idx] == kOpen) return values_[idx];
idx = (idx + step_) & (Size() - 1);
}
}
__host__ __device__ Uint64 KeyAt(int idx) const { return 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 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(); }
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
+111 -76
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.
@@ -143,6 +148,22 @@ using SimplePolygon = std::vector<PolyVert>;
using Polygons = std::vector<SimplePolygon>;
/** @} */
/**
* An alternative to Mesh for output suitable for pushing into graphics
* libraries directly.
*/
struct MeshGL {
/// Number of vertices
int NumVert() const { return this->vertPos.size() / 3; };
/// Number of triangles
int NumTri() const { return this->triVerts.size() / 3; };
std::vector<float> vertPos;
std::vector<float> vertNormal;
std::vector<uint32_t> triVerts;
std::vector<float> halfedgeTangent;
};
/**
* The triangle-mesh input and output of this library.
*/
@@ -161,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 originalID. 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> originalID;
/// 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 * originalID.size().
std::vector<float> transform;
/// 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]};
}
}
}
};
@@ -271,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
@@ -512,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__
+2 -16
View File
@@ -19,18 +19,9 @@ add_subdirectory(third_party)
enable_testing()
set(SOURCE_FILES polygon_test.cpp mesh_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
if(MANIFOLD_CBIND)
list(APPEND SOURCE_FILES manifoldc_test.cpp)
endif()
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
target_link_libraries(${PROJECT_NAME} polygon GTest::GTest manifold samples samplesGPU)
if(MANIFOLD_CBIND)
target_link_libraries(${PROJECT_NAME} manifoldc)
endif()
if(MANIFOLD_EXPORT)
add_subdirectory(meshIO)
target_link_libraries(${PROJECT_NAME} meshIO)
@@ -38,17 +29,12 @@ if(MANIFOLD_EXPORT)
endif()
if(MANIFOLD_USE_CUDA)
set(CUDA_FILES mesh_test.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})
-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), 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), 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
+149 -412
View File
@@ -63,112 +63,6 @@ Mesh Tet() {
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 CubeGL() {
MeshGL cube;
cube.numProp = 6;
glm::mat3 t[3] = {
glm::mat3(1), {1, 0, 0, 0, 0, -1, 0, 1, 0}, {0, 1, 0, -1, 0, 0, 0, 0, 1}};
glm::vec3 n(0, 1, 0);
glm::mat4x3 p(1, 1, 1, //
1, 1, -1, //
-1, 1, -1, //
-1, 1, 1);
for (const int dir : {0, 1, 2}) {
for (const float side : {1, -1}) {
const int start = cube.vertProperties.size();
cube.triVerts.push_back(start);
cube.triVerts.push_back(start + 1);
cube.triVerts.push_back(start + 2);
cube.triVerts.push_back(start);
cube.triVerts.push_back(start + 2);
cube.triVerts.push_back(start + 3);
const glm::vec3 norm = side * t[dir] * n;
for (const int i : {0, 1, 2, 3}) {
const glm::vec3 v = side * t[dir] * p[i];
const glm::vec3 ni = side * t[dir] * n;
for (const int j : {0, 1, 2}) cube.vertProperties.push_back(v[j]);
for (const int j : {0, 1, 2}) cube.vertProperties.push_back(ni[j]);
}
}
}
return Weld(cube);
}
MeshGL WithIndexColors(const Mesh& in) {
MeshGL inGL(in);
inGL.originalID = {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.originalID.clear();
inGL.transform.clear();
inGL.faceID.clear();
inGL.originalID = {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.originalID = {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)
@@ -180,117 +74,54 @@ void Identical(const Mesh& mesh1, const Mesh& mesh2) {
ASSERT_EQ(mesh1.triVerts[i], mesh2.triVerts[i]);
}
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();
}
void RelatedGL(const Manifold& out, const std::vector<MeshGL>& originals,
bool checkNormals = false) {
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.originalID.size(); ++run) {
const float* m = output.transform.data() + 12 * run;
const glm::mat4x3 transform =
output.transform.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].originalID.size(), 1);
if (originals[i].originalID[0] == output.originalID[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 Related(const Manifold& out, const std::vector<Mesh>& input,
const std::map<int, int>& meshID2idx) {
Mesh output = out.GetMesh();
MeshRelation relation = out.GetMeshRelation();
for (int tri = 0; tri < out.NumTri(); ++tri) {
int meshID = relation.triBary[tri].originalID;
int meshIdx = meshID2idx.at(meshID);
ASSERT_LT(meshIdx, input.size());
const Mesh& inMesh = input[meshIdx];
int inTri = relation.triBary[tri].tri;
ASSERT_LT(inTri, inMesh.triVerts.size());
glm::mat3 inTriangle = {inMesh.vertPos[inMesh.triVerts[inTri][0]],
inMesh.vertPos[inMesh.triVerts[inTri][1]],
inMesh.vertPos[inMesh.triVerts[inTri][2]]};
for (int j : {0, 1, 2}) {
glm::vec3 vPos = output.vertPos[output.triVerts[tri][j]];
glm::vec3 uvw = relation.UVW(tri, j);
ASSERT_NEAR(uvw[0] + uvw[1] + uvw[2], 1, 0.0002);
glm::vec3 vRelation = inTriangle * uvw;
for (int k : {0, 1, 2})
ASSERT_NEAR(vPos[k], vRelation[k], 10 * out.Precision());
}
}
}
struct MeshSize {
int numVert, numTri;
int numProp = 0;
int numPropVert = numVert;
};
void RelatedOp(const Manifold& inP, const Manifold& inQ, const Manifold& outR) {
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
int meshID = inP.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(inP.GetMesh());
meshID = inQ.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(inQ.GetMesh());
Related(outR, input, meshID2idx);
}
void ExpectMeshes(const Manifold& manifold,
const std::vector<MeshSize>& meshSize) {
EXPECT_FALSE(manifold.IsEmpty());
const std::vector<std::pair<int, int>>& numVertTri) {
EXPECT_TRUE(manifold.IsManifold());
EXPECT_TRUE(manifold.MatchesTriNormals());
std::vector<Manifold> manifolds = manifold.Decompose();
ASSERT_EQ(manifolds.size(), meshSize.size());
ASSERT_EQ(manifolds.size(), numVertTri.size());
std::sort(manifolds.begin(), manifolds.end(),
[](const Manifold& a, const Manifold& b) {
return a.NumVert() != b.NumVert() ? a.NumVert() > b.NumVert()
@@ -298,14 +129,8 @@ void ExpectMeshes(const Manifold& manifold,
});
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());
EXPECT_EQ(manifolds[i].NumVert(), numVertTri[i].first);
EXPECT_EQ(manifolds[i].NumTri(), numVertTri[i].second);
const Mesh mesh = manifolds[i].GetMesh();
for (const glm::vec3& normal : mesh.vertNormal) {
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
@@ -371,7 +196,8 @@ TEST(Manifold, GetMeshGL) {
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]);
ASSERT_EQ(meshGL_out.vertPos[3 * i + j], mesh_out.vertPos[i][j]);
ASSERT_EQ(meshGL_out.vertNormal[3 * i + j], mesh_out.vertNormal[i][j]);
}
}
for (int i = 0; i < meshGL_out.NumTri(); ++i) {
@@ -391,8 +217,10 @@ TEST(Manifold, Empty) {
TEST(Manifold, ValidInput) {
std::vector<float> propTol = {0.1, 0.2};
MeshGL tetGL = TetGL();
Manifold tet(tetGL, propTol);
std::vector<float> prop = {0, 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6};
std::vector<glm::ivec3> triProp = {
{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {6, 5, 4}};
Manifold tet(Tet(), triProp, prop, propTol);
EXPECT_FALSE(tet.IsEmpty());
EXPECT_EQ(tet.Status(), Manifold::Error::NO_ERROR);
EXPECT_TRUE(tet.IsManifold());
@@ -443,20 +271,34 @@ TEST(Manifold, InvalidInput4) {
}
TEST(Manifold, InvalidInput5) {
MeshGL tetGL = TetGL();
tetGL.mergeFromVert[tetGL.mergeFromVert.size() - 1] = 7;
Manifold tet(tetGL);
std::vector<float> propTol = {0.1, 0.2};
std::vector<float> prop = {0, 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6};
std::vector<glm::ivec3> triProp = {
{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {6, 5, 4}};
Manifold tet(Tet(), triProp, prop, propTol);
EXPECT_TRUE(tet.IsEmpty());
EXPECT_EQ(tet.Status(), Manifold::Error::MERGE_INDEX_OUT_OF_BOUNDS);
EXPECT_EQ(tet.Status(), Manifold::Error::PROPERTIES_WRONG_LENGTH);
EXPECT_TRUE(tet.IsManifold());
}
TEST(Manifold, InvalidInput6) {
std::vector<float> propTol = {0.1, 0.2};
std::vector<float> prop = {0, 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6};
std::vector<glm::ivec3> triProp = {{2, 0, 1}, {0, 3, 1}, {2, 3, 0}};
Manifold tet(Tet(), triProp, prop, propTol);
EXPECT_TRUE(tet.IsEmpty());
EXPECT_EQ(tet.Status(), Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH);
EXPECT_TRUE(tet.IsManifold());
}
TEST(Manifold, InvalidInput7) {
MeshGL tetGL = TetGL();
tetGL.triVerts[tetGL.triVerts.size() - 1] = 7;
Manifold tet(tetGL);
std::vector<float> propTol = {0.1, 0.2};
std::vector<float> prop = {0, 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5, 6, -6};
std::vector<glm::ivec3> triProp = {
{2, 0, 1}, {0, 3, 1}, {2, 3, 0}, {6, 5, 7}};
Manifold tet(Tet(), triProp, prop, propTol);
EXPECT_TRUE(tet.IsEmpty());
EXPECT_EQ(tet.Status(), Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS);
EXPECT_EQ(tet.Status(), Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
EXPECT_TRUE(tet.IsManifold());
}
@@ -466,21 +308,24 @@ TEST(Manifold, InvalidInput7) {
*/
TEST(Manifold, Decompose) {
std::vector<Manifold> manifoldList;
manifoldList.emplace_back(Manifold::Tetrahedron());
manifoldList.emplace_back(Manifold::Cube());
manifoldList.emplace_back(Manifold::Sphere(1, 4));
manifoldList.push_back(Manifold::Tetrahedron());
manifoldList.push_back(Manifold::Cube());
manifoldList.push_back(Manifold::Sphere(1, 4));
Manifold manifolds = Manifold::Compose(manifoldList);
ExpectMeshes(manifolds, {{8, 12}, {6, 8}, {4, 4}});
std::vector<MeshGL> input;
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
for (const Manifold& manifold : manifoldList) {
EXPECT_GE(manifold.OriginalID(), 0);
input.emplace_back(manifold.GetMeshGL());
int meshID = manifold.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(manifold.GetMesh());
}
RelatedGL(manifolds, input);
Related(manifolds, input, meshID2idx);
}
/**
@@ -602,11 +447,12 @@ TEST(Manifold, ManualSmooth) {
options.mat.roughness = 0.1;
options.mat.vertColor.resize(interp.NumVert());
MeshRelation rel = interp.GetMeshRelation();
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 glm::vec3& uvw = rel.UVW(tri, i);
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));
@@ -617,18 +463,6 @@ TEST(Manifold, ManualSmooth) {
#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);
@@ -645,11 +479,12 @@ TEST(Manifold, Csaszar) {
options.mat.roughness = 0.1;
options.mat.vertColor.resize(csaszar.NumVert());
MeshRelation rel = csaszar.GetMeshRelation();
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 glm::vec3& uvw = rel.barycentric[rel.triBary[tri].vertBary[i]];
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));
@@ -672,7 +507,7 @@ TEST(Manifold, GetProperties) {
cube = cube.Scale(glm::vec3(-1.0f));
prop = cube.GetProperties();
EXPECT_FLOAT_EQ(prop.volume, 1.0f);
EXPECT_FLOAT_EQ(prop.volume, -1.0f);
EXPECT_FLOAT_EQ(prop.surfaceArea, 6.0f);
}
@@ -751,54 +586,39 @@ TEST(Manifold, Transform) {
TEST(Manifold, MeshRelation) {
const float period = glm::two_pi<float>();
Mesh gyroidMesh = LevelSet(Gyroid(), {glm::vec3(0), glm::vec3(period)}, 3);
MeshGL gyroidMeshGL = WithIndexColors(gyroidMesh);
Manifold gyroid(gyroidMeshGL);
Mesh gyroidMesh = LevelSet(Gyroid(), {glm::vec3(0), glm::vec3(period)}, 0.5);
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
input.push_back(gyroidMesh);
Manifold gyroid(input[0]);
#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);
if (options.exportModels) ExportMesh("gyroid.glb", gyroid.GetMesh(), {});
#endif
RelatedGL(gyroid, {gyroidMeshGL});
int meshID = gyroid.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size() - 1;
Related(gyroid, input, meshID2idx);
}
TEST(Manifold, MeshRelationRefine) {
const Mesh in = Csaszar();
MeshGL inGL = WithIndexColors(in);
Manifold csaszar(inGL);
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
RelatedGL(csaszar, {inGL});
input.push_back(Csaszar());
Manifold csaszar(input[0]);
int meshID = csaszar.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size() - 1;
Related(csaszar, input, meshID2idx);
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.originalID.size(), 1);
ASSERT_EQ(outGL.originalID.size(), 1);
ASSERT_EQ(outGL.originalID[0], inGL.originalID[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);
Related(csaszar, input, meshID2idx);
}
/**
@@ -806,76 +626,15 @@ TEST(Manifold, FaceIDRoundTrip) {
*/
TEST(Boolean, Tetra) {
Manifold tetra = Manifold::Tetrahedron();
MeshGL tetraGL = WithPositionColors(tetra);
tetra = Manifold(tetraGL);
EXPECT_TRUE(!tetra.IsEmpty());
EXPECT_TRUE(tetra.IsManifold());
Manifold tetra2 = tetra.Translate(glm::vec3(0.5f));
Manifold tetra2 = tetra;
tetra2 = tetra2.Translate(glm::vec3(0.5f)).AsOriginal();
Manifold result = tetra2 - tetra;
ExpectMeshes(result, {{8, 12, 3, 11}});
ExpectMeshes(result, {{8, 12}});
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.originalID.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.originalID.size(), 2);
}
TEST(Boolean, Normals) {
const MeshGL cubeGL = CubeGL();
const Manifold cube(cubeGL);
const MeshGL sphereGL = WithNormals(Manifold::Sphere(60));
const Manifold sphere(sphereGL);
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, {cubeGL, sphereGL}, true);
}
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);
RelatedOp(tetra, tetra2, result);
}
/**
@@ -911,51 +670,20 @@ TEST(Boolean, Perturb) {
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 cylinder2 = cylinder.AsOriginal();
cylinder2 = cylinder2.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}});
ExpectMeshes(out, {{32, 64}});
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);
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMesh(), {});
#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});
RelatedOp(cylinder, cylinder2, out);
}
TEST(Boolean, MultiCoplanar) {
@@ -1082,8 +810,8 @@ TEST(Boolean, Empty) {
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));
cubes.push_back(Manifold::Cube(glm::vec3(3.0f), true));
cubes.push_back(Manifold::Cube(glm::vec3(2.0f), true));
Manifold doubled = Manifold::Compose(cubes);
Manifold cube = Manifold::Cube(glm::vec3(1.0f), true);
@@ -1136,39 +864,36 @@ TEST(Boolean, Precision2) {
*/
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 sphere2 = sphere;
sphere2 = sphere2.Translate(glm::vec3(0.5));
sphere2 = sphere2.AsOriginal();
Manifold result = sphere - sphere2;
ExpectMeshes(result, {{74, 144, 3, 110}});
ExpectMeshes(result, {{74, 144}});
EXPECT_EQ(result.NumDegenerateTris(), 0);
RelatedGL(result, {sphereGL});
RelatedOp(sphere, sphere2, result);
}
TEST(Boolean, MeshRelation) {
const float period = glm::two_pi<float>();
Mesh gyroidMesh = LevelSet(Gyroid(), {glm::vec3(0), glm::vec3(period)}, 0.5);
MeshGL gyroidMeshGL = WithPositionColors(gyroidMesh);
Manifold gyroid(gyroidMeshGL);
Manifold gyroid(gyroidMesh);
Manifold gyroid2 = gyroid.Translate(glm::vec3(2.0f));
Mesh gyroidMesh2 = gyroidMesh;
std::transform(gyroidMesh.vertPos.begin(), gyroidMesh.vertPos.end(),
gyroidMesh2.vertPos.begin(),
[](const glm::vec3& v) { return v + glm::vec3(2.0f); });
Manifold gyroid2(gyroidMesh2);
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);
if (options.exportModels) ExportMesh("gyroidUnion.glb", result.GetMesh(), {});
#endif
EXPECT_TRUE(result.IsManifold());
@@ -1179,7 +904,20 @@ TEST(Boolean, MeshRelation) {
EXPECT_NEAR(prop.volume, 226, 1);
EXPECT_NEAR(prop.surfaceArea, 387, 1);
RelatedGL(result, {gyroidMeshGL});
std::vector<Mesh> input;
std::map<int, int> meshID2idx;
int meshID = gyroid.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(gyroidMesh);
meshID = gyroid2.OriginalID();
EXPECT_GE(meshID, 0);
meshID2idx[meshID] = input.size();
input.push_back(gyroidMesh2);
Related(result, input, meshID2idx);
}
TEST(Boolean, Cylinders) {
@@ -1315,8 +1053,7 @@ TEST(Boolean, Subtract) {
first.GetMesh();
}
// FIXME: test is failing on Mac CI (passing on others)
TEST(Boolean, DISABLED_Close) {
TEST(Boolean, Close) {
PolygonParams().processOverlaps = true;
const float r = 10;