diff --git a/CMakeLists.txt b/CMakeLists.txt index 260872909c609ab96aaf14d3ea460493d6e4d484..0fa1f06bbcd83e28cc6b1d0c7a8a256eee7c06e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,8 @@ add_library(csg) target_sources(csg PRIVATE src/csg/impl/parser.cpp - src/csg/impl/levelset.cpp + src/csg/impl/levelset_3d.cpp + src/csg/impl/levelset_2d.cpp src/csg/impl/csg.cpp ) diff --git a/src/csg/impl/csg.cpp b/src/csg/impl/csg.cpp index 7ea6c38ee30693a6c0321b2bea4b36fda775f0eb..0ade29d5614be9f31c1ac6906a96d29b5b3e8eb0 100644 --- a/src/csg/impl/csg.cpp +++ b/src/csg/impl/csg.cpp @@ -37,7 +37,7 @@ class CsgIF::Impl { public: double call_signed_distance(const std::shared_ptr &a_tree, double xx, double yy, double zz) const { - return signed_distance(a_tree->top, xx, yy, zz); + return signed_distance_3d(a_tree->top, xx, yy, zz); } }; diff --git a/src/csg/impl/csg_types.hpp b/src/csg/impl/csg_types.hpp index e70f97bf6adb93e1b9122e1453d8fd1fd19e24be..448ad8d05b117b1cf5d7613be0074a6f0522e87b 100644 --- a/src/csg/impl/csg_types.hpp +++ b/src/csg/impl/csg_types.hpp @@ -9,6 +9,15 @@ namespace csg { +struct Circle { + double radius; +}; + +struct Square { + std::tuple size; + bool center; +}; + struct Sphere { double radius; }; @@ -31,38 +40,87 @@ struct Cone { bool center; }; -struct Mulmatrix; -struct Union; -struct Intersection; -struct Difference; +enum Dimension { D2, D3 }; + +template struct Mulmatrix; +template struct Union; +template struct Intersection; +template struct Difference; +struct LinearExtrude; +struct RotateExtrude; -using Type = std::variant; +template struct TypeHelper; -struct Union { - std::vector objs; +template <> struct TypeHelper { + using Type = + std::variant, + Intersection, Difference, + Mulmatrix>; }; -struct Intersection { - std::vector objs; +template <> struct TypeHelper { + using Type = + std::variant, + Intersection, Difference, + Mulmatrix, LinearExtrude, RotateExtrude>; }; -struct Difference { - std::shared_ptr first_obj; - Union next_objs; +template struct Union { + std::vector::Type> objs; }; -struct Mulmatrix { +template struct Intersection { + std::vector::Type> objs; +}; + +template struct Difference { + std::shared_ptr::Type> first_obj; + Union next_objs; +}; + +template <> struct Mulmatrix { std::array, 3> rotation; std::array translation; - Union group; + Union group; +}; + +template <> struct Mulmatrix { + std::array, 2> rotation; + std::array translation; + Union group; +}; + +struct LinearExtrude { + double height; + bool center; + double twist; + Union group; +}; + +struct RotateExtrude { + double angle; + Union group; }; struct Tree { - Union top; + Union top; }; -double signed_distance(const Union &, double, double, double); +const double EXTERNAL_FLOW = -1.0; + +double signed_distance_3d(const Union &, double, double, double); + +// defining some useful aliases +using Mulmatrix3D = Mulmatrix; +using Mulmatrix2D = Mulmatrix; +using Union3D = Union; +using Union2D = Union; +using Intersection3D = Intersection; +using Intersection2D = Intersection; +using Difference3D = Difference; +using Difference2D = Difference; +using Type2D = TypeHelper::Type; +using Type3D = TypeHelper::Type; } // namespace csg diff --git a/src/csg/impl/levelset_2d.cpp b/src/csg/impl/levelset_2d.cpp new file mode 100644 index 0000000000000000000000000000000000000000..32f593c76803522cf50c8bf1112a786b242f8d1d --- /dev/null +++ b/src/csg/impl/levelset_2d.cpp @@ -0,0 +1,98 @@ +#include "csg_types.hpp" + +#include +#include +#include +#include + +namespace { + +template struct overloaded : Ts... { using Ts::operator()...; }; +template +// clang-format off +overloaded(Ts...) -> overloaded; // not needed as of C++20 +// clang-format on + +} // namespace + +namespace csg { + +double signed_distance_2d(const Square &, double, double); +double signed_distance_2d(const Circle &, double, double); +double signed_distance_2d(const Type2D &, double, double); +double signed_distance_2d(const Difference2D &, double, double); +double signed_distance_2d(const Intersection2D &, double, double); +double signed_distance_2d(const Mulmatrix2D &, double, double); +double signed_distance_2d(const Union2D &, double, double); + +double signed_distance_2d(const Union2D &group, double xx, double yy) { + auto sdist = -std::numeric_limits::max(); + for (const auto &member : group.objs) { + auto sd = signed_distance_2d(member, xx, yy); + sdist = std::max(sdist, sd); + }; + + return sdist; +} + +double signed_distance_2d(const Intersection2D &group, double xx, double yy) { + auto sdist = std::numeric_limits::max(); + for (const auto &member : group.objs) { + auto sd = signed_distance_2d(member, xx, yy); + sdist = std::min(sdist, sd); + }; + return sdist; +} + +double signed_distance_2d(const Difference2D &group, double xx, double yy) { + auto sdist = signed_distance_2d(*group.first_obj, xx, yy); + + for (const auto &member : group.next_objs.objs) { + auto sd = signed_distance_2d(member, xx, yy); + sdist = std::min(sdist, -sd); + }; + return sdist; +} + +double signed_distance_2d(const Mulmatrix2D &mm, double xx, double yy) { + // TODO: Invert non-orthogonal matrices + auto XX = xx - mm.translation[0]; + auto YY = yy - mm.translation[1]; + return signed_distance_2d(mm.group, + mm.rotation[0][0] * XX + mm.rotation[1][0] * YY, + mm.rotation[0][1] * XX + mm.rotation[1][1] * YY); +} + +double signed_distance_2d(const Square &sq, double xx, double yy) { + + auto [Lx, Ly] = sq.size; + auto XX = sq.center ? xx + Lx / 2 : xx; + double sign_x = (XX >= 0 && XX <= Lx) ? -1.0 : 1.0; + auto dist_x = std::min(std::fabs(XX), std::fabs(XX - Lx)); + + auto YY = sq.center ? yy + Ly / 2 : yy; + double sign_y = (YY >= 0 && YY <= Ly) ? -1.0 : 1.0; + auto dist_y = std::min(std::fabs(YY), std::fabs(YY - Ly)); + + return EXTERNAL_FLOW * std::max(sign_x * dist_x, sign_y * dist_y); +} + +double signed_distance_2d(const Circle &cir, double xx, double yy) { + + auto delta = xx * xx + yy * yy - cir.radius * cir.radius; + double sign = delta <= 0 ? -1.0 : 1.0; + auto dist = std::fabs(delta); + + return EXTERNAL_FLOW * sign * dist; +} + +double signed_distance_2d(const Type2D &obj, double xx, double yy) { + + return std::visit( + overloaded{ + [xx, yy](auto &&arg) { return signed_distance_2d(arg, xx, yy); }, + }, + obj); +} + +} // namespace csg diff --git a/src/csg/impl/levelset.cpp b/src/csg/impl/levelset_3d.cpp similarity index 55% rename from src/csg/impl/levelset.cpp rename to src/csg/impl/levelset_3d.cpp index 0f23278424c5668f955fcc00265a18f256545b3c..243714aa82193807761f52a0d4185e0a0bc48412 100644 --- a/src/csg/impl/levelset.cpp +++ b/src/csg/impl/levelset_3d.cpp @@ -7,8 +7,6 @@ namespace { -const double EXTERNAL_FLOW = -1.0; - template struct overloaded : Ts... { using Ts::operator()...; }; template // clang-format off @@ -19,59 +17,65 @@ overloaded(Ts...) -> overloaded; // not needed as of C++20 namespace csg { -double signed_distance(const Cone &, double, double, double); -double signed_distance(const Cube &, double, double, double); -double signed_distance(const Cylinder &, double, double, double); -double signed_distance(const Difference &, double, double, double); -double signed_distance(const Intersection &, double, double, double); -double signed_distance(const Mulmatrix &, double, double, double); -double signed_distance(const Sphere &, double, double, double); -double signed_distance(const Type &, double, double, double); - -double signed_distance(const Union &group, double xx, double yy, double zz) { +double signed_distance_3d(const Cone &, double, double, double); +double signed_distance_3d(const Cube &, double, double, double); +double signed_distance_3d(const Cylinder &, double, double, double); +double signed_distance_3d(const Difference3D &, double, double, double); +double signed_distance_3d(const Intersection3D &, double, double, double); +double signed_distance_3d(const Mulmatrix3D &, double, double, double); +double signed_distance_3d(const Sphere &, double, double, double); +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_2d(const Union2D &, double, double); + +double signed_distance_3d(const Union3D &group, double xx, double yy, + double zz) { auto sdist = -std::numeric_limits::max(); for (const auto &member : group.objs) { - auto sd = signed_distance(member, xx, yy, zz); + auto sd = signed_distance_3d(member, xx, yy, zz); sdist = std::max(sdist, sd); }; return sdist; } -double signed_distance(const Intersection &group, double xx, double yy, - double zz) { +double signed_distance_3d(const Intersection3D &group, double xx, double yy, + double zz) { auto sdist = std::numeric_limits::max(); for (const auto &member : group.objs) { - auto sd = signed_distance(member, xx, yy, zz); + auto sd = signed_distance_3d(member, xx, yy, zz); sdist = std::min(sdist, sd); }; return sdist; } -double signed_distance(const Difference &group, double xx, double yy, - double zz) { - auto sdist = signed_distance(*group.first_obj, xx, yy, zz); +double signed_distance_3d(const Difference3D &group, double xx, double yy, + double zz) { + auto sdist = signed_distance_3d(*group.first_obj, xx, yy, zz); for (const auto &member : group.next_objs.objs) { - auto sd = signed_distance(member, xx, yy, zz); + auto sd = signed_distance_3d(member, xx, yy, zz); sdist = std::min(sdist, -sd); }; return sdist; } -double signed_distance(const Mulmatrix &mm, double xx, double yy, double zz) { +double signed_distance_3d(const Mulmatrix3D &mm, double xx, double yy, + double zz) { // TODO: Invert non-orthogonal matrices auto XX = xx - mm.translation[0]; auto YY = yy - mm.translation[1]; auto ZZ = zz - mm.translation[2]; - return signed_distance( + return signed_distance_3d( mm.group, mm.rotation[0][0] * XX + mm.rotation[1][0] * YY + mm.rotation[2][0] * ZZ, mm.rotation[0][1] * XX + mm.rotation[1][1] * YY + mm.rotation[2][1] * ZZ, mm.rotation[0][2] * XX + mm.rotation[1][2] * YY + mm.rotation[2][2] * ZZ); } -double signed_distance(const Cone &cone, double xx, double yy, double zz) { +double signed_distance_3d(const Cone &cone, double xx, double yy, double zz) { double ZZ = cone.center ? zz + cone.height / 2 : zz; double sign_z = (ZZ >= 0 && ZZ <= cone.height) ? -1.0 : 1.0; auto dist_z = std::min(std::fabs(ZZ), std::fabs(ZZ - cone.height)); @@ -84,7 +88,7 @@ double signed_distance(const Cone &cone, double xx, double yy, double zz) { return EXTERNAL_FLOW * std::max(sign_z * dist_z, sign_r * dist_r); } -double signed_distance(const Cube &cube, double xx, double yy, double zz) { +double signed_distance_3d(const Cube &cube, double xx, double yy, double zz) { auto [Lx, Ly, Lz] = cube.size; auto XX = cube.center ? xx + Lx / 2 : xx; @@ -103,14 +107,15 @@ double signed_distance(const Cube &cube, double xx, double yy, double zz) { std::max({sign_x * dist_x, sign_y * dist_y, sign_z * dist_z}); } -double signed_distance(const Sphere &sph, double xx, double yy, double zz) { +double signed_distance_3d(const Sphere &sph, double xx, double yy, double zz) { auto delta_r = xx * xx + yy * yy + zz * zz - sph.radius * sph.radius; double sign = delta_r <= 0 ? -1.0 : 1.0; auto dist = std::fabs(delta_r); return EXTERNAL_FLOW * sign * dist; } -double signed_distance(const Cylinder &cyl, double xx, double yy, double zz) { +double signed_distance_3d(const Cylinder &cyl, double xx, double yy, + double zz) { auto ZZ = cyl.center ? zz + cyl.height / 2 : zz; double sign_z = (ZZ >= 0 && ZZ <= cyl.height) ? -1.0 : 1.0; auto dist_z = std::min(std::fabs(ZZ), std::fabs(ZZ - cyl.height)); @@ -123,11 +128,27 @@ double signed_distance(const Cylinder &cyl, double xx, double yy, double zz) { return EXTERNAL_FLOW * std::max(sign_z * dist_z, sign_r * dist_r); } -double signed_distance(const Type &obj, double xx, double yy, double zz) { +double signed_distance_3d(const LinearExtrude &lin_ext, double xx, double yy, + double zz) { + // TODO: support height, center and twist + return signed_distance_2d(lin_ext.group, xx, yy); +} + +double signed_distance_3d(const RotateExtrude &rot_ext, double xx, double yy, + double zz) { + // TODO: support angle + auto XX = std::hypot(xx, yy); + auto YY = zz; + return signed_distance_2d(rot_ext.group, XX, YY); +} + +double signed_distance_3d(const Type3D &obj, double xx, double yy, double zz) { return std::visit( overloaded{ - [xx, yy, zz](auto &&arg) { return signed_distance(arg, xx, yy, zz); }, + [xx, yy, zz](auto &&arg) { + return signed_distance_3d(arg, xx, yy, zz); + }, }, obj); } diff --git a/src/csg/impl/parser.cpp b/src/csg/impl/parser.cpp index a6cd4b1bfb4067f878c29f434a871523bf279716..34b6477ba0d57e9b4d6a75f023bbd8d1601d5359 100644 --- a/src/csg/impl/parser.cpp +++ b/src/csg/impl/parser.cpp @@ -13,18 +13,23 @@ using namespace tao::pegtl; namespace { +using Attr = std::variant, std::string>; + struct parser_state { - std::vector> current_objs; - std::vector current_group; + std::vector> current_3d_objs; + std::vector> current_2d_objs; + std::vector current_3d_group; + std::vector current_2d_group; std::string current_name; std::vector current_vec; std::vector> current_matrix; std::vector>> current_matrices; - std::map, std::string>> - curr_attr; + std::map curr_attr; + std::vector> curr_attrs; }; +enum Dimension { D2, D3 }; + } // namespace namespace { @@ -58,8 +63,8 @@ struct L_FUN : pad, space> {}; struct R_FUN : pad, space> {}; struct L_ARR : pad, space> {}; struct R_ARR : pad, space> {}; -struct L_BLK : pad, space> {}; -struct R_BLK : pad, space> {}; +template struct L_BLK : pad, space> {}; +template struct R_BLK : pad, space> {}; struct S_CLN : pad, space> {}; struct true_literal : string<'t', 'r', 'u', 'e'> {}; @@ -96,60 +101,117 @@ struct cube : seq, L_FUN, attr_list, R_FUN> {}; struct cylinder : seq, L_FUN, attr_list, R_FUN> {}; -struct shape : seq, opt> {}; +struct circle + : seq, L_FUN, attr_list, R_FUN> {}; + +struct square + : seq, L_FUN, attr_list, R_FUN> {}; + +template struct shape; -struct obj_list; +template <> +struct shape : seq, opt> {}; -struct bool_union : seq, L_FUN, R_FUN, L_BLK, - obj_list, R_BLK> {}; +template <> +struct shape : seq, opt> {}; +template struct obj_list; + +template +struct bool_union : seq, L_FUN, R_FUN, + L_BLK, obj_list, R_BLK> {}; + +template struct bool_intersection : seq, - L_FUN, R_FUN, L_BLK, obj_list, R_BLK> {}; + L_FUN, R_FUN, L_BLK, obj_list, R_BLK> {}; +template struct bool_diff : seq, - L_FUN, R_FUN, L_BLK, obj_list, R_BLK> {}; + L_FUN, R_FUN, L_BLK, obj_list, R_BLK> {}; -struct bool_exp : sor {}; +template +struct bool_exp : sor, bool_intersection, bool_diff> { +}; -struct mulmat : seq, - L_FUN, matrix, R_FUN, L_BLK, obj_list, R_BLK> {}; +template +struct mulmat + : seq, L_FUN, + matrix, R_FUN, L_BLK, obj_list, R_BLK> {}; -struct group : seq, L_FUN, R_FUN, - opt>, opt> {}; +template +struct group + : seq, L_FUN, R_FUN, + opt, obj_list, R_BLK>>, opt> {}; -struct render : seq, L_FUN, attr_list, - R_FUN, opt>, opt> {}; +struct extrude_rot : seq, + L_FUN, attr_list, R_FUN, L_BLK, + obj_list, R_BLK> {}; + +struct extrude_lin : seq, + L_FUN, attr_list, R_FUN, L_BLK, + obj_list, R_BLK> {}; + +struct render + : seq, L_FUN, attr_list, R_FUN, + opt, obj_list, + R_BLK>>, + opt> {}; struct colorgroup : seq, L_FUN, vector, R_FUN, - opt>, opt> {}; + opt, obj_list, + R_BLK>>, + opt> {}; -struct csg_obj : sor {}; +template +struct csg_obj : sor, mulmat, bool_exp, group, + extrude_lin, extrude_rot, render, colorgroup> {}; -struct obj_list : plus> {}; +template struct obj_list : plus, space>> {}; -struct grammar : seq {}; +struct grammar : seq, eof> {}; template struct action {}; -template <> struct action { +template <> struct action> { template static void apply(const Input &in, parser_state &st) { // std::cout << " { " << std::endl; - std::vector new_group; - st.current_objs.push_back(new_group); + std::vector new_3d_group; + st.current_3d_objs.push_back(new_3d_group); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + std::vector new_2d_group; + st.current_2d_objs.push_back(new_2d_group); } }; -template <> struct action { +template <> struct action> { template static void apply(const Input &in, parser_state &st) { // std::cout << " } " << std::endl; - st.current_group.clear(); - for (auto obj : st.current_objs.back()) { - st.current_group.push_back(obj); + st.current_3d_group.clear(); + for (auto obj : st.current_3d_objs.back()) { + st.current_3d_group.push_back(obj); + } + st.current_3d_objs.pop_back(); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + st.current_2d_group.clear(); + for (auto obj : st.current_2d_objs.back()) { + st.current_2d_group.push_back(obj); } - st.current_objs.pop_back(); + st.current_2d_objs.pop_back(); } }; @@ -171,6 +233,14 @@ template <> struct action { } }; +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + st.curr_attrs.push_back(st.curr_attr); + st.curr_attr.clear(); + } +}; + template <> struct action { template static void apply(const Input &in, parser_state &st) { @@ -179,73 +249,145 @@ template <> struct action { } }; -void add_group(parser_state &st) { - csg::Union group; - for (const auto &curr_obj : st.current_group) { +void add_group_2d(parser_state &st) { + csg::Union2D group; + for (const auto &curr_obj : st.current_2d_group) { group.objs.push_back(curr_obj); } - st.current_objs.back().push_back(group); + st.current_2d_objs.back().push_back(group); } -template <> struct action { +void add_group_3d(parser_state &st) { + csg::Union3D group; + for (const auto &curr_obj : st.current_3d_group) { + group.objs.push_back(curr_obj); + } + st.current_3d_objs.back().push_back(group); +} + +template <> struct action> { template static void apply(const Input &in, parser_state &st) { - add_group(st); + add_group_3d(st); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + add_group_2d(st); } }; template <> struct action { template static void apply(const Input &in, parser_state &st) { - add_group(st); + add_group_3d(st); } }; template <> struct action { template static void apply(const Input &in, parser_state &st) { - add_group(st); + add_group_3d(st); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + add_group_3d(st); } }; -template <> struct action { +template <> struct action> { template static void apply(const Input &in, parser_state &st) { - add_group(st); + add_group_2d(st); } }; -template <> struct action { +template <> struct action> { template static void apply(const Input &in, parser_state &st) { - auto csg_in = csg::Intersection(); - for (const auto &curr_obj : st.current_group) { + auto csg_in = csg::Intersection3D(); + for (const auto &curr_obj : st.current_3d_group) { csg_in.objs.push_back(curr_obj); } - st.current_objs.back().push_back(csg_in); + st.current_3d_objs.back().push_back(csg_in); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + auto csg_in = csg::Intersection2D(); + for (const auto &curr_obj : st.current_2d_group) { + csg_in.objs.push_back(curr_obj); + } + st.current_2d_objs.back().push_back(csg_in); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + auto csg_diff = csg::Difference3D(); + for (auto it = st.current_3d_group.begin(); it != st.current_3d_group.end(); + ++it) { + if (it == st.current_3d_group.begin()) { + csg_diff.first_obj = std::make_shared(*it); + } else { + csg_diff.next_objs.objs.push_back(*it); + } + } + st.current_3d_objs.back().push_back(csg_diff); } }; -template <> struct action { +template <> struct action> { template static void apply(const Input &in, parser_state &st) { - auto csg_diff = csg::Difference(); - for (auto it = st.current_group.begin(); it != st.current_group.end(); + auto csg_diff = csg::Difference2D(); + for (auto it = st.current_2d_group.begin(); it != st.current_2d_group.end(); ++it) { - if (it == st.current_group.begin()) { - csg_diff.first_obj = std::make_shared(*it); + if (it == st.current_2d_group.begin()) { + csg_diff.first_obj = std::make_shared(*it); } else { csg_diff.next_objs.objs.push_back(*it); } } - st.current_objs.back().push_back(csg_diff); + st.current_2d_objs.back().push_back(csg_diff); + } +}; + +template <> struct action> { + template + static void apply(const Input &in, parser_state &st) { + auto mulmat = csg::Mulmatrix2D(); + auto mat = st.current_matrices.back(); + // clang-format off + mulmat.rotation[0] = {mat[0][0], mat[0][1]}; + mulmat.rotation[1] = {mat[1][0], mat[1][1]}; + // clang-format on + + mulmat.translation = { + mat[0][3], + mat[1][3], + }; + + for (const auto &curr_obj : st.current_2d_group) { + mulmat.group.objs.push_back(curr_obj); + } + st.current_2d_objs.back().push_back(mulmat); + st.current_matrices.pop_back(); } }; -template <> struct action { +template <> struct action> { template static void apply(const Input &in, parser_state &st) { - auto mulmat = csg::Mulmatrix(); + auto mulmat = csg::Mulmatrix3D(); auto mat = st.current_matrices.back(); // clang-format off mulmat.rotation[0] = {mat[0][0], mat[0][1], mat[0][2]}; @@ -259,53 +401,89 @@ template <> struct action { mat[2][3], }; - for (const auto &curr_obj : st.current_group) { + for (const auto &curr_obj : st.current_3d_group) { mulmat.group.objs.push_back(curr_obj); } - st.current_objs.back().push_back(mulmat); + st.current_3d_objs.back().push_back(mulmat); st.current_matrices.pop_back(); } }; +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + auto lin_ext = csg::LinearExtrude(); + auto &curr_attr = st.curr_attrs.back(); + lin_ext.height = std::get(curr_attr["height"]); + lin_ext.center = std::get(curr_attr["center"]); + lin_ext.twist = std::get(curr_attr["twist"]); + + for (const auto &curr_obj : st.current_2d_group) { + lin_ext.group.objs.push_back(curr_obj); + } + + st.current_3d_objs.back().push_back(lin_ext); + st.curr_attrs.pop_back(); + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + auto rot_ext = csg::RotateExtrude(); + auto &curr_attr = st.curr_attrs.back(); + rot_ext.angle = std::get(curr_attr["angle"]); + + for (const auto &curr_obj : st.current_2d_group) { + rot_ext.group.objs.push_back(curr_obj); + } + + st.current_3d_objs.back().push_back(rot_ext); + st.curr_attrs.pop_back(); + } +}; + template <> struct action { template static void apply(const Input &in, parser_state &st) { csg::Cube cub; - auto size = std::get>(st.curr_attr["size"]); + auto &curr_attr = st.curr_attrs.back(); + auto size = std::get>(curr_attr["size"]); cub.size = {size[0], size[1], size[2]}; - cub.center = std::get(st.curr_attr["center"]); + cub.center = std::get(curr_attr["center"]); - st.current_objs.back().push_back(cub); - st.curr_attr.clear(); + st.current_3d_objs.back().push_back(cub); + st.curr_attrs.pop_back(); } }; template <> struct action { template static void apply(const Input &in, parser_state &st) { - if (st.curr_attr.count("r")) { + auto &curr_attr = st.curr_attrs.back(); + if (curr_attr.count("r")) { // proper cylinder - if (st.curr_attr.count("r1") || st.curr_attr.count("r2")) { + if (curr_attr.count("r1") || curr_attr.count("r2")) { std::cout << " ERROR: cannot specify both r and (r1 or r2); ambiguous " << std::endl; } csg::Cylinder cyl; - cyl.center = std::get(st.curr_attr["center"]); - cyl.height = std::get(st.curr_attr["h"]); - cyl.radius = std::get(st.curr_attr["r"]); - st.current_objs.back().push_back(cyl); + cyl.center = std::get(curr_attr["center"]); + cyl.height = std::get(curr_attr["h"]); + cyl.radius = std::get(curr_attr["r"]); + st.current_3d_objs.back().push_back(cyl); + st.curr_attrs.pop_back(); } else { // conic "cylinder" csg::Cone cone; - cone.center = std::get(st.curr_attr["center"]); - cone.height = std::get(st.curr_attr["h"]); - cone.radius1 = std::get(st.curr_attr["r1"]); - cone.radius2 = std::get(st.curr_attr["r2"]); - st.current_objs.back().push_back(cone); + cone.center = std::get(curr_attr["center"]); + cone.height = std::get(curr_attr["h"]); + cone.radius1 = std::get(curr_attr["r1"]); + cone.radius2 = std::get(curr_attr["r2"]); + st.current_3d_objs.back().push_back(cone); + st.curr_attrs.pop_back(); } - - st.curr_attr.clear(); } }; @@ -313,10 +491,37 @@ template <> struct action { template static void apply(const Input &in, parser_state &st) { csg::Sphere sph; - sph.radius = std::get(st.curr_attr["r"]); + auto &curr_attr = st.curr_attrs.back(); + sph.radius = std::get(curr_attr["r"]); - st.current_objs.back().push_back(sph); - st.curr_attr.clear(); + st.current_3d_objs.back().push_back(sph); + st.curr_attrs.pop_back(); + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + csg::Circle cir; + auto &curr_attr = st.curr_attrs.back(); + cir.radius = std::get(curr_attr["r"]); + + st.current_2d_objs.back().push_back(cir); + st.curr_attrs.pop_back(); + } +}; + +template <> struct action { + template + static void apply(const Input &in, parser_state &st) { + csg::Square sq; + auto &curr_attr = st.curr_attrs.back(); + auto size = std::get>(curr_attr["size"]); + sq.size = {size[0], size[1]}; + sq.center = std::get(curr_attr["center"]); + + st.current_2d_objs.back().push_back(sq); + st.curr_attrs.pop_back(); } }; @@ -372,8 +577,8 @@ template <> struct action { std::optional do_parse(std::string str) { parser_state st; - std::vector new_group; - st.current_objs.push_back(new_group); + std::vector new_3d_group; + st.current_3d_objs.push_back(new_3d_group); memory_input in(str, "std::cin"); if (!parse(in, st)) { return std::nullopt; @@ -392,9 +597,9 @@ std::shared_ptr parse_csg(std::string str) { } auto st = maybe_state.value(); - assert(st.current_objs.size() == 1); + assert(st.current_3d_objs.size() == 1); Tree tree; - for (auto obj : st.current_objs.back()) { + for (auto obj : st.current_3d_objs.back()) { tree.top.objs.push_back(obj); } assert(tree.top.objs.size() > 0); // Disallow empty .csg file diff --git a/src/csg/meson.build b/src/csg/meson.build index ca6bcd515dad2ba4616fd2c7cf0e32908a7ca2e8..c70ecd7ee173bde8e33e39115d25a468edb571ab 100644 --- a/src/csg/meson.build +++ b/src/csg/meson.build @@ -1,7 +1,8 @@ lib_csg_parser = static_library( 'csg-parser', 'impl/csg.cpp', - 'impl/levelset.cpp', + 'impl/levelset_3d.cpp', + 'impl/levelset_2d.cpp', 'impl/parser.cpp', include_directories: tao_inc, install : true) diff --git a/src/csg/tests/levelset/boolean.t.cpp b/src/csg/tests/levelset/boolean.t.cpp index 461d254ccec3188da2dae66e0ca19ea72c632cd8..f2eb216ee9a7b5947fe70592dd6addda6c549f66 100644 --- a/src/csg/tests/levelset/boolean.t.cpp +++ b/src/csg/tests/levelset/boolean.t.cpp @@ -7,12 +7,12 @@ namespace { -TEST_CASE("Union", "[Levelset Boolean]") { +TEST_CASE("Union3D", "[Levelset Boolean]") { // Create a union of a cube of size 12 and a sphere of radius 8 csg::Cube my_cub{.size = {12, 12, 12}, .center = true}; csg::Sphere my_sph{.radius = 8}; - auto my_union = csg::Union(); + auto my_union = csg::Union3D(); my_union.objs.push_back(my_cub); my_union.objs.push_back(my_sph); @@ -33,12 +33,12 @@ TEST_CASE("Union", "[Levelset Boolean]") { CHECK_FALSE(Approx(-1) == my_levelset(9, 0, 0)); } -TEST_CASE("Intersection", "[Levelset Boolean]") { +TEST_CASE("Intersection3D", "[Levelset Boolean]") { // Create an of a cube of size 12 and a sphere of radius 8 csg::Cube my_cub{.size = {12, 12, 12}, .center = true}; csg::Sphere my_sph{.radius = 8}; - auto my_in = csg::Intersection(); + auto my_in = csg::Intersection3D(); my_in.objs.push_back(my_cub); my_in.objs.push_back(my_sph); @@ -59,17 +59,17 @@ TEST_CASE("Intersection", "[Levelset Boolean]") { CHECK(Approx(1) == my_levelset(-5, 0, 0)); } -TEST_CASE("Difference", "[Levelset Boolean]") { +TEST_CASE("Difference3D", "[Levelset Boolean]") { // Create an of a cube of size 12 and remove a sphere of radius 8 csg::Cube my_cub{.size = {12, 12, 12}, .center = true}; csg::Sphere my_sph{.radius = 8}; - csg::Union my_union; + csg::Union3D my_union; my_union.objs.push_back(my_sph); - auto my_diff = csg::Difference({ - std::make_shared(my_cub), - csg::Union(my_union), + auto my_diff = csg::Difference3D({ + std::make_shared(my_cub), + csg::Union3D(my_union), }); auto my_tree = std::make_shared(); diff --git a/src/csg/tests/levelset/extrude.t.cpp b/src/csg/tests/levelset/extrude.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..205c3973fe78d3f5325f68c443643fee5f437c4f --- /dev/null +++ b/src/csg/tests/levelset/extrude.t.cpp @@ -0,0 +1,71 @@ +#include "catch2/catch.hpp" + +#include +#include + +namespace { + +TEST_CASE("two shape linear", "[Levelset Extrude]") { + auto my_lin_ext = csg::LinearExtrude(); + + csg::Circle my_cir{.radius = 1}; + my_lin_ext.group.objs.push_back(my_cir); + + auto my_mat = csg::Mulmatrix2D(); + my_mat.rotation[0] = std::array{{1, 0}}; + my_mat.rotation[1] = std::array{{0, 1}}; + my_mat.translation = {4, 0}; + my_mat.group.objs.push_back(my_cir); + my_lin_ext.group.objs.push_back(my_mat); + + auto my_tree = std::make_shared(); + my_tree->top.objs.push_back(my_lin_ext); + csg::CsgIF my_levelset(my_tree); + + SECTION("Outside") { + CHECK_FALSE(0 < my_levelset(0, 2, 0)); + CHECK_FALSE(0 < my_levelset(-4, 0, 0)); + } + SECTION("Inside") { + CHECK(0 < my_levelset(0, 0, 0)); + CHECK(0 < my_levelset(0.5, 0, 0)); + CHECK(0 < my_levelset(-0.5, 0, 0)); + CHECK(0 < my_levelset(4.5, 0, 0)); + CHECK(0 < my_levelset(3.5, 0, 0)); + CHECK(0 < my_levelset(3.5, 0, 100)); + CHECK(0 < my_levelset(3.5, 0, -100)); + } +} + +TEST_CASE("simple torus", "[Levelset Extrude]") { + auto my_rot_ext = csg::RotateExtrude(); + + csg::Circle my_cir{.radius = 1}; + auto my_mat = csg::Mulmatrix2D(); + my_mat.rotation[0] = std::array{{1, 0}}; + my_mat.rotation[1] = std::array{{0, 1}}; + my_mat.translation = {2, 0}; + my_mat.group.objs.push_back(my_cir); + my_rot_ext.group.objs.push_back(my_mat); + + auto my_tree = std::make_shared(); + my_tree->top.objs.push_back(my_rot_ext); + csg::CsgIF my_levelset(my_tree); + + SECTION("Outside") { + CHECK_FALSE(0 < my_levelset(0, 0, 0)); + CHECK_FALSE(0 < my_levelset(0.9, 0, 0)); + CHECK_FALSE(0 < my_levelset(-0.9, 0, 0)); + CHECK_FALSE(0 < my_levelset(2, 0, 1.1)); + CHECK_FALSE(0 < my_levelset(-2, 0, -1.1)); + } + SECTION("Inside") { + CHECK(0 < my_levelset(2, 0, 0)); + CHECK(0 < my_levelset(1.1, 0, 0)); + CHECK(0 < my_levelset(-1.1, 0, 0)); + CHECK(0 < my_levelset(2, 0, 0.9)); + CHECK(0 < my_levelset(-2, 0, -0.9)); + } +} + +} // namespace diff --git a/src/csg/tests/levelset/transform.t.cpp b/src/csg/tests/levelset/transform.t.cpp index 055bf4fc462c82b97a46c7811746be93e7752f83..ffa4ec127b79959b7e228e631b5267e5792c287c 100644 --- a/src/csg/tests/levelset/transform.t.cpp +++ b/src/csg/tests/levelset/transform.t.cpp @@ -11,7 +11,7 @@ TEST_CASE("Translation", "[Levelset Transform]") { csg::Cylinder my_cyl{.radius = 2, .height = 20, .center = true}; - auto my_mat = csg::Mulmatrix(); + auto my_mat = csg::Mulmatrix3D(); my_mat.rotation[0] = std::array{{1, 0, 0}}; my_mat.rotation[1] = std::array{{0, 1, 0}}; my_mat.rotation[2] = std::array{{0, 0, 1}}; @@ -46,7 +46,7 @@ TEST_CASE("90° Rotation around y-axis, rotating z-axis into x-axis", csg::Cylinder my_cyl{.radius = 2, .height = 20, .center = false}; - auto my_mat = csg::Mulmatrix(); + auto my_mat = csg::Mulmatrix3D(); my_mat.rotation[0] = std::array{{0, 0, 1}}; my_mat.rotation[1] = std::array{{0, 1, 0}}; my_mat.rotation[2] = std::array{{-1, 0, 0}}; @@ -93,7 +93,7 @@ TEST_CASE("Orthogonal rotation + translation of cylinder", double Cx = 10, Cy = 5, Cz = 5; csg::Cylinder my_cyl{.radius = radius, .height = height, .center = true}; - auto my_mat = csg::Mulmatrix(); + auto my_mat = csg::Mulmatrix3D(); my_mat.rotation[0] = std::array{{0, 0, 1}}; my_mat.rotation[1] = std::array{{0, 1, 0}}; my_mat.rotation[2] = std::array{{-1, 0, 0}}; diff --git a/src/csg/tests/meson.build b/src/csg/tests/meson.build index 4f068140acca0929e72f0ace30caaae0c29d36b6..e114a28b89100fb291e479095b05f1d6589ec246 100644 --- a/src/csg/tests/meson.build +++ b/src/csg/tests/meson.build @@ -3,12 +3,14 @@ unit_exe = executable( 'levelset/boolean.t.cpp', 'levelset/primitives.t.cpp', 'levelset/transform.t.cpp', + 'levelset/extrude.t.cpp', 'parser/boolean.t.cpp', 'parser/main.cpp', 'parser/nest.cpp', 'parser/other.t.cpp', 'parser/primitives.t.cpp', 'parser/transform.t.cpp', + 'parser/extrude.t.cpp', include_directories: [parser_inc, catch2_inc], link_with: lib_csg_parser ) diff --git a/src/csg/tests/parser/boolean.t.cpp b/src/csg/tests/parser/boolean.t.cpp index 45fa06290b0f5c9fcb216641c4e726a8cead1a8a..b96d85ce52df6aa2c865b8f4d4967600e8e379ea 100644 --- a/src/csg/tests/parser/boolean.t.cpp +++ b/src/csg/tests/parser/boolean.t.cpp @@ -28,7 +28,7 @@ union() { } )"); - auto un = std::get(st.top.objs.back()); + auto un = std::get(st.top.objs.back()); CHECK(st.top.objs.size() == 1); auto cub = std::get(un.objs.at(0)); @@ -50,7 +50,7 @@ intersection() { )"); CHECK(st.top.objs.size() == 1); - auto ints = std::get(st.top.objs.back()); + auto ints = std::get(st.top.objs.back()); CHECK(ints.objs.size() == 2); auto cub = std::get(ints.objs.at(0)); @@ -72,7 +72,7 @@ difference() { } )"); - auto diff = std::get(st.top.objs.back()); + auto diff = std::get(st.top.objs.back()); CHECK(st.top.objs.size() == 1); auto cub = std::get(*diff.first_obj); @@ -84,3 +84,11 @@ difference() { CHECK(ZZ == 12); CHECK(sph.radius == 8); } + +TEST_CASE("incompatible 2D shape", "[csg]") { + auto st = csg::parse_csg(R"( +sphere(r = 10); +circle(size = [1,2], center=true); +)"); + CHECK(st == nullptr); +} diff --git a/src/csg/tests/parser/extrude.t.cpp b/src/csg/tests/parser/extrude.t.cpp new file mode 100644 index 0000000000000000000000000000000000000000..48ab4e01c0de0f8d2273eab3fa7b8353576b2bb7 --- /dev/null +++ b/src/csg/tests/parser/extrude.t.cpp @@ -0,0 +1,116 @@ +#include "catch2/catch.hpp" + +#include +#include +#include + +namespace { +TEST_CASE("linear extrude", "[csg]") { + auto st = *csg::parse_csg(R"( +linear_extrude( +height = 10, +center = true, +twist = 0 +) { + circle(r = 1); +} +)"); + auto lin_ext = std::get(st.top.objs.back()); + CHECK(lin_ext.group.objs.size() == 1); + CHECK(lin_ext.height == 10); + CHECK(lin_ext.center == true); + CHECK(lin_ext.twist == 0); + + auto cir = std::get(lin_ext.group.objs.at(0)); + CHECK(cir.radius == 1); +} + +TEST_CASE("rotate extrude", "[csg]") { + auto st = *csg::parse_csg(R"( +rotate_extrude( +angle = 90 +) { + square(size = [2, 3], center = false); +} +)"); + auto rot_ext = std::get(st.top.objs.back()); + CHECK(rot_ext.group.objs.size() == 1); + CHECK(rot_ext.angle == 90); + + auto sq = std::get(rot_ext.group.objs.at(0)); + auto [xx, yy] = sq.size; + CHECK(xx == 2); + CHECK(yy == 3); +} + +TEST_CASE("extrude two shapes", "[csg]") { + auto st = *csg::parse_csg(R"( +linear_extrude( +height = 10, +center = true, +twist = 0 +) { + circle(r = 1); + square(size = [2, 2], center = false); +} +)"); + + auto lin_ext = std::get(st.top.objs.back()); + CHECK(lin_ext.group.objs.size() == 2); + CHECK(lin_ext.height == 10); + CHECK(lin_ext.center == true); + CHECK(lin_ext.twist == 0); + + auto cir = std::get(lin_ext.group.objs.at(0)); + CHECK(cir.radius == 1); + + auto sq = std::get(lin_ext.group.objs.at(1)); + auto [xx, yy] = sq.size; + CHECK(xx == 2); + CHECK(yy == 2); +} + +TEST_CASE("extrude with mulmatrix", "[csg]") { + auto st = *csg::parse_csg(R"( +linear_extrude(height = 10, center = false, twist = 5) { + circle(r = 2); + multmatrix([[1, 0, 0, 5], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) { + square(size = [4, 5], center = false); + } +} +)"); + + auto lin_ext = std::get(st.top.objs.back()); + CHECK(lin_ext.group.objs.size() == 2); + CHECK(lin_ext.height == 10); + CHECK(lin_ext.center == false); + CHECK(lin_ext.twist == 5); + + auto cir = std::get(lin_ext.group.objs.at(0)); + CHECK(cir.radius == 2); + + auto mm = std::get(lin_ext.group.objs.at(1)); + auto [Cx, Cy] = mm.translation; + CHECK(Cx == 5); + CHECK(Cy == 5); + CHECK(mm.group.objs.size() == 1); + + auto sq = std::get(mm.group.objs.at(0)); + auto [xx, yy] = sq.size; + CHECK(xx == 4); + CHECK(yy == 5); +} + +TEST_CASE("extrude with 3D object", "[csg]") { + auto st = csg::parse_csg(R"( +linear_extrude(height = 10, center = false, twist = 5) { + multmatrix([[1, 0, 0, 5], [0, 1, 0, 5], [0, 0, 1, 0], [0, 0, 0, 1]]) { + sphere(r = 8); + } +} +)"); + + CHECK(st == nullptr); +} + +} // namespace diff --git a/src/csg/tests/parser/nest.cpp b/src/csg/tests/parser/nest.cpp index 60a28a3965d838e38755447664e21b136b072971..6c9137da357a3e7c9eb0d95618726d6f528976be 100644 --- a/src/csg/tests/parser/nest.cpp +++ b/src/csg/tests/parser/nest.cpp @@ -27,8 +27,8 @@ multmatrix( )"); CHECK(maybe_st != nullptr); auto st = maybe_st.get(); - auto mat = std::get(st->top.objs.back()); - auto mat2 = std::get(mat.group.objs.back()); + auto mat = std::get(st->top.objs.back()); + auto mat2 = std::get(mat.group.objs.back()); auto cyl = std::get(mat2.group.objs.at(0)); CHECK(cyl.height == 2); CHECK(cyl.radius == 10); diff --git a/src/csg/tests/parser/transform.t.cpp b/src/csg/tests/parser/transform.t.cpp index d1fce41662b1376860b710e26547e0d92f743b2b..3f042ffacdb1102663f253f24a7a4b43654c1949 100644 --- a/src/csg/tests/parser/transform.t.cpp +++ b/src/csg/tests/parser/transform.t.cpp @@ -25,7 +25,7 @@ multmatrix( cylinder(h = 2, r = 10, center=true); } )"); - auto mat = std::get(st.top.objs.back()); + auto mat = std::get(st.top.objs.back()); CHECK(mat.group.objs.size() == 1); auto cyl = std::get(mat.group.objs.at(0)); CHECK(cyl.height == 2); @@ -46,7 +46,7 @@ multmatrix( sphere(r = 10); } )"); - auto mat = std::get(st.top.objs.back()); + auto mat = std::get(st.top.objs.back()); auto cyl = std::get(mat.group.objs.at(0)); auto sph = std::get(mat.group.objs.at(1)); CHECK(cyl.height == 2); @@ -79,14 +79,14 @@ multmatrix( cylinder(h=4, r1=1, r2=2, center=true); } )"); - auto mat = std::get(st.top.objs.at(0)); + auto mat = std::get(st.top.objs.at(0)); auto cyl = std::get(mat.group.objs.at(0)); auto sph = std::get(mat.group.objs.at(1)); CHECK(cyl.height == 2); CHECK(cyl.radius == 10); CHECK(sph.radius == 11); - auto mat2 = std::get(st.top.objs.at(1)); + auto mat2 = std::get(st.top.objs.at(1)); auto cube = std::get(mat2.group.objs.at(0)); auto cone = std::get(mat2.group.objs.at(1)); auto [XX, YY, ZZ] = cube.size; @@ -116,7 +116,7 @@ multmatrix( )"); auto cyl = std::get(st.top.objs.at(0)); auto sph = std::get(st.top.objs.at(1)); - auto mat = std::get(st.top.objs.at(2)); + auto mat = std::get(st.top.objs.at(2)); CHECK(cyl.height == 2); CHECK(cyl.radius == 10); CHECK(sph.radius == 11);