Compare commits

..
Author SHA1 Message Date
Emmett Lalish f44963698f wtf 2023-07-30 09:03:18 -07:00
Emmett Lalish 7c454dc7a4 tighten CCW (#508) 2023-07-29 22:37:52 -07:00
Emmett Lalishandpca006132 ffdd97d4d3 adding python set_properties (#504)
* adding test of properties

* python set_properties

* fixed sponge example

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-07-29 22:10:28 -07:00
Emmett Lalishandpca006132 7facd0ecc0 Make save dropdown menu (#505)
* fix include

* made new dropdown

* mobile ui

* cleanup

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-07-28 12:23:57 -07:00
pca006132 59760b3369 added basic 3mf export (#500)
* added basic 3mf export

* updated worker test

* fixes

* formatting

* fix
2023-07-27 08:34:51 -07:00
Stéphane GINIER 5f9b484b6c Preventing crash with self intersection (#501)
* Prevent infinite recursion

* Add test for self intersection
2023-07-26 21:43:55 -07:00
pca006132 18edefc6c4 some more optimizations (#503)
* simple speedup from stl algorithms

* fix build

* std::list allocator optimization

* added documentation

* fix weird formatting issues

* try fixing MacOS build

it seems that Apple Clang 14 does not support pstl

* fix again

* fix macos build

* fix apple

* clean up using macro

* fix apple?

* small fix...

* fix apple (again...)
2023-07-25 02:33:15 +08:00
Emmett Lalishandpca006132 93e06a2de8 Update python mesh (#492)
* updated python mesh

* updated python meshgl

* clean up array related stuff

* fix hsplit

* fix according to comments

* make to_mesh accept MeshGL

* fixes

---------

Co-authored-by: pca006132 <john.lck40@gmail.com>
2023-07-24 12:08:00 +08:00
Geoff deRosenroll 047159e2c4 Add hull bindings (c, wasm/js, and python) (#497)
* Add C bindings to hull methods

* Add hull python bindings

* WIP adding JS bindings to hull methods

* Add flexible static hull for manifold

* Add hull_points to manifold class

* Remove compose binding

* Cleanup translate/rotate signature overloads
2023-07-20 09:25:27 -07:00
33 changed files with 682 additions and 112 deletions
+4
View File
@@ -140,4 +140,8 @@
"Verts"
],
"javascript.format.semicolons": "insert",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.formatting.provider": "none",
}
+1 -1
View File
@@ -99,7 +99,7 @@ node test/manifold_test.js
### Python
The CMake script will build the python binding `manifold3d` automatically. To
use the extension, please add `$BUILD_DIR/tools` to your `PYTHONPATH`, where
use the extension, please add `$BUILD_DIR/bindings/python` to your `PYTHONPATH`, where
`$BUILD_DIR` is the build directory for CMake. Examples using the python binding
can be found in `bindings/python/examples`. To see exported samples, run:
```
+24
View File
@@ -106,6 +106,30 @@ ManifoldCrossSection *manifold_cross_section_intersection(
return to_c(new (mem) CrossSection(cs));
}
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
ManifoldCrossSection *cs) {
auto hulled = from_c(cs)->Hull();
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_batch_hull(
void *mem, ManifoldCrossSectionVec *css) {
auto hulled = CrossSection::Hull(*from_c(css));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
void *mem, ManifoldSimplePolygon *ps) {
auto hulled = CrossSection::Hull(*from_c(ps));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_hull_polygons(
void *mem, ManifoldPolygons *ps) {
auto hulled = CrossSection::Hull(*from_c(ps));
return to_c(new (mem) CrossSection(hulled));
}
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
ManifoldCrossSection *cs,
float x, float y) {
+29
View File
@@ -8,6 +8,7 @@ extern "C" {
#endif
// Polygons
ManifoldSimplePolygon *manifold_simple_polygon(void *mem, ManifoldVec2 *ps,
size_t length);
ManifoldPolygons *manifold_polygons(void *mem, ManifoldSimplePolygon **ps,
@@ -24,6 +25,7 @@ ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons *ps, int simple_idx,
int pt_idx);
// 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);
@@ -94,7 +96,14 @@ ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
float normal_x, float normal_y,
float normal_z, float offset);
// Convex Hulls
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m);
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms);
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps, size_t length);
// Manifold Transformations
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z);
ManifoldManifold *manifold_rotate(void *mem, ManifoldManifold *m, float x,
@@ -112,6 +121,7 @@ ManifoldManifold *manifold_warp(void *mem, ManifoldManifold *m,
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);
@@ -137,6 +147,7 @@ ManifoldManifoldVec *manifold_decompose(void *mem, ManifoldManifold *m);
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);
@@ -204,6 +215,17 @@ ManifoldCrossSection *manifold_cross_section_difference(
ManifoldCrossSection *manifold_cross_section_intersection(
void *mem, ManifoldCrossSection *a, ManifoldCrossSection *b);
// CrossSection Convex Hulls
ManifoldCrossSection *manifold_cross_section_hull(void *mem,
ManifoldCrossSection *cs);
ManifoldCrossSection *manifold_cross_section_batch_hull(
void *mem, ManifoldCrossSectionVec *css);
ManifoldCrossSection *manifold_cross_section_hull_simple_polygon(
void *mem, ManifoldSimplePolygon *ps);
ManifoldCrossSection *manifold_cross_section_hull_polygons(
void *mem, ManifoldPolygons *ps);
// CrossSection Transformation
ManifoldCrossSection *manifold_cross_section_translate(void *mem,
@@ -268,6 +290,7 @@ ManifoldCrossSection *manifold_rect_as_cross_section(void *mem,
ManifoldRect *r);
// 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);
@@ -292,11 +315,13 @@ 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);
@@ -319,6 +344,7 @@ uint32_t *manifold_meshgl_face_id(void *mem, ManifoldMeshGL *m);
float *manifold_meshgl_halfedge_tangent(void *mem, ManifoldMeshGL *m);
// memory size
size_t manifold_manifold_size();
size_t manifold_manifold_vec_size();
size_t manifold_cross_section_size();
@@ -332,6 +358,7 @@ size_t manifold_rect_size();
size_t manifold_curvature_size();
// destruction
void manifold_destruct_manifold(ManifoldManifold *m);
void manifold_destruct_manifold_vec(ManifoldManifoldVec *ms);
void manifold_destruct_cross_section(ManifoldCrossSection *m);
@@ -343,6 +370,7 @@ void manifold_destruct_box(ManifoldBox *b);
void manifold_destruct_rect(ManifoldRect *b);
// pointer free + destruction
void manifold_delete_manifold(ManifoldManifold *m);
void manifold_delete_manifold_vec(ManifoldManifoldVec *ms);
void manifold_delete_cross_section(ManifoldCrossSection *cs);
@@ -354,6 +382,7 @@ void manifold_delete_box(ManifoldBox *b);
void manifold_delete_rect(ManifoldRect *b);
// MeshIO / Export
#ifdef MANIFOLD_EXPORT
ManifoldMaterial *manifold_material(void *mem);
void manifold_material_set_roughness(ManifoldMaterial *mat, float roughness);
+20
View File
@@ -190,6 +190,26 @@ ManifoldManifold *manifold_trim_by_plane(void *mem, ManifoldManifold *m,
return to_c(new (mem) Manifold(trimmed));
}
ManifoldManifold *manifold_hull(void *mem, ManifoldManifold *m) {
auto hulled = from_c(m)->Hull();
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_batch_hull(void *mem, ManifoldManifoldVec *ms) {
auto hulled = Manifold::Hull(*from_c(ms));
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_hull_pts(void *mem, ManifoldVec3 *ps,
size_t length) {
std::vector<glm::vec3> vec(length);
for (int i = 0; i < length; ++i) {
vec[i] = {ps[i].x, ps[i].y, ps[i].z};
}
auto hulled = Manifold::Hull(vec);
return to_c(new (mem) Manifold(hulled));
}
ManifoldManifold *manifold_translate(void *mem, ManifoldManifold *m, float x,
float y, float z) {
auto v = glm::vec3(x, y, z);
+5 -2
View File
@@ -36,10 +36,13 @@ if __name__ == "__main__":
mesh = model.to_mesh()
if export_models:
if mesh.vert_properties.shape[1] > 3:
vertices = np.hsplit(mesh.vert_properties, 3)[0]
vertices = mesh.vert_properties[:, :3]
colors = (mesh.vert_properties[:, 3:] * 255).astype(np.uint8)
else:
vertices = mesh.vert_properties
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts)
colors = None
meshOut = trimesh.Trimesh(vertices=vertices, faces=mesh.tri_verts,
vertex_colors=colors)
trimesh.exchange.export.export_mesh(meshOut, f'{f}.glb', 'glb')
print(f'Exported model to {f}.glb')
t1 = time()
+40
View File
@@ -0,0 +1,40 @@
from manifold3d import Manifold
import numpy as np
def fractal(holes, hole, w, position, depth, maxDepth):
w /= 3
holes.append(hole.scale([w, w, 1.0]).translate(
[position[0], position[1], 0.0]))
if depth == maxDepth:
return
offsets = np.array([
[-w, -w],
[-w, 0.0],
[-w, w],
[0.0, w],
[w, w],
[w, 0.0],
[w, -w],
[0.0, -w],
])
for offset in offsets:
fractal(holes, hole, w, position + offset, depth + 1, maxDepth)
def posColors(pos, _):
return [1 - p / 2 for p in pos] + [1.0]
def run(n=1):
result = Manifold.cube([1, 1, 1], True)
holes = []
fractal(holes, result, 1.0, np.array([0.0, 0.0]), 1, n)
hole = Manifold.compose(holes)
result -= hole
result -= hole.rotate([90, 0, 0])
result -= hole.rotate([0, 90, 0])
return result.trim_by_plane([1, 1, 1], 0).set_properties(4, posColors).scale(100)
+72
View File
@@ -96,6 +96,24 @@ PYBIND11_MODULE(manifold3d, m) {
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"hull", [](Manifold &self) { return self.Hull(); },
"Compute the convex hull of all points in this manifold.")
.def_static(
"batch_hull",
[](std::vector<Manifold> &ms) { return Manifold::Hull(ms); },
"Compute the convex hull enveloping a set of manifolds.")
.def_static(
"hull_points",
[](std::vector<Float3> &pts) {
std::vector<glm::vec3> vec(pts.size());
for (int i = 0; i < pts.size(); i++) {
vec[i] = {std::get<0>(pts[i]), std::get<1>(pts[i]),
std::get<2>(pts[i])};
}
return Manifold::Hull(vec);
},
"Compute the convex hull enveloping a set of 3d points.")
.def(
"transform",
[](Manifold &self, py::array_t<float> &mat) {
@@ -236,6 +254,36 @@ PYBIND11_MODULE(manifold3d, m) {
"the user to choose their function with discretion."
"\n\n"
":param warpFunc: A function that modifies a given vertex position.")
.def(
"set_properties",
[](Manifold &self, int newNumProp,
const std::function<py::array_t<float>(
Float3, const py::array_t<float> &)> &f) {
const int oldNumProp = self.NumProp();
return self.SetProperties(newNumProp, [newNumProp, oldNumProp, &f](
float *newProps,
glm::vec3 v,
const float *oldProps) {
auto array = f(std::make_tuple(v.x, v.y, v.z),
py::array(oldNumProp, oldProps));
if (array.ndim() != 1 || array.shape(0) != newNumProp)
throw std::runtime_error("Invalid vector shape, expected (" +
std::to_string(newNumProp) + ")");
auto array_view = array.unchecked<1>();
for (int i = 0; i < newNumProp; i++) newProps[i] = array_view(i);
});
},
py::arg("new_num_prop"), py::arg("f"),
"Create a new copy of this manifold with updated vertex properties "
"by supplying a function that takes the existing position and "
"properties as input. You may specify any number of output "
"properties, allowing creation and removal of channels. Note: "
"undefined behavior will result if you read past the number of input "
"properties or write past the number of output properties."
"\n\n"
":param numProp: The new number of properties per vertex."
":param propFunc: A function that modifies the properties of a given "
"vertex.")
.def(
"refine", [](Manifold &self, int n) { return self.Refine(n); },
py::arg("n"),
@@ -410,6 +458,13 @@ PYBIND11_MODULE(manifold3d, m) {
.def_static(
"from_mesh", [](const MeshGL &mesh) { return Manifold(mesh); },
py::arg("mesh"))
.def_static(
"compose",
[](const std::vector<Manifold> &list) {
return Manifold::Compose(list);
},
"combine several manifolds into one without checking for "
"intersections.")
.def_static(
"tetrahedron", []() { return Manifold::Tetrahedron(); },
"Constructs a tetrahedron centered at the origin with one vertex at "
@@ -786,6 +841,23 @@ PYBIND11_MODULE(manifold3d, m) {
.def(py::self + py::self, "Boolean union.")
.def(py::self - py::self, "Boolean difference.")
.def(py::self ^ py::self, "Boolean intersection.")
.def(
"hull", [](CrossSection &self) { return self.Hull(); },
"Compute the convex hull of this cross-section.")
.def_static(
"batch_hull",
[](std::vector<CrossSection> &cs) { return CrossSection::Hull(cs); },
"Compute the convex hull enveloping a set of cross-sections.")
.def_static(
"hull_points",
[](std::vector<Float2> &pts) {
std::vector<glm::vec2> poly(pts.size());
for (int i = 0; i < pts.size(); i++) {
poly[i] = {std::get<0>(pts[i]), std::get<1>(pts[i])};
}
return CrossSection::Hull(poly);
},
"Compute the convex hull enveloping a set of 2d points.")
.def("decompose", &CrossSection::Decompose,
"This operation returns a vector of CrossSections that are "
"topologically disconnected, each containing one outline contour "
+9
View File
@@ -125,6 +125,10 @@ EMSCRIPTEN_BINDINGS(whatever) {
function("_crossSectionUnionN", &cross_js::UnionN);
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
function("_crossSectionIntersectionN", &cross_js::IntersectionN);
function("_crossSectionCollectVertices", &cross_js::CollectVertices);
function("_crossSectionHullPoints",
select_overload<CrossSection(std::vector<glm::vec2>)>(
&CrossSection::Hull));
class_<Manifold>("Manifold")
.constructor(&man_js::FromMeshJS)
@@ -134,6 +138,7 @@ EMSCRIPTEN_BINDINGS(whatever) {
.function("_Split", &man_js::Split)
.function("_SplitByPlane", &man_js::SplitByPlane)
.function("_TrimByPlane", &Manifold::TrimByPlane)
.function("hull", select_overload<Manifold() const>(&Manifold::Hull))
.function("_GetMeshJS", &js::GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &man_js::Warp)
@@ -176,6 +181,10 @@ EMSCRIPTEN_BINDINGS(whatever) {
function("_manifoldUnionN", &man_js::UnionN);
function("_manifoldDifferenceN", &man_js::DifferenceN);
function("_manifoldIntersectionN", &man_js::IntersectionN);
function("_manifoldCollectVertices", &man_js::CollectVertices);
function("_manifoldHullPoints",
select_overload<Manifold(const std::vector<glm::vec3>&)>(
&Manifold::Hull));
// Quality Globals
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
+57
View File
@@ -472,6 +472,36 @@ Module.setup = function() {
Module.CrossSection.difference = crossSectionBatchbool('DifferenceN');
Module.CrossSection.intersection = crossSectionBatchbool('IntersectionN');
function pushVec2(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1]};
return p;
})
}
Module.CrossSection.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec2();
for (const cs of args) {
if (cs instanceof CrossSectionCtor) {
Module._crossSectionCollectVertices(pts, cs);
} else if (
cs instanceof Array && cs.length == 2 && typeof cs[0] == 'number') {
pts.push_back({x: cs[0], y: cs[1]});
} else if (cs.x) {
pts.push_back(cs);
} else {
const wrap =
((cs[0].length == 2 && typeof cs[0][0] == 'number') || cs[0].x);
const polys = wrap ? [cs] : cs;
for (const poly of polys) pushVec2(pts, poly);
}
}
const result = Module._crossSectionHullPoints(pts);
pts.delete();
return result;
};
Module.CrossSection.prototype = Object.create(CrossSectionCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
@@ -596,6 +626,33 @@ Module.setup = function() {
return out;
};
function pushVec3(vec, ps) {
toVec(vec, ps, p => {
if (p instanceof Array) return {x: p[0], y: p[1], z: p[2]};
return p;
})
}
Module.Manifold.hull = function(...args) {
if (args.length == 1) args = args[0];
let pts = new Module.Vector_vec3();
for (const m of args) {
if (m instanceof ManifoldCtor) {
Module._manifoldCollectVertices(pts, m);
} else if (
m instanceof Array && m.length == 3 && typeof m[0] == 'number') {
pts.push_back({x: m[0], y: m[1], z: m[2]});
} else if (m.x) {
pts.push_back(m);
} else {
pushVec3(pts, m);
}
}
const result = Module._manifoldHullPoints(pts);
pts.delete();
return result;
};
Module.Manifold.prototype = Object.create(ManifoldCtor.prototype);
// Because the constructor and prototype are being replaced, instanceof will
+1
View File
@@ -0,0 +1 @@
declare module '@jscadui/3mf-export';
+43 -8
View File
@@ -130,18 +130,14 @@ model-viewer {
.header .margin {
font-size: 16px;
margin: 1em;
}
#file {
width: 250px;
margin: 1em 0.5em;
}
.dropdown {
display: none;
position: absolute;
z-index: 1;
width: 250px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
}
@@ -150,6 +146,10 @@ model-viewer {
display: block;
}
.hide {
display: none;
}
.uparrow {
display: inline-block;
position: relative;
@@ -174,6 +174,7 @@ model-viewer {
position: absolute;
width: 36px;
top: 0px;
right: 0px;
background-size: 20px;
background-repeat: no-repeat;
background-position: center;
@@ -193,7 +194,6 @@ model-viewer {
.trash {
background-image: url(/icons/trash.png);
right: 0px;
}
.icon:hover {
@@ -212,6 +212,15 @@ model-viewer {
background-color: rgb(255, 177, 177);
}
#file-margin {
position: relative;
width: 250px;
}
#save {
width: 125px;
}
@media only screen and (max-width: 600px) {
.container {
flex-direction: column;
@@ -220,5 +229,31 @@ model-viewer {
.col {
width: 100%;
height: 50%;
}
}
#file-margin {
width: calc(100% - 32px);
}
p {
margin: .2em 1em;
text-align: center;
}
.header .margin {
margin: 0.2em 16px;
}
#button-container {
flex-direction: row;
margin-left: 8px;
margin-right: 8px;
width: calc(100% - 16px);
}
#button-container .margin {
width: calc(50% - 16px);
margin: 8px;
}
}
+62 -28
View File
@@ -26,20 +26,31 @@ let editor = undefined;
// File UI ------------------------------------------------------------
const fileButton = document.querySelector('#file');
const currentElement = document.querySelector('#current');
const arrow = document.querySelector('.uparrow');
const dropdown = document.querySelector('.dropdown');
const currentFileElement = document.querySelector('#current');
const fileArrow = document.querySelector('#file .uparrow');
const fileDropdown = document.querySelector('#fileDropdown');
const saveContainer = document.querySelector('#save');
const saveDropdown = document.querySelector('#saveDropdown');
const saveArrow = document.querySelector('#save .uparrow');
const hideDropdown = function() {
dropdown.classList.remove('show');
arrow.classList.remove('down');
fileDropdown.classList.remove('show');
saveDropdown.classList.remove('show');
fileArrow.classList.remove('down');
saveArrow.classList.remove('down');
};
const toggleDropdown = function(event) {
const toggleFileDropdown = function(event) {
event.stopPropagation();
dropdown.classList.toggle('show');
arrow.classList.toggle('down');
fileDropdown.classList.toggle('show');
fileArrow.classList.toggle('down');
};
fileButton.onclick = toggleDropdown;
const toggleSaveDropdown = function(event) {
event.stopPropagation();
saveDropdown.classList.toggle('show');
saveArrow.classList.toggle('down');
};
fileButton.onclick = toggleFileDropdown;
saveArrow.parentElement.onclick = toggleSaveDropdown;
document.body.onclick = hideDropdown;
const prefix = 'ManifoldCAD';
@@ -62,7 +73,7 @@ function nthKey(n) {
function saveCurrent() {
if (editor) {
const currentName = currentElement.textContent;
const currentName = currentFileElement.textContent;
if (!exampleFunctions.get(currentName)) {
setScript(currentName, editor.getValue());
}
@@ -77,7 +88,7 @@ let isExample = true;
function switchTo(scriptName) {
if (editor) {
switching = true;
currentElement.textContent = scriptName;
currentFileElement.textContent = scriptName;
setScript('currentName', scriptName);
const code =
exampleFunctions.get(scriptName) ?? getScript(scriptName) ?? '';
@@ -148,8 +159,8 @@ function addEdit(button) {
if (!input) return;
const newName = uniqueName(input);
label.textContent = newName;
if (currentElement.textContent == oldName) {
currentElement.textContent = newName;
if (currentFileElement.textContent == oldName) {
currentFileElement.textContent = newName;
}
removeScript(oldName);
setScript(newName, code);
@@ -182,7 +193,7 @@ function addEdit(button) {
}, {once: true});
} else if (performance.now() - lastClick > 500) {
removeScript(label.textContent);
if (currentElement.textContent == label.textContent) {
if (currentFileElement.textContent == label.textContent) {
switchTo('Intro');
}
const container = button.parentElement;
@@ -269,10 +280,10 @@ require(['vs/editor/editor.main'], async function() {
for (const [name] of exampleFunctions) {
const button = createDropdownItem(name);
dropdown.appendChild(button.parentElement);
fileDropdown.appendChild(button.parentElement);
}
let currentName = currentElement.textContent;
let currentName = currentFileElement.textContent;
for (let i = 0; i < window.localStorage.length; i++) {
const key = nthKey(i);
@@ -347,7 +358,10 @@ function finishRun() {
}
const mv = document.querySelector('model-viewer');
let objectURL = null;
const output = {
glbURL: null,
threeMFURL: null
};
let manifoldWorker = null;
function createWorker() {
@@ -370,10 +384,16 @@ function createWorker() {
finishRun();
runButton.disabled = true;
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
mv.src = objectURL;
if (objectURL == null) {
if (output.threeMFURL != null) {
URL.revokeObjectURL(output.threeMFURL);
output.threeMFURL = null;
}
URL.revokeObjectURL(output.glbURL);
output.glbURL = e.data.glbURL;
output.threeMFURL = e.data.threeMFURL;
threemfButton.disabled = output.threeMFURL == null;
mv.src = output.glbURL;
if (output.glbURL == null) {
mv.showPoster();
poster.textContent = 'Error';
createWorker();
@@ -411,10 +431,24 @@ runButton.onclick = function() {
}
};
const downloadButton = document.querySelector('#download');
downloadButton.onclick = function() {
const link = document.createElement('a');
link.download = 'manifold.glb';
link.href = objectURL;
link.click();
};
function clickSave(saveButton, filename, outputName) {
const container = saveButton.parentElement;
return () => {
const oldSave = saveContainer.firstElementChild;
if (oldSave !== container) {
saveDropdown.insertBefore(oldSave, saveDropdown.firstElementChild);
saveContainer.insertBefore(container, saveDropdown);
container.appendChild(saveArrow.parentElement);
}
const link = document.createElement('a');
link.download = filename;
link.href = output[outputName];
link.click();
};
}
const glbButton = document.querySelector('#glb');
glbButton.onclick = clickSave(glbButton, 'manifold.glb', 'glbURL');
const threemfButton = document.querySelector('#threemf');
threemfButton.onclick = clickSave(threemfButton, 'manifold.3mf', 'threeMFURL');
+18 -6
View File
@@ -40,15 +40,27 @@
<div class="page">
<div class="container header" style="background-color: #A288B3;">
<p style="flex: 2;"><a target="_blank" href="https://github.com/elalish/manifold">Manifold</a>CAD </p>
<div class="margin" style="position: relative;">
<button type="button" id="file" class="blue"><b class="uparrow"></b>&nbsp;&nbsp;<span
<div class="margin" id="file-margin" >
<button type="button" id="file" class="blue item"><b class="uparrow"></b>&nbsp;&nbsp;<span
id="current">Intro</span></button>
<div class="dropdown">
<div class="dropdown" id="fileDropdown">
<button type="button" class="green item" id="new">New</button>
</div>
</div>
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
<button type="button" id="download" class="blue margin">Save GLB</button>
<div class="container" id="button-container">
<button type="button" id="compile" class="margin green" disabled><span>Run</span></button>
<div id="save" class="margin" style="position: relative; ">
<div class="item">
<button type="button" id="glb" class="blue item">Save GLB</button>
<button type="button" class="icon"><b class="uparrow" style="right: 2px;"></b></button>
</div>
<div class="dropdown" id="saveDropdown">
<div class="item">
<button type="button" id="threemf" class="blue item">Save 3MF</button>
</div>
</div>
</div>
</div>
</div>
<div class="container" style="flex: 2;">
<div id="editor" class="col"></div>
@@ -74,4 +86,4 @@
<script type="module" src="editor.js"></script>
<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>
</html>
</html>
+16 -4
View File
@@ -11,6 +11,8 @@
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"@jscadui/3mf-export": "^0.3.0",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.151.2"
@@ -411,6 +413,11 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jscadui/3mf-export": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@jscadui/3mf-export/-/3mf-export-0.3.0.tgz",
"integrity": "sha512-5NfknQSTO2+rt7m7PGDvIST8V2RNNWBGvQPEmVY2DTMCEZ6y4zyIltxwVbnQ6NRCEeWZseSb3FpmflU1AWE9vw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -581,6 +588,12 @@
"vitest": ">=0.30.1 <1"
}
},
"node_modules/@vitest/ui/node_modules/fflate": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
"dev": true
},
"node_modules/@vitest/utils": {
"version": "0.31.1",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.31.1.tgz",
@@ -1000,10 +1013,9 @@
}
},
"node_modules/fflate": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
"dev": true
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.0.tgz",
"integrity": "sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg=="
},
"node_modules/fill-range": {
"version": "7.0.1",
+2
View File
@@ -16,6 +16,8 @@
"@gltf-transform/core": "^3.2.1",
"@gltf-transform/extensions": "^3.2.1",
"@gltf-transform/functions": "^3.2.1",
"@jscadui/3mf-export": "^0.3.0",
"fflate": "^0.8.0",
"gl-matrix": "^3.4.3",
"simple-dropzone": "0.8.3",
"three": "0.151.2"
+6 -6
View File
@@ -39,7 +39,7 @@ function initialized(worker) {
});
}
let objectURL = null;
let glbURL = null;
async function runExample(name) {
const worker = new ManifoldWorker();
@@ -55,12 +55,12 @@ async function runExample(name) {
worker.onmessage = async function(e) {
try {
URL.revokeObjectURL(objectURL);
objectURL = e.data.objectURL;
if (objectURL == null) {
reject('no objectURL');
URL.revokeObjectURL(glbURL);
glbURL = e.data.glbURL;
if (glbURL == null) {
reject('no glbURL)');
}
const docIn = await io.read(objectURL);
const docIn = await io.read(glbURL);
const nodes = docIn.getRoot().listNodes();
for (const node of nodes) {
const mesh = node.getMesh();
+28 -5
View File
@@ -14,11 +14,14 @@
import {Document, Material, Node, WebIO} from '@gltf-transform/core';
import {KHRMaterialsUnlit, KHRONOS_EXTENSIONS} from '@gltf-transform/extensions';
import {fileForContentTypes, to3dmodel} from '@jscadui/3mf-export';
import {strToU8, Zippable, zipSync} from 'fflate'
import * as glMatrix from 'gl-matrix';
import Module from './built/manifold';
//@ts-ignore
import {setupIO, writeMesh} from './gltf-io';
import type {GLTFMaterial, Quat} from './public/editor';
import type {CrossSection, Manifold, ManifoldToplevel, Mesh, Vec3} from './public/manifold';
@@ -42,23 +45,23 @@ io.registerExtensions(KHRONOS_EXTENSIONS);
// manifold static methods (that return a new manifold)
const manifoldStaticFunctions = [
'cube', 'cylinder', 'sphere', 'tetrahedron', 'extrude', 'revolve', 'compose',
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh'
'union', 'difference', 'intersection', 'levelSet', 'smooth', 'ofMesh', 'hull'
];
// manifold member functions (that return a new manifold)
const manifoldMemberFunctions = [
'add', 'subtract', 'intersect', 'decompose', 'warp', 'transform', 'translate',
'rotate', 'scale', 'mirror', 'refine', 'setProperties', 'asOriginal',
'trimByPlane', 'split', 'splitByPlane'
'trimByPlane', 'split', 'splitByPlane', 'hull'
];
// CrossSection static methods (that return a new cross-section)
const crossSectionStaticFunctions = [
'square', 'circle', 'union', 'difference', 'intersection', 'compose',
'ofPolygons'
'ofPolygons', 'hull'
];
// CrossSection member functions (that return a new cross-section)
const crossSectionMemberFunctions = [
'add', 'subtract', 'intersect', 'rectClip', 'decompose', 'transform',
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset'
'translate', 'rotate', 'scale', 'mirror', 'simplify', 'offset', 'hull'
];
// top level functions that construct a new manifold/mesh
const toplevelConstructors = ['show', 'only', 'setMaterial'];
@@ -457,8 +460,28 @@ async function exportGLB(manifold?: Manifold) {
wrapper.addChild(node);
}
const results: {glbURL?: string, threeMFURL?: string} = {};
if (manifold != null) {
const mesh = manifold.getMesh();
let vertices = new Float32Array(mesh.numVert * 3);
for (let i = 0; i < mesh.numVert; ++i) {
for (let j = 0; j < 3; ++j)
vertices[i * 3 + j] = mesh.vertProperties[i * mesh.numProp + j];
}
const model =
to3dmodel({simple: [{vertices, indices: mesh.triVerts, id: '1'}]});
const files: Zippable = {};
files['3D/3dmodel.model'] = strToU8(model);
files[fileForContentTypes.name] = strToU8(fileForContentTypes.content);
const zipFile = zipSync(files);
results.threeMFURL = URL.createObjectURL(new Blob(
[zipFile],
{type: 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml'}));
}
const glb = await io.writeBinary(doc);
const blob = new Blob([glb], {type: 'application/octet-stream'});
self.postMessage({objectURL: URL.createObjectURL(blob)});
results.glbURL = URL.createObjectURL(blob);
self.postMessage(results);
}
+10
View File
@@ -159,6 +159,11 @@ CrossSection Offset(CrossSection& cross_section, double delta, int join_type,
: CrossSection::JoinType::Miter;
return cross_section.Offset(delta, jt, miter_limit, arc_tolerance);
}
void CollectVertices(std::vector<glm::vec2>& verts, const CrossSection& cs) {
auto polys = cs.ToPolygons();
for (auto poly : polys) verts.insert(verts.end(), poly.begin(), poly.end());
}
} // namespace cross_js
namespace man_js {
@@ -219,4 +224,9 @@ std::vector<Manifold> SplitByPlane(Manifold& m, glm::vec3 normal,
auto [a, b] = m.SplitByPlane(normal, originOffset);
return {a, b};
}
void CollectVertices(std::vector<glm::vec3>& verts, const Manifold& manifold) {
Mesh mesh = manifold.GetMesh();
verts.insert(verts.end(), mesh.vertPos.begin(), mesh.vertPos.end());
}
} // namespace man_js
+28 -5
View File
@@ -111,6 +111,7 @@ export class CrossSection {
* @param v The vector to add to every vertex.
*/
translate(v: Vec2): CrossSection;
translate(x: number, y?: number): CrossSection;
/**
* Applies a (Z-axis) rotation to the CrossSection, in degrees. This operation
@@ -173,11 +174,6 @@ export class CrossSection {
delta: number, joinType?: JoinType, miterLimit?: number,
circularSegments?: number): CrossSection;
/**
* Compute the convex hull of the contours in this CrossSection.
*/
hull(): CrossSection;
/**
* Remove vertices from the contours in this CrossSection that are less than
* the specified distance epsilon from an imaginary line that passes through
@@ -246,6 +242,18 @@ export class CrossSection {
*/
static intersection(polygons: (CrossSection|Polygons)[]): CrossSection;
// Convex Hulls
/**
* Compute the convex hull of the contours in this CrossSection.
*/
hull(): CrossSection;
/**
* Compute the convex hull of all points in a list of polygons/cross-sections.
*/
static hull(polygons: (CrossSection|Polygons)[]): CrossSection;
// Topological Operations
/**
@@ -507,6 +515,7 @@ export class Manifold {
* @param v The vector to add to every vertex.
*/
translate(v: Vec3): Manifold;
translate(x: number, y?: number, z?: number): Manifold;
/**
* Applies an Euler angle rotation to the manifold, first about the X axis,
@@ -519,6 +528,7 @@ export class Manifold {
* @param v [X, Y, Z] rotation in degrees.
*/
rotate(v: Vec3): Manifold;
rotate(x: number, y?: number, z?: number): Manifold;
/**
* Scale this Manifold in space. This operation can be chained. Transforms are
@@ -675,6 +685,19 @@ export class Manifold {
*/
trimByPlane(normal: Vec3, originOffset: number): Manifold;
// Convex Hulls
/**
* Compute the convex hull of all points in this Manifold.
*/
hull(): Manifold;
/**
* Compute the convex hull of all points contained within a set of Manifolds
* and point vectors.
*/
static hull(points: (Manifold|Vec3)[]): Manifold;
// Topological Operations
/**
+2 -1
View File
@@ -287,7 +287,8 @@ SparseIndices Collider::Collisions(const VecDH<T>& queriesIn) const {
{thrust::pair<int*, int*>(nullptr, nullptr), counts.ptrD(),
nodeBBox_.ptrD(), internalChildren_.ptrD()}));
// compute start index for each query and total count
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin());
exclusive_scan(policy, counts.begin(), counts.end(), counts.begin(), 0,
std::plus<int>());
SparseIndices queryTri(counts.back());
// actually recording collisions
for_each_n(policy, zip(queriesIn.cbegin(), countAt(0)), queriesIn.size(),
+23
View File
@@ -612,6 +612,12 @@ CrossSection CrossSection::Offset(double delta, JoinType jointype,
return CrossSection(ps);
}
/**
* Compute the convex hull enveloping a set of cross-sections.
*
* @param crossSections A vector of cross-sections over which to compute a
* convex hull.
*/
CrossSection CrossSection::Hull(
const std::vector<CrossSection>& crossSections) {
int n = 0;
@@ -629,14 +635,31 @@ CrossSection CrossSection::Hull(
return CrossSection(C2::PathsD{HullImpl(pts)});
}
/**
* Compute the convex hull of this cross-section.
*/
CrossSection CrossSection::Hull() const {
return Hull(std::vector<CrossSection>{*this});
}
/**
* Compute the convex hull of a set of points. If the given points are fewer
* than 3, an empty CrossSection will be returned.
*
* @param pts A vector of 2-dimensional points over which to compute a convex
* hull.
*/
CrossSection CrossSection::Hull(SimplePolygon pts) {
return CrossSection(C2::PathsD{HullImpl(pts)});
}
/**
* Compute the convex hull of a set of points/polygons. If the given points are
* fewer than 3, an empty CrossSection will be returned.
*
* @param pts A vector of vectors of 2-dimensional points over which to compute
* a convex hull.
*/
CrossSection CrossSection::Hull(const Polygons polys) {
SimplePolygon pts;
for (auto poly : polys) {
+20 -8
View File
@@ -222,9 +222,12 @@ void Manifold::Impl::SimplifyTopology() {
precision_};
for_each_n(policy, countAt(0), nbEdges,
[=] __host__ __device__(int i) { bflagsPtr[i] = se(i); });
std::vector<int> visited(halfedge_.size(), -1);
int tag = 0;
for (int i = 0; i < nbEdges; ++i) {
if (bflags[i]) {
RecursiveEdgeSwap(i);
tag++;
RecursiveEdgeSwap(i, visited, tag);
numFlagged++;
}
}
@@ -483,13 +486,17 @@ void Manifold::Impl::CollapseEdge(const int edge) {
RemoveIfFolded(start);
}
void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
void Manifold::Impl::RecursiveEdgeSwap(const int edge,
std::vector<int>& visited, int& tag) {
VecDH<TriRef>& triRef = meshRelation_.triRef;
if (edge < 0) return;
const int pair = halfedge_[edge].pairedHalfedge;
if (pair < 0) return;
// avoid infinite recursion
if (visited[edge] == tag && visited[pair] == tag) return;
const glm::ivec3 tri0edge = TriOf(edge);
const glm::ivec3 tri1edge = TriOf(pair);
const glm::ivec3 perm0 = TriOf(edge % 3);
@@ -568,12 +575,15 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
SwapEdge();
const glm::vec2 e23 = v[3] - v[2];
if (glm::dot(e23, e23) < precision_ * precision_) {
tag++;
CollapseEdge(tri0edge[2]);
} else {
RecursiveEdgeSwap(tri0edge[0]);
RecursiveEdgeSwap(tri0edge[1]);
RecursiveEdgeSwap(tri1edge[0]);
RecursiveEdgeSwap(tri1edge[1]);
visited[edge] = tag;
visited[pair] = tag;
RecursiveEdgeSwap(tri0edge[0], visited, tag);
RecursiveEdgeSwap(tri0edge[1], visited, tag);
RecursiveEdgeSwap(tri1edge[0], visited, tag);
RecursiveEdgeSwap(tri1edge[1], visited, tag);
}
return;
} else if (CCW(v[0], v[3], v[2], precision_) <= 0 ||
@@ -582,8 +592,10 @@ void Manifold::Impl::RecursiveEdgeSwap(const int edge) {
}
// Normal path
SwapEdge();
RecursiveEdgeSwap(halfedge_[tri0edge[1]].pairedHalfedge);
RecursiveEdgeSwap(halfedge_[tri1edge[0]].pairedHalfedge);
visited[edge] = tag;
visited[pair] = tag;
RecursiveEdgeSwap(halfedge_[tri0edge[1]].pairedHalfedge, visited, tag);
RecursiveEdgeSwap(halfedge_[tri1edge[0]].pairedHalfedge, visited, tag);
}
void Manifold::Impl::SplitPinchedVerts() {
+1 -1
View File
@@ -121,7 +121,7 @@ struct Manifold::Impl {
void SimplifyTopology();
void DedupeEdge(int edge);
void CollapseEdge(int edge);
void RecursiveEdgeSwap(int edge);
void RecursiveEdgeSwap(int edge, std::vector<int>& visited, int& tag);
void RemoveIfFolded(int edge);
void PairUp(int edge0, int edge1);
void UpdateVert(int vert, int startEdge, int endEdge);
+42 -18
View File
@@ -15,8 +15,14 @@
#include "polygon.h"
#include <algorithm>
#if MANIFOLD_PAR == 'T'
#include <execution>
#endif
#include <list>
#include <map>
#if __has_include(<memory_resource>)
#include <memory_resource>
#endif
#include <queue>
#include <set>
#include <stack>
@@ -303,14 +309,25 @@ class Monotones {
private:
struct VertAdj;
typedef std::list<VertAdj>::iterator VertItr;
struct EdgePair;
typedef std::list<EdgePair>::iterator PairItr;
enum VertType { Start, WestSide, EastSide, Merge, End, Skip };
#if __has_include(<memory_resource>)
typedef std::pmr::list<VertAdj>::iterator VertItr;
typedef std::pmr::list<EdgePair>::iterator PairItr;
std::list<VertAdj> monotones_; // sweep-line list of verts
std::list<EdgePair> activePairs_; // west to east list of monotone edge pairs
std::pmr::monotonic_buffer_resource mbr;
std::pmr::polymorphic_allocator<int> pa{&mbr};
std::pmr::list<VertAdj> monotones_{pa}; // sweep-line list of verts
std::pmr::list<EdgePair> activePairs_{pa}; // west to east monotone edges
std::pmr::list<EdgePair> inactivePairs_{pa}; // completed monotones
#else
typedef std::list<VertAdj>::iterator VertItr;
typedef std::list<EdgePair>::iterator PairItr;
std::list<VertAdj> monotones_; // sweep-line list of verts
std::list<EdgePair> activePairs_; // west to east monotone edges
std::list<EdgePair> inactivePairs_; // completed monotones
#endif
float precision_; // a triangle of this height or less is degenerate
/**
@@ -392,7 +409,7 @@ class Monotones {
class Triangulator {
public:
Triangulator(VertItr vert, float precision) : precision_(precision) {
reflex_chain_.push(vert);
reflex_chain_.push_back(vert);
other_side_ = vert;
}
int NumTriangles() const { return triangles_output_; }
@@ -407,14 +424,14 @@ class Monotones {
*/
void ProcessVert(const VertItr vi, bool onRight, bool last,
std::vector<glm::ivec3> &triangles) {
VertItr v_top = reflex_chain_.top();
VertItr v_top = reflex_chain_.back();
if (reflex_chain_.size() < 2) {
reflex_chain_.push(vi);
reflex_chain_.push_back(vi);
onRight_ = onRight;
return;
}
reflex_chain_.pop();
VertItr vj = reflex_chain_.top();
reflex_chain_.pop_back();
VertItr vj = reflex_chain_.back();
if (onRight_ == onRight && !last) {
// This only creates enough triangles to ensure the reflex chain is
// still reflex.
@@ -423,13 +440,13 @@ class Monotones {
while (ccw == (onRight_ ? 1 : -1) || ccw == 0) {
AddTriangle(triangles, vi, vj, v_top);
v_top = vj;
reflex_chain_.pop();
reflex_chain_.pop_back();
if (reflex_chain_.empty()) break;
vj = reflex_chain_.top();
vj = reflex_chain_.back();
ccw = CCW(vi->pos, vj->pos, v_top->pos, precision_);
}
reflex_chain_.push(v_top);
reflex_chain_.push(vi);
reflex_chain_.push_back(v_top);
reflex_chain_.push_back(vi);
} else {
// This branch empties the reflex chain and switches sides. It must be
// used for the last point, as it will output all the triangles
@@ -438,19 +455,19 @@ class Monotones {
onRight_ = !onRight_;
VertItr v_last = v_top;
while (!reflex_chain_.empty()) {
vj = reflex_chain_.top();
vj = reflex_chain_.back();
AddTriangle(triangles, vi, v_last, vj);
v_last = vj;
reflex_chain_.pop();
reflex_chain_.pop_back();
}
reflex_chain_.push(v_top);
reflex_chain_.push(vi);
reflex_chain_.push_back(v_top);
reflex_chain_.push_back(vi);
other_side_ = v_top;
}
}
private:
std::stack<VertItr> reflex_chain_;
std::vector<VertItr> reflex_chain_;
VertItr other_side_; // The end vertex across from the reflex chain
bool onRight_; // The side the reflex chain is on
int triangles_output_ = 0;
@@ -784,7 +801,11 @@ class Monotones {
starts.push_back(v);
}
}
#if MANIFOLD_PAR == 'T' && !(__APPLE__)
std::sort(std::execution::par_unseq, starts.begin(), starts.end(), cmp);
#else
std::sort(starts.begin(), starts.end(), cmp);
#endif
std::vector<VertItr> skipped;
VertItr insertAt = monotones_.begin();
@@ -1053,6 +1074,9 @@ std::vector<glm::ivec3> TriangulateIdx(const PolygonsIdx &polys,
Monotones monotones(polys, precision);
monotones.Triangulate(triangles);
#ifdef MANIFOLD_DEBUG
std::cout << "in triangulator: "
<< (PolygonParams().intermediateChecks ? "true" : "false")
<< std::endl;
if (params.intermediateChecks) {
CheckTopology(triangles, polys);
if (!params.processOverlaps) {
+83 -18
View File
@@ -30,6 +30,9 @@
#define MANIFOLD_PAR_NS omp
#elif MANIFOLD_PAR == 'T'
#include <thrust/system/tbb/execution_policy.h>
#include <algorithm>
#include <execution>
#define MANIFOLD_PAR_NS tbb
#else
#define MANIFOLD_PAR_NS cpp
@@ -136,6 +139,70 @@ inline ExecutionPolicy autoPolicy(int size) {
thrust::NAME(thrust::cpp::par, args...); \
}
#if MANIFOLD_PAR == 'T' && !(__APPLE__)
// sometimes stl variant is faster
#define STL_DYNAMIC_BACKEND(NAME, RET) \
template <typename Ret = RET, typename... Args> \
Ret NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
case ExecutionPolicy::Par: \
return std::NAME(std::execution::par_unseq, args...); \
case ExecutionPolicy::Seq: \
break; \
} \
return std::NAME(args...); \
}
#define STL_DYNAMIC_BACKEND_VOID(NAME) \
template <typename... Args> \
void NAME(ExecutionPolicy policy, Args... args) { \
switch (policy) { \
case ExecutionPolicy::ParUnseq: \
case ExecutionPolicy::Par: \
std::NAME(std::execution::par_unseq, args...); \
break; \
case ExecutionPolicy::Seq: \
std::NAME(args...); \
break; \
} \
}
template <typename... Args>
void exclusive_scan(ExecutionPolicy policy, Args... args) {
// https://github.com/llvm/llvm-project/issues/59810
std::exclusive_scan(args...);
}
template <typename DerivedPolicy, typename InputIterator1,
typename InputIterator2, typename OutputIterator, typename Predicate>
OutputIterator copy_if(ExecutionPolicy policy, InputIterator1 first,
InputIterator1 last, InputIterator2 stencil,
OutputIterator result, Predicate pred) {
if (policy == ExecutionPolicy::Seq)
return thrust::copy_if(thrust::cpp::par, first, last, stencil, result,
pred);
else
// note: this is not a typo, see
// https://github.com/NVIDIA/thrust/issues/1977
return thrust::copy_if(first, last, stencil, result, pred);
}
template <typename DerivedPolicy, typename InputIterator1,
typename OutputIterator, typename Predicate>
OutputIterator copy_if(ExecutionPolicy policy, InputIterator1 first,
InputIterator1 last, OutputIterator result,
Predicate pred) {
if (policy == ExecutionPolicy::Seq)
return std::copy_if(first, last, result, pred);
else
return std::copy_if(std::execution::par_unseq, first, last, result, pred);
}
#else
#define STL_DYNAMIC_BACKEND(NAME, RET) THRUST_DYNAMIC_BACKEND(NAME, RET)
#define STL_DYNAMIC_BACKEND_VOID(NAME) THRUST_DYNAMIC_BACKEND_VOID(NAME)
THRUST_DYNAMIC_BACKEND_VOID(exclusive_scan)
THRUST_DYNAMIC_BACKEND(copy_if, void)
#endif
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each)
THRUST_DYNAMIC_BACKEND_HOST_VOID(for_each_n)
@@ -143,36 +210,34 @@ THRUST_DYNAMIC_BACKEND_VOID(gather)
THRUST_DYNAMIC_BACKEND_VOID(scatter)
THRUST_DYNAMIC_BACKEND_VOID(for_each)
THRUST_DYNAMIC_BACKEND_VOID(for_each_n)
THRUST_DYNAMIC_BACKEND_VOID(sort)
THRUST_DYNAMIC_BACKEND_VOID(stable_sort)
THRUST_DYNAMIC_BACKEND_VOID(fill)
THRUST_DYNAMIC_BACKEND_VOID(sequence)
THRUST_DYNAMIC_BACKEND_VOID(sort_by_key)
THRUST_DYNAMIC_BACKEND_VOID(stable_sort_by_key)
THRUST_DYNAMIC_BACKEND_VOID(copy)
THRUST_DYNAMIC_BACKEND_VOID(transform)
THRUST_DYNAMIC_BACKEND_VOID(inclusive_scan)
THRUST_DYNAMIC_BACKEND_VOID(exclusive_scan)
THRUST_DYNAMIC_BACKEND_VOID(uninitialized_fill)
THRUST_DYNAMIC_BACKEND_VOID(uninitialized_copy)
THRUST_DYNAMIC_BACKEND_VOID(stable_sort)
THRUST_DYNAMIC_BACKEND_VOID(fill)
THRUST_DYNAMIC_BACKEND_VOID(copy)
THRUST_DYNAMIC_BACKEND_VOID(inclusive_scan)
THRUST_DYNAMIC_BACKEND_VOID(copy_n)
STL_DYNAMIC_BACKEND_VOID(sort)
// void implies that the user have to specify the return type in the template
// argument, as we are unable to deduce it
THRUST_DYNAMIC_BACKEND(transform_reduce, void)
THRUST_DYNAMIC_BACKEND(gather_if, void)
THRUST_DYNAMIC_BACKEND(reduce_by_key, void)
THRUST_DYNAMIC_BACKEND(lower_bound, void)
THRUST_DYNAMIC_BACKEND(remove, void)
THRUST_DYNAMIC_BACKEND(find, void)
THRUST_DYNAMIC_BACKEND(find_if, void)
THRUST_DYNAMIC_BACKEND(all_of, bool)
THRUST_DYNAMIC_BACKEND(is_sorted, bool)
THRUST_DYNAMIC_BACKEND(reduce, void)
THRUST_DYNAMIC_BACKEND(count_if, int)
THRUST_DYNAMIC_BACKEND(binary_search, bool)
// void implies that the user have to specify the return type in the template
// argument, as we are unable to deduce it
THRUST_DYNAMIC_BACKEND(remove, void)
THRUST_DYNAMIC_BACKEND(copy_if, void)
THRUST_DYNAMIC_BACKEND(remove_if, void)
THRUST_DYNAMIC_BACKEND(unique, void)
THRUST_DYNAMIC_BACKEND(find, void)
THRUST_DYNAMIC_BACKEND(find_if, void)
THRUST_DYNAMIC_BACKEND(reduce_by_key, void)
THRUST_DYNAMIC_BACKEND(transform_reduce, void)
THRUST_DYNAMIC_BACKEND(lower_bound, void)
THRUST_DYNAMIC_BACKEND(gather_if, void)
STL_DYNAMIC_BACKEND(remove_if, void)
STL_DYNAMIC_BACKEND(unique, void)
} // namespace manifold
+1 -1
View File
@@ -106,7 +106,7 @@ inline HOST_DEVICE int CCW(glm::vec2 p0, glm::vec2 p1, glm::vec2 p2,
glm::vec2 v2 = p2 - p0;
float area = v1.x * v2.y - v1.y * v2.x;
float base2 = glm::max(glm::dot(v1, v1), glm::dot(v2, v2));
if (area * area <= base2 * tol * tol)
if (area * area * 4 <= base2 * tol * tol)
return 0;
else
return area > 0 ? 1 : -1;
+4
View File
@@ -26,6 +26,8 @@ using namespace manifold;
* The very simplest Boolean operation test.
*/
TEST(Boolean, Tetra) {
std::cout << (PolygonParams().intermediateChecks ? "true" : "false")
<< std::endl;
Manifold tetra = Manifold::Tetrahedron();
MeshGL tetraGL = WithPositionColors(tetra);
tetra = Manifold(tetraGL);
@@ -37,6 +39,8 @@ TEST(Boolean, Tetra) {
ExpectMeshes(result, {{8, 12, 3, 11}});
RelatedGL(result, {tetraGL});
std::cout << (PolygonParams().intermediateChecks ? "true" : "false")
<< std::endl;
}
TEST(Boolean, MeshGLRoundTrip) {
Binary file not shown.
Binary file not shown.
+17
View File
@@ -301,6 +301,23 @@ TEST(Polygon, ColinearY) {
TestPoly(polys, 16);
}
TEST(Polygon, NearlyColinearY) {
Polygons polys;
polys.push_back({
{3.5, 0}, //
{3.0, 0.5}, //
{3.0, 0.0144}, //
{2.0, 0.4}, //
{1.8, 0.1}, //
{1.6, 0.0155}, //
{1.5, 0.0147}, //
{1.4, 0.0144}, //
{1.0, 0.1}, //
{0, 0}, //
});
TestPoly(polys, 8, 0.001);
}
TEST(Polygon, Concave) {
Polygons polys;
polys.push_back({
+13
View File
@@ -255,3 +255,16 @@ TEST(Samples, Sponge4) {
#endif
}
#endif
#ifdef MANIFOLD_EXPORT
TEST(Samples, SelfIntersect) {
manifold::PolygonParams().processOverlaps = true;
std::string file = __FILE__;
std::string dir = file.substr(0, file.rfind('/'));
Manifold m1 = ImportMesh(dir + "/models/self_intersectA.glb");
Manifold m2 = ImportMesh(dir + "/models/self_intersectB.glb");
Manifold res = m1 + m2;
res.GetMeshGL(); // test crash
manifold::PolygonParams().processOverlaps = false;
}
#endif
+1
View File
@@ -65,6 +65,7 @@ int main(int argc, char** argv) {
}
manifold::PolygonParams().intermediateChecks = true;
manifold::PolygonParams().processOverlaps = false;
return RUN_ALL_TESTS();