diff --git a/include/csg_types.hpp b/include/csg_types.hpp index 5d16bda517930ed9887cb9b3a3c2f89bacd6ad71..305e29cee98313387963828d6ab3cc2421133272 100644 --- a/include/csg_types.hpp +++ b/include/csg_types.hpp @@ -106,6 +106,7 @@ template struct Intersection; template struct Difference; struct LinearExtrude; struct RotateExtrude; +struct Hull; template struct TypeHelper; @@ -120,7 +121,7 @@ template <> struct TypeHelper { using Type = std::variant, Intersection, Difference, Mulmatrix, - LinearExtrude, RotateExtrude, Polyhedron>; + LinearExtrude, RotateExtrude, Polyhedron, Hull>; }; template struct Union { @@ -193,6 +194,34 @@ struct RotateExtrude { Union group; }; +struct Hull { +private: + std::shared_ptr m_cgal_polyhedron; + std::shared_ptr m_cgal_aabb_tree; + +public: + std::optional name; + + //TODO: Extend support beyond just sphere & cube + std::tuple cube_size; + std::array cube_center; + double sphere_radius; + + void generate_polyhedron() { + m_cgal_polyhedron = cgal_helper::create_polyhedron_from_hull( + cube_size, cube_center, sphere_radius); + m_cgal_aabb_tree = cgal_helper::create_aabb_tree(m_cgal_polyhedron); + } + + const std::shared_ptr &cgal_polyhedron() const { + return m_cgal_polyhedron; + } + + const std::shared_ptr &cgal_aabb_tree() const { + return m_cgal_aabb_tree; + } +}; + struct Tree { Union top; }; diff --git a/src/csg/cgal_helper_polyhedron.cpp b/src/csg/cgal_helper_polyhedron.cpp index f4c79a13fa6afd761fd8e1becee499ef7ea994c2..1fa87b37084f75f9d77da158f9a0b9d55ac1ac46 100644 --- a/src/csg/cgal_helper_polyhedron.cpp +++ b/src/csg/cgal_helper_polyhedron.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace { @@ -96,4 +98,41 @@ bool inside(const std::shared_ptr &tree, double xx, double yy, return inside_tester(pt) == CGAL::ON_BOUNDED_SIDE; } +std::shared_ptr +create_polyhedron_from_hull(const std::tuple &cube_size, + const std::array &cube_center, + double sphere_radius) { + + // This is currently chosen somewhat arbitrarily + // based on manual testing and CGAL examples + // TODO: Is this sufficient? + const int NUM_SAMPLING_PTS = 250; + + auto p = std::make_shared(); + + std::list points; + CGAL::Random_points_on_sphere_3 s(sphere_radius); + std::copy_n(s, NUM_SAMPLING_PTS, std::back_inserter(points)); + + CGAL::Random_points_in_cube_3 c(1.0); + std::list points_c; + std::copy_n(c, NUM_SAMPLING_PTS, std::back_inserter(points_c)); + + auto [sa, sb, sc] = cube_size; + + const cgal_helper::CK::Aff_transformation_3 transf( + sa / 2, 0.0, 0.0, cube_center[0], 0.0, sb / 2, 0.0, cube_center[1], 0.0, + 0.0, sc / 2, cube_center[2]); + std::transform(points_c.begin(), points_c.end(), points_c.begin(), transf); + + points.insert(points.end(), points_c.begin(), points_c.end()); + + // compute convex hull of non-collinear points + CGAL::convex_hull_3(points.begin(), points.end(), *p); + + CGAL_assertion(p->is_valid()); + + return p; +} + } // namespace cgal_helper diff --git a/src/csg/levelset_3d.cpp b/src/csg/levelset_3d.cpp index c2b5eeebf26793e9dfb61b55f82ba9c337e2dad9..75343ddec72a806210037703f5a895afb1c88b29 100644 --- a/src/csg/levelset_3d.cpp +++ b/src/csg/levelset_3d.cpp @@ -30,6 +30,7 @@ double signed_distance_3d(const Type3D &, double, double, double); double signed_distance_3d(const LinearExtrude &, double, double, double); double signed_distance_3d(const RotateExtrude &, double, double, double); double signed_distance_3d(const Polyhedron &, double, double, double); +double signed_distance_3d(const Hull &, double, double, double); double signed_distance_2d(const Union2D &, double, double); @@ -170,6 +171,11 @@ double signed_distance_3d(const Polyhedron &polyhedron, double xx, double yy, : -1.0; } +double signed_distance_3d(const Hull &hull, double xx, double yy, double zz) { + // TODO: support signed distance instead of -1.0/1.0 + return cgal_helper::inside(hull.cgal_aabb_tree(), xx, yy, zz) ? 1.0 : -1.0; +} + double signed_distance_3d(const Type3D &obj, double xx, double yy, double zz) { return std::visit( diff --git a/src/csg/parser.cpp b/src/csg/parser.cpp index fca91f2f4bd32ce458a70e66852e73b01c7b40c2..e05e05f74a2eb464fe1d2765a91e9ae11a66fd9b 100644 --- a/src/csg/parser.cpp +++ b/src/csg/parser.cpp @@ -171,6 +171,10 @@ struct extrude_lin : seq, obj_list, R_BLK> {}; +struct hull + : seq, L_FUN, R_FUN, L_BLK, + obj_list, R_BLK> {}; + struct render : seq, L_FUN, attr_list, R_FUN, opt, obj_list, @@ -184,7 +188,7 @@ struct colorgroup : seq, L_FUN, vector, R_FUN, template struct csg_obj : sor, mulmat, bool_exp, group, - extrude_lin, extrude_rot, render, colorgroup> {}; + extrude_lin, extrude_rot, hull, render, colorgroup> {}; template struct obj_list : plus, space>> {}; @@ -485,6 +489,43 @@ template <> struct action { } }; +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + std::stringstream ss(in.string()); + csg::Hull hull; + + // Only a very limited hull is currently supported + if (st.current_3d_group.size() == 2) { + auto &obj0 = st.current_3d_group[0]; + auto &obj1 = st.current_3d_group[1]; + if (std::holds_alternative(obj0) and + std::holds_alternative(obj1)) { + auto &mm = std::get(obj0); + if (mm.group.objs.size() == 1 and + std::holds_alternative(mm.group.objs[0])) { + auto &cub = std::get(mm.group.objs[0]); + if (cub.center) { + hull.cube_size = cub.size; + hull.cube_center = mm.translation; + hull.sphere_radius = std::get(obj1).radius; + hull.generate_polyhedron(); + st.current_3d_objs.back().push_back(hull); + return; + } + } + } + } + + // If the conditions were not met and throw exception + std::string except_src = "action"; + std::string except_msg = + "hull() support is limited to a multmatrix of a centered cube followed " + "by a sphere; added for the CLR support"; + throw csg::Exception(except_src, except_msg); + } +}; + std::optional get_name(AttrMap curr_attr) { if (curr_attr.count("$name")) { auto quoted_name = std::get(curr_attr["$name"]); diff --git a/src/csg/tests/CMakeLists.txt b/src/csg/tests/CMakeLists.txt index 94b3fb782be902fdb72815a2f63660689f54d3d1..7450fc8d4fe82813bb1d381ef8270e4ff0f90e61 100644 --- a/src/csg/tests/CMakeLists.txt +++ b/src/csg/tests/CMakeLists.txt @@ -5,11 +5,13 @@ add_executable(unit_tests_csg EXCLUDE_FROM_ALL levelset/boolean.t.cpp levelset/extrude.t.cpp + levelset/hull.t.cpp levelset/internal_flow.t.cpp levelset/primitives.t.cpp levelset/transform.t.cpp parser/boolean.t.cpp parser/extrude.t.cpp + parser/hull.t.cpp parser/nest.cpp parser/other.t.cpp parser/primitives.t.cpp diff --git a/src/csg/tests/levelset/hull.t.cpp b/src/csg/tests/levelset/hull.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7909907ec9d891a21f459c6b64cbe8d6e63e2d3d --- /dev/null +++ b/src/csg/tests/levelset/hull.t.cpp @@ -0,0 +1,34 @@ +#include "catch2/catch.hpp" + +#include +#include + +namespace { + +TEST_CASE("hull of a sphere and translated cube", "[Levelset Hull]") { + csg::Hull my_hull; + + double XX = 1.0, YY = 2.0, ZZ = 3.0, Cx = 5.0, Cy = 5.0, Cz = 6.0, R = 1.0; + + my_hull.cube_size = {XX, YY, ZZ}; + my_hull.cube_center = {Cx, Cy, Cz}; + my_hull.sphere_radius = R; + my_hull.generate_polyhedron(); + + auto my_tree = std::make_shared(); + my_tree->top.objs.push_back(my_hull); + csg::CsgIF my_levelset(my_tree); + + SECTION("Outside") { + CHECK_FALSE(0 < my_levelset(0, 0, Cz)); + CHECK_FALSE(0 < my_levelset(1.3 * R, 0, 0)); + CHECK_FALSE(0 < my_levelset(Cx + 1.1 * XX / 2, Cy, Cz)); + } + SECTION("Inside") { + CHECK(0 < my_levelset(0, 0, 0)); + CHECK(0 < my_levelset(Cx, Cy, Cz)); + CHECK(0 < my_levelset(1.1 * R, 0, 0)); + } +} + +} // namespace diff --git a/src/csg/tests/parser/hull.t.cpp b/src/csg/tests/parser/hull.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..615f01b8cd8f1cfd3a89f8356b2b738ea82efd1c --- /dev/null +++ b/src/csg/tests/parser/hull.t.cpp @@ -0,0 +1,67 @@ +#include "catch2/catch.hpp" + +#include +#include +#include + +namespace { +TEST_CASE("hull of translated, centered cube and a sphere", "[csg]") { + auto st = *csg::parse_csg(R"( +hull() { + multmatrix([[1, 0, 0, 5], [0, 1, 0, 6], [0, 0, 1, 7], [0, 0, 0, 1]]) { + cube(size = [1, 2, 3], center = true); + } + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); +} +)"); + auto hull = std::get(st.top.objs.back()); + auto [XX, YY, ZZ] = hull.cube_size; + auto [Cx, Cy, Cz] = hull.cube_center; + + CHECK(XX == 1); + CHECK(YY == 2); + CHECK(ZZ == 3); + + CHECK(Cx == 5); + CHECK(Cy == 6); + CHECK(Cz == 7); + + CHECK(hull.sphere_radius == 4); +} + +TEST_CASE("hull with non-centered cube not supported", "[csg]") { + CHECK_THROWS_WITH(csg::parse_csg(R"( +hull() { + multmatrix([[1, 0, 0, 5], [0, 1, 0, 6], [0, 0, 1, 7], [0, 0, 0, 1]]) { + cube(size = [1, 2, 3], center = false); + } + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); +} +)"), + Catch::Contains("action")); +} + +TEST_CASE("hull with three elements not supported", "[csg]") { + CHECK_THROWS_WITH(csg::parse_csg(R"( +hull() { + multmatrix([[1, 0, 0, 5], [0, 1, 0, 6], [0, 0, 1, 7], [0, 0, 0, 1]]) { + cube(size = [1, 2, 3], center = true); + } + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); +} +)"), + Catch::Contains("action")); +} + +TEST_CASE("hull with other shape combos not supported", "[csg]") { + CHECK_THROWS_WITH(csg::parse_csg(R"( +hull() { + cube(size = [1, 2, 3], center = true); + sphere($fn = 0, $fa = 12, $fs = 2, r = 4); +} +)"), + Catch::Contains("action")); +} + +} // namespace diff --git a/src/csg_cgal_helper.hpp b/src/csg_cgal_helper.hpp index d437d5104b68641984a259c54fcfad46a2998acf..7dbded38cc30f0af60b62b2b54778f431f83a8ca 100644 --- a/src/csg_cgal_helper.hpp +++ b/src/csg_cgal_helper.hpp @@ -41,6 +41,11 @@ bool inside(const Polygon &polygon, double xx, double yy); double abs_max_distance(const Polygon &ploygon, double xx, double yy, bool inside); +std::shared_ptr +create_polyhedron_from_hull(const std::tuple &cube_size, + const std::array &cube_center, + double sphere_radius); + } // namespace cgal_helper #endif