From e33c665876484a79fb762f07d61718088340fc0a Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Mon, 30 Aug 2021 10:51:58 +0200 Subject: [PATCH] Partial emboss --- resources/icons/emboss.svg | 63 ++ src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Emboss.cpp | 111 ++++ src/libslic3r/Emboss.hpp | 73 +++ src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp | 124 ++++ src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp | 83 +++ src/slic3r/GUI/Gizmos/GLGizmosManager.cpp | 2 + tests/libslic3r/test_meshboolean.cpp | 764 ++++++++++++++++++++++ 9 files changed, 1224 insertions(+) create mode 100644 resources/icons/emboss.svg create mode 100644 src/libslic3r/Emboss.cpp create mode 100644 src/libslic3r/Emboss.hpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoEmboss.cpp create mode 100644 src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp diff --git a/resources/icons/emboss.svg b/resources/icons/emboss.svg new file mode 100644 index 000000000..fdea19b77 --- /dev/null +++ b/resources/icons/emboss.svg @@ -0,0 +1,63 @@ + +image/svg+xml + + + + + + + + + + + + diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index f9bef903b..89363bd23 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 000000000..c7837671b --- /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 000000000..4086f775d --- /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 34f1a188d..f95930701 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 000000000..dee4cd3b4 --- /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 000000000..13ec92098 --- /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 556983060..ec111236e 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 97d03ac23..1db26381c 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])); +}