Better healing for Glyph shape - remove duplicit points + self intersections

Add search of intersecting points (compared with CGAL)
Triangulation can [optionaly] accept multi points
This commit is contained in:
Filip Sykala - NTB T15p 2022-08-25 13:28:10 +02:00
parent 94f735168c
commit 63121cee2e
18 changed files with 892 additions and 241 deletions

View File

@ -172,6 +172,7 @@ public:
BoundingBox rotated(double angle, const Point &center) const;
void rotate(double angle) { (*this) = this->rotated(angle); }
void rotate(double angle, const Point &center) { (*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);

View File

@ -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

View File

@ -3,6 +3,7 @@
#include <cstdlib>
#include <boost/nowide/convert.hpp>
#include <ClipperUtils.hpp> // 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);
/// <summary>
/// TODO: move to ExPolygon utils
/// Remove multi points. When exist multi point dilate it by rect 3x3 and union result.
/// </summary>
/// <param name="expolygons">Shape which can contain same point, will be extended by dilatation rects</param>
/// <returns>ExPolygons with only unique points</returns>
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<stbtt_fontinfo> 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<double>(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<int>());
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<Emboss::Glyph> 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<Emboss::Glyph> 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<int>(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<Point> points;
std::set<Point> 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<Point> 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<int>(std::round(point.x / Emboss::SHAPE_SCALE)),
static_cast<int>(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<stbtt_fontinfo> 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<Vec3i> &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<Vec3i> shape_triangles=Triangulation::triangulate(shape2d, points);
uint32_t count_point = points.size();
indexed_triangle_set result;
result.vertices.reserve(2 * count_point);
std::vector<Vec3f> &front_points = result.vertices; // alias
std::vector<Vec3f> 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<uint32_t> changes = Triangulation::create_changes(points, duplicits);
std::vector<Vec3i> 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<Vec3f> &front_points = result.vertices;
std::vector<Vec3f> 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<uint32_t>::max();
for (uint32_t i = 0; i < changes.size(); ++i) {
uint32_t index = changes[i];
if (max_index != std::numeric_limits<uint32_t>::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<Vec3i> 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<Vec3f, Vec3f> Emboss::ProjectZ::create_front_back(const Point &p) const
{

View File

@ -155,6 +155,13 @@ public:
/// <returns>Inner polygon cw(outer ccw)</returns>
static ExPolygons text2shapes(FontFileWithCache &font, const char *text, const FontProp &font_prop);
/// <summary>
/// Fix intersections and self intersections in polygons glyph shape
/// </summary>
/// <param name="shape">Input shape to heal</param>
/// <returns>Healed shapes</returns>
static ExPolygons heal_shape(const Polygons &shape);
/// <summary>
/// Use data from font property to modify transformation
/// </summary>

View File

@ -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;

View File

@ -0,0 +1,170 @@
#include "IntersectionPoints.hpp"
//#define USE_CGAL_SWEEP_LINE
#ifdef USE_CGAL_SWEEP_LINE
#include <CGAL/Cartesian.h>
#include <CGAL/MP_Float.h>
#include <CGAL/Quotient.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Sweep_line_2_algorithms.h>
using NT = CGAL::Quotient<CGAL::MP_Float>;
using Kernel = CGAL::Cartesian<NT>;
using P2 = Kernel::Point_2;
using Traits_2 = CGAL::Arr_segment_traits_2<Kernel>;
using Segment = Traits_2::Curve_2;
using Segments = std::vector<Segment>;
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<P2> 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 <libslic3r/BoundingBox.hpp>
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: BentleyOttmann_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<double>());
}
}
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

View File

@ -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_

View File

@ -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)

View File

@ -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_

View File

@ -158,6 +158,24 @@ bool has_duplicate_points(std::vector<Point> &&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);

View File

@ -260,6 +260,9 @@ inline bool has_duplicate_successive_points_closed(const std::vector<Point> &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)

View File

@ -377,4 +377,10 @@ void SVG::export_expolygons(const char *path, const std::vector<std::pair<Slic3r
svg.Close();
}
float SVG::to_svg_coord(float x)
{
// return x;
return unscale<float>(x) * 10.f;
}
} // namespace Slic3r

View File

@ -167,9 +167,9 @@ public:
{ export_expolygons(path.c_str(), expolygons_with_attributes); }
private:
static float to_svg_coord(float x) throw() { return unscale<float>(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); }
};
}

View File

@ -1,35 +1,90 @@
#include "Triangulation.hpp"
#include "IntersectionPoints.hpp"
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/spatial_sort.h>
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<uint32_t>(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<uint32_t>(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<uint32_t, K>;
@ -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<Vec3i> 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<CDT::Face_handle> 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<Vec3i> 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<uint32_t>::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<uint32_t> duplicit_indices(duplicits.size(), std::numeric_limits<uint32_t>::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<uint32_t>::max()) {
d_index = index;
changes.push_back(index);
++index;
} else {
changes.push_back(d_index);
}
}
return changes;
}

