Refactor of Intersection of lines to use AABB Tree and retur also inces of intersected lines

Refactor Emboss to remove priv namespace
heal_polygons function got parameter for count of heal iteration of final ExPolygons
heal_polygons return whether heal was successfull or not
This commit is contained in:
Filip Sykala - NTB T15p 2023-10-12 14:40:38 +02:00
parent 037835065d
commit 4724d6791a
7 changed files with 231 additions and 413 deletions

View File

@ -35,14 +35,14 @@ using namespace Slic3r;
using namespace Emboss; using namespace Emboss;
using fontinfo_opt = std::optional<stbtt_fontinfo>; using fontinfo_opt = std::optional<stbtt_fontinfo>;
// for try approach to heal shape by Clipper::Closing // NOTE: approach to heal shape by Clipper::Closing is not working
//#define HEAL_WITH_CLOSING
// functionality to remove all spikes from shape // functionality to remove all spikes from shape
// Potentionaly useable for eliminate spike in layer
//#define REMOVE_SPIKES //#define REMOVE_SPIKES
// do not expose out of this file stbtt_ data types // do not expose out of this file stbtt_ data types
namespace priv{ namespace{
using Polygon = Slic3r::Polygon; using Polygon = Slic3r::Polygon;
bool is_valid(const FontFile &font, unsigned int index); bool is_valid(const FontFile &font, unsigned int index);
fontinfo_opt load_font_info(const unsigned char *data, unsigned int index = 0); fontinfo_opt load_font_info(const unsigned char *data, unsigned int index = 0);
@ -62,7 +62,6 @@ void remove_bad(Polygons &polygons);
void remove_bad(ExPolygons &expolygons); void remove_bad(ExPolygons &expolygons);
// Try to remove self intersection by subtracting rect 2x2 px // Try to remove self intersection by subtracting rect 2x2 px
bool remove_self_intersections(ExPolygons &shape, unsigned max_iteration = 10);
ExPolygon create_bounding_rect(const ExPolygons &shape); ExPolygon create_bounding_rect(const ExPolygons &shape);
void remove_small_islands(ExPolygons &shape, double minimal_area); void remove_small_islands(ExPolygons &shape, double minimal_area);
@ -118,11 +117,9 @@ void remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc);
void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc); void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc);
#endif #endif
};
// spike ... very sharp corner - when not removed cause iteration of heal process // spike ... very sharp corner - when not removed cause iteration of heal process
// index ... index of duplicit point in polygon // index ... index of duplicit point in polygon
bool priv::remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &spike_desc) { bool remove_when_spike(Slic3r::Polygon &polygon, size_t index, const SpikeDesc &spike_desc) {
std::optional<Point> add; std::optional<Point> add;
bool do_erase = false; bool do_erase = false;
@ -206,10 +203,10 @@ bool priv::remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &sp
return false; return false;
} }
void priv::remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates) { void remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates) {
if (duplicates.empty()) if (duplicates.empty())
return; return;
auto check = [](Polygon &polygon, const Point &d) -> bool { auto check = [](Slic3r::Polygon &polygon, const Point &d) -> bool {
double spike_bevel = 1 / SHAPE_SCALE; double spike_bevel = 1 / SHAPE_SCALE;
double spike_length = 5.; double spike_length = 5.;
const static SpikeDesc sd(spike_bevel, spike_length); const static SpikeDesc sd(spike_bevel, spike_length);
@ -239,14 +236,14 @@ void priv::remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &dup
remove_bad(expolygons); remove_bad(expolygons);
} }
bool priv::is_valid(const FontFile &font, unsigned int index) { bool is_valid(const FontFile &font, unsigned int index) {
if (font.data == nullptr) return false; if (font.data == nullptr) return false;
if (font.data->empty()) return false; if (font.data->empty()) return false;
if (index >= font.infos.size()) return false; if (index >= font.infos.size()) return false;
return true; return true;
} }
fontinfo_opt priv::load_font_info( fontinfo_opt load_font_info(
const unsigned char *data, unsigned int index) const unsigned char *data, unsigned int index)
{ {
int font_offset = stbtt_GetFontOffsetForIndex(data, index); int font_offset = stbtt_GetFontOffsetForIndex(data, index);
@ -264,14 +261,14 @@ fontinfo_opt priv::load_font_info(
return font_info; return font_info;
} }
void priv::remove_bad(Polygons &polygons) { void remove_bad(Polygons &polygons) {
polygons.erase( polygons.erase(
std::remove_if(polygons.begin(), polygons.end(), std::remove_if(polygons.begin(), polygons.end(),
[](const Polygon &p) { return p.size() < 3; }), [](const Polygon &p) { return p.size() < 3; }),
polygons.end()); polygons.end());
} }
void priv::remove_bad(ExPolygons &expolygons) { void remove_bad(ExPolygons &expolygons) {
expolygons.erase( expolygons.erase(
std::remove_if(expolygons.begin(), expolygons.end(), std::remove_if(expolygons.begin(), expolygons.end(),
[](const ExPolygon &p) { return p.contour.size() < 3; }), [](const ExPolygon &p) { return p.contour.size() < 3; }),
@ -281,7 +278,7 @@ void priv::remove_bad(ExPolygons &expolygons) {
remove_bad(expolygon.holes); remove_bad(expolygon.holes);
} }
Points priv::collect_close_points(const ExPolygons &expolygons, double distance) { Points collect_close_points(const ExPolygons &expolygons, double distance) {
if (expolygons.empty()) return {}; if (expolygons.empty()) return {};
if (distance < 0.) return {}; if (distance < 0.) return {};
@ -330,6 +327,8 @@ Points priv::collect_close_points(const ExPolygons &expolygons, double distance)
return res; return res;
} }
} // end namespace
bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double distance) bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double distance)
{ {
if (expolygons.empty()) return false; if (expolygons.empty()) return false;
@ -433,63 +432,7 @@ bool Emboss::divide_segments_for_close_point(ExPolygons &expolygons, double dist
return true; return true;
} }
bool priv::remove_self_intersections(ExPolygons &shape, unsigned max_iteration) { std::pair<ExPolygons, bool> Emboss::heal_polygons(const Polygons &shape, bool is_non_zero, unsigned int max_iteration)
if (shape.empty())
return true;
Pointfs intersections_f = intersection_points(shape);
if (intersections_f.empty())
return true;
// create loop permanent memory
Polygons holes;
Points intersections;
while (--max_iteration) {
// convert intersections into Points
assert(intersections.empty());
intersections.reserve(intersections_f.size());
std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections),
[](const Vec2d &p) { return Point(std::floor(p.x()), std::floor(p.y())); });
// intersections should be unique poits
std::sort(intersections.begin(), intersections.end());
auto it = std::unique(intersections.begin(), intersections.end());
intersections.erase(it, intersections.end());
assert(holes.empty());
holes.reserve(intersections.size());
// Fix self intersection in result by subtracting hole 2x2
for (const Point &p : intersections) {
Polygon hole(priv::pts_2x2);
hole.translate(p);
holes.push_back(hole);
}
// Union of overlapped holes is not neccessary
// Clipper calculate winding number separately for each input parameter
// if (holes.size() > 1) holes = Slic3r::union_(holes);
shape = Slic3r::diff_ex(shape, holes, ApplySafetyOffset::Yes);
// TODO: find where diff ex could create same neighbor
remove_same_neighbor(shape);
// find new intersections made by diff_ex
intersections_f = intersection_points(shape);
if (intersections_f.empty())
return true;
else {
// clear permanent vectors
holes.clear();
intersections.clear();
}
}
assert(max_iteration == 0);
assert(!intersections_f.empty());
return false;
}
ExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero)
{ {
const double clean_distance = 1.415; // little grater than sqrt(2) const double clean_distance = 1.415; // little grater than sqrt(2)
ClipperLib::PolyFillType fill_type = is_non_zero ? ClipperLib::PolyFillType fill_type = is_non_zero ?
@ -510,21 +453,47 @@ ExPolygons Emboss::heal_polygons(const Polygons &shape, bool is_non_zero)
if (!duplicits.empty()) { if (!duplicits.empty()) {
polygons.reserve(polygons.size() + duplicits.size()); polygons.reserve(polygons.size() + duplicits.size());
for (const Point &p : duplicits) { for (const Point &p : duplicits) {
Polygon rect_3x3(priv::pts_3x3); Polygon rect_3x3(pts_3x3);
rect_3x3.translate(p); rect_3x3.translate(p);
polygons.push_back(rect_3x3); polygons.push_back(rect_3x3);
} }
} }
ExPolygons res = Slic3r::union_ex(polygons, fill_type); ExPolygons res = Slic3r::union_ex(polygons, fill_type);
const unsigned int max_iteration = 10;
bool is_healed = heal_expolygons(res, max_iteration); bool is_healed = heal_expolygons(res, max_iteration);
return res; return {res, is_healed};
} }
#include "libslic3r/SVG.hpp"
void priv::visualize_heal(const std::string &svg_filepath, const ExPolygons &expolygons) { bool Emboss::heal_expolygons(ExPolygons &shape, unsigned max_iteration)
{
return ::heal_dupl_inter(shape, max_iteration);
}
#include "libslic3r/SVG.hpp" // for visualize_heal
namespace {
Points get_unique_intersections(const Slic3r::IntersectionsLines &intersections)
{
Points result;
if (intersections.empty())
return result;
// convert intersections into Points
result.reserve(intersections.size());
std::transform(intersections.begin(), intersections.end(), std::back_inserter(result),
[](const Slic3r::IntersectionLines &i) { return Point(
std::floor(i.intersection.x()),
std::floor(i.intersection.y()));
});
// intersections should be unique poits
std::sort(result.begin(), result.end());
auto it = std::unique(result.begin(), result.end());
result.erase(it, result.end());
return result;
}
void visualize_heal(const std::string &svg_filepath, const ExPolygons &expolygons)
{
Points pts = to_points(expolygons); Points pts = to_points(expolygons);
BoundingBox bb(pts); BoundingBox bb(pts);
// double svg_scale = SHAPE_SCALE / unscale<double>(1.); // double svg_scale = SHAPE_SCALE / unscale<double>(1.);
@ -536,41 +505,11 @@ void priv::visualize_heal(const std::string &svg_filepath, const ExPolygons &exp
int black_size = std::max(bb.size().x(), bb.size().y()) / 20; int black_size = std::max(bb.size().x(), bb.size().y()) / 20;
svg.draw(duplicits, "black", black_size); svg.draw(duplicits, "black", black_size);
Pointfs intersections_f = intersection_points(expolygons); Slic3r::IntersectionsLines intersections_f = get_intersections(expolygons);
Points intersections; Points intersections = get_unique_intersections(intersections_f);
intersections.reserve(intersections_f.size());
std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections),
[](const Vec2d &p) { return p.cast<int>(); });
svg.draw(intersections, "red", black_size * 1.2); svg.draw(intersections, "red", black_size * 1.2);
} }
bool Emboss::heal_expolygons(ExPolygons &shape, unsigned max_iteration)
{
return priv::heal_dupl_inter(shape, max_iteration);
}
#ifndef HEAL_WITH_CLOSING
namespace {
Points get_unique_intersections(const ExPolygons &shape)
{
Pointfs intersections_f = intersection_points(shape);
Points intersections; // result
if (intersections_f.empty())
return intersections;
// convert intersections into Points
intersections.reserve(intersections_f.size());
std::transform(intersections_f.begin(), intersections_f.end(), std::back_inserter(intersections),
[](const Vec2d &p) { return Point(std::floor(p.x()), std::floor(p.y())); });
// intersections should be unique poits
std::sort(intersections.begin(), intersections.end());
auto it = std::unique(intersections.begin(), intersections.end());
intersections.erase(it, intersections.end());
return intersections;
}
Polygons get_holes_with_points(const Polygons &holes, const Points &points) Polygons get_holes_with_points(const Polygons &holes, const Points &points)
{ {
Polygons result; Polygons result;
@ -609,43 +548,96 @@ bool fill_trouble_holes(const Polygons &holes, const Points &duplicates, const P
return true; return true;
} }
} // namespace // extend functionality from Points.cpp --> collect_duplicates
// with address of duplicated points
struct Duplicate {
Point point;
std::vector<uint32_t> indices;
};
using Duplicates = std::vector<Duplicate>;
Duplicates collect_duplicit_indices(const ExPolygons &expoly)
{
Points pts = to_points(expoly);
bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration) // initialize original index locations
std::vector<uint32_t> idx(pts.size());
iota(idx.begin(), idx.end(), 0);
std::sort(idx.begin(), idx.end(),
[&pts](uint32_t i1, uint32_t i2) { return pts[i1] < pts[i2]; });
Duplicates result;
const Point *prev = &pts[idx.front()];
for (size_t i = 1; i < idx.size(); ++i) {
uint32_t index = idx[i];
const Point *act = &pts[index];
if (*prev == *act) {
// duplicit point
if (!result.empty() && result.back().point == *act) {
// more than 2 points with same coordinate
result.back().indices.push_back(index);
} else {
uint32_t prev_index = idx[i-1];
result.push_back({*act, {prev_index, index}});
}
continue;
}
prev = act;
}
return result;
}
Points get_points(const Duplicates& duplicate_indices)
{
Points result;
if (duplicate_indices.empty())
return result;
// convert intersections into Points
result.reserve(duplicate_indices.size());
std::transform(duplicate_indices.begin(), duplicate_indices.end(), std::back_inserter(result),
[](const Duplicate &d) { return d.point; });
return result;
}
bool heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
{ {
if (shape.empty()) return true; if (shape.empty()) return true;
remove_same_neighbor(shape);
// create loop permanent memory // create loop permanent memory
Polygons holes; Polygons holes;
while (--max_iteration) { while (--max_iteration) {
remove_same_neighbor(shape); Duplicates duplicate_indices = collect_duplicit_indices(shape);
Points duplicates = collect_duplicates(to_points(shape)); //Points duplicates = collect_duplicates(to_points(shape));
Points intersections = get_unique_intersections(shape); IntersectionsLines intersections = get_intersections(shape);
// Check whether shape is already healed // Check whether shape is already healed
if (intersections.empty() && duplicates.empty()) if (intersections.empty() && duplicate_indices.empty())
return true; return true;
if (fill_trouble_holes(holes, duplicates, intersections, shape)) { Points duplicate_points = get_points(duplicate_indices);
Points intersection_points = get_unique_intersections(intersections);
if (fill_trouble_holes(holes, duplicate_points, intersection_points, shape)) {
holes.clear(); holes.clear();
continue; continue;
} }
holes.clear(); holes.clear();
holes.reserve(intersections.size() + duplicates.size()); holes.reserve(intersections.size() + duplicate_points.size());
remove_spikes_in_duplicates(shape, duplicates); remove_spikes_in_duplicates(shape, duplicate_points);
// Fix self intersection in result by subtracting hole 2x2 // Fix self intersection in result by subtracting hole 2x2
for (const Point &p : intersections) { for (const Point &p : intersection_points) {
Polygon hole(priv::pts_2x2); Polygon hole(pts_2x2);
hole.translate(p); hole.translate(p);
holes.push_back(hole); holes.push_back(hole);
} }
// Fix duplicit points by hole 3x3 around duplicit point // Fix duplicit points by hole 3x3 around duplicit point
for (const Point &p : duplicates) { for (const Point &p : duplicate_points) {
Polygon hole(priv::pts_3x3); Polygon hole(pts_3x3);
hole.translate(p); hole.translate(p);
holes.push_back(hole); holes.push_back(hole);
} }
@ -654,45 +646,40 @@ bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
// ApplySafetyOffset::Yes is incompatible with function fill_trouble_holes // ApplySafetyOffset::Yes is incompatible with function fill_trouble_holes
} }
//priv::visualize_heal("C:/data/temp/heal.svg", shape); // Create partialy healed output
assert(false); Duplicates duplicates = collect_duplicit_indices(shape);
shape = {priv::create_bounding_rect(shape)}; IntersectionsLines intersections = get_intersections(shape);
return false; if (duplicates.empty() && intersections.empty()){
} // healed in the last loop
#else
bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
{
remove_same_neighbor(shape);
const float delta = 2.f;
const ClipperLib::JoinType joinType = ClipperLib::JoinType::jtRound;
// remove double points
while (max_iteration) {
--max_iteration;
// if(!priv::remove_self_intersections(shape, max_iteration)) break;
shape = Slic3r::union_ex(shape);
shape = Slic3r::closing_ex(shape, delta, joinType);
// double minimal_area = 1000;
// priv::remove_small_islands(shape, minimal_area);
// check that duplicates and intersections do NOT exists
Points duplicits = collect_duplicates(to_points(shape));
Pointfs intersections_f = intersection_points(shape);
if (duplicits.empty() && intersections_f.empty())
return true; return true;
} }
// priv::visualize_heal("C:/data/temp/heal.svg", shape); // visualize_heal("C:/data/temp/heal.svg", shape);
assert(false); assert(false); // Can not heal this shape
shape = {priv::create_bounding_rect(shape)}; // investigate how to heal better way
ExPolygonsIndices ei(shape);
std::vector<bool> is_healed(shape.size(), {true});
for (const Duplicate &duplicate : duplicates){
for (uint32_t i : duplicate.indices)
is_healed[ei.cvt(i).expolygons_index] = false;
}
for (const IntersectionLines &intersection : intersections) {
is_healed[ei.cvt(intersection.line_index1).expolygons_index] = false;
is_healed[ei.cvt(intersection.line_index2).expolygons_index] = false;
}
for (size_t shape_index = 0; shape_index < shape.size(); shape_index++) {
if (!is_healed[shape_index]) {
// exchange non healed expoly with bb rect
ExPolygon &expoly = shape[shape_index];
expoly = create_bounding_rect({expoly});
}
}
return false; return false;
} }
#endif // !HEAL_WITH_CLOSING
ExPolygon priv::create_bounding_rect(const ExPolygons &shape) { ExPolygon create_bounding_rect(const ExPolygons &shape) {
BoundingBox bb = get_extents(shape); BoundingBox bb = get_extents(shape);
Point size = bb.size(); Point size = bb.size();
if (size.x() < 10) if (size.x() < 10)
@ -716,7 +703,7 @@ ExPolygon priv::create_bounding_rect(const ExPolygons &shape) {
return ExPolygon(rect, hole); return ExPolygon(rect, hole);
} }
void priv::remove_small_islands(ExPolygons &expolygons, double minimal_area) { void remove_small_islands(ExPolygons &expolygons, double minimal_area) {
if (expolygons.empty()) if (expolygons.empty())
return; return;
@ -734,7 +721,7 @@ void priv::remove_small_islands(ExPolygons &expolygons, double minimal_area) {
} }
} }
std::optional<Glyph> priv::get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness) std::optional<Glyph> get_glyph(const stbtt_fontinfo &font_info, int unicode_letter, float flatness)
{ {
int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter); int glyph_index = stbtt_FindGlyphIndex(&font_info, unicode_letter);
if (glyph_index == 0) { if (glyph_index == 0) {
@ -793,15 +780,17 @@ std::optional<Glyph> priv::get_glyph(const stbtt_fontinfo &font_info, int unicod
glyph_polygons.emplace_back(pts); glyph_polygons.emplace_back(pts);
} }
if (!glyph_polygons.empty()) { if (!glyph_polygons.empty()) {
unsigned max_iteration = 10;
// TrueTypeFonts use non zero winding number // TrueTypeFonts use non zero winding number
// https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01 // https://docs.microsoft.com/en-us/typography/opentype/spec/ttch01
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
glyph.shape = Emboss::heal_polygons(glyph_polygons); bool is_non_zero = true;
glyph.shape = Emboss::heal_polygons(glyph_polygons, is_non_zero, max_iteration).first;
} }
return glyph; return glyph;
} }
const Glyph* priv::get_glyph( const Glyph* get_glyph(
int unicode, int unicode,
const FontFile & font, const FontFile & font,
const FontProp & font_prop, const FontProp & font_prop,
@ -818,7 +807,7 @@ const Glyph* priv::get_glyph(
if (!font_info_opt.has_value()) { if (!font_info_opt.has_value()) {
font_info_opt = priv::load_font_info(font.data->data(), font_index); font_info_opt = load_font_info(font.data->data(), font_index);
// can load font info? // can load font info?
if (!font_info_opt.has_value()) return nullptr; if (!font_info_opt.has_value()) return nullptr;
} }
@ -828,7 +817,7 @@ const Glyph* priv::get_glyph(
// Fix for very small flatness because it create huge amount of points from curve // Fix for very small flatness because it create huge amount of points from curve
if (flatness < RESOLUTION) flatness = RESOLUTION; if (flatness < RESOLUTION) flatness = RESOLUTION;
std::optional<Glyph> glyph_opt = priv::get_glyph(*font_info_opt, unicode, flatness); std::optional<Glyph> glyph_opt = get_glyph(*font_info_opt, unicode, flatness);
// IMPROVE: multiple loadig glyph without data // IMPROVE: multiple loadig glyph without data
// has definition inside of font? // has definition inside of font?
@ -864,17 +853,19 @@ const Glyph* priv::get_glyph(
return &it->second; return &it->second;
} }
EmbossStyle priv::create_style(const std::wstring& name, const std::wstring& path) { EmbossStyle create_style(const std::wstring& name, const std::wstring& path) {
return { boost::nowide::narrow(name.c_str()), return { boost::nowide::narrow(name.c_str()),
boost::nowide::narrow(path.c_str()), boost::nowide::narrow(path.c_str()),
EmbossStyle::Type::file_path, FontProp() }; EmbossStyle::Type::file_path, FontProp() };
} }
Point priv::to_point(const stbtt__point &point) { Point to_point(const stbtt__point &point) {
return Point(static_cast<int>(std::round(point.x / SHAPE_SCALE)), return Point(static_cast<int>(std::round(point.x / SHAPE_SCALE)),
static_cast<int>(std::round(point.y / SHAPE_SCALE))); static_cast<int>(std::round(point.y / SHAPE_SCALE)));
} }
} // namespace
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
#include <wingdi.h> #include <wingdi.h>
@ -1004,7 +995,7 @@ EmbossStyles Emboss::get_font_list_by_register() {
if (pos >= font_name_w.size()) continue; if (pos >= font_name_w.size()) continue;
// remove TrueType text from name // remove TrueType text from name
font_name_w = std::wstring(font_name_w, 0, pos); font_name_w = std::wstring(font_name_w, 0, pos);
font_list.emplace_back(priv::create_style(font_name_w, path_w)); font_list.emplace_back(create_style(font_name_w, path_w));
} while (result != ERROR_NO_MORE_ITEMS); } while (result != ERROR_NO_MORE_ITEMS);
delete[] font_name; delete[] font_name;
delete[] fileTTF_name; delete[] fileTTF_name;
@ -1039,7 +1030,7 @@ EmbossStyles Emboss::get_font_list_by_enumeration() {
EmbossStyles font_list; EmbossStyles font_list;
for (const std::wstring &font_name : font_names) { for (const std::wstring &font_name : font_names) {
font_list.emplace_back(priv::create_style(font_name, L"")); font_list.emplace_back(create_style(font_name, L""));
} }
return font_list; return font_list;
} }
@ -1061,7 +1052,7 @@ EmbossStyles Emboss::get_font_list_by_folder() {
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;
std::wstring file_name(fd.cFileName); std::wstring file_name(fd.cFileName);
// TODO: find font name instead of filename // TODO: find font name instead of filename
result.emplace_back(priv::create_style(file_name, search_dir + file_name)); result.emplace_back(create_style(file_name, search_dir + file_name));
} while (::FindNextFile(hFind, &fd)); } while (::FindNextFile(hFind, &fd));
::FindClose(hFind); ::FindClose(hFind);
} }
@ -1095,7 +1086,7 @@ std::unique_ptr<FontFile> Emboss::create_font_file(
std::vector<FontFile::Info> infos; std::vector<FontFile::Info> infos;
infos.reserve(c_size); infos.reserve(c_size);
for (unsigned int i = 0; i < c_size; ++i) { for (unsigned int i = 0; i < c_size; ++i) {
auto font_info = priv::load_font_info(data->data(), i); auto font_info = load_font_info(data->data(), i);
if (!font_info.has_value()) return nullptr; if (!font_info.has_value()) return nullptr;
const stbtt_fontinfo *info = &(*font_info); const stbtt_fontinfo *info = &(*font_info);
@ -1215,16 +1206,16 @@ std::optional<Glyph> Emboss::letter2glyph(const FontFile &font,
int letter, int letter,
float flatness) float flatness)
{ {
if (!priv::is_valid(font, font_index)) return {}; if (!is_valid(font, font_index)) return {};
auto font_info_opt = priv::load_font_info(font.data->data(), font_index); auto font_info_opt = load_font_info(font.data->data(), font_index);
if (!font_info_opt.has_value()) return {}; if (!font_info_opt.has_value()) return {};
return priv::get_glyph(*font_info_opt, letter, flatness); return get_glyph(*font_info_opt, letter, flatness);
} }
const FontFile::Info &Emboss::get_font_info(const FontFile &font, const FontProp &prop) const FontFile::Info &Emboss::get_font_info(const FontFile &font, const FontProp &prop)
{ {
unsigned int font_index = prop.collection_number.value_or(0); unsigned int font_index = prop.collection_number.value_or(0);
assert(priv::is_valid(font, font_index)); assert(is_valid(font, font_index));
return font.infos[font_index]; return font.infos[font_index];
} }
@ -1255,7 +1246,7 @@ ExPolygons letter2shapes(
if (letter == '\t') { if (letter == '\t') {
// '\t' = 4*space => same as imgui // '\t' = 4*space => same as imgui
const int count_spaces = 4; const int count_spaces = 4;
const Glyph *space = priv::get_glyph(int(' '), font, font_prop, cache, font_info_cache); const Glyph *space = get_glyph(int(' '), font, font_prop, cache, font_info_cache);
if (space == nullptr) if (space == nullptr)
return {}; return {};
cursor.x() += count_spaces * space->advance_width; cursor.x() += count_spaces * space->advance_width;
@ -1268,7 +1259,7 @@ ExPolygons letter2shapes(
auto it = cache.find(unicode); auto it = cache.find(unicode);
// Create glyph from font file and cache it // Create glyph from font file and cache it
const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : priv::get_glyph(unicode, font, font_prop, cache, font_info_cache); const Glyph *glyph_ptr = (it != cache.end()) ? &it->second : get_glyph(unicode, font, font_prop, cache, font_info_cache);
if (glyph_ptr == nullptr) if (glyph_ptr == nullptr)
return {}; return {};
@ -1360,7 +1351,7 @@ ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const
assert(font_with_cache.has_value()); assert(font_with_cache.has_value());
const FontFile &font = *font_with_cache.font_file; const FontFile &font = *font_with_cache.font_file;
unsigned int font_index = font_prop.collection_number.value_or(0); unsigned int font_index = font_prop.collection_number.value_or(0);
if (!priv::is_valid(font, font_index)) if (!is_valid(font, font_index))
return {}; return {};
unsigned counter = 0; unsigned counter = 0;
@ -1441,7 +1432,7 @@ void Emboss::apply_transformation(const std::optional<float>& angle, const std::
bool Emboss::is_italic(const FontFile &font, unsigned int font_index) bool Emboss::is_italic(const FontFile &font, unsigned int font_index)
{ {
if (font_index >= font.infos.size()) return false; if (font_index >= font.infos.size()) return false;
fontinfo_opt font_info_opt = priv::load_font_info(font.data->data(), font_index); fontinfo_opt font_info_opt = load_font_info(font.data->data(), font_index);
if (!font_info_opt.has_value()) return false; if (!font_info_opt.has_value()) return false;
stbtt_fontinfo *info = &(*font_info_opt); stbtt_fontinfo *info = &(*font_info_opt);
@ -1481,14 +1472,14 @@ std::string Emboss::create_range_text(const std::string &text,
unsigned int font_index, unsigned int font_index,
bool *exist_unknown) bool *exist_unknown)
{ {
if (!priv::is_valid(font, font_index)) return {}; if (!is_valid(font, font_index)) return {};
std::wstring ws = boost::nowide::widen(text); std::wstring ws = boost::nowide::widen(text);
// need remove symbols not contained in font // need remove symbols not contained in font
std::sort(ws.begin(), ws.end()); std::sort(ws.begin(), ws.end());
auto font_info_opt = priv::load_font_info(font.data->data(), 0); auto font_info_opt = load_font_info(font.data->data(), 0);
if (!font_info_opt.has_value()) return {}; if (!font_info_opt.has_value()) return {};
const stbtt_fontinfo *font_info = &(*font_info_opt); const stbtt_fontinfo *font_info = &(*font_info_opt);
@ -1525,7 +1516,7 @@ double Emboss::get_text_shape_scale(const FontProp &fp, const FontFile &ff)
return scale * SHAPE_SCALE; return scale * SHAPE_SCALE;
} }
namespace priv { namespace {
void add_quad(uint32_t i1, void add_quad(uint32_t i1,
uint32_t i2, uint32_t i2,
@ -1666,7 +1657,7 @@ indexed_triangle_set polygons2model_duplicit(
} }
return result; return result;
} }
} // namespace priv } // namespace
indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d, indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
const IProjection &projection) const IProjection &projection)
@ -1674,8 +1665,8 @@ indexed_triangle_set Emboss::polygons2model(const ExPolygons &shape2d,
Points points = to_points(shape2d); Points points = to_points(shape2d);
Points duplicits = collect_duplicates(points); Points duplicits = collect_duplicates(points);
return (duplicits.empty()) ? return (duplicits.empty()) ?
priv::polygons2model_unique(shape2d, projection, points) : polygons2model_unique(shape2d, projection, points) :
priv::polygons2model_duplicit(shape2d, projection, points, duplicits); polygons2model_duplicit(shape2d, projection, points, duplicits);
} }
std::pair<Vec3d, Vec3d> Emboss::ProjectZ::create_front_back(const Point &p) const std::pair<Vec3d, Vec3d> Emboss::ProjectZ::create_front_back(const Point &p) const
@ -2067,7 +2058,7 @@ double Emboss::get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned
#ifdef REMOVE_SPIKES #ifdef REMOVE_SPIKES
#include <Geometry.hpp> #include <Geometry.hpp>
void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc) void remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
{ {
enum class Type { enum class Type {
add, // Move with point B on A-side and add new point on C-side add, // Move with point B on A-side and add new point on C-side
@ -2204,14 +2195,14 @@ void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
} }
} }
void priv::remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc) void remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc)
{ {
for (Polygon &polygon : polygons) for (Polygon &polygon : polygons)
remove_spikes(polygon, spike_desc); remove_spikes(polygon, spike_desc);
remove_bad(polygons); remove_bad(polygons);
} }
void priv::remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc) void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc)
{ {
for (ExPolygon &expolygon : expolygons) { for (ExPolygon &expolygon : expolygons) {
remove_spikes(expolygon.contour, spike_desc); remove_spikes(expolygon.contour, spike_desc);

View File

@ -167,8 +167,9 @@ namespace Emboss
/// Also try to reduce amount of points and remove useless polygon parts /// Also try to reduce amount of points and remove useless polygon parts
/// </summary> /// </summary>
/// <param name="is_non_zero">Fill type ClipperLib::pftNonZero for overlapping otherwise </param> /// <param name="is_non_zero">Fill type ClipperLib::pftNonZero for overlapping otherwise </param>
/// <returns>Healed shapes</returns> /// <param name="max_iteration">Look at heal_expolygon()::max_iteration</param>
ExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true); /// <returns>Healed shapes with flag is fully healed</returns>
std::pair<ExPolygons, bool> heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10);
/// <summary> /// <summary>
/// NOTE: call Slic3r::union_ex before this call /// NOTE: call Slic3r::union_ex before this call

View File

@ -3,231 +3,47 @@
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/ ///|/
#include "IntersectionPoints.hpp" #include "IntersectionPoints.hpp"
//#define USE_CGAL_SWEEP_LINE
#define USE_AABB_TREE
//#define USE_AABB_TREE_FLOAT
//#define USE_LINE_TO_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>
#include <libslic3r/AABBTreeLines.hpp> #include <libslic3r/AABBTreeLines.hpp>
//NOTE: using CGAL SweepLines is slower !!! (example in git history)
namespace { namespace {
#ifdef USE_AABB_TREE
// NOTE: it is about 18% slower than USE_LINE_TO_LINE on 'contour_ALIENATO.TTF_glyph_i'
using namespace Slic3r; using namespace Slic3r;
Pointfs compute_intersections(const Lines &lines) IntersectionsLines compute_intersections(const Lines &lines)
{ {
if (lines.size() < 3) if (lines.size() < 3)
return {}; return {};
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines);
Pointfs result; IntersectionsLines result;
for (size_t li = 0; li < lines.size()-1; ++li) { for (uint32_t li = 0; li < lines.size()-1; ++li) {
const Line &l = lines[li]; const Line &l = lines[li];
auto intersections = AABBTreeLines::get_intersections_with_line<false, Point, Line>(lines, tree, l); auto intersections = AABBTreeLines::get_intersections_with_line<false, Point, Line>(lines, tree, l);
for (const auto &[p, node_index] : intersections) { for (const auto &[p, node_index] : intersections) {
if (node_index - 1 <= li) if (node_index - 1 <= li)
continue; continue;
if (p == l.a || p == l.b) if (const Line &l_ = lines[node_index];
l_.a == l.a ||
l_.a == l.b ||
l_.b == l.a ||
l_.b == l.b )
// it is duplicit point not intersection
continue; continue;
result.push_back(p.cast<double>());
// NOTE: fix AABBTree to compute intersection with double preccission!!
Vec2d intersection_point = p.cast<double>();
result.push_back(IntersectionLines{li, static_cast<uint32_t>(node_index), intersection_point});
} }
} }
return result; return result;
} }
#endif // USE_AABB_TREE
#ifdef USE_AABB_TREE_FLOAT
// NOTE: It is slower than int tree, but has floating point for intersection
using namespace Slic3r;
Pointfs compute_intersections(const Lines &lines)
{
if (lines.size() < 3)
return {};
Linesf input;
input.reserve(lines.size());
for (const Line &line : lines)
input.emplace_back(line.a.cast<double>(), line.b.cast<double>());
auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(input);
Pointfs result;
for (size_t li = 0; li < lines.size() - 1; ++li) {
const Linef &l = input[li];
auto intersections = AABBTreeLines::get_intersections_with_line<false, Vec2d, Linef>(input, tree, l);
for (const auto &[p, node_index] : intersections) {
if (node_index - 1 <= li)
continue;
if (p == l.a || p == l.b)
continue;
result.push_back(p.cast<double>());
}
}
return result;
}
#endif // USE_AABB_TREE_FLOAT
#ifdef USE_LINE_TO_LINE
// FIXME O(n^2) complexity!
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: Use 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;
BoundingBox bb({a, b});
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;
// NOTE: Be Carefull - Not only neighbor has same point
if (a == b_ || b == a_ || a == a_ || b == b_)
continue;
BoundingBox bb_({a_, b_});
// intersect of BB compare min max
if (bb.overlap(bb_) && l.intersection(l_, &i))
pts.push_back(i.cast<double>());
}
}
return pts;
}
#endif // USE_LINE_TO_LINE
} // namespace } // namespace
Slic3r::Pointfs Slic3r::intersection_points(const Lines &lines) namespace Slic3r {
{ IntersectionsLines get_intersections(const Lines &lines) { return compute_intersections(lines); }
return compute_intersections(lines); IntersectionsLines get_intersections(const Polygon &polygon) { return compute_intersections(to_lines(polygon)); }
IntersectionsLines get_intersections(const Polygons &polygons) { return compute_intersections(to_lines(polygons)); }
IntersectionsLines get_intersections(const ExPolygon &expolygon) { return compute_intersections(to_lines(expolygon)); }
IntersectionsLines get_intersections(const ExPolygons &expolygons) { return compute_intersections(to_lines(expolygons)); }
} }
Slic3r::Pointfs Slic3r::intersection_points(const Polygon &polygon)
{
return compute_intersections(to_lines(polygon));
}
Slic3r::Pointfs Slic3r::intersection_points(const Polygons &polygons)
{
return compute_intersections(to_lines(polygons));
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygon &expolygon)
{
return compute_intersections(to_lines(expolygon));
}
Slic3r::Pointfs Slic3r::intersection_points(const ExPolygons &expolygons)
{
return compute_intersections(to_lines(expolygons));
}
#endif // USE_CGAL_SWEEP_LINE

