elalish / manifold Goto Github PK
View Code? Open in Web Editor NEWGeometry library for topological robustness
License: Apache License 2.0
Geometry library for topological robustness
License: Apache License 2.0
Steps to reproduce:
Mesh firstMesh;
firstMesh.vertPos = {
{0.000000, 0.000000, 0.000000},
{1850.000000, 0.000000, 0.000000},
{1850.000000, 170.000000, 0.000000},
{0.000000, 170.000000, 0.000000},
{0.000000, 0.000000, -3380.000000},
{1850.000000, 170.000000, -3380.000000},
{1850.000000, 0.000000, -3380.000000},
{0.000000, 170.000000, -3380.000000}};
firstMesh.triVerts = {{1, 2, 3}, {3, 4, 1}, {5, 6, 7}, {6, 5, 8},
{7, 3, 2}, {7, 6, 3}, {6, 4, 3}, {6, 8, 4},
{8, 1, 4}, {8, 5, 1}, {5, 2, 1}, {5, 7, 2}};
Mesh secondMesh;
secondMesh.vertPos = {{365.000000, 50105.000000, -3380.000000},
{0.000000, 50105.000000, -3380.000000},
{365.000000, 50105.000000, -1310.000000},
{1375.000000, 50105.000000, -3380.000000},
{1375.000000, 50105.000000, -1310.000000},
{1850.000000, 50105.000000, -3380.000000},
{0.000000, 50105.000000, 0.000000},
{1850.000000, 50105.000000, 0.000000},
{365.000000, 105.000000, -1310.000000},
{0.000000, 105.000000, -3380.000000},
{365.000000, 105.000000, -3380.000000},
{1850.000000, 105.000000, -3380.000000},
{1375.000000, 105.000000, -1310.000000},
{1375.000000, 105.000000, -3380.000000},
{0.000000, 105.000000, 0.000000},
{1850.000000, 105.000000, 0.000000}};
secondMesh.triVerts = {
{1, 2, 3}, {4, 5, 6}, {2, 7, 3}, {6, 5, 8}, {7, 8, 3},
{5, 3, 8}, {5, 3, 8}, {9, 10, 11}, {12, 13, 14}, {9, 15, 10},
{16, 13, 12}, {9, 16, 15}, {16, 9, 13}, {3, 11, 1}, {9, 11, 3},
{5, 9, 3}, {13, 9, 5}, {4, 13, 5}, {14, 13, 4}, {6, 14, 4},
{12, 14, 6}, {8, 12, 6}, {16, 12, 8}, {7, 16, 8}, {15, 16, 7},
{2, 15, 7}, {10, 15, 2}, {1, 10, 2}, {11, 10, 1}};
Manifold first(firstMesh);
Manifold second(secondMesh);
first -= second;
This is a very interesting endeavor. Congratulations @elalish !
This is a wishlist item:
Currently, vertices coming from different meshes are labelled with different meshID
, preventing them from merging. However, when joining manifolds face-to-face, it may be desirable to assign vertices with the same original meshID the same meshID and redo/simplify the face triangulation to reduce the number of vertices.
For example, consider the result of the Boolean.FaceUnion
test:
Can we make it so that it will not have vertices in the middle?
Reproduce:
diff --git a/manifold/src/edge_op.cpp b/manifold/src/edge_op.cpp
index 20878e1..ca2267d 100644
--- a/manifold/src/edge_op.cpp
+++ b/manifold/src/edge_op.cpp
@@ -242,9 +242,14 @@ void Manifold::Impl::CollapseEdge(const int edge) {
std::vector<int> edges;
// Orbit endVert
int current = halfedge[tri0edge[1]].pairedHalfedge;
+ int counter = 0;
while (current != tri1edge[2]) {
current = NextHalfedge(current);
edges.push_back(current);
+ if (counter++ >= 10000000 && counter % 10000000 == 0) {
+ std::cout << "CollapseEdge: counter exceeded " << counter << std::endl;
+ std::cout << "cannot find " << tri1edge[2] << ", got " << current << std::endl;
+ }
current = halfedge[current].pairedHalfedge;
}
#include "manifold.h"
using namespace manifold;
int main() {
Manifold rod = Manifold::Cylinder(1.0, 0.4, -1.0, 20);
float arrays1[][12] = {
{ 0, 0, 1, 1, //
-1, 0, 0, 2, //
0, -1, 0, 7 }, //
{ 1, 0, 0, 3, //
0, 1, 0, 2, //
0, 0, 1, 6 }, //
{ 0, 0, 1, 3, //
-1, 0, 0, 3, //
0, -1, 0, 6 }, //
{ 0, 0, 1, 3, //
-1, 0, 0, 3, //
0, -1, 0, 7,}, //
{ 0, 0, 1, 2, //
-1, 0, 0, 3, //
0, -1, 0, 8,}, //
{ 0, 0, 1, 1, //
-1, 0, 0, 3, //
0, -1, 0, 7,}, //
{ 1, 0, 0, 3, //
0, 0, 1, 4, //
0, -1, 0, 6,}, //
{ 1, 0, 0, 4, //
0, 0, 1, 4, //
0, -1, 0, 6,}, //
};
float arrays2[][12] = {
{ 0, 0, 1, 2, //
-1, 0, 0, 2, //
0, -1, 0, 7 }, //
{ 1, 0, 0, 3, //
0, 0, 1, 2, //
0, -1, 0, 6 }, //
{ 1, 0, 0, 4, //
0, 1, 0, 3, //
0, 0, 1, 6 }, //
{ 1, 0, 0, 3, //
0, 1, 0, 3, //
0, 0, 1, 7 }, //
{ 1, 0, 0, 2, //
0, 1, 0, 3, //
0, 0, 1, 7 }, //
{ 1, 0, 0, 1, //
0, 1, 0, 3, //
0, 0, 1, 7 }, //
{ 1, 0, 0, 3, //
0, 1, 0, 4, //
0, 0, 1, 7 }, //
{ 1, 0, 0, 3, //
0, 1, 0, 5, //
0, 0, 1, 6 }, //
{ 0, 0, 1, 3, //
-1, 0, 0, 4, //
0, -1, 0, 6,}, //
};
Manifold m1;
for (auto &array : arrays1) {
glm::mat4x3 mat;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
mat[i][j] = array[j * 4 + i];
}
}
m1 += Manifold(rod).Transform(mat);
}
Manifold m2;
for (auto &array : arrays2) {
glm::mat4x3 mat;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 3; j++) {
mat[i][j] = array[j * 4 + i];
}
}
m2 += Manifold(rod).Transform(mat);
}
m1 += m2;
return 0;
}
Interestingly, by changing the order of the union operations, it will not trigger this problem. I guess this example can be further reduced, but haven't yet tried doing that. Found this while porting some of the benchmarks in https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355 to the pybind11 binding.
meshIO
is not really a core part of this Manifold library. It is the only part that depends on Assimp (a large, complicated dependency) and it is unlikely to fit into other libraries well, as they probably have their own mesh input/output anyway. I only included meshIO
as a means to quickly test this library, but the functionality is lacking (no texture/property support for one thing). I don't want to focus on that, as it's a complicated area in its own right and others do it better. For C++, it's easy enough to not include that part, but for building bundles like WASM and Python, I think it's important to keep this separate.
I'm fine with either removing meshIO
from these bundles to focus only on our core library, or if we find it to be really useful, then making a separate bundle for meshIO
. There is no dependency between meshIO
and manifold
since they communicate via Mesh
, so the separation should be pretty clean.
Perhaps we can use https://llvm.org/docs/LibFuzzer.html to test the library? Generate a number of primitive objects, perform random translation/scaling/rotation and boolean operations for them. There are a few things we can test for:
To make manifold easier to build there was a proposal to remove thrust for C++ primitives.
This is tracking that thrust removal proposal.
The 'Manifoldness' section of the Wiki talks about "triangle meshes".
Can this library work with meshes that contain faces of arity higher than three?
If not this constraint should probably be mentioned in the first sentence of the README as it may save people looking for something that does (and not checking the Wiki) a lot of time. :)
Specifically: If I input a mesh made out of e.g. quads and perform a boolean operation, will the result mesh only contain triangles? Even in areas not affected by the boolean operation at all?
I was trying to port some tests from OpenSCAD to python for more test cases and performance check. I'm currently looking at https://gist.github.com/ochafik/2db96400e3c1f73558fcede990b8a355 . It seems that quite a lot of the more complicated cases are using hull()
, so I guess it would be desirable to implement it.
Not sure if we should implement it by ourselves or use an existing implementation. I saw a header only library here: https://github.com/leomccormack/convhull_3d/blob/master/convhull_3d.h and can probably be parallelized if needed.
Hi !
I'm trying to integrate the library with emscripten and i'm a bit struggling.
I tried a lot of stuff but nothing worked.
Does anyone know a proper way to do it ?
Thanks a lot
Mesh offsetting seems very useful for adding tolerance to parts, especially when combined with face selection.
I wonder if I can just offset each vertices according to their tangent? Although we may need to recompute the tangent if we only selected a subset of faces connecting to that vertex. OK this will not work.
Another wishlist item, feel free to ignore.
I think this need a Polyline/curve class. At a simplistic level engineering models usually are created first in 2D and then "extrude"-ed into 3D shapes which are then CSG'ed together. You are missing the Polyline/curve class that would create the 2D shapes that are fundamental to CAD. This feels like a gap.
It would be nice to be able to similarly treat them as smoothable Bezier curves like the tri-meshes so they are equally refineable like the trimeshes.
@pca006132 has in his fork a branch with Python bindings. Just to see how far I get, I tried to apply those patches for python to the most recent upstream manifold which now builds perfectly with CPP
compile option on my mac. The python bindings and the example pythonTest
seemed to compile perfectly. In the manifold root folder where the python test script test.py
I created a symbolic link named pythonTest
to the compiled shared library and assumed I could run python test
. Well, that is how far I got:
from pythonTest import ManifoldSet, Polygons
ModuleNotFoundError: No module named 'pythonTest'
I am aware that this is all very early stage and unstable and the examples at pybind are a tiny bit different (using pip
to compile and install the examples). So what step am I missing to get that module imported ?
Hi, thank you for your efforts on doing a fast and robust csg lib
what would be a killer feature is being able to retrieve the intersection data, precisely as naturally ordered polyline(s).
which boils down to just having the intersection points in the right order ; with a topological predicate since there can be several intersection loops.
A convenient function would be to measure the minimum gap between two Manifolds (good for checking that mechanisms have the proper tolerances). The signature would be: float Manifold.MinGap(const Manifold& other, float searchLength) const
It would always return a value between zero and searchLength
. It would perform an intersection and if that was non-empty, it returns zero (overlapping). If not, it will collide the triangles of each mesh with the verts of the other, expanded into boxes 2*searchLength
wide, then for those collisions, calculate point-to-triangle distances. The minimum is returned.
I guess it might be better to open an issue for this and discuss about the possible implementation for lazy boolean operations. The opt branch of my fork only implements the optimizations for union.
Idea: Apply boolean operations lazily to allow more optimizations, e.g. operation reordering or cheap batch processing for certain operations.
Operation reordering:
a - b - c <=> a - (b + c)
. Again, the idea is to minimize the number of required operations. For this case, b
and c
might be disjoint, if so they can be composed together cheaply. If not, the LHS is perhaps the cheapest way to do this.Batch processing:
O(n^2)
time with a simple implementation, it is still much cheaper in practice than doing union for up to something like 10000 manifolds.Cache for manifolds that only differ in transformation: We can use a shared_ptr
of the underlying CSG tree, and store the transformation separately. When we need to evaluate the CSG tree, e.g. when exporting the object or need to apply some complicated operations on it, we can cache the result and then apply transform to a copy of the result. This way, we can store objects that only differ in transformation (e.g. a grid of objects) cheaply, and allows us to defer the evaluation of the CSG tree as late as possible. This way, copying the manifold is also essentially free: we only need to copy the shared_ptr
and the 4x3 matrix.
Once you have a Three.js example, it would be nice to feature it on the homepage of manifold with some type of impressive animation. Two objects moving relatively to each other and showing the complex intersection being resolved by this library in real time. That would immediately sell people on the value of this library.
I had to comment out tests containing specific paths
and this is the result of the suite (click the spoiler )
[==========] Running 86 tests from 4 test cases.
[----------] Global test environment set-up.
[----------] 43 tests from Polygon
[ RUN ] Polygon.SimpleHole
[ OK ] Polygon.SimpleHole (1 ms)
[ RUN ] Polygon.SimpleHole2
[ OK ] Polygon.SimpleHole2 (0 ms)
[ RUN ] Polygon.MultiMerge
[ OK ] Polygon.MultiMerge (0 ms)
[ RUN ] Polygon.Colinear
[ OK ] Polygon.Colinear (0 ms)
[ RUN ] Polygon.Merges
[ OK ] Polygon.Merges (0 ms)
[ RUN ] Polygon.ColinearY
[ OK ] Polygon.ColinearY (0 ms)
[ RUN ] Polygon.Concave
[ OK ] Polygon.Concave (0 ms)
[ RUN ] Polygon.Concave2
[ OK ] Polygon.Concave2 (0 ms)
[ RUN ] Polygon.Sliver
[ OK ] Polygon.Sliver (0 ms)
[ RUN ] Polygon.Duplicate
[ OK ] Polygon.Duplicate (0 ms)
[ RUN ] Polygon.Folded
[ OK ] Polygon.Folded (0 ms)
[ RUN ] Polygon.NearlyLinear
[ OK ] Polygon.NearlyLinear (0 ms)
[ RUN ] Polygon.Sliver2
[ OK ] Polygon.Sliver2 (0 ms)
[ RUN ] Polygon.Sliver3
[ OK ] Polygon.Sliver3 (0 ms)
[ RUN ] Polygon.Sliver4
[ OK ] Polygon.Sliver4 (0 ms)
[ RUN ] Polygon.Sliver5
[ OK ] Polygon.Sliver5 (0 ms)
[ RUN ] Polygon.Colinear2
[ OK ] Polygon.Colinear2 (0 ms)
[ RUN ] Polygon.Split
[ OK ] Polygon.Split (0 ms)
[ RUN ] Polygon.Duplicates
[ OK ] Polygon.Duplicates (0 ms)
[ RUN ] Polygon.Simple1
[ OK ] Polygon.Simple1 (0 ms)
[ RUN ] Polygon.Simple2
[ OK ] Polygon.Simple2 (0 ms)
[ RUN ] Polygon.Simple3
[ OK ] Polygon.Simple3 (0 ms)
[ RUN ] Polygon.Simple4
[ OK ] Polygon.Simple4 (0 ms)
[ RUN ] Polygon.Simple5
[ OK ] Polygon.Simple5 (0 ms)
[ RUN ] Polygon.Simple6
[ OK ] Polygon.Simple6 (0 ms)
[ RUN ] Polygon.TouchingHole
[ OK ] Polygon.TouchingHole (0 ms)
[ RUN ] Polygon.Degenerate
[ OK ] Polygon.Degenerate (0 ms)
[ RUN ] Polygon.Degenerate2
[ OK ] Polygon.Degenerate2 (0 ms)
[ RUN ] Polygon.Degenerate3
[ OK ] Polygon.Degenerate3 (0 ms)
[ RUN ] Polygon.Degenerate4
[ OK ] Polygon.Degenerate4 (0 ms)
[ RUN ] Polygon.Degenerate5
[ OK ] Polygon.Degenerate5 (0 ms)
[ RUN ] Polygon.Degenerate6
[ OK ] Polygon.Degenerate6 (0 ms)
[ RUN ] Polygon.Tricky
[ OK ] Polygon.Tricky (0 ms)
[ RUN ] Polygon.Tricky2
[ OK ] Polygon.Tricky2 (0 ms)
[ RUN ] Polygon.Slit
[ OK ] Polygon.Slit (0 ms)
[ RUN ] Polygon.SharedEdge
[ OK ] Polygon.SharedEdge (0 ms)
[ RUN ] Polygon.Comb
[ OK ] Polygon.Comb (0 ms)
[ RUN ] Polygon.Comb2
[ OK ] Polygon.Comb2 (0 ms)
[ RUN ] Polygon.PointPoly
[ OK ] Polygon.PointPoly (1 ms)
[ RUN ] Polygon.KissingZigzag
[ OK ] Polygon.KissingZigzag (1 ms)
[ RUN ] Polygon.Sponge
[ OK ] Polygon.Sponge (1 ms)
[ RUN ] Polygon.SquareHoles
[ OK ] Polygon.SquareHoles (1 ms)
[ RUN ] Polygon.BigSponge
[ OK ] Polygon.BigSponge (16 ms)
[----------] 43 tests from Polygon (103 ms total)
[----------] 15 tests from Manifold
[ RUN ] Manifold.GetMesh
[ OK ] Manifold.GetMesh (204 ms)
[ RUN ] Manifold.Decompose
[ OK ] Manifold.Decompose (26 ms)
[ RUN ] Manifold.Sphere
[ OK ] Manifold.Sphere (6 ms)
[ RUN ] Manifold.Normals
[ OK ] Manifold.Normals (4 ms)
[ RUN ] Manifold.Extrude
[ OK ] Manifold.Extrude (5 ms)
[ RUN ] Manifold.ExtrudeCone
[ OK ] Manifold.ExtrudeCone (3 ms)
[ RUN ] Manifold.Revolve
[ OK ] Manifold.Revolve (4 ms)
[ RUN ] Manifold.Revolve2
[ OK ] Manifold.Revolve2 (4 ms)
[ RUN ] Manifold.Smooth
[ OK ] Manifold.Smooth (38 ms)
[ RUN ] Manifold.SmoothSphere
[ OK ] Manifold.SmoothSphere (99 ms)
[ RUN ] Manifold.ManualSmooth
[ OK ] Manifold.ManualSmooth (74 ms)
[ RUN ] Manifold.GetProperties
[ OK ] Manifold.GetProperties (4 ms)
[ RUN ] Manifold.Precision
[ OK ] Manifold.Precision (4 ms)
[ RUN ] Manifold.GetCurvature
[ OK ] Manifold.GetCurvature (40 ms)
[ RUN ] Manifold.Transform
[ OK ] Manifold.Transform (4 ms)
[----------] 15 tests from Manifold (530 ms total)
[----------] 19 tests from Boolean
[ RUN ] Boolean.Tetra
[ OK ] Boolean.Tetra (21 ms)
[ RUN ] Boolean.SelfSubtract
[ OK ] Boolean.SelfSubtract (12 ms)
[ RUN ] Boolean.Perturb
[ OK ] Boolean.Perturb (15 ms)
[ RUN ] Boolean.Coplanar
[ OK ] Boolean.Coplanar (21 ms)
[ RUN ] Boolean.MultiCoplanar
[ OK ] Boolean.MultiCoplanar (33 ms)
[ RUN ] Boolean.FaceUnion
[ OK ] Boolean.FaceUnion (19 ms)
[ RUN ] Boolean.EdgeUnion
[ OK ] Boolean.EdgeUnion (25 ms)
[ RUN ] Boolean.EdgeUnion2
[ OK ] Boolean.EdgeUnion2 (25 ms)
[ RUN ] Boolean.CornerUnion
[ OK ] Boolean.CornerUnion (25 ms)
[ RUN ] Boolean.Split
[ OK ] Boolean.Split (29 ms)
[ RUN ] Boolean.SplitByPlane
[ OK ] Boolean.SplitByPlane (46 ms)
[ RUN ] Boolean.SplitByPlane60
[ OK ] Boolean.SplitByPlane60 (45 ms)
[ RUN ] Boolean.Vug
[ OK ] Boolean.Vug (48 ms)
[ RUN ] Boolean.Empty
[ OK ] Boolean.Empty (4 ms)
[ RUN ] Boolean.Winding
[ OK ] Boolean.Winding (22 ms)
[ RUN ] Boolean.NonIntersecting
[ OK ] Boolean.NonIntersecting (16 ms)
[ RUN ] Boolean.Precision
[ OK ] Boolean.Precision (24 ms)
[ RUN ] Boolean.Precision2
[ OK ] Boolean.Precision2 (33 ms)
[ RUN ] Boolean.Sphere
[ OK ] Boolean.Sphere (25 ms)
[----------] 19 tests from Boolean (499 ms total)
[----------] 9 tests from Samples
[ RUN ] Samples.Knot13
[ OK ] Samples.Knot13 (23 ms)
[ RUN ] Samples.Knot42
[ OK ] Samples.Knot42 (29 ms)
[ RUN ] Samples.Scallop
[ OK ] Samples.Scallop (65 ms)
[ RUN ] Samples.TetPuzzle
[ OK ] Samples.TetPuzzle (37 ms)
[ RUN ] Samples.FrameReduced
[ OK ] Samples.FrameReduced (76 ms)
[ RUN ] Samples.Frame
unknown file: error: C++ exception with description "Error in file: C:\repos\manifold\manifold\src\boolean_result.cu (187): 'edgePos.size() % 2 == 0' is false: Non-manifold edge! Not an even number of points." thrown in the test body.
[ FAILED ] Samples.Frame (76 ms)
[ RUN ] Samples.Bracelet
unknown file: error: C++ exception with description "Error in file: C:\repos\manifold\manifold\src\boolean_result.cu (187): 'edgePos.size() % 2 == 0' is false: Non-manifold edge! Not an even number of points." thrown in the test body.
[ FAILED ] Samples.Bracelet (37 ms)
[ RUN ] Samples.Sponge1
[ OK ] Samples.Sponge1 (49 ms)
[ RUN ] Samples.Sponge4
unknown file: error: C++ exception with description "Error in file: C:\repos\manifold\manifold\src\boolean_result.cu (187): 'edgePos.size() % 2 == 0' is false: Non-manifold edge! Not an even number of points." thrown in the test body.
[ FAILED ] Samples.Sponge4 (716 ms)
[----------] 9 tests from Samples (1113 ms total)
[----------] Global test environment tear-down
[==========] 86 tests from 4 test cases ran. (2248 ms total)
[ PASSED ] 83 tests.
[ FAILED ] 3 tests, listed below:
[ FAILED ] Samples.Frame
[ FAILED ] Samples.Bracelet
[ FAILED ] Samples.Sponge4
3 FAILED TESTS
C:\repos\manifold\build\test\Release\manifold_test.exe (process 19368) exited with code 1.
To automatically close the console when debugging stops, enable Tools->Options->Debugging->Automatically close the console when debugging stops.
Press any key to close this window . . .
Ugh, I really wish our CI had a GPU. I just realized I've been neglecting to test on CUDA locally, and honestly I'm not sure for how long. However, today I noticed that Manifold.SmoothSphere
gives: for_each: failed to synchronize: cudaErrorIllegalAddress: an illegal memory access was encountered" thrown in the test body.
and then Manifold.ManualSmooth
seg faults, but just on CUDA.
I went back to #157 and found the same problem there. I'm having some trouble getting things to build before that, probably because I need different flags or something. @pca006132 Can you repro?
First noticed in #171, but subsequently the same failure was found on master when running CUDA. The infinite loop is a byproduct of the mesh not being manifold. Technically IsManifold()
checks two different things: first, is the data structure sane, and second are there any duplicated edges (which make it not a 2-manifold - two triangles attached to every edge). SimplifyTopology
is designed to fix meshes that are not 2-manifold into ones that are 2-manifold. However, we should not be producing any intermediate mesh that isn't sane. I need to add an assert about this to make these problems easier to spot (this is the second time I've run into something like this).
I am using the latest patches for emscripten on an Intel Mac with MacOS 11.6.5. And I believe to have installed all the dependencies. For Mac there is no nvcc
. Here is how far I got with the other options:
compiling for CPP:
cmake -DCMAKE_BUILD_TYPE=Release -DTHRUST_BACKEND=CPP .. && make
error:
...
In file included from /Users/robertosaccon/cAndCpp/manifold/collider/src/collider.cpp:15:
In file included from /Users/robertosaccon/cAndCpp/manifold/collider/include/collider.cuh:16:
/Users/robertosaccon/cAndCpp/manifold/utilities/include/sparse.cuh:16:10: fatal error: 'thrust/binary_search.h' file not found
#include <thrust/binary_search.h>
^~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
make[2]: *** [collider/CMakeFiles/collider.dir/src/collider.cpp.o] Error 1
make[1]: *** [collider/CMakeFiles/collider.dir/all] Error 2
make: *** [all] Error 2
and my attempt for emscripten:
emcmake cmake -DCMAKE_BUILD_TYPE=Release .. && emmake make
error:
...
CMake Error at third_party/CMakeLists.txt:8 (add_subdirectory):
add_subdirectory given source "assimp" which is not an existing directory.
CMake Error at tools/CMakeLists.txt:16 (find_package):
By not providing "FindCGAL.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "CGAL", but
CMake did not find one.
Could not find a package configuration file provided by "CGAL" with any of
the following names:
CGALConfig.cmake
cgal-config.cmake
Add the installation prefix of "CGAL" to CMAKE_PREFIX_PATH or set
"CGAL_DIR" to a directory containing one of the above files. If "CGAL"
provides a separate development package or SDK, be sure it has been
installed.
-- Configuring incomplete, errors occurred!
I have CGAL installed (with brew
). Any advice on how to build it on a Mac ?
The expected workflow is to subdivide by 2 and then refine by -2 with a constraint of modified edge length sizes.
I believe this is a possible operator.
What do you think?
@pca006132 I'm breaking you're repro out here, since I think it's separate from the issue it's in currently, but definitely worth looking into. I can also repro it locally, which is helpful.
Copied from #139 (comment), which was inspired from #141 (comment)
TEST(Boolean, Close) {
Manifold a = Manifold::Sphere(10, 256);
Manifold result = a;
for (int i = 0; i < 10; i++) {
std::cout << i << std::endl;
result ^= a.Translate({a.Precision()/10 * i, 0.0, 0.0});
EXPECT_TRUE(result.IsManifold());
EXPECT_TRUE(result.MatchesTriNormals());
}
}
What I need to check: is this polygon actually epsilon-overlapped, or does the triangulator just have a bug? If it is overlapped, this may imply the precision is not well-bounded (due to nearly-parallel planes?), in which case it may be better to look into making the triangulator robust to overlapping input rather than intentionally failing.
Compile the following code with address sanitizer enabled:
#include <algorithm>
#include "manifold.h"
using namespace manifold;
std::vector<Manifold> RoundedFrameComponents(float edgeLength, float radius,
int circularSegments) {
Manifold edge = Manifold::Cylinder(edgeLength, radius, -1, circularSegments);
Manifold corner = Manifold::Sphere(radius, circularSegments);
std::vector<Manifold> edge1({corner, edge});
for (Manifold& m : edge1) {
m.Rotate(-90).Translate({-edgeLength / 2, -edgeLength / 2, 0});
}
std::vector<Manifold> edge2;
for (Manifold m : edge1) {
edge2.push_back(Manifold(m).Rotate(0, 0, 180));
edge2.push_back(
Manifold(m).Translate({-edgeLength / 2, -edgeLength / 2, 0}));
}
std::vector<Manifold> edge4;
for (Manifold m : edge2) {
edge4.push_back(Manifold(m).Rotate(0, 0, 90));
edge4.push_back(Manifold(m));
}
std::vector<Manifold> frame;
for (Manifold m : edge4) {
frame.push_back(Manifold(m).Translate({0, 0, -edgeLength / 2}));
frame.push_back(Manifold(m).Rotate(180));
}
return frame;
}
int main() {
auto components = RoundedFrameComponents(100, 10, 4);
std::vector<unsigned int> indices({5, 8, 10, 2, 1});
Manifold m;
for (auto i : indices) {
m = m + components[i];
// just to force evaluate
m.NumVert();
}
return 0;
}
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_PAR="NONE" -DMANIFOLD_USE_CUDA=OFF -DCMAKE_CXX_FLAGS="-g -fsanitize=address" -DCMAKE_CXX_COMPILER="clang++" ..
and it will abort with the following message:
==1210305==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61c000002878 at pc 0x000000566be3 bp 0x7ffffffed250 sp 0x7ffffffed248
READ of size 4 at 0x61c000002878 thread T0
[Detaching after fork from child process 1210309]
#0 0x566be2 in manifold::Manifold::Impl::RecursiveEdgeSwap(int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:386:23
#1 0x56691f in manifold::Manifold::Impl::RecursiveEdgeSwap(int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:476:3
#2 0x560b71 in manifold::Manifold::Impl::SimplifyTopology() /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:186:5
#3 0x5a00c6 in manifold::Boolean3::Result(manifold::Manifold::OpType) const /home/pca006132/code/test/manifold/manifold/src/boolean_result.cpp:688:8
#4 0x50abd1 in manifold::Manifold::Boolean(manifold::Manifold const&, manifold::Manifold::OpType) const /home/pca006132/code/test/manifold/manifold/src/manifold.cpp:467:50
#5 0x50af9a in manifold::Manifold::operator+(manifold::Manifold const&) const /home/pca006132/code/test/manifold/manifold/src/manifold.cpp:475:10
#6 0x504b7f in main /home/pca006132/code/test/manifold/tools/union.cpp:47:11
#7 0x7ffff7c8978f in __libc_start_main (/nix/store/qjgj2642srlbr59wwdihnn66sw97ming-glibc-2.33-123/lib/libc.so.6+0x2778f)
#8 0x423279 in _start /build/glibc-2.33/csu/../sysdeps/x86_64/start.S:120
Tested on both ed272b0 and the current master.
Also, different union order will give a different number of vertices. I suspect this is also related to the problem in #131 (comment) . Changing the order in BatchBoolean
will randomly fail some tests, for example by sorting the manifolds instead of using a heap, I managed to get Samples.Sponge4 passed but failed the Samples.FrameReduced test, not sorting/using a heap will lead to a different kind of failure for example.
This case is found by getting a set of manifolds, and repeatedly do a random shuffle and union them until it fails (or executed enough rounds so it can be considered not buggy). And then you can try to reduce the case by doing a random shuffle and discard the last element, similarly until you can no longer reproduce the error after discarding the last element.
This tracks the bug mentioned in #160
I managed to reproduce this with address sanitizer enabled, giving some useful backtrace:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Boolean
[ RUN ] Boolean.Close
=================================================================
==14036==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed: 0x7fff00b0b510 in thread T0
#0 0x52ae07 in operator delete(void*) (/home/pca006132/code/manifold/build/test/manifold_test+0x52ae07)
#1 0x5c55bb in __gnu_cxx::new_allocator<std::_List_node<(anonymous namespace)::Monotones::EdgePair> >::deallocate(std::_List_node<(anonymous namespace)::Monotones::EdgePair>*, unsigned long) /nix/store/as1xzrm2921pnxx4jvxj39jn4v88wdy1-gcc-11.3.0/include/c++/11.3.0/ext/new_allocator.h:145:2
#2 0x5c55bb in std::allocator_traits<std::allocator<std::_List_node<(anonymous namespace)::Monotones::EdgePair> > >::deallocate(std::allocator<std::_List_node<(anonymous namespace)::Monotones::EdgePair> >&, std::_List_node<(anonymous namespace)::Monotones::EdgePair>*, unsigned long) /nix/store/as1xzrm2921pnxx4jvxj39jn4v88wdy1-gcc-11.3.0/include/c++/11.3.0/bits/alloc_traits.h:496:13
#3 0x5c55bb in std::__cxx11::_List_base<(anonymous namespace)::Monotones::EdgePair, std::allocator<(anonymous namespace)::Monotones::EdgePair> >::_M_put_node(std::_List_node<(anonymous namespace)::Monotones::EdgePair>*) /nix/store/as1xzrm2921pnxx4jvxj39jn4v88wdy1-gcc-11.3.0/include/c++/11.3.0/bits/stl_list.h:446:9
#4 0x5c55bb in std::__cxx11::_List_base<(anonymous namespace)::Monotones::EdgePair, std::allocator<(anonymous namespace)::Monotones::EdgePair> >::_M_clear() /nix/store/as1xzrm2921pnxx4jvxj39jn4v88wdy1-gcc-11.3.0/include/c++/11.3.0/bits/list.tcc:81:4
#5 0x5c55bb in std::__cxx11::_List_base<(anonymous namespace)::Monotones::EdgePair, std::allocator<(anonymous namespace)::Monotones::EdgePair> >::~_List_base() /nix/store/as1xzrm2921pnxx4jvxj39jn4v88wdy1-gcc-11.3.0/include/c++/11.3.0/bits/stl_list.h:499:9
#6 0x5c55bb in (anonymous namespace)::Monotones::~Monotones() /home/pca006132/code/manifold/src/polygon/src/polygon.cpp:65:7
#7 0x5c55bb in manifold::Triangulate(std::vector<std::vector<manifold::PolyVert, std::allocator<manifold::PolyVert> >, std::allocator<std::vector<manifold::PolyVert, std::allocator<manifold::PolyVert> > > > const&, float) /home/pca006132/code/manifold/src/polygon/src/polygon.cpp:922:3
#8 0x76ca66 in manifold::Manifold::Impl::Face2Tri(manifold::VecDH<int> const&, manifold::VecDH<manifold::BaryRef> const&, manifold::VecDH<int> const&) /home/pca006132/code/manifold/src/manifold/src/face_op.cpp:152:41
#9 0x759cdb in manifold::Boolean3::Result(manifold::Manifold::OpType) const /home/pca006132/code/manifold/src/manifold/src/boolean_result.cpp:682:8
#10 0x6a4fef in manifold::CsgOpNode::ToLeafNode() const /home/pca006132/code/manifold/src/manifold/src/csg_tree.cpp:289:50
#11 0x6f0a29 in manifold::Manifold::GetCsgLeafNode() const /home/pca006132/code/manifold/src/manifold/src/manifold.cpp:83:22
#12 0x6f82fb in manifold::Manifold::IsManifold() const /home/pca006132/code/manifold/src/manifold/src/manifold.cpp:333:10
#13 0x5a6651 in Boolean_Close_Test::TestBody() /home/pca006132/code/manifold/test/mesh_test.cpp:924:5
#14 0x665a10 in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2443:10
#15 0x665a10 in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2479:14
#16 0x5fdc8f in testing::Test::Run() /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2517:5
#17 0x6014db in testing::TestInfo::Run() /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2693:11
#18 0x602c8f in testing::TestCase::Run() /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2811:28
#19 0x62b268 in testing::internal::UnitTestImpl::RunAllTests() /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:5177:43
#20 0x667d50 in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2443:10
#21 0x667d50 in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:2479:14
#22 0x62a09a in testing::UnitTest::Run() /home/pca006132/code/manifold/test/third_party/google_test/googletest/src/gtest.cc:4786:10
#23 0x5be4de in RUN_ALL_TESTS() /home/pca006132/code/manifold/test/third_party/google_test/googletest/include/gtest/gtest.h:2341:46
#24 0x5be4de in main /home/pca006132/code/manifold/test/test_main.cpp:56:10
#25 0x7fa9d7b0d236 in __libc_start_call_main (/nix/store/scd5n7xsn0hh0lvhhnycr9gx0h8xfzsl-glibc-2.34-210/lib/libc.so.6+0x29236)
#26 0x7fa9d7b0d2f4 in __libc_start_main@GLIBC_2.2.5 (/nix/store/scd5n7xsn0hh0lvhhnycr9gx0h8xfzsl-glibc-2.34-210/lib/libc.so.6+0x292f4)
#27 0x44b8c0 in _start /build/glibc-2.34/csu/../sysdeps/x86_64/start.S:116
Address 0x7ffd43ec2b90 is located in stack of thread T0 at offset 1680 in frame
#0 0x5be6ff in manifold::Triangulate(std::vector<std::vector<manifold::PolyVert, std::allocator<manifold::PolyVert> >, std::allocator<std::vector<manifold::PolyVert, std::allocator<manifold::PolyVert> > > > const&, float) /home/pca006132/code/manifold/src/polygon/src/polygon.cpp:913
This frame has 32 object(s):
[32, 40) '__dnew.i.i249.i'
[64, 72) '__dnew.i.i234.i'
[96, 104) '__dnew.i.i202.i'
[128, 136) '__dnew.i.i.i'
[160, 264) 'triangulator.i' (line 108)
[304, 336) 'ref.tmp77.i' (line 130)
[368, 400) 'ref.tmp81.i' (line 130)
[432, 464) 'ref.tmp118.i' (line 137)
[496, 528) 'ref.tmp122.i' (line 137)
[560, 568) '__dnew.i.i304.i.i'
[592, 600) '__dnew.i.i272.i.i'
[624, 656) 'ref.tmp26.i.i' (line 805)
[688, 720) 'ref.tmp28.i.i' (line 805)
[752, 784) 'ref.tmp51.i.i' (line 808)
[816, 848) 'ref.tmp55.i.i' (line 808)
[880, 888) '__dnew.i.i906.i.i'
[912, 920) '__dnew.i.i891.i.i'
[944, 952) '__dnew.i.i850.i.i'
[976, 984) '__dnew.i.i835.i.i'
[1008, 1016) '__dnew.i.i636.i.i'
[1040, 1048) '__dnew.i.i543.i.i'
[1072, 1080) '__dnew.i.i.i.i'
[1104, 1136) 'nextAttached.i.i' (line 635)
[1168, 1200) 'ref.tmp79.i.i' (line 673)
[1232, 1264) 'ref.tmp83.i.i' (line 673)
[1296, 1328) 'ref.tmp174.i.i' (line 695)
[1360, 1392) 'ref.tmp178.i.i' (line 695)
[1424, 1456) 'ref.tmp236.i.i' (line 702)
[1488, 1520) 'ref.tmp240.i.i' (line 702)
[1552, 1584) 'ref.tmp266.i.i' (line 704)
[1616, 1648) 'ref.tmp270.i.i' (line 704)
[1680, 1760) 'monotones' (line 917) <== Memory access at offset 1680 is inside this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
I'm pretty sure there's a way to tell Github to run clang-format on any submitted PR, which would be nice both with viewing diffs and with keeping the code base consistent. I have VSCode set up to use the standard google
style with clang-format, so that's what I'd like automatically applied for other contributors.
I managed to repro the problem from #171 on master by manually reordering the sponge operations. Many different orders worked fine, but this one, which is most similar to the result of the other PR, did not:
Manifold tmp1 = hole.Rotate(90) + hole.Rotate(90).Rotate(0, 0, 90);
Manifold tmp2 = hole + tmp1;
result = result - tmp2;
Not only did it come up with 39k degenerate triangles, it also ended up with the wrong genus. It's pretty clear there's a problem:
I think it would be cool once you have a JS interface and a WASM module to create a Three.js example of just simple CSG Booleans and submit it to Three.js as PR. It wouldn't even need all the other functionality that this library offers to start -- and that may help get it accepted as the WASM bundle will be smaller.
Robust CSG booleans have been missing from Three.js and other online WebGL/WebGPU library and this could fulfill that need. I think that just robust CSG booleans would be very interesting to the Three.js project.
As discussed previously, here is example input leading to:
Error in file: ...\manifold\polygon\src\polygon.cpp (599): 'skipped.empty() || !vert->IsPast(skipped.back(), precision_)' is false: Not Geometrically Valid! None of the skipped verts is valid.
a.obj:
v 0.000000 0.000000 0.000000
v 0.000000 230.000000 0.000000
v -1630.000000 230.000000 0.000000
v -1400.000000 0.000000 0.000000
v 0.000000 0.000000 -2143.000000
v -1630.000000 230.000000 -2143.000000
v 0.000000 230.000000 -2143.000000
v -1400.000000 0.000000 -2143.000000
f 1 2 3
f 3 4 1
f 5 6 7
f 6 5 8
f 7 3 2
f 7 6 3
f 6 4 3
f 6 8 4
f 8 1 4
f 8 5 1
f 5 2 1
f 5 7 2
b.obj:
v -1185.000000 -70.000000 -2143.000000
v -305.000000 -70.000000 -2143.000000
v -1185.000000 -70.000000 0.000000
v -305.000000 -70.000000 0.000000
v -1185.000000 15320.000000 0.000000
v -305.000000 15320.000000 -2143.000000
v -1185.000000 15320.000000 -2143.000000
v -305.000000 15320.000000 0.000000
v -1185.000000 460.000000 0.000000
v -305.000000 460.000000 0.000000
v -1185.000000 460.000000 -2143.000000
v -305.000000 460.000000 -2143.000000
v -1185.000000 160.000000 -2143.000000
v -305.000000 160.000000 0.000000
v -1185.000000 160.000000 0.000000
v -305.000000 160.000000 -2143.000000
f 1 2 3
f 4 3 2
f 5 6 7
f 6 5 8
f 3 7 1
f 5 7 3
f 4 5 3
f 8 5 4
f 2 8 4
f 6 8 2
f 1 6 2
f 7 6 1
f 9 10 11
f 12 11 10
f 13 14 15
f 14 13 16
f 11 15 9
f 13 15 11
f 12 13 11
f 16 13 12
f 10 16 12
f 14 16 10
f 9 14 10
f 15 14 9
the exception happens when we try to get the result of a - b.
In my use case, I have to combine some complicated models together via union, but only have a few faces touching each other (or maybe no intersection). Is it possible to only triangulate the affected faces or somehow limit the computation to neighboring edges/vertices?
Example performance test:
#include "manifold.h"
#include "meshIO.h"
#include "glm/vec3.hpp"
#include <chrono>
using namespace manifold;
int main() {
Manifold m = ImportMesh("test.stl");
Manifold result = m;
for (int i = 0; i < 100; i++) {
auto start = std::chrono::high_resolution_clock::now();
result = result + m.Translate(glm::vec3(18*i, 0, 0));
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Iteration " << i << ": " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
}
return 0;
}
Result: Iteration time increases as result
becomes larger.
My model: test.zip (unpack for the test.stl
)
The target is something like this:
So their bounding boxes will intersect, but they are actually disjoint. (I can probably use combine, but I am a bit afraid that I may accidentally create something that is not valid).
For some functions such as smooth, it might be desirable to only operate on a subset of vertices/edges, for example to make round edges or add fillet. I think it may make sense to allow users to filter edges/vertices via custom predicate, and allow users to mark vertices with unique IDs for tracking after other operations.
We currently use Polygons
as input to several Manifold
constructors, and they could easily be output as well (think Slice
), but we have no way to operate on them in 2D. I would like to think of Polygon
as the 2D equivalent to Mesh
, and I'd like to create a CrossSection
class that is the 2D equivalent to Manifold
.
A CrossSection
will always be geometrically-valid (non-overlapping), as any input Polygons
will be automatically fixed up according to winding number. Clipper lib seems like a good candidate to support useful 2D operations. I propose we adopt the "strictly positive" fill rule; this will allow Boolean-style operations simply by adding Polygons with the proper winding direction. This class will be totally independent of the Manifold
class; they can simply interact through exported Polygons
.
A simple relation API might serve as an alternative to translate and rotate. For example, one may specify two circles of two different meshes to be concentric with other additional constraints. As the intention of this is to substitute things like translate and rotate, we don't need to store the relations, i.e. we only compute the required transformation immediately and apply it. If the set of constraints do not yield a unique solution, we can give any valid solution or return an error (when the user expects to fully define the object). The problem is that future transformations may violate old constraints.
The API I'm thinking about is something like this:
a_tran = place([a]).such_that([a.hole1.concentric(b.hole1)])[0]
where the place([a])
specifies the objects we want to move (a
in this case), and the such_that
clause specifies the set of constraints. The function will return a list of rigid transformations, or return an error if there is no solution.
Hi!
I was trying to get the original vertex data from boolean operations.
https://github.com/V-Sekai/godot/blob/csg-manifold-04/modules/csg/csg.cpp#L298-L419
Having trouble getting the original mesh id and its triangle index so I can check the property.
Apply the following patch:
diff --git a/tools/perf_test.cpp b/tools/perf_test.cpp
index 2ad385a..c002aa0 100644
--- a/tools/perf_test.cpp
+++ b/tools/perf_test.cpp
@@ -23,7 +23,6 @@ int main(int argc, char **argv) {
for (int i = 0; i < 8; ++i) {
Manifold sphere = Manifold::Sphere(1, (8 << i) * 4);
Manifold sphere2 = sphere;
- sphere2.Translate(glm::vec3(0.5));
auto start = std::chrono::high_resolution_clock::now();
Manifold diff = sphere - sphere2;
auto end = std::chrono::high_resolution_clock::now();
And run perfTest
with CPP
backend, it will segfault after n=8192. With address sanitizer enabled:
nTri = 512, time = 0.0255094 sec
nTri = 2048, time = 0.101124 sec
nTri = 8192, time = 0.321662 sec
=================================================================
==1540173==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6310002a87f4 at pc 0x00000056519a bp 0x7ffdeba3df50 sp 0x7ffdeba3df48
WRITE of size 4 at 0x6310002a87f4 thread T0
#0 0x565199 in manifold::Manifold::Impl::RemoveIfFolded(int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:211:50
#1 0x564728 in manifold::Manifold::Impl::FormLoop(int, int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:193:3
#2 0x560e31 in manifold::Manifold::Impl::CollapseEdge(int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:294:9
#3 0x55e2a2 in manifold::Manifold::Impl::SimplifyTopology() /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:131:39
#4 0x59ae56 in manifold::Boolean3::Result(manifold::Manifold::OpType) const /home/pca006132/code/test/manifold/manifold/src/boolean_result.cpp:690:8
#5 0x509601 in manifold::Manifold::Boolean(manifold::Manifold const&, manifold::Manifold::OpType) const /home/pca006132/code/test/manifold/manifold/src/manifold.cpp:466:50
#6 0x509b8d in manifold::Manifold::operator-(manifold::Manifold const&) const /home/pca006132/code/test/manifold/manifold/src/manifold.cpp:489:10
#7 0x5039bd in main /home/pca006132/code/test/manifold/tools/perf_test.cpp:27:28
#8 0x7fc9f51c078f in __libc_start_main (/nix/store/qjgj2642srlbr59wwdihnn66sw97ming-glibc-2.33-123/lib/libc.so.6+0x2778f)
#9 0x423279 in _start /build/glibc-2.33/csu/../sysdeps/x86_64/start.S:120
0x6310002a87f4 is located 12 bytes to the left of 68688-byte region [0x6310002a8800,0x6310002b9450)
allocated by thread T0 here:
#0 0x4c9497 in __interceptor_malloc (/home/pca006132/code/test/manifold/buildCPP/tools/perfTest+0x4c9497)
#1 0x5642b9 in manifold::ManagedVec<glm::vec<3, float, (glm::qualifier)0> >::mallocManaged(glm::vec<3, float, (glm::qualifier)0>**, unsigned long) /home/pca006132/code/test/manifold/utilities/include/vec_dh.h:257:36
#2 0x5642b9 in manifold::ManagedVec<glm::vec<3, float, (glm::qualifier)0> >::reserve(unsigned long) /home/pca006132/code/test/manifold/utilities/include/vec_dh.h:158:7
#3 0x5642b9 in manifold::ManagedVec<glm::vec<3, float, (glm::qualifier)0> >::push_back(glm::vec<3, float, (glm::qualifier)0> const&) /home/pca006132/code/test/manifold/utilities/include/vec_dh.h:186:7
#4 0x5642b9 in manifold::VecDH<glm::vec<3, float, (glm::qualifier)0> >::push_back(glm::vec<3, float, (glm::qualifier)0> const&) /home/pca006132/code/test/manifold/utilities/include/vec_dh.h:417:40
#5 0x5642b9 in manifold::Manifold::Impl::FormLoop(int, int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:178:12
#6 0x560e31 in manifold::Manifold::Impl::CollapseEdge(int) /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:294:9
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/pca006132/code/test/manifold/manifold/src/edge_op.cpp:211:50 in manifold::Manifold::Impl::RemoveIfFolded(int)
I've built the project with address sanitizer enabled:
cmake -DCMAKE_BUILD_TYPE=Release -DMANIFOLD_USE_CPP=on -DCMAKE_CUDA_COMPILER=$CUDACXX -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined" -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang ..
and Manifold.ManualSmooth
reports the following error:
==39777==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f2cc3a027dc at pc 0x0000021183e3 bp 0x7ffda836e930 sp 0x7ffda836e928
READ of size 4 at 0x7f2cc3a027dc thread T0
#0 0x21183e2 in Manifold_ManualSmooth_Test::TestBody() /home/pca006132/code/manifold/test/mesh_test.cpp:308:36
#1 0x21c3b10 in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2443:10
#2 0x21c3b10 in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2479:14
#3 0x218353f in testing::Test::Run() /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2517:5
#4 0x2185399 in testing::TestInfo::Run() /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2693:11
#5 0x2186cc6 in testing::TestCase::Run() /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2811:28
#6 0x21a0607 in testing::internal::UnitTestImpl::RunAllTests() /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:5177:43
#7 0x21c767f in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2443:10
#8 0x21c767f in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:2479:14
#9 0x219ef3e in testing::UnitTest::Run() /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/src/gtest.cc:4786:10
#10 0x20e4ef5 in RUN_ALL_TESTS() /home/pca006132/code/manifold/build/third_party/google_test/googletest-src/googletest/include/gtest/gtest.h:2341:46
#11 0x20e4ef5 in main /home/pca006132/code/manifold/test/test_main.cpp:50:10
#12 0x7f2cc910778f in __libc_start_main (/nix/store/qjgj2642srlbr59wwdihnn66sw97ming-glibc-2.33-123/lib/libc.so.6+0x2778f)
#13 0x2004589 in _start /build/glibc-2.33/csu/../sysdeps/x86_64/start.S:120
0x7f2cc3a027dc is located 36 bytes to the left of 494496-byte region [0x7f2cc3a02800,0x7f2cc3a7b3a0)
allocated by thread T0 here:
#0 0x20e20e7 in operator new(unsigned long) (/home/pca006132/code/manifold/build/test/manifold_test+0x20e20e7)
#1 0x21e7609 in __gnu_cxx::new_allocator<glm::vec<3, float, (glm::qualifier)0> >::allocate(unsigned long, void const*) /nix/store/wly4zlhr614xi6mb97d4r040d69ikkfw-gcc-10.3.0/include/c++/10.3.0/ext/new_allocator.h:115:42
#2 0x21e7609 in std::allocator_traits<std::allocator<glm::vec<3, float, (glm::qualifier)0> > >::allocate(std::allocator<glm::vec<3, float, (glm::qualifier)0> >&, unsigned long) /nix/store/wly4zlhr614xi6mb97d4r040d69ikkfw-gcc-10.3.0/include/c++/10.3.0/bits/alloc_traits.h:460:22
#3 0x21e7609 in std::_Vector_base<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > >::_M_allocate(unsigned long) /nix/store/wly4zlhr614xi6mb97d4r040d69ikkfw-gcc-10.3.0/include/c++/10.3.0/bits/stl_vector.h:346:36
#4 0x21e7609 in void std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > >::_M_range_insert<thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*> >(__gnu_cxx::__normal_iterator<glm::vec<3, float, (glm::qualifier)0>*, std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > > >, thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>, thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>, std::forward_iterator_tag) /nix/store/wly4zlhr614xi6mb97d4r040d69ikkfw-gcc-10.3.0/include/c++/10.3.0/bits/vector.tcc:769:38
#5 0x21e7609 in void std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > >::_M_insert_dispatch<thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*> >(__gnu_cxx::__normal_iterator<glm::vec<3, float, (glm::qualifier)0>*, std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > > >, thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>, thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>, std::__false_type) /nix/store/wly4zlhr614xi6mb97d4r040d69ikkfw-gcc-10.3.0/include/c++/10.3.0/bits/stl_vector.h:1665:16
#6 0x21e7609 in __gnu_cxx::__normal_iterator<glm::vec<3, float, (glm::qualifier)0>*, std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > > > std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > >::insert<thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>, void>(__gnu_cxx::__normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*, std::vector<glm::vec<3, float, (glm::qualifier)0>, std::allocator<glm::vec<3, float, (glm::qualifier)0> > > >, thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>, thrust::detail::normal_iterator<glm::vec<3, float, (glm::qualifier)0> const*>) /nix/store/wly4zlhr614xi6mb97d4r040d69ikkfw-gcc-10.3.0/include/c++/10.3.0/bits/stl_vector.h:1383:19
#7 0x21e7609 in manifold::Manifold::GetMeshRelation() const /home/pca006132/code/manifold/manifold/src/manifold.cu:277:111
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/pca006132/code/manifold/test/mesh_test.cpp:308:36 in Manifold_ManualSmooth_Test::TestBody()
Shadow bytes around the buggy address:
0x0fe6187384a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0fe6187384b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0fe6187384c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0fe6187384d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0fe6187384e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0fe6187384f0: fa fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa fa
0x0fe618738500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe618738510: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe618738520: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe618738530: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0fe618738540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==39777==ABORTING
As this library is intended to be fast, I guess we should probably open an issue to track its performance?
I did some microbenchmark using the perfTest
binary and a unionPerfTest
code that I wrote to test lazy union (union 100 spheres with diameter=2.5 with varying separation distance, i.e. may or may not overlap). I've tested single thread using CPP backend, OpenMP backend and CUDA backend all on the same laptop with i5-8300H and GTX1050 Mobile. Here is my spreadsheet, and here is my branch for some optimizations that looks quite effective for CUDA and small meshes :). I will open a separate PR for the branch, after the build script PR is merged.
From the results, we can see that:
VecDH
and provide an explicit copy method.Signed distance functions (SDF) are another way to represent 3D solids. They can be discrete (voxels) or continuous (mathematical functions). Traditionally they can be converted to a manifold surface mesh via the Marching Cubes algorithm. I think an implementation of this will fit nicely as an alternative way to create complicated manifolds. SDFs naturally handle Boolean-type operations as well, but they do so in ways that scale differently computationally and can also soften the intersections naturally. I also have some ideas to improve on Marching Cubes via my own Marching Tetrahedra algorithm.
Create strategy to change exception code to return codes.
Some discussion is in the Godot issue.
I wasn't able to recreate the steps, but here are the scene files and screenshots. Note that this file isn't an exact reproducable test case.
Before:
After:
The following code will union 4 manifolds (should all be valid, checked via IsManifold
). With MANIFOLD_USE_OMP=on
or using CUDA, it will non-deterministically fail. With MANIFOLD_USE_CPP=on
, it will fail for the first run. The commented part is to show that these manifolds don't intersect each other (but they do touch each other). Keeping only the first two elements of the vector will cause OpenMP to union them correctly, but the sequential backend will still throw an error.
#include "manifold.h"
using namespace manifold;
void unionAll(std::vector<Manifold> manifolds, std::vector<int> indices) {
Manifold result;
for (int i : indices) {
// Manifold intersection = result ^ manifolds[i];
// std::cout << "intersections: " << intersection.NumVert() << std::endl;
result += manifolds[i];
}
}
int main() {
// cannot find a better way to produce this...
std::vector<glm::vec3> vertPos(
{{-8.3, -8.3, 0}, {-7, -7, 0}, {-7, -7.05, 1}, {-7, -7, 1},
{-2.5, -7.05, 1}, {-2.5, -7, 1}, {-2.5, -6.75, 1}, {-8.3, -8.3, 6},
{-7, -7.05, 6}, {-2.5, -7.05, 6}, {-2.5, -6.75, 6}, {-8.3, 8.3, 0},
{-7, 7, 0}, {-7, 7, 1}, {-7, 7.05, 1}, {-2.5, 6.75, 1},
{-2.5, 7, 1}, {-2.5, 7.05, 1}, {-8.3, 8.3, 6}, {-7, 7.05, 6},
{-2.5, 6.75, 6}, {-2.5, 7.05, 6}, {2.5, -7.05, 1}, {2.5, -7, 1},
{2.5, -6.75, 1}, {7, -7, 0}, {8.3, -8.3, 0}, {7, -7.05, 1},
{7, -7, 1}, {2.5, -7.05, 6}, {2.5, -6.75, 6}, {7, -7.05, 6},
{8.3, -8.3, 6}, {2.5, 6.75, 1}, {2.5, 7, 1}, {2.5, 7.05, 1},
{7, 7, 0}, {8.3, 8.3, 0}, {7, 7, 1}, {7, 7.05, 1},
{2.5, 6.75, 6}, {2.5, 7.05, 6}, {7, 7.05, 6}, {8.3, 8.3, 6}});
std::vector<glm::ivec3> triVerts(
{{5, 1, 3}, {3, 2, 4}, {2, 8, 4}, {12, 1, 0}, {3, 1, 13},
{11, 0, 7}, {19, 2, 3}, {1, 26, 0}, {4, 5, 3}, {25, 1, 5},
{6, 23, 5}, {10, 5, 4}, {6, 5, 10}, {6, 10, 24}, {19, 8, 2},
{18, 7, 8}, {4, 8, 9}, {7, 0, 32}, {10, 4, 9}, {32, 8, 7},
{8, 32, 9}, {30, 10, 9}, {11, 12, 0}, {12, 13, 1}, {19, 3, 13},
{12, 16, 13}, {14, 13, 16}, {13, 14, 19}, {34, 16, 12}, {11, 36, 12},
{14, 16, 17}, {16, 33, 15}, {37, 11, 18}, {17, 21, 14}, {20, 16, 15},
{17, 16, 20}, {7, 18, 11}, {8, 19, 18}, {14, 21, 19}, {21, 18, 19},
{21, 17, 20}, {15, 40, 20}, {18, 21, 41}, {41, 21, 20}, {24, 23, 6},
{25, 26, 1}, {23, 25, 5}, {27, 23, 22}, {0, 26, 32}, {22, 23, 30},
{30, 23, 24}, {27, 22, 29}, {28, 25, 23}, {28, 23, 27}, {28, 27, 31},
{25, 28, 36}, {36, 26, 25}, {38, 28, 31}, {24, 10, 30}, {22, 30, 29},
{30, 9, 29}, {32, 29, 9}, {27, 29, 31}, {32, 31, 29}, {43, 32, 26},
{31, 32, 43}, {38, 34, 12}, {34, 33, 16}, {36, 38, 12}, {37, 36, 11},
{35, 34, 38}, {40, 15, 33}, {33, 34, 40}, {40, 34, 35}, {38, 36, 28},
{37, 26, 36}, {26, 37, 43}, {39, 38, 31}, {38, 39, 35}, {42, 35, 39},
{35, 41, 40}, {18, 43, 37}, {41, 35, 42}, {20, 40, 41}, {41, 42, 18},
{18, 42, 43}, {31, 42, 39}, {43, 42, 31}});
Manifold m1 = Manifold(Mesh{vertPos, triVerts});
std::cout << "is m1 a manifold? " << m1.IsManifold() << std::endl;
Manifold m2 = Manifold::Cube({14, 14, 1}, true).Translate({0, 0, 0.5});
std::vector<Manifold> manifolds;
manifolds.push_back(m1);
manifolds.push_back(m2);
manifolds.push_back(m1);
manifolds.back().Translate({18, 0, 0});
manifolds.push_back(m2);
manifolds.back().Translate({18, 0, 0});
for (int i = 0; i < 10; i++) {
std::cout << "union order: 0 1 2 3" << std::endl;
unionAll(manifolds, {0, 1, 2, 3});
std::cout << "union order: 0 2 1 3" << std::endl;
unionAll(manifolds, {0, 2, 1, 3});
}
return 0;
}
It would be nice to be able to use manifold as a header library.
Are there any downside or opposition to restructuring it like that?
third_party/google_test
is not cloneable (the url starts with git@
while the other submodules are all https://
urls and are fine.
Here is the full log:
git clone --recurse-submodules https://github.com/rsaccon/manifold.git
Cloning into 'manifold'...
remote: Enumerating objects: 4847, done.
remote: Counting objects: 100% (485/485), done.
remote: Compressing objects: 100% (171/171), done.
remote: Total 4847 (delta 332), reused 407 (delta 298), pack-reused 4362
Receiving objects: 100% (4847/4847), 24.07 MiB | 3.28 MiB/s, done.
Resolving deltas: 100% (3185/3185), done.
Submodule 'third_party/assimp' (https://github.com/assimp/assimp) registered for path 'third_party/assimp'
Submodule 'third_party/glm' (https://github.com/g-truc/glm) registered for path 'third_party/glm'
Submodule 'third_party/google_test' ([email protected]:google/googletest.git) registered for path 'third_party/google_test'
Submodule 'third_party/thrust' (https://github.com/NVIDIA/thrust) registered for path 'third_party/thrust'
Cloning into '/Users/robertosaccon/cAndCpp/manifold/third_party/assimp'...
remote: Enumerating objects: 71922, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 71922 (delta 5), reused 5 (delta 4), pack-reused 71914
Receiving objects: 100% (71922/71922), 190.19 MiB | 2.02 MiB/s, done.
Resolving deltas: 100% (51145/51145), done.
Cloning into '/Users/robertosaccon/cAndCpp/manifold/third_party/glm'...
remote: Enumerating objects: 56725, done.
remote: Counting objects: 100% (435/435), done.
remote: Compressing objects: 100% (179/179), done.
remote: Total 56725 (delta 273), reused 329 (delta 256), pack-reused 56290
Receiving objects: 100% (56725/56725), 69.17 MiB | 3.25 MiB/s, done.
Resolving deltas: 100% (42792/42792), done.
Cloning into '/Users/robertosaccon/cAndCpp/manifold/third_party/google_test'...
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
fatal: clone of '[email protected]:google/googletest.git' into submodule path '/Users/robertosaccon/cAndCpp/manifold/third_party/google_test' failed
Failed to clone 'third_party/google_test'. Retry scheduled
Cloning into '/Users/robertosaccon/cAndCpp/manifold/third_party/thrust'...
remote: Enumerating objects: 48193, done.
remote: Counting objects: 100% (1687/1687), done.
remote: Compressing objects: 100% (694/694), done.
remote: Total 48193 (delta 1026), reused 1536 (delta 937), pack-reused 46506
Receiving objects: 100% (48193/48193), 16.22 MiB | 3.31 MiB/s, done.
Resolving deltas: 100% (38310/38310), done.
Cloning into '/Users/robertosaccon/cAndCpp/manifold/third_party/google_test'...
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
fatal: clone of '[email protected]:google/googletest.git' into submodule path '/Users/robertosaccon/cAndCpp/manifold/third_party/google_test' failed
Failed to clone 'third_party/google_test' a second time, aborting
Currently the vertNormal
supplied in a Mesh
is ignored and recalculated instead. Ideally it should be read and used to form the halfedgeTangent
vector. Need to ensure vertNormal_
is making it through SimplifyTopology()
without getting broken.
Just googled for unified memory support in thrust. It seems that they have something called universal_vector which can be accessed from both the host and the device. I guess we can probably use this instead of VecDH with cleaner code and support for complex modeling with GPUs that only has a small RAM? And perhaps we can choose to run the operations on the host or device depending on the workload without much code duplication. I haven't tried this yet though, not sure about its performance or if there is any quirks.
I wonder if it is possible to integrate a 2D constraint engine with this library? That would be a very key step in moving towards proper CAD.
I wonder what FreeCAD uses for its 2D constraint system? https://wiki.freecadweb.org/Sketcher_Workbench
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.