Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d564b675b8 | ||
|
|
ef60fb6da1 | ||
|
|
6e0728a764 | ||
|
|
6a891bea18 | ||
|
|
c87d0d9c24 | ||
|
|
4415cfce46 | ||
|
|
bbb1fabcac | ||
|
|
f43dded234 | ||
|
|
05f9ceb999 | ||
|
|
422c7f26d9 | ||
|
|
a701d2be8e | ||
|
|
5b5f56674a | ||
|
|
1b7b365c3a | ||
|
|
812b056179 | ||
|
|
bd46bfd1cf | ||
|
|
a7b457db74 | ||
|
|
87059508e7 |
@@ -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,11 +12,12 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
cuda_support: [ON, OFF]
|
||||
parallel_backend: [NONE, OMP, TBB]
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
if: github.event.pull_request.draft == false
|
||||
container:
|
||||
image: docker://nvidia/cuda:11.6.0-devel-ubuntu20.04
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} .. && make
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
# note that the test for CUDA backend does not really test CUDA, as we
|
||||
# don't have CUDA GPU on GitHub Action
|
||||
@@ -52,7 +53,7 @@ jobs:
|
||||
if: matrix.parallel_backend == 'NONE' && matrix.cuda_support == 'OFF'
|
||||
run: |
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=OFF -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -DCODE_COVERAGE=ON .. && make
|
||||
lcov --capture --initial --directory . --output-file ./code_coverage_init.info
|
||||
cd test
|
||||
./manifold_test
|
||||
@@ -67,7 +68,36 @@ 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:
|
||||
@@ -111,6 +141,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
build_windows:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE]
|
||||
@@ -144,7 +175,7 @@ jobs:
|
||||
- name: Build ${{matrix.backend}}
|
||||
shell: powershell
|
||||
run: |
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cmake . -DCMAKE_BUILD_TYPE=Release -B build -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} -DMANIFOLD_USE_CUDA=${{matrix.cuda_support}} -A x64
|
||||
cd build
|
||||
cmake --build . --target ALL_BUILD --config Release
|
||||
- name: Test ${{matrix.parallel_backend}} with CUDA ${{matrix.cuda_support}}
|
||||
@@ -154,6 +185,7 @@ jobs:
|
||||
./Release/manifold_test.exe
|
||||
|
||||
build_mac:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
parallel_backend: [NONE, TBB]
|
||||
@@ -175,13 +207,14 @@ jobs:
|
||||
git apply thrust.diff
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DMANIFOLD_DEBUG=ON -DMANIFOLD_PYBIND=ON -DMANIFOLD_PAR=${{matrix.parallel_backend}} .. && make
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/test
|
||||
./manifold_test
|
||||
|
||||
build_nix:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
variant: [none, omp, tbb, none-cuda, omp-cuda, tbb-cuda, js]
|
||||
@@ -191,4 +224,3 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: cachix/install-nix-action@v15
|
||||
- run: nix build -L '.?submodules=1#manifold-${{matrix.variant}}'
|
||||
|
||||
|
||||
Vendored
+11
-1
@@ -104,7 +104,7 @@
|
||||
"editor.formatOnSave": true,
|
||||
"cmake.configureArgs": [
|
||||
"-DBUILD_SHARED_LIBS=ON",
|
||||
"-DMANIFOLD_EXPORT=OFF",
|
||||
"-DMANIFOLD_EXPORT=ON",
|
||||
"-DMANIFOLD_DEBUG=ON",
|
||||
"-DMANIFOLD_USE_CUDA=OFF",
|
||||
"-DMANIFOLD_PAR=NONE",
|
||||
@@ -118,4 +118,14 @@
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"editor.detectIndentation": false,
|
||||
"cSpell.words": [
|
||||
"Bary",
|
||||
"csaszar",
|
||||
"Gyroid",
|
||||
"halfedge",
|
||||
"halfedges",
|
||||
"Tris",
|
||||
"Verts"
|
||||
],
|
||||
"javascript.format.semicolons": "insert",
|
||||
}
|
||||
+20
-7
@@ -18,9 +18,11 @@ project(manifold LANGUAGES CXX)
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
option(MANIFOLD_EXPORT off)
|
||||
option(MANIFOLD_DEBUG off)
|
||||
option(MANIFOLD_USE_CUDA off)
|
||||
option(MANIFOLD_EXPORT OFF)
|
||||
option(MANIFOLD_DEBUG OFF)
|
||||
option(MANIFOLD_USE_CUDA OFF)
|
||||
option(MANIFOLD_PYBIND ON)
|
||||
option(MANIFOLD_CBIND OFF)
|
||||
set(MANIFOLD_PAR "NONE" CACHE STRING "Parallel backend, either \"TBB\" or \"OpenMP\" or \"NONE\"")
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
@@ -29,15 +31,26 @@ if(EMSCRIPTEN)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -sALLOW_MEMORY_GROWTH=1)
|
||||
endif()
|
||||
|
||||
option(PYBIND11_FINDPYTHON on)
|
||||
option(PYBIND11_FINDPYTHON ON)
|
||||
|
||||
option(BUILD_TEST_CGAL off)
|
||||
option(BUILD_TEST_CGAL OFF)
|
||||
|
||||
option(BUILD_SHARED_LIBS off)
|
||||
option(BUILD_SHARED_LIBS OFF)
|
||||
set(GLM_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/glm)
|
||||
set(PYBIND11_DIR ${PROJECT_SOURCE_DIR}/bindings/python/third_party/pybind11)
|
||||
set(THRUST_INC_DIR ${PROJECT_SOURCE_DIR}/src/third_party/thrust)
|
||||
|
||||
if(MANIFOLD_CBIND)
|
||||
set(MANIFOLD_EXPORT ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_SHARED_LIBS OR MANIFOLD_CBIND)
|
||||
# Allow shared libraries to be relocatable (add Place Independent Code flag).
|
||||
# Also include when statically linking C bindings to avoid issues with bundling
|
||||
# artefacts in host languages using them for FFI.
|
||||
add_compile_options(-fPIC)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
enable_language(CUDA)
|
||||
find_package(CUDA REQUIRED)
|
||||
@@ -72,4 +85,4 @@ add_subdirectory(src)
|
||||
add_subdirectory(samples)
|
||||
add_subdirectory(test)
|
||||
add_subdirectory(extras)
|
||||
add_subdirectory(bindings)
|
||||
add_subdirectory(bindings)
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_CBIND)
|
||||
add_subdirectory(c)
|
||||
endif()
|
||||
|
||||
if(NOT EMSCRIPTEN AND MANIFOLD_PYBIND)
|
||||
add_subdirectory(python)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
project(manifoldc)
|
||||
|
||||
|
||||
add_library(manifoldc manifoldc.cpp conv.cpp)
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(manifoldc.cpp conv.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET manifoldc PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
manifoldc
|
||||
PRIVATE manifold sdf meshIO graphlite
|
||||
)
|
||||
|
||||
target_include_directories(manifoldc PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_compile_options(manifoldc PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(manifoldc PRIVATE cxx_std_17)
|
||||
@@ -0,0 +1,94 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <public.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
ManifoldBox *manifold_box(void *mem, float x1, float y1, float z1, float x2,
|
||||
float y2, float z2) {
|
||||
auto p1 = glm::vec3(x1, y1, z1);
|
||||
auto p2 = glm::vec3(x2, y2, z2);
|
||||
auto box = new (mem) Box(p1, p2);
|
||||
return to_c(box);
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_min(ManifoldBox *b) { return to_c((*from_c(b)).min); }
|
||||
|
||||
ManifoldVec3 manifold_box_max(ManifoldBox *b) { return to_c((*from_c(b)).max); }
|
||||
|
||||
ManifoldVec3 manifold_box_dimensions(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
auto v = box.Size();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
ManifoldVec3 manifold_box_center(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
auto v = box.Center();
|
||||
return {v.x, v.y, v.z};
|
||||
}
|
||||
|
||||
float manifold_box_scale(ManifoldBox *b) {
|
||||
auto box = *from_c(b);
|
||||
return box.Scale();
|
||||
}
|
||||
|
||||
int manifold_box_contains_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return box.Contains(p);
|
||||
}
|
||||
|
||||
int manifold_box_contains_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
auto outer = *from_c(a);
|
||||
auto inner = *from_c(b);
|
||||
return outer.Contains(inner);
|
||||
}
|
||||
|
||||
void manifold_box_include_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
box.Union(p);
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_union(void *mem, ManifoldBox *a, ManifoldBox *b) {
|
||||
auto box = (*from_c(a)).Union(*from_c(b));
|
||||
return to_c(new (mem) Box(box));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_transform(void *mem, ManifoldBox *b, float x1,
|
||||
float y1, float z1, float x2, float y2,
|
||||
float z2, float x3, float y3, float z3,
|
||||
float x4, float y4, float z4) {
|
||||
auto mat = glm::mat4x3(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4);
|
||||
auto transformed = (*from_c(b)).Transform(mat);
|
||||
return to_c(new (mem) Box(transformed));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_translate(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto translated = (*from_c(b)) + p;
|
||||
return to_c(new (mem) Box(translated));
|
||||
}
|
||||
|
||||
ManifoldBox *manifold_box_mul(void *mem, ManifoldBox *b, float x, float y,
|
||||
float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
auto scaled = (*from_c(b)) * p;
|
||||
return to_c(new (mem) Box(scaled));
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_pt(ManifoldBox *b, float x, float y, float z) {
|
||||
auto box = *from_c(b);
|
||||
auto p = glm::vec3(x, y, z);
|
||||
return box.DoesOverlap(p);
|
||||
}
|
||||
|
||||
int manifold_box_does_overlap_box(ManifoldBox *a, ManifoldBox *b) {
|
||||
return (*from_c(a)).DoesOverlap(*from_c(b));
|
||||
}
|
||||
|
||||
int manifold_box_is_finite(ManifoldBox *b) { return (*from_c(b)).IsFinite(); }
|
||||
@@ -0,0 +1,166 @@
|
||||
#include <conv.h>
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <sdf.h>
|
||||
|
||||
#include "include/types.h"
|
||||
#include "public.h"
|
||||
#include "types.h"
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m) {
|
||||
return reinterpret_cast<ManifoldManifold *>(m);
|
||||
}
|
||||
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *m) {
|
||||
return reinterpret_cast<ManifoldSimplePolygon *>(m);
|
||||
}
|
||||
|
||||
ManifoldPolygons *to_c(manifold::Polygons *m) {
|
||||
return reinterpret_cast<ManifoldPolygons *>(m);
|
||||
}
|
||||
|
||||
ManifoldMesh *to_c(manifold::Mesh *m) {
|
||||
return reinterpret_cast<ManifoldMesh *>(m);
|
||||
}
|
||||
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m) {
|
||||
return reinterpret_cast<ManifoldMeshGL *>(m);
|
||||
}
|
||||
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m) {
|
||||
return reinterpret_cast<ManifoldCurvature *>(m);
|
||||
}
|
||||
|
||||
ManifoldComponents *to_c(manifold::Components *components) {
|
||||
return reinterpret_cast<ManifoldComponents *>(components);
|
||||
}
|
||||
|
||||
ManifoldError to_c(manifold::Manifold::Error error) {
|
||||
ManifoldError e = 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;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <manifold.h>
|
||||
#include <meshIO.h>
|
||||
#include <public.h>
|
||||
#include <sdf.h>
|
||||
#include <types.h>
|
||||
|
||||
ManifoldManifold *to_c(manifold::Manifold *m);
|
||||
ManifoldSimplePolygon *to_c(manifold::SimplePolygon *p);
|
||||
ManifoldPolygons *to_c(manifold::Polygons *ps);
|
||||
ManifoldMesh *to_c(manifold::Mesh *m);
|
||||
ManifoldMeshGL *to_c(manifold::MeshGL *m);
|
||||
ManifoldBox *to_c(manifold::Box *m);
|
||||
ManifoldCurvature *to_c(manifold::Curvature *m);
|
||||
ManifoldComponents *to_c(manifold::Components *components);
|
||||
ManifoldError to_c(manifold::Manifold::Error error);
|
||||
ManifoldMaterial *to_c(manifold::Material *m);
|
||||
ManifoldExportOptions *to_c(manifold::ExportOptions *m);
|
||||
ManifoldVec3 to_c(glm::vec3 v);
|
||||
ManifoldIVec3 to_c(glm::ivec3 v);
|
||||
ManifoldProperties to_c(manifold::Properties p);
|
||||
|
||||
const manifold::Manifold *from_c(ManifoldManifold *m);
|
||||
const manifold::SimplePolygon *from_c(ManifoldSimplePolygon *m);
|
||||
const manifold::Polygons *from_c(ManifoldPolygons *m);
|
||||
const manifold::Mesh *from_c(ManifoldMesh *m);
|
||||
const manifold::MeshGL *from_c(ManifoldMeshGL *m);
|
||||
const manifold::Box *from_c(ManifoldBox *m);
|
||||
const manifold::Curvature *from_c(ManifoldCurvature *m);
|
||||
const manifold::Components *from_c(ManifoldComponents *components);
|
||||
manifold::Material *from_c(ManifoldMaterial *mat);
|
||||
manifold::ExportOptions *from_c(ManifoldExportOptions *options);
|
||||
glm::vec3 from_c(ManifoldVec3 v);
|
||||
glm::ivec3 from_c(ManifoldIVec3 v);
|
||||
glm::vec4 from_c(ManifoldVec4 v);
|
||||
|
||||
std::vector<glm::vec3> vector_of_vec_array(ManifoldVec3 *vs, size_t length);
|
||||
std::vector<glm::ivec3> vector_of_vec_array(ManifoldIVec3 *vs, size_t length);
|
||||
std::vector<glm::vec4> vector_of_vec_array(ManifoldVec4 *vs, size_t length);
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> vector_of_array(T *ts, size_t length) {
|
||||
auto vec = std::vector<T>();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
vec.push_back(ts[i]);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *copy_data(void *mem, std::vector<T> v) {
|
||||
T *ts = reinterpret_cast<T *>(mem);
|
||||
memcpy(ts, v.data(), sizeof(T) * v.size());
|
||||
return ts;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
#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
|
||||
@@ -0,0 +1,74 @@
|
||||
#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;
|
||||
@@ -0,0 +1,454 @@
|
||||
#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
|
||||
@@ -0,0 +1,72 @@
|
||||
#include "meshIO.h"
|
||||
|
||||
#include <conv.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
ManifoldMaterial *manifold_material(void *mem) {
|
||||
return to_c(new (mem) manifold::Material());
|
||||
}
|
||||
|
||||
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness) {
|
||||
from_c(mat)->roughness = roughness;
|
||||
}
|
||||
|
||||
void manifold_material_set_metalness(ManifoldMaterial *mat, float metalness) {
|
||||
from_c(mat)->metalness = metalness;
|
||||
}
|
||||
|
||||
void manifold_material_set_color(ManifoldMaterial *mat, ManifoldVec4 color) {
|
||||
from_c(mat)->color = from_c(color);
|
||||
}
|
||||
|
||||
void manifold_material_set_vert_color(ManifoldMaterial *mat,
|
||||
ManifoldVec4 *vert_color, size_t n_vert) {
|
||||
from_c(mat)->vertColor = vector_of_vec_array(vert_color, n_vert);
|
||||
}
|
||||
|
||||
ManifoldExportOptions *manifold_export_options(void *mem) {
|
||||
return to_c(new (mem) manifold::ExportOptions());
|
||||
}
|
||||
|
||||
void manifold_export_options_set_faceted(ManifoldExportOptions *options,
|
||||
int faceted) {
|
||||
from_c(options)->faceted = faceted;
|
||||
}
|
||||
|
||||
void manifold_export_options_set_material(ManifoldExportOptions *options,
|
||||
ManifoldMaterial *mat) {
|
||||
from_c(options)->mat = *from_c(mat);
|
||||
}
|
||||
|
||||
void manifold_export_meshgl(const char *filename, ManifoldMeshGL *mesh,
|
||||
ManifoldExportOptions *options) {
|
||||
manifold::ExportMesh(std::string(filename), *from_c(mesh), *from_c(options));
|
||||
}
|
||||
|
||||
// memory size
|
||||
size_t manifold_material_size() { return sizeof(manifold::Material); }
|
||||
|
||||
size_t manifold_export_options_size() {
|
||||
return sizeof(manifold::ExportOptions);
|
||||
}
|
||||
|
||||
// memory free + destruction
|
||||
void manifold_delete_material(ManifoldMaterial *m) { delete (from_c(m)); }
|
||||
|
||||
void manifold_delete_export_options(ManifoldExportOptions *m) {
|
||||
delete (from_c(m));
|
||||
}
|
||||
|
||||
// destruction
|
||||
void manifold_destruct_material(ManifoldMaterial *m) { from_c(m)->~Material(); }
|
||||
|
||||
void manifold_destruct_export_options(ManifoldExportOptions *m) {
|
||||
from_c(m)->~ExportOptions();
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -18,7 +18,7 @@ add_subdirectory(third_party)
|
||||
pybind11_add_module(pymanifold pymanifold.cpp)
|
||||
target_link_libraries(pymanifold PRIVATE manifold)
|
||||
target_compile_options(pymanifold PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_14)
|
||||
target_compile_features(pymanifold PUBLIC cxx_std_17)
|
||||
target_include_directories(pymanifold
|
||||
PRIVATE ${PYBIND11_DIR}/include
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from pymanifold import Manifold
|
||||
|
||||
def run():
|
||||
# for some reason this causes collider error
|
||||
obj = Manifold.cube()
|
||||
obj += Manifold([Manifold.cube()]).rotate(0, 0, 45+180)
|
||||
return obj
|
||||
@@ -40,8 +40,16 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
.def(py::init<>())
|
||||
.def(py::init([](std::vector<Manifold> &manifolds) {
|
||||
Manifold result;
|
||||
for (Manifold &manifold : manifolds) result += manifold;
|
||||
return result;
|
||||
if (manifolds.size() >= 1) {
|
||||
// for some reason using Manifold() as the initial object
|
||||
// will cause failure for python specifically
|
||||
// unable to reproduce with c++ directly
|
||||
Manifold first = manifolds[0];
|
||||
for (int i = 1; i < manifolds.size(); i++) first += manifolds[i];
|
||||
return first;
|
||||
} else {
|
||||
return Manifold();
|
||||
}
|
||||
}),
|
||||
"Construct manifold as the union of a set of manifolds.")
|
||||
.def(py::self + py::self, "Boolean union.")
|
||||
@@ -196,53 +204,53 @@ PYBIND11_MODULE(pymanifold, m) {
|
||||
":param n: The number of pieces to split every edge into. Must be > "
|
||||
"1.")
|
||||
.def("to_mesh", &Manifold::GetMesh)
|
||||
.def_static("smooth", Manifold::Smooth,
|
||||
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_static(
|
||||
"smooth", [](const Mesh &mesh) { return Manifold::Smooth(mesh); },
|
||||
"Constructs a smooth version of the input mesh by creating "
|
||||
"tangents; this\n"
|
||||
"method will throw if you have supplied tangents with your "
|
||||
"mesh already. The\n"
|
||||
"actual triangle resolution is unchanged; use the Refine() "
|
||||
"method to\n"
|
||||
"interpolate to a higher-resolution curve.\n"
|
||||
"\n"
|
||||
"By default, every edge is calculated for maximum smoothness "
|
||||
"(very much\n"
|
||||
"approximately), attempting to minimize the maximum mean "
|
||||
"Curvature magnitude.\n"
|
||||
"No higher-order derivatives are considered, as the "
|
||||
"interpolation is\n"
|
||||
"independent per triangle, only sharing constraints on their "
|
||||
"boundaries.\n"
|
||||
"\n"
|
||||
":param mesh: input Mesh.\n"
|
||||
":param sharpenedEdges: If desired, you can supply a vector "
|
||||
"of sharpened\n"
|
||||
"halfedges, which should in general be a small subset of all "
|
||||
"halfedges. Order\n"
|
||||
"of entries doesn't matter, as each one specifies the "
|
||||
"desired smoothness\n"
|
||||
"(between zero and one, with one the default for all "
|
||||
"unspecified halfedges)\n"
|
||||
"and the halfedge index (3 * triangle index + [0,1,2] where "
|
||||
"0 is the edge\n"
|
||||
"between triVert 0 and 1, etc).\n"
|
||||
"\n"
|
||||
"At a smoothness value of zero, a sharp crease is made. The "
|
||||
"smoothness is\n"
|
||||
"interpolated along each edge, so the specified value should "
|
||||
"be thought of as\n"
|
||||
"an average. Where exactly two sharpened edges meet at a "
|
||||
"vertex, their\n"
|
||||
"tangents are rotated to be colinear so that the sharpened "
|
||||
"edge can be\n"
|
||||
"continuous. Vertices with only one sharpened edge are "
|
||||
"completely smooth,\n"
|
||||
"allowing sharpened edges to smoothly vanish at termination. "
|
||||
"A single vertex\n"
|
||||
"can be sharpened by sharping all edges that are incident on "
|
||||
"it, allowing\n"
|
||||
"cones to be formed.")
|
||||
.def_static(
|
||||
"from_mesh", [](const Mesh &mesh) { return Manifold(mesh); },
|
||||
py::arg("mesh"))
|
||||
|
||||
@@ -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_14)
|
||||
target_compile_features(manifoldjs PUBLIC cxx_std_17)
|
||||
set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")
|
||||
|
||||
file(COPY examples;test;. DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -35,4 +35,3 @@ add_custom_command(
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
|
||||
${CMAKE_CURRENT_BINARY_DIR}/examples/)
|
||||
|
||||
+51
-25
@@ -15,6 +15,8 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace emscripten;
|
||||
|
||||
#include <manifold.h>
|
||||
@@ -60,15 +62,29 @@ 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("vertPos",
|
||||
val(typed_memory_view(mesh.vertPos.size(), mesh.vertPos.data()))
|
||||
meshJS.set("vertProperties",
|
||||
val(typed_memory_view(mesh.vertProperties.size(),
|
||||
mesh.vertProperties.data()))
|
||||
.call<val>("slice"));
|
||||
meshJS.set("vertNormal", val(typed_memory_view(mesh.vertNormal.size(),
|
||||
mesh.vertNormal.data()))
|
||||
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()))
|
||||
.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()))
|
||||
@@ -79,10 +95,26 @@ 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.vertPos = convertJSArrayToNumberVector<float>(mesh["vertPos"]);
|
||||
if (mesh["vertNormal"] != val::undefined()) {
|
||||
out.vertNormal = convertJSArrayToNumberVector<float>(mesh["vertNormal"]);
|
||||
out.vertProperties =
|
||||
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
|
||||
if (mesh["mergeFromVert"] != val::undefined()) {
|
||||
out.mergeFromVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
|
||||
}
|
||||
if (mesh["mergeToVert"] != val::undefined()) {
|
||||
out.mergeToVert =
|
||||
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
|
||||
}
|
||||
if (mesh["runIndex"] != val::undefined()) {
|
||||
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
|
||||
}
|
||||
if (mesh["originalID"] != val::undefined()) {
|
||||
out.originalID = convertJSArrayToNumberVector<uint32_t>(mesh["originalID"]);
|
||||
}
|
||||
if (mesh["faceID"] != val::undefined()) {
|
||||
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
|
||||
}
|
||||
if (mesh["halfedgeTangent"] != val::undefined()) {
|
||||
out.halfedgeTangent =
|
||||
@@ -158,10 +190,15 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS)
|
||||
.value("PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_WRONG_LENGTH",
|
||||
Manifold::Error::TRI_PROPERTIES_WRONG_LENGTH)
|
||||
.value("TRI_PROPERTIES_OUT_OF_BOUNDS",
|
||||
Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
.value("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_object<Box>("box").field("min", &Box::min).field("max", &Box::max);
|
||||
|
||||
@@ -173,16 +210,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.field("surfaceArea", &Properties::surfaceArea)
|
||||
.field("volume", &Properties::volume);
|
||||
|
||||
value_object<BaryRef>("baryRef")
|
||||
.field("meshID", &BaryRef::meshID)
|
||||
.field("originalID", &BaryRef::originalID)
|
||||
.field("tri", &BaryRef::tri)
|
||||
.field("vertBary", &BaryRef::vertBary);
|
||||
|
||||
value_object<MeshRelation>("meshRelation")
|
||||
.field("barycentric", &MeshRelation::barycentric)
|
||||
.field("triBary", &MeshRelation::triBary);
|
||||
|
||||
value_object<Curvature>("curvature")
|
||||
.field("maxMeanCurvature", &Curvature::maxMeanCurvature)
|
||||
.field("minMeanCurvature", &Curvature::minMeanCurvature)
|
||||
@@ -198,7 +225,6 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
register_vector<float>("Vector_f32");
|
||||
register_vector<Manifold>("Vector_manifold");
|
||||
register_vector<Smoothness>("Vector_smoothness");
|
||||
register_vector<BaryRef>("Vector_baryRef");
|
||||
register_vector<glm::vec4>("Vector_vec4");
|
||||
|
||||
class_<Manifold>("Manifold")
|
||||
@@ -213,7 +239,8 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("_Translate", &Manifold::Translate)
|
||||
.function("_Rotate", &Manifold::Rotate)
|
||||
.function("_Scale", &Manifold::Scale)
|
||||
.function("_Decompose", &Manifold::Decompose)
|
||||
.function("_Decompose", select_overload<std::vector<Manifold>() const>(
|
||||
&Manifold::Decompose))
|
||||
.function("isEmpty", &Manifold::IsEmpty)
|
||||
.function("status", &Manifold::Status)
|
||||
.function("numVert", &Manifold::NumVert)
|
||||
@@ -225,8 +252,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
|
||||
.function("getProperties", &Manifold::GetProperties)
|
||||
.function("_getCurvature", &Manifold::GetCurvature)
|
||||
.function("originalID", &Manifold::OriginalID)
|
||||
.function("asOriginal", &Manifold::AsOriginal)
|
||||
.function("_getMeshRelation", &Manifold::GetMeshRelation);
|
||||
.function("asOriginal", &Manifold::AsOriginal);
|
||||
|
||||
function("_Cube", &Manifold::Cube);
|
||||
function("_Cylinder", &Manifold::Cylinder);
|
||||
|
||||
+77
-76
@@ -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,14 +123,24 @@ Module.setup = function() {
|
||||
|
||||
class Mesh {
|
||||
constructor({
|
||||
numProp = 3,
|
||||
triVerts = new Uint32Array(),
|
||||
vertPos = new Float32Array(),
|
||||
vertNormal,
|
||||
vertProperties = new Float32Array(),
|
||||
mergeFromVert,
|
||||
mergeToVert,
|
||||
runIndex,
|
||||
originalID,
|
||||
faceID,
|
||||
halfedgeTangent
|
||||
} = {}) {
|
||||
this.numProp = numProp;
|
||||
this.triVerts = triVerts;
|
||||
this.vertPos = vertPos;
|
||||
this.vertNormal = vertNormal;
|
||||
this.vertProperties = vertProperties;
|
||||
this.mergeFromVert = mergeFromVert;
|
||||
this.mergeToVert = mergeToVert;
|
||||
this.runIndex = runIndex;
|
||||
this.originalID = originalID;
|
||||
this.faceID = faceID;
|
||||
this.halfedgeTangent = halfedgeTangent;
|
||||
}
|
||||
|
||||
@@ -139,7 +149,7 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
get numVert() {
|
||||
return this.vertPos.length / 3;
|
||||
return this.vertProperties.length / this.numProp;
|
||||
}
|
||||
|
||||
verts(tri) {
|
||||
@@ -147,11 +157,11 @@ Module.setup = function() {
|
||||
}
|
||||
|
||||
position(vert) {
|
||||
return this.vertPos.subarray(3 * vert, 3 * (vert + 1));
|
||||
return this.vertProperties.subarray(numProp * vert, numProp * vert + 3);
|
||||
}
|
||||
|
||||
normal(vert) {
|
||||
return this.vertNormal.subarray(3 * vert, 3 * (vert + 1));
|
||||
extras(vert) {
|
||||
return this.vertProperties.subarray(numProp * vert + 3, numProp * (vert + 1));
|
||||
}
|
||||
|
||||
tangent(halfedge) {
|
||||
@@ -161,32 +171,11 @@ Module.setup = function() {
|
||||
|
||||
Module.Mesh = Mesh;
|
||||
|
||||
Module.Manifold.prototype.getMesh = function() {
|
||||
Module.Manifold.prototype.getMesh = function () {
|
||||
return new Mesh(this._GetMeshJS());
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.getMeshRelation = function() {
|
||||
const result = this._getMeshRelation();
|
||||
const oldBarycentric = result.barycentric;
|
||||
const oldTriBary = result.triBary;
|
||||
const conversion1 = v => [v.x, v.y, v.z];
|
||||
const conversion2 = v => [v[0], v[1], v[2]];
|
||||
const conversion3 = v => {
|
||||
return {
|
||||
meshID: v.meshID,
|
||||
originalID: v.originalID,
|
||||
tri: v.tri,
|
||||
vertBary: conversion2(v.vertBary)
|
||||
};
|
||||
};
|
||||
result.barycentric = fromVec(oldBarycentric, conversion1);
|
||||
result.triBary = fromVec(oldTriBary, conversion3);
|
||||
oldBarycentric.delete();
|
||||
oldTriBary.delete();
|
||||
return result;
|
||||
};
|
||||
|
||||
Module.Manifold.prototype.boundingBox = function() {
|
||||
Module.Manifold.prototype.boundingBox = function () {
|
||||
const result = this._boundingBox();
|
||||
return {
|
||||
min: ['x', 'y', 'z'].map(f => result.min[f]),
|
||||
@@ -209,11 +198,23 @@ Module.setup = function() {
|
||||
case Module.status.PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Properties have wrong length';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_WRONG_LENGTH.value:
|
||||
message = 'Tri properties have wrong length';
|
||||
case Module.status.MISSING_POSITION_PROPERTIES.value:
|
||||
message = 'Less than three properties';
|
||||
break;
|
||||
case Module.status.TRI_PROPERTIES_OUT_OF_BOUNDS.value:
|
||||
message = 'Tri properties out of bounds';
|
||||
case Module.status.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';
|
||||
}
|
||||
|
||||
const base = Error.apply(this, [message, ...args]);
|
||||
@@ -225,11 +226,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();
|
||||
@@ -242,30 +243,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);
|
||||
@@ -273,25 +274,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);
|
||||
@@ -299,12 +300,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');
|
||||
@@ -317,14 +318,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');
|
||||
|
||||
@@ -128,7 +128,7 @@ model-viewer {
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
}
|
||||
|
||||
.margin {
|
||||
.header .margin {
|
||||
font-size: 16px;
|
||||
margin: 1em;
|
||||
}
|
||||
@@ -211,4 +211,4 @@ model-viewer {
|
||||
.col {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+332
-332
@@ -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;
|
||||
}
|
||||
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
return result;
|
||||
},
|
||||
|
||||
Heart: function() {
|
||||
// Smooth, complex manifolds can be created using the warp() function. This
|
||||
// example recreates the Exploitable Heart by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:6190
|
||||
|
||||
const func = (v) => {
|
||||
const x2 = v[0] * v[0];
|
||||
const y2 = v[1] * v[1];
|
||||
const z = v[2];
|
||||
const z2 = z * z;
|
||||
const a = x2 + 9 / 4 * y2 + z2;
|
||||
const b = z * z2 * (x2 + 9 / 80 * y2);
|
||||
const a2 = a * a;
|
||||
const a3 = a * a2;
|
||||
|
||||
const step = (r) => {
|
||||
const r2 = r * r;
|
||||
const r4 = r2 * r2;
|
||||
// Taubin's function: https://mathworld.wolfram.com/HeartSurface.html
|
||||
const f = a3 * r4 * r2 - b * r4 * r - 3 * a2 * r4 + 3 * a * r2 - 1;
|
||||
// Derivative
|
||||
const df = 6 * a3 * r4 * r - 5 * b * r4 - 12 * a2 * r2 * r + 6 * a * r;
|
||||
return f / df;
|
||||
};
|
||||
// Newton's method for root finding
|
||||
let r = 1.5;
|
||||
let dr = 1;
|
||||
while (Math.abs(dr) > 0.0001) {
|
||||
dr = step(r);
|
||||
r -= dr;
|
||||
return frame;
|
||||
}
|
||||
// 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;
|
||||
},
|
||||
setMinCircularAngle(3);
|
||||
setMinCircularEdgeLength(0.5);
|
||||
const result = roundedFrame(100, 10);
|
||||
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
|
||||
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 height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
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 vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
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 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 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;
|
||||
},
|
||||
|
||||
vertPos.push(
|
||||
Scallop: function () {
|
||||
// A smoothed manifold demonstrating selective edge sharpening with smooth()
|
||||
// and refine(), see more details at:
|
||||
// https://elalish.blogspot.com/2022/03/smoothing-triangle-meshes.html
|
||||
|
||||
const height = 10;
|
||||
const radius = 30;
|
||||
const offset = 20;
|
||||
const wiggles = 12;
|
||||
const sharpness = 0.8;
|
||||
const n = 50;
|
||||
|
||||
const vertPos = [];
|
||||
const triVerts = [];
|
||||
vertPos.push(-offset, 0, height, -offset, 0, -height);
|
||||
const sharpenedEdges = [];
|
||||
|
||||
const delta = 3.14159 / wiggles;
|
||||
for (let i = 0; i < 2 * wiggles; ++i) {
|
||||
const theta = (i - wiggles) * delta;
|
||||
const amp = 0.5 * height * Math.max(Math.cos(0.8 * theta), 0);
|
||||
|
||||
vertPos.push(
|
||||
radius * Math.cos(theta), radius * Math.sin(theta),
|
||||
amp * (i % 2 == 0 ? 1 : -1));
|
||||
let j = i + 1;
|
||||
if (j == 2 * wiggles) j = 0;
|
||||
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.vertPos = Float32Array.from(vertPos);
|
||||
const result = smooth(scallop, sharpenedEdges).refine(n);
|
||||
return result;
|
||||
},
|
||||
const scallop = new Mesh();
|
||||
scallop.triVerts = Uint32Array.from(triVerts);
|
||||
scallop.vertProperties = 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 @@ exports.functions = {
|
||||
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)]));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
knot = compose(knots);
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
return knot;
|
||||
}
|
||||
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
},
|
||||
|
||||
MengerSponge: function() {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
|
||||
[0.0, -w]
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
// This recreates Matlab Knot by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:7080
|
||||
|
||||
const result = torusKnot(1, 3, 25, 10, 3.75);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
MengerSponge: function () {
|
||||
// This example demonstrates how symbolic perturbation correctly creates
|
||||
// holes even though the subtracted objects are exactly coplanar.
|
||||
|
||||
StretchyBracelet: function() {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
function vec2add(a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
}
|
||||
|
||||
function base(
|
||||
function fractal(holes, hole, w, position, depth, maxDepth) {
|
||||
w /= 3;
|
||||
holes.push(
|
||||
hole.scale([w, w, 1.0]).translate([position[0], position[1], 0.0]));
|
||||
if (depth == maxDepth) return;
|
||||
const offsets = [
|
||||
[-w, -w], [-w, 0.0], [-w, w], [0.0, w], [w, w], [w, 0.0], [w, -w],
|
||||
[0.0, -w]
|
||||
];
|
||||
for (let offset of offsets)
|
||||
fractal(holes, hole, w, vec2add(position, offset), depth + 1, maxDepth);
|
||||
}
|
||||
|
||||
function mengerSponge(n) {
|
||||
let result = cube([1, 1, 1], true);
|
||||
const holes = [];
|
||||
fractal(holes, result, 1.0, [0.0, 0.0], 1, n);
|
||||
|
||||
const hole = compose(holes);
|
||||
|
||||
result = difference(result, hole);
|
||||
result = difference(result, hole.rotate([90, 0, 0]));
|
||||
result = difference(result, hole.rotate([0, 90, 0]));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = mengerSponge(3).scale(100);
|
||||
return result;
|
||||
},
|
||||
|
||||
StretchyBracelet: function () {
|
||||
// Recreates Stretchy Bracelet by Emmett Lalish:
|
||||
// https://www.thingiverse.com/thing:13505
|
||||
|
||||
function base(
|
||||
width, radius, decorRadius, twistRadius, nDecor, innerRadius,
|
||||
outerRadius, cut, nCut, nDivision) {
|
||||
function rotate(v, theta) {
|
||||
return [
|
||||
v[0] * Math.cos(theta) - v[1] * Math.sin(theta),
|
||||
v[0] * Math.sin(theta) + v[1] * Math.cos(theta)
|
||||
];
|
||||
}
|
||||
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));
|
||||
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;
|
||||
}
|
||||
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));
|
||||
}
|
||||
width, radius - thickness, decorRadius, twistRadius, nDecor,
|
||||
innerRadius, outerRadius + 3 * adjThickness, cut, nCut,
|
||||
nDivision));
|
||||
}
|
||||
|
||||
const result = stretchyBracelet();
|
||||
return result;
|
||||
},
|
||||
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.
|
||||
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;
|
||||
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) +
|
||||
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]
|
||||
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);
|
||||
};
|
||||
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 =
|
||||
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]));
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
|
||||
exports.functionBodies = new Map();
|
||||
for (const [func, code] of Object.entries(exports.functions)) {
|
||||
const whole = code.toString();
|
||||
const lines = whole.split('\n');
|
||||
lines.splice(0, 1); // remove first line
|
||||
lines.splice(-2, 2); // remove last two lines
|
||||
// remove first six leading spaces
|
||||
const body = '\n' + lines.map(l => l.slice(6)).join('\n');
|
||||
|
||||
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 =
|
||||
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.
|
After Width: | Height: | Size: 107 KiB |
@@ -11,7 +11,7 @@
|
||||
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="background-color: rgb(162 136 179);">
|
||||
<div class="container header" 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>
|
||||
|
||||
@@ -38,7 +38,10 @@
|
||||
<body>
|
||||
<p>This example demonstrates feeding Manifold's output into <a
|
||||
href="https://modelviewer.dev/"><model-viewer></a>
|
||||
via the three.js GLTFExporter. The most basic three.js example is <a href="three.html">here</a>.
|
||||
via <a href="https://gltf-transform.donmccurdy.com/index.html">gltf-transform</a>.
|
||||
It also shows how to pass mesh properties like UV coordinates through Manifold and
|
||||
how to re-associate textures after operations.
|
||||
A more basic three.js example is <a href="three.html">here</a>.
|
||||
</p>
|
||||
<select>
|
||||
<option value="difference" selected>Difference</option>
|
||||
@@ -49,29 +52,58 @@
|
||||
</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>
|
||||
<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();
|
||||
|
||||
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 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 space = cubeUV().scale(50);
|
||||
const moon = sphereUV().scale(60);
|
||||
materialMap.set(space.originalID(), spaceIndices);
|
||||
materialMap.set(moon.originalID(), moonIndices);
|
||||
|
||||
const csg = function (operation) {
|
||||
mesh.geometry?.dispose();
|
||||
mesh.geometry = mesh2geometry(wasm[operation](manifold_1, manifold_2).getMesh());
|
||||
push2MV(mesh);
|
||||
push2MV(wasm[operation](space, moon));
|
||||
};
|
||||
|
||||
document.querySelector('select').onchange = function (event) {
|
||||
@@ -83,44 +115,154 @@
|
||||
|
||||
const mv = document.querySelector('model-viewer');
|
||||
let objectURL = null;
|
||||
const exporter = new THREE.GLTFExporter();
|
||||
|
||||
function push2MV(mesh) {
|
||||
exporter.parse(
|
||||
mesh,
|
||||
(gltf) => {
|
||||
const blob = new Blob([gltf], { type: 'application/octet-stream' });
|
||||
URL.revokeObjectURL(objectURL);
|
||||
objectURL = URL.createObjectURL(blob);
|
||||
mv.src = objectURL;
|
||||
},
|
||||
() => console.log('GLTF export failed!'),
|
||||
{ binary: true }
|
||||
);
|
||||
async function push2MV(manifold) {
|
||||
// From Z-up to Y-up (glTF)
|
||||
const mesh = manifold.rotate([-90, 0, 0]).getMesh();
|
||||
|
||||
for (const [i, id] of mesh.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;
|
||||
}
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
function cubeUV() {
|
||||
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);
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
// most of three.js geometries aren't manifolds, so...
|
||||
function simplify(geometry) {
|
||||
delete geometry.attributes.normal;
|
||||
delete geometry.attributes.uv;
|
||||
const simplified = THREE.BufferGeometryUtils.mergeVertices(geometry);
|
||||
simplified.computeVertexNormals();
|
||||
return simplified;
|
||||
function sphereUV() {
|
||||
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);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
@@ -1,119 +0,0 @@
|
||||
<!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 <model-viewer> 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>
|
||||
@@ -94,16 +94,14 @@
|
||||
|
||||
// functions to convert between three.js and wasm
|
||||
function geometry2mesh(geometry) {
|
||||
const vertPos = geometry.attributes.position.array;
|
||||
const vertNormal = geometry.attributes.normal.array;
|
||||
const vertProperties = geometry.attributes.position.array;
|
||||
const triVerts = geometry.index.array;
|
||||
return new wasm.Mesh({vertPos, vertNormal, triVerts});
|
||||
return new wasm.Mesh({ numProp: 3, vertProperties, triVerts });
|
||||
}
|
||||
|
||||
function mesh2geometry(mesh) {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertPos, 3));
|
||||
geometry.setAttribute('normal', new THREE.BufferAttribute(mesh.vertNormal, 3));
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
|
||||
@@ -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,30 +125,31 @@ 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.vertPos, 3));
|
||||
geometry.setAttribute('position', new THREE.BufferAttribute(mesh.vertProperties, 3));
|
||||
geometry.setIndex(new THREE.BufferAttribute(mesh.triVerts, 1));
|
||||
return geometry;
|
||||
}
|
||||
Vendored
+21
-43
@@ -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,12 +30,6 @@ type Properties = {
|
||||
surfaceArea: number,
|
||||
volume: number
|
||||
};
|
||||
type BaryRef = {
|
||||
meshID: number,
|
||||
originalID: number,
|
||||
tri: number,
|
||||
vertBary: Vec3
|
||||
};
|
||||
type Curvature = {
|
||||
maxMeanCurvature: number,
|
||||
minMeanCurvature: number,
|
||||
@@ -44,21 +38,22 @@ type Curvature = {
|
||||
vertMeanCurvature: number[],
|
||||
vertGaussianCurvature: number[]
|
||||
};
|
||||
type MeshRelation = {
|
||||
barycentric: Vec3[],
|
||||
triBary: BaryRef[],
|
||||
};
|
||||
|
||||
declare class Mesh {
|
||||
vertPos: Float32Array;
|
||||
numProp: number;
|
||||
vertProperties: Float32Array;
|
||||
triVerts: Uint32Array;
|
||||
vertNormal?: Float32Array;
|
||||
mergeFromVert?: Uint32Array;
|
||||
mergeToVert?: Uint32Array;
|
||||
runIndex?: Uint32Array;
|
||||
originalID?: Uint32Array;
|
||||
faceID?: Uint32Array;
|
||||
halfedgeTangent?: Float32Array;
|
||||
get numTri(): number;
|
||||
get numVert(): number;
|
||||
verts(tri: number): Uint32Array<3>;
|
||||
position(vert: number): Float32Array<3>;
|
||||
normal(vert: number): Float32Array<3>;
|
||||
extras(vert: number): Float32Array;
|
||||
tangent(halfedge: number): Float32Array<4>;
|
||||
}
|
||||
|
||||
@@ -102,7 +97,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
|
||||
@@ -217,26 +212,9 @@ 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 meshIDs to a new original,
|
||||
* (e.g. a different UV mapping), you can reset its IDs to a new original,
|
||||
* meaning it will now be referenced by its descendants instead of the meshes
|
||||
* it was built from, allowing you to differentiate the copies when applying
|
||||
* your properties to the final result.
|
||||
@@ -250,8 +228,8 @@ declare class Manifold {
|
||||
asOriginal(): Manifold;
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
* If this mesh is an original, this returns its ID that can be referenced
|
||||
* by product manifolds. If this manifold is a product, this
|
||||
* returns -1.
|
||||
*/
|
||||
originalID(): number;
|
||||
@@ -264,7 +242,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
|
||||
@@ -280,8 +258,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.
|
||||
@@ -348,8 +326,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
|
||||
@@ -363,7 +341,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;
|
||||
@@ -400,8 +378,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
@@ -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, 1, 'Volume');
|
||||
expect(result.volume).to.be.closeTo(353706, 10, '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, 1, 'Volume');
|
||||
expect(result.volume).to.be.closeTo(282743, 10, '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, 1, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 1, 'Surface Area');
|
||||
expect(result.volume).to.be.closeTo(406457, 10, 'Volume');
|
||||
expect(result.surfaceArea).to.be.closeTo(247590, 10, 'Surface Area');
|
||||
});
|
||||
|
||||
test('Stretchy Bracelet', () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ add_executable(perfTest perf_test.cpp)
|
||||
target_link_libraries(perfTest manifold)
|
||||
|
||||
target_compile_options(perfTest PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTest PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTest PUBLIC cxx_std_17)
|
||||
|
||||
if(BUILD_TEST_CGAL)
|
||||
add_executable(perfTestCGAL perf_test_cgal.cpp)
|
||||
@@ -29,5 +29,5 @@ if(BUILD_TEST_CGAL)
|
||||
target_link_libraries(perfTestCGAL manifold CGAL::CGAL CGAL::CGAL_Core)
|
||||
|
||||
target_compile_options(perfTestCGAL PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_14)
|
||||
target_compile_features(perfTestCGAL PUBLIC cxx_std_17)
|
||||
endif()
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
nativeBuildInputs = (with pkgs; [ cmake (python39.withPackages(ps: with ps; [trimesh])) ]) ++ build-tools ++
|
||||
(if cuda-support then with pkgs.cudaPackages; [ cuda_nvcc cuda_cudart cuda_cccl pkgs.addOpenGLRunpath ] else [ ]);
|
||||
cmakeFlags = [
|
||||
"-DMANIFOLD_PYBIND=ON"
|
||||
"-DMANIFOLD_PAR=${pkgs.lib.strings.toUpper parallel-backend}"
|
||||
"-DMANIFOLD_USE_CUDA=${if cuda-support then "ON" else "OFF"}"
|
||||
];
|
||||
|
||||
@@ -25,7 +25,7 @@ target_link_libraries(samples
|
||||
)
|
||||
|
||||
target_compile_options(samples PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samples PUBLIC cxx_std_14)
|
||||
target_compile_features(samples PUBLIC cxx_std_17)
|
||||
|
||||
project(samplesGPU)
|
||||
|
||||
@@ -46,6 +46,5 @@ target_link_libraries(samplesGPU
|
||||
|
||||
target_compile_options(samplesGPU PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(samplesGPU
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
@@ -52,6 +52,7 @@ 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;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (collider)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
@@ -25,8 +25,6 @@ endif()
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC utilities)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
||||
@@ -18,18 +18,16 @@ file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties( ${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
set_source_files_properties(${SOURCE_FILES} PROPERTIES LANGUAGE CUDA)
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include )
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC utilities
|
||||
PRIVATE collider polygon ${MANIFOLD_INCLUDE} graphlite
|
||||
)
|
||||
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_14
|
||||
PRIVATE cxx_std_17
|
||||
target_compile_features(${PROJECT_NAME}
|
||||
PUBLIC cxx_std_17
|
||||
)
|
||||
|
||||
|
||||
@@ -49,12 +49,11 @@ class Manifold {
|
||||
Manifold(Manifold&&) noexcept;
|
||||
Manifold& operator=(Manifold&&) noexcept;
|
||||
|
||||
Manifold(
|
||||
const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
Manifold(const MeshGL&, const std::vector<float>& propertyTolerance = {});
|
||||
Manifold(const Mesh&);
|
||||
|
||||
static Manifold Smooth(const MeshGL&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Smooth(const Mesh&,
|
||||
const std::vector<Smoothness>& sharpenedEdges = {});
|
||||
static Manifold Tetrahedron();
|
||||
@@ -75,6 +74,9 @@ class Manifold {
|
||||
*/
|
||||
///@{
|
||||
static Manifold Compose(const std::vector<Manifold>&);
|
||||
|
||||
Components GetComponents() const;
|
||||
std::vector<Manifold> Decompose(Components components) const;
|
||||
std::vector<Manifold> Decompose() const;
|
||||
///@}
|
||||
|
||||
@@ -98,7 +100,7 @@ class Manifold {
|
||||
*/
|
||||
///@{
|
||||
Mesh GetMesh() const;
|
||||
MeshGL GetMeshGL() const;
|
||||
MeshGL GetMeshGL(glm::ivec3 normalIdx = glm::ivec3(0)) const;
|
||||
bool IsEmpty() const;
|
||||
enum class Error {
|
||||
NO_ERROR,
|
||||
@@ -106,13 +108,19 @@ class Manifold {
|
||||
NOT_MANIFOLD,
|
||||
VERTEX_INDEX_OUT_OF_BOUNDS,
|
||||
PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_WRONG_LENGTH,
|
||||
TRI_PROPERTIES_OUT_OF_BOUNDS,
|
||||
MISSING_POSITION_PROPERTIES,
|
||||
MERGE_VECTORS_DIFFERENT_LENGTHS,
|
||||
MERGE_INDEX_OUT_OF_BOUNDS,
|
||||
TRANSFORM_WRONG_LENGTH,
|
||||
RUN_INDEX_WRONG_LENGTH,
|
||||
FACE_ID_WRONG_LENGTH,
|
||||
};
|
||||
Error Status() const;
|
||||
int NumVert() const;
|
||||
int NumEdge() const;
|
||||
int NumTri() const;
|
||||
int NumProp() const;
|
||||
int NumPropVert() const;
|
||||
Box BoundingBox() const;
|
||||
float Precision() const;
|
||||
int Genus() const;
|
||||
@@ -120,14 +128,14 @@ class Manifold {
|
||||
Curvature GetCurvature() const;
|
||||
///@}
|
||||
|
||||
/** @name Relation
|
||||
/** @name Mesh ID
|
||||
* Details of the manifold's relation to its input meshes, for the purposes
|
||||
* of reapplying mesh properties.
|
||||
*/
|
||||
///@{
|
||||
MeshRelation GetMeshRelation() const;
|
||||
int OriginalID() const;
|
||||
Manifold AsOriginal() const;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
///@}
|
||||
|
||||
/** @name Modification
|
||||
|
||||
+139
-108
@@ -197,17 +197,10 @@ std::vector<Halfedge> PairUp(std::vector<EdgePos> &edgePos) {
|
||||
return edges;
|
||||
}
|
||||
|
||||
// A Ref carries the reference of a halfedge's startVert back to the input
|
||||
// manifolds. PQ is 0 if the halfedge comes from triangle tri of P, and 1 for Q.
|
||||
// vert is 0, 1, or 2 to denote which vertex of tri it is, and -1 if it is new.
|
||||
struct Ref {
|
||||
int PQ, tri, vert;
|
||||
};
|
||||
|
||||
void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
VecDH<int> &facePtrR,
|
||||
std::map<int, std::vector<EdgePos>> &edgesP,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<int> &i03, const VecDH<int> &vP2R,
|
||||
const VecDH<int>::IterC faceP2R, bool forward) {
|
||||
// Each edge in the map is partially retained; for each of these, look up
|
||||
@@ -266,11 +259,8 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW. This is only valid if this is a retained vert; it
|
||||
// will be ignored later if the vert is new.
|
||||
const Ref forwardRef = {forward ? 0 : 1, faceLeftP,
|
||||
(edgeP + (reversed ? 1 : 0)) % 3};
|
||||
const Ref backwardRef = {
|
||||
forward ? 0 : 1, faceRightP,
|
||||
(halfedge.pairedHalfedge + (reversed ? 1 : 0)) % 3};
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
@@ -293,7 +283,8 @@ void AppendPartialEdges(Manifold::Impl &outR, VecDH<char> &wholeHalfedgeP,
|
||||
void AppendNewEdges(
|
||||
Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
std::map<std::pair<int, int>, std::vector<EdgePos>> &edgesNew,
|
||||
VecDH<Ref> &halfedgeRef, const VecDH<int> &facePQ2R, const int numFaceP) {
|
||||
VecDH<TriRef> &halfedgeRef, const VecDH<int> &facePQ2R,
|
||||
const int numFaceP) {
|
||||
// Pair up each edge's verts and distribute to faces based on indices in key.
|
||||
VecDH<Halfedge> &halfedgeR = outR.halfedge_;
|
||||
VecDH<glm::vec3> &vertPosR = outR.vertPos_;
|
||||
@@ -322,8 +313,8 @@ void AppendNewEdges(
|
||||
// add halfedges to result
|
||||
const int faceLeft = facePQ2R[faceP];
|
||||
const int faceRight = facePQ2R[numFaceP + faceQ];
|
||||
const Ref forwardRef = {0, faceP, -4};
|
||||
const Ref backwardRef = {1, faceQ, -4};
|
||||
const TriRef forwardRef = {0, -1, faceP};
|
||||
const TriRef backwardRef = {1, -1, faceQ};
|
||||
for (Halfedge e : edges) {
|
||||
const int forwardEdge = facePtrR[faceLeft]++;
|
||||
const int backwardEdge = facePtrR[faceRight]++;
|
||||
@@ -344,7 +335,7 @@ void AppendNewEdges(
|
||||
|
||||
struct DuplicateHalfedges {
|
||||
Halfedge *halfedgesR;
|
||||
Ref *halfedgeRef;
|
||||
TriRef *halfedgeRef;
|
||||
int *facePtr;
|
||||
const Halfedge *halfedgesP;
|
||||
const int *i03;
|
||||
@@ -374,11 +365,8 @@ struct DuplicateHalfedges {
|
||||
// Negative inclusion means the halfedges are reversed, which means our
|
||||
// reference is now to the endVert instead of the startVert, which is one
|
||||
// position advanced CCW.
|
||||
const Ref forwardRef = {forward ? 0 : 1, faceLeftP,
|
||||
(edgeP + (inclusion < 0 ? 1 : 0)) % 3};
|
||||
const Ref backwardRef = {
|
||||
forward ? 0 : 1, faceRightP,
|
||||
(halfedge.pairedHalfedge + (inclusion < 0 ? 1 : 0)) % 3};
|
||||
const TriRef forwardRef = {forward ? 0 : 1, -1, faceLeftP};
|
||||
const TriRef backwardRef = {forward ? 0 : 1, -1, faceRightP};
|
||||
|
||||
for (int i = 0; i < glm::abs(inclusion); ++i) {
|
||||
int forwardEdge = AtomicAdd(facePtr[halfedge.face], 1);
|
||||
@@ -398,7 +386,7 @@ struct DuplicateHalfedges {
|
||||
};
|
||||
|
||||
void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
VecDH<Ref> &halfedgeRef, const Manifold::Impl &inP,
|
||||
VecDH<TriRef> &halfedgeRef, const Manifold::Impl &inP,
|
||||
const VecDH<char> wholeHalfedgeP, const VecDH<int> &i03,
|
||||
const VecDH<int> &vP2R, const int *faceP2R, bool forward,
|
||||
ExecutionPolicy policy) {
|
||||
@@ -410,97 +398,142 @@ void AppendWholeEdges(Manifold::Impl &outR, VecDH<int> &facePtrR,
|
||||
i03.cptrD(), vP2R.cptrD(), faceP2R, forward}));
|
||||
}
|
||||
|
||||
struct CreateBarycentric {
|
||||
glm::vec3 *barycentricR;
|
||||
BaryRef *faceRef;
|
||||
int *idx;
|
||||
|
||||
struct MapTriRef {
|
||||
const TriRef *triRefP;
|
||||
const TriRef *triRefQ;
|
||||
const int offsetQ;
|
||||
const int firstNewVert;
|
||||
const glm::vec3 *vertPosR;
|
||||
|
||||
__host__ __device__ void operator()(TriRef &triRef) {
|
||||
const int tri = triRef.tri;
|
||||
const bool PQ = triRef.meshID == 0;
|
||||
triRef = PQ ? triRefP[tri] : triRefQ[tri];
|
||||
if (!PQ) triRef.meshID += offsetQ;
|
||||
}
|
||||
};
|
||||
|
||||
VecDH<TriRef> UpdateReference(Manifold::Impl &outR, const Manifold::Impl &inP,
|
||||
const Manifold::Impl &inQ, bool invertQ,
|
||||
ExecutionPolicy policy) {
|
||||
VecDH<TriRef> refPQ = outR.meshRelation_.triRef;
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
for_each_n(policy, outR.meshRelation_.triRef.begin(), outR.NumTri(),
|
||||
MapTriRef({inP.meshRelation_.triRef.cptrD(),
|
||||
inQ.meshRelation_.triRef.cptrD(), offsetQ}));
|
||||
|
||||
for (const auto &pair : inP.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
for (const auto &pair : inQ.meshRelation_.meshIDtransform) {
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ] = pair.second;
|
||||
outR.meshRelation_.meshIDtransform[pair.first + offsetQ].backSide ^=
|
||||
invertQ;
|
||||
}
|
||||
return refPQ;
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
glm::vec3 *uvw;
|
||||
const glm::vec3 *vertPosP;
|
||||
const glm::vec3 *vertPosQ;
|
||||
const glm::vec3 *vertPosR;
|
||||
const Halfedge *halfedgeP;
|
||||
const Halfedge *halfedgeQ;
|
||||
const BaryRef *triBaryP;
|
||||
const BaryRef *triBaryQ;
|
||||
const glm::vec3 *barycentricP;
|
||||
const glm::vec3 *barycentricQ;
|
||||
const bool invertQ;
|
||||
const Halfedge *halfedgeR;
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<int &, Ref, Halfedge> inOut) {
|
||||
int &halfedgeBary = thrust::get<0>(inOut);
|
||||
const Ref halfedgeRef = thrust::get<1>(inOut);
|
||||
const Halfedge halfedgeR = thrust::get<2>(inOut);
|
||||
__host__ __device__ void operator()(thrust::tuple<int, TriRef> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const TriRef refPQ = thrust::get<1>(in);
|
||||
if (halfedgeR[3 * tri].startVert < 0) return;
|
||||
|
||||
const glm::vec3 *barycentric =
|
||||
halfedgeRef.PQ == 0 ? barycentricP : barycentricQ;
|
||||
const int tri = halfedgeRef.tri;
|
||||
const BaryRef oldRef = halfedgeRef.PQ == 0 ? triBaryP[tri] : triBaryQ[tri];
|
||||
const int triPQ = refPQ.tri;
|
||||
const bool PQ = refPQ.meshID == 0;
|
||||
const auto &vertPos = PQ ? vertPosP : vertPosQ;
|
||||
const auto &halfedge = PQ ? halfedgeP : halfedgeQ;
|
||||
|
||||
faceRef[halfedgeR.face] = oldRef;
|
||||
glm::mat3 triPos;
|
||||
for (const int j : {0, 1, 2})
|
||||
triPos[j] = vertPos[halfedge[3 * triPQ + j].startVert];
|
||||
|
||||
if (halfedgeRef.PQ == 1) {
|
||||
// Mark the meshID as coming from Q
|
||||
faceRef[halfedgeR.face].meshID += offsetQ;
|
||||
}
|
||||
|
||||
if (halfedgeR.startVert < firstNewVert) { // retained vert
|
||||
int i = halfedgeRef.vert;
|
||||
const int bary = oldRef.vertBary[i];
|
||||
if (bary < 0) {
|
||||
halfedgeBary = bary;
|
||||
} else {
|
||||
halfedgeBary = AtomicAdd(*idx, 1);
|
||||
barycentricR[halfedgeBary] = barycentric[bary];
|
||||
}
|
||||
} else { // new vert
|
||||
halfedgeBary = AtomicAdd(*idx, 1);
|
||||
|
||||
const glm::vec3 *vertPos = halfedgeRef.PQ == 0 ? vertPosP : vertPosQ;
|
||||
const Halfedge *halfedge = halfedgeRef.PQ == 0 ? halfedgeP : halfedgeQ;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2})
|
||||
triPos[i] = vertPos[halfedge[3 * tri + i].startVert];
|
||||
|
||||
glm::mat3 uvwOldTri;
|
||||
for (int i : {0, 1, 2})
|
||||
uvwOldTri[i] = UVW(oldRef.vertBary[i], barycentric);
|
||||
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPosR[halfedgeR.startVert], triPos, precision);
|
||||
barycentricR[halfedgeBary] = uvwOldTri * uvw;
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = halfedgeR[3 * tri + i].startVert;
|
||||
uvw[3 * tri + i] = GetBarycentric(vertPosR[vert], triPos, precision);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<VecDH<BaryRef>, VecDH<int>> CalculateMeshRelation(
|
||||
Manifold::Impl &outR, const VecDH<Ref> &halfedgeRef,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ, int firstNewVert,
|
||||
int numFaceR, bool invertQ, ExecutionPolicy policy) {
|
||||
outR.meshRelation_.barycentric.resize(outR.halfedge_.size());
|
||||
VecDH<BaryRef> faceRef(numFaceR);
|
||||
VecDH<int> halfedgeBary(halfedgeRef.size());
|
||||
void CreateProperties(Manifold::Impl &outR, const VecDH<TriRef> &refPQ,
|
||||
const Manifold::Impl &inP, const Manifold::Impl &inQ,
|
||||
ExecutionPolicy policy) {
|
||||
const int numPropP = inP.NumProp();
|
||||
const int numPropQ = inQ.NumProp();
|
||||
const int numProp = glm::max(numPropP, numPropQ);
|
||||
outR.meshRelation_.numProp = numProp;
|
||||
if (numProp == 0) return;
|
||||
|
||||
const int offsetQ = Manifold::Impl::meshIDCounter_;
|
||||
VecDH<int> idx(1, 0);
|
||||
for_each_n(
|
||||
policy,
|
||||
zip(halfedgeBary.begin(), halfedgeRef.begin(), outR.halfedge_.cbegin()),
|
||||
halfedgeRef.size(),
|
||||
CreateBarycentric(
|
||||
{outR.meshRelation_.barycentric.ptrD(), faceRef.ptrD(), idx.ptrD(),
|
||||
offsetQ, firstNewVert, outR.vertPos_.cptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
inP.meshRelation_.triBary.cptrD(), inQ.meshRelation_.triBary.cptrD(),
|
||||
inP.meshRelation_.barycentric.cptrD(),
|
||||
inQ.meshRelation_.barycentric.cptrD(), invertQ, outR.precision_}));
|
||||
outR.meshRelation_.barycentric.resize(idx[0]);
|
||||
const int numTri = outR.NumTri();
|
||||
outR.meshRelation_.triProperties.resize(numTri);
|
||||
|
||||
return std::make_pair(faceRef, halfedgeBary);
|
||||
VecDH<glm::vec3> bary(outR.halfedge_.size());
|
||||
for_each_n(policy, zip(countAt(0), refPQ.cbegin()), numTri,
|
||||
Barycentric({bary.ptrD(), inP.vertPos_.cptrD(),
|
||||
inQ.vertPos_.cptrD(), outR.vertPos_.cptrD(),
|
||||
inP.halfedge_.cptrD(), inQ.halfedge_.cptrD(),
|
||||
outR.halfedge_.cptrD(), outR.precision_}));
|
||||
|
||||
std::map<std::tuple<int, int, int, int>, int> propIdx;
|
||||
int idx = 0;
|
||||
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
// Skip collapsed triangles
|
||||
if (outR.halfedge_[3 * tri].startVert < 0) continue;
|
||||
|
||||
const int triPQ = refPQ[tri].tri;
|
||||
const bool PQ = refPQ[tri].meshID == 0;
|
||||
const auto &properties =
|
||||
PQ ? inP.meshRelation_.properties : inQ.meshRelation_.properties;
|
||||
const auto &triProp = PQ ? inP.meshRelation_.triProperties[triPQ]
|
||||
: inQ.meshRelation_.triProperties[triPQ];
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int vert = outR.halfedge_[3 * tri + i].startVert;
|
||||
const glm::vec3 uvw = bary[3 * tri + i];
|
||||
|
||||
auto key = std::make_tuple(PQ, vert, -1, -1);
|
||||
int edge = -1;
|
||||
for (const int j : {0, 1, 2}) {
|
||||
if (uvw[j] == 1) {
|
||||
// On a retained vert, the propVert must also match
|
||||
std::get<2>(key) = triProp[j];
|
||||
edge = -1;
|
||||
break;
|
||||
}
|
||||
if (uvw[j] == 0) edge = j;
|
||||
}
|
||||
if (edge >= 0) {
|
||||
// On an edge, both propVerts must match
|
||||
const int p0 = triProp[Next3(edge)];
|
||||
const int p1 = triProp[Prev3(edge)];
|
||||
std::get<2>(key) = glm::min(p0, p1);
|
||||
std::get<3>(key) = glm::max(p0, p1);
|
||||
}
|
||||
|
||||
const auto it = propIdx.find(key);
|
||||
if (it != propIdx.end()) {
|
||||
outR.meshRelation_.triProperties[tri][i] = it->second;
|
||||
continue;
|
||||
}
|
||||
outR.meshRelation_.triProperties[tri][i] = idx;
|
||||
propIdx.insert({key, idx++});
|
||||
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
glm::vec3 oldProps;
|
||||
for (const int j : {0, 1, 2})
|
||||
oldProps[j] = properties[numProp * triProp[j] + p];
|
||||
outR.meshRelation_.properties.push_back(glm::dot(uvw, oldProps));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -622,9 +655,9 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
// Intersected halfedges are marked false.
|
||||
VecDH<char> wholeHalfedgeP(inP_.halfedge_.size(), true);
|
||||
VecDH<char> wholeHalfedgeQ(inQ_.halfedge_.size(), true);
|
||||
// The halfedgeRef contains the data that will become triBary once the faces
|
||||
// The halfedgeRef contains the data that will become triRef once the faces
|
||||
// are triangulated.
|
||||
VecDH<Ref> halfedgeRef(2 * outR.NumEdge());
|
||||
VecDH<TriRef> halfedgeRef(2 * outR.NumEdge());
|
||||
|
||||
AppendPartialEdges(outR, wholeHalfedgeP, facePtrR, edgesP, halfedgeRef, inP_,
|
||||
i03, vP2R, facePQ2R.begin(), true);
|
||||
@@ -639,11 +672,6 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
AppendWholeEdges(outR, facePtrR, halfedgeRef, inQ_, wholeHalfedgeQ, i30, vQ2R,
|
||||
facePQ2R.cptrD() + inP_.NumTri(), false, policy_);
|
||||
|
||||
VecDH<BaryRef> faceRef;
|
||||
VecDH<int> halfedgeBary;
|
||||
std::tie(faceRef, halfedgeBary) = CalculateMeshRelation(
|
||||
outR, halfedgeRef, inP_, inQ_, nPv + nQv, numFaceR, invertQ, policy_);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
assemble.Stop();
|
||||
Timer triangulate;
|
||||
@@ -655,7 +683,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "polygon mesh is not manifold!");
|
||||
|
||||
outR.Face2Tri(faceEdge, faceRef, halfedgeBary);
|
||||
outR.Face2Tri(faceEdge, halfedgeRef);
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
triangulate.Stop();
|
||||
@@ -666,13 +694,15 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.IsManifold(), logicErr, "triangulated mesh is not manifold!");
|
||||
|
||||
VecDH<TriRef> refPQ = UpdateReference(outR, inP_, inQ_, invertQ, policy_);
|
||||
|
||||
outR.SimplifyTopology();
|
||||
|
||||
CreateProperties(outR, refPQ, inP_, inQ_, policy_);
|
||||
|
||||
if (ManifoldParams().intermediateChecks)
|
||||
ASSERT(outR.Is2Manifold(), logicErr, "simplified mesh is not 2-manifold!");
|
||||
|
||||
outR.IncrementMeshIDs(0, outR.NumTri());
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
simplify.Stop();
|
||||
Timer sort;
|
||||
@@ -680,6 +710,7 @@ Manifold::Impl Boolean3::Result(Manifold::OpType op) const {
|
||||
#endif
|
||||
|
||||
outR.Finish();
|
||||
outR.IncrementMeshIDs();
|
||||
|
||||
#ifdef MANIFOLD_DEBUG
|
||||
sort.Stop();
|
||||
|
||||
@@ -50,6 +50,48 @@ struct RemoveFace {
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
* actual triangle resolution is unchanged; use the Refine() method to
|
||||
* interpolate to a higher-resolution curve.
|
||||
*
|
||||
* By default, every edge is calculated for maximum smoothness (very much
|
||||
* approximately), attempting to minimize the maximum mean Curvature magnitude.
|
||||
* No higher-order derivatives are considered, as the interpolation is
|
||||
* independent per triangle, only sharing constraints on their boundaries.
|
||||
*
|
||||
* @param meshGL input MeshGL.
|
||||
* @param sharpenedEdges If desired, you can supply a vector of sharpened
|
||||
* halfedges, which should in general be a small subset of all halfedges. Order
|
||||
* of entries doesn't matter, as each one specifies the desired smoothness
|
||||
* (between zero and one, with one the default for all unspecified halfedges)
|
||||
* and the halfedge index (3 * triangle index + [0,1,2] where 0 is the edge
|
||||
* between triVert 0 and 1, etc).
|
||||
*
|
||||
* At a smoothness value of zero, a sharp crease is made. The smoothness is
|
||||
* interpolated along each edge, so the specified value should be thought of as
|
||||
* an average. Where exactly two sharpened edges meet at a vertex, their
|
||||
* tangents are rotated to be colinear so that the sharpened edge can be
|
||||
* continuous. Vertices with only one sharpened edge are completely smooth,
|
||||
* allowing sharpened edges to smoothly vanish at termination. A single vertex
|
||||
* can be sharpened by sharping all edges that are incident on it, allowing
|
||||
* cones to be formed.
|
||||
*/
|
||||
Manifold Manifold::Smooth(const MeshGL& meshGL,
|
||||
const std::vector<Smoothness>& sharpenedEdges) {
|
||||
ASSERT(meshGL.halfedgeTangent.empty(), std::runtime_error,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
// Don't allow any triangle merging.
|
||||
std::vector<float> propertyTolerance(meshGL.numProp - 3, -1);
|
||||
std::shared_ptr<Impl> impl =
|
||||
std::make_shared<Impl>(meshGL, propertyTolerance);
|
||||
impl->CreateTangents(sharpenedEdges);
|
||||
return Manifold(impl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a smooth version of the input mesh by creating tangents; this
|
||||
* method will throw if you have supplied tangents with your mesh already. The
|
||||
@@ -84,7 +126,8 @@ Manifold Manifold::Smooth(const Mesh& mesh,
|
||||
"when supplying tangents, the normal constructor should be used "
|
||||
"rather than Smooth().");
|
||||
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh);
|
||||
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
|
||||
std::shared_ptr<Impl> impl = std::make_shared<Impl>(mesh, relation);
|
||||
impl->CreateTangents(sharpenedEdges);
|
||||
return Manifold(impl);
|
||||
}
|
||||
@@ -108,7 +151,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;
|
||||
return cube.AsOriginal();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +181,8 @@ 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));
|
||||
cylinder =
|
||||
cylinder.Translate(glm::vec3(0.0f, 0.0f, -height / 2.0f)).AsOriginal();
|
||||
return cylinder;
|
||||
}
|
||||
|
||||
@@ -161,7 +205,7 @@ Manifold Manifold::Sphere(float radius, int circularSegments) {
|
||||
pImpl_->NumVert(), ToSphere({radius}));
|
||||
pImpl_->Finish();
|
||||
// Ignore preceding octahedron.
|
||||
pImpl_->ReinitializeReference(Impl::meshIDCounter_.fetch_add(1));
|
||||
pImpl_->InitializeOriginal();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -239,7 +283,9 @@ Manifold Manifold::Extrude(Polygons crossSection, float height, int nDivisions,
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeNewReference();
|
||||
pImpl_->meshRelation_.originalID = ReserveIDs(1);
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -338,7 +384,9 @@ Manifold Manifold::Revolve(const Polygons& crossSection, int circularSegments) {
|
||||
|
||||
pImpl_->CreateHalfedges(triVertsDH);
|
||||
pImpl_->Finish();
|
||||
pImpl_->InitializeNewReference();
|
||||
pImpl_->meshRelation_.originalID = ReserveIDs(1);
|
||||
pImpl_->InitializeOriginal();
|
||||
pImpl_->CreateFaces();
|
||||
return Manifold(pImpl_);
|
||||
}
|
||||
|
||||
@@ -357,12 +405,7 @@ Manifold Manifold::Compose(const std::vector<Manifold>& manifolds) {
|
||||
return Manifold(std::make_shared<Impl>(CsgLeafNode::Compose(children)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of Compose().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
Components Manifold::GetComponents() const {
|
||||
Graph graph;
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
for (int i = 0; i < NumVert(); ++i) {
|
||||
@@ -374,16 +417,21 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
}
|
||||
std::vector<int> components;
|
||||
const int numLabel = ConnectedComponents(components, graph);
|
||||
return {components, numLabel};
|
||||
}
|
||||
|
||||
if (numLabel == 1) {
|
||||
std::vector<Manifold> Manifold::Decompose(Components components) const {
|
||||
auto pImpl_ = GetCsgLeafNode().GetImpl();
|
||||
|
||||
if (components.numComponents == 1) {
|
||||
std::vector<Manifold> meshes(1);
|
||||
meshes[0] = *this;
|
||||
return meshes;
|
||||
}
|
||||
VecDH<int> vertLabel(components);
|
||||
VecDH<int> vertLabel(components.indices);
|
||||
|
||||
std::vector<Manifold> meshes;
|
||||
for (int i = 0; i < numLabel; ++i) {
|
||||
for (int i = 0; i < components.numComponents; ++i) {
|
||||
auto impl = std::make_shared<Impl>();
|
||||
// inherit original object's precision
|
||||
impl->precision_ = pImpl_->precision_;
|
||||
@@ -418,4 +466,13 @@ std::vector<Manifold> Manifold::Decompose() const {
|
||||
}
|
||||
return meshes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This operation returns a vector of Manifolds that are topologically
|
||||
* disconnected. If everything is connected, the vector is length one,
|
||||
* containing a copy of the original. It is the inverse operation of Compose().
|
||||
*/
|
||||
std::vector<Manifold> Manifold::Decompose() const {
|
||||
return Decompose(GetComponents());
|
||||
}
|
||||
} // namespace manifold
|
||||
|
||||
@@ -40,16 +40,6 @@ struct TransformNormals {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateTriBary {
|
||||
const int nextBary;
|
||||
|
||||
__host__ __device__ BaryRef operator()(BaryRef ref) {
|
||||
for (int i : {0, 1, 2})
|
||||
if (ref.vertBary[i] >= 0) ref.vertBary[i] += nextBary;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateHalfedge {
|
||||
const int nextVert;
|
||||
const int nextEdge;
|
||||
@@ -64,6 +54,15 @@ struct UpdateHalfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshIDs {
|
||||
const int offset;
|
||||
|
||||
__host__ __device__ TriRef operator()(TriRef ref) {
|
||||
ref.meshID += offset;
|
||||
return ref;
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckOverlap {
|
||||
const Box *boxes;
|
||||
const size_t i;
|
||||
@@ -139,7 +138,6 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
int numVert = 0;
|
||||
int numEdge = 0;
|
||||
int numTri = 0;
|
||||
int numBary = 0;
|
||||
for (auto &node : nodes) {
|
||||
float nodeOldScale = node->pImpl_->bBox_.Scale();
|
||||
float nodeNewScale =
|
||||
@@ -153,7 +151,6 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
numVert += node->pImpl_->NumVert();
|
||||
numEdge += node->pImpl_->NumEdge();
|
||||
numTri += node->pImpl_->NumTri();
|
||||
numBary += node->pImpl_->meshRelation_.barycentric.size();
|
||||
}
|
||||
|
||||
Manifold::Impl combined;
|
||||
@@ -162,14 +159,13 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
combined.halfedge_.resize(2 * numEdge);
|
||||
combined.faceNormal_.resize(numTri);
|
||||
combined.halfedgeTangent_.resize(2 * numEdge);
|
||||
combined.meshRelation_.barycentric.resize(numBary);
|
||||
combined.meshRelation_.triBary.resize(numTri);
|
||||
combined.meshRelation_.triRef.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
int nextVert = 0;
|
||||
int nextEdge = 0;
|
||||
int nextTri = 0;
|
||||
int nextBary = 0;
|
||||
int i = 0;
|
||||
for (auto &node : nodes) {
|
||||
if (node->transform_ == glm::mat4x3(1.0f)) {
|
||||
copy(policy, node->pImpl_->vertPos_.begin(), node->pImpl_->vertPos_.end(),
|
||||
@@ -195,31 +191,28 @@ Manifold::Impl CsgLeafNode::Compose(
|
||||
copy(policy, node->pImpl_->halfedgeTangent_.begin(),
|
||||
node->pImpl_->halfedgeTangent_.end(),
|
||||
combined.halfedgeTangent_.begin() + nextEdge);
|
||||
copy(policy, node->pImpl_->meshRelation_.barycentric.begin(),
|
||||
node->pImpl_->meshRelation_.barycentric.end(),
|
||||
combined.meshRelation_.barycentric.begin() + nextBary);
|
||||
transform(policy, node->pImpl_->meshRelation_.triBary.begin(),
|
||||
node->pImpl_->meshRelation_.triBary.end(),
|
||||
combined.meshRelation_.triBary.begin() + nextTri,
|
||||
UpdateTriBary({nextBary}));
|
||||
transform(policy, node->pImpl_->halfedge_.begin(),
|
||||
node->pImpl_->halfedge_.end(),
|
||||
combined.halfedge_.begin() + nextEdge,
|
||||
UpdateHalfedge({nextVert, nextEdge, nextTri}));
|
||||
|
||||
// Since the nodes may be copies containing the same meshIDs, it is
|
||||
// important to increment them separately so that each node instance gets
|
||||
// important to add an offset so that each node instance gets
|
||||
// unique meshIDs.
|
||||
combined.IncrementMeshIDs(nextTri, node->pImpl_->NumTri());
|
||||
const int offset = i++ * Manifold::Impl::meshIDCounter_;
|
||||
transform(policy, node->pImpl_->meshRelation_.triRef.begin(),
|
||||
node->pImpl_->meshRelation_.triRef.end(),
|
||||
combined.meshRelation_.triRef.begin() + nextTri,
|
||||
UpdateMeshIDs({offset}));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,19 +60,19 @@ struct ShortEdge {
|
||||
|
||||
struct FlagEdge {
|
||||
const Halfedge* halfedge;
|
||||
const BaryRef* triBary;
|
||||
const TriRef* triRef;
|
||||
|
||||
__host__ __device__ bool operator()(int edge) {
|
||||
if (halfedge[edge].pairedHalfedge < 0) return false;
|
||||
// Flag redundant edges - those where the startVert is surrounded by only
|
||||
// two original triangles.
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const TriRef ref0 = triRef[edge / 3];
|
||||
int current = NextHalfedge(halfedge[edge].pairedHalfedge);
|
||||
const BaryRef ref1 = triBary[current / 3];
|
||||
const TriRef ref1 = triRef[current / 3];
|
||||
while (current != edge) {
|
||||
current = NextHalfedge(halfedge[current].pairedHalfedge);
|
||||
int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
const TriRef ref = triRef[tri];
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return false;
|
||||
@@ -168,7 +168,7 @@ void Manifold::Impl::SimplifyTopology() {
|
||||
numFlagged =
|
||||
copy_if<decltype(flaggedEdges.begin())>(
|
||||
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triBary.cptrD()})) -
|
||||
FlagEdge({halfedge_.cptrD(), meshRelation_.triRef.cptrD()})) -
|
||||
flaggedEdges.begin();
|
||||
flaggedEdges.resize(numFlagged);
|
||||
|
||||
@@ -213,8 +213,11 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
halfedge_.push_back({outsideVert, endVert, -1, newFace});
|
||||
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, current);
|
||||
if (meshRelation_.triBary.size() > 0)
|
||||
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
newHalfedge += 3;
|
||||
@@ -227,8 +230,11 @@ void Manifold::Impl::DedupeEdge(const int edge) {
|
||||
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
|
||||
PairUp(newHalfedge + 1, opposite);
|
||||
PairUp(newHalfedge, newHalfedge - 3);
|
||||
if (meshRelation_.triBary.size() > 0)
|
||||
meshRelation_.triBary.push_back(meshRelation_.triBary[oldFace]);
|
||||
if (meshRelation_.triRef.size() > 0)
|
||||
meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);
|
||||
if (meshRelation_.triProperties.size() > 0)
|
||||
meshRelation_.triProperties.push_back(
|
||||
meshRelation_.triProperties[oldFace]);
|
||||
if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);
|
||||
|
||||
break;
|
||||
@@ -301,7 +307,8 @@ void Manifold::Impl::RemoveIfFolded(int edge) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
|
||||
const Halfedge toRemove = halfedge_[edge];
|
||||
if (toRemove.pairedHalfedge < 0) return;
|
||||
@@ -326,24 +333,32 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
|
||||
// Orbit startVert
|
||||
int start = halfedge_[tri1edge[1]].pairedHalfedge;
|
||||
const BaryRef ref0 = triBary[edge / 3];
|
||||
const BaryRef ref1 = triBary[toRemove.pairedHalfedge / 3];
|
||||
if (!shortEdge) {
|
||||
current = start;
|
||||
TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];
|
||||
glm::vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
glm::vec3 pNext = vertPos_[halfedge_[current].endVert];
|
||||
const int tri = current / 3;
|
||||
const BaryRef ref = triBary[tri];
|
||||
const TriRef ref = triRef[tri];
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
// Don't collapse if the edge is not redundant (this may have changed due
|
||||
// to the collapse of neighbors).
|
||||
if ((ref.meshID != ref0.meshID || ref.tri != ref0.tri) &&
|
||||
(ref.meshID != ref1.meshID || ref.tri != ref1.tri))
|
||||
return;
|
||||
if (ref.meshID != refCheck.meshID || ref.tri != refCheck.tri) {
|
||||
refCheck = triRef[edge / 3];
|
||||
if (ref.meshID != refCheck.meshID || ref.tri != refCheck.tri) {
|
||||
return;
|
||||
} else {
|
||||
// Don't collapse if the edges separating the faces are not colinear
|
||||
// (can happen when the two faces are coplanar).
|
||||
if (CCW(projection * pOld, projection * pLast, projection * pNew,
|
||||
precision_) != 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't collapse edge if it would cause a triangle to invert.
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(faceNormal_[tri]);
|
||||
if (CCW(projection * pNext, projection * pLast, projection * pNew,
|
||||
precision_) < 0)
|
||||
return;
|
||||
@@ -358,18 +373,30 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
CollapseTri(tri1edge);
|
||||
|
||||
// Orbit startVert
|
||||
const int tri0 = edge / 3;
|
||||
const int tri1 = toRemove.pairedHalfedge / 3;
|
||||
const int triVert0 = (edge + 1) % 3;
|
||||
const int triVert1 = toRemove.pairedHalfedge % 3;
|
||||
const int prop0 = triProp.size() > 0 ? triProp[tri0][edge % 3] : -1;
|
||||
const int prop1 = triProp.size() > 0
|
||||
? triProp[tri1][(toRemove.pairedHalfedge + 1) % 3]
|
||||
: -1;
|
||||
current = start;
|
||||
while (current != tri0edge[2]) {
|
||||
current = NextHalfedge(current);
|
||||
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
|
||||
if (!shortEdge) {
|
||||
// Update the shifted triangles to the vertBary of endVert
|
||||
const int tri = current / 3;
|
||||
const int vIdx = current - 3 * tri;
|
||||
triBary[tri].vertBary[vIdx] =
|
||||
(ref0.meshID == triBary[tri].meshID && ref0.tri == triBary[tri].tri)
|
||||
? ref0.vertBary[(edge + 1) % 3]
|
||||
: ref1.vertBary[toRemove.pairedHalfedge % 3];
|
||||
if (triProp.size() > 0) {
|
||||
if (triProp[tri][vIdx] == prop0) {
|
||||
triProp[tri][vIdx] = triProp[tri0][triVert0];
|
||||
} else if (triProp[tri][vIdx] == prop1) {
|
||||
triProp[tri][vIdx] = triProp[tri1][triVert1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int vert = halfedge_[current].endVert;
|
||||
@@ -391,7 +418,7 @@ void Manifold::Impl::CollapseEdge(const int edge) {
|
||||
}
|
||||
|
||||
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
if (edge < 0) return;
|
||||
const int pair = halfedge_[edge].pairedHalfedge;
|
||||
@@ -431,23 +458,28 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
|
||||
const int tri0 = halfedge_[tri0edge[0]].face;
|
||||
const int tri1 = halfedge_[tri1edge[0]].face;
|
||||
faceNormal_[tri0] = faceNormal_[tri1];
|
||||
triBary[tri0] = triBary[tri1];
|
||||
triBary[tri0].vertBary[perm0[1]] = triBary[tri1].vertBary[perm1[0]];
|
||||
triBary[tri0].vertBary[perm0[0]] = triBary[tri1].vertBary[perm1[2]];
|
||||
// Calculate a new barycentric coordinate for the split triangle.
|
||||
const glm::vec3 uvw0 = UVW(triBary[tri1].vertBary[perm1[0]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
const glm::vec3 uvw1 = UVW(triBary[tri1].vertBary[perm1[1]],
|
||||
meshRelation_.barycentric.cptrH());
|
||||
triRef[tri0] = triRef[tri1];
|
||||
const float l01 = glm::length(v[1] - v[0]);
|
||||
const float l02 = glm::length(v[2] - v[0]);
|
||||
const float a = glm::max(0.0f, glm::min(1.0f, l02 / l01));
|
||||
const glm::vec3 uvw2 = a * uvw0 + (1 - a) * uvw1;
|
||||
// And assign it.
|
||||
const int newBary = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.push_back(uvw2);
|
||||
triBary[tri1].vertBary[perm1[0]] = newBary;
|
||||
triBary[tri0].vertBary[perm0[2]] = newBary;
|
||||
// Update properties if applicable
|
||||
if (meshRelation_.properties.size() > 0) {
|
||||
VecDH<glm::ivec3>& triProp = meshRelation_.triProperties;
|
||||
VecDH<float>& prop = meshRelation_.properties;
|
||||
triProp[tri0] = triProp[tri1];
|
||||
triProp[tri0][perm0[1]] = triProp[tri1][perm1[0]];
|
||||
triProp[tri0][perm0[0]] = triProp[tri1][perm1[2]];
|
||||
const int numProp = NumProp();
|
||||
const int newProp = prop.size() / numProp;
|
||||
const int propIdx0 = triProp[tri1][perm1[0]];
|
||||
const int propIdx1 = triProp[tri1][perm1[1]];
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
prop.push_back(a * prop[numProp * propIdx0 + p] +
|
||||
(1 - a) * prop[numProp * propIdx1 + p]);
|
||||
}
|
||||
triProp[tri1][perm1[0]] = newProp;
|
||||
triProp[tri0][perm0[2]] = newProp;
|
||||
}
|
||||
|
||||
// if the new edge already exists, duplicate the verts and split the mesh.
|
||||
int current = halfedge_[tri1edge[0]].pairedHalfedge;
|
||||
|
||||
@@ -30,15 +30,14 @@ namespace manifold {
|
||||
* faceNormal_ values are retained, repeated as necessary.
|
||||
*/
|
||||
void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
const VecDH<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary) {
|
||||
const VecDH<TriRef>& halfedgeRef) {
|
||||
VecDH<glm::ivec3> triVerts;
|
||||
VecDH<glm::vec3> triNormal;
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
triBary.resize(0);
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
triRef.resize(0);
|
||||
triVerts.reserve(halfedge_.size());
|
||||
triNormal.reserve(halfedge_.size());
|
||||
triBary.reserve(halfedge_.size());
|
||||
triRef.reserve(halfedge_.size());
|
||||
|
||||
for (int face = 0; face < faceEdge.size() - 1; ++face) {
|
||||
const int firstEdge = faceEdge[face];
|
||||
@@ -47,12 +46,6 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
ASSERT(numEdge >= 3, topologyErr, "face has less than three edges.");
|
||||
const glm::vec3 normal = faceNormal_[face];
|
||||
|
||||
auto linearSearch = [](const int* mapping, int value) {
|
||||
int i = 0;
|
||||
while (mapping[i] != value) ++i;
|
||||
return i;
|
||||
};
|
||||
|
||||
if (numEdge == 3) { // Single triangle
|
||||
int mapping[3] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -72,11 +65,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
int index = linearSearch(mapping, tri[k]);
|
||||
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
} else if (numEdge == 4) { // Pair of triangles
|
||||
int mapping[4] = {halfedge_[firstEdge].startVert,
|
||||
halfedge_[firstEdge + 1].startVert,
|
||||
@@ -125,19 +114,11 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
for (auto tri : {tri0, tri1}) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
int index = linearSearch(mapping, tri[k]);
|
||||
triBary.back().vertBary[k] = halfedgeBary[firstEdge + index];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
}
|
||||
} else { // General triangulation
|
||||
const glm::mat3x2 projection = GetAxisAlignedProjection(normal);
|
||||
|
||||
std::map<int, int> vertBary;
|
||||
for (int j = firstEdge; j < lastEdge; ++j)
|
||||
vertBary[halfedge_[j].startVert] = halfedgeBary[j];
|
||||
|
||||
const Polygons polys = Face2Polygons(face, projection, faceEdge);
|
||||
|
||||
std::vector<glm::ivec3> newTris = Triangulate(polys, precision_);
|
||||
@@ -145,10 +126,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
|
||||
for (auto tri : newTris) {
|
||||
triVerts.push_back(tri);
|
||||
triNormal.push_back(normal);
|
||||
triBary.push_back(faceRef[face]);
|
||||
for (int k : {0, 1, 2}) {
|
||||
triBary.back().vertBary[k] = vertBary[tri[k]];
|
||||
}
|
||||
triRef.push_back(halfedgeRef[firstEdge]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+349
-169
@@ -19,8 +19,10 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "graph.h"
|
||||
#include "hashtable.h"
|
||||
#include "par.h"
|
||||
|
||||
namespace {
|
||||
@@ -41,6 +43,12 @@ __host__ __device__ void AtomicAddVec3(glm::vec3& target,
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ int FlipHalfedge(int halfedge) {
|
||||
const int tri = halfedge / 3;
|
||||
const int vert = 2 - (halfedge - 3 * tri);
|
||||
return 3 * tri + vert;
|
||||
}
|
||||
|
||||
struct Normalize {
|
||||
__host__ __device__ void operator()(glm::vec3& v) { v = SafeNormalize(v); }
|
||||
};
|
||||
@@ -63,6 +71,42 @@ struct TransformNormals {
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformTangents {
|
||||
const glm::mat3 transform;
|
||||
const bool invert;
|
||||
const glm::vec4* oldTangents;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<glm::vec4&, int> inOut) {
|
||||
glm::vec4& tangent = thrust::get<0>(inOut);
|
||||
int edge = thrust::get<1>(inOut);
|
||||
if (invert) {
|
||||
edge = halfedge[FlipHalfedge(edge)].pairedHalfedge;
|
||||
}
|
||||
|
||||
tangent = glm::vec4(transform * glm::vec3(oldTangents[edge]),
|
||||
oldTangents[edge].w);
|
||||
}
|
||||
};
|
||||
|
||||
struct FlipTris {
|
||||
Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& bary = thrust::get<0>(inOut);
|
||||
const int tri = thrust::get<1>(inOut);
|
||||
|
||||
thrust::swap(halfedge[3 * tri], halfedge[3 * tri + 2]);
|
||||
|
||||
for (const int i : {0, 1, 2}) {
|
||||
thrust::swap(halfedge[3 * tri + i].startVert,
|
||||
halfedge[3 * tri + i].endVert);
|
||||
halfedge[3 * tri + i].pairedHalfedge =
|
||||
FlipHalfedge(halfedge[3 * tri + i].pairedHalfedge);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AssignNormals {
|
||||
glm::vec3* vertNormal;
|
||||
const glm::vec3* vertPos;
|
||||
@@ -157,52 +201,43 @@ struct ReindexTriVerts {
|
||||
}
|
||||
};
|
||||
|
||||
struct InitializeBaryRef {
|
||||
struct InitializeTriRef {
|
||||
const int meshID;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef&, int> inOut) {
|
||||
BaryRef& baryRef = thrust::get<0>(inOut);
|
||||
__host__ __device__ void operator()(thrust::tuple<TriRef&, int> inOut) {
|
||||
TriRef& baryRef = thrust::get<0>(inOut);
|
||||
int tri = thrust::get<1>(inOut);
|
||||
|
||||
baryRef.meshID = meshID;
|
||||
baryRef.originalID = meshID;
|
||||
baryRef.tri = tri;
|
||||
baryRef.vertBary = {-3, -2, -1};
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkMeshID {
|
||||
int* includesMeshID;
|
||||
HashTableD<uint32_t> table;
|
||||
|
||||
__host__ __device__ void operator()(BaryRef& ref) {
|
||||
includesMeshID[ref.meshID] = 1;
|
||||
__host__ __device__ void operator()(TriRef& ref) {
|
||||
if (table.Full()) return;
|
||||
table.Insert(ref.meshID, 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct UpdateMeshID {
|
||||
const int* meshIDold2new;
|
||||
const HashTableD<uint32_t> meshIDold2new;
|
||||
const int meshIDoffset;
|
||||
|
||||
__host__ __device__ void operator()(BaryRef& ref) {
|
||||
__host__ __device__ void operator()(TriRef& 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;
|
||||
@@ -210,13 +245,17 @@ struct CoplanarEdge {
|
||||
const float precision;
|
||||
|
||||
__host__ __device__ void operator()(
|
||||
thrust::tuple<thrust::pair<int, int>&, int> inOut) {
|
||||
thrust::tuple<thrust::pair<int, int>&, thrust::pair<int, int>&, int>
|
||||
inOut) {
|
||||
thrust::pair<int, int>& face2face = thrust::get<0>(inOut);
|
||||
const int edgeIdx = thrust::get<1>(inOut);
|
||||
thrust::pair<int, int>& vert2vert = thrust::get<1>(inOut);
|
||||
const int edgeIdx = thrust::get<2>(inOut);
|
||||
|
||||
const Halfedge edge = halfedge[edgeIdx];
|
||||
if (!edge.IsForward()) return;
|
||||
const Halfedge pair = halfedge[edge.pairedHalfedge];
|
||||
if (triRef[edge.face].meshID != triRef[pair.face].meshID) return;
|
||||
|
||||
const glm::vec3 base = vertPos[edge.startVert];
|
||||
|
||||
const int baseNum = edgeIdx - 3 * edge.face;
|
||||
@@ -224,6 +263,23 @@ struct CoplanarEdge {
|
||||
const int edgeNum = baseNum == 0 ? 2 : baseNum - 1;
|
||||
const int pairNum = jointNum == 0 ? 2 : jointNum - 1;
|
||||
|
||||
if (numProp > 0) {
|
||||
const int prop0 = triProp[edge.face][baseNum];
|
||||
const int prop1 = triProp[edge.face][edgeNum];
|
||||
bool propEqual = true;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
if (glm::abs(prop[numProp * prop0 + p] - prop[numProp * prop1 + p]) >
|
||||
propTol[p]) {
|
||||
propEqual = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (propEqual) {
|
||||
vert2vert.first = prop0;
|
||||
vert2vert.second = prop1;
|
||||
}
|
||||
}
|
||||
|
||||
const glm::vec3 jointVec = vertPos[pair.startVert] - base;
|
||||
const glm::vec3 edgeVec =
|
||||
vertPos[halfedge[3 * edge.face + edgeNum].startVert] - base;
|
||||
@@ -236,6 +292,8 @@ struct CoplanarEdge {
|
||||
glm::vec3 normal = glm::cross(jointVec, edgeVec);
|
||||
const float area = glm::length(normal);
|
||||
const float areaPair = glm::length(glm::cross(pairVec, jointVec));
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
// Don't link degenerate triangles
|
||||
if (area < length * precision || areaPair < lengthPair * precision) return;
|
||||
|
||||
@@ -263,16 +321,14 @@ struct CoplanarEdge {
|
||||
pairVec + normal * scale * (pairProp - baseProp);
|
||||
|
||||
glm::vec3 cross = glm::cross(iJointVec, iEdgeVec);
|
||||
const float area = glm::max(
|
||||
const float areaP = glm::max(
|
||||
glm::length(cross), glm::length(glm::cross(iPairVec, iJointVec)));
|
||||
const float volume = glm::abs(glm::dot(cross, iPairVec));
|
||||
const float volumeP = glm::abs(glm::dot(cross, iPairVec));
|
||||
// Only operate on consistent triangles
|
||||
if (volume > area * precision) return;
|
||||
if (volumeP > areaP * precision) return;
|
||||
}
|
||||
}
|
||||
|
||||
triArea[edge.face] = area;
|
||||
triArea[pair.face] = areaPair;
|
||||
face2face.first = edge.face;
|
||||
face2face.second = pair.face;
|
||||
}
|
||||
@@ -288,22 +344,181 @@ struct EdgeBox {
|
||||
}
|
||||
};
|
||||
|
||||
int GetLabels(std::vector<int>& components,
|
||||
const VecDH<thrust::pair<int, int>>& edges, int numNodes) {
|
||||
Graph graph;
|
||||
for (int i = 0; i < numNodes; ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (int i = 0; i < edges.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = edges[i];
|
||||
if (edge.first < 0) continue;
|
||||
graph.add_edge(edge.first, edge.second);
|
||||
}
|
||||
|
||||
return ConnectedComponents(components, graph);
|
||||
}
|
||||
|
||||
void DedupePropVerts(manifold::VecDH<glm::ivec3>& triProp,
|
||||
const VecDH<thrust::pair<int, int>>& vert2vert) {
|
||||
std::vector<int> vertLabels;
|
||||
const int numLabels = GetLabels(vertLabels, vert2vert, vert2vert.size());
|
||||
|
||||
std::vector<int> label2vert(numLabels);
|
||||
for (int v = 0; v < vert2vert.size(); ++v) {
|
||||
label2vert[vertLabels[v]] = v;
|
||||
}
|
||||
for (int tri = 0; tri < triProp.size(); ++tri) {
|
||||
for (int i : {0, 1, 2})
|
||||
triProp[tri][i] = label2vert[vertLabels[triProp[tri][i]]];
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
std::atomic<int> Manifold::Impl::meshIDCounter_(1);
|
||||
std::atomic<uint32_t> Manifold::Impl::meshIDCounter_(1);
|
||||
|
||||
uint32_t Manifold::Impl::ReserveIDs(uint32_t n) {
|
||||
return Manifold::Impl::meshIDCounter_.fetch_add(n, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Manifold::Impl::Impl(const MeshGL& meshGL,
|
||||
std::vector<float> propertyTolerance) {
|
||||
Mesh mesh;
|
||||
const int numVert = meshGL.NumVert();
|
||||
const int numTri = meshGL.NumTri();
|
||||
|
||||
if (meshGL.numProp < 3) {
|
||||
MarkFailure(Error::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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a manifold from an input triangle Mesh. Will return an empty Manifold
|
||||
* and set an Error Status if the Mesh is not manifold or otherwise invalid.
|
||||
* TODO: update halfedgeTangent during SimplifyTopology.
|
||||
*/
|
||||
Manifold::Impl::Impl(const Mesh& mesh,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: vertPos_(mesh.vertPos), halfedgeTangent_(mesh.halfedgeTangent) {
|
||||
Manifold::Impl::Impl(const Mesh& mesh, const MeshRelationD& relation,
|
||||
const std::vector<float>& propertyTolerance,
|
||||
bool hasFaceIDs)
|
||||
: vertPos_(mesh.vertPos),
|
||||
halfedgeTangent_(mesh.halfedgeTangent),
|
||||
meshRelation_(relation) {
|
||||
VecDH<glm::ivec3> triVerts = mesh.triVerts;
|
||||
if (!IsIndexInBounds(triVerts)) {
|
||||
MarkFailure(Error::VERTEX_INDEX_OUT_OF_BOUNDS);
|
||||
@@ -324,8 +539,11 @@ Manifold::Impl::Impl(const Mesh& mesh,
|
||||
return;
|
||||
}
|
||||
CalculateNormals();
|
||||
InitializeNewReference(triProperties, properties, propertyTolerance);
|
||||
if (status_ != Error::NO_ERROR) return;
|
||||
|
||||
InitializeOriginal();
|
||||
if (!hasFaceIDs) {
|
||||
CreateFaces(propertyTolerance);
|
||||
}
|
||||
|
||||
SimplifyTopology();
|
||||
Finish();
|
||||
@@ -378,7 +596,9 @@ Manifold::Impl::Impl(Shape shape) {
|
||||
vertPos_ = vertPos;
|
||||
CreateHalfedges(triVerts);
|
||||
Finish();
|
||||
InitializeNewReference();
|
||||
meshRelation_.originalID = ReserveIDs(1);
|
||||
InitializeOriginal();
|
||||
CreateFaces();
|
||||
}
|
||||
|
||||
void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
@@ -401,67 +621,42 @@ void Manifold::Impl::RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts) {
|
||||
ReindexTriVerts({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
void Manifold::Impl::ReinitializeReference(int meshID) {
|
||||
// instead of storing the meshID, we store 0 and set the mapping to
|
||||
// 0 -> meshID, because the meshID after boolean operation also starts from 0.
|
||||
void Manifold::Impl::InitializeOriginal() {
|
||||
const int meshID = meshRelation_.originalID;
|
||||
// Don't initialize if it's not an original
|
||||
if (meshID < 0) return;
|
||||
meshRelation_.triRef.resize(NumTri());
|
||||
for_each_n(autoPolicy(NumTri()),
|
||||
zip(meshRelation_.triBary.begin(), countAt(0)), NumTri(),
|
||||
InitializeBaryRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.originalID = meshID;
|
||||
zip(meshRelation_.triRef.begin(), countAt(0)), NumTri(),
|
||||
InitializeTriRef({meshID, halfedge_.cptrD()}));
|
||||
meshRelation_.meshIDtransform.clear();
|
||||
meshRelation_.meshIDtransform[meshID] = {};
|
||||
}
|
||||
|
||||
int Manifold::Impl::InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance) {
|
||||
meshRelation_.triBary.resize(NumTri());
|
||||
const int nextMeshID = meshIDCounter_.fetch_add(1, std::memory_order_relaxed);
|
||||
ReinitializeReference(nextMeshID);
|
||||
|
||||
const int numProps = propertyTolerance.size();
|
||||
|
||||
VecDH<glm::ivec3> triPropertiesD(triProperties);
|
||||
VecDH<float> propertiesD(properties);
|
||||
VecDH<float> propertyToleranceD(propertyTolerance);
|
||||
|
||||
if (numProps > 0) {
|
||||
if (triProperties.size() != NumTri() && triProperties.size() != 0) {
|
||||
MarkFailure(Error::TRI_PROPERTIES_WRONG_LENGTH);
|
||||
return nextMeshID;
|
||||
};
|
||||
if (properties.size() % numProps != 0) {
|
||||
MarkFailure(Error::PROPERTIES_WRONG_LENGTH);
|
||||
return nextMeshID;
|
||||
};
|
||||
|
||||
const int numSets = properties.size() / numProps;
|
||||
if (!all_of(autoPolicy(triProperties.size()), triPropertiesD.begin(),
|
||||
triPropertiesD.end(), CheckProperties({numSets}))) {
|
||||
MarkFailure(Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
return nextMeshID;
|
||||
};
|
||||
}
|
||||
void Manifold::Impl::CreateFaces(const std::vector<float>& propertyTolerance) {
|
||||
VecDH<float> propertyToleranceD =
|
||||
propertyTolerance.empty()
|
||||
? VecDH<float>(meshRelation_.numProp, kTolerance)
|
||||
: propertyTolerance;
|
||||
|
||||
VecDH<thrust::pair<int, int>> face2face(halfedge_.size(), {-1, -1});
|
||||
VecDH<thrust::pair<int, int>> vert2vert(halfedge_.size(), {-1, -1});
|
||||
VecDH<float> triArea(NumTri());
|
||||
for_each_n(autoPolicy(halfedge_.size()), zip(face2face.begin(), countAt(0)),
|
||||
halfedge_.size(),
|
||||
CoplanarEdge({triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
triPropertiesD.cptrD(), propertiesD.cptrD(),
|
||||
propertyToleranceD.cptrD(), numProps, precision_}));
|
||||
for_each_n(
|
||||
autoPolicy(halfedge_.size()),
|
||||
zip(face2face.begin(), vert2vert.begin(), countAt(0)), halfedge_.size(),
|
||||
CoplanarEdge(
|
||||
{triArea.ptrD(), halfedge_.cptrD(), vertPos_.cptrD(),
|
||||
meshRelation_.triRef.cptrD(), meshRelation_.triProperties.cptrD(),
|
||||
meshRelation_.properties.cptrD(), propertyToleranceD.cptrD(),
|
||||
meshRelation_.numProp, precision_}));
|
||||
|
||||
Graph graph;
|
||||
for (int i = 0; i < NumTri(); ++i) {
|
||||
graph.add_nodes(i);
|
||||
}
|
||||
for (int i = 0; i < face2face.size(); ++i) {
|
||||
const thrust::pair<int, int> edge = face2face[i];
|
||||
if (edge.first < 0) continue;
|
||||
graph.add_edge(edge.first, edge.second);
|
||||
if (meshRelation_.triProperties.size() > 0) {
|
||||
DedupePropVerts(meshRelation_.triProperties, vert2vert);
|
||||
}
|
||||
|
||||
std::vector<int> components;
|
||||
const int numComponent = ConnectedComponents(components, graph);
|
||||
const int numComponent = GetLabels(components, face2face, NumTri());
|
||||
|
||||
std::vector<int> comp2tri(numComponent, -1);
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
@@ -473,51 +668,9 @@ int Manifold::Impl::InitializeNewReference(
|
||||
}
|
||||
}
|
||||
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
std::map<std::pair<int, int>, int> triVert2bary;
|
||||
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
const int refTri = comp2tri[components[tri]];
|
||||
if (refTri == tri) continue;
|
||||
|
||||
glm::mat3 triPos;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_[3 * refTri + i].startVert;
|
||||
triPos[i] = vertPos_[vert];
|
||||
triVert2bary[{refTri, vert}] = i - 3;
|
||||
}
|
||||
|
||||
glm::ivec3 vertBary;
|
||||
bool coplanar = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
const int vert = halfedge_[3 * tri + i].startVert;
|
||||
if (triVert2bary.find({refTri, vert}) == triVert2bary.end()) {
|
||||
const glm::vec3 uvw =
|
||||
GetBarycentric(vertPos_[vert], triPos, precision_);
|
||||
if (isnan(uvw[0])) {
|
||||
coplanar = false;
|
||||
triVert2bary[{refTri, vert}] = -4;
|
||||
break;
|
||||
}
|
||||
triVert2bary[{refTri, vert}] = meshRelation_.barycentric.size();
|
||||
meshRelation_.barycentric.push_back(uvw);
|
||||
}
|
||||
const int bary = triVert2bary[{refTri, vert}];
|
||||
if (bary < -3) {
|
||||
coplanar = false;
|
||||
break;
|
||||
}
|
||||
vertBary[i] = bary;
|
||||
}
|
||||
|
||||
if (coplanar) {
|
||||
BaryRef& ref = triBary[tri];
|
||||
ref.tri = refTri;
|
||||
ref.vertBary = vertBary;
|
||||
}
|
||||
}
|
||||
|
||||
return nextMeshID;
|
||||
VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
for (int tri = 0; tri < NumTri(); ++tri)
|
||||
triRef[tri].tri = comp2tri[components[tri]];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,8 +702,8 @@ void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Does a full recalculation of the face bounding boxes, including updating the
|
||||
* collider, but does not resort the faces.
|
||||
* Does a full recalculation of the face bounding boxes, including updating
|
||||
* the collider, but does not resort the faces.
|
||||
*/
|
||||
void Manifold::Impl::Update() {
|
||||
CalculateBBox();
|
||||
@@ -580,7 +733,11 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
result.precision_ = precision_;
|
||||
result.bBox_ = bBox_;
|
||||
result.halfedge_ = halfedge_;
|
||||
result.halfedgeTangent_ = halfedgeTangent_;
|
||||
result.halfedgeTangent_.resize(halfedgeTangent_.size());
|
||||
|
||||
for (auto& m : result.meshRelation_.meshIDtransform) {
|
||||
m.second.transform = transform_ * glm::mat4(m.second.transform);
|
||||
}
|
||||
|
||||
result.vertPos_.resize(NumVert());
|
||||
result.faceNormal_.resize(faceNormal_.size());
|
||||
@@ -588,12 +745,27 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
transform(policy, vertPos_.begin(), vertPos_.end(), result.vertPos_.begin(),
|
||||
Transform4x3({transform_}));
|
||||
|
||||
glm::mat3 normalTransform =
|
||||
glm::inverse(glm::transpose(glm::mat3(transform_)));
|
||||
glm::mat3 normalTransform = NormalTransform(transform_);
|
||||
transform(policy, faceNormal_.begin(), faceNormal_.end(),
|
||||
result.faceNormal_.begin(), TransformNormals({normalTransform}));
|
||||
transform(policy, vertNormal_.begin(), vertNormal_.end(),
|
||||
result.vertNormal_.begin(), TransformNormals({normalTransform}));
|
||||
|
||||
const bool invert = glm::determinant(glm::mat3(transform_)) < 0;
|
||||
|
||||
if (halfedgeTangent_.size() > 0) {
|
||||
for_each_n(
|
||||
policy, zip(result.halfedgeTangent_.begin(), countAt(0)),
|
||||
halfedgeTangent_.size(),
|
||||
TransformTangents({glm::mat3(transform_), invert,
|
||||
halfedgeTangent_.cptrD(), halfedge_.cptrD()}));
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
for_each_n(policy, zip(result.meshRelation_.triRef.begin(), countAt(0)),
|
||||
result.NumTri(), FlipTris({result.halfedge_.ptrD()}));
|
||||
}
|
||||
|
||||
// This optimization does a cheap collider update if the transform is
|
||||
// axis-aligned.
|
||||
if (!result.collider_.Transform(transform_)) result.Update();
|
||||
@@ -610,8 +782,8 @@ Manifold::Impl Manifold::Impl::Transform(const glm::mat4x3& transform_) const {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the precision based on the bounding box, and limits its minimum value by
|
||||
* the optional input.
|
||||
* Sets the precision based on the bounding box, and limits its minimum value
|
||||
* by the optional input.
|
||||
*/
|
||||
void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
precision_ = glm::max(minPrecision, kTolerance * bBox_.Scale());
|
||||
@@ -621,13 +793,13 @@ void Manifold::Impl::SetPrecision(float minPrecision) {
|
||||
/**
|
||||
* If face normals are already present, this function uses them to compute
|
||||
* vertex normals (angle-weighted pseudo-normals); otherwise it also computes
|
||||
* the face normals. Face normals are only calculated when needed because nearly
|
||||
* degenerate faces will accrue rounding error, while the Boolean can retain
|
||||
* their original normal, which is more accurate and can help with merging
|
||||
* coplanar faces.
|
||||
* the face normals. Face normals are only calculated when needed because
|
||||
* nearly degenerate faces will accrue rounding error, while the Boolean can
|
||||
* retain their original normal, which is more accurate and can help with
|
||||
* merging coplanar faces.
|
||||
*
|
||||
* If the face normals have been invalidated by an operation like Warp(), ensure
|
||||
* you do faceNormal_.resize(0) before calling this function to force
|
||||
* If the face normals have been invalidated by an operation like Warp(),
|
||||
* ensure you do faceNormal_.resize(0) before calling this function to force
|
||||
* recalculation.
|
||||
*/
|
||||
void Manifold::Impl::CalculateNormals() {
|
||||
@@ -650,35 +822,43 @@ void Manifold::Impl::CalculateNormals() {
|
||||
* Remaps all the contained meshIDs to new unique values to represent new
|
||||
* instances of these meshes.
|
||||
*/
|
||||
void Manifold::Impl::IncrementMeshIDs(int start, int length) {
|
||||
VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
ASSERT(start >= 0 && length >= 0 && start + length <= triBary.size(),
|
||||
logicErr, "out of bounds");
|
||||
const auto policy = autoPolicy(length);
|
||||
// Use double the space since the Boolean has P and Q instances.
|
||||
VecDH<int> includesMeshID(2 * Manifold::Impl::meshIDCounter_, 0);
|
||||
void Manifold::Impl::IncrementMeshIDs() {
|
||||
const int numTri = NumTri();
|
||||
const auto policy = autoPolicy(numTri);
|
||||
HashTable<uint32_t> meshIDold2new(meshRelation_.meshIDtransform.size() * 2);
|
||||
|
||||
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}));
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
+29
-18
@@ -13,6 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
#include <map>
|
||||
|
||||
#include "collider.h"
|
||||
#include "manifold.h"
|
||||
#include "optional_assert.h"
|
||||
@@ -25,11 +27,18 @@ namespace manifold {
|
||||
|
||||
/** @ingroup Private */
|
||||
struct Manifold::Impl {
|
||||
struct Relation {
|
||||
glm::mat4x3 transform = glm::mat4x3(1);
|
||||
bool backSide = false;
|
||||
};
|
||||
struct MeshRelationD {
|
||||
VecDH<glm::vec3> barycentric;
|
||||
VecDH<BaryRef> triBary;
|
||||
/// The meshID of this Manifold if it is an original; -1 otherwise.
|
||||
/// The originalID of this Manifold if it is an original; -1 otherwise.
|
||||
int originalID = -1;
|
||||
int numProp = 0;
|
||||
VecDH<TriRef> triRef;
|
||||
VecDH<glm::ivec3> triProperties;
|
||||
VecDH<float> properties;
|
||||
std::map<int, Relation> meshIDtransform;
|
||||
};
|
||||
|
||||
Box bBox_;
|
||||
@@ -43,27 +52,24 @@ struct Manifold::Impl {
|
||||
MeshRelationD meshRelation_;
|
||||
Collider collider_;
|
||||
|
||||
static std::atomic<int> meshIDCounter_;
|
||||
static std::atomic<uint32_t> meshIDCounter_;
|
||||
static uint32_t ReserveIDs(uint32_t);
|
||||
|
||||
Impl() {}
|
||||
enum class Shape { TETRAHEDRON, CUBE, OCTAHEDRON };
|
||||
Impl(Shape);
|
||||
|
||||
Impl(const Mesh&,
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
|
||||
int InitializeNewReference(
|
||||
const std::vector<glm::ivec3>& triProperties = std::vector<glm::ivec3>(),
|
||||
const std::vector<float>& properties = std::vector<float>(),
|
||||
const std::vector<float>& propertyTolerance = std::vector<float>());
|
||||
Impl(const MeshGL&, std::vector<float> propertyTolerance = {});
|
||||
Impl(const Mesh&, const MeshRelationD& relation,
|
||||
const std::vector<float>& propertyTolerance = {},
|
||||
bool hasFaceIDs = false);
|
||||
|
||||
void CreateFaces(const std::vector<float>& propertyTolerance = {});
|
||||
void RemoveUnreferencedVerts(VecDH<glm::ivec3>& triVerts);
|
||||
void ReinitializeReference(int meshID);
|
||||
void InitializeOriginal();
|
||||
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
|
||||
void CalculateNormals();
|
||||
void IncrementMeshIDs(int start, int length);
|
||||
void IncrementMeshIDs();
|
||||
|
||||
void Update();
|
||||
void MarkFailure(Error status);
|
||||
@@ -75,6 +81,11 @@ struct Manifold::Impl {
|
||||
int NumVert() const { return vertPos_.size(); }
|
||||
int NumEdge() const { return halfedge_.size() / 2; }
|
||||
int NumTri() const { return halfedge_.size() / 3; }
|
||||
int NumProp() const { return meshRelation_.numProp; }
|
||||
int NumPropVert() const {
|
||||
return NumProp() == 0 ? NumVert()
|
||||
: meshRelation_.properties.size() / NumProp();
|
||||
}
|
||||
|
||||
// properties.cu
|
||||
Properties GetProperties() const;
|
||||
@@ -92,14 +103,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<BaryRef>& faceRef,
|
||||
const VecDH<int>& halfedgeBary);
|
||||
void Face2Tri(const VecDH<int>& faceEdge, const VecDH<TriRef>& halfedgeRef);
|
||||
Polygons Face2Polygons(int face, glm::mat3x2 projection,
|
||||
const VecDH<int>& faceEdge) const;
|
||||
|
||||
@@ -116,7 +127,7 @@ struct Manifold::Impl {
|
||||
|
||||
// smoothing.cu
|
||||
void CreateTangents(const std::vector<Smoothness>&);
|
||||
MeshRelationD Subdivide(int n);
|
||||
VecDH<Barycentric> Subdivide(int n);
|
||||
void Refine(int n);
|
||||
};
|
||||
} // namespace manifold
|
||||
|
||||
+187
-78
@@ -12,6 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
|
||||
#include "boolean3.h"
|
||||
#include "csg_tree.h"
|
||||
#include "impl.h"
|
||||
@@ -81,38 +85,43 @@ 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,
|
||||
const std::vector<glm::ivec3>& triProperties,
|
||||
const std::vector<float>& properties,
|
||||
const std::vector<float>& propertyTolerance)
|
||||
: pNode_(std::make_shared<CsgLeafNode>(std::make_shared<Impl>(
|
||||
mesh, triProperties, properties, propertyTolerance))) {}
|
||||
Manifold::Manifold(const Mesh& mesh) {
|
||||
Impl::MeshRelationD relation = {(int)ReserveIDs(1)};
|
||||
pNode_ =
|
||||
std::make_shared<CsgLeafNode>(std::make_shared<Impl>(mesh, relation));
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns a Mesh of simple vectors of vertices and triangles suitable for
|
||||
@@ -138,29 +147,33 @@ Mesh Manifold::GetMesh() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
MeshGL Manifold::GetMeshGL() const {
|
||||
/**
|
||||
* The most complete output of this library, returning a MeshGL that is designed
|
||||
* to easily push into a renderer, including all interleaved vertex properties
|
||||
* that may have been input. It also includes relations to all the input meshes
|
||||
* that form a part of this result and the transforms applied to each.
|
||||
*
|
||||
* @param normalIdx If the original MeshGL inputs that formed this manifold had
|
||||
* properties corresponding to normal vectors, you can specify which property
|
||||
* channels these are (x, y, z), which will cause this output MeshGL to
|
||||
* automatically update these normals according to the applied transforms and
|
||||
* front/back side. Each channel must be >= 3 and < numProp, and all original
|
||||
* MeshGLs must use the same channels for their normals.
|
||||
*/
|
||||
MeshGL Manifold::GetMeshGL(glm::ivec3 normalIdx) const {
|
||||
const Impl& impl = *GetCsgLeafNode().GetImpl();
|
||||
|
||||
const int numVert = NumVert();
|
||||
const int numProp = NumProp();
|
||||
const int numVert = NumPropVert();
|
||||
const int numTri = NumTri();
|
||||
|
||||
const bool updateNormals =
|
||||
glm::all(glm::greaterThan(normalIdx, glm::ivec3(2)));
|
||||
|
||||
MeshGL out;
|
||||
out.vertPos.resize(3 * numVert);
|
||||
out.vertNormal.resize(3 * numVert);
|
||||
out.numProp = 3 + numProp;
|
||||
out.triVerts.resize(3 * numTri);
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
const glm::vec3 v = impl.vertPos_[i];
|
||||
out.vertPos[3 * i] = v.x;
|
||||
out.vertPos[3 * i + 1] = v.y;
|
||||
out.vertPos[3 * i + 2] = v.z;
|
||||
const glm::vec3 n = impl.vertNormal_[i];
|
||||
out.vertNormal[3 * i] = n.x;
|
||||
out.vertNormal[3 * i + 1] = n.y;
|
||||
out.vertNormal[3 * i + 2] = n.z;
|
||||
}
|
||||
for (int i = 0; i < numTri * 3; ++i) {
|
||||
out.triVerts[i] = impl.halfedge_[i].startVert;
|
||||
}
|
||||
|
||||
const int numHalfedge = impl.halfedgeTangent_.size();
|
||||
out.halfedgeTangent.resize(4 * numHalfedge);
|
||||
for (int i = 0; i < numHalfedge; ++i) {
|
||||
@@ -171,6 +184,110 @@ MeshGL Manifold::GetMeshGL() const {
|
||||
out.halfedgeTangent[4 * i + 3] = t.w;
|
||||
}
|
||||
|
||||
// Sort the triangles into runs
|
||||
out.faceID.resize(numTri);
|
||||
std::vector<int> triNew2Old(numTri);
|
||||
std::iota(triNew2Old.begin(), triNew2Old.end(), 0);
|
||||
const TriRef* triRef = impl.meshRelation_.triRef.cptrD();
|
||||
// Don't sort originals - keep them in order
|
||||
if (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;
|
||||
}
|
||||
|
||||
@@ -261,6 +378,18 @@ int Manifold::NumEdge() const { return GetCsgLeafNode().GetImpl()->NumEdge(); }
|
||||
* The number of triangles in the Manifold.
|
||||
*/
|
||||
int Manifold::NumTri() const { return GetCsgLeafNode().GetImpl()->NumTri(); }
|
||||
/**
|
||||
* The number of properties per vertex in the Manifold.
|
||||
*/
|
||||
int Manifold::NumProp() const { return GetCsgLeafNode().GetImpl()->NumProp(); }
|
||||
/**
|
||||
* The number of property vertices in the Manifold. This will always be >=
|
||||
* NumVert, as some physical vertices may be duplicated to account for different
|
||||
* properties on different neighboring triangles.
|
||||
*/
|
||||
int Manifold::NumPropVert() const {
|
||||
return GetCsgLeafNode().GetImpl()->NumPropVert();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the axis-aligned bounding box of all the Manifold's vertices.
|
||||
@@ -310,30 +439,6 @@ Curvature Manifold::GetCurvature() const {
|
||||
return GetCsgLeafNode().GetImpl()->GetCurvature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the relationship to the previous meshes, for the purpose of assigning
|
||||
* properties like texture coordinates. The triBary vector is the same length as
|
||||
* Mesh.triVerts: BaryRef.originalID indicates the source mesh and BaryRef.tri
|
||||
* is that mesh's triangle index to which these barycentric coordinates refer.
|
||||
* BaryRef.vertBary gives an index for each vertex into the barycentric vector
|
||||
* if that index is >= 0, indicating it is a new vertex. If the index is < 0,
|
||||
* this indicates it is an original vertex, the index + 3 vert of the referenced
|
||||
* triangle.
|
||||
*
|
||||
* BaryRef.meshID is a unique ID to the particular instance of a given mesh. For
|
||||
* instance, if you want to convert the triangle mesh to a polygon mesh, all the
|
||||
* triangles from a given face will have the same .meshID and .tri values.
|
||||
*/
|
||||
MeshRelation Manifold::GetMeshRelation() const {
|
||||
MeshRelation out;
|
||||
const auto& relation = GetCsgLeafNode().GetImpl()->meshRelation_;
|
||||
out.triBary.insert(out.triBary.end(), relation.triBary.begin(),
|
||||
relation.triBary.end());
|
||||
out.barycentric.insert(out.barycentric.end(), relation.barycentric.begin(),
|
||||
relation.barycentric.end());
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this mesh is an original, this returns its meshID that can be referenced
|
||||
* by product manifolds' MeshRelation. If this manifold is a product, this
|
||||
@@ -344,26 +449,30 @@ int Manifold::OriginalID() const {
|
||||
}
|
||||
|
||||
/**
|
||||
* If you copy a manifold, but you want this new copy to have new properties
|
||||
* (e.g. a different UV mapping), you can reset its meshIDs to a new original,
|
||||
* meaning it will now be referenced by its descendents instead of the meshes it
|
||||
* was built from, allowing you to differentiate the copies when applying your
|
||||
* properties to the final result.
|
||||
*
|
||||
* This function also condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. If you want to have inconsistent properties across
|
||||
* these faces, meaning you want to preserve some of these edges, you should
|
||||
* instead call GetMesh(), calculate your properties and use these to construct
|
||||
* a new manifold.
|
||||
* This function condenses all coplanar faces in the relation, and
|
||||
* collapses those edges. In the process the relation to ancestor meshes is lost
|
||||
* and this new Manifold is marked an original. Properties are preserved, so if
|
||||
* they do not match across an edge, that edge will be kept.
|
||||
*/
|
||||
Manifold Manifold::AsOriginal() const {
|
||||
auto newImpl = std::make_shared<Impl>(*GetCsgLeafNode().GetImpl());
|
||||
newImpl->InitializeNewReference();
|
||||
newImpl->meshRelation_.originalID = ReserveIDs(1);
|
||||
newImpl->InitializeOriginal();
|
||||
newImpl->CreateFaces();
|
||||
newImpl->SimplifyTopology();
|
||||
newImpl->Finish();
|
||||
return Manifold(std::make_shared<CsgLeafNode>(newImpl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first of n sequential new unique mesh IDs for marking sets of
|
||||
* triangles that can be looked up after further operations. Assign to
|
||||
* MeshGL.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.
|
||||
*/
|
||||
|
||||
+59
-18
@@ -45,6 +45,11 @@ __host__ __device__ inline glm::vec3 UVW(int vert,
|
||||
return uvw;
|
||||
}
|
||||
|
||||
__host__ __device__ inline glm::mat3 NormalTransform(
|
||||
const glm::mat4x3& transform) {
|
||||
return glm::inverse(glm::transpose(glm::mat3(transform)));
|
||||
}
|
||||
|
||||
/**
|
||||
* By using the closest axis-aligned projection to the normal instead of a
|
||||
* projection along the normal, we avoid introducing any rounding error.
|
||||
@@ -78,34 +83,43 @@ __host__ __device__ inline glm::vec3 GetBarycentric(const glm::vec3& v,
|
||||
triPos[1] - triPos[0]);
|
||||
const glm::vec3 d2(glm::dot(edges[0], edges[0]), glm::dot(edges[1], edges[1]),
|
||||
glm::dot(edges[2], edges[2]));
|
||||
int longside = d2[0] > d2[1] && d2[0] > d2[2] ? 0 : d2[1] > d2[2] ? 1 : 2;
|
||||
const int longSide = d2[0] > d2[1] && d2[0] > d2[2] ? 0
|
||||
: d2[1] > d2[2] ? 1
|
||||
: 2;
|
||||
const glm::vec3 crossP = glm::cross(edges[0], edges[1]);
|
||||
const float area2 = glm::dot(crossP, crossP);
|
||||
const float tol2 = precision * precision;
|
||||
const float vol = glm::dot(crossP, v - triPos[2]);
|
||||
if (vol * vol > area2 * tol2) return glm::vec3(NAN);
|
||||
|
||||
if (d2[longside] < tol2) { // point
|
||||
glm::vec3 uvw(0);
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const glm::vec3 dv = v - triPos[i];
|
||||
if (glm::dot(dv, dv) < tol2) {
|
||||
// Return exactly equal if within tolerance of vert.
|
||||
uvw[i] = 1;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
|
||||
if (d2[longSide] < tol2) { // point
|
||||
return glm::vec3(1, 0, 0);
|
||||
} else if (area2 > d2[longside] * tol2) { // triangle
|
||||
glm::vec3 uvw(0);
|
||||
for (int i : {0, 1, 2}) {
|
||||
int j = i + 1;
|
||||
if (j > 2) j -= 3;
|
||||
uvw[i] = glm::dot(glm::cross(edges[i], v - triPos[j]), crossP);
|
||||
} else if (area2 > d2[longSide] * tol2) { // triangle
|
||||
for (const int i : {0, 1, 2}) {
|
||||
const int j = Next3(i);
|
||||
const glm::vec3 crossPv = glm::cross(edges[i], v - triPos[j]);
|
||||
const float area2v = glm::dot(crossPv, crossPv);
|
||||
// Return exactly equal if within tolerance of edge.
|
||||
uvw[i] = area2v < d2[i] * tol2 ? 0 : glm::dot(crossPv, crossP);
|
||||
}
|
||||
uvw /= (uvw[0] + uvw[1] + uvw[2]);
|
||||
return uvw;
|
||||
} else { // line
|
||||
int nextside = longside + 1;
|
||||
if (nextside > 2) nextside -= 3;
|
||||
const int nextV = Next3(longSide);
|
||||
const float alpha =
|
||||
glm::dot(v - triPos[nextside], edges[longside]) / d2[longside];
|
||||
glm::vec3 uvw(0);
|
||||
uvw[longside] = 0;
|
||||
uvw[nextside++] = 1 - alpha;
|
||||
if (nextside > 2) nextside -= 3;
|
||||
uvw[nextside] = alpha;
|
||||
glm::dot(v - triPos[nextV], edges[longSide]) / d2[longSide];
|
||||
uvw[longSide] = 0;
|
||||
uvw[nextV] = 1 - alpha;
|
||||
const int lastV = Next3(nextV);
|
||||
uvw[lastV] = alpha;
|
||||
return uvw;
|
||||
}
|
||||
}
|
||||
@@ -125,6 +139,24 @@ struct Halfedge {
|
||||
}
|
||||
};
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
glm::vec3 uvw;
|
||||
};
|
||||
|
||||
struct TriRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// The triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri].
|
||||
int tri;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a temporary edge structure which only stores edges forward and
|
||||
* references the halfedge it was created from.
|
||||
@@ -191,5 +223,14 @@ inline std::ostream& operator<<(std::ostream& stream, const Halfedge& edge) {
|
||||
<< ", pairedHalfedge = " << edge.pairedHalfedge
|
||||
<< ", face = " << edge.face;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const Barycentric& bary) {
|
||||
return stream << "tri = " << bary.tri << ", uvw = " << bary.uvw;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const TriRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri;
|
||||
}
|
||||
#endif
|
||||
} // namespace manifold
|
||||
|
||||
@@ -33,10 +33,23 @@ __host__ __device__ int VertsPerTri(int edgeVerts) {
|
||||
return (edgeVerts * edgeVerts + edgeVerts) / 2;
|
||||
}
|
||||
|
||||
struct Barycentric {
|
||||
int tri;
|
||||
glm::vec3 uvw;
|
||||
};
|
||||
/**
|
||||
* Retained verts are part of several triangles, and it doesn't matter which one
|
||||
* the vertBary refers to. Here, whichever is last will win and it's done on the
|
||||
* CPU for simplicity for now. Using AtomicCAS on .tri should work for a GPU
|
||||
* version if desired.
|
||||
*/
|
||||
void FillRetainedVerts(VecDH<Barycentric>& vertBary,
|
||||
const VecDH<Halfedge>& halfedge_) {
|
||||
const int numTri = halfedge_.size() / 3;
|
||||
for (int tri = 0; tri < numTri; ++tri) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
glm::vec3 uvw(0);
|
||||
uvw[i] = 1;
|
||||
vertBary[halfedge_[3 * tri + i].startVert] = {tri, uvw};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReindexHalfedge {
|
||||
int* half2Edge;
|
||||
@@ -51,6 +64,7 @@ struct ReindexHalfedge {
|
||||
|
||||
struct EdgeVerts {
|
||||
glm::vec3* vertPos;
|
||||
Barycentric* vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
|
||||
@@ -59,35 +73,32 @@ struct EdgeVerts {
|
||||
TmpEdge edgeVerts = thrust::get<1>(in);
|
||||
|
||||
float invTotal = 1.0f / n;
|
||||
for (int i = 1; i < n; ++i)
|
||||
vertPos[startIdx + (n - 1) * edge + i - 1] =
|
||||
(float(n - i) * vertPos[edgeVerts.first] +
|
||||
float(i) * vertPos[edgeVerts.second]) *
|
||||
invTotal;
|
||||
for (int i = 1; i < n; ++i) {
|
||||
const int vert = startIdx + (n - 1) * edge + i - 1;
|
||||
const float v = i * invTotal;
|
||||
const float u = 1 - v;
|
||||
vertPos[vert] =
|
||||
u * vertPos[edgeVerts.first] + v * vertPos[edgeVerts.second];
|
||||
|
||||
const int tri = edgeVerts.halfedgeIdx / 3;
|
||||
const int idx = edgeVerts.halfedgeIdx - 3 * tri;
|
||||
glm::vec3 uvw(0);
|
||||
uvw[idx] = u;
|
||||
uvw[Next3(idx)] = v;
|
||||
vertBary[vert] = {tri, uvw};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InteriorVerts {
|
||||
glm::vec3* vertPos;
|
||||
glm::vec3* uvw;
|
||||
BaryRef* triBary;
|
||||
glm::vec3* uvwNew;
|
||||
BaryRef* triBaryNew;
|
||||
const glm::vec3* uvwOld;
|
||||
Barycentric* vertBary;
|
||||
const int startIdx;
|
||||
const int n;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, BaryRef> in) {
|
||||
const int tri = thrust::get<0>(in);
|
||||
const BaryRef baryOld = thrust::get<1>(in);
|
||||
|
||||
glm::mat3 uvwOldTri;
|
||||
for (int i : {0, 1, 2}) uvwOldTri[i] = UVW(baryOld.vertBary[i], uvwOld);
|
||||
|
||||
__host__ __device__ void operator()(int tri) {
|
||||
const float invTotal = 1.0f / n;
|
||||
int posTri = tri * n * n;
|
||||
int posBary = tri * VertsPerTri(n + 1);
|
||||
int pos = startIdx + tri * VertsPerTri(n - 2);
|
||||
for (int i = 0; i <= n; ++i) {
|
||||
for (int j = 0; j <= n - i; ++j) {
|
||||
@@ -95,34 +106,13 @@ struct InteriorVerts {
|
||||
const float u = invTotal * j;
|
||||
const float v = invTotal * k;
|
||||
const float w = invTotal * i;
|
||||
const int first = posBary;
|
||||
uvw[posBary] = {u, v, w};
|
||||
uvwNew[posBary] = uvwOldTri * uvw[posBary];
|
||||
++posBary;
|
||||
if (j == n - i) continue;
|
||||
if (i == 0 || j == 0 || k == 0 || j == n - i) continue;
|
||||
|
||||
// The three retained verts are denoted by their index - 3. uvw entries
|
||||
// are added for them out of laziness of indexing only.
|
||||
const int a = (k == n) ? -2 : first;
|
||||
const int b = (i == n - 1) ? -1 : first + n - i + 1;
|
||||
const int c = (j == n - 1) ? -3 : first + 1;
|
||||
glm::ivec3 vertBary(c, a, b);
|
||||
triBary[posTri] = {-1, -1, tri, vertBary};
|
||||
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID, baryOld.tri,
|
||||
vertBary};
|
||||
if (j < n - 1 - i) {
|
||||
int d = b + 1; // d cannot be a retained vert
|
||||
vertBary = {b, d, c};
|
||||
triBary[posTri] = {-1, -1, tri, vertBary};
|
||||
triBaryNew[posTri++] = {baryOld.meshID, baryOld.originalID,
|
||||
baryOld.tri, vertBary};
|
||||
}
|
||||
vertPos[pos] = u * vertPos[halfedge[3 * tri].startVert] + //
|
||||
v * vertPos[halfedge[3 * tri + 1].startVert] + //
|
||||
w * vertPos[halfedge[3 * tri + 2].startVert];
|
||||
|
||||
if (i == 0 || j == 0 || k == 0) continue;
|
||||
|
||||
vertPos[pos++] = u * vertPos[halfedge[3 * tri].startVert] + //
|
||||
v * vertPos[halfedge[3 * tri + 1].startVert] + //
|
||||
w * vertPos[halfedge[3 * tri + 2].startVert];
|
||||
vertBary[pos++] = {tri, {u, v, w}};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,24 +212,6 @@ struct SmoothBezier {
|
||||
}
|
||||
};
|
||||
|
||||
struct TriBary2Vert {
|
||||
Barycentric* vertBary;
|
||||
int* lock;
|
||||
const glm::vec3* uvw;
|
||||
const Halfedge* halfedge;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<BaryRef, int> in) {
|
||||
const BaryRef baryRef = thrust::get<0>(in);
|
||||
const int tri = thrust::get<1>(in);
|
||||
|
||||
for (int i : {0, 1, 2}) {
|
||||
int vert = halfedge[3 * tri + i].startVert;
|
||||
if (AtomicAdd(lock[vert], 1) != 0) continue;
|
||||
vertBary[vert] = {baryRef.tri, UVW(baryRef.vertBary[i], uvw)};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct InterpTri {
|
||||
const Halfedge* halfedge;
|
||||
const glm::vec4* halfedgeTangent;
|
||||
@@ -365,14 +337,14 @@ void Manifold::Impl::CreateTangents(
|
||||
vertNormal_.cptrD(), halfedge_.cptrD()}));
|
||||
|
||||
if (!sharpenedEdges.empty()) {
|
||||
const VecDH<BaryRef>& triBary = meshRelation_.triBary;
|
||||
const VecDH<TriRef>& triRef = meshRelation_.triRef;
|
||||
|
||||
// sharpenedEdges are referenced to the input Mesh, but the triangles have
|
||||
// been sorted in creating the Manifold, so the indices are converted using
|
||||
// meshRelation_.
|
||||
std::vector<int> oldHalfedge2New(halfedge_.size());
|
||||
for (int tri = 0; tri < NumTri(); ++tri) {
|
||||
int oldTri = triBary[tri].tri;
|
||||
int oldTri = triRef[tri].tri;
|
||||
for (int i : {0, 1, 2}) oldHalfedge2New[3 * oldTri + i] = 3 * tri + i;
|
||||
}
|
||||
|
||||
@@ -459,24 +431,22 @@ void Manifold::Impl::CreateTangents(
|
||||
* run after the new vertices have moved, which is a likely scenario after
|
||||
* refinement (smoothing).
|
||||
*/
|
||||
Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return meshRelation_;
|
||||
VecDH<Barycentric> Manifold::Impl::Subdivide(int n) {
|
||||
if (n < 2) return VecDH<Barycentric>();
|
||||
faceNormal_.resize(0);
|
||||
vertNormal_.resize(0);
|
||||
int numVert = NumVert();
|
||||
int numEdge = NumEdge();
|
||||
int numTri = NumTri();
|
||||
const int numVert = NumVert();
|
||||
const int numEdge = NumEdge();
|
||||
const int numTri = NumTri();
|
||||
// Append new verts
|
||||
int vertsPerEdge = n - 1;
|
||||
int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
const int vertsPerEdge = n - 1;
|
||||
const int triVertStart = numVert + numEdge * vertsPerEdge;
|
||||
vertPos_.resize(triVertStart + numTri * VertsPerTri(n - 2));
|
||||
VecDH<Barycentric> vertBary(vertPos_.size());
|
||||
FillRetainedVerts(vertBary, halfedge_);
|
||||
|
||||
MeshRelationD relation;
|
||||
relation.barycentric.resize(numTri * VertsPerTri(n + 1));
|
||||
relation.triBary.resize(n * n * numTri);
|
||||
MeshRelationD oldMeshRelation = std::move(meshRelation_);
|
||||
meshRelation_.barycentric.resize(relation.barycentric.size());
|
||||
meshRelation_.triBary.resize(relation.triBary.size());
|
||||
meshRelation_.triRef.resize(n * n * numTri);
|
||||
meshRelation_.originalID = oldMeshRelation.originalID;
|
||||
|
||||
VecDH<TmpEdge> edges = CreateTmpEdges(halfedge_);
|
||||
@@ -485,36 +455,40 @@ Manifold::Impl::MeshRelationD Manifold::Impl::Subdivide(int n) {
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
ReindexHalfedge({half2Edge.ptrD()}));
|
||||
for_each_n(policy, zip(countAt(0), edges.begin()), numEdge,
|
||||
EdgeVerts({vertPos_.ptrD(), numVert, n}));
|
||||
for_each_n(
|
||||
policy, zip(countAt(0), oldMeshRelation.triBary.begin()), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), relation.barycentric.ptrD(),
|
||||
relation.triBary.ptrD(), meshRelation_.barycentric.ptrD(),
|
||||
meshRelation_.triBary.ptrD(),
|
||||
oldMeshRelation.barycentric.cptrD(), triVertStart, n,
|
||||
halfedge_.ptrD()}));
|
||||
EdgeVerts({vertPos_.ptrD(), vertBary.ptrD(), numVert, n}));
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
InteriorVerts({vertPos_.ptrD(), vertBary.ptrD(), triVertStart, n,
|
||||
halfedge_.cptrD()}));
|
||||
// Create sub-triangles
|
||||
VecDH<glm::ivec3> triVerts(n * n * numTri);
|
||||
for_each_n(policy, countAt(0), numTri,
|
||||
SplitTris({triVerts.ptrD(), halfedge_.cptrD(), half2Edge.cptrD(),
|
||||
numVert, triVertStart, n}));
|
||||
CreateHalfedges(triVerts);
|
||||
return relation;
|
||||
// Make original since the subdivided faces are intended to be warped into
|
||||
// being non-coplanar, and hence not being related to the original faces.
|
||||
meshRelation_.originalID = ReserveIDs(1);
|
||||
InitializeOriginal();
|
||||
|
||||
if (meshRelation_.numProp > 0) {
|
||||
meshRelation_.properties.resize(meshRelation_.numProp * numVert);
|
||||
meshRelation_.triProperties.resize(meshRelation_.triRef.size());
|
||||
// Fill properties according to barycentric.
|
||||
// Set triProp to share properties on continuous edges.
|
||||
// Duplicate properties will be removed during sorting.
|
||||
}
|
||||
|
||||
return vertBary;
|
||||
}
|
||||
|
||||
void Manifold::Impl::Refine(int n) {
|
||||
Manifold::Impl old = *this;
|
||||
MeshRelationD relation = Subdivide(n);
|
||||
VecDH<Barycentric> vertBary = Subdivide(n);
|
||||
if (vertBary.size() == 0) return;
|
||||
|
||||
if (old.halfedgeTangent_.size() == old.halfedge_.size()) {
|
||||
VecDH<Barycentric> vertBary(NumVert());
|
||||
VecDH<int> lock(NumVert(), 0);
|
||||
auto policy = autoPolicy(NumTri());
|
||||
for_each_n(policy, zip(relation.triBary.begin(), countAt(0)), NumTri(),
|
||||
TriBary2Vert({vertBary.ptrD(), lock.ptrD(),
|
||||
relation.barycentric.cptrD(), halfedge_.cptrD()}));
|
||||
|
||||
for_each_n(policy, zip(vertPos_.begin(), vertBary.begin()), NumVert(),
|
||||
for_each_n(autoPolicy(NumTri()), zip(vertPos_.begin(), vertBary.begin()),
|
||||
NumVert(),
|
||||
InterpTri({old.halfedge_.cptrD(), old.halfedgeTangent_.cptrD(),
|
||||
old.vertPos_.cptrD()}));
|
||||
}
|
||||
|
||||
@@ -119,6 +119,42 @@ struct Reindex {
|
||||
}
|
||||
};
|
||||
|
||||
struct MarkProp {
|
||||
int* keep;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3 triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
keep[triProp[i]] = 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GatherProps {
|
||||
float* properties;
|
||||
const float* oldProperties;
|
||||
const int numProp;
|
||||
|
||||
__host__ __device__ void operator()(thrust::tuple<int, int, int> in) {
|
||||
const int oldIdx = thrust::get<0>(in);
|
||||
const int newIdx = thrust::get<1>(in);
|
||||
const int keep = thrust::get<2>(in);
|
||||
if (keep == 0) return;
|
||||
for (int p = 0; p < numProp; ++p) {
|
||||
properties[newIdx * numProp + p] = oldProperties[oldIdx * numProp + p];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ReindexProps {
|
||||
const int* old2new;
|
||||
|
||||
__host__ __device__ void operator()(glm::ivec3& triProp) {
|
||||
for (const int i : {0, 1, 2}) {
|
||||
triProp[i] = old2new[triProp[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
VecDH<T> tmp(std::move(inOut));
|
||||
@@ -127,7 +163,7 @@ void Permute(VecDH<T>& inOut, const VecDH<int>& new2Old) {
|
||||
tmp.begin(), inOut.begin());
|
||||
}
|
||||
|
||||
template void Permute<BaryRef>(VecDH<BaryRef>&, const VecDH<int>&);
|
||||
template void Permute<TriRef>(VecDH<TriRef>&, const VecDH<int>&);
|
||||
template void Permute<glm::vec3>(VecDH<glm::vec3>&, const VecDH<int>&);
|
||||
|
||||
struct ReindexFace {
|
||||
@@ -182,6 +218,7 @@ void Manifold::Impl::Finish() {
|
||||
GetFaceBoxMorton(faceBox, faceMorton);
|
||||
SortFaces(faceBox, faceMorton);
|
||||
if (halfedge_.size() == 0) return;
|
||||
CompactProps();
|
||||
|
||||
ASSERT(halfedge_.size() % 6 == 0, topologyErr,
|
||||
"Not an even number of faces after sorting faces!");
|
||||
@@ -202,8 +239,8 @@ void Manifold::Impl::Finish() {
|
||||
"Halfedge index is negative!");
|
||||
ASSERT(extrema.pairedHalfedge < 2 * NumEdge(), topologyErr,
|
||||
"Halfedge index exceeds number of halfedges!");
|
||||
ASSERT(meshRelation_.triBary.size() == NumTri() ||
|
||||
meshRelation_.triBary.size() == 0,
|
||||
ASSERT(meshRelation_.triRef.size() == NumTri() ||
|
||||
meshRelation_.triRef.size() == 0,
|
||||
logicErr, "Mesh Relation doesn't fit!");
|
||||
ASSERT(faceNormal_.size() == NumTri() || faceNormal_.size() == 0, logicErr,
|
||||
"faceNormal size = " + std::to_string(faceNormal_.size()) +
|
||||
@@ -263,6 +300,32 @@ void Manifold::Impl::ReindexVerts(const VecDH<int>& vertNew2Old,
|
||||
Reindex({vertOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes unreferenced property verts and reindexes triProperties.
|
||||
*/
|
||||
void Manifold::Impl::CompactProps() {
|
||||
if (meshRelation_.numProp == 0) return;
|
||||
|
||||
const int numVerts = meshRelation_.properties.size() / meshRelation_.numProp;
|
||||
VecDH<int> keep(numVerts, 0);
|
||||
auto policy = autoPolicy(numVerts);
|
||||
|
||||
for_each(policy, meshRelation_.triProperties.cbegin(),
|
||||
meshRelation_.triProperties.cend(), MarkProp({keep.ptrD()}));
|
||||
VecDH<int> propOld2New(numVerts + 1, 0);
|
||||
inclusive_scan(policy, keep.begin(), keep.end(), propOld2New.begin() + 1);
|
||||
|
||||
VecDH<float> oldProp = meshRelation_.properties;
|
||||
const int numVertsNew = propOld2New[numVerts];
|
||||
meshRelation_.properties.resize(meshRelation_.numProp * numVertsNew);
|
||||
for_each_n(policy, zip(countAt(0), propOld2New.cbegin(), keep.cbegin()),
|
||||
numVerts,
|
||||
GatherProps({meshRelation_.properties.ptrD(), oldProp.cptrD(),
|
||||
meshRelation_.numProp}));
|
||||
for_each_n(policy, meshRelation_.triProperties.begin(), NumTri(),
|
||||
ReindexProps({propOld2New.cptrD()}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the faceBox and faceMorton input with the bounding boxes and Morton
|
||||
* codes of the faces, respectively. The Morton code is based on the center of
|
||||
@@ -310,9 +373,10 @@ void Manifold::Impl::SortFaces(VecDH<Box>& faceBox,
|
||||
*/
|
||||
void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
if (meshRelation_.triBary.size() == NumTri())
|
||||
Permute(meshRelation_.triBary, faceNew2Old);
|
||||
|
||||
if (meshRelation_.triRef.size() == NumTri())
|
||||
Permute(meshRelation_.triRef, faceNew2Old);
|
||||
if (meshRelation_.triProperties.size() == NumTri())
|
||||
Permute(meshRelation_.triProperties, faceNew2Old);
|
||||
if (faceNormal_.size() == NumTri()) Permute(faceNormal_, faceNew2Old);
|
||||
|
||||
VecDH<Halfedge> oldHalfedge(std::move(halfedge_));
|
||||
@@ -333,11 +397,23 @@ void Manifold::Impl::GatherFaces(const VecDH<int>& faceNew2Old) {
|
||||
void Manifold::Impl::GatherFaces(const Impl& old,
|
||||
const VecDH<int>& faceNew2Old) {
|
||||
const int numTri = faceNew2Old.size();
|
||||
meshRelation_.triBary.resize(numTri);
|
||||
auto policy = autoPolicy(numTri);
|
||||
|
||||
meshRelation_.triRef.resize(numTri);
|
||||
gather(policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triBary.begin(), meshRelation_.triBary.begin());
|
||||
meshRelation_.barycentric = old.meshRelation_.barycentric;
|
||||
old.meshRelation_.triRef.begin(), meshRelation_.triRef.begin());
|
||||
|
||||
for (const auto& pair : old.meshRelation_.meshIDtransform) {
|
||||
meshRelation_.meshIDtransform[pair.first] = pair.second;
|
||||
}
|
||||
|
||||
if (old.meshRelation_.triProperties.size() > 0) {
|
||||
meshRelation_.triProperties.resize(numTri);
|
||||
gather(policy, faceNew2Old.begin(), faceNew2Old.end(),
|
||||
old.meshRelation_.triProperties.begin(),
|
||||
meshRelation_.triProperties.begin());
|
||||
meshRelation_.properties = old.meshRelation_.properties;
|
||||
}
|
||||
|
||||
if (old.faceNormal_.size() == old.NumTri()) {
|
||||
faceNormal_.resize(numTri);
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project (polygon)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
@@ -25,4 +25,4 @@ target_link_libraries( ${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+43
-103
@@ -14,37 +14,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hashtable.h"
|
||||
#include "optional"
|
||||
#include "par.h"
|
||||
#include "public.h"
|
||||
#include "utils.h"
|
||||
#include "vec_dh.h"
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
typedef unsigned long long int Uint64;
|
||||
|
||||
constexpr Uint64 kOpen = std::numeric_limits<Uint64>::max();
|
||||
|
||||
__host__ __device__ Uint64 AtomicCAS(Uint64& target, Uint64 compare,
|
||||
Uint64 val) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
return atomicCAS(&target, compare, val);
|
||||
#else
|
||||
std::atomic<Uint64>& tar = reinterpret_cast<std::atomic<Uint64>&>(target);
|
||||
tar.compare_exchange_strong(compare, val);
|
||||
return compare;
|
||||
#endif
|
||||
}
|
||||
|
||||
__host__ __device__ int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
__host__ __device__ int Prev3(int i) {
|
||||
constexpr glm::ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
__host__ __device__ Uint64 identity(Uint64 x) { return x; }
|
||||
|
||||
__host__ __device__ glm::ivec3 TetTri0(int i) {
|
||||
constexpr glm::ivec3 tetTri0[16] = {{-1, -1, -1}, //
|
||||
@@ -135,7 +114,6 @@ __host__ __device__ glm::ivec4 DecodeMorton(Uint64 code) {
|
||||
}
|
||||
|
||||
struct GridVert {
|
||||
Uint64 key = kOpen;
|
||||
float distance = NAN;
|
||||
int edgeVerts[7] = {-1, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
@@ -146,73 +124,11 @@ struct GridVert {
|
||||
}
|
||||
};
|
||||
|
||||
class HashTableD {
|
||||
public:
|
||||
HashTableD(VecDH<GridVert>& alloc, VecDH<uint32_t>& used, uint32_t step = 127)
|
||||
: step_{step}, table_{alloc}, used_{used} {}
|
||||
|
||||
__host__ __device__ int Size() const { return table_.size(); }
|
||||
|
||||
__host__ __device__ bool Full() const { return used_[0] * 2 > Size(); }
|
||||
|
||||
__host__ __device__ void Insert(const GridVert& vert) {
|
||||
uint32_t idx = vert.key & (Size() - 1);
|
||||
while (1) {
|
||||
Uint64& key = table_[idx].key;
|
||||
const Uint64 found = AtomicCAS(key, kOpen, vert.key);
|
||||
if (found == kOpen) {
|
||||
AtomicAdd(used_[0], 0x1u);
|
||||
table_[idx] = vert;
|
||||
return;
|
||||
}
|
||||
if (found == vert.key) return;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ GridVert operator[](Uint64 key) const {
|
||||
uint32_t idx = key & (Size() - 1);
|
||||
while (1) {
|
||||
const GridVert found = table_[idx];
|
||||
if (found.key == key || found.key == kOpen) return found;
|
||||
idx = (idx + step_) & (Size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
__host__ __device__ GridVert At(int idx) const { return table_[idx]; }
|
||||
|
||||
private:
|
||||
uint32_t step_;
|
||||
VecD<GridVert> table_;
|
||||
VecD<uint32_t> used_;
|
||||
};
|
||||
|
||||
class HashTable {
|
||||
public:
|
||||
HashTable(uint32_t size, uint32_t step = 127)
|
||||
: alloc_{1 << (int)ceil(log2(size)), {}}, table_{alloc_, used_, step} {}
|
||||
|
||||
HashTableD D() { return table_; }
|
||||
|
||||
int Entries() const { return used_[0]; }
|
||||
|
||||
int Size() const { return table_.Size(); }
|
||||
|
||||
bool Full() const { return used_[0] * 2 > Size(); }
|
||||
|
||||
float FilledFraction() const { return static_cast<float>(used_[0]) / Size(); }
|
||||
|
||||
private:
|
||||
VecDH<GridVert> alloc_;
|
||||
VecDH<uint32_t> used_ = VecDH<uint32_t>(1, 0);
|
||||
HashTableD table_;
|
||||
};
|
||||
|
||||
template <typename Func>
|
||||
struct ComputeVerts {
|
||||
glm::vec3* vertPos;
|
||||
int* vertIndex;
|
||||
HashTableD gridVerts;
|
||||
HashTableD<GridVert, identity> gridVerts;
|
||||
const Func sdf;
|
||||
const glm::vec3 origin;
|
||||
const glm::ivec3 gridSize;
|
||||
@@ -247,7 +163,6 @@ struct ComputeVerts {
|
||||
const glm::vec3 position = Position(gridIndex);
|
||||
|
||||
GridVert gridVert;
|
||||
gridVert.key = mortonCode;
|
||||
gridVert.distance = BoundedSDF(gridIndex);
|
||||
|
||||
bool keep = false;
|
||||
@@ -270,14 +185,14 @@ struct ComputeVerts {
|
||||
gridVert.edgeVerts[i] = idx;
|
||||
}
|
||||
|
||||
if (keep) gridVerts.Insert(gridVert);
|
||||
if (keep) gridVerts.Insert(mortonCode, gridVert);
|
||||
}
|
||||
};
|
||||
|
||||
struct BuildTris {
|
||||
glm::ivec3* triVerts;
|
||||
int* triIndex;
|
||||
const HashTableD gridVerts;
|
||||
const HashTableD<GridVert, identity> gridVerts;
|
||||
|
||||
__host__ __device__ void CreateTri(const glm::ivec3& tri,
|
||||
const int edges[6]) {
|
||||
@@ -295,10 +210,11 @@ struct BuildTris {
|
||||
}
|
||||
|
||||
__host__ __device__ void operator()(int idx) {
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
if (base.key == kOpen) return;
|
||||
Uint64 basekey = gridVerts.KeyAt(idx);
|
||||
if (basekey == kOpen) return;
|
||||
|
||||
const glm::ivec4 baseIndex = DecodeMorton(base.key);
|
||||
const GridVert& base = gridVerts.At(idx);
|
||||
const glm::ivec4 baseIndex = DecodeMorton(basekey);
|
||||
|
||||
glm::ivec4 leadIndex = baseIndex;
|
||||
if (leadIndex.w == 0)
|
||||
@@ -355,6 +271,20 @@ 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 {
|
||||
@@ -388,7 +318,8 @@ namespace manifold {
|
||||
* input to the Manifold constructor for further operations.
|
||||
*/
|
||||
template <typename Func>
|
||||
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0,
|
||||
std::optional<ExecutionPolicy> policy = std::nullopt) {
|
||||
Mesh out;
|
||||
|
||||
const glm::vec3 dim = bounds.Size();
|
||||
@@ -397,17 +328,26 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
const glm::vec3 spacing = dim / (glm::vec3(gridSize));
|
||||
|
||||
const Uint64 maxMorton = MortonCode(glm::ivec4(gridSize + 1, 1));
|
||||
const auto policy = autoPolicy(maxMorton);
|
||||
|
||||
// Parallel policies violate will crash language runtimes with runtime locks
|
||||
// that expect to not be called back by unregistered threads. This allows
|
||||
// bindings use LevelSet despite being compiled with MANIFOLD_PAR
|
||||
// active (CUDA is already avoided when Func is a function ptr).
|
||||
const auto pol =
|
||||
(!policy.has_value() || (policy.value() == ParUnseq && !CudaEnabled()))
|
||||
? autoPolicy(maxMorton)
|
||||
: policy.value();
|
||||
|
||||
int tableSize = glm::min(
|
||||
2 * maxMorton, static_cast<Uint64>(10 * glm::pow(maxMorton, 0.667)));
|
||||
HashTable gridVerts(tableSize);
|
||||
HashTable<GridVert, identity> gridVerts(tableSize);
|
||||
VecDH<glm::vec3> vertPos(gridVerts.Size() * 7);
|
||||
|
||||
while (1) {
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(
|
||||
policy, countAt(0), maxMorton + 1,
|
||||
// avoid handing dynamic function pointers to CUDA
|
||||
for_each_n_wrapper<Func>(
|
||||
pol, maxMorton + 1,
|
||||
ComputeVerts<Func>({vertPos.ptrD(), index.ptrD(), gridVerts.D(), sdf,
|
||||
bounds.min, gridSize + 1, spacing, level}));
|
||||
|
||||
@@ -420,7 +360,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
tableSize *= 2;
|
||||
else
|
||||
tableSize *= ratio;
|
||||
gridVerts = HashTable(tableSize);
|
||||
gridVerts = HashTable<GridVert, identity>(tableSize);
|
||||
vertPos = VecDH<glm::vec3>(gridVerts.Size() * 7);
|
||||
} else { // Success
|
||||
vertPos.resize(index[0]);
|
||||
@@ -431,7 +371,7 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
VecDH<glm::ivec3> triVerts(gridVerts.Entries() * 12); // worst case
|
||||
|
||||
VecDH<int> index(1, 0);
|
||||
for_each_n(policy, countAt(0), gridVerts.Size(),
|
||||
for_each_n(pol, countAt(0), gridVerts.Size(),
|
||||
BuildTris({triVerts.ptrD(), index.ptrD(), gridVerts.D()}));
|
||||
triVerts.resize(index[0]);
|
||||
|
||||
@@ -440,4 +380,4 @@ inline Mesh LevelSet(Func sdf, Box bounds, float edgeLength, float level = 0) {
|
||||
return out;
|
||||
}
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
} // namespace manifold
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
project (graphlite)
|
||||
|
||||
add_library(${PROJECT_NAME} src/connected_components.cpp)
|
||||
add_library(${PROJECT_NAME} OBJECT src/connected_components.cpp)
|
||||
|
||||
target_include_directories(${PROJECT_NAME}
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}/include
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
project(utilities)
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(${PROJECT_NAME} ${SOURCE_FILES})
|
||||
add_library(${PROJECT_NAME} OBJECT ${SOURCE_FILES})
|
||||
|
||||
message("CUDA Support: ${MANIFOLD_USE_CUDA}")
|
||||
message("Parallel Backend: ${MANIFOLD_PAR}")
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
// 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
|
||||
+76
-111
@@ -89,11 +89,6 @@ inline HOST_DEVICE glm::mat4x3 RotateUp(glm::vec3 up) {
|
||||
return glm::mat4x3(glm::rotate(glm::mat4(1), angle, axis));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 1 for positive values, -1 for negative, and 0 for exactly zero.
|
||||
*/
|
||||
inline HOST_DEVICE int Signum(float val) { return (val > 0) - (val < 0); }
|
||||
|
||||
/**
|
||||
* Determines if the three points are wound counter-clockwise, clockwise, or
|
||||
* colinear within the specified tolerance.
|
||||
@@ -148,22 +143,6 @@ using SimplePolygon = std::vector<PolyVert>;
|
||||
using Polygons = std::vector<SimplePolygon>;
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of vertices
|
||||
int NumVert() const { return this->vertPos.size() / 3; };
|
||||
/// Number of triangles
|
||||
int NumTri() const { return this->triVerts.size() / 3; };
|
||||
|
||||
std::vector<float> vertPos;
|
||||
std::vector<float> vertNormal;
|
||||
std::vector<uint32_t> triVerts;
|
||||
std::vector<float> halfedgeTangent;
|
||||
};
|
||||
|
||||
/**
|
||||
* The triangle-mesh input and output of this library.
|
||||
*/
|
||||
@@ -182,45 +161,81 @@ struct Mesh {
|
||||
/// as 3 * tri + i, representing the tangent from Mesh.triVerts[tri][i] along
|
||||
/// the CCW edge. If empty, mesh is faceted.
|
||||
std::vector<glm::vec4> halfedgeTangent;
|
||||
};
|
||||
|
||||
Mesh() = default;
|
||||
Mesh(const std::vector<glm::vec3>& vertPos_,
|
||||
const std::vector<glm::ivec3>& triVerts_,
|
||||
const std::vector<glm::vec3>& vertNormal_ = {},
|
||||
const std::vector<glm::vec4>& halfedgeTangent_ = {})
|
||||
: vertPos(vertPos_),
|
||||
triVerts(triVerts_),
|
||||
vertNormal(vertNormal_),
|
||||
halfedgeTangent(halfedgeTangent_) {}
|
||||
/**
|
||||
* An alternative to Mesh for output suitable for pushing into graphics
|
||||
* libraries directly. This may not be manifold since the verts are duplicated
|
||||
* along property boundaries that do not match. The additional merge vectors
|
||||
* store this missing information, allowing the manifold to be reconstructed.
|
||||
*/
|
||||
struct MeshGL {
|
||||
/// Number of property vertices
|
||||
uint32_t NumVert() const { return vertProperties.size() / numProp; };
|
||||
/// Number of triangles
|
||||
uint32_t NumTri() const { return triVerts.size() / 3; };
|
||||
|
||||
Mesh(const MeshGL& in) {
|
||||
const int numTri = in.NumTri();
|
||||
const int numVert = in.NumVert();
|
||||
triVerts.resize(numTri);
|
||||
vertPos.resize(numVert);
|
||||
if (!in.vertNormal.empty()) {
|
||||
vertNormal.resize(numVert);
|
||||
/// Number of properties per vertex, always >= 3.
|
||||
uint32_t numProp = 3;
|
||||
/// Flat, GL-style interleaved list of all vertex properties: propVal =
|
||||
/// vertProperties[vert * numProp + propIdx]. The first three properties are
|
||||
/// always the position x, y, z.
|
||||
std::vector<float> vertProperties;
|
||||
/// The vertex indices of the three triangle corners in CCW (from the outside)
|
||||
/// order, for each triangle.
|
||||
std::vector<uint32_t> triVerts;
|
||||
/// Optional: A list of only the vertex indicies that need to be merged to
|
||||
/// reconstruct the manifold.
|
||||
std::vector<uint32_t> mergeFromVert;
|
||||
/// Optional: The same length as mergeFromVert, and the corresponding value
|
||||
/// contains the vertex to merge with. It will have an identical position, but
|
||||
/// the other properties may differ.
|
||||
std::vector<uint32_t> mergeToVert;
|
||||
/// Optional: Indicates runs of triangles that correspond to a particular
|
||||
/// input mesh instance. The runs encompass all of triVerts and are sorted
|
||||
/// by 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];
|
||||
}
|
||||
|
||||
for (int i = 0; i < numVert; ++i) {
|
||||
vertPos[i] = {in.vertPos[3 * i], in.vertPos[3 * i + 1],
|
||||
in.vertPos[3 * i + 2]};
|
||||
if (!in.vertNormal.empty()) {
|
||||
vertNormal[i] = {in.vertNormal[3 * i], in.vertNormal[3 * i + 1],
|
||||
in.vertNormal[3 * i + 2]};
|
||||
}
|
||||
triVerts.resize(3 * mesh.triVerts.size());
|
||||
for (int i = 0; i < mesh.triVerts.size(); ++i) {
|
||||
for (int j : {0, 1, 2}) triVerts[3 * i + j] = mesh.triVerts[i][j];
|
||||
}
|
||||
for (int i = 0; i < numTri; ++i) {
|
||||
triVerts[i] = {in.triVerts[3 * i], in.triVerts[3 * i + 1],
|
||||
in.triVerts[3 * i + 2]};
|
||||
if (!in.halfedgeTangent.empty()) {
|
||||
for (const int j : {0, 1, 2})
|
||||
halfedgeTangent[3 * i + j] = {
|
||||
in.halfedgeTangent[4 * (3 * i + j)],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 1],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 2],
|
||||
in.halfedgeTangent[4 * (3 * i + j) + 3]};
|
||||
}
|
||||
halfedgeTangent.resize(4 * mesh.halfedgeTangent.size());
|
||||
for (int i = 0; i < mesh.halfedgeTangent.size(); ++i) {
|
||||
for (int j : {0, 1, 2, 3})
|
||||
halfedgeTangent[4 * i + j] = mesh.halfedgeTangent[i][j];
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -256,59 +271,15 @@ struct Curvature {
|
||||
};
|
||||
|
||||
/**
|
||||
* Part of MeshRelation - represents a single triangle relation to an original
|
||||
* Mesh.
|
||||
* Connected component indices and number of discrete components. Intermediate
|
||||
* step for Manifold.Decompose(), completed by Manifold.GetComponents()
|
||||
*/
|
||||
struct BaryRef {
|
||||
/// The unique ID of the mesh instance of this triangle. If .meshID and .tri
|
||||
/// match for two triangles, then they are coplanar and came from the same
|
||||
/// face.
|
||||
int meshID;
|
||||
/// The OriginalID of the mesh this triangle came from. This ID is ideal for
|
||||
/// reapplying properties like UV coordinates to the output mesh.
|
||||
int originalID;
|
||||
/// The triangle index of the original triangle this was part of:
|
||||
/// Mesh.triVerts[tri].
|
||||
int tri;
|
||||
/// For the three corners of the output triangle, new (intersection) vertices
|
||||
/// store an index to the MeshRelation.barycentric vector, while original
|
||||
/// vertices have negative values referring to Mesh.triVerts[tri][i + 3].
|
||||
glm::ivec3 vertBary;
|
||||
struct Components {
|
||||
std::vector<int> indices;
|
||||
int numComponents;
|
||||
};
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @ingroup Connections
|
||||
* Represents the relationship of this output Mesh to all input Meshes that
|
||||
* eventually led to it, see Manifold.GetMeshRelation().
|
||||
*/
|
||||
struct MeshRelation {
|
||||
/// A vector of shared barycentric coordinates representing the position of a
|
||||
/// vertex relative to its original triangle.
|
||||
std::vector<glm::vec3> barycentric;
|
||||
/// A vector matching Mesh.triVerts that contains the relation of each output
|
||||
/// triangle to a single input triangle.
|
||||
std::vector<BaryRef> triBary;
|
||||
|
||||
/**
|
||||
* A convenience function to get the barycentric coordinates of a given
|
||||
* corner.
|
||||
*
|
||||
* @param tri A valid triangle index of Mesh.triVerts.
|
||||
* @param vert The corner of the triangle: 0, 1, or 2.
|
||||
*/
|
||||
inline glm::vec3 UVW(int tri, int vert) {
|
||||
glm::vec3 uvw(0.0f);
|
||||
const int idx = triBary[tri].vertBary[vert];
|
||||
if (idx < 0) {
|
||||
uvw[idx + 3] = 1;
|
||||
} else {
|
||||
uvw = barycentric[idx];
|
||||
}
|
||||
return uvw;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @ingroup Connections
|
||||
* Axis-aligned bounding box
|
||||
@@ -541,12 +512,6 @@ inline std::ostream& operator<<(std::ostream& stream, const glm::mat4x3& mat) {
|
||||
<< tam[2] << std::endl;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, const BaryRef& ref) {
|
||||
return stream << "meshID: " << ref.meshID
|
||||
<< ", originalID: " << ref.originalID << ", tri: " << ref.tri
|
||||
<< ", uvw idx: " << ref.vertBary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the contents of this vector to standard output. Only exists if compiled
|
||||
* with MANIFOLD_DEBUG flag.
|
||||
|
||||
@@ -103,6 +103,16 @@ thrust::counting_iterator<T> countAt(T i) {
|
||||
return thrust::make_counting_iterator(i);
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Next3(int i) {
|
||||
constexpr glm::ivec3 next3(1, 2, 0);
|
||||
return next3[i];
|
||||
}
|
||||
|
||||
inline __host__ __device__ int Prev3(int i) {
|
||||
constexpr glm::ivec3 prev3(2, 0, 1);
|
||||
return prev3[i];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__host__ __device__ T AtomicAdd(T& target, T add) {
|
||||
#ifdef __CUDA_ARCH__
|
||||
|
||||
+16
-2
@@ -19,9 +19,18 @@ add_subdirectory(third_party)
|
||||
enable_testing()
|
||||
|
||||
set(SOURCE_FILES polygon_test.cpp mesh_test.cpp sdf_test.cpp samples_test.cpp test_main.cpp)
|
||||
|
||||
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)
|
||||
@@ -29,12 +38,17 @@ if(MANIFOLD_EXPORT)
|
||||
endif()
|
||||
|
||||
if(MANIFOLD_USE_CUDA)
|
||||
set_source_files_properties(mesh_test.cpp sdf_test.cpp PROPERTIES LANGUAGE CUDA)
|
||||
set(CUDA_FILES 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_property(TARGET ${PROJECT_NAME} PROPERTY CUDA_ARCHITECTURES 61)
|
||||
endif()
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
add_test(test_all ${PROJECT_NAME})
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "manifoldc.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "manifold.h"
|
||||
#include "polygon.h"
|
||||
#include "sdf.h"
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
#include "meshIO.h"
|
||||
#endif
|
||||
|
||||
float eps = 0.000001;
|
||||
|
||||
bool approx_float_array(float *a, float *b, size_t len) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (abs(a[i] - b[i]) > eps) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(CBIND, sphere) {
|
||||
int n = 25;
|
||||
size_t sz = manifold_manifold_size();
|
||||
ManifoldManifold *sphere = manifold_sphere(malloc(sz), 1.0f, 4 * n);
|
||||
|
||||
EXPECT_EQ(manifold_status(sphere), 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]);
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,4 @@ target_link_libraries(${PROJECT_NAME}
|
||||
)
|
||||
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE ${MANIFOLD_FLAGS})
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
|
||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
||||
|
||||
@@ -41,8 +41,14 @@ struct Material {
|
||||
glm::vec4 color = glm::vec4(1.0f);
|
||||
/// Optional: If non-empty, must match Mesh.vertPos. Provides an RGBA color
|
||||
/// for each vertex, linearly interpolated across triangles. Colors are
|
||||
/// linear, not sRGB.
|
||||
/// linear, not sRGB. Only used with Mesh export, not MeshGL.
|
||||
std::vector<glm::vec4> vertColor;
|
||||
/// For MeshGL export, gives the property indicies where the normal channels
|
||||
/// can be found. Must be >= 3, since the first three are position.
|
||||
glm::ivec3 normalChannels = glm::ivec3(-1);
|
||||
/// For MeshGL export, gives the property indicies where the color channels
|
||||
/// can be found. Any index < 0 will output all 1.0 for that channel.
|
||||
glm::ivec4 colorChannels = glm::ivec4(-1);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -60,6 +66,9 @@ Mesh ImportMesh(const std::string& filename, bool forceCleanup = false);
|
||||
|
||||
void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
const ExportOptions& options);
|
||||
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options);
|
||||
/** @} */
|
||||
/** @} */
|
||||
} // namespace manifold
|
||||
+158
-45
@@ -19,11 +19,75 @@
|
||||
|
||||
#include "assimp/Exporter.hpp"
|
||||
#include "assimp/Importer.hpp"
|
||||
#include "assimp/pbrmaterial.h"
|
||||
#include "assimp/material.h"
|
||||
#include "assimp/postprocess.h"
|
||||
#include "assimp/scene.h"
|
||||
#include "optional_assert.h"
|
||||
|
||||
#ifndef AI_MATKEY_ROUGHNESS_FACTOR
|
||||
#include "assimp/pbrmaterial.h"
|
||||
#define AI_MATKEY_METALLIC_FACTOR \
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR
|
||||
#define AI_MATKEY_ROUGHNESS_FACTOR \
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
using namespace manifold;
|
||||
|
||||
aiScene* CreateScene(const ExportOptions& options) {
|
||||
aiScene* scene = new aiScene();
|
||||
|
||||
scene->mNumMaterials = 1;
|
||||
scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
|
||||
scene->mMaterials[0] = new aiMaterial();
|
||||
|
||||
aiMaterial* material = scene->mMaterials[0];
|
||||
material->AddProperty(&options.mat.roughness, 1, AI_MATKEY_ROUGHNESS_FACTOR);
|
||||
material->AddProperty(&options.mat.metalness, 1, AI_MATKEY_METALLIC_FACTOR);
|
||||
const glm::vec4& color = options.mat.color;
|
||||
aiColor4D baseColor(color.r, color.g, color.b, color.a);
|
||||
material->AddProperty(&baseColor, 1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
scene->mNumMeshes = 1;
|
||||
scene->mMeshes = new aiMesh*[scene->mNumMeshes];
|
||||
scene->mMeshes[0] = new aiMesh();
|
||||
scene->mMeshes[0]->mMaterialIndex = 0;
|
||||
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->mNumMeshes = 1;
|
||||
scene->mRootNode->mMeshes = new glm::uint[scene->mRootNode->mNumMeshes];
|
||||
scene->mRootNode->mMeshes[0] = 0;
|
||||
|
||||
scene->mMeshes[0]->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
return scene;
|
||||
}
|
||||
|
||||
std::string GetType(const std::string& filename) {
|
||||
std::string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
if (ext == "glb") ext = "glb2";
|
||||
if (ext == "gltf") ext = "gltf2";
|
||||
return ext;
|
||||
}
|
||||
|
||||
void ExportScene(aiScene* scene, const std::string& filename) {
|
||||
Assimp::Exporter exporter;
|
||||
|
||||
// int n = exporter.GetExportFormatCount();
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// auto desc = exporter.GetExportFormatDescription(i);
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
auto result = exporter.Export(scene, GetType(filename), filename);
|
||||
|
||||
delete scene;
|
||||
|
||||
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace manifold {
|
||||
|
||||
/**
|
||||
@@ -91,6 +155,95 @@ Mesh ImportMesh(const std::string& filename, bool forceCleanup) {
|
||||
return mesh_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Mesh to the desired file type, determined from the extension
|
||||
* specified. In the case of .glb/.gltf, this will save in version 2.0.
|
||||
*
|
||||
* This is a very simple export function and is intended primarily as a
|
||||
* demonstration. Generally users of this library will need to modify this to
|
||||
* write all the important properties for their application and read any custom
|
||||
* data structures.
|
||||
*
|
||||
* @param filename The file extension must be one that Assimp supports for
|
||||
* export. GLB & 3MF are recommended.
|
||||
* @param mesh The mesh to export, likely from Manifold.GetMeshGL().
|
||||
* @param options The options currently only affect an exported GLB's material.
|
||||
* Pass {} for defaults.
|
||||
*/
|
||||
void ExportMesh(const std::string& filename, const MeshGL& mesh,
|
||||
const ExportOptions& options) {
|
||||
if (mesh.triVerts.size() == 0) {
|
||||
std::cout << filename << " was not saved because the input mesh was empty."
|
||||
<< std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string type = GetType(filename);
|
||||
const bool isYup = type == "glb2" || type == "gltf2";
|
||||
|
||||
aiScene* scene = CreateScene(options);
|
||||
aiMesh* mesh_out = scene->mMeshes[0];
|
||||
|
||||
mesh_out->mNumVertices = mesh.NumVert();
|
||||
mesh_out->mVertices = new aiVector3D[mesh_out->mNumVertices];
|
||||
if (!options.faceted) {
|
||||
bool validChannels = true;
|
||||
for (int i : {0, 1, 2}) {
|
||||
int c = options.mat.normalChannels[i];
|
||||
validChannels &= c >= 3 && c < (int)mesh.numProp;
|
||||
}
|
||||
ASSERT(validChannels, userErr,
|
||||
"When faceted is false, valid normalChannels must be supplied.");
|
||||
mesh_out->mNormals = new aiVector3D[mesh_out->mNumVertices];
|
||||
}
|
||||
|
||||
bool validChannels = true;
|
||||
bool hasColor = false;
|
||||
for (int i : {0, 1, 2, 3}) {
|
||||
int c = options.mat.colorChannels[i];
|
||||
validChannels &= c < (int)mesh.numProp;
|
||||
hasColor |= c >= 0;
|
||||
}
|
||||
ASSERT(validChannels, userErr, "Invalid colorChannels.");
|
||||
if (hasColor) mesh_out->mColors[0] = new aiColor4D[mesh_out->mNumVertices];
|
||||
|
||||
for (int i = 0; i < mesh_out->mNumVertices; ++i) {
|
||||
glm::vec3 v;
|
||||
for (int j : {0, 1, 2}) v[j] = mesh.vertProperties[i * mesh.numProp + j];
|
||||
mesh_out->mVertices[i] =
|
||||
isYup ? aiVector3D(v.y, v.z, v.x) : aiVector3D(v.x, v.y, v.z);
|
||||
if (!options.faceted) {
|
||||
glm::vec3 n;
|
||||
for (int j : {0, 1, 2})
|
||||
n[j] = mesh.vertProperties[i * mesh.numProp +
|
||||
options.mat.normalChannels[j]];
|
||||
mesh_out->mNormals[i] =
|
||||
isYup ? aiVector3D(n.y, n.z, n.x) : aiVector3D(n.x, n.y, n.z);
|
||||
}
|
||||
if (hasColor) {
|
||||
glm::vec4 c;
|
||||
for (int j : {0, 1, 2, 3})
|
||||
c[j] = options.mat.colorChannels[j] < 0
|
||||
? 1
|
||||
: mesh.vertProperties[i * mesh.numProp +
|
||||
options.mat.colorChannels[j]];
|
||||
mesh_out->mColors[0][i] = aiColor4D(c.r, c.g, c.b, c.a);
|
||||
}
|
||||
}
|
||||
|
||||
mesh_out->mNumFaces = mesh.NumTri();
|
||||
mesh_out->mFaces = new aiFace[mesh_out->mNumFaces];
|
||||
|
||||
for (int i = 0; i < mesh_out->mNumFaces; ++i) {
|
||||
aiFace& face = mesh_out->mFaces[i];
|
||||
face.mNumIndices = 3;
|
||||
face.mIndices = new glm::uint[face.mNumIndices];
|
||||
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[3 * i + j];
|
||||
}
|
||||
|
||||
ExportScene(scene, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Mesh to the desired file type, determined from the extension
|
||||
* specified. In the case of .glb/.gltf, this will save in version 2.0.
|
||||
@@ -114,38 +267,11 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
return;
|
||||
}
|
||||
|
||||
std::string ext = filename.substr(filename.find_last_of(".") + 1);
|
||||
const bool isYup = ext == "glb" || ext == "gltf";
|
||||
if (ext == "glb") ext = "glb2";
|
||||
if (ext == "gltf") ext = "gltf2";
|
||||
|
||||
aiScene* scene = new aiScene();
|
||||
|
||||
scene->mNumMaterials = 1;
|
||||
scene->mMaterials = new aiMaterial*[scene->mNumMaterials];
|
||||
scene->mMaterials[0] = new aiMaterial();
|
||||
|
||||
aiMaterial* material = scene->mMaterials[0];
|
||||
material->AddProperty(&options.mat.roughness, 1,
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR);
|
||||
material->AddProperty(&options.mat.metalness, 1,
|
||||
AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR);
|
||||
const glm::vec4& color = options.mat.color;
|
||||
aiColor4D baseColor(color.r, color.g, color.b, color.a);
|
||||
material->AddProperty(&baseColor, 1, AI_MATKEY_COLOR_DIFFUSE);
|
||||
|
||||
scene->mNumMeshes = 1;
|
||||
scene->mMeshes = new aiMesh*[scene->mNumMeshes];
|
||||
scene->mMeshes[0] = new aiMesh();
|
||||
scene->mMeshes[0]->mMaterialIndex = 0;
|
||||
|
||||
scene->mRootNode = new aiNode();
|
||||
scene->mRootNode->mNumMeshes = 1;
|
||||
scene->mRootNode->mMeshes = new glm::uint[scene->mRootNode->mNumMeshes];
|
||||
scene->mRootNode->mMeshes[0] = 0;
|
||||
std::string type = GetType(filename);
|
||||
const bool isYup = type == "glb2" || type == "gltf2";
|
||||
|
||||
aiScene* scene = CreateScene(options);
|
||||
aiMesh* mesh_out = scene->mMeshes[0];
|
||||
mesh_out->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
|
||||
mesh_out->mNumVertices = mesh.vertPos.size();
|
||||
mesh_out->mVertices = new aiVector3D[mesh_out->mNumVertices];
|
||||
@@ -186,20 +312,7 @@ void ExportMesh(const std::string& filename, const Mesh& mesh,
|
||||
for (int j : {0, 1, 2}) face.mIndices[j] = mesh.triVerts[i][j];
|
||||
}
|
||||
|
||||
Assimp::Exporter exporter;
|
||||
|
||||
// int n = exporter.GetExportFormatCount();
|
||||
// for (int i = 0; i < n; ++i) {
|
||||
// auto desc = exporter.GetExportFormatDescription(i);
|
||||
// std::cout << i << ", id = " << desc->id << ", " << desc->description
|
||||
// << std::endl;
|
||||
// }
|
||||
|
||||
auto result = exporter.Export(scene, ext, filename);
|
||||
|
||||
delete scene;
|
||||
|
||||
ASSERT(result == AI_SUCCESS, userErr, exporter.GetErrorString());
|
||||
ExportScene(scene, filename);
|
||||
}
|
||||
|
||||
} // namespace manifold
|
||||
|
||||
+412
-149
@@ -63,6 +63,112 @@ 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)
|
||||
@@ -74,54 +180,117 @@ void Identical(const Mesh& mesh1, const Mesh& mesh2) {
|
||||
ASSERT_EQ(mesh1.triVerts[i], mesh2.triVerts[i]);
|
||||
}
|
||||
|
||||
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());
|
||||
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 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);
|
||||
}
|
||||
struct MeshSize {
|
||||
int numVert, numTri;
|
||||
int numProp = 0;
|
||||
int numPropVert = numVert;
|
||||
};
|
||||
|
||||
void ExpectMeshes(const Manifold& manifold,
|
||||
const std::vector<std::pair<int, int>>& numVertTri) {
|
||||
const std::vector<MeshSize>& meshSize) {
|
||||
EXPECT_FALSE(manifold.IsEmpty());
|
||||
EXPECT_TRUE(manifold.IsManifold());
|
||||
EXPECT_TRUE(manifold.MatchesTriNormals());
|
||||
std::vector<Manifold> manifolds = manifold.Decompose();
|
||||
ASSERT_EQ(manifolds.size(), numVertTri.size());
|
||||
ASSERT_EQ(manifolds.size(), meshSize.size());
|
||||
std::sort(manifolds.begin(), manifolds.end(),
|
||||
[](const Manifold& a, const Manifold& b) {
|
||||
return a.NumVert() != b.NumVert() ? a.NumVert() > b.NumVert()
|
||||
@@ -129,8 +298,14 @@ void ExpectMeshes(const Manifold& manifold,
|
||||
});
|
||||
for (int i = 0; i < manifolds.size(); ++i) {
|
||||
EXPECT_TRUE(manifolds[i].IsManifold());
|
||||
EXPECT_EQ(manifolds[i].NumVert(), numVertTri[i].first);
|
||||
EXPECT_EQ(manifolds[i].NumTri(), numVertTri[i].second);
|
||||
EXPECT_EQ(manifolds[i].NumVert(), meshSize[i].numVert);
|
||||
EXPECT_EQ(manifolds[i].NumTri(), meshSize[i].numTri);
|
||||
EXPECT_EQ(manifolds[i].NumProp(), meshSize[i].numProp);
|
||||
EXPECT_EQ(manifolds[i].NumPropVert(), meshSize[i].numPropVert);
|
||||
const MeshGL meshGL = manifolds[i].GetMeshGL();
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(), meshGL.mergeToVert.size());
|
||||
EXPECT_EQ(meshGL.mergeFromVert.size(),
|
||||
meshGL.NumVert() - manifolds[i].NumVert());
|
||||
const Mesh mesh = manifolds[i].GetMesh();
|
||||
for (const glm::vec3& normal : mesh.vertNormal) {
|
||||
ASSERT_NEAR(glm::length(normal), 1, 0.0001);
|
||||
@@ -196,8 +371,7 @@ 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.vertPos[3 * i + j], mesh_out.vertPos[i][j]);
|
||||
ASSERT_EQ(meshGL_out.vertNormal[3 * i + j], mesh_out.vertNormal[i][j]);
|
||||
ASSERT_EQ(meshGL_out.vertProperties[3 * i + j], mesh_out.vertPos[i][j]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < meshGL_out.NumTri(); ++i) {
|
||||
@@ -217,10 +391,8 @@ TEST(Manifold, Empty) {
|
||||
|
||||
TEST(Manifold, ValidInput) {
|
||||
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, 4}};
|
||||
Manifold tet(Tet(), triProp, prop, propTol);
|
||||
MeshGL tetGL = TetGL();
|
||||
Manifold tet(tetGL, propTol);
|
||||
EXPECT_FALSE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::NO_ERROR);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
@@ -271,34 +443,20 @@ TEST(Manifold, InvalidInput4) {
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput5) {
|
||||
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);
|
||||
MeshGL tetGL = TetGL();
|
||||
tetGL.mergeFromVert[tetGL.mergeFromVert.size() - 1] = 7;
|
||||
Manifold tet(tetGL);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
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_EQ(tet.Status(), Manifold::Error::MERGE_INDEX_OUT_OF_BOUNDS);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
TEST(Manifold, InvalidInput7) {
|
||||
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);
|
||||
MeshGL tetGL = TetGL();
|
||||
tetGL.triVerts[tetGL.triVerts.size() - 1] = 7;
|
||||
Manifold tet(tetGL);
|
||||
EXPECT_TRUE(tet.IsEmpty());
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::TRI_PROPERTIES_OUT_OF_BOUNDS);
|
||||
EXPECT_EQ(tet.Status(), Manifold::Error::VERTEX_INDEX_OUT_OF_BOUNDS);
|
||||
EXPECT_TRUE(tet.IsManifold());
|
||||
}
|
||||
|
||||
@@ -308,24 +466,21 @@ TEST(Manifold, InvalidInput7) {
|
||||
*/
|
||||
TEST(Manifold, Decompose) {
|
||||
std::vector<Manifold> manifoldList;
|
||||
manifoldList.push_back(Manifold::Tetrahedron());
|
||||
manifoldList.push_back(Manifold::Cube());
|
||||
manifoldList.push_back(Manifold::Sphere(1, 4));
|
||||
manifoldList.emplace_back(Manifold::Tetrahedron());
|
||||
manifoldList.emplace_back(Manifold::Cube());
|
||||
manifoldList.emplace_back(Manifold::Sphere(1, 4));
|
||||
Manifold manifolds = Manifold::Compose(manifoldList);
|
||||
|
||||
ExpectMeshes(manifolds, {{8, 12}, {6, 8}, {4, 4}});
|
||||
|
||||
std::vector<Mesh> input;
|
||||
std::map<int, int> meshID2idx;
|
||||
std::vector<MeshGL> input;
|
||||
|
||||
for (const Manifold& manifold : manifoldList) {
|
||||
int meshID = manifold.OriginalID();
|
||||
EXPECT_GE(meshID, 0);
|
||||
meshID2idx[meshID] = input.size();
|
||||
input.push_back(manifold.GetMesh());
|
||||
EXPECT_GE(manifold.OriginalID(), 0);
|
||||
input.emplace_back(manifold.GetMeshGL());
|
||||
}
|
||||
|
||||
Related(manifolds, input, meshID2idx);
|
||||
RelatedGL(manifolds, input);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,12 +602,11 @@ 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 = rel.UVW(tri, i);
|
||||
const glm::vec3& uvw = {0.5, 0.5, 0.0};
|
||||
const float alpha = glm::min(uvw[0], glm::min(uvw[1], uvw[2]));
|
||||
options.mat.vertColor[out.triVerts[tri][i]] =
|
||||
glm::mix(purple, red, glm::smoothstep(0.0f, 0.2f, alpha));
|
||||
@@ -463,6 +617,18 @@ 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);
|
||||
@@ -479,12 +645,11 @@ 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 = rel.barycentric[rel.triBary[tri].vertBary[i]];
|
||||
const glm::vec3& uvw = {0.5, 0.5, 0.0};
|
||||
const float alpha = glm::min(uvw[0], glm::min(uvw[1], uvw[2]));
|
||||
options.mat.vertColor[out.triVerts[tri][i]] =
|
||||
glm::mix(yellow, blue, glm::smoothstep(0.0f, 0.2f, alpha));
|
||||
@@ -507,7 +672,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);
|
||||
}
|
||||
|
||||
@@ -586,39 +751,54 @@ TEST(Manifold, Transform) {
|
||||
TEST(Manifold, MeshRelation) {
|
||||
const float period = glm::two_pi<float>();
|
||||
|
||||
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]);
|
||||
Mesh gyroidMesh = LevelSet(Gyroid(), {glm::vec3(0), glm::vec3(period)}, 3);
|
||||
MeshGL gyroidMeshGL = WithIndexColors(gyroidMesh);
|
||||
Manifold gyroid(gyroidMeshGL);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("gyroid.glb", gyroid.GetMesh(), {});
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("gyroid.glb", gyroid.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
int meshID = gyroid.OriginalID();
|
||||
EXPECT_GE(meshID, 0);
|
||||
meshID2idx[meshID] = input.size() - 1;
|
||||
|
||||
Related(gyroid, input, meshID2idx);
|
||||
RelatedGL(gyroid, {gyroidMeshGL});
|
||||
}
|
||||
|
||||
TEST(Manifold, MeshRelationRefine) {
|
||||
std::vector<Mesh> input;
|
||||
std::map<int, int> meshID2idx;
|
||||
const Mesh in = Csaszar();
|
||||
MeshGL inGL = WithIndexColors(in);
|
||||
Manifold 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);
|
||||
RelatedGL(csaszar, {inGL});
|
||||
csaszar.Refine(4);
|
||||
Related(csaszar, input, meshID2idx);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -626,15 +806,76 @@ TEST(Manifold, MeshRelationRefine) {
|
||||
*/
|
||||
TEST(Boolean, Tetra) {
|
||||
Manifold tetra = Manifold::Tetrahedron();
|
||||
EXPECT_TRUE(tetra.IsManifold());
|
||||
MeshGL tetraGL = WithPositionColors(tetra);
|
||||
tetra = Manifold(tetraGL);
|
||||
EXPECT_TRUE(!tetra.IsEmpty());
|
||||
|
||||
Manifold tetra2 = tetra;
|
||||
tetra2 = tetra2.Translate(glm::vec3(0.5f)).AsOriginal();
|
||||
Manifold tetra2 = tetra.Translate(glm::vec3(0.5f));
|
||||
Manifold result = tetra2 - tetra;
|
||||
|
||||
ExpectMeshes(result, {{8, 12}});
|
||||
ExpectMeshes(result, {{8, 12, 3, 11}});
|
||||
|
||||
RelatedOp(tetra, tetra2, result);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -670,20 +911,51 @@ TEST(Boolean, Perturb) {
|
||||
|
||||
TEST(Boolean, Coplanar) {
|
||||
Manifold cylinder = Manifold::Cylinder(1.0f, 1.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});
|
||||
MeshGL cylinderGL = WithPositionColors(cylinder);
|
||||
cylinder = Manifold(cylinderGL);
|
||||
|
||||
Manifold cylinder2 = cylinder.Scale({0.5f, 0.5f, 1.0f})
|
||||
.Rotate(0, 0, 15)
|
||||
.Translate({0.25f, 0.25f, 0.0f});
|
||||
Manifold out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{32, 64}});
|
||||
ExpectMeshes(out, {{32, 64, 3, 49}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMesh(), {});
|
||||
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
|
||||
|
||||
RelatedOp(cylinder, cylinder2, out);
|
||||
RelatedGL(out, {cylinderGL});
|
||||
}
|
||||
|
||||
/**
|
||||
* Colinear edges are not collapsed like above due to non-coplanar properties.
|
||||
*/
|
||||
TEST(Boolean, CoplanarProp) {
|
||||
Manifold cylinder = Manifold::Cylinder(1.0f, 1.0f);
|
||||
MeshGL cylinderGL = WithIndexColors(cylinder.GetMesh());
|
||||
cylinder = Manifold(cylinderGL);
|
||||
|
||||
Manifold cylinder2 = cylinder.Scale({0.5f, 0.5f, 1.0f})
|
||||
.Rotate(0, 0, 15)
|
||||
.Translate({0.25f, 0.25f, 0.0f});
|
||||
Manifold out = cylinder - cylinder2;
|
||||
ExpectMeshes(out, {{42, 84, 3, 68}});
|
||||
EXPECT_EQ(out.NumDegenerateTris(), 0);
|
||||
EXPECT_EQ(out.Genus(), 1);
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels) ExportMesh("coplanar.glb", out.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
RelatedGL(out, {cylinderGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MultiCoplanar) {
|
||||
@@ -810,8 +1082,8 @@ TEST(Boolean, Empty) {
|
||||
|
||||
TEST(Boolean, Winding) {
|
||||
std::vector<Manifold> cubes;
|
||||
cubes.push_back(Manifold::Cube(glm::vec3(3.0f), true));
|
||||
cubes.push_back(Manifold::Cube(glm::vec3(2.0f), true));
|
||||
cubes.emplace_back(Manifold::Cube(glm::vec3(3.0f), true));
|
||||
cubes.emplace_back(Manifold::Cube(glm::vec3(2.0f), true));
|
||||
Manifold doubled = Manifold::Compose(cubes);
|
||||
|
||||
Manifold cube = Manifold::Cube(glm::vec3(1.0f), true);
|
||||
@@ -864,36 +1136,39 @@ TEST(Boolean, Precision2) {
|
||||
*/
|
||||
TEST(Boolean, Sphere) {
|
||||
Manifold sphere = Manifold::Sphere(1.0f, 12);
|
||||
Manifold sphere2 = sphere;
|
||||
sphere2 = sphere2.Translate(glm::vec3(0.5));
|
||||
sphere2 = sphere2.AsOriginal();
|
||||
MeshGL sphereGL = WithPositionColors(sphere);
|
||||
sphere = Manifold(sphereGL);
|
||||
|
||||
Manifold sphere2 = sphere.Translate(glm::vec3(0.5));
|
||||
Manifold result = sphere - sphere2;
|
||||
|
||||
ExpectMeshes(result, {{74, 144}});
|
||||
ExpectMeshes(result, {{74, 144, 3, 110}});
|
||||
EXPECT_EQ(result.NumDegenerateTris(), 0);
|
||||
|
||||
RelatedOp(sphere, sphere2, result);
|
||||
RelatedGL(result, {sphereGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, MeshRelation) {
|
||||
const float period = glm::two_pi<float>();
|
||||
|
||||
Mesh gyroidMesh = LevelSet(Gyroid(), {glm::vec3(0), glm::vec3(period)}, 0.5);
|
||||
Manifold gyroid(gyroidMesh);
|
||||
MeshGL gyroidMeshGL = WithPositionColors(gyroidMesh);
|
||||
Manifold gyroid(gyroidMeshGL);
|
||||
|
||||
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);
|
||||
Manifold gyroid2 = gyroid.Translate(glm::vec3(2.0f));
|
||||
|
||||
EXPECT_FALSE(gyroid.IsEmpty());
|
||||
EXPECT_TRUE(gyroid.IsManifold());
|
||||
EXPECT_TRUE(gyroid.MatchesTriNormals());
|
||||
EXPECT_LE(gyroid.NumDegenerateTris(), 0);
|
||||
Manifold result = gyroid + gyroid2;
|
||||
|
||||
#ifdef MANIFOLD_EXPORT
|
||||
if (options.exportModels) ExportMesh("gyroidUnion.glb", result.GetMesh(), {});
|
||||
ExportOptions opt;
|
||||
opt.mat.roughness = 1;
|
||||
opt.mat.colorChannels = glm::ivec4(3, 4, 5, -1);
|
||||
if (options.exportModels)
|
||||
ExportMesh("gyroidUnion.glb", result.GetMeshGL(), opt);
|
||||
#endif
|
||||
|
||||
EXPECT_TRUE(result.IsManifold());
|
||||
@@ -904,20 +1179,7 @@ TEST(Boolean, MeshRelation) {
|
||||
EXPECT_NEAR(prop.volume, 226, 1);
|
||||
EXPECT_NEAR(prop.surfaceArea, 387, 1);
|
||||
|
||||
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);
|
||||
RelatedGL(result, {gyroidMeshGL});
|
||||
}
|
||||
|
||||
TEST(Boolean, Cylinders) {
|
||||
@@ -1053,7 +1315,8 @@ TEST(Boolean, Subtract) {
|
||||
first.GetMesh();
|
||||
}
|
||||
|
||||
TEST(Boolean, Close) {
|
||||
// FIXME: test is failing on Mac CI (passing on others)
|
||||
TEST(Boolean, DISABLED_Close) {
|
||||
PolygonParams().processOverlaps = true;
|
||||
|
||||
const float r = 10;
|
||||
|
||||
Reference in New Issue
Block a user