View File

@ -9,13 +9,19 @@
namespace Slic3r { namespace Slic3r {
struct IntersectionLines {
uint32_t line_index1;
uint32_t line_index2;
Vec2d intersection;
};
using IntersectionsLines = std::vector<IntersectionLines>;
// collect all intersecting points // collect all intersecting points
//FIXME O(n^2) complexity! IntersectionsLines get_intersections(const Lines &lines);
Pointfs intersection_points(const Lines &lines); IntersectionsLines get_intersections(const Polygon &polygon);
Pointfs intersection_points(const Polygon &polygon); IntersectionsLines get_intersections(const Polygons &polygons);
Pointfs intersection_points(const Polygons &polygons); IntersectionsLines get_intersections(const ExPolygon &expolygon);
Pointfs intersection_points(const ExPolygon &expolygon); IntersectionsLines get_intersections(const ExPolygons &expolygons);
Pointfs intersection_points(const ExPolygons &expolygons);
} // namespace Slic3r } // namespace Slic3r
#endif // slic3r_IntersectionPoints_hpp_ #endif // slic3r_IntersectionPoints_hpp_

View File

@ -18,7 +18,7 @@ struct LinesPath{
Polygons polygons; Polygons polygons;
Polylines polylines; }; Polylines polylines; };
LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param); LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param);
ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape); ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param); ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param);
} // namespace } // namespace
@ -45,7 +45,7 @@ ExPolygonsWithIds create_shape_with_ids(const NSVGimage &image, const NSVGLinePa
if (is_fill_used) { if (is_fill_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id); unsigned unique_id = static_cast<unsigned>(2 * shape_id);
result.push_back({unique_id, fill_to_expolygons(lines_path, shape)}); result.push_back({unique_id, fill_to_expolygons(lines_path, shape, param)});
} }
if (is_stroke_used) { if (is_stroke_used) {
unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1); unsigned unique_id = static_cast<unsigned>(2 * shape_id + 1);
@ -352,7 +352,7 @@ LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams &param)
return result; return result;
} }
ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape) ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shape, const NSVGLineParams &param)
{ {
Polygons fill = lines_path.polygons; // copy Polygons fill = lines_path.polygons; // copy
@ -366,7 +366,7 @@ ExPolygons fill_to_expolygons(const LinesPath &lines_path, const NSVGshape &shap
if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD) if (shape.fillRule == NSVGfillRule::NSVG_FILLRULE_EVENODD)
is_non_zero = false; is_non_zero = false;
return Emboss::heal_polygons(fill, is_non_zero); return Emboss::heal_polygons(fill, is_non_zero, param.max_heal_iteration).first;
} }
struct DashesParam{ struct DashesParam{
@ -514,7 +514,8 @@ ExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGshape &sh
polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type)); polygons_append(result, offset(lines_path.polylines, stroke_width / 2, join_type, mitter, end_type));
} }
return Emboss::heal_polygons(result); bool is_non_zero = true;
return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration).first;
} }
} // namespace } // namespace

View File

@ -38,6 +38,9 @@ struct NSVGLineParams
// Is used only with rounded Stroke // Is used only with rounded Stroke
double arc_tolerance = 1.; double arc_tolerance = 1.;
// Maximal count of heal iteration
unsigned max_heal_iteration = 10;
explicit NSVGLineParams(double tesselation_tolerance): explicit NSVGLineParams(double tesselation_tolerance):
tesselation_tolerance(tesselation_tolerance), tesselation_tolerance(tesselation_tolerance),
arc_tolerance(std::pow(tesselation_tolerance, 1/3.)) arc_tolerance(std::pow(tesselation_tolerance, 1/3.))

View File

@ -65,7 +65,7 @@ inline bool has_self_intersection(
lines.reserve(constrained_half_edges.size()); lines.reserve(constrained_half_edges.size());
for (const auto &he : constrained_half_edges) for (const auto &he : constrained_half_edges)
lines.emplace_back(points[he.first], points[he.second]); lines.emplace_back(points[he.first], points[he.second]);
return !intersection_points(lines).empty(); return !get_intersections(lines).empty();
} }
} // namespace priv } // namespace priv