View File

@ -34,15 +34,37 @@ public:
static Indices triangulate(const Polygons &polygons);
static Indices triangulate(const ExPolygons &expolygons);
/// <summary>
/// Filter out triagles without both side edge or inside half edges
/// Main purpose: Filter out triangles which lay outside of ExPolygon
/// given to triangulation
/// </summary>
/// <param name="indices">Triangles</param>
/// <param name="half_edges">Only outer edges</param>
static void remove_outer(Indices &indices, const HalfEdges &half_edges);
// Map for convert original index to set without duplication
// from_index<to_index>
using Changes = std::vector<uint32_t>;
/// <summary>
/// Create conversion map from original index into new
/// with respect of duplicit point
/// </summary>
/// <param name="points">input set of points</param>
/// <param name="duplicits">duplicit points collected from points</param>
/// <returns>Conversion map for point index</returns>
static Changes create_changes(const Points &points, const Points &duplicits);
/// <summary>
/// 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"
/// </summary>
/// <param name="expolygons">Input shape to triangulation - define edges</param>
/// <param name="points">Points from expolygons</param>
/// <returns>Triangle indices</returns>
static Indices triangulate(const ExPolygons &expolygons, const Points& points);
/// <summary>
/// Triangulation for expolygons containing multiple points with same coordinate
/// </summary>
/// <param name="expolygons">Input shape to triangulation - define edge</param>
/// <param name="points">Points from expolygons</param>
/// <param name="changes">Changes swap for indicies into points</param>
/// <returns>Triangle indices</returns>
static Indices triangulate(const ExPolygons &expolygons, const Points& points, const Changes& changes);
};
} // namespace Slic3r

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" width="1000px" height="1000px">
<path fill="#808080" d="M 437 868 425 886 417 866 405 884 400 874 388 880 402 896 381 899 395 915 374 918 388 934 367 936 381 952 360 955 374 971 360 982 371 973 353 961 373 953 355 941 375 933 357 921 377 913 359 901 379 894 361 882 372 869 365 857 350 873 346 852 331 867 326 856 313 859 323 878 301 875 311 894 290 892 300 911 279 909 289 927 267 925 278 944 264 947 276 941 262 925 283 922 269 906 290 903 275 887 297 884 282 869 303 866 289 850 300 847 296 834 278 846 278 824 260 836 254 822 240 822 246 843 226 836 231 856 211 849 217 870 197 863 202 884 182 877 188 898 180 892 193 889 183 870 204 872 193 853 215 855 204 836 225 838 214 819 232 809 230 795 210 804 214 783 195 792 188 774 175 771 175 792 157 780 158 802 140 790 140 812 122 800 123 822 110 818 124 818 118 797 138 804 132 783 153 790 147 769 167 776 161 756 172 758 173 744 152 749 152 734 154 720 133 723 134 708 122 702 118 723 103 707 98 729 83 713 79 734 60 730 73 733 72 712 91 723 90 701 108 713 107 691 124 692 129 679 107 678 120 661 99 659 112 643 91 641 96 630 86 621 76 640 66 621 56 641 46 622 36 641 30 632 42 638 47 617 61 633 66 612 81 627 85 606 92 614 100 603 79 596 96 583 76 576 93 563 72 556 81 550 81 542 73 531 60 548 53 527 40 544 25 532 36 541 44 521 56 539 64 520 81 528 92 520 75 507 95 500 77 487 98 480 80 467 92 454 87 441 70 455 68 433 52 447 42 432 50 443 63 426 70 446 83 428 96 440 108 434 94 418 115 415 101 399 122 396 108 380 122 369 120 355 100 364 104 343 85 352 82 338 87 351 104 338 105 359 122 346 126 358 139 354 128 336 149 337 138 319 160 320 148 302 170 303 159 284 168 287 168 273 148 278 142 258 145 272 164 262 172 279 185 276 175 257 197 259 187 241 208 243 198 224 219 226 209 207 224 202 220 192 226 199 231 196 245 193 235 174 256 177 246 158 260 155 274 155 267 134 288 141 282 120 302 126 296 106 313 102 327 101 319 81 338 73 352 73 346 52 366 60 361 39 380 34 393 38 394 17 411 29 412 8 429 21 430 -1 440 5 448 17 462 0 467 21 481 5 496 18 499 32 518 22 515 43 534 34 543 52 544 66 564 58 570 76 569 90 590 86 582 105 603 101 594 121 615 117 614 130 614 144 635 137 628 158 649 152 655 171 653 185 674 182 665 201 686 198 684 211 698 209 690 190 700 192 687 197 700 214 688 218 686 232 707 229 697 248 718 246 708 265 730 262 720 281 741 279 736 288 750 290 747 269 766 278 763 257 778 258 764 260 773 279 752 275 760 295 744 299 741 312 762 311 751 330 772 329 761 347 783 346 777 355 780 364 793 369 794 347 811 361 813 339 830 353 831 331 839 338 825 336 829 358 809 348 813 370 793 361 797 382 786 378 780 391 802 394 787 409 809 412 794 428 815 431 810 444 821 452 828 431 841 448 848 428 861 445 867 424 879 432 866 427 865 449 848 436 846 457 829 444 828 465 816 460 808 471 828 479 811 491 831 498 813 511 833 518 825 527 834 537 846 519 854 539 866 520 874 540 886 522 896 532 884 525 878 545 865 528 859 549 845 532 839 553 824 546 813 555 831 567 811 575 828 587 808 594 826 607 816 610 823 622 838 607 842 628 857 612 861 633 876 618 880 622 870 640 861 621 850 639 841 620 830 638 821 619 810 637 810 628 797 634 811 650 790 652 803 669 782 671 796 687 786 686 790 699 808 688 807 709 825 698 824 719 843 708 842 730 860 718 861 730 854 718 839 734 835 713 820 728 816 707 801 723 796 702 781 717 774 705 760 707 770 727 749 724 758 743 741 750 741 764 762 757 756 778 776 771 770 792 790 785 784 806 805 799 810 818 806 805 788 816 789 795 771 806 771 785 753 797 754 775 736 787 736 765 718 777 722 768 708 768 713 788 693 781 699 802 684 802 681 816 703 814 692 832 713 831 703 849 724 848 713 866 735 865 724 883 746 881 741 892 740 878 720 885 726 865 705 872 711 851 691 858 696 837 676 844 682 824 662 831 662 818 649 814 649 836 631 823 631 845 619 840 613 853 635 856 620 871 641 875 626 890 648 894 633 909 654 913 639 928 661 931 656 947 658 933 637 936 647 917 626 919 636 900 615 903 625 884 603 886 614 867 592 869 592 852 580 846 576 867 561 851 556 872 548 865 540 876 560 884 542 896 562 904 545 916 565 923 547 936 567 943 549 955 569 963 562 982 567 969 546 967 560 951 539 948 553 932 532 929 546 913 525 911 539 895 517 892 520 872 509 864 501 884 489 867 482 887 476 878 465 887 483 899 462 906 480 919 460 926 477 939 457 946 474 959 454 966 472 978 460 994 468 983 448 975 466 963 446 955 463 943 443 936 461 923 441 916 459 904 439 896 446 878 z" />
<path fill="#404040" d="M 391 214 383 234 371 216 363 236 354 228 342 222 338 243 323 228 319 249 304 235 300 256 296 247 283 242 282 264 265 251 263 272 246 259 245 280 240 272 226 272 232 293 212 286 218 307 197 300 203 320 188 322 175 326 186 344 165 343 176 361 155 361 166 379 156 378 144 384 158 400 136 403 151 419 129 421 143 437 130 446 120 455 138 467 118 475 136 487 116 495 135 507 124 520 135 529 143 509 155 527 163 508 175 526 183 506 195 524 203 505 204 514 217 519 219 498 235 511 237 490 254 504 256 482 270 487 283 491 283 469 301 481 301 460 316 462 331 450 345 451 340 430 360 438 355 417 375 425 370 404 380 408 393 404 382 386 403 387 392 369 404 366 416 359 401 344 422 340 407 325 421 311 432 303 416 289 437 284 420 270 432 259 433 245 412 249 420 230 399 234 402 222 z" />
<path fill="#404040" d="M 467 232 486 242 476 254 469 266 489 272 473 285 494 291 477 305 498 311 481 324 502 330 496 347 491 360 512 362 508 377 507 391 528 386 521 406 542 401 534 421 555 416 551 426 553 440 572 431 568 452 588 444 596 463 598 477 618 467 622 482 628 495 644 481 646 502 663 488 669 501 676 513 691 497 696 517 710 501 721 514 730 525 742 507 750 527 762 510 769 530 782 512 784 522 788 520 796 509 776 501 794 489 774 481 782 464 788 452 767 448 782 433 761 429 776 413 755 410 770 394 749 391 756 382 758 368 737 371 747 352 725 355 735 336 714 338 718 311 697 314 706 295 685 298 692 292 689 279 670 289 672 268 653 279 655 257 636 268 638 247 619 257 622 248 615 236 600 252 596 231 581 246 576 225 562 241 558 230 549 220 537 237 529 218 517 236 509 216 497 234 489 214 477 232 477 222 z" />
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -6,7 +6,7 @@
#include <optional>
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/Utils.hpp> // for next_highest_power_of_2()
#include <libslic3r/IntersectionPoints.hpp>
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<int>());
// 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();

View File

@ -6,31 +6,21 @@
using namespace Slic3r;
namespace Private{
void store_trinagulation(const ExPolygons & shape,
void store_trinagulation(const ExPolygons &shape,
const std::vector<Vec3i> &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<Vec3i> 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<Vec3i> shape_triangles = Triangulation::triangulate(shape2d);
//Private::store_trinagulation(shape2d, shape_triangles);
CHECK(shape_triangles.size() == 4);
}