diff --git a/resources/icons/emboss.svg b/resources/icons/emboss.svg
new file mode 100644
index 0000000000..fdea19b774
--- /dev/null
+++ b/resources/icons/emboss.svg
@@ -0,0 +1,63 @@
+
+
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index f9bef903bc..89363bd239 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -33,6 +33,8 @@ add_library(libslic3r STATIC
EdgeGrid.hpp
ElephantFootCompensation.cpp
ElephantFootCompensation.hpp
+ Emboss.cpp
+ Emboss.hpp
enum_bitmask.hpp
ExPolygon.cpp
ExPolygon.hpp
diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp
new file mode 100644
index 0000000000..c7837671b7
--- /dev/null
+++ b/src/libslic3r/Emboss.cpp
@@ -0,0 +1,111 @@
+#include "Emboss.hpp"
+#include
+
+#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation
+#include "imgui/imstb_truetype.h" // stbtt_fontinfo
+
+using namespace Slic3r;
+
+std::optional Emboss::load_font(const char *file_path)
+{
+ FILE *file = fopen(file_path, "rb");
+ if (file == nullptr) {
+ std::cerr << "Couldn't open " << file_path << " for reading.";
+ return {};
+ }
+
+ // find size of file
+ if (fseek(file, 0L, SEEK_END) != 0) {
+ std::cerr << "Couldn't fseek file " << file_path << " for size measure.";
+ return {};
+ }
+ size_t size = ftell(file);
+ if (size == 0) {
+ std::cerr << "Size of font file is zero. Can't read.";
+ return {};
+ }
+ rewind(file);
+
+ Font res;
+ res.buffer = std::vector(size);
+ size_t count_loaded_bytes = fread((void *) &res.buffer.front(), 1, size, file);
+
+ unsigned int index = 0;
+ int font_offset = 0;
+ while (font_offset >= 0) {
+ font_offset = stbtt_GetFontOffsetForIndex(res.buffer.data(), index++);
+ }
+ // at least one font must be inside collection
+ if (index < 1) {
+ std::cerr << "There is no font collection inside file.";
+ return {};
+ }
+ // select default font on index 0
+ res.index = 0;
+ res.count = index;
+ // first fonst has offset zero
+ font_offset = 0;
+
+ stbtt_fontinfo font_info;
+ if (stbtt_InitFont(&font_info, res.buffer.data(), font_offset) == 0) {
+ std::cerr << "Can't initialize font.";
+ return {};
+ }
+
+ // load information about line gap
+ stbtt_GetFontVMetrics(&(font_info), &res.ascent, &res.descent, &res.linegap);
+ return res;
+}
+
+Polygons Emboss::letter2polygons(const Font &font, char letter)
+{
+ int font_offset = stbtt_GetFontOffsetForIndex(font.buffer.data(), font.index);
+ stbtt_fontinfo font_info;
+ if (stbtt_InitFont(&font_info, font.buffer.data(), font_offset) == 0)
+ return Polygons();
+
+ int glyph_index = stbtt_FindGlyphIndex(&(font_info), letter);
+ int advanceWidth, leftSideBearing;
+ stbtt_GetGlyphHMetrics(&(font_info), glyph_index, &advanceWidth,
+ &leftSideBearing);
+
+ stbtt_vertex *vertices;
+ int num_verts = stbtt_GetGlyphShape(&(font_info), glyph_index, &vertices);
+ if (num_verts < 0) return {}; // no shape
+
+ int * contour_lengths = NULL;
+ int num_countour = 0;
+ stbtt__point *points = stbtt_FlattenCurves(vertices, num_verts, font.flatness,
+ &contour_lengths,
+ &num_countour,
+ (font_info).userdata);
+
+ Polygons result;
+ result.reserve(num_countour);
+ size_t pi = 0; // point index
+ for (size_t ci = 0; ci < num_countour; ++ci) {
+ int length = contour_lengths[ci];
+ Points pts;
+ pts.reserve(length);
+ for (size_t i = 0; i < length; i++) {
+ const stbtt__point &point = points[pi];
+ pi++;
+ pts.emplace_back(point.x, point.y);
+ }
+ result.emplace_back(pts);
+ }
+
+ BoundingBox bb;
+ for (auto &r : result) bb.merge(r.points);
+
+ // inner ccw
+ // outer cw
+ return result;
+}
+
+indexed_triangle_set Emboss::create_model(const std::string &text,
+ const Font & font,
+ float z_size)
+{
+ return indexed_triangle_set();
+}
diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp
new file mode 100644
index 0000000000..4086f775dd
--- /dev/null
+++ b/src/libslic3r/Emboss.hpp
@@ -0,0 +1,73 @@
+#ifndef slic3r_Emboss_hpp_
+#define slic3r_Emboss_hpp_
+
+#include
+#include
+#include // indexed_triangle_set
+#include "Polygon.hpp"
+
+namespace Slic3r {
+
+///
+/// class with only static function add ability to engraved OR raised
+/// text OR polygons onto model surface
+///
+class Emboss
+{
+public:
+ Emboss() = delete;
+
+ ///
+ /// keep information from file about font
+ ///
+ struct Font
+ {
+ // loaded data from font file
+ std::vector buffer;
+
+ unsigned int index=0; // index of actual file info in collection
+ unsigned int count=0; // count of fonts in file collection
+
+ // vertical position is "scale*(ascent - descent + lineGap)"
+ int ascent=0, descent=0, linegap=0;
+ // user defined unscaled char space
+ int extra_char_space = 0;
+
+ // unscaled precision of lettter outline curve in conversion to lines
+ float flatness = 2.;
+
+ // change size of font
+ float scale = 1.;
+ };
+
+ ///
+ /// Load font file into buffer
+ ///
+ /// Location of .ttf or .ttc font file
+ /// Font object when loaded.
+ static std::optional load_font(const char *file_path);
+
+ ///
+ /// convert letter into polygons
+ ///
+ /// Define fonts
+ /// Character to convert
+ /// inner polygon ccw(outer cw)
+ static Polygons letter2polygons(const Font &font, char letter);
+
+ static Polygons text2polygons(const Font &font, const std::string &text);
+
+ ///
+ /// Create triangle model for text
+ ///
+ ///
+ ///
+ ///
+ ///
+ static indexed_triangle_set create_model(const std::string &text,
+ const Font & font,
+ float z_size);
+};
+
+} // namespace Slic3r
+#endif // slic3r_Emboss_hpp_
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 34f1a188d2..f959307010 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -37,6 +37,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmosCommon.hpp
GUI/Gizmos/GLGizmoBase.cpp
GUI/Gizmos/GLGizmoBase.hpp
+ GUI/Gizmos/GLGizmoEmboss.cpp
+ GUI/Gizmos/GLGizmoEmboss.hpp
GUI/Gizmos/GLGizmoMove.cpp
GUI/Gizmos/GLGizmoMove.hpp
GUI/Gizmos/GLGizmoRotate.cpp
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
new file mode 100644
index 0000000000..dee4cd3b4d
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp
@@ -0,0 +1,124 @@
+#include "GLGizmoEmboss.hpp"
+#include "slic3r/GUI/GLCanvas3D.hpp"
+#include "slic3r/GUI/GUI_App.hpp"
+#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
+#include "slic3r/GUI/GUI_ObjectList.hpp"
+#include "slic3r/GUI/NotificationManager.hpp"
+#include "slic3r/GUI/Plater.hpp"
+
+#include "libslic3r/Model.hpp"
+#include "libslic3r/QuadricEdgeCollapse.hpp"
+
+namespace Slic3r::GUI {
+
+GLGizmoEmboss::GLGizmoEmboss(GLCanvas3D & parent,
+ const std::string &icon_filename,
+ unsigned int sprite_id)
+ : GLGizmoBase(parent, icon_filename, sprite_id)
+ , m_fonts({{"NotoSans Regular",
+ Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf"},
+ {"Arial", "C:/windows/fonts/arialbd.ttf"}})
+ , m_selected(1)
+ , m_text_size(255)
+ , m_text(new char[255])
+{
+ // TODO: suggest to use https://fontawesome.com/
+ int index = 0;
+ for (char &c : _u8L("Embossed text")) {
+ m_text[index++] = c;
+ }
+ m_text[index] = '\0';
+}
+
+GLGizmoEmboss::~GLGizmoEmboss() {}
+
+bool GLGizmoEmboss::on_init()
+{
+ //m_grabbers.emplace_back();
+ m_shortcut_key = WXK_CONTROL_Q;
+ return true;
+}
+
+std::string GLGizmoEmboss::on_get_name() const
+{
+ return (_L("Emboss")).ToUTF8().data();
+}
+
+void GLGizmoEmboss::on_render() {}
+void GLGizmoEmboss::on_render_for_picking() {}
+
+void GLGizmoEmboss::on_render_input_window(float x, float y, float bottom_limit)
+{
+ int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoCollapse;
+ m_imgui->begin(on_get_name(), flag);
+ ImGui::Text("welcome in emboss text");
+ auto& current = m_fonts[m_selected];
+ if (ImGui::BeginCombo("##font_selector", current.name.c_str())) {
+ for (const MyFont &f : m_fonts) {
+ ImGui::PushID((void*)&f.name);
+ if (ImGui::Selectable(f.name.c_str(), &f == ¤t)) {
+ m_selected = &f - &m_fonts.front();
+ }
+ ImGui::PopID();
+ }
+ ImGui::EndCombo();
+ }
+ ImGui::SameLine();
+ ImGui::Button("Add");
+
+ size_t text_size = 255;
+ //sizeof(*m_text.get());
+ ImVec2 input_size(-FLT_MIN, ImGui::GetTextLineHeight() * 6);
+ ImGuiInputTextFlags flags =
+ ImGuiInputTextFlags_::ImGuiInputTextFlags_AllowTabInput
+ | ImGuiInputTextFlags_::ImGuiInputTextFlags_AutoSelectAll
+ //|ImGuiInputTextFlags_::ImGuiInputTextFlags_CtrlEnterForNewLine
+ ;
+ if (ImGui::InputTextMultiline("##Text", m_text.get(), text_size, input_size, flags)) {
+
+ }
+
+ ImVec2 t0(25, 25);
+ ImVec2 t1(150, 5);
+ ImVec2 t2(10, 100);
+ ImU32 c = ImGui::ColorConvertFloat4ToU32(ImVec4(.0, .8, .2, 1.));
+ ImGui::GetOverlayDrawList()->AddTriangleFilled(t0, t1, t2, c);
+
+ m_imgui->end();
+}
+
+
+bool GLGizmoEmboss::on_is_activable() const
+{
+ return !m_parent.get_selection().is_empty();
+}
+
+void GLGizmoEmboss::on_set_state()
+{
+ // Closing gizmo. e.g. selecting another one
+ if (GLGizmoBase::m_state == GLGizmoBase::Off) {
+
+ // refuse outgoing during simlification
+ if (false) {
+ GLGizmoBase::m_state = GLGizmoBase::On;
+ auto notification_manager = wxGetApp().plater()->get_notification_manager();
+ notification_manager->push_notification(
+ NotificationType::CustomNotification,
+ NotificationManager::NotificationLevel::RegularNotification,
+ _u8L("ERROR: Wait until ends or Cancel process."));
+ return;
+ }
+
+ } else if (GLGizmoBase::m_state == GLGizmoBase::On) {
+ // when open by hyperlink it needs to show up
+ //request_rerender();
+ }
+}
+
+void GLGizmoEmboss::close() {
+ // close gizmo == open it again
+ GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
+ gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
+}
+} // namespace Slic3r::GUI
diff --git a/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
new file mode 100644
index 0000000000..13ec92098f
--- /dev/null
+++ b/src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
@@ -0,0 +1,83 @@
+#ifndef slic3r_GLGizmoEmboss_hpp_
+#define slic3r_GLGizmoEmboss_hpp_
+
+// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code,
+// which overrides our localization "L" macro.
+#include "GLGizmoBase.hpp"
+#include "admesh/stl.h" // indexed_triangle_set
+#include
+#include
+
+namespace Slic3r {
+class ModelVolume;
+namespace GUI {
+
+class GLGizmoEmboss : public GLGizmoBase
+{
+public:
+ GLGizmoEmboss(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
+ virtual ~GLGizmoEmboss();
+protected:
+ virtual bool on_init() override;
+ virtual std::string on_get_name() const override;
+ virtual void on_render() override;
+ virtual void on_render_for_picking() override;
+ virtual void on_render_input_window(float x, float y, float bottom_limit) override;
+ virtual bool on_is_activable() const override;
+ virtual bool on_is_selectable() const override { return true; }
+ virtual void on_set_state() override;
+
+private:
+ void close();
+
+ struct MyFont;
+ MyFont createFont(const char * fileName);
+
+ struct Configuration
+ {
+ bool use_count = false;
+ // minimal triangle count
+ float decimate_ratio = 50.f; // in percent
+ uint32_t wanted_count = 0; // initialize by percents
+
+ // maximal quadric error
+ float max_error = 1.;
+
+ void fix_count_by_ratio(size_t triangle_count)
+ {
+ wanted_count = static_cast(
+ std::round(triangle_count * (100.f-decimate_ratio) / 100.f));
+ }
+ } m_configuration;
+
+ // This configs holds GUI layout size given by translated texts.
+ // etc. When language changes, GUI is recreated and this class constructed again,
+ // so the change takes effect. (info by GLGizmoFdmSupports.hpp)
+ struct GuiCfg
+ {
+
+ };
+ std::optional m_gui_cfg;
+
+ struct MyFont
+ {
+ std::string name;
+ std::string file_path;
+
+ MyFont(const std::string &name, const std::string &file_path)
+ : name(name), file_path(file_path)
+ {}
+ };
+
+ std::vector m_fonts;
+ size_t m_selected;// index to m_fonts
+
+
+ size_t m_text_size; // allocated size by m_text
+ std::unique_ptr m_text;
+};
+
+} // namespace GUI
+} // namespace Slic3r
+
+#endif // slic3r_GLGizmoEmboss_hpp_
diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
index 5569830606..ec111236ee 100644
--- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
+++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp
@@ -21,6 +21,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
+#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@@ -104,6 +105,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
+ m_gizmos.emplace_back(new GLGizmoEmboss(m_parent, "emboss.svg", 10)); // TODO: fix icon
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
diff --git a/tests/libslic3r/test_meshboolean.cpp b/tests/libslic3r/test_meshboolean.cpp
index 97d03ac238..1db26381c7 100644
--- a/tests/libslic3r/test_meshboolean.cpp
+++ b/tests/libslic3r/test_meshboolean.cpp
@@ -24,3 +24,767 @@ TEST_CASE("CGAL and TriangleMesh conversions", "[MeshBoolean]") {
REQUIRE(! MeshBoolean::cgal::does_self_intersect(M));
}
+
+Vec3d calc_normal(const Vec3i &triangle, const std::vector &vertices)
+{
+ Vec3d v0 = vertices[triangle[0]].cast();
+ Vec3d v1 = vertices[triangle[1]].cast();
+ Vec3d v2 = vertices[triangle[2]].cast();
+ // n = triangle normal
+ Vec3d n = (v1 - v0).cross(v2 - v0);
+ n.normalize();
+ return n;
+}
+
+TEST_CASE("Add TriangleMeshes", "[MeshBoolean]")
+{
+ TriangleMesh tm1 = make_sphere(1.6, 1.6);
+ Vec3f move(5, -3, 7);
+ move.normalize();
+ tm1.translate(0.3 * move);
+ its_write_obj(tm1.its, "tm1.obj");
+ TriangleMesh tm2 = make_cube(1., 1., 1.);
+ its_write_obj(tm2.its, "tm2.obj");
+ MeshBoolean::cgal::plus(tm1, tm2);
+ its_write_obj(tm1.its, "test_add.obj");
+}
+
+#include "libslic3r/Emboss.hpp"
+
+Polygons ttf2polygons(const char * font_name, char letter, float flatness = 1.f) {
+ auto font = Emboss::load_font(font_name);
+ if (!font.has_value()) return Polygons();
+ font->flatness = flatness;
+ return Emboss::letter2polygons(*font, letter);
+}
+
+#include "libslic3r/SVG.hpp"
+void store_to_svg(Polygons polygons,std::string file_name = "letter.svg")
+{
+ double scale = 1e6;
+ for (auto &p : polygons) p.scale(scale);
+ SVG svg("letter.svg", BoundingBox(polygons.front().points));
+ svg.draw(polygons);
+}
+
+struct Plane
+{
+ Vec3d point = Vec3d(0., 0., 0.); // lay on plane - define zero position
+ Vec3d normal = Vec3d(0., 0., 1.);// [unit vector] - define orientation
+};
+
+struct OrientedPlane: public Plane
+{
+ // Must be perpendiculat to normal
+ Vec3d up = Vec3d(0., 1., 0.); // [unit vector]
+};
+
+struct EmbossConfig
+{
+ // emboss plane must be above surface
+ // point define zero for 2d polygon and must be out of model
+ // normal is direction to model
+ // up define orientation of polygon
+ OrientedPlane projection;
+
+
+ // Move surface distance
+ // Positive value out of model (Raised)
+ // Negative value into model (Engraved)
+ float height = 1.; // [in milimeters]
+};
+
+struct FontConfig
+{
+ const char *font_path = "C:/windows/fonts/arialbd.ttf";
+ float flatness = 2.; // precision of lettter outline curve in conversion to lines
+ float scale = 1.0; // size of text
+ float letter_space = 1.; // unscaled space between letters
+ float line_space = 1.; // unscaled space between lines
+};
+
+struct Seam
+{
+ std::vector seam_points; // indexes of vertices which made seam
+ std::vector inner_faces; // indexes of triangle (its.indices) which are inside of polygon
+ std::vector outline_faces; // indexes of triangle (its.indices) contains seam points
+};
+
+
+#include
+#include
+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);
+}
+
+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 = calc_hit_point(hit, its);
+ CHECK(abs(hp.x() - ray_point.x()) < .1);
+ CHECK(abs(hp.y() - ray_point.y()) < .1);
+}
+
+// represents triangle extend by seam
+struct TrianglePath
+{
+ // input edge, output edge, when cross triangle border
+ std::optional> edges;
+
+ // when edges has value than first and last point lay on triangle edge
+ std::vector points;
+
+ // first point has index offset_id in result vertices
+ uint32_t offset_id;
+};
+using TrianglePaths = std::vector;
+
+std::vector triangulate(const Vec3i &triangle, const Vec3d &triangle_normal, const std::vector &vertices, const TrianglePaths& paths);
+
+// create transformation matrix to convert direction vectors
+// do not care about up vector
+// Directions are normalized
+Eigen::Matrix3d create_transformation(const Vec3d &from_dir, const Vec3d &to_dir)
+{
+ Vec3d axis = from_dir.cross(to_dir);
+ axis.normalize();
+ double angle = acos(from_dir.dot(to_dir));
+ auto rotation = Eigen::AngleAxisd(angle, axis);
+ return rotation.matrix();
+}
+
+TEST_CASE("Transformation matrix", "[]") {
+ Vec3d d1(3, -7, 13);
+ Vec3d d2(-9, 5, 1);
+ d1.normalize();
+ d2.normalize();
+ auto tr_mat = create_transformation(d1, d2);
+
+ Vec3d d1_tr = tr_mat * d1;
+ Vec3d diff = d1_tr - d2;
+ double eps = 1e-12;
+ for (double d : diff)
+ CHECK(abs(d) < std::numeric_limits::epsilon());
+}
+
+// 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;
+}
+
+TEST_CASE("ray segment intersection", "[MeshBoolean]")
+{
+ Vec2d r_point(1,1);
+ Vec2d r_dir(1,0);
+
+ // colinear
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(0,0), Vec2d(2,0)).has_value());
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(2,0), Vec2d(0,0)).has_value());
+
+ // before ray
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(0,0), Vec2d(0,2)).has_value());
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(0,2), Vec2d(0,0)).has_value());
+
+ // above ray
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(2,2), Vec2d(2,3)).has_value());
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(2,3), Vec2d(2,2)).has_value());
+
+ // belove ray
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(2,0), Vec2d(2,-1)).has_value());
+ CHECK(!ray_segment_intersection(r_point, r_dir, Vec2d(2,-1), Vec2d(2,0)).has_value());
+
+ // intersection at [2,1] distance 1
+ auto t1 = ray_segment_intersection(r_point, r_dir, Vec2d(2,0), Vec2d(2,2));
+ REQUIRE(t1.has_value());
+ auto t2 = 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());
+}
+
+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);
+}
+
+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 = get_intersection(point, dir, triangle);
+ CHECK(abs(i.x()) < std::numeric_limits::epsilon());
+ CHECK(abs(i.y() - 1.) < std::numeric_limits::epsilon());
+}
+
+#include
+
+std::optional create_seam(const Polygon &shape, const OrientedPlane& plane, indexed_triangle_set &its) {
+ // IMPROVE: create area of interests (mask for neighbors)
+ auto neighbors = its_face_edge_ids(its);
+ auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
+ its.vertices, its.indices);
+ const Vec3d z_axis(0, 0, 1);
+ // IMPROVE: parallelize
+ // collect intersection with model
+ const Vec3d& ray_dir = plane.normal;
+ Vec3d side = plane.up.cross(plane.normal);
+ std::vector hits;
+ hits.reserve(shape.points.size());
+ for (const Point &p : shape.points) {
+ Vec3d ray_point = plane.point +plane.up*p.y()+side*p.x();
+ igl::Hit hit;
+ AABBTreeIndirect::intersect_ray_first_hit(its.vertices, its.indices,
+ tree, ray_point, ray_dir,
+ hit);
+ hits.push_back(hit);
+ }
+
+ // triangle idx, changes inside triangle
+ std::map changes;
+
+ std::set remove_indices;
+ std::vector add_vertices;
+ std::vector add_indices;
+ for (size_t i = 0; i < shape.points.size(); ++i) {
+ size_t next_i = i + 1;
+ if (i == shape.points.size()) next_i = 0;
+
+ Point dir2d = shape.points[next_i] - shape.points[i];
+ Vec3d dir = dir2d.x() * side + dir2d.y() * plane.up;
+ const igl::Hit &start_hit = hits[i];
+
+ size_t ti = start_hit.id; // triangle index
+ const igl::Hit &end_hit = hits[next_i];
+
+ Vec3i t = its.indices[ti];
+ Vec3d hit_point = calc_hit_point(start_hit, t, its.vertices);
+
+ Vec3d start_point = hit_point;
+ std::optional edge_index;
+
+ while (ti != end_hit.id) {
+ Vec3d n = calc_normal(t, its.vertices); // triangle normal
+ Eigen::Matrix3d rotation = create_transformation(n, z_axis);
+ Vec3d t_dir = n.cross(dir).cross(n); // direction on triangle surface
+
+ auto remove_z = [&](const Vec3d &point) -> Vec2d {
+ Vec3d rotated_point = rotation * point;
+ assert(abs(rotated_point.z()) < 1e-5);
+ return Vec2d(rotated_point.x(), rotated_point.y());
+ };
+
+ std::array triangle2d;
+ for (size_t i = 0; i < 3; ++i)
+ triangle2d[i] = remove_z(its.vertices[t[i]].cast());
+
+ Vec2d start_point_2d = remove_z(start_point);
+
+ // move start point on edge of this triangle
+ if (edge_index.has_value()) {
+ size_t e2 = *edge_index +1;
+ if (e2 == 3) e2 = 0;
+ const Vec2d &p1 = triangle2d[*edge_index];
+ const Vec2d &p2 = triangle2d[e2];
+ Vec2d dir = p2 - p1;
+ start_point_2d = Geometry::foot_pt(p1, dir, start_point_2d);
+ }
+
+ Vec2d dir_point_2d = remove_z(start_point+t_dir);
+
+ Vec2d next_point_2d = get_intersection(start_point_2d, dir_point_2d, triangle2d);
+
+ //next_triangle =
+ // Find interection with triangle border from start point in direction t_dir
+ // ?? Convert to 2d?
+ }
+ }
+
+ // connect hits over surface
+ return {};
+}
+
+std::vector triangulate(const std::vector & points,
+ const std::vector> &edges);
+
+std::vector triangulate(const Polygon &polygon) {
+ const Points &pts = polygon.points;
+
+ std::vector points;
+ points.reserve(pts.size());
+ std::transform(pts.begin(), pts.end(), std::back_inserter(points),
+ [](const Point &p) -> Vec2f { return p.cast(); });
+
+ std::vector> edges;
+ edges.reserve(pts.size());
+ for (int i = 1; i < pts.size(); ++i) edges.emplace_back(i - 1, i);
+ edges.emplace_back(pts.size() - 1, 0);
+ return triangulate(points, edges);
+}
+
+
+indexed_triangle_set emboss3d(const Polygon &shape, float height) {
+ // CW order of triangle indices
+ std::vector shape_triangles = triangulate(shape);
+
+ indexed_triangle_set result;
+ const Points &pts = shape.points;
+ size_t count_point = pts.size();
+ result.vertices.reserve(2 * count_point);
+ // top points
+ std::transform(pts.begin(), pts.end(), std::back_inserter(result.vertices),
+ [](const Point &p) { return Vec3f(p.x(), p.y(), 0); });
+ // bottom points
+ std::transform(pts.begin(), pts.end(), std::back_inserter(result.vertices),
+ [height](const Point &p) { return Vec3f(p.x(), p.y(), height); });
+
+ result.indices.reserve(shape_triangles.size() * 2 + count_point*2);
+ // top triangles - change to CCW
+ for (const Vec3i &t : shape_triangles)
+ result.indices.emplace_back(t.x(), t.z(), t.y());
+ // bottom triangles - use CW
+ for (const Vec3i &t : shape_triangles)
+ result.indices.emplace_back(
+ t.x() + count_point,
+ t.y() + count_point,
+ t.z() + count_point
+ );
+
+ // quads around - zig zag by triangles
+ for (uint32_t i = 0; i < count_point; ++i) {
+ // previous index
+ uint32_t ip = (i == 0) ? (count_point - 1) : (i - 1);
+ // bottom indices
+ uint32_t i2 = i + count_point;
+ uint32_t ip2 = ip + count_point;
+
+ result.indices.emplace_back(i, i2, ip);
+ result.indices.emplace_back(ip2, ip, i2);
+ }
+ return result;
+}
+
+#include
+#include
+#include
+#include
+
+typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
+typedef CGAL::Exact_predicates_exact_constructions_kernel EK;
+typedef CGAL::Surface_mesh Mesh;
+namespace PMP = CGAL::Polygon_mesh_processing;
+namespace params = PMP::parameters;
+
+// is it realy neccessary to copy all?
+Mesh its_to_mesh(const indexed_triangle_set &its)
+{
+ Mesh mesh;
+ size_t vertices_count = its.vertices.size();
+ size_t edges_count = (its.indices.size() * 3) / 2;
+ size_t faces_count = its.indices.size();
+ mesh.reserve(vertices_count, edges_count, faces_count);
+
+ for (auto &v : its.vertices)
+ mesh.add_vertex(typename Mesh::Point{v.x(), v.y(), v.z()});
+
+ using VI = typename Mesh::Vertex_index;
+ for (auto &f : its.indices)
+ mesh.add_face(VI(f(0)), VI(f(1)), VI(f(2)));
+
+ return mesh;
+}
+
+indexed_triangle_set mesh_to_its(const Mesh &mesh)
+{
+ indexed_triangle_set res;
+ res.vertices.reserve(mesh.num_vertices());
+ res.indices.reserve(mesh.num_faces());
+
+ for (auto &vi : mesh.vertices()) {
+ auto &v = mesh.point(vi); // index addresing, to compare same float point
+ res.vertices.emplace_back(v.x(),v.y(), v.z());
+ }
+
+ for (auto &face : mesh.faces()) {
+ auto vtc = mesh.vertices_around_face(mesh.halfedge(face));
+ int i = 0;
+ Vec3i facet;
+ for (auto v : vtc) {
+ if (i > 2) {
+ i = 0;
+ break;
+ }
+ facet(i++) = v;
+ }
+ if (i == 3) res.indices.emplace_back(facet);
+ }
+ return res;
+}
+
+void emboss3d_(const Polygon& shape, const EmbossConfig &cfg, indexed_triangle_set &its)
+{
+ indexed_triangle_set shape3d = emboss3d(shape, cfg.height);
+
+ //its_write_obj(shape3d,"shape3d.obj");
+
+ Mesh m1 = its_to_mesh(its);
+ Mesh m2 = its_to_mesh(shape3d);
+
+ size_t in_vertices_count = its.vertices.size();
+ size_t in_indices_count = its.indices.size();
+
+ auto tt = m1.property_map("e:removed");
+ Mesh::Property_map ecm1 = tt.first; // hope in copy
+
+ PMP::corefine(m1, m2
+ , PMP::parameters::edge_is_constrained_map(ecm1)
+ //, PMP::parameters::do_not_modify(true)
+ );
+
+
+ //PMP::Corefinement::Intersection_of_triangle_meshes()
+
+ size_t count_true = 0;
+ for (const Mesh::Edge_index &e : edges(m1))
+ if (ecm1[e]) {
+ ++count_true;
+ const Mesh::Halfedge_index &h = e.halfedge();
+ const Mesh::Halfedge_index &h2 = m1.opposite(h);
+ const Mesh::Vertex_index &v = m1.target(h);
+ std::cout <<
+ "edge " << e <<
+ " halfedge " << h <<
+ " target " << v <<
+ " index0 " << v.idx() <<
+ " index1 " << m1.target(h2).idx() <<
+ std::endl;
+ }
+
+
+ its = mesh_to_its(m1);
+
+ // twice and move additional vertices
+ Vec3f move = (cfg.projection.normal * cfg.height).cast();
+ size_t new_add_vertice = its.vertices.size() - in_vertices_count;
+ its.vertices.reserve(its.vertices.size() + new_add_vertice);
+ for (size_t i = in_vertices_count; i < its.vertices.size(); ++i)
+ its.vertices.emplace_back(its.vertices[i] + move);
+
+ // zig zag edge collected edge
+ for (size_t i = in_indices_count; i < its.indices.size(); ++i) {
+ // new added triangle
+ Vec3i &t = its.indices[i];
+
+ std::array is_new;
+ bool is_inner = true;
+ for (size_t i = 0; i < 3; ++i) {
+ bool is_n = t[0] >= in_vertices_count;
+ is_inner |= is_n;
+ is_new[i] = is_n;
+ }
+
+ }
+
+ its_write_obj(mesh_to_its(m1), "corefine1.obj");
+ its_write_obj(mesh_to_its(m2), "corefine2.obj");
+
+ /*MeshBoolean::cgal::CGALMesh cm1 = m1->m;
+
+ My_visitor sm_v;
+
+ CGAL::Polygon_mesh_processing::corefine(m1->m, m2->m,
+ CGAL::Polygon_mesh_processing::parameters::visitor(sm_v)
+ , CGAL::parameters::do_not_modify(true)
+ );*/
+
+ //TriangleMesh tm1 = cgal_to_triangle_mesh(*m1);
+ //store_obj("tm1.obj", &tm1);
+ //TriangleMesh tm2 = cgal_to_triangle_mesh(*m2);
+ //store_obj("tm2.obj", &tm1);
+
+ its = mesh_to_its(m1);
+ return;
+}
+
+void emboss3d(const Polygon& shape, const EmbossConfig &cfg, indexed_triangle_set &its)
+{
+ indexed_triangle_set shape3d = emboss3d(shape, abs(cfg.height));
+ //its_write_obj(shape3d,"shape3d.obj");
+ auto m1 = MeshBoolean::cgal::triangle_mesh_to_cgal(its);
+ auto m2 = MeshBoolean::cgal::triangle_mesh_to_cgal(shape3d);
+
+ bool is_plus = shape.is_counter_clockwise() == (cfg.height > 0.f);
+ if (is_plus) {
+ MeshBoolean::cgal::plus(*m1, *m2);
+ } else {
+ MeshBoolean::cgal::minus(*m1, *m2);
+ }
+
+ TriangleMesh tm1 = cgal_to_triangle_mesh(*m1);
+ store_obj("tm1.obj", &tm1);
+ //TriangleMesh tm2 = cgal_to_triangle_mesh(*m2);
+ //store_obj("tm2.obj", &tm1);
+
+ its = tm1.its; // copy
+}
+
+TEST_CASE("Emboss polygon", "[MeshBoolean]")
+{
+ const char *font_name = "C:/windows/fonts/arialbd.ttf";
+ char letter = '%';
+ float flatness = 2.;
+ Polygons polygons = ttf2polygons(font_name, letter, flatness);
+ store_to_svg(polygons);
+
+ //TriangleMesh tm = make_sphere(1., 1.); tm.scale(10.f);
+
+ TriangleMesh tm = make_cube(10., 5., 2.);
+ tm.translate(Vec3f(0, 0, 1.7));
+ polygons = {Polygon({{1, 1}, {1, 2}, {2, 2}, {2, 1}})}; // rectangle CW
+ polygons = {Polygon({{1, 1}, {2, 1}, {2, 2}, {1, 2}})}; // rectangle CCW
+
+ EmbossConfig ec;
+ ec.height = 3;
+ indexed_triangle_set its = tm.its; // copy
+ for (const auto& polygon : polygons) {
+ // TODO: differ CW and CCW
+ emboss3d_(polygon, ec, its);
+ }
+}
+
+
+#include
+#include
+#include
+// triangulate with constrains on edges
+std::vector triangulate(const std::vector& points,
+ const std::vector>& edges)
+{
+ // use cgal triangulation
+ using K = CGAL::Exact_predicates_inexact_constructions_kernel;
+ using Itag = CGAL::Exact_predicates_tag;
+ using CDT = CGAL::Constrained_Delaunay_triangulation_2;
+ using Point = CDT::Point;
+
+ // construct a constrained triangulation
+ CDT cdt;
+ std::map map; // for indices
+ std::vector vertices_handle; // for constriants
+ vertices_handle.reserve(points.size());
+ for (size_t i = 0; i < points.size(); ++i) {
+ const Vec2f &p = points[i];
+ Point cdt_p(p.x(), p.y());
+ auto handl = cdt.insert(cdt_p);
+ vertices_handle.push_back(handl);
+ map[handl] = i;
+ }
+
+ for (const std::pair &edge : edges) {
+ cdt.insert_constraint(vertices_handle[edge.first],
+ vertices_handle[edge.second]);
+ }
+
+ auto faces = cdt.finite_face_handles();
+ std::vector indices;
+ indices.reserve(faces.size());
+ for (CDT::Face_handle face : faces){
+ auto v0 = face->vertex(0);
+ auto v1 = face->vertex(1);
+ auto v2 = face->vertex(2);
+ indices.emplace_back(map[v0], map[v1], map[v2]);
+
+ /*
+ auto p0 = v0->point();
+ auto p1 = v1->point();
+ auto p2 = v2->point();
+ std::cout << "Triangle: " <<
+ map[v0] << " [" << p0.x() << ", " << p0.y() << "], " <<
+ map[v1] << " [" << p1.x() << ", " << p1.y() << "], " <<
+ map[v2] << " [" << p2.x() << ", " << p2.y() << "] " << std::endl;*/
+ }
+ return indices;
+}
+
+std::vector triangulate(const Vec3i & triangle,
+ const Vec3d & triangle_normal,
+ const std::vector &vertices,
+ const TrianglePaths& paths)
+{
+ size_t count_point = 3;
+ size_t count_circles = 0;
+ for (const auto &path : paths){
+ count_point += path.points.size();
+ if (!path.edges.has_value())
+ ++count_circles;
+ }
+
+ std::vector points3d;
+ points3d.reserve(count_point);
+ std::vector index_map;
+ index_map.reserve(count_point);
+
+ // add source triangle points
+ for (int vi : triangle) {
+ points3d.push_back(vertices[vi]);
+ index_map.push_back(vi);
+ }
+
+ // add path points with edges
+ std::vector> edges;
+ edges.reserve(count_point - 3 - paths.size() + count_circles);
+ for (const auto &path : paths) {
+ size_t index = path.offset_id;
+ bool is_first = true;
+ for (const Vec3f &point : path.points) {
+ int index = points3d.size();
+ if (is_first) {
+ is_first = false;
+ // all path on triangle
+ if (!path.edges.has_value())
+ edges.push_back({index, index + path.points.size()});
+ } else {
+ edges.push_back({index-1, index});
+ }
+ points3d.push_back(point);
+ index_map.push_back(index);
+ ++index;
+ }
+ }
+
+ // TODO:check tr_mat
+
+ // create transform matrix to remove z coordinate by normal
+ const Vec3d z_axis(0, 0, 1);
+ auto rotation = create_transformation(triangle_normal, z_axis);
+
+ // convert points to 2d
+ std::vector points_2d;
+ points_2d.reserve(count_point);
+ for (const Vec3f &p : points3d) {
+ Vec3d p_tr = rotation * p.cast();
+ points_2d.emplace_back(p_tr.x(), p_tr.y());
+ }
+
+ // connecto to triangles
+ std::vector indices = triangulate(points_2d, edges);
+
+ // TODO: Check thin border triangle caused by float preccision
+
+ // remap indexes
+ for (Vec3i &triangle : indices)
+ for (int &i : triangle) i = index_map[i];
+ return indices;
+}
+
+TEST_CASE("Triangulate by cgal", "[]")
+{
+ std::vector points = {
+ Vec2f(1, 1),
+ Vec2f(2, 1),
+ Vec2f(2, 2),
+ Vec2f(1, 2)
+ };
+ std::vector> edges1 = {{1, 3}};
+ std::vector indices1 = triangulate(points, edges1);
+
+ auto check = [](int i1, int i2, Vec3i t)->bool { return true;
+ return (t[0] == i1 || t[1] == i1 || t[2] == i1) &&
+ (t[0] == i2 || t[1] == i2 || t[2] == i2);
+ };
+ REQUIRE(indices1.size() == 2);
+ int i1 = edges1.front().first,
+ i2 = edges1.front().second;
+ CHECK(check(i1, i2, indices1[0]));
+ CHECK(check(i1, i2, indices1[1]));
+
+ std::vector> edges2 = {{0, 2}};
+ std::vector indices2 = triangulate(points, edges2);
+ REQUIRE(indices2.size() == 2);
+ i1 = edges2.front().first;
+ i2 = edges2.front().second;
+ CHECK(check(i1, i2, indices2[0]));
+ CHECK(check(i1, i2, indices2[1]));
+}