Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVojtech Bubnik <bubnikv@gmail.com>2022-03-09 12:43:19 +0300
committerVojtech Bubnik <bubnikv@gmail.com>2022-03-09 12:43:19 +0300
commit6959fa63927a40a99d5d6e53aa0219c2e3eadf60 (patch)
treef333b073c0cfeb0e8210353b4710afbcf3beceaa
parentdf9220e5a8c3f8e6938d2e7ff8fd3611e01e51a4 (diff)
Experimental CGAL based code for curved text extrusionvb_emboss
-rw-r--r--tests/libslic3r/test_emboss-backup.cpp758
-rw-r--r--tests/libslic3r/test_emboss.cpp557
-rw-r--r--tests/libslic3r/test_triangulation.cpp3
3 files changed, 1317 insertions, 1 deletions
diff --git a/tests/libslic3r/test_emboss-backup.cpp b/tests/libslic3r/test_emboss-backup.cpp
new file mode 100644
index 000000000..bfe867c96
--- /dev/null
+++ b/tests/libslic3r/test_emboss-backup.cpp
@@ -0,0 +1,758 @@
+#include <catch2/catch.hpp>
+
+#include <libslic3r/Emboss.hpp>
+#include <libslic3r/SVG.hpp> // only debug visualization
+
+#include <optional>
+#include <libslic3r/AABBTreeIndirect.hpp>
+#include <libslic3r/Utils.hpp> // for next_highest_power_of_2()
+
+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<double> 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<double>::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<Vec2d, 3> &triangle)
+{
+ std::optional<double> 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<double> 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<Vec3f> &vertices)
+{
+ double c1 = h.u;
+ double c2 = h.v;
+ double c0 = 1.0 - c1 - c2;
+ Vec3d v0 = vertices[triangle[0]].cast<double>();
+ Vec3d v1 = vertices[triangle[1]].cast<double>();
+ Vec3d v2 = vertices[triangle[2]].cast<double>();
+ 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
+
+#include "imgui/imstb_truetype.h"
+TEST_CASE("Emboss text - Times MacOs", "[Emboss]") {
+
+ std::string font_path = "C:/Users/filip/Downloads/Times.ttc";
+ //std::string font_path = "//System/Library/Fonts/Times.ttc";
+ char letter = 'C';
+ float flatness = 2.;
+ 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<unsigned char> buffer(size);
+ size_t count_loaded_bytes = fread((void *) &buffer.front(), 1, size, file);
+ REQUIRE(count_loaded_bytes == size);
+ 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);
+}
+
+#include <libslic3r/Utils.hpp>
+TEST_CASE("Emboss text", "[Emboss]")
+{
+ std::string font_path = std::string(TEST_DATA_DIR) + "/fonts/NotoSans-Regular.ttf";
+ char letter = '%';
+ float flatness = 2.;
+
+ auto font = Emboss::load_font(font_path.c_str());
+ REQUIRE(font != nullptr);
+
+ std::optional<Emboss::Glyph> glyph = Emboss::letter2glyph(*font, 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());
+}
+
+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<double>::epsilon());
+}
+
+TEST_CASE("triangle intersection", "[]")
+{
+ Vec2d point(1, 1);
+ Vec2d dir(-1, 0);
+ std::array<Vec2d, 3> 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<double>::epsilon());
+ CHECK(abs(i.y() - 1.) < std::numeric_limits<double>::epsilon());
+}
+
+#ifndef __APPLE__
+#include <string>
+#include <iostream>
+#include <filesystem>
+namespace fs = std::filesystem;
+TEST_CASE("Italic check", "[]")
+{
+ std::queue<std::string> dir_paths;
+#ifdef _WIN32
+ dir_paths.push("C:/Windows/Fonts");
+#elif defined(__linux__)
+ dir_paths.push("/usr/share/fonts");
+#endif
+ 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 (entry.is_directory()) {
+ dir_paths.push(act_path.u8string());
+ continue;
+ }
+ std::string ext = act_path.extension().u8string();
+ 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.u8string();
+ auto font_opt = Emboss::load_font(path_str.c_str());
+ if (font_opt == nullptr) continue;
+ if (Emboss::is_italic(*font_opt))
+ exist_italic = true;
+ else
+ exist_non_italic = true;
+ //std::cout << ((Emboss::is_italic(*font_opt)) ? "[yes] " : "[no ] ") << entry.path() << std::endl;
+ }
+ }
+ CHECK(exist_italic);
+ CHECK(exist_non_italic);
+}
+#endif // not __APPLE__
+
+#include <CGAL/Polygon_mesh_processing/corefinement.h>
+#include <CGAL/Exact_integer.h>
+#include <CGAL/Surface_mesh.h>
+#include <CGAL/Cartesian_converter.h>
+
+struct GlyphID
+{
+ int32_t glyph { -1 };
+ int32_t expoly { -1 };
+ int32_t contour { -1 };
+ // vertex or edge ID, where edge ID is the index of the source point.
+ int32_t idx { -1 };
+
+ GlyphID& operator++() { ++idx; return *this; }
+};
+
+namespace Slic3r::MeshBoolean::cgal2 {
+
+ namespace CGALProc = CGAL::Polygon_mesh_processing;
+ namespace CGALParams = CGAL::Polygon_mesh_processing::parameters;
+
+ using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel;
+ using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
+ using _EpicMesh = CGAL::Surface_mesh<EpicKernel::Point_3>;
+ using _EpecMesh = CGAL::Surface_mesh<EpecKernel::Point_3>;
+
+ using CGALMesh = _EpicMesh;
+
+ void triangle_mesh_to_cgal(const std::vector<stl_vertex>& V,
+ const std::vector<stl_triangle_vertex_indices>& F,
+ CGALMesh& out,
+ CGALMesh::Property_map<CGAL::SM_Face_index, int32_t> object_face_source_id)
+ {
+ if (F.empty()) return;
+
+ size_t vertices_count = V.size();
+ size_t edges_count = (F.size() * 3) / 2;
+ size_t faces_count = F.size();
+ out.reserve(vertices_count, edges_count, faces_count);
+
+ for (auto& v : V)
+ out.add_vertex(typename CGALMesh::Point{ v.x(), v.y(), v.z() });
+
+ using VI = typename CGALMesh::Vertex_index;
+ for (auto& f : F) {
+ auto fid = out.add_face(VI(f(0)), VI(f(1)), VI(f(2)));
+ object_face_source_id[fid] = int32_t(&f - &F.front());
+ }
+ }
+
+ void glyph2model(
+ const ExPolygons& glyph,
+ int32_t glyph_id,
+ const Slic3r::Emboss::IProject& projection,
+ CGALMesh& out,
+ typename CGALMesh::Property_map<CGAL::SM_Edge_index, GlyphID> &glyph_id_edge,
+ typename CGALMesh::Property_map<CGAL::SM_Face_index, GlyphID> &glyph_id_face)
+ {
+ std::vector<CGALMesh::Vertex_index> indices;
+ auto insert_contour = [&projection, &indices , &out, glyph_id, &glyph_id_edge, &glyph_id_face](const Polygon& polygon, int32_t iexpoly, int32_t id) {
+ indices.clear();
+ indices.reserve(polygon.points.size() * 2);
+ for (const Point& p2 : polygon.points) {
+ auto p = projection.project(p2);
+ indices.emplace_back(out.add_vertex(typename CGALMesh::Point{ p.first.x(), p.first.y(), p.first.z() }));
+ indices.emplace_back(out.add_vertex(typename CGALMesh::Point{ p.second.x(), p.second.y(), p.second.z() }));
+ }
+ for (int32_t i = 0; i < int32_t(indices.size()); i += 2) {
+ int32_t j = (i + 2) % int32_t(indices.size());
+ auto find_edge = [&out](CGALMesh::Face_index fi, CGALMesh::Vertex_index from, CGALMesh::Vertex_index to) {
+ CGALMesh::Halfedge_index hi = out.halfedge(fi);
+ for (; out.target(hi) != to; hi = out.next(hi));
+ assert(out.source(hi) == from);
+ assert(out.target(hi) == to);
+ return hi;
+ };
+ auto fi = out.add_face(indices[i], indices[i + 1], indices[j]);
+ GlyphID glid { glyph_id, iexpoly, id, i * 2 };
+ glyph_id_edge[out.edge(find_edge(fi, indices[i], indices[i + 1]))] = glid;
+ glyph_id_face[fi] = ++ glid;
+ glyph_id_edge[out.edge(find_edge(fi, indices[i + 1], indices[j]))] = ++ glid;
+ glyph_id_face[out.add_face(indices[j], indices[i + 1], indices[j + 1])] = ++ glid;
+ }
+ };
+
+ size_t count_point = count_points(glyph);
+ out.reserve(out.number_of_vertices() + 2 * count_point, out.number_of_edges() + 4 * count_point, out.number_of_faces() + 2 * count_point);
+
+ for (const ExPolygon &expolygon : glyph) {
+ int32_t idx_contour = &expolygon - &glyph.front();
+ insert_contour(expolygon.contour, idx_contour, 0);
+ for (const Polygon& hole : expolygon.holes)
+ insert_contour(hole, idx_contour, 1 + (&hole - &expolygon.holes.front()));
+ }
+ }
+}
+
+bool its_write_obj(const indexed_triangle_set& its, const std::vector<Vec3f> &color, const char* file)
+{
+ Slic3r::CNumericLocalesSetter locales_setter;
+ FILE* fp = fopen(file, "w");
+ if (fp == nullptr) {
+ return false;
+ }
+
+ for (size_t i = 0; i < its.vertices.size(); ++i)
+ fprintf(fp, "v %f %f %f %f %f %f\n",
+ its.vertices[i](0), its.vertices[i](1), its.vertices[i](2),
+ color[i](0), color[i](1), color[i](2));
+ for (size_t i = 0; i < its.indices.size(); ++i)
+ fprintf(fp, "f %d %d %d\n", its.indices[i][0] + 1, its.indices[i][1] + 1, its.indices[i][2] + 1);
+ fclose(fp);
+ return true;
+}
+
+TEST_CASE("Emboss extrude cut", "[Emboss-Cut]")
+{
+ std::string font_path = std::string(TEST_DATA_DIR) + "/fonts/NotoSans-Regular.ttf";
+ char letter = '%';
+ float flatness = 2.;
+
+ auto font = Emboss::load_font(font_path.c_str());
+ REQUIRE(font != nullptr);
+
+ std::optional<Emboss::Glyph> glyph = Emboss::letter2glyph(*font, letter, flatness);
+ REQUIRE(glyph.has_value());
+
+ ExPolygons shape = glyph->shape;
+ REQUIRE(!shape.empty());
+
+ float z_depth = 10.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));
+
+ MeshBoolean::cgal2::CGALMesh cgalcube, cgaltext;
+ auto object_face_source_id = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Face_index, int32_t>("f:object_face_source_id").first;
+ MeshBoolean::cgal2::triangle_mesh_to_cgal(cube.vertices, cube.indices, cgalcube, object_face_source_id);
+
+ auto edge_glyph_id = cgaltext.add_property_map<MeshBoolean::cgal2::CGALMesh::Edge_index, GlyphID>("e:glyph_id").first;
+ auto face_glyph_id = cgaltext.add_property_map<MeshBoolean::cgal2::CGALMesh::Face_index, GlyphID>("f:glyph_id").first;
+ auto vertex_glyph_id = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Vertex_index, GlyphID>("v:glyph_id").first;
+
+ MeshBoolean::cgal2::glyph2model(shape, 0, projection, cgaltext, edge_glyph_id, face_glyph_id);
+
+ struct Visitor {
+ using TriangleMesh = Slic3r::MeshBoolean::cgal2::CGALMesh;
+
+ const TriangleMesh &object;
+ const TriangleMesh &glyphs;
+ // Properties of the glyphs mesh:
+ TriangleMesh::Property_map<CGAL::SM_Edge_index, GlyphID> glyph_id_edge;
+ TriangleMesh::Property_map<CGAL::SM_Face_index, GlyphID> glyph_id_face;
+ // Properties of the object mesh.
+ TriangleMesh::Property_map<CGAL::SM_Face_index, int32_t> object_face_source_id;
+ TriangleMesh::Property_map<CGAL::SM_Vertex_index, GlyphID> object_vertex_glyph_id;
+
+ typedef boost::graph_traits<TriangleMesh> 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;
+
+ void before_subface_creations(face_descriptor f_old, TriangleMesh& mesh)
+ {
+ assert(&mesh == &object);
+ source_face_id = object_face_source_id[f_old];
+ }
+ void after_subface_created(face_descriptor f_new, TriangleMesh& mesh) {
+ assert(&mesh == &object);
+ object_face_source_id[f_new] = source_face_id;
+ }
+
+ std::vector<const GlyphID*> 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 TriangleMesh& tm_edge,
+ // Mesh containing hh_face
+ const TriangleMesh& 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 GlyphID* glyph = nullptr;
+ if (&tm_face == &glyphs) {
+ assert(&tm_edge == &object);
+ switch (simplex_dimension) {
+ case 1:
+ // edge x edge intersection
+ glyph = &glyph_id_edge[glyphs.edge(hh_face)];
+ break;
+ case 2:
+ // edge x face intersection
+ glyph = &glyph_id_face[glyphs.face(hh_face)];
+ break;
+ default:
+ assert(false);
+ }
+ if (edge_source_coplanar_with_face)
+ object_vertex_glyph_id[object.source(hh_edge)] = *glyph;
+ if (edge_target_coplanar_with_face)
+ object_vertex_glyph_id[object.target(hh_edge)] = *glyph;
+ }
+ else {
+ assert(&tm_edge == &glyphs && &tm_face == &object);
+ assert(!edge_source_coplanar_with_face);
+ assert(!edge_target_coplanar_with_face);
+ glyph = &glyph_id_edge[glyphs.edge(hh_edge)];
+ if (simplex_dimension)
+ object_vertex_glyph_id[object.target(hh_face)] = *glyph;
+ }
+ intersection_point_glyph[i_id] = glyph;
+ }
+
+ void new_vertex_added(std::size_t node_id, vertex_descriptor vh, const TriangleMesh &tm)
+ {
+ assert(&tm == &object);
+ assert(node_id < intersection_point_glyph.size());
+ const GlyphID * glyph = intersection_point_glyph[node_id];
+ assert(glyph != nullptr);
+ assert(glyph->glyph != -1);
+ assert(glyph->expoly != -1);
+ assert(glyph->contour != -1);
+ assert(glyph->idx != -1);
+ object_vertex_glyph_id[vh] = glyph ? *glyph : GlyphID{};
+ }
+
+ void after_subface_creations(TriangleMesh&) {}
+ void before_subface_created(TriangleMesh&) {}
+ void before_edge_split(halfedge_descriptor /* h */, TriangleMesh& /* tm */) {}
+ void edge_split(halfedge_descriptor /* hnew */, TriangleMesh& /* tm */) {}
+ void after_edge_split() {}
+ void add_retriangulation_edge(halfedge_descriptor /* h */, TriangleMesh& /* tm */) {}
+ }
+ visitor { cgalcube, cgaltext, edge_glyph_id, face_glyph_id, object_face_source_id, vertex_glyph_id };
+
+ auto ecm = get(CGAL::dynamic_edge_property_t<bool>(), cgalcube);
+ auto ecm2 = get(CGAL::dynamic_edge_property_t<bool>(), cgaltext);
+ 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::throw_on_self_intersection(false).visitor(visitor).edge_is_constrained_map(ecm2).do_not_modify(true);
+ // CGAL::Polygon_mesh_processing::corefine(cgalcube, cgalcube2, p, p);
+
+ CGAL::Polygon_mesh_processing::corefine(cgalcube, cgaltext, p, q);
+
+ auto vertex_colors = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Vertex_index, CGAL::Color>("v:color").first;
+ auto face_colors = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Face_index, CGAL::Color>("f:color").first;
+
+ const CGAL::Color marked { 255, 0, 0 };
+ for (auto fi : cgalcube.faces()) {
+ CGAL::Color color(0, 255, 0);
+ auto hi_end = cgalcube.halfedge(fi);
+ auto hi = hi_end;
+ do {
+ if (get(ecm, cgalcube.edge(hi))) {
+ // This face has a constrained edge.
+ GlyphID g1 = vertex_glyph_id[cgalcube.source(hi)];
+ GlyphID g2 = vertex_glyph_id[cgalcube.target(hi)];
+ assert(g1.glyph != -1 && g1.glyph == g2.glyph);
+ assert(g1.expoly != -1 && g1.expoly == g2.expoly);
+ assert(g1.contour != -1 && g1.contour == g2.contour);
+ assert(g1.idx != -1);
+ assert(g2.idx != -1);
+ const auto &expoly = glyph->shape[g1.expoly];
+ const auto &contour = g1.contour == 0 ? expoly.contour : expoly.holes[g1.contour - 1];
+ bool ccw = false;
+ int32_t i1 = g1.idx / 4;
+ int32_t i2 = g2.idx / 4;
+ if (g1.idx < g2.idx) {
+ if (i1 == 0 && i2 + 1 == contour.size()) {
+ // cw
+ } else {
+ ccw = true;
+ }
+ } else {
+ if (i2 == 0 && i1 + 1 == contour.size()) {
+ ccw = true;
+ std::swap(g1, g2);
+ std::swap(i1, i2);
+ }
+ }
+ if (ccw) {
+ // Face is left of a constrained edge.
+ // Check whether the face is facing forward or backwards.
+ int type = g1.idx % 4;
+ // The object vertex intersects glyph edge. Take front point of the intersecting glyph edge.
+ const auto& p = cgaltext.point(MeshBoolean::cgal2::CGALMesh::Vertex_index((i1 + (type >= 2)) * 2 + 1));
+ // Is this face oriented towards p or away from p?
+ auto abcp = CGAL::orientation(cgalcube.point(cgalcube.source(hi)), cgalcube.point(cgalcube.target(hi)), cgalcube.point(cgalcube.target(cgalcube.next(hi))), p);
+ assert(abcp != CGAL::COPLANAR);
+ if (abcp == CGAL::POSITIVE)
+ color = marked;
+ }
+ break;
+ }
+ hi = cgalcube.next(hi);
+ } while (hi != hi_end);
+ face_colors[fi] = color;
+ }
+
+ // Seed fill the other faces inside the region.
+ std::vector<MeshBoolean::cgal2::CGALMesh::Face_index> queue;
+ for (auto fi_seed : cgalcube.faces())
+ if (face_colors[fi_seed] != marked) {
+ // Is this face completely unconstrained?
+ auto hi = cgalcube.halfedge(fi_seed);
+ auto hi_prev = cgalcube.prev(hi);
+ auto hi_next = cgalcube.next(hi);
+ if (! get(ecm, cgalcube.edge(hi)) && ! get(ecm, cgalcube.edge(hi_prev)) && ! get(ecm, cgalcube.edge(hi_next))) {
+ queue.emplace_back(fi_seed);
+ do {
+ auto fi = queue.back();
+ queue.pop_back();
+ auto hi = cgalcube.halfedge(fi);
+ auto hi_prev = cgalcube.prev(hi);
+ auto hi_next = cgalcube.next(hi);
+ assert(! get(ecm, cgalcube.edge(hi)) && ! get(ecm, cgalcube.edge(hi_prev)) && ! get(ecm, cgalcube.edge(hi_next)));
+ auto this_opposite = cgalcube.face(cgalcube.opposite(hi));
+ bool this_marked = face_colors[this_opposite] == marked;
+ auto prev_opposite = cgalcube.face(cgalcube.opposite(hi_prev));
+ bool prev_marked = face_colors[prev_opposite] == marked;
+ auto next_opposite = cgalcube.face(cgalcube.opposite(hi_next));
+ bool next_marked = face_colors[next_opposite] == marked;
+ int num_marked = this_marked + prev_marked + next_marked;
+ if (num_marked >= 2) {
+ face_colors[fi] = marked;
+ if (num_marked == 2)
+ queue.emplace_back(! this_marked ? this_opposite : ! prev_marked ? prev_opposite : next_opposite);
+ }
+ } while (! queue.empty());
+ }
+ }
+
+ // Mapping of its_extruded faces to source faces.
+ enum class FaceType : int32_t {
+ Unknown = -1,
+ Unmarked = -2,
+ UnmarkedSplit = -3,
+ Marked = -4,
+ MarkedSplit = -5,
+ UnmarkedEmitted = -6,
+ };
+ std::vector<int32_t> map_faces(cube.indices.size(), int32_t(FaceType::Unknown));
+ for (auto fi_seed : cgalcube.faces()) {
+ int32_t &state = map_faces[object_face_source_id[fi_seed]];
+ bool m = face_colors[fi_seed] == marked;
+ switch (state) {
+ case int32_t(FaceType::Unknown):
+ state = m ? int32_t(FaceType::Marked) : int32_t(FaceType::Unmarked);
+ break;
+ case int32_t(FaceType::Unmarked):
+ case int32_t(FaceType::UnmarkedSplit):
+ state = m ? int32_t(FaceType::MarkedSplit) : int32_t(FaceType::UnmarkedSplit);
+ break;
+ case int32_t(FaceType::Marked):
+ case int32_t(FaceType::MarkedSplit):
+ state = int32_t(FaceType::MarkedSplit);
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+ CGAL::IO::write_OFF("c:\\data\\temp\\corefined.off", cgalcube);
+
+ indexed_triangle_set its_extruded;
+ its_extruded.indices.reserve(cgalcube.number_of_faces());
+ its_extruded.vertices.reserve(cgalcube.number_of_vertices());
+ // Mapping of its_extruded vertices (original and offsetted) to cgalcuble's vertices.
+ std::vector<std::pair<int32_t, int32_t>> map_vertices(cgalcube.number_of_vertices(), std::pair<int32_t, int32_t>{-1, -1});
+
+ Vec3f extrude_dir { 0, 0, 5.f };
+ for (auto fi : cgalcube.faces()) {
+ const int32_t source_face_id = object_face_source_id[fi];
+ const int32_t state = map_faces[source_face_id];
+ assert(state == int32_t(FaceType::Unmarked) || state == int32_t(FaceType::UnmarkedSplit) || state == int32_t(FaceType::UnmarkedEmitted) ||
+ state == int32_t(FaceType::Marked) || state == int32_t(FaceType::MarkedSplit));
+ if (state == int32_t(FaceType::UnmarkedEmitted)) {
+ // Already emitted.
+ } else if (state == int32_t(FaceType::Unmarked) || state == int32_t(FaceType::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);
+ map_faces[source_face_id] = int32_t(FaceType::UnmarkedEmitted);
+ } else {
+ auto hi = cgalcube.halfedge(fi);
+ auto hi_prev = cgalcube.prev(hi);
+ auto hi_next = cgalcube.next(hi);
+ const Vec3i source_vertices{ int((std::size_t)cgalcube.target(hi)), int((std::size_t)cgalcube.target(hi_next)), int((std::size_t)cgalcube.target(hi_prev)) };
+ Vec3i target_vertices;
+ if (face_colors[fi] == marked) {
+ // 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 (face_colors[cgalcube.face(cgalcube.opposite(hi))] != marked)
+ // Edge separating extruded / non-extruded region.
+ boundary_vertex[i] = boundary_vertex[(i + 2) % 3] = true;
+ hi = cgalcube.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 = cgalcube.point(cgalcube.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 = cgalcube.point(cgalcube.target(hi));
+ its_extruded.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+ }
+ hi = cgalcube.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] });
+ }
+ }
+ } else {
+ // 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 = cgalcube.point(cgalcube.target(hi));
+ its_extruded.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+ hi = cgalcube.next(hi);
+ }
+ its_extruded.indices.emplace_back(target_vertices);
+ }
+ }
+ }
+
+ its_write_obj(its_extruded, "c:\\data\\temp\\text-extruded.obj");
+
+ indexed_triangle_set edges_its;
+ std::vector<Vec3f> edges_its_colors;
+ for (auto ei : cgalcube.edges())
+ if (cgalcube.is_valid(ei)) {
+ const auto &p1 = cgalcube.point(cgalcube.vertex(ei, 0));
+ const auto &p2 = cgalcube.point(cgalcube.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));
+}
diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp
index 7f7045ffe..546e416ac 100644
--- a/tests/libslic3r/test_emboss.cpp
+++ b/tests/libslic3r/test_emboss.cpp
@@ -5,6 +5,7 @@
#include <optional>
#include <libslic3r/AABBTreeIndirect.hpp>
+#include <libslic3r/Utils.hpp> // for next_highest_power_of_2()
using namespace Slic3r;
@@ -250,3 +251,559 @@ TEST_CASE("Italic check", "[]")
CHECK(exist_non_italic);
}
#endif // not __APPLE__
+
+#include <CGAL/Polygon_mesh_processing/corefinement.h>
+#include <CGAL/Exact_integer.h>
+#include <CGAL/Surface_mesh.h>
+#include <CGAL/Cartesian_converter.h>
+
+// Referencing a glyph contour (an ExPolygon) plus a vertex base of the contour.
+struct GlyphContour {
+ // Index of a glyph in a vector of glyphs.
+ int32_t glyph{ -1 };
+ // Index of an ExPolygon in ExPolygons of a glyph.
+ 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 };
+};
+
+struct GlyphID
+{
+ int32_t glyph_contour{ -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
+ int32_t idx { -1 };
+
+ GlyphID& operator++() { ++idx; return *this; }
+};
+
+namespace Slic3r::MeshBoolean::cgal2 {
+
+ namespace CGALProc = CGAL::Polygon_mesh_processing;
+ namespace CGALParams = CGAL::Polygon_mesh_processing::parameters;
+
+// using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel;
+ using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel;
+ using _EpicMesh = CGAL::Surface_mesh<EpicKernel::Point_3>;
+// using _EpecMesh = CGAL::Surface_mesh<EpecKernel::Point_3>;
+
+ using CGALMesh = _EpicMesh;
+
+ // Add an indexed triangle mesh to CGAL Surface_mesh.
+ // Store map of CGAL face to source face index into object_face_source_id.
+ void triangle_mesh_to_cgal(
+ const std::vector<stl_vertex> &V,
+ const std::vector<stl_triangle_vertex_indices> &F,
+ CGALMesh &out,
+ CGALMesh::Property_map<CGAL::SM_Face_index, int32_t> object_face_source_id)
+ {
+ if (F.empty())
+ return;
+
+ size_t vertices_count = V.size();
+ size_t edges_count = (F.size() * 3) / 2;
+ size_t faces_count = F.size();
+ out.reserve(vertices_count, edges_count, faces_count);
+
+ for (auto& v : V)
+ out.add_vertex(typename CGALMesh::Point{ v.x(), v.y(), v.z() });
+
+ using VI = typename CGALMesh::Vertex_index;
+ for (auto& f : F) {
+ auto fid = out.add_face(VI(f(0)), VI(f(1)), VI(f(2)));
+ object_face_source_id[fid] = int32_t(&f - &F.front());
+ }
+ }
+
+ void glyph2model(
+ const ExPolygons &glyph,
+ int32_t glyph_id,
+ const Slic3r::Emboss::IProject &projection,
+ CGALMesh &out,
+ std::vector<GlyphContour> &glyph_contours,
+ CGALMesh::Property_map<CGAL::SM_Edge_index, GlyphID> &glyph_id_edge,
+ CGALMesh::Property_map<CGAL::SM_Face_index, GlyphID> &glyph_id_face)
+ {
+ std::vector<CGALMesh::Vertex_index> indices;
+ auto insert_contour = [&projection, &indices , &out, glyph_id, &glyph_contours, &glyph_id_edge, &glyph_id_face](const Polygon& polygon, int32_t iexpoly, int32_t id) {
+ indices.clear();
+ indices.reserve(polygon.points.size() * 2);
+ size_t num_vertices_old = out.number_of_vertices();
+ GlyphID glid{ int32_t(glyph_contours.size()), 0 };
+ glyph_contours.push_back({ glyph_id, iexpoly, id, int32_t(num_vertices_old) });
+ for (const Point& p2 : polygon.points) {
+ auto p = projection.project(p2);
+ auto vi = out.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 = out.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);
+ }
+ for (int32_t i = 0; i < int32_t(indices.size()); i += 2) {
+ int32_t j = (i + 2) % int32_t(indices.size());
+ auto find_edge = [&out](CGALMesh::Face_index fi, CGALMesh::Vertex_index from, CGALMesh::Vertex_index to) {
+ CGALMesh::Halfedge_index hi = out.halfedge(fi);
+ for (; out.target(hi) != to; hi = out.next(hi));
+ assert(out.source(hi) == from);
+ assert(out.target(hi) == to);
+ return hi;
+ };
+ auto fi = out.add_face(indices[i], indices[i + 1], indices[j]);
+ glyph_id_edge[out.edge(find_edge(fi, indices[i], indices[i + 1]))] = glid;
+ glyph_id_face[fi] = ++ glid;
+ glyph_id_edge[out.edge(find_edge(fi, indices[i + 1], indices[j]))] = ++ glid;
+ glyph_id_face[out.add_face(indices[j], indices[i + 1], indices[j + 1])] = ++ glid;
+ ++ glid;
+ }
+ };
+
+ size_t count_point = count_points(glyph);
+ out.reserve(out.number_of_vertices() + 2 * count_point, out.number_of_edges() + 4 * count_point, out.number_of_faces() + 2 * count_point);
+
+ for (const ExPolygon &expolygon : glyph) {
+ int32_t idx_contour = &expolygon - &glyph.front();
+ insert_contour(expolygon.contour, idx_contour, 0);
+ for (const Polygon& hole : expolygon.holes)
+ insert_contour(hole, idx_contour, 1 + (&hole - &expolygon.holes.front()));
+ }
+ }
+}
+
+bool its_write_obj(const indexed_triangle_set& its, const std::vector<Vec3f> &color, const char* file)
+{
+ Slic3r::CNumericLocalesSetter locales_setter;
+ FILE* fp = fopen(file, "w");
+ if (fp == nullptr) {
+ return false;
+ }
+
+ for (size_t i = 0; i < its.vertices.size(); ++i)
+ fprintf(fp, "v %f %f %f %f %f %f\n",
+ its.vertices[i](0), its.vertices[i](1), its.vertices[i](2),
+ color[i](0), color[i](1), color[i](2));
+ for (size_t i = 0; i < its.indices.size(); ++i)
+ fprintf(fp, "f %d %d %d\n", its.indices[i][0] + 1, its.indices[i][1] + 1, its.indices[i][2] + 1);
+ fclose(fp);
+ return true;
+}
+
+TEST_CASE("Emboss extrude cut", "[Emboss-Cut]")
+{
+ std::string font_path = std::string(TEST_DATA_DIR) + "/fonts/NotoSans-Regular.ttf";
+ char letter = '%';
+ float flatness = 2.;
+
+ auto font = Emboss::load_font(font_path.c_str());
+ REQUIRE(font != nullptr);
+
+ std::optional<Emboss::Glyph> glyph = Emboss::letter2glyph(*font, 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(0, -40, 40));
+ 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);
+
+ MeshBoolean::cgal2::CGALMesh cgalcube, cgaltext;
+ auto object_face_source_id = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Face_index, int32_t>("f:object_face_source_id").first;
+ MeshBoolean::cgal2::triangle_mesh_to_cgal(cube.vertices, cube.indices, cgalcube, object_face_source_id);
+
+ auto edge_glyph_id = cgaltext.add_property_map<MeshBoolean::cgal2::CGALMesh::Edge_index, GlyphID>("e:glyph_id").first;
+ auto face_glyph_id = cgaltext.add_property_map<MeshBoolean::cgal2::CGALMesh::Face_index, GlyphID>("f:glyph_id").first;
+ auto vertex_glyph_id = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Vertex_index, GlyphID>("v:glyph_id").first;
+ std::vector<GlyphContour> glyph_contours;
+
+ MeshBoolean::cgal2::glyph2model(shape, 0, projection, cgaltext, glyph_contours, edge_glyph_id, face_glyph_id);
+
+ struct Visitor {
+ using TriangleMesh = Slic3r::MeshBoolean::cgal2::CGALMesh;
+
+ const TriangleMesh &object;
+ const TriangleMesh &glyphs;
+// const std::vector<GlyphContour> &glyph_contours;
+ // Properties of the glyphs mesh:
+ TriangleMesh::Property_map<CGAL::SM_Edge_index, GlyphID> glyph_id_edge;
+ TriangleMesh::Property_map<CGAL::SM_Face_index, GlyphID> glyph_id_face;
+ // Properties of the object mesh.
+ TriangleMesh::Property_map<CGAL::SM_Face_index, int32_t> object_face_source_id;
+ TriangleMesh::Property_map<CGAL::SM_Vertex_index, GlyphID> object_vertex_glyph_id;
+
+ typedef boost::graph_traits<TriangleMesh> 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;
+
+ void before_subface_creations(face_descriptor f_old, TriangleMesh& mesh)
+ {
+ assert(&mesh == &object);
+ source_face_id = object_face_source_id[f_old];
+ }
+ void after_subface_created(face_descriptor f_new, TriangleMesh& mesh) {
+ assert(&mesh == &object);
+ object_face_source_id[f_new] = source_face_id;
+ }
+
+ std::vector<const GlyphID*> 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 TriangleMesh& tm_edge,
+ // Mesh containing hh_face
+ const TriangleMesh& 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 GlyphID* glyph = nullptr;
+ if (&tm_face == &glyphs) {
+ assert(&tm_edge == &object);
+ switch (simplex_dimension) {
+ case 1:
+ // edge x edge intersection
+ glyph = &glyph_id_edge[glyphs.edge(hh_face)];
+ break;
+ case 2:
+ // edge x face intersection
+ glyph = &glyph_id_face[glyphs.face(hh_face)];
+ break;
+ default:
+ assert(false);
+ }
+ if (edge_source_coplanar_with_face)
+ object_vertex_glyph_id[object.source(hh_edge)] = *glyph;
+ if (edge_target_coplanar_with_face)
+ object_vertex_glyph_id[object.target(hh_edge)] = *glyph;
+ } else {
+ assert(&tm_edge == &glyphs && &tm_face == &object);
+ assert(!edge_source_coplanar_with_face);
+ assert(!edge_target_coplanar_with_face);
+ glyph = &glyph_id_edge[glyphs.edge(hh_edge)];
+ if (simplex_dimension == 0)
+ object_vertex_glyph_id[object.target(hh_face)] = *glyph;
+ }
+ intersection_point_glyph[i_id] = glyph;
+ }
+
+ void new_vertex_added(std::size_t node_id, vertex_descriptor vh, const TriangleMesh &tm)
+ {
+ assert(&tm == &object);
+ assert(node_id < intersection_point_glyph.size());
+ const GlyphID * glyph = intersection_point_glyph[node_id];
+ assert(glyph != nullptr);
+ assert(glyph->glyph_contour != -1);
+ assert(glyph->idx != -1);
+ object_vertex_glyph_id[vh] = glyph ? *glyph : GlyphID{};
+ }
+
+ void after_subface_creations(TriangleMesh&) {}
+ void before_subface_created(TriangleMesh&) {}
+ void before_edge_split(halfedge_descriptor /* h */, TriangleMesh& /* tm */) {}
+ void edge_split(halfedge_descriptor /* hnew */, TriangleMesh& /* tm */) {}
+ void after_edge_split() {}
+ void add_retriangulation_edge(halfedge_descriptor /* h */, TriangleMesh& /* tm */) {}
+ }
+ visitor { cgalcube, cgaltext, /* glyph_contours, */ edge_glyph_id, face_glyph_id, object_face_source_id, vertex_glyph_id};
+
+ auto ecm = get(CGAL::dynamic_edge_property_t<bool>(), cgalcube);
+ 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::visitor(visitor).do_not_modify(true);
+ // CGAL::Polygon_mesh_processing::corefine(cgalcube, cgalcube2, p, p);
+
+ CGAL::Polygon_mesh_processing::corefine(cgalcube, cgaltext, p, q);
+
+ auto vertex_colors = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Vertex_index, CGAL::Color>("v:color").first;
+ auto face_colors = cgalcube.add_property_map<MeshBoolean::cgal2::CGALMesh::Face_index, CGAL::Color>("f:color").first;
+
+ const CGAL::Color marked { 255, 0, 0 };
+ for (auto fi : cgalcube.faces()) {
+ CGAL::Color color(0, 255, 0);
+ auto hi_end = cgalcube.halfedge(fi);
+ auto hi = hi_end;
+ do {
+ if (get(ecm, cgalcube.edge(hi))) {
+ // This face has a constrained edge.
+ GlyphID g1 = vertex_glyph_id[cgalcube.source(hi)];
+ GlyphID g2 = vertex_glyph_id[cgalcube.target(hi)];
+ assert(g1.glyph_contour != -1 && g1.glyph_contour == g2.glyph_contour);
+ assert(g1.idx != -1);
+ assert(g2.idx != -1);
+ const GlyphContour &glyph_contour = glyph_contours[g1.glyph_contour];
+ const auto &expoly = glyph->shape[glyph_contour.expoly];
+ const auto &contour = glyph_contour.contour == 0 ? expoly.contour : expoly.holes[glyph_contour.contour - 1];
+ bool inside = false;
+ int32_t i1 = g1.idx / 4;
+ int32_t i2 = g2.idx / 4;
+ if (g1.idx == g2.idx) {
+ // Crossing both object vertices with the same glyph face.
+ int type = g1.idx % 4;
+ assert(type == 1 || type == 3);
+ const auto& p = cgalcube.point(cgalcube.target(cgalcube.next(hi)));
+ int i = i1 * 2;
+ int j = (i1 + 1 == int(contour.size())) ? 0 : i + 2;
+ i += glyph_contour.vertex_base;
+ j += glyph_contour.vertex_base;
+ auto abcp = type == 1 ?
+ CGAL::orientation(cgaltext.point(CGAL::SM_Vertex_index(i)), cgaltext.point(CGAL::SM_Vertex_index(i + 1)), cgaltext.point(CGAL::SM_Vertex_index(j)), p) :
+ CGAL::orientation(cgaltext.point(CGAL::SM_Vertex_index(j)), cgaltext.point(CGAL::SM_Vertex_index(i + 1)), cgaltext.point(CGAL::SM_Vertex_index(j + 1)), p);
+ inside = abcp == CGAL::POSITIVE;
+ } else if (g1.idx < g2.idx) {
+ if (i1 == 0 && i2 + 1 == contour.size()) {
+ // cw
+ } else {
+ inside = true;
+ }
+ } else {
+ if (i2 == 0 && i1 + 1 == contour.size()) {
+ inside = true;
+ std::swap(g1, g2);
+ std::swap(i1, i2);
+ }
+ }
+ if (inside) {
+ // Is this face oriented towards p or away from p?
+ const auto &a = cgalcube.point(cgalcube.source(hi));
+ const auto &b = cgalcube.point(cgalcube.target(hi));
+ const auto &c = cgalcube.point(cgalcube.target(cgalcube.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)
+ color = marked;
+ }
+ break;
+ }
+ hi = cgalcube.next(hi);
+ } while (hi != hi_end);
+ face_colors[fi] = color;
+ }
+
+ CGAL::IO::write_OFF("c:\\data\\temp\\corefined-0.off", cgalcube);
+
+ // Seed fill the other faces inside the region.
+ std::vector<MeshBoolean::cgal2::CGALMesh::Face_index> queue;
+ for (auto fi_seed : cgalcube.faces())
+ if (face_colors[fi_seed] != marked) {
+ // Is this face completely unconstrained?
+ auto hi = cgalcube.halfedge(fi_seed);
+ auto hi_prev = cgalcube.prev(hi);
+ auto hi_next = cgalcube.next(hi);
+ if (! get(ecm, cgalcube.edge(hi)) && ! get(ecm, cgalcube.edge(hi_prev)) && ! get(ecm, cgalcube.edge(hi_next))) {
+ queue.emplace_back(fi_seed);
+ do {
+ auto fi = queue.back();
+ queue.pop_back();
+ auto hi = cgalcube.halfedge(fi);
+ auto hi_prev = cgalcube.prev(hi);
+ auto hi_next = cgalcube.next(hi);
+ // The following condition may not apply if crossing a silhouette wrt. the glyph projection direction.
+// assert(! get(ecm, cgalcube.edge(hi)) && ! get(ecm, cgalcube.edge(hi_prev)) && ! get(ecm, cgalcube.edge(hi_next)));
+ auto this_opposite = cgalcube.face(cgalcube.opposite(hi));
+ bool this_marked = face_colors[this_opposite] == marked;
+ auto prev_opposite = cgalcube.face(cgalcube.opposite(hi_prev));
+ bool prev_marked = face_colors[prev_opposite] == marked;
+ auto next_opposite = cgalcube.face(cgalcube.opposite(hi_next));
+ bool next_marked = face_colors[next_opposite] == marked;
+ int num_marked = this_marked + prev_marked + next_marked;
+ if (num_marked >= 2) {
+ face_colors[fi] = marked;
+ if (num_marked == 2)
+ queue.emplace_back(! this_marked ? this_opposite : ! prev_marked ? prev_opposite : next_opposite);
+ }
+ } while (! queue.empty());
+ }
+ }
+
+ CGAL::IO::write_OFF("c:\\data\\temp\\corefined.off", cgalcube);
+
+ // 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<FaceState> face_states(cube.indices.size(), FaceState::Unknown);
+ for (auto fi_seed : cgalcube.faces()) {
+ FaceState &state = face_states[object_face_source_id[fi_seed]];
+ bool m = face_colors[fi_seed] == marked;
+ switch (state) {
+ case FaceState::Unknown:
+ state = m ? FaceState::Marked : FaceState::Unmarked;
+ break;
+ case FaceState::Unmarked:
+ case FaceState::UnmarkedSplit:
+ state = m ? 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(cgalcube.number_of_faces());
+ its_extruded.vertices.reserve(cgalcube.number_of_vertices());
+ // Mapping of its_extruded vertices (original and offsetted) to cgalcuble's vertices.
+ std::vector<std::pair<int32_t, int32_t>> map_vertices(cgalcube.number_of_vertices(), std::pair<int32_t, int32_t>{-1, -1});
+
+ Vec3f extrude_dir { 0, 0, 5.f };
+ for (auto fi : cgalcube.faces()) {
+ const int32_t source_face_id = object_face_source_id[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) {
+ // Already emitted.
+ } else 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;
+ } else {
+ auto hi = cgalcube.halfedge(fi);
+ auto hi_prev = cgalcube.prev(hi);
+ auto hi_next = cgalcube.next(hi);
+ const Vec3i source_vertices{ int((std::size_t)cgalcube.target(hi)), int((std::size_t)cgalcube.target(hi_next)), int((std::size_t)cgalcube.target(hi_prev)) };
+ Vec3i target_vertices;
+ if (face_colors[fi] == marked) {
+ // 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 (face_colors[cgalcube.face(cgalcube.opposite(hi))] != marked)
+ // Edge separating extruded / non-extruded region.
+ boundary_vertex[i] = boundary_vertex[(i + 2) % 3] = true;
+ hi = cgalcube.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 = cgalcube.point(cgalcube.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 = cgalcube.point(cgalcube.target(hi));
+ its_extruded.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+ }
+ hi = cgalcube.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] });
+ }
+ }
+ } else {
+ // 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 = cgalcube.point(cgalcube.target(hi));
+ its_extruded.vertices.emplace_back(p.x(), p.y(), p.z());
+ }
+ hi = cgalcube.next(hi);
+ }
+ its_extruded.indices.emplace_back(target_vertices);
+ }
+ }
+ }
+
+ its_write_obj(its_extruded, "c:\\data\\temp\\text-extruded.obj");
+
+ indexed_triangle_set edges_its;
+ std::vector<Vec3f> edges_its_colors;
+ for (auto ei : cgalcube.edges())
+ if (cgalcube.is_valid(ei)) {
+ const auto &p1 = cgalcube.point(cgalcube.vertex(ei, 0));
+ const auto &p2 = cgalcube.point(cgalcube.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));
+}
diff --git a/tests/libslic3r/test_triangulation.cpp b/tests/libslic3r/test_triangulation.cpp
index bc0f371d7..f0143b696 100644
--- a/tests/libslic3r/test_triangulation.cpp
+++ b/tests/libslic3r/test_triangulation.cpp
@@ -40,7 +40,7 @@ void store_trinagulation(const ExPolygons & shape,
}
} // namespace
-
+/*
TEST_CASE("Triangulate rectangle with restriction on edge", "[Triangulation]")
{
// 0 1 2 3
@@ -89,6 +89,7 @@ TEST_CASE("Triangulation polygon", "[triangulation]")
CHECK(tp.size() == tep.size());
CHECK(tp.size() == 3);
}
+*/
TEST_CASE("Triangulation M shape polygon", "[triangulation]")
{