From 63121cee2ed8aac45eef9a614dd8ecd2f730a1dc Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 25 Aug 2022 13:28:10 +0200 Subject: [PATCH] Better healing for Glyph shape - remove duplicit points + self intersections Add search of intersecting points (compared with CGAL) Triangulation can [optionaly] accept multi points --- src/libslic3r/BoundingBox.hpp | 1 + src/libslic3r/CMakeLists.txt | 4 +- src/libslic3r/Emboss.cpp | 401 +++++++++++++------- src/libslic3r/Emboss.hpp | 7 + src/libslic3r/ExPolygon.hpp | 13 + src/libslic3r/IntersectionPoints.cpp | 170 +++++++++ src/libslic3r/IntersectionPoints.hpp | 16 + src/libslic3r/NSVGUtils.cpp | 17 +- src/libslic3r/NSVGUtils.hpp | 4 + src/libslic3r/Point.cpp | 18 + src/libslic3r/Point.hpp | 3 + src/libslic3r/SVG.cpp | 6 + src/libslic3r/SVG.hpp | 6 +- src/libslic3r/Triangulation.cpp | 254 ++++++++++--- src/libslic3r/Triangulation.hpp | 38 +- tests/data/contour_ALIENATO.TTF_glyph_i.svg | 5 + tests/libslic3r/test_emboss.cpp | 115 +++++- tests/libslic3r/test_triangulation.cpp | 55 ++- 18 files changed, 892 insertions(+), 241 deletions(-) create mode 100644 src/libslic3r/IntersectionPoints.cpp create mode 100644 src/libslic3r/IntersectionPoints.hpp create mode 100644 tests/data/contour_ALIENATO.TTF_glyph_i.svg diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 1056c134d..de99b9c5a 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -172,6 +172,7 @@ public: BoundingBox rotated(double angle, const Point ¢er) const; void rotate(double angle) { (*this) = this->rotated(angle); } void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); } + bool intersects(const BoundingBox &other) const { return this->min(0) <= other.max(0) && this->max(0) >= other.min(0) && this->min(1) <= other.max(1) && this->max(1) >= other.min(1); } // Align the min corner to a grid of cell_size x cell_size cells, // to encompass the original bounding box. void align_to_grid(const coord_t cell_size); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9e5d9a68c..ce4ef6340 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -388,9 +388,11 @@ cmake_policy(POP) add_library(libslic3r_cgal STATIC CutSurface.hpp CutSurface.cpp + IntersectionPoints.hpp IntersectionPoints.cpp MeshBoolean.hpp MeshBoolean.cpp TryCatchSignal.hpp TryCatchSignal.cpp - Triangulation.hpp Triangulation.cpp) + Triangulation.hpp Triangulation.cpp +) target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 19cfda3b1..a3a97f34d 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -3,6 +3,7 @@ #include #include #include // union_ex +#include "IntersectionPoints.hpp" #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation #include "imgui/imstb_truetype.h" // stbtt_fontinfo @@ -32,14 +33,6 @@ public: static EmbossStyle create_style(std::wstring name, std::wstring path); - /// - /// TODO: move to ExPolygon utils - /// Remove multi points. When exist multi point dilate it by rect 3x3 and union result. - /// - /// Shape which can contain same point, will be extended by dilatation rects - /// ExPolygons with only unique points - static ExPolygons dilate_to_unique_points(ExPolygons &expolygons); - // scale and convert float to int coordinate static Point to_point(const stbtt__point &point); }; @@ -69,6 +62,112 @@ std::optional Private::load_font_info( return font_info; } +ExPolygons Emboss::heal_shape(const Polygons &shape) { + // When edit this code check that font 'ALIENATE.TTF' and glyph 'i' still work + // fix of self intersections + // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Functions/SimplifyPolygon.htm + ClipperLib::Paths paths = ClipperLib::SimplifyPolygons(ClipperUtils::PolygonsProvider(shape)); + ClipperLib::CleanPolygons(paths); + Polygons polygons = to_polygons(paths); + static const Points pts_2x2({Point(0, 0), Point(1, 0), Point(1, 1), Point(0, 1)}); + static const Points pts_3x3({Point(-1, -1), Point(1, -1), Point(1, 1), Point(-1, 1)}); + + // do not remove all duplicits but do it better way + Points duplicits = collect_duplications(to_points(polygons)); + if (!duplicits.empty()) { + polygons.reserve(polygons.size() + duplicits.size()); + for (const Point &p : duplicits) { + Slic3r::Polygon rect_3x3(pts_3x3); + rect_3x3.translate(p); + polygons.push_back(rect_3x3); + } + } + ExPolygons res = Slic3r::union_ex(polygons, ClipperLib::pftNonZero); + + Slic3r::Polygons holes; + int max_iteration = 10; + while (--max_iteration){ + Pointfs intersections = intersection_points(res); + Points duplicits = collect_duplications(to_points(res)); + if (intersections.empty() && duplicits.empty()) break; + + holes.clear(); + holes.reserve(intersections.size() + duplicits.size()); + + // Fix self intersection in result by subtracting hole 2x2 + if (!intersections.empty()) { + for (const Vec2d &p : intersections) { + Slic3r::Polygon hole(pts_2x2); + Point tr(std::floor(p.x()), std::floor(p.y())); + hole.translate(tr); + holes.push_back(hole); + } + } + + // fix duplicit points by hole 3x3 around duplicit point + if (!duplicits.empty()) { + holes.reserve(duplicits.size()); + for (const Point &p : duplicits) { + Slic3r::Polygon hole(pts_3x3); + hole.translate(p); + holes.push_back(hole); + } + } + + holes = Slic3r::union_(holes); + res = Slic3r::diff_ex(res, holes, ApplySafetyOffset::Yes); + } + /* VISUALIZATION of BAD symbols for debug + { + double svg_scale = Emboss::SHAPE_SCALE / unscale(1.); + BoundingBox bb(to_points(shape)); + //bb.scale(svg_scale); + SVG svg("C:/data/temp/fix_self_intersection.svg", bb); + svg.draw(shape); + // svg.draw(polygons, "orange"); + svg.draw(res, "green"); + + svg.draw(duplicits, "lightgray", 13 / Emboss::SHAPE_SCALE); + Points duplicits3 = collect_duplications(to_points(res)); + svg.draw(duplicits3, "black", 7 / Emboss::SHAPE_SCALE); + + Pointfs pts2 = intersection_points(res); + Points pts_i; pts_i.reserve(pts2.size()); + for (auto p : pts2) pts_i.push_back(p.cast()); + svg.draw(pts_i, "red", 8 / Emboss::SHAPE_SCALE); + } //*/ + + if (max_iteration == 0) { + assert(false); + BoundingBox bb = get_extents(shape); + Point size = bb.size(); + if (size.x() < 10) bb.max.x() += 10; + if (size.y() < 10) bb.max.y() += 10; + + Polygon rect({ // CCW + bb.min, + {bb.max.x(), bb.min.y()}, + bb.max, + {bb.min.x(), bb.max.y()} + }); + + Point offset = bb.size()*0.1; + Polygon hole({ // CW + bb.min + offset, + {bb.min.x()+offset.x(), bb.max.y()-offset.y()}, + bb.max - offset, + {bb.max.x()-offset.x(), bb.min.y()+offset.y()} + }); + ExPolygon res(rect, hole); + // BAD symbol + return {res}; + } + + assert(intersection_points(res).empty()); + assert(collect_duplications(to_points(res)).empty()); + return res; +} + std::optional Private::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) { int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); @@ -127,12 +226,7 @@ std::optional Private::get_glyph(const stbtt_fontinfo &font_info, std::reverse(pts.begin(), pts.end()); glyph_polygons.emplace_back(pts); } - - // fix for bad defined fonts - glyph.shape = Slic3r::union_ex(glyph_polygons); - - // inner cw - hole - // outer ccw - contour + glyph.shape = Emboss::heal_shape(glyph_polygons); return glyph; } @@ -175,28 +269,25 @@ const Emboss::Glyph* Private::get_glyph( glyph_opt->left_side_bearing = static_cast(glyph_opt->left_side_bearing / Emboss::SHAPE_SCALE); - if (font_prop.boldness.has_value()) { - float delta = *font_prop.boldness / Emboss::SHAPE_SCALE / font_prop.size_in_mm; - glyph_opt->shape = offset_ex(glyph_opt->shape, delta); - } - - if (font_prop.skew.has_value()) { - const float &ratio = *font_prop.skew; - auto skew = [&ratio](Slic3r::Polygon &polygon) { - for (Slic3r::Point &p : polygon.points) { p.x() += p.y() * ratio; } - }; - for (ExPolygon &expolygon : glyph_opt->shape) { - skew(expolygon.contour); - for (Slic3r::Polygon &hole : expolygon.holes) skew(hole); + if (!glyph_opt->shape.empty()) { + if (font_prop.boldness.has_value()) { + float delta = *font_prop.boldness / Emboss::SHAPE_SCALE / + font_prop.size_in_mm; + glyph_opt->shape = Slic3r::union_ex(offset_ex(glyph_opt->shape, delta)); + } + if (font_prop.skew.has_value()) { + const float &ratio = *font_prop.skew; + auto skew = [&ratio](Slic3r::Polygon &polygon) { + for (Slic3r::Point &p : polygon.points) { + p.x() += p.y() * ratio; + } + }; + for (ExPolygon &expolygon : glyph_opt->shape) { + skew(expolygon.contour); + for (Slic3r::Polygon &hole : expolygon.holes) skew(hole); + } } } - - // union of shape - // (for sure) I do not believe in font corectness - // modification like bold or skew could create artefacts - glyph_opt->shape = Slic3r::union_ex(glyph_opt->shape); - // unify multipoints with similar position. Could appear after union - dilate_to_unique_points(glyph_opt->shape); auto it = cache.insert({unicode, std::move(*glyph_opt)}); assert(it.second); return &it.first->second; @@ -208,72 +299,6 @@ EmbossStyle Private::create_style(std::wstring name, std::wstring path) { EmbossStyle::Type::file_path, FontProp() }; } -ExPolygons Private::dilate_to_unique_points(ExPolygons &expolygons) -{ - std::set points; - std::set multi_points; - auto find_multipoint = [&points, &multi_points](const Points &pts) { - for (const Point &p : pts) { - auto it = points.find(p); - if (it != points.end()) - multi_points.insert(p); - else - points.insert(p); - } - }; - for (const ExPolygon &expolygon : expolygons) { - find_multipoint(expolygon.contour.points); - for (const Slic3r::Polygon &hole : expolygon.holes) - find_multipoint(hole.points); - } - // speed up, no multipoints - if (multi_points.empty()) return expolygons; - - // CCW rectangle around zero with size 3*3 px for dilatation - const Points rect_3_3{Point(1, 1), Point(-1, 1), Point(-1, -1), Point(1, -1)}; - const Points rect_side{Point(1, 0), Point(0, 1), Point(-1, 0), Point(0, -1)}; - - // all new added points for reduction - std::set rects_points; - - // extends expolygons with dilatation rectangle - expolygons.reserve(expolygons.size() + multi_points.size()); - for (const Point &multi_point : multi_points) { - Slic3r::Polygon rect(rect_3_3); // copy points - rect.translate(multi_point); - for (const Point& p : rect.points) rects_points.insert(p); - // add side point to be sure with result - for (const Point& p : rect_side) rects_points.insert(p + multi_point); - expolygons.emplace_back(rect); - } - ExPolygons result = union_ex(expolygons); - - // reduce new created close points - auto reduce_close_points = [&rects_points](Points &pts) { - bool is_first = false; - size_t offset = 0; - bool is_prev_rect = false; - for (size_t i = 0; i < pts.size(); i++) { - const Point &p = pts[i]; - bool is_rect = (rects_points.find(p) != rects_points.end()); - if (is_prev_rect && is_rect) ++offset; - if (offset != 0) pts[i - offset] = p; - if (i == 0 && is_rect) is_first = true; - is_prev_rect = is_rect; - } - // remove last - if (is_first && is_prev_rect) ++offset; - if (offset != 0) - pts.erase(pts.begin() + (pts.size() - offset), pts.end()); - }; - for (ExPolygon &expolygon : result) { - reduce_close_points(expolygon.contour.points); - for (Slic3r::Polygon &hole : expolygon.holes) - reduce_close_points(hole.points); - } - return result; -} - Point Private::to_point(const stbtt__point &point) { return Point(static_cast(std::round(point.x / Emboss::SHAPE_SCALE)), static_cast(std::round(point.y / Emboss::SHAPE_SCALE))); @@ -627,7 +652,6 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, const FontProp &font_prop) { assert(font_with_cache.has_value()); - std::optional font_info_opt; Point cursor(0, 0); ExPolygons result; @@ -671,8 +695,7 @@ ExPolygons Emboss::text2shapes(FontFileWithCache &font_with_cache, cursor.x() += glyph_ptr->advance_width; expolygons_append(result, std::move(expolygons)); } - result = Slic3r::union_ex(result); - return Private::dilate_to_unique_points(result); + return Slic3r::union_ex(result); } void Emboss::apply_transformation(const FontProp &font_prop, @@ -777,37 +800,136 @@ double Emboss::get_shape_scale(const FontProp &fp, const FontFile &ff) return scale * Emboss::SHAPE_SCALE; } -indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, - const IProjection &projection) +static void store_trinagulation( + const ExPolygons &shape, + const std::vector &triangles, + const char *file_name = "C:/data/temp/triangulation.svg", + double scale = 1e5) { - indexed_triangle_set result; - size_t count_point = count_points(shape2d); - result.vertices.reserve(2 * count_point); + BoundingBox bb; + for (const auto &expoly : shape) bb.merge(expoly.contour.points); + bb.scale(scale); + SVG svg_vis(file_name, bb); + svg_vis.draw(shape, "gray", .7f); + Points pts = to_points(shape); + svg_vis.draw(pts, "black", 4 * scale); + for (const Vec3i &t : triangles) { + Slic3r::Polygon triangle({pts[t[0]], pts[t[1]], pts[t[2]]}); + triangle.scale(scale); + svg_vis.draw(triangle, "green"); + } +} +namespace priv { +void add_quad(uint32_t i1, + uint32_t i2, + indexed_triangle_set &result, + uint32_t count_point) +{ + // bottom indices + uint32_t i1_ = i1 + count_point; + uint32_t i2_ = i2 + count_point; + result.indices.emplace_back(i2, i2_, i1); + result.indices.emplace_back(i1_, i1, i2_); +}; + +indexed_triangle_set polygons2model_unique( + const ExPolygons &shape2d, + const Emboss::IProjection &projection, + const Points &points) +{ + // CW order of triangle indices + std::vector shape_triangles=Triangulation::triangulate(shape2d, points); + uint32_t count_point = points.size(); + + indexed_triangle_set result; + result.vertices.reserve(2 * count_point); + std::vector &front_points = result.vertices; // alias + std::vector back_points; + back_points.reserve(count_point); + + for (const Point &p : points) { + auto p2 = projection.create_front_back(p); + front_points.push_back(p2.first); + back_points.push_back(p2.second); + } + + // insert back points, front are already in + result.vertices.insert(result.vertices.end(), + std::make_move_iterator(back_points.begin()), + std::make_move_iterator(back_points.end())); + result.indices.reserve(shape_triangles.size() * 2 + points.size() * 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 + size_t polygon_offset = 0; + auto add_quads = [&polygon_offset,&result, &count_point] + (const Slic3r::Polygon& polygon) { + uint32_t polygon_points = polygon.points.size(); + // previous index + uint32_t prev = polygon_offset + polygon_points - 1; + for (uint32_t p = 0; p < polygon_points; ++p) { + uint32_t index = polygon_offset + p; + add_quad(prev, index, result, count_point); + prev = index; + } + polygon_offset += polygon_points; + }; + + for (const ExPolygon &expolygon : shape2d) { + add_quads(expolygon.contour); + for (const Slic3r::Polygon &hole : expolygon.holes) add_quads(hole); + } + + return result; +} + +indexed_triangle_set polygons2model_duplicit( + const ExPolygons &shape2d, + const Emboss::IProjection &projection, + const Points &points, + const Points &duplicits) +{ + // CW order of triangle indices + std::vector changes = Triangulation::create_changes(points, duplicits); + std::vector shape_triangles = Triangulation::triangulate(shape2d, points, changes); + uint32_t count_point = *std::max_element(changes.begin(), changes.end()) + 1; + + indexed_triangle_set result; + result.vertices.reserve(2 * count_point); std::vector &front_points = result.vertices; std::vector back_points; back_points.reserve(count_point); - auto insert_point = [&projection, &front_points, - &back_points](const Polygon& polygon) { - for (const Point& p : polygon.points) { - auto p2 = projection.create_front_back(p); - front_points.emplace_back(p2.first); - back_points.emplace_back(p2.second); - } - }; - for (const ExPolygon &expolygon : shape2d) { - insert_point(expolygon.contour); - for (const Polygon &hole : expolygon.holes) insert_point(hole); + uint32_t max_index = std::numeric_limits::max(); + for (uint32_t i = 0; i < changes.size(); ++i) { + uint32_t index = changes[i]; + if (max_index != std::numeric_limits::max() && + index <= max_index) continue; // duplicit point + assert(index == max_index + 1); + assert(front_points.size() == index); + assert(back_points.size() == index); + max_index = index; + const Point &p = points[i]; + auto p2 = projection.create_front_back(p); + front_points.push_back(p2.first); + back_points.push_back(p2.second); } + assert(max_index+1 == count_point); + // insert back points, front are already in result.vertices.insert(result.vertices.end(), std::make_move_iterator(back_points.begin()), std::make_move_iterator(back_points.end())); - // CW order of triangle indices - std::vector shape_triangles = Triangulation::triangulate(shape2d); - result.indices.reserve(shape_triangles.size() * 2 + count_point * 2); + result.indices.reserve(shape_triangles.size() * 2 + points.size() * 2); // top triangles - change to CCW for (const Vec3i &t : shape_triangles) result.indices.emplace_back(t.x(), t.z(), t.y()); @@ -818,28 +940,37 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, // quads around - zig zag by triangles size_t polygon_offset = 0; - auto add_quads = [&result,&polygon_offset, count_point](const Polygon& polygon) { + auto add_quads = [&polygon_offset, &result, count_point, &changes] + (const Slic3r::Polygon &polygon) { uint32_t polygon_points = polygon.points.size(); - for (uint32_t p = 0; p < polygon_points; p++) { - uint32_t i = polygon_offset + p; - // previous index - uint32_t ip = (p == 0) ? (polygon_offset + polygon_points - 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); + // previous index + uint32_t prev = changes[polygon_offset + polygon_points - 1]; + for (uint32_t p = 0; p < polygon_points; ++p) { + uint32_t index = changes[polygon_offset + p]; + if (prev == index) continue; + add_quad(prev, index, result, count_point); + prev = index; } polygon_offset += polygon_points; }; - + for (const ExPolygon &expolygon : shape2d) { add_quads(expolygon.contour); - for (const Polygon &hole : expolygon.holes) add_quads(hole); + for (const Slic3r::Polygon &hole : expolygon.holes) add_quads(hole); } return result; } +} // namespace priv + +indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, + const IProjection &projection) +{ + Points points = to_points(shape2d); + Points duplicits = collect_duplications(points); + return (duplicits.empty()) ? + priv::polygons2model_unique(shape2d, projection, points) : + priv::polygons2model_duplicit(shape2d, projection, points, duplicits); +} std::pair Emboss::ProjectZ::create_front_back(const Point &p) const { diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index dbe20fe87..13d19f06c 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -155,6 +155,13 @@ public: /// Inner polygon cw(outer ccw) static ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop); + /// + /// Fix intersections and self intersections in polygons glyph shape + /// + /// Input shape to heal + /// Healed shapes + static ExPolygons heal_shape(const Polygons &shape); + /// /// Use data from font property to modify transformation /// diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index cec59c419..ff86d049c 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -129,6 +129,19 @@ inline Lines to_lines(const ExPolygons &src) return lines; } +inline Points to_points(const ExPolygons &src) +{ + Points points; + size_t count = count_points(src); + points.reserve(count); + for (const ExPolygon &expolygon : src) { + append(points, expolygon.contour.points); + for (const Polygon &hole : expolygon.holes) + append(points, hole.points); + } + return points; +} + inline Polylines to_polylines(const ExPolygon &src) { Polylines polylines; diff --git a/src/libslic3r/IntersectionPoints.cpp b/src/libslic3r/IntersectionPoints.cpp new file mode 100644 index 000000000..13d40a6eb --- /dev/null +++ b/src/libslic3r/IntersectionPoints.cpp @@ -0,0 +1,170 @@ +#include "IntersectionPoints.hpp" + +//#define USE_CGAL_SWEEP_LINE +#ifdef USE_CGAL_SWEEP_LINE + +#include +#include +#include +#include +#include + +using NT = CGAL::Quotient; +using Kernel = CGAL::Cartesian; +using P2 = Kernel::Point_2; +using Traits_2 = CGAL::Arr_segment_traits_2; +using Segment = Traits_2::Curve_2; +using Segments = std::vector; + +namespace priv { + +P2 convert(const Slic3r::Point &p) { return P2(p.x(), p.y()); } +Slic3r::Vec2d convert(const P2 &p) +{ + return Slic3r::Vec2d(CGAL::to_double(p.x()), CGAL::to_double(p.y())); +} + +Slic3r::Pointfs compute_intersections(const Segments &segments) +{ + std::vector intersections; + // Compute all intersection points. + CGAL::compute_intersection_points(segments.begin(), segments.end(), + std::back_inserter(intersections)); + if (intersections.empty()) return {}; + Slic3r::Pointfs pts; + pts.reserve(intersections.size()); + for (const P2 &p : intersections) pts.push_back(convert(p)); + return pts; +} + +void add_polygon(const Slic3r::Polygon &polygon, Segments &segments) +{ + if (polygon.points.size() < 2) return; + P2 prev_point = priv::convert(polygon.last_point()); + for (const Slic3r::Point &p : polygon.points) { + P2 act_point = priv::convert(p); + if (prev_point == act_point) continue; + segments.emplace_back(prev_point, act_point); + prev_point = act_point; + } +} +Slic3r::Pointfs Slic3r::intersection_points(const Lines &lines) +{ + return priv::compute_intersections2(lines); + Segments segments; + segments.reserve(lines.size()); + for (Line l : lines) + segments.emplace_back(priv::convert(l.a), priv::convert(l.b)); + return priv::compute_intersections(segments); +} + +Slic3r::Pointfs Slic3r::intersection_points(const Polygon &polygon) +{ + Segments segments; + segments.reserve(polygon.points.size()); + priv::add_polygon(polygon, segments); + return priv::compute_intersections(segments); +} + +Slic3r::Pointfs Slic3r::intersection_points(const Polygons &polygons) +{ + Segments segments; + segments.reserve(count_points(polygons)); + for (const Polygon &polygon : polygons) + priv::add_polygon(polygon, segments); + return priv::compute_intersections(segments); +} + +Slic3r::Pointfs Slic3r::intersection_points(const ExPolygon &expolygon) +{ + Segments segments; + segments.reserve(count_points(expolygon)); + priv::add_polygon(expolygon.contour, segments); + for (const Polygon &hole : expolygon.holes) + priv::add_polygon(hole, segments); + return priv::compute_intersections(segments); +} + +Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons) +{ + Segments segments; + segments.reserve(count_points(expolygons)); + for (const ExPolygon &expolygon : expolygons) { + priv::add_polygon(expolygon.contour, segments); + for (const Polygon &hole : expolygon.holes) + priv::add_polygon(hole, segments); + } + return priv::compute_intersections(segments); +} + + +} // namespace priv + +#else // USE_CGAL_SWEEP_LINE + +// use bounding boxes +#include + +namespace priv { +Slic3r::Pointfs compute_intersections(const Slic3r::Lines &lines) +{ + using namespace Slic3r; + // IMPROVE0: BoundingBoxes of Polygons + // IMPROVE1: Polygon's neighbor lines can't intersect + // e.g. use indices to Point to find same points + // IMPROVE2: Bentley–Ottmann_algorithm + // https://doc.cgal.org/latest/Surface_sweep_2/index.html -- CGAL implementation is significantly slower + // https://stackoverflow.com/questions/4407493/is-there-a-robust-c-implementation-of-the-bentley-ottmann-algorithm + Pointfs pts; + Point i; + for (size_t li = 0; li < lines.size(); ++li) { + const Line &l = lines[li]; + const Point &a = l.a; + const Point &b = l.b; + Point min(std::min(a.x(), b.x()), std::min(a.y(), b.y())); + Point max(std::max(a.x(), b.x()), std::max(a.y(), b.y())); + BoundingBox bb(min, max); + for (size_t li_ = li + 1; li_ < lines.size(); ++li_) { + const Line &l_ = lines[li_]; + const Point &a_ = l_.a; + const Point &b_ = l_.b; + if (a == b_ || b == a_ || a == a_ || b == b_) continue; + Point min_(std::min(a_.x(), b_.x()), std::min(a_.y(), b_.y())); + Point max_(std::max(a_.x(), b_.x()), std::max(a_.y(), b_.y())); + BoundingBox bb_(min_, max_); + // intersect of BB compare min max + if (bb.intersects(bb_) && + l.intersection(l_, &i)) + pts.push_back(i.cast()); + } + } + return pts; +} +} // namespace priv + +Slic3r::Pointfs Slic3r::intersection_points(const Lines &lines) +{ + return priv::compute_intersections(lines); +} + +Slic3r::Pointfs Slic3r::intersection_points(const Polygon &polygon) +{ + return priv::compute_intersections(to_lines(polygon)); +} + +Slic3r::Pointfs Slic3r::intersection_points(const Polygons &polygons) +{ + return priv::compute_intersections(to_lines(polygons)); +} + +Slic3r::Pointfs Slic3r::intersection_points(const ExPolygon &expolygon) +{ + return priv::compute_intersections(to_lines(expolygon)); +} + +Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons) +{ + return priv::compute_intersections(to_lines(expolygons)); +} + +#endif // USE_CGAL_SWEEP_LINE diff --git a/src/libslic3r/IntersectionPoints.hpp b/src/libslic3r/IntersectionPoints.hpp new file mode 100644 index 000000000..2b1d3aa73 --- /dev/null +++ b/src/libslic3r/IntersectionPoints.hpp @@ -0,0 +1,16 @@ +#ifndef slic3r_IntersectionPoints_hpp_ +#define slic3r_IntersectionPoints_hpp_ + +#include "ExPolygon.hpp" + +namespace Slic3r { + +// collect all intersecting points +Pointfs intersection_points(const Lines &lines); +Pointfs intersection_points(const Polygon &polygon); +Pointfs intersection_points(const Polygons &polygons); +Pointfs intersection_points(const ExPolygon &expolygon); +Pointfs intersection_points(const ExPolygons &expolygons); + +} // namespace Slic3r +#endif // slic3r_IntersectionPoints_hpp_ \ No newline at end of file diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index 3f2b766db..a8f23e2e9 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -36,9 +36,7 @@ void NSVGUtils::flatten_cubic_bez(Polygon &polygon, flatten_cubic_bez(polygon, tessTol, p1234, p234, p34, p4, level); } -ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image, - float tessTol, - int max_level) +Polygons NSVGUtils::to_polygons(NSVGimage *image, float tessTol, int max_level) { Polygons polygons; for (NSVGshape *shape = image->shapes; shape != NULL; @@ -59,14 +57,23 @@ ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image, flatten_cubic_bez(polygon, tessTol, p1, p2, p3, p4, max_level); } - if (path->closed) { + if (path->closed && !polygon.empty()) { polygons.push_back(polygon); polygon = Slic3r::Polygon(); } } } - polygons.push_back(polygon); + if (!polygon.empty()) + polygons.push_back(polygon); } + return polygons; +} + +ExPolygons NSVGUtils::to_ExPolygons(NSVGimage *image, + float tessTol, + int max_level) +{ + Polygons polygons = to_polygons(image, tessTol, max_level); // Fix Y axis for (Polygon &polygon : polygons) diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index 308f11775..d82901fcc 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -25,6 +25,10 @@ public: static ExPolygons to_ExPolygons(NSVGimage *image, float tessTol = 10., int max_level = 10); + // convert svg paths to Polygons + static Polygons to_polygons(NSVGimage *image, + float tessTol = 10., + int max_level = 10); }; } // namespace Slic3r #endif // slic3r_NSVGUtils_hpp_ diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 9f56f96d6..2e282b400 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -158,6 +158,24 @@ bool has_duplicate_points(std::vector &&pts) return false; } +Points collect_duplications(Points pts /* Copy */) +{ + std::stable_sort(pts.begin(), pts.end()); + Points duplicits; + const Point *prev = &pts.front(); + for (size_t i = 1; i < pts.size(); ++i) { + const Point *act = &pts[i]; + if (*prev == *act) { + // duplicit point + if (!duplicits.empty() && duplicits.back() == *act) + continue; // only unique duplicits + duplicits.push_back(*act); + } + prev = act; + } + return duplicits; +} + BoundingBox get_extents(const Points &pts) { return BoundingBox(pts); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index dbf3c3832..d7286106f 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -260,6 +260,9 @@ inline bool has_duplicate_successive_points_closed(const std::vector &pts return has_duplicate_successive_points(pts) || (pts.size() >= 2 && pts.front() == pts.back()); } +// Collect adjecent(duplicit points) +Points collect_duplications(Points pts /* Copy */); + inline bool shorter_then(const Point& p0, const coord_t len) { if (p0.x() > len || p0.x() < -len) diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6a743b1eb..415aa5823 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -377,4 +377,10 @@ void SVG::export_expolygons(const char *path, const std::vector(x) * 10.f; } + +} // namespace Slic3r diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 314fae9b9..68089a062 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -167,9 +167,9 @@ public: { export_expolygons(path.c_str(), expolygons_with_attributes); } private: - static float to_svg_coord(float x) throw() { return unscale(x) * 10.f; } - static float to_svg_x(float x) throw() { return to_svg_coord(x); } - float to_svg_y(float x) const throw() { return flipY ? this->height - to_svg_coord(x) : to_svg_coord(x); } + static float to_svg_coord(float x) throw(); + static float to_svg_x(float x) throw() { return to_svg_coord(x); } + float to_svg_y(float x) const throw() { return flipY ? this->height - to_svg_coord(x) : to_svg_coord(x); } }; } diff --git a/src/libslic3r/Triangulation.cpp b/src/libslic3r/Triangulation.cpp index 3df2b279b..02fd6f601 100644 --- a/src/libslic3r/Triangulation.cpp +++ b/src/libslic3r/Triangulation.cpp @@ -1,35 +1,90 @@ #include "Triangulation.hpp" - +#include "IntersectionPoints.hpp" #include #include #include #include using namespace Slic3r; - namespace Slic3r::Private { -inline void insert_points(Points& points, const Polygon &polygon) { - points.insert(points.end(), polygon.points.begin(), polygon.points.end()); +inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon, const Triangulation::Changes& changes) { + const Points &pts = polygon.points; + uint32_t size = static_cast(pts.size()); + uint32_t last_index = offset + size - 1; + uint32_t prev_index = changes[last_index]; + for (uint32_t i = 0; i < size; ++i) { + uint32_t index = changes[offset + i]; + // when duplicit points are neighbor + if (prev_index == index) continue; + edges.push_back({prev_index, index}); + prev_index = index; + } + offset += size; } inline void insert_edges(Triangulation::HalfEdges &edges, uint32_t &offset, const Polygon &polygon) { const Points &pts = polygon.points; - for (uint32_t i = 1; i < pts.size(); ++i) { - uint32_t i2 = i + offset; - edges.push_back({i2 - 1, i2}); - } uint32_t size = static_cast(pts.size()); - // add connection from first to last point - edges.push_back({offset + size - 1, offset}); + uint32_t prev_index = offset + size - 1; + for (uint32_t i = 0; i < size; ++i) { + uint32_t index = offset + i; + edges.push_back({prev_index, index}); + prev_index = index; + } offset += size; } } // namespace Private +#define VISUALIZE_TRIANGULATION +#ifdef VISUALIZE_TRIANGULATION +#include "admesh/stl.h" // indexed triangle set +static void visualize(const Points &points, + const Triangulation::Indices &indices, + const char *filename) +{ + // visualize + indexed_triangle_set its; + its.vertices.reserve(points.size()); + for (const Point &p : points) its.vertices.emplace_back(p.x(), p.y(), 0.); + its.indices = indices; + its_write_obj(its, filename); +} +#endif // VISUALIZE_TRIANGULATION + +static bool has_bidirectional_constrained(const Triangulation::HalfEdges &constrained) { + for (const auto &c : constrained) { + auto key = std::make_pair(c.second, c.first); + auto it = std::lower_bound(constrained.begin(), constrained.end(), key); + if (it != constrained.end() && *it == key) return true; + } + return false; +} + +static bool has_self_intersection(const Points &points, + const Triangulation::HalfEdges &constrained_half_edges) +{ + Lines lines; + lines.reserve(constrained_half_edges.size()); + for (const auto &he : constrained_half_edges) + lines.emplace_back(points[he.first], points[he.second]); + return !intersection_points(lines).empty(); +} + Triangulation::Indices Triangulation::triangulate(const Points &points, const HalfEdges &constrained_half_edges) { + assert(!points.empty()); + assert(!constrained_half_edges.empty()); + // edges can NOT contain bidirectional constrained + assert(!has_bidirectional_constrained(constrained_half_edges)); + // constrained must be sorted + assert(std::is_sorted(constrained_half_edges.begin(), + constrained_half_edges.end())); + // check that there is only unique poistion of points + assert(std::adjacent_find(points.begin(), points.end()) == points.end()); + assert(!has_self_intersection(points, constrained_half_edges)); // use cgal triangulation using K = CGAL::Exact_predicates_inexact_constructions_kernel; using Vb = CGAL::Triangulation_vertex_base_with_info_2; @@ -71,51 +126,73 @@ Triangulation::Indices Triangulation::triangulate(const Points &points, // Unmark constrained edges of outside faces. size_t num_faces = 0; for (CDT::Face_handle fh : faces) { - for (int i = 0; i < 3; ++ i) { - if (fh->is_constrained(i)) { - auto key = std::make_pair(fh->vertex((i + 2) % 3)->info(), fh->vertex((i + 1) % 3)->info()); - if (auto it = std::lower_bound(constrained_half_edges.begin(), constrained_half_edges.end(), key); it != constrained_half_edges.end() && *it == key) { - // This face contains a constrained edge and it is outside. - for (int j = 0; j < 3; ++ j) - fh->set_constraint(j, false); - goto end; - } - } + for (int i = 0; i < 3; ++i) if (fh->vertex(i)->info() == 752) { + int j = 42; } - ++ num_faces; - end:; + + for (int i = 0; i < 3; ++i) { + if (!fh->is_constrained(i)) continue; + auto key = std::make_pair(fh->vertex((i + 2) % 3)->info(), fh->vertex((i + 1) % 3)->info()); + auto it = std::lower_bound(constrained_half_edges.begin(), constrained_half_edges.end(), key); + if (it == constrained_half_edges.end() || *it != key) continue; + // This face contains a constrained edge and it is outside. + for (int j = 0; j < 3; ++ j) + fh->set_constraint(j, false); + --num_faces; + break; + } + ++num_faces; } + auto inside = [](CDT::Face_handle &fh) { + return fh->neighbor(0) != fh && + (fh->is_constrained(0) || + fh->is_constrained(1) || + fh->is_constrained(2)); + }; + +#ifdef VISUALIZE_TRIANGULATION + std::vector indices2; + indices2.reserve(num_faces); + for (CDT::Face_handle fh : faces) + if (inside(fh)) indices2.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info()); + visualize(points, indices2, "C:/data/temp/triangulation_without_floodfill.obj"); +#endif // VISUALIZE_TRIANGULATION + // Propagate inside the constrained regions. std::vector queue; queue.reserve(num_faces); - auto inside = [](CDT::Face_handle &fh) { return fh->neighbor(0) != fh && (fh->is_constrained(0) || fh->is_constrained(1) || fh->is_constrained(2)); }; - for (CDT::Face_handle seed : faces) - if (inside(seed)) { - // Seed fill to neighbor faces. - queue.emplace_back(seed); - while (! queue.empty()) { - CDT::Face_handle fh = queue.back(); - queue.pop_back(); - for (int i = 0; i < 3; ++ i) - if (! fh->is_constrained(i)) { - // Propagate along this edge. - fh->set_constraint(i, true); - CDT::Face_handle nh = fh->neighbor(i); - bool was_inside = inside(nh); - // Mark the other side of this edge. - nh->set_constraint(nh->index(fh), true); - if (! was_inside) - queue.push_back(nh); - } + for (CDT::Face_handle seed : faces){ + if (!inside(seed)) continue; + // Seed fill to neighbor faces. + queue.emplace_back(seed); + while (! queue.empty()) { + CDT::Face_handle fh = queue.back(); + queue.pop_back(); + for (int i = 0; i < 3; ++i) { + if (fh->is_constrained(i)) continue; + // Propagate along this edge. + fh->set_constraint(i, true); + CDT::Face_handle nh = fh->neighbor(i); + bool was_inside = inside(nh); + // Mark the other side of this edge. + nh->set_constraint(nh->index(fh), true); + if (! was_inside) + queue.push_back(nh); } } + } std::vector indices; indices.reserve(num_faces); for (CDT::Face_handle fh : faces) if (inside(fh)) indices.emplace_back(fh->vertex(0)->info(), fh->vertex(1)->info(), fh->vertex(2)->info()); + +#ifdef VISUALIZE_TRIANGULATION + visualize(points, indices, "C:/data/temp/triangulation.obj"); +#endif // VISUALIZE_TRIANGULATION + return indices; } @@ -141,7 +218,7 @@ Triangulation::Indices Triangulation::triangulate(const Polygons &polygons) uint32_t offset = 0; for (const Polygon &polygon : polygons) { - Private::insert_points(points, polygon); + Slic3r::append(points, polygon.points); Private::insert_edges(edges, offset, polygon); } @@ -149,25 +226,90 @@ Triangulation::Indices Triangulation::triangulate(const Polygons &polygons) return triangulate(points, edges); } -Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons) +Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons){ + Points pts = to_points(expolygons); + Points d_pts = collect_duplications(pts); + if (d_pts.empty()) return triangulate(expolygons, pts); + + Changes changes = create_changes(pts, d_pts); + Indices indices = triangulate(expolygons, pts, changes); + // reverse map for changes + Changes changes2(changes.size(), std::numeric_limits::max()); + for (size_t i = 0; i < changes.size(); ++i) + changes2[changes[i]] = i; + + // convert indices into expolygons indicies + for (Vec3i &t : indices) + for (size_t ti = 0; ti < 3; ti++) t[ti] = changes2[t[ti]]; + + return indices; +} + +Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points &points) { - size_t count = count_points(expolygons); - Points points; - points.reserve(count); + assert(count_points(expolygons) == points.size()); + // when contain duplicit coordinate in points will not work properly + assert(collect_duplications(points).empty()); HalfEdges edges; - edges.reserve(count); + edges.reserve(points.size()); uint32_t offset = 0; - - for (const ExPolygon &expolygon : expolygons) { - Private::insert_points(points, expolygon.contour); + for (const ExPolygon &expolygon : expolygons) { Private::insert_edges(edges, offset, expolygon.contour); - for (const Polygon &hole : expolygon.holes) { - Private::insert_points(points, hole); - Private::insert_edges(edges, offset, hole); - } - } - + for (const Polygon &hole : expolygon.holes) + Private::insert_edges(edges, offset, hole); + } std::sort(edges.begin(), edges.end()); return triangulate(points, edges); } + +Triangulation::Indices Triangulation::triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes) +{ + assert(!points.empty()); + assert(count_points(expolygons) == points.size()); + assert(changes.size() == points.size()); + // IMPROVE: search from end and somehow distiquish that value is not a change + uint32_t count_points = *std::max_element(changes.begin(), changes.end())+1; + Points pts(count_points); + for (size_t i = 0; i < changes.size(); i++) + pts[changes[i]] = points[i]; + + HalfEdges edges; + edges.reserve(points.size()); + uint32_t offset = 0; + for (const ExPolygon &expolygon : expolygons) { + Private::insert_edges(edges, offset, expolygon.contour, changes); + for (const Polygon &hole : expolygon.holes) + Private::insert_edges(edges, offset, hole, changes); + } + + std::sort(edges.begin(), edges.end()); + return triangulate(pts, edges); +} + +Triangulation::Changes Triangulation::create_changes(const Points &points, const Points &duplicits) +{ + assert(!duplicits.empty()); + assert(duplicits.size() < points.size()/2); + std::vector duplicit_indices(duplicits.size(), std::numeric_limits::max()); + Changes changes; + changes.reserve(points.size()); + uint32_t index = 0; + for (const Point &p: points) { + auto it = std::lower_bound(duplicits.begin(), duplicits.end(), p); + if (it == duplicits.end() || *it != p) { + changes.push_back(index); + ++index; + continue; + } + uint32_t &d_index = duplicit_indices[it - duplicits.begin()]; + if (d_index == std::numeric_limits::max()) { + d_index = index; + changes.push_back(index); + ++index; + } else { + changes.push_back(d_index); + } + } + return changes; +} diff --git a/src/libslic3r/Triangulation.hpp b/src/libslic3r/Triangulation.hpp index 1adf1b93c..18c485eaf 100644 --- a/src/libslic3r/Triangulation.hpp +++ b/src/libslic3r/Triangulation.hpp @@ -34,15 +34,37 @@ public: static Indices triangulate(const Polygons &polygons); static Indices triangulate(const ExPolygons &expolygons); - /// - /// Filter out triagles without both side edge or inside half edges - /// Main purpose: Filter out triangles which lay outside of ExPolygon - /// given to triangulation - /// - /// Triangles - /// Only outer edges - static void remove_outer(Indices &indices, const HalfEdges &half_edges); + // Map for convert original index to set without duplication + // from_index + using Changes = std::vector; + /// + /// Create conversion map from original index into new + /// with respect of duplicit point + /// + /// input set of points + /// duplicit points collected from points + /// Conversion map for point index + static Changes create_changes(const Points &points, const Points &duplicits); + + /// + /// Triangulation for expolygons, speed up when points are already collected + /// NOTE: Not working properly for ExPolygons with multiple point on same coordinate + /// You should check it by "collect_changes" + /// + /// Input shape to triangulation - define edges + /// Points from expolygons + /// Triangle indices + static Indices triangulate(const ExPolygons &expolygons, const Points& points); + + /// + /// Triangulation for expolygons containing multiple points with same coordinate + /// + /// Input shape to triangulation - define edge + /// Points from expolygons + /// Changes swap for indicies into points + /// Triangle indices + static Indices triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes); }; } // namespace Slic3r diff --git a/tests/data/contour_ALIENATO.TTF_glyph_i.svg b/tests/data/contour_ALIENATO.TTF_glyph_i.svg new file mode 100644 index 000000000..f1d5a130f --- /dev/null +++ b/tests/data/contour_ALIENATO.TTF_glyph_i.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/tests/libslic3r/test_emboss.cpp b/tests/libslic3r/test_emboss.cpp index 118c7b942..cf01befb6 100644 --- a/tests/libslic3r/test_emboss.cpp +++ b/tests/libslic3r/test_emboss.cpp @@ -6,7 +6,7 @@ #include #include #include // for next_highest_power_of_2() - +#include using namespace Slic3r; namespace Private{ @@ -157,6 +157,119 @@ TEST_CASE("Convert glyph % to model", "[Emboss]") CHECK(!its.indices.empty()); } +//#define VISUALIZE +#ifdef VISUALIZE +TEST_CASE("Visualize glyph from font", "[Emboss]") +{ + std::string font_path = "C:/data/ALIENATO.TTF"; + std::string text = "i"; + + Emboss::FontFileWithCache font( + Emboss::create_font_file(font_path.c_str())); + REQUIRE(font.has_value()); + FontProp fp; + fp.size_in_mm = 8; + fp.emboss = 4; + ExPolygons shapes = Emboss::text2shapes(font, text.c_str(), fp); + + // char letter = 'i'; + // unsigned int font_index = 0; // collection + // float flatness = 5; + // auto glyph = Emboss::letter2glyph(*font.font_file, font_index, letter, + // flatness); ExPolygons shapes2 = glyph->shape; { SVG + //svg("C:/data/temp/stored_letter.svg", get_extents(shapes2)); + //svg.draw(shapes2); } // debug shape + + REQUIRE(!shapes.empty()); + //{ SVG svg("C:/data/temp/shapes.svg"); svg.draw(shapes); } // debug shape + + float z_depth = 100.f; + Emboss::ProjectZ projection(z_depth); + indexed_triangle_set its = Emboss::polygons2model(shapes, projection); + its_write_obj(its, "C:/data/temp/bad_glyph.obj"); + + CHECK(!its.indices.empty()); + TriangleMesh tm(its); + auto s = tm.stats(); +} +#endif // VISUALIZE + +#include "test_utils.hpp" +#include "nanosvg/nanosvg.h" // load SVG file +#include "libslic3r/NSVGUtils.hpp" +void heal_and_check(const Polygons &polygons) +{ + Pointfs intersections_prev = intersection_points(polygons); + Points polygons_points = to_points(polygons); + Points duplicits_prev = collect_duplications(polygons_points); + + ExPolygons shape = Emboss::heal_shape(polygons); + + // Is default shape for unhealabled shape? + bool is_default_shape = + shape.size() == 1 && + shape.front().contour.points.size() == 4 && + shape.front().holes.size() == 1 && + shape.front().holes.front().points.size() == 4 ; + CHECK(!is_default_shape); + + Pointfs intersections = intersection_points(shape); + Points shape_points = to_points(shape); + Points duplicits = collect_duplications(shape_points); + //{ + // BoundingBox bb(polygons_points); + // // bb.scale(svg_scale); + // SVG svg("C:/data/temp/test_visualization.svg", bb); + // svg.draw(polygons, "gray"); // input + // svg.draw(shape, "green"); // output + + // Points pts; + // pts.reserve(intersections.size()); + // for (const Vec2d &intersection : intersections) + // pts.push_back(intersection.cast()); + // svg.draw(pts, "red", 10); + // svg.draw(duplicits, "orenge", 10); + //} + + CHECK(intersections.empty()); + CHECK(duplicits.empty()); +} + +void scale(Polygons &polygons, double multiplicator) { + for (Polygon &polygon : polygons) + for (Point &p : polygon) p *= multiplicator; +} + +TEST_CASE("Heal of damaged polygons", "[Emboss]") +{ + // Shape loaded from svg is letter 'i' from font 'ALIENATE.TTF' + std::string file_name = "contour_ALIENATO.TTF_glyph_i.svg"; + std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name; + NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f); + Polygons polygons = NSVGUtils::to_polygons(image); + nsvgDelete(image); + + heal_and_check(polygons); + + Polygons scaled_shape = polygons; // copy + scale(scaled_shape, 1 / Emboss::SHAPE_SCALE); + heal_and_check(scaled_shape); + + // different scale + scale(scaled_shape, 10.); + heal_and_check(scaled_shape); + + // check reverse order of points + Polygons reverse_shape = polygons; + for (Polygon &p : reverse_shape) + std::reverse(p.points.begin(), p.points.end()); + heal_and_check(scaled_shape); + +#ifdef VISUALIZE + CHECK(false); +#endif // VISUALIZE +} + TEST_CASE("Convert text with glyph cache to model", "[Emboss]") { std::string font_path = get_font_filepath(); diff --git a/tests/libslic3r/test_triangulation.cpp b/tests/libslic3r/test_triangulation.cpp index ed2bbef83..5a1f99fa9 100644 --- a/tests/libslic3r/test_triangulation.cpp +++ b/tests/libslic3r/test_triangulation.cpp @@ -6,31 +6,21 @@ using namespace Slic3r; namespace Private{ -void store_trinagulation(const ExPolygons & shape, +void store_trinagulation(const ExPolygons &shape, const std::vector &triangles, - const char* file_name = "Triangulation_visualization.svg", - double scale = 1e5) + const char* file_name = "C:/data/temp/triangulation.svg", + double scale = 1e5) { BoundingBox bb; for (const auto &expoly : shape) bb.merge(expoly.contour.points); bb.scale(scale); SVG svg_vis(file_name, bb); svg_vis.draw(shape, "gray", .7f); + Points pts = to_points(shape); + svg_vis.draw(pts, "black", 4 * scale); - size_t count = count_points(shape); - Points points; - points.reserve(count); - auto insert_point = [](const Slic3r::Polygon &polygon, Points &points) { - for (const Point &p : polygon.points) points.emplace_back(p); - }; - for (const ExPolygon &expolygon : shape) { - insert_point(expolygon.contour, points); - for (const Slic3r::Polygon &hole : expolygon.holes) - insert_point(hole, points); - } - - for (const auto &t : triangles) { - Polygon triangle({points[t[0]], points[t[1]], points[t[2]]}); + for (const Vec3i &t : triangles) { + Slic3r::Polygon triangle({pts[t[0]], pts[t[1]], pts[t[2]]}); triangle.scale(scale); svg_vis.draw(triangle, "green"); } @@ -119,18 +109,19 @@ TEST_CASE("Triangulation M shape polygon", "[triangulation]") } // same point in triangulation are not Supported -//TEST_CASE("Triangulation 2 polygons with same point", "[triangulation][not supported]") -//{ -// Slic3r::Polygon polygon1 = { -// Point(416, 346), Point(445, 362), -// Point(463, 389), Point(469, 427) /* This point */, -// Point(445, 491) -// }; -// Slic3r::Polygon polygon2 = { -// Point(495, 488), Point(469, 427) /* This point */, -// Point(495, 364) -// }; -// ExPolygons shape2d = {ExPolygon(polygon1), ExPolygon(polygon2)}; -// std::vector shape_triangles = Triangulation::triangulate(shape2d); -// store_trinagulation(shape2d, shape_triangles); -//} +TEST_CASE("Triangulation 2 polygons with same point", "[triangulation]") +{ + Slic3r::Polygon polygon1 = { + Point(416, 346), Point(445, 362), + Point(463, 389), Point(469, 427) /* This point */, + Point(445, 491) + }; + Slic3r::Polygon polygon2 = { + Point(495, 488), Point(469, 427) /* This point */, + Point(495, 364) + }; + ExPolygons shape2d = {ExPolygon(polygon1), ExPolygon(polygon2)}; + std::vector shape_triangles = Triangulation::triangulate(shape2d); + //Private::store_trinagulation(shape2d, shape_triangles); + CHECK(shape_triangles.size() == 4); +}