#include #include #include // only debug visualization #include #include #include // for next_highest_power_of_2() #include using namespace Slic3r; namespace Private{ // calculate multiplication of ray dir to intersect - inspired by // segment_segment_intersection when ray dir is normalized retur distance from // ray point to intersection No value mean no intersection std::optional ray_segment_intersection(const Vec2d &r_point, const Vec2d &r_dir, const Vec2d &s0, const Vec2d &s1) { auto denominate = [](const Vec2d &v0, const Vec2d &v1) -> double { return v0.x() * v1.y() - v1.x() * v0.y(); }; Vec2d segment_dir = s1 - s0; double d = denominate(segment_dir, r_dir); if (std::abs(d) < std::numeric_limits::epsilon()) // Line and ray are collinear. return {}; Vec2d s12 = s0 - r_point; double s_number = denominate(r_dir, s12); bool change_sign = false; if (d < 0.) { change_sign = true; d = -d; s_number = -s_number; } if (s_number < 0. || s_number > d) // Intersection outside of segment. return {}; double r_number = denominate(segment_dir, s12); if (change_sign) r_number = -r_number; if (r_number < 0.) // Intersection before ray start. return {}; return r_number / d; } Vec2d get_intersection(const Vec2d & point, const Vec2d & dir, const std::array &triangle) { std::optional t; for (size_t i = 0; i < 3; ++i) { size_t i2 = i + 1; if (i2 == 3) i2 = 0; if (!t.has_value()) { t = ray_segment_intersection(point, dir, triangle[i], triangle[i2]); continue; } // small distance could be preccission inconsistance std::optional t2 = ray_segment_intersection(point, dir, triangle[i], triangle[i2]); if (t2.has_value() && *t2 > *t) t = t2; } assert(t.has_value()); // Not found intersection. return point + dir * (*t); } Vec3d calc_hit_point(const igl::Hit & h, const Vec3i & triangle, const std::vector &vertices) { double c1 = h.u; double c2 = h.v; double c0 = 1.0 - c1 - c2; Vec3d v0 = vertices[triangle[0]].cast(); Vec3d v1 = vertices[triangle[1]].cast(); Vec3d v2 = vertices[triangle[2]].cast(); return v0 * c0 + v1 * c1 + v2 * c2; } Vec3d calc_hit_point(const igl::Hit &h, indexed_triangle_set &its) { return calc_hit_point(h, its.indices[h.id], its.vertices); } } // namespace Private std::string get_font_filepath() { std::string resource_dir = std::string(TEST_DATA_DIR) + "/../../resources/"; return resource_dir + "fonts/NotoSans-Regular.ttf"; } #include "imgui/imstb_truetype.h" TEST_CASE("Read glyph C shape from font, stb library calls ONLY", "[Emboss]") { std::string font_path = get_font_filepath(); char letter = 'C'; // Read font file FILE *file = fopen(font_path.c_str(), "rb"); REQUIRE(file != nullptr); // find size of file REQUIRE(fseek(file, 0L, SEEK_END) == 0); size_t size = ftell(file); REQUIRE(size != 0); rewind(file); std::vector buffer(size); size_t count_loaded_bytes = fread((void *) &buffer.front(), 1, size, file); REQUIRE(count_loaded_bytes == size); // Use stb true type library int font_offset = stbtt_GetFontOffsetForIndex(buffer.data(), 0); REQUIRE(font_offset >= 0); stbtt_fontinfo font_info; REQUIRE(stbtt_InitFont(&font_info, buffer.data(), font_offset) != 0); int unicode_letter = (int) letter; int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); REQUIRE(glyph_index != 0); stbtt_vertex *vertices; int num_verts = stbtt_GetGlyphShape(&font_info, glyph_index, &vertices); CHECK(num_verts > 0); free(vertices); } #include TEST_CASE("Convert glyph % to model", "[Emboss]") { std::string font_path = get_font_filepath(); unsigned int font_index = 0; // collection char letter = '%'; float flatness = 2.; auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); std::optional glyph = Emboss::letter2glyph(*font, font_index, letter, flatness); REQUIRE(glyph.has_value()); ExPolygons shape = glyph->shape; REQUIRE(!shape.empty()); float z_depth = 1.f; Emboss::ProjectZ projection(z_depth); indexed_triangle_set its = Emboss::polygons2model(shape, projection); CHECK(!its.indices.empty()); } //#define VISUALIZE #ifdef VISUALIZE TEST_CASE("Visualize glyph from font", "[Emboss]") { std::string font_path = "C:/data/ALIENATO.TTF"; std::string text = "i"; Emboss::FontFileWithCache font( Emboss::create_font_file(font_path.c_str())); REQUIRE(font.has_value()); FontProp fp; fp.size_in_mm = 8; fp.emboss = 4; ExPolygons shapes = Emboss::text2shapes(font, text.c_str(), fp); // char letter = 'i'; // unsigned int font_index = 0; // collection // float flatness = 5; // auto glyph = Emboss::letter2glyph(*font.font_file, font_index, letter, // flatness); ExPolygons shapes2 = glyph->shape; { SVG //svg("C:/data/temp/stored_letter.svg", get_extents(shapes2)); //svg.draw(shapes2); } // debug shape REQUIRE(!shapes.empty()); //{ SVG svg("C:/data/temp/shapes.svg"); svg.draw(shapes); } // debug shape float z_depth = 100.f; Emboss::ProjectZ projection(z_depth); indexed_triangle_set its = Emboss::polygons2model(shapes, projection); its_write_obj(its, "C:/data/temp/bad_glyph.obj"); CHECK(!its.indices.empty()); TriangleMesh tm(its); auto s = tm.stats(); } #endif // VISUALIZE #include "test_utils.hpp" #include "nanosvg/nanosvg.h" // load SVG file #include "libslic3r/NSVGUtils.hpp" ExPolygons heal_and_check(const Polygons &polygons) { Pointfs intersections_prev = intersection_points(polygons); Points polygons_points = to_points(polygons); Points duplicits_prev = collect_duplicates(polygons_points); ExPolygons shape = Emboss::heal_shape(polygons); // Is default shape for unhealabled shape? bool is_default_shape = shape.size() == 1 && shape.front().contour.points.size() == 4 && shape.front().holes.size() == 1 && shape.front().holes.front().points.size() == 4 ; CHECK(!is_default_shape); Pointfs intersections = intersection_points(shape); Points shape_points = to_points(shape); Points duplicits = collect_duplicates(shape_points); //{ // BoundingBox bb(polygons_points); // // bb.scale(svg_scale); // SVG svg("C:/data/temp/test_visualization.svg", bb); // svg.draw(polygons, "gray"); // input // svg.draw(shape, "green"); // output // Points pts; // pts.reserve(intersections.size()); // for (const Vec2d &intersection : intersections) // pts.push_back(intersection.cast()); // svg.draw(pts, "red", 10); // svg.draw(duplicits, "orenge", 10); //} CHECK(intersections.empty()); CHECK(duplicits.empty()); return shape; } void scale(Polygons &polygons, double multiplicator) { for (Polygon &polygon : polygons) for (Point &p : polygon) p *= multiplicator; } Polygons load_polygons(const std::string &svg_file) { std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file; NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); Polygons polygons = NSVGUtils::to_polygons(image); nsvgDelete(image); return polygons; } TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]") { // Shape loaded from svg is letter 'i' from font 'ALIENATE.TTF' std::string file_name = "contour_ALIENATO.TTF_glyph_i.svg"; Polygons polygons = load_polygons(file_name); auto a = heal_and_check(polygons); Polygons scaled_shape = polygons; // copy scale(scaled_shape, 1 / Emboss::SHAPE_SCALE); auto b = heal_and_check(scaled_shape); // different scale scale(scaled_shape, 10.); auto c = heal_and_check(scaled_shape); // check reverse order of points Polygons reverse_shape = polygons; for (Polygon &p : reverse_shape) std::reverse(p.points.begin(), p.points.end()); auto d = heal_and_check(scaled_shape); #ifdef VISUALIZE CHECK(false); #endif // VISUALIZE } TEST_CASE("Heal of 'm' in Allura_Script.ttf", "[Emboss]") { Polygons polygons = load_polygons("contour_Allura_Script.ttf_glyph_m.svg"); auto a = heal_and_check(polygons); } TEST_CASE("Heal of points close to line", "[Emboss]") { std::string file_name = "points_close_to_line.svg"; std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); Polygons polygons = NSVGUtils::to_polygons(image); nsvgDelete(image); REQUIRE(polygons.size() == 1); Polygon polygon = polygons.front(); polygon.points.pop_back();// NSVG put first point as last one when polygon is closed ExPolygons expoly({ExPolygon(polygon)}); CHECK(Emboss::divide_segments_for_close_point(expoly, .6)); //{ SVG svg("C:/data/temp/healed.svg"); svg.draw(expoly);} CHECK(to_points(expoly).size() >= (to_points(polygon).size() + 2)); } TEST_CASE("Convert text with glyph cache to model", "[Emboss]") { std::string font_path = get_font_filepath(); std::string text = "Because Ford never learned to say his original name, \n\ his father eventually died of shame, which is still \r\n\ a terminal disease in some parts of the Galaxy.\n\r\ The other kids at school nicknamed him Ix,\n\ which in the language of Betelgeuse Five translates as\t\n\ \"boy who is not able satisfactorily to explain what a Hrung is,\n\ nor why it should choose to collapse on Betelgeuse Seven\"."; float line_height = 10.f, depth = 2.f; auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); Emboss::FontFileWithCache ffwc(std::move(font)); FontProp fp{line_height, depth}; ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp); REQUIRE(!shapes.empty()); Emboss::ProjectZ projection(depth); indexed_triangle_set its = Emboss::polygons2model(shapes, projection); CHECK(!its.indices.empty()); //its_write_obj(its, "C:/data/temp/text.obj"); } TEST_CASE("Test hit point", "[AABBTreeIndirect]") { indexed_triangle_set its; its.vertices = { Vec3f(1, 1, 1), Vec3f(2, 10, 2), Vec3f(10, 0, 2), }; its.indices = {Vec3i(0, 2, 1)}; auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( its.vertices, its.indices); Vec3d ray_point(8, 1, 0); Vec3d ray_dir(0, 0, 1); igl::Hit hit; AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices, tree, ray_point, ray_dir, hit); Vec3d hp = Private::calc_hit_point(hit, its); CHECK(abs(hp.x() - ray_point.x()) < .1); CHECK(abs(hp.y() - ray_point.y()) < .1); } TEST_CASE("ray segment intersection", "[MeshBoolean]") { Vec2d r_point(1, 1); Vec2d r_dir(1, 0); // colinear CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(0, 0), Vec2d(2, 0)).has_value()); CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 0), Vec2d(0, 0)).has_value()); // before ray CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(0, 0), Vec2d(0, 2)).has_value()); CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(0, 2), Vec2d(0, 0)).has_value()); // above ray CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 2), Vec2d(2, 3)).has_value()); CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 3), Vec2d(2, 2)).has_value()); // belove ray CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 0), Vec2d(2, -1)).has_value()); CHECK(!Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, -1), Vec2d(2, 0)).has_value()); // intersection at [2,1] distance 1 auto t1 = Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 0), Vec2d(2, 2)); REQUIRE(t1.has_value()); auto t2 = Private::ray_segment_intersection(r_point, r_dir, Vec2d(2, 2), Vec2d(2, 0)); REQUIRE(t2.has_value()); CHECK(abs(*t1 - *t2) < std::numeric_limits::epsilon()); } TEST_CASE("triangle intersection", "[]") { Vec2d point(1, 1); Vec2d dir(-1, 0); std::array triangle = {Vec2d(0, 0), Vec2d(5, 0), Vec2d(0, 5)}; Vec2d i = Private::get_intersection(point, dir, triangle); CHECK(abs(i.x()) < std::numeric_limits::epsilon()); CHECK(abs(i.y() - 1.) < std::numeric_limits::epsilon()); } #if defined _WIN32 #define FONT_DIR_PATH "C:/Windows/Fonts"; #endif //#elif defined __linux__ //#define FONT_DIR_PATH "/usr/share/fonts"; //#elif defined __APPLE__ //#define FONT_DIR_PATH "//System/Library/Fonts"; //#endif #ifdef FONT_DIR_PATH #include #include #include namespace fs = boost::filesystem; //#include //namespace fs = std::filesystem; // Check function Emboss::is_italic that exist some italic and some non-italic font. TEST_CASE("Italic check", "[Emboss]") { std::string dir_path = FONT_DIR_PATH; std::queue dir_paths; dir_paths.push(dir_path); bool exist_italic = false; bool exist_non_italic = false; while (!dir_paths.empty()) { std::string dir_path = dir_paths.front(); dir_paths.pop(); for (const auto &entry : fs::directory_iterator(dir_path)) { const fs::path &act_path = entry.path(); if (fs::is_directory(entry)) { dir_paths.push(act_path.string()); continue; } std::string ext = act_path.extension().string(); std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return std::tolower(c); }); if (ext != ".ttf") continue; std::string path_str = act_path.string(); auto font_opt = Emboss::create_font_file(path_str.c_str()); if (font_opt == nullptr) continue; unsigned int collection_number = 0; if (Emboss::is_italic(*font_opt, collection_number)) exist_italic = true; else exist_non_italic = true; if (exist_italic && exist_non_italic) break; //std::cout << ((Emboss::is_italic(*font_opt)) ? "[yes] " : "[no ] ") << entry.path() << std::endl; } } CHECK(exist_italic); CHECK(exist_non_italic); } #endif // FONT_DIR_PATH #include "libslic3r/CutSurface.hpp" TEST_CASE("Cut surface", "[]") { std::string font_path = get_font_filepath(); char letter = '%'; float flatness = 2.; unsigned int font_index = 0; // collection double z_depth = 50.; // projection size auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); std::optional glyph = Emboss::letter2glyph(*font, font_index, letter, flatness); REQUIRE(glyph.has_value()); ExPolygons shape = glyph->shape; REQUIRE(!shape.empty()); Transform3d tr = Transform3d::Identity(); tr.translate(Vec3d(0., 0., -z_depth)); tr.scale(Emboss::SHAPE_SCALE); Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth)); auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5); its_translate(object, Vec3f(49 - 25, -10 - 25, -40)); auto cube2 = object; // copy its_translate(cube2, Vec3f(100, -40, 7.5)); its_merge(object, std::move(cube2)); auto surfaces = cut_surface(shape, {object}, cut_projection, 0); CHECK(!surfaces.empty()); Emboss::OrthoProject projection(Transform3d::Identity(), Vec3d(0., 0., 10.)); its_translate(surfaces, Vec3f(0., 0., 10.)); indexed_triangle_set its = cut2model(surfaces, projection); CHECK(!its.empty()); //its_write_obj(its, "C:/data/temp/projected.obj"); } #include #include #include TEST_CASE("UndoRedo serialization", "[Emboss]") { TextConfiguration tc; tc.text = "Dovede-li se člověk zasmát sám sobě, nevyjde ze smíchu po celý život."; EmbossStyle& es = tc.style; es.name = "Seneca"; es.path = "Simply the best"; es.type = EmbossStyle::Type::file_path; FontProp &fp = es.prop; fp.angle = 100.; fp.distance = 10.; fp.char_gap = 1; fp.use_surface = true; tc.fix_3mf_tr = Transform3d::Identity(); std::stringstream ss; // any stream can be used { cereal::BinaryOutputArchive oarchive(ss); // Create an output archive oarchive(tc); } // archive goes out of scope, ensuring all contents are flushed TextConfiguration tc_loaded; { cereal::BinaryInputArchive iarchive(ss); // Create an input archive iarchive(tc_loaded); } CHECK(tc.style == tc_loaded.style); CHECK(tc.text == tc_loaded.text); CHECK(tc.fix_3mf_tr.has_value() == tc_loaded.fix_3mf_tr.has_value()); } #include #include #include #include /// /// Distiguish point made by shape(Expolygon) /// Referencing an ExPolygon contour plus a vertex base of the contour. /// Used for adressing Vertex of mesh created by extrude ExPolygons /// struct ShapesVertexId { // Index of an ExPolygon in ExPolygons. int32_t expoly{ -1 }; // Index of a contour in ExPolygon. // 0 - outer contour, >0 - hole int32_t contour{ -1 }; // Base of the zero'th point of a contour in text mesh. // There are two vertices (front and rear) created for each contour, // thus there are 2x more vertices in text mesh than the number of contour points. int32_t vertex_base{ -1 }; }; /// /// IntersectingElemnt /// /// Adress polygon inside of ExPolygon /// Keep information about source of vertex: /// - from face (one of 2 possible) /// - from edge (one of 2 possible) /// /// V1~~~~V2 /// : f1 /| /// : / | /// : /e1| /// : / |e2 /// :/ f2 | /// V1'~~~V2' /// /// | .. edge /// / .. edge /// : .. foreign edge - neighbor /// ~ .. no care edge - idealy should not cross model /// V1,V1' .. projected 2d point to 3d /// V2,V2' .. projected 2d point to 3d /// /// f1 .. text_face_1 (triangle face made by side of shape contour) /// f2 .. text_face_2 /// e1 .. text_edge_1 (edge on side of face made by side of shape contour) /// e2 .. text_edge_2 /// /// struct IntersectingElemnt { // Index into vector of ShapeVertexId // describe point on shape contour int32_t vertex_index{-1}; // index of point in Polygon contour int32_t point_index{-1}; // vertex or edge ID, where edge ID is the index of the source point. // There are 4 consecutive indices generated for a single glyph edge: // 0th - 1st text edge (straight) // 1th - 1st text face // 2nd - 2nd text edge (diagonal) // 3th - 2nd text face // Type of intersecting element from extruded shape( 3d ) enum class Type { edge_1 = 0, face_1 = 1, edge_2 = 2, face_2 = 3, undefined = 4 } type = Type::undefined; }; namespace Slic3r::MeshBoolean::cgal2 { namespace CGALProc = CGAL::Polygon_mesh_processing; namespace CGALParams = CGAL::Polygon_mesh_processing::parameters; using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel; using _EpicMesh = CGAL::Surface_mesh; // using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel; // using _EpecMesh = CGAL::Surface_mesh; using CGALMesh = _EpicMesh; /// /// Convert triangle mesh model to CGAL Surface_mesh /// Add property map for source face index /// /// Model /// Property map name for store conversion from CGAL face to index to its /// CGAL mesh - half edge mesh CGALMesh to_cgal(const indexed_triangle_set &its, const std::string &face_map_name) { CGALMesh result; if (its.empty()) return result; const std::vector &V = its.vertices; const std::vector &F = its.indices; // convert from CGAL face to its face auto face_map = result.add_property_map(face_map_name).first; size_t vertices_count = V.size(); size_t edges_count = (F.size() * 3) / 2; size_t faces_count = F.size(); result.reserve(vertices_count, edges_count, faces_count); for (auto &v : V) result.add_vertex(typename CGALMesh::Point{v.x(), v.y(), v.z()}); using VI = typename CGALMesh::Vertex_index; for (auto &f : F) { auto fid = result.add_face(VI(f(0)), VI(f(1)), VI(f(2))); // index of face in source triangle mesh int32_t index = static_cast(&f - &F.front()); face_map[fid] = index; } return result; } /// /// Covert 2d shape (e.g. Glyph) to CGAL model /// /// 2d shape to project /// Define transformation 2d point into 3d /// Identify shape /// Name of property map to store conversion from edge to contour /// Name of property map to store conversion from face to contour /// Identify point on shape contour /// CGAL model of extruded shape CGALMesh to_cgal(const ExPolygons &shape, const Slic3r::Emboss::IProjection &projection, int32_t shape_id, const std::string &edge_shape_map_name, const std::string &face_shape_map_name, std::vector &contour_indices) { CGALMesh result; if (shape.empty()) return result; auto edge_shape_map = result.add_property_map(edge_shape_map_name).first; auto face_shape_map = result.add_property_map(face_shape_map_name).first; std::vector indices; auto insert_contour = [&projection, &indices , &result, &contour_indices, &edge_shape_map, &face_shape_map](const Polygon& polygon, int32_t iexpoly, int32_t id) { indices.clear(); indices.reserve(polygon.points.size() * 2); size_t num_vertices_old = result.number_of_vertices(); int32_t vertex_index = static_cast(contour_indices.size()); contour_indices.push_back({iexpoly, id, int32_t(num_vertices_old) }); for (const Point& p2 : polygon.points) { auto p = projection.create_front_back(p2); auto vi = result.add_vertex(typename CGALMesh::Point{ p.first.x(), p.first.y(), p.first.z() }); assert((size_t)vi == indices.size() + num_vertices_old); indices.emplace_back(vi); vi = result.add_vertex(typename CGALMesh::Point{ p.second.x(), p.second.y(), p.second.z() }); assert((size_t)vi == indices.size() + num_vertices_old); indices.emplace_back(vi); } int32_t contour_index = 0; for (int32_t i = 0; i < int32_t(indices.size()); i += 2) { int32_t j = (i + 2) % int32_t(indices.size()); auto find_edge = [&result](CGALMesh::Face_index fi, CGALMesh::Vertex_index from, CGALMesh::Vertex_index to) { CGALMesh::Halfedge_index hi = result.halfedge(fi); for (; result.target(hi) != to; hi = result.next(hi)); assert(result.source(hi) == from); assert(result.target(hi) == to); return hi; }; auto fi = result.add_face(indices[i], indices[i + 1], indices[j]); edge_shape_map[result.edge(find_edge(fi, indices[i], indices[i + 1]))] = IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::edge_1}; face_shape_map[fi] = IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::face_1}; edge_shape_map[result.edge(find_edge(fi, indices[i + 1], indices[j]))] = IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::edge_2}; face_shape_map[result.add_face(indices[j], indices[i + 1], indices[j + 1])] = IntersectingElemnt{vertex_index, contour_index, IntersectingElemnt::Type::face_2}; ++contour_index; } }; size_t count_point = count_points(shape); result.reserve(result.number_of_vertices() + 2 * count_point, result.number_of_edges() + 4 * count_point, result.number_of_faces() + 2 * count_point); // Identify polygon // (contour_id > 0) are holes for (const auto &s : shape) { size_t contour_id = 0; insert_contour(s.contour, shape_id, contour_id++); for (const Polygon &hole : s.holes) insert_contour(hole, shape_id, contour_id++); ++shape_id; } return result; } } #include "libslic3r/TriangleMesh.hpp" //// 1 //// // Question store(1) Or calculate on demand(2) ?? // (1) type: vector > // (1) Needs recalculation when merge and propagation togewther with its // (2) Could appear surface mistakes(need calc - all half edges) // (2) NO need of trace cut outline and connect it with letter conture points /// /// Cut surface shape from source model /// /// Input source mesh /// Input 2d shape to cut from surface /// Define transformation from 2d to 3d /// Cutted surface, Its do not represent Volume indexed_triangle_set cut_shape(const indexed_triangle_set &source, const ExPolygon &shape, const Emboss::IProjection &projection) { // NOT implemented yet return {}; } /// /// Cut surface shape from source model /// /// Input source mesh /// Input 2d shape to cut from surface /// Define transformation from 2d to 3d /// Cutted surface, Its do not represent Volume indexed_triangle_set cut_shape(const indexed_triangle_set &source, const ExPolygons &shapes, const Emboss::IProjection &projection) { indexed_triangle_set result; for (const ExPolygon &shape : shapes) its_merge(result, cut_shape(source, shape, projection)); return result; } using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh; // First Idea //// 1 //// // Use source model to modify ONLY surface of text ModelVolume // Second Idea // Store original its inside of text configuration[optional] // Cause problem with next editation of object -> cut, simplify, Netfabb, Hollow, ...(transform original vertices) TEST_CASE("Emboss extrude cut", "[Emboss-Cut]") { std::string font_path = get_font_filepath(); unsigned int font_index = 0; // collection char letter = '%'; float flatness = 2.; auto font = Emboss::create_font_file(font_path.c_str()); REQUIRE(font != nullptr); std::optional glyph = Emboss::letter2glyph(*font, font_index, letter, flatness); REQUIRE(glyph.has_value()); ExPolygons shape = glyph->shape; REQUIRE(!shape.empty()); float z_depth = 50.f; Emboss::ProjectZ projection(z_depth); #if 0 indexed_triangle_set text = Emboss::polygons2model(shape, projection); BoundingBoxf3 bbox = bounding_box(text); CHECK(!text.indices.empty()); #endif auto cube = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5); its_translate(cube, Vec3f(49 - 25, -10 - 25, 2.5)); auto cube2 = cube; // its_translate(cube2, Vec3f(0, 0, 40)); its_translate(cube2, Vec3f(100, -40, 40)); its_merge(cube, std::move(cube2)); //cube = its_make_sphere(350., 1.); //for (auto &face : cube2.indices) // for (int i = 0; i < 3; ++ i) // face(i) += int(cube.vertices.size()); //append(cube.vertices, cube2.vertices); //append(cube.indices, cube2.indices); using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh; // name of CGAL property map for store source object face id - index into its.indices std::string face_map_name = "f:face_map"; std::string face_type_map_name = "f:type"; // identify glyph for intersected vertex std::string vert_shape_map_name = "v:glyph_id"; MyMesh cgal_object = MeshBoolean::cgal2::to_cgal(cube, face_map_name); auto face_map = cgal_object.property_map(face_map_name).first; auto vert_shape_map = cgal_object.add_property_map(vert_shape_map_name).first; std::string edge_shape_map_name = "e:glyph_id"; std::string face_shape_map_name = "f:glyph_id"; std::vector glyph_contours; MyMesh cgal_shape = MeshBoolean::cgal2::to_cgal(shape, projection, 0, edge_shape_map_name, face_shape_map_name, glyph_contours); auto edge_shape_map = cgal_shape.property_map(edge_shape_map_name).first; auto face_shape_map = cgal_shape.property_map(face_shape_map_name).first; // bool map for affected edge using d_prop_bool = CGAL::dynamic_edge_property_t; using ecm_it = boost::property_map::SMPM; using EcmType = CGAL::internal::Dynamic; EcmType ecm = get(d_prop_bool(), cgal_object); struct Visitor { const MyMesh &object; const MyMesh &shape; // Properties of the shape mesh: MyMesh::Property_map edge_shape_map; MyMesh::Property_map face_shape_map; // Properties of the object mesh. MyMesh::Property_map face_map; MyMesh::Property_map vert_shape_map; typedef boost::graph_traits GT; typedef typename GT::face_descriptor face_descriptor; typedef typename GT::halfedge_descriptor halfedge_descriptor; typedef typename GT::vertex_descriptor vertex_descriptor; int32_t source_face_id = -1; void before_subface_creations(face_descriptor f_old, MyMesh& mesh) { assert(&mesh == &object); source_face_id = face_map[f_old]; } // it is called multiple times for one source_face_id void after_subface_created(face_descriptor f_new, MyMesh &mesh) { assert(&mesh == &object); assert(source_face_id != -1); face_map[f_new] = source_face_id; } std::vector intersection_point_glyph; // Intersecting an edge hh_edge from tm_edge with a face hh_face of tm_face. void intersection_point_detected( // ID of the intersection point, starting at 0. Ids are consecutive. std::size_t i_id, // Dimension of a simplex part of face(hh_face) that is intersected by hh_edge: // 0 for vertex: target(hh_face) // 1 for edge: hh_face // 2 for the interior of face: face(hh_face) int simplex_dimension, // Edge of tm_edge, see edge_source_coplanar_with_face & edge_target_coplanar_with_face whether any vertex of hh_edge is coplanar with face(hh_face). halfedge_descriptor hh_edge, // Vertex, halfedge or face of tm_face intersected by hh_edge, see comment at simplex_dimension. halfedge_descriptor hh_face, // Mesh containing hh_edge const MyMesh& tm_edge, // Mesh containing hh_face const MyMesh& tm_face, // source(hh_edge) is coplanar with face(hh_face). bool edge_source_coplanar_with_face, // target(hh_edge) is coplanar with face(hh_face). bool edge_target_coplanar_with_face) { if (i_id <= intersection_point_glyph.size()) { intersection_point_glyph.reserve(Slic3r::next_highest_power_of_2(i_id + 1)); intersection_point_glyph.resize(i_id + 1); } const IntersectingElemnt* glyph = nullptr; if (&tm_face == &shape) { assert(&tm_edge == &object); switch (simplex_dimension) { case 1: // edge x edge intersection glyph = &edge_shape_map[shape.edge(hh_face)]; break; case 2: // edge x face intersection glyph = &face_shape_map[shape.face(hh_face)]; break; default: assert(false); } if (edge_source_coplanar_with_face) vert_shape_map[object.source(hh_edge)] = *glyph; if (edge_target_coplanar_with_face) vert_shape_map[object.target(hh_edge)] = *glyph; } else { assert(&tm_edge == &shape && &tm_face == &object); assert(!edge_source_coplanar_with_face); assert(!edge_target_coplanar_with_face); glyph = &edge_shape_map[shape.edge(hh_edge)]; if (simplex_dimension == 0) vert_shape_map[object.target(hh_face)] = *glyph; } intersection_point_glyph[i_id] = glyph; } void new_vertex_added(std::size_t node_id, vertex_descriptor vh, const MyMesh &tm) { assert(&tm == &object); assert(node_id < intersection_point_glyph.size()); const IntersectingElemnt * glyph = intersection_point_glyph[node_id]; assert(glyph != nullptr); assert(glyph->vertex_index != -1); assert(glyph->point_index != -1); vert_shape_map[vh] = glyph ? *glyph : IntersectingElemnt{}; } void after_subface_creations(MyMesh&) {} void before_subface_created(MyMesh&) {} void before_edge_split(halfedge_descriptor /* h */, MyMesh& /* tm */) {} void edge_split(halfedge_descriptor /* hnew */, MyMesh& /* tm */) {} void after_edge_split() {} void add_retriangulation_edge(halfedge_descriptor /* h */, MyMesh& /* tm */) {} } visitor{cgal_object, cgal_shape, edge_shape_map, face_shape_map, face_map, vert_shape_map}; const auto& p = CGAL::Polygon_mesh_processing::parameters::throw_on_self_intersection(false).visitor(visitor).edge_is_constrained_map(ecm); const auto& q = CGAL::Polygon_mesh_processing::parameters::do_not_modify(true); // CGAL::Polygon_mesh_processing::corefine(cgal_object, cgalcube2, p, p); CGAL::Polygon_mesh_processing::corefine(cgal_object, cgal_shape, p, q); enum class SideType { // face inside of the cutted shape inside, // face outside of the cutted shape outside, // face without constrained edge (In or Out) not_constrained }; auto side_type_map = cgal_object.add_property_map("f:side").first; for (auto fi : cgal_object.faces()) { SideType side_type = SideType::not_constrained; auto hi_end = cgal_object.halfedge(fi); auto hi = hi_end; do { CGAL::SM_Edge_index edge_index = cgal_object.edge(hi); // is edge new created - constrained? if (get(ecm, edge_index)) { // This face has a constrained edge. IntersectingElemnt shape_from = vert_shape_map[cgal_object.source(hi)]; IntersectingElemnt shape_to = vert_shape_map[cgal_object.target(hi)]; assert(shape_from.vertex_index != -1); assert(shape_from.vertex_index == shape_to.vertex_index); assert(shape_from.point_index != -1); assert(shape_to.point_index != -1); const ShapesVertexId &vertex_index = glyph_contours[shape_from.vertex_index]; const ExPolygon &expoly = shape[vertex_index.expoly]; const Polygon &contour = vertex_index.contour == 0 ? expoly.contour : expoly.holes[vertex_index.contour - 1]; bool is_inside = false; // 4 type // index into contour int32_t i_from = shape_from.point_index; int32_t i_to = shape_to.point_index; if (i_from == i_to && shape_from.type == shape_to.type) { const auto &p = cgal_object.point(cgal_object.target(cgal_object.next(hi))); int i = i_from * 2; int j = (i_from + 1 == int(contour.size())) ? 0 : i + 2; i += vertex_index.vertex_base; j += vertex_index.vertex_base; auto abcp = shape_from.type == IntersectingElemnt::Type::face_1 ? CGAL::orientation( cgal_shape.point(CGAL::SM_Vertex_index(i)), cgal_shape.point(CGAL::SM_Vertex_index(i + 1)), cgal_shape.point(CGAL::SM_Vertex_index(j)), p) : // shape_from.type == IntersectingElemnt::Type::face_2 CGAL::orientation( cgal_shape.point(CGAL::SM_Vertex_index(j)), cgal_shape.point(CGAL::SM_Vertex_index(i + 1)), cgal_shape.point(CGAL::SM_Vertex_index(j + 1)), p); is_inside = abcp == CGAL::POSITIVE; } else if (i_from < i_to || (i_from == i_to && shape_from.type < shape_to.type)) { bool is_last = i_from == 0 && static_cast(i_to + 1) == contour.size(); if (!is_last) is_inside = true; } else { // i_from > i_to || (i_from == i_to && shape_from.type > shape_to.type) bool is_last = i_to == 0 && static_cast(i_from + 1) == contour.size(); if (is_last) is_inside = true; } if (is_inside) { // Is this face oriented towards p or away from p? const auto &a = cgal_object.point(cgal_object.source(hi)); const auto &b = cgal_object.point(cgal_object.target(hi)); const auto &c = cgal_object.point(cgal_object.target(cgal_object.next(hi))); //FIXME prosim nahrad skutecnou projekci. //projection.project() const auto p = a + MeshBoolean::cgal2::EpicKernel::Vector_3(0, 0, 10); auto abcp = CGAL::orientation(a, b, c, p); if (abcp == CGAL::POSITIVE) side_type = SideType::inside; else is_inside = false; } if (!is_inside) side_type = SideType::outside; break; } // next half edge index inside of face hi = cgal_object.next(hi); } while (hi != hi_end); side_type_map[fi] = side_type; } // debug output auto face_colors = cgal_object.add_property_map("f:color").first; for (auto fi : cgal_object.faces()) { auto &color = face_colors[fi]; switch (side_type_map[fi]) { case SideType::inside: color = CGAL::Color{255, 0, 0}; break; case SideType::outside: color = CGAL::Color{255, 0, 255}; break; case SideType::not_constrained: color = CGAL::Color{0, 255, 0}; break; } } CGAL::IO::write_OFF("c:\\data\\temp\\constrained.off", cgal_object); // Seed fill the other faces inside the region. for (Visitor::face_descriptor fi : cgal_object.faces()) { if (side_type_map[fi] != SideType::not_constrained) continue; // check if neighbor face is inside Visitor::halfedge_descriptor hi = cgal_object.halfedge(fi); Visitor::halfedge_descriptor hi_end = hi; bool has_inside_neighbor = false; std::vector queue; do { Visitor::face_descriptor fi_opposite = cgal_object.face(cgal_object.opposite(hi)); SideType side = side_type_map[fi_opposite]; if (side == SideType::inside) { has_inside_neighbor = true; } else if (side == SideType::not_constrained) { queue.emplace_back(fi_opposite); } hi = cgal_object.next(hi); } while (hi != hi_end); if (!has_inside_neighbor) continue; side_type_map[fi] = SideType::inside; while (!queue.empty()) { Visitor::face_descriptor fi = queue.back(); queue.pop_back(); // Do not fill twice if (side_type_map[fi] == SideType::inside) continue; side_type_map[fi] = SideType::inside; // check neighbor triangle Visitor::halfedge_descriptor hi = cgal_object.halfedge(fi); Visitor::halfedge_descriptor hi_end = hi; do { Visitor::face_descriptor fi_opposite = cgal_object.face(cgal_object.opposite(hi)); SideType side = side_type_map[fi_opposite]; if (side == SideType::not_constrained) queue.emplace_back(fi_opposite); hi = cgal_object.next(hi); } while (hi != hi_end); } } // debug output for (auto fi : cgal_object.faces()) { auto &color = face_colors[fi]; switch (side_type_map[fi]) { case SideType::inside: color = CGAL::Color{255, 0, 0}; break; case SideType::outside: color = CGAL::Color{255, 0, 255}; break; case SideType::not_constrained: color = CGAL::Color{0, 255, 0}; break; } } CGAL::IO::write_OFF("c:\\data\\temp\\filled.off", cgal_object); // Mapping of its_extruded faces to source faces. enum class FaceState : int8_t { Unknown = -1, Unmarked = -2, UnmarkedSplit = -3, Marked = -4, MarkedSplit = -5, UnmarkedEmitted = -6, }; std::vector face_states(cube.indices.size(), FaceState::Unknown); for (auto fi_seed : cgal_object.faces()) { FaceState &state = face_states[face_map[fi_seed]]; bool is_face_inside = side_type_map[fi_seed] == SideType::inside; switch (state) { case FaceState::Unknown: state = is_face_inside ? FaceState::Marked : FaceState::Unmarked; break; case FaceState::Unmarked: case FaceState::UnmarkedSplit: state = is_face_inside ? FaceState::MarkedSplit : FaceState::UnmarkedSplit; break; case FaceState::Marked: case FaceState::MarkedSplit: state = FaceState::MarkedSplit; break; default: assert(false); } } indexed_triangle_set its_extruded; its_extruded.indices.reserve(cgal_object.number_of_faces()); its_extruded.vertices.reserve(cgal_object.number_of_vertices()); // Mapping of its_extruded vertices (original and offsetted) to cgalcuble's vertices. std::vector> map_vertices(cgal_object.number_of_vertices(), std::pair{-1, -1}); Vec3f extrude_dir { 0, 0, 5.f }; for (auto fi : cgal_object.faces()) { const int32_t source_face_id = face_map[fi]; const FaceState state = face_states[source_face_id]; assert(state == FaceState::Unmarked || state == FaceState::UnmarkedSplit || state == FaceState::UnmarkedEmitted || state == FaceState::Marked || state == FaceState::MarkedSplit); if (state == FaceState::UnmarkedEmitted) continue; // Already emitted. if (state == FaceState::Unmarked || state == FaceState::UnmarkedSplit) { // Just copy the unsplit source face. const Vec3i source_vertices = cube.indices[source_face_id]; Vec3i target_vertices; for (int i = 0; i < 3; ++i) { target_vertices(i) = map_vertices[source_vertices(i)].first; if (target_vertices(i) == -1) { map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size()); its_extruded.vertices.emplace_back(cube.vertices[source_vertices(i)]); } } its_extruded.indices.emplace_back(target_vertices); face_states[source_face_id] = FaceState::UnmarkedEmitted; continue; // revert modification } auto hi = cgal_object.halfedge(fi); auto hi_prev = cgal_object.prev(hi); auto hi_next = cgal_object.next(hi); const Vec3i source_vertices{ int((std::size_t)cgal_object.target(hi)), int((std::size_t)cgal_object.target(hi_next)), int((std::size_t)cgal_object.target(hi_prev)) }; Vec3i target_vertices; if (side_type_map[fi] != SideType::inside) { // Copy the face. Vec3i target_vertices; for (int i = 0; i < 3; ++ i) { target_vertices(i) = map_vertices[source_vertices(i)].first; if (target_vertices(i) == -1) { map_vertices[source_vertices(i)].first = target_vertices(i) = int(its_extruded.vertices.size()); const auto &p = cgal_object.point(cgal_object.target(hi)); its_extruded.vertices.emplace_back(p.x(), p.y(), p.z()); } hi = cgal_object.next(hi); } its_extruded.indices.emplace_back(target_vertices); continue; // copy splitted triangle } // Extrude the face. Neighbor edges separating extruded face from // non-extruded face will be extruded. bool boundary_vertex[3] = {false, false, false}; Vec3i target_vertices_extruded{-1, -1, -1}; for (int i = 0; i < 3; ++i) { if (side_type_map[cgal_object.face(cgal_object.opposite(hi))] != SideType::inside) // Edge separating extruded / non-extruded region. boundary_vertex[i] = true; hi = cgal_object.next(hi); } for (int i = 0; i < 3; ++i) { target_vertices_extruded(i) = map_vertices[source_vertices(i)].second; if (target_vertices_extruded(i) == -1) { map_vertices[source_vertices(i)].second = target_vertices_extruded(i) = int( its_extruded.vertices.size()); const auto &p = cgal_object.point(cgal_object.target(hi)); its_extruded.vertices.emplace_back( Vec3f{float(p.x()), float(p.y()), float(p.z())} + extrude_dir); } if (boundary_vertex[i]) { target_vertices(i) = map_vertices[source_vertices(i)].first; if (target_vertices(i) == -1) { map_vertices[source_vertices(i)].first = target_vertices( i) = int(its_extruded.vertices.size()); const auto &p = cgal_object.point(cgal_object.target(hi)); its_extruded.vertices.emplace_back(p.x(), p.y(), p.z()); } } hi = cgal_object.next(hi); } its_extruded.indices.emplace_back(target_vertices_extruded); // Add the sides. for (int i = 0; i < 3; ++i) { int j = (i + 1) % 3; assert(target_vertices_extruded[i] != -1 && target_vertices_extruded[j] != -1); if (boundary_vertex[i] && boundary_vertex[j]) { assert(target_vertices[i] != -1 && target_vertices[j] != -1); its_extruded.indices.emplace_back( Vec3i{target_vertices[i], target_vertices[j], target_vertices_extruded[i]}); its_extruded.indices.emplace_back( Vec3i{target_vertices_extruded[i], target_vertices[j], target_vertices_extruded[j]}); } } } its_write_obj(its_extruded, "c:\\data\\temp\\text-extruded.obj"); indexed_triangle_set edges_its; std::vector edges_its_colors; for (auto ei : cgal_object.edges()) if (cgal_object.is_valid(ei)) { const auto &p1 = cgal_object.point(cgal_object.vertex(ei, 0)); const auto &p2 = cgal_object.point(cgal_object.vertex(ei, 1)); bool constrained = get(ecm, ei); Vec3f color = constrained ? Vec3f{ 1.f, 0, 0 } : Vec3f{ 0, 1., 0 }; edges_its.indices.emplace_back(Vec3i(edges_its.vertices.size(), edges_its.vertices.size() + 1, edges_its.vertices.size() + 2)); edges_its.vertices.emplace_back(Vec3f(p1.x(), p1.y(), p1.z())); edges_its.vertices.emplace_back(Vec3f(p2.x(), p2.y(), p2.z())); edges_its.vertices.emplace_back(Vec3f(p2.x(), p2.y(), p2.z() + 0.001)); edges_its_colors.emplace_back(color); edges_its_colors.emplace_back(color); edges_its_colors.emplace_back(color); } its_write_obj(edges_its, edges_its_colors, "c:\\data\\temp\\corefined-edges.obj"); // MeshBoolean::cgal::minus(cube, cube2); // REQUIRE(!MeshBoolean::cgal::does_self_intersect(cube)); }