Align outline points

This commit is contained in:
Filip Sykala 2021-04-30 10:07:44 +02:00 committed by Lukas Matena
parent 3c4f68fa3f
commit 10c05ca01e
6 changed files with 443 additions and 73 deletions

View File

@ -279,7 +279,18 @@ std::optional<Slic3r::Vec2d> LineUtils::intersection(const Line &ray1, const Lin
return (ray1.a.cast<double>() + t * v1);
}
LineUtils::LineConnection LineUtils::create_line_connection(const Slic3r::Lines &lines)
double LineUtils::foot(const Line &line, const Point &point)
{
Vec2d a = line.a.cast<double>();
Vec2d vec = point.cast<double>() - a;
Vec2d b = line.b.cast<double>();
Vec2d dir = b - a;
double l2 = dir.squaredNorm();
return vec.dot(dir) / l2;
}
LineUtils::LineConnection LineUtils::create_line_connection(
const Slic3r::Lines &lines)
{
LineConnection line_connection;
static const size_t bad_index = -1;

View File

@ -119,6 +119,15 @@ public:
/// <returns>line direction</returns>
static Point direction(const Line &line) { return line.b - line.a; }
/// <summary>
/// Calculate foot point in maner of Geometry::foot_pt
/// - without unnecessary conversion
/// </summary>
/// <param name="line">input line</param>
/// <param name="point">point to search foot on line</param>
/// <returns>ration betwen point line.a and line.b (in range from 0. to 1.)</returns>
static double foot(const Line &line, const Point& point);
// line index, <a connection, b connection>
using LineConnection = std::map<size_t, std::pair<size_t, size_t>>;
/// <summary>

View File

@ -1209,69 +1209,204 @@ SampleIslandUtils::Field SampleIslandUtils::create_field(
return field;
}
std::pair<Slic3r::ExPolygon, std::map<size_t, size_t>>
SampleIslandUtils::outline_offset(const Slic3r::ExPolygon &island,
coord_t offset_distance)
{
Polygons polygons = offset(island, -offset_distance, ClipperLib::jtSquare);
if (polygons.empty()) return {}; // no place for support point
assert(polygons.front().is_counter_clockwise());
ExPolygon offseted(polygons.front());
for (size_t i = 1; i < polygons.size(); ++i) {
Polygon &hole = polygons[i];
assert(hole.is_clockwise());
offseted.holes.push_back(hole);
}
// TODO: Connect indexes for convert during creation of offset
// !! this implementation was fast for develop BUT NOT for running !!
Lines island_lines = to_lines(island);
Lines offset_lines = to_lines(offseted);
// Convert index map from island index to offseted index
std::map<size_t, size_t> converter;
for (size_t island_line_index = 0; island_line_index < island_lines.size(); ++island_line_index) {
const Line &island_line = island_lines[island_line_index];
Vec2f dir1 = LineUtils::direction(island_line).cast<float>();
for (size_t offset_line_index = 0; offset_line_index < offset_lines.size(); ++offset_line_index) {
const Line &offset_line = offset_lines[offset_line_index];
Vec2f dir2 = LineUtils::direction(offset_line).cast<float>();
if (fabs(dir1.dot(dir2)) < 1e-4) { // in similar direction
Point middle = offset_line.a / 2 + offset_line.b / 2;
double distance = island_line.perp_distance_to(middle);
if (fabs(distance - offset_distance) < 20) {
// found offseted line
converter[island_line_index] = offset_line_index;
break;
}
}
}
}
return {offseted, converter};
}
SupportIslandPoints SampleIslandUtils::sample_outline(
const Field &field, const SampleConfig &config)
{
// TODO: fix it by neighbor line angles
// Do not create inner expoly and test all points to lay inside
Polygons polygons = offset(field.border,-config.minimal_distance_from_outline + 5, ClipperLib::jtSquare);
if (polygons.empty()) return {}; // no place for support point
ExPolygon inner(polygons.front());
for (size_t i = 1; i < polygons.size(); ++i) {
Polygon &hole = polygons[i];
inner.holes.push_back(hole);
}
const ExPolygon &border = field.border;
const Polygon &contour = border.contour;
assert(field.source_indexes.size() >= contour.size());
// convert map from field index to inner(line index)
std::map<size_t, size_t> field_2_inner;
ExPolygon inner;
std::tie(inner, field_2_inner) = outline_offset(border, config.minimal_distance_from_outline);
coord_t max_align_distance = config.max_align_distance;
coord_t sample_distance = config.outline_sample_distance;
coord_t outline_distance = config.minimal_distance_from_outline;
SupportIslandPoints result;
auto add_sample = [&](const Line &line, size_t source_index, coord_t& last_support) {
double line_length_double = line.length();
coord_t line_length = static_cast<coord_t>(line_length_double);
using RestrictionPtr = std::shared_ptr<SupportOutlineIslandPoint::Restriction>;
auto add_sample = [&](size_t index, const RestrictionPtr& restriction, coord_t &last_support) {
using Position = SupportOutlineIslandPoint::Position;
const Line & line = restriction->lines[index];
const double &line_length_double = restriction->lengths[index];
coord_t line_length = static_cast<coord_t>(std::round(line_length_double));
if (last_support + line_length > sample_distance) {
Point dir = LineUtils::direction(line);
Point perp = PointUtils::perp(dir);
double size = perp.cast<double>().norm();
Point move_from_outline = perp * (outline_distance / size);
Point direction = LineUtils::direction(line);
do {
double ratio = (sample_distance - last_support) / line_length_double;
Point point = line.a + dir * ratio + move_from_outline;
// TODO: improve prevent sampling out of field near sharp corner
// use range for possible ratio
if (inner.contains(point))
result.emplace_back(std::make_unique<SupportOutlineIslandPoint>(
point, source_index, SupportIslandPoint::Type::outline));
result.emplace_back(
std::make_unique<SupportOutlineIslandPoint>(
Position(index, ratio), restriction,
SupportIslandPoint::Type::outline)
);
last_support -= sample_distance;
} while (last_support + line_length > sample_distance);
}
last_support += line_length;
};
Lines contour_lines = to_lines(field.border.contour);
coord_t last_support = sample_distance / 2;
for (const Line &line : contour_lines) {
size_t index = &line - &contour_lines.front();
assert(field.source_indexes.size() > index);
size_t source_index = field.source_indexes[index];
if (source_index == field.source_indexe_for_change) {
last_support = sample_distance / 2;
continue;
auto add_circle_sample = [&](const Polygon& polygon) {
// IMPROVE: find interesting points to start sampling
Lines lines = to_lines(polygon);
std::vector<double> lengths;
lengths.reserve(lines.size());
double sum_lengths = 0;
for (const Line &line : lines) {
double length = line.length();
sum_lengths += length;
lengths.push_back(length);
}
add_sample(line, source_index, last_support);
}
size_t index_offset = contour_lines.size();
for (const Polygon &hole : field.border.holes) {
Lines hole_lines = to_lines(hole);
coord_t last_support = sample_distance / 2;
for (const Line &line : hole_lines) {
size_t hole_line_index = (&line - &hole_lines.front());
size_t index = index_offset + hole_line_index;
assert(field.source_indexes.size() > index);
// no samples on this polygon
using Restriction = SupportOutlineIslandPoint::RestrictionCircleSequence;
auto restriction = std::make_shared<Restriction>(lines, lengths, max_align_distance);
coord_t last_support = std::min(static_cast<coord_t>(sum_lengths), sample_distance) / 2;
for (size_t index = 0; index < lines.size(); ++index) {
add_sample(index, restriction, last_support);
}
};
// sample line sequence
auto add_lines_samples = [&](const Lines &inner_lines,
size_t first_index,
size_t last_index) {
Lines lines;
// is over start ?
if (first_index > last_index) {
size_t count = first_index + inner_lines.size() - last_index;
lines.reserve(count);
std::copy(inner_lines.begin() + last_index,
inner_lines.end(),
std::back_inserter(lines));
std::copy(inner_lines.begin(),
inner_lines.begin() + first_index,
std::back_inserter(lines));
} else {
size_t count = last_index - first_index;
lines.reserve(count);
std::copy(inner_lines.begin() + first_index,
inner_lines.begin() + last_index,
std::back_inserter(lines));
}
// IMPROVE: find interesting points to start sampling
std::vector<double> lengths;
lengths.reserve(lines.size());
double sum_lengths = 0;
for (const Line &line : lines) {
double length = line.length();
sum_lengths += length;
lengths.push_back(length);
}
using Restriction = SupportOutlineIslandPoint::RestrictionLineSequence;
auto restriction = std::make_shared<Restriction>(lines, lengths, max_align_distance);
// CHECK: Is correct to has always one support on outline sequence?
// or no sample small sequence at all?
coord_t last_support = std::min(static_cast<coord_t>(sum_lengths), sample_distance) / 2;
for (size_t index = 0; index < lines.size(); ++index) {
add_sample(index, restriction, last_support);
}
};
const size_t& change_index = field.source_indexe_for_change;
auto sample_polygon = [&](const Polygon &polygon,
const Polygon &inner_polygon,
size_t index_offset) {
// contain polygon tiny wide change?
size_t first_change_index = polygon.size();
for (size_t polygon_index = 0; polygon_index < polygon.size(); ++polygon_index) {
size_t index = polygon_index + index_offset;
assert(index < field.source_indexes.size());
size_t source_index = field.source_indexes[index];
add_sample(line, source_index, last_support);
if (source_index == change_index) {
// found change from wide to tiny part
first_change_index = polygon_index;
break;
}
}
index_offset += hole_lines.size();
// is polygon without change
if (first_change_index == polygon.size()) {
add_circle_sample(inner_polygon);
} else { // exist change create line sequences
// initialize with non valid values
Lines inner_lines = to_lines(inner_polygon);
size_t inner_invalid = inner_lines.size();
// first and last index to inner lines
size_t inner_first = inner_invalid;
size_t inner_last = inner_invalid;
for (size_t polygon_index = first_change_index + 1;
polygon_index != first_change_index; ++polygon_index) {
if (polygon_index == polygon.size()) polygon_index = 0;
size_t index = polygon_index + index_offset;
assert(index < field.source_indexes.size());
size_t source_index = field.source_indexes[index];
if (source_index == change_index) {
if (inner_first == inner_invalid) continue;
// create Restriction object
add_lines_samples(inner_lines, inner_first, inner_last);
inner_first = inner_invalid;
inner_last = inner_invalid;
continue;
}
auto convert_index_item = field_2_inner.find(polygon_index);
// check if exist inner line
if (convert_index_item == field_2_inner.end()) continue;
inner_last = convert_index_item->second - index_offset;
// initialize first index
if (inner_first == inner_invalid) inner_first = inner_last;
}
}
};
size_t index_offset = 0;
sample_polygon(contour, inner.contour, index_offset);
index_offset = contour.size();
for (size_t hole_index = 0; hole_index < border.holes.size(); ++hole_index) {
const Polygon &hole = border.holes[hole_index];
sample_polygon(hole, inner.holes[hole_index], index_offset);
index_offset += hole.size();
}
return result;
}

View File

@ -289,8 +289,21 @@ public:
/// <returns>support for outline</returns>
static SupportIslandPoints sample_outline(const Field & field,
const SampleConfig &config);
private:
/// <summary>
/// create offsetted field
/// </summary>
/// <param name="island">source field</param>
/// <param name="offset_distance">distance from outline</param>
/// <returns>offseted field
/// First - offseted island outline
/// Second - map for convert source field index to result border index
/// </returns>
static std::pair<Slic3r::ExPolygon, std::map<size_t, size_t>>
outline_offset(const Slic3r::ExPolygon &island, coord_t offset_distance);
// debug draw functions
public :
static void draw(SVG & svg,
const Field &field,
bool draw_border_line_indexes = false,

View File

@ -1,5 +1,6 @@
#include "SupportIslandPoint.hpp"
#include "VoronoiGraphUtils.hpp"
#include "LineUtils.hpp"
using namespace Slic3r::sla;
@ -39,9 +40,7 @@ coord_t SupportIslandPoint::move(const Point &destination)
{
Point diff = destination - point;
point = destination;
// TODO: check move out of island !!
// + need island ExPolygon
return diff.x() + diff.y(); // Manhatn distance
return abs(diff.x()) + abs(diff.y()); // Manhatn distance
}
std::string SupportIslandPoint::to_string(const Type &type)
@ -86,9 +85,88 @@ coord_t SupportCenterIslandPoint::move(const Point &destination)
VoronoiGraphUtils::align(position, destination,
configuration->max_align_distance);
Point new_point = VoronoiGraphUtils::create_edge_point(position);
Point move = new_point - point;
point = new_point;
coord_t manhatn_distance = abs(move.x()) + abs(move.y());
return manhatn_distance;
return SupportIslandPoint::move(new_point);
}
///////////////
// Point on Outline
///////////////
SupportOutlineIslandPoint::SupportOutlineIslandPoint(
Position position, std::shared_ptr<Restriction> restriction, Type type)
: SupportIslandPoint(calc_point(position, *restriction), type)
, position(position)
, restriction(std::move(restriction))
{}
bool SupportOutlineIslandPoint::can_move() const { return true; }
coord_t SupportOutlineIslandPoint::move(const Point &destination)
{
size_t index = position.index;
MoveResult closest = create_result(index, destination);
const double &length = restriction->lengths[position.index];
double distance = (1.0 - position.ratio) * length;
while (distance < restriction->max_align_distance) {
auto next_index = restriction->next_index(index);
if (!next_index.has_value()) break;
index = *next_index;
update_result(closest, index, destination);
distance += restriction->lengths[index];
}
index = position.index;
distance = static_cast<coord_t>(position.ratio) * length;
while (distance < restriction->max_align_distance) {
auto prev_index = restriction->prev_index(index);
if (!prev_index.has_value()) break;
index = *prev_index;
update_result(closest, index, destination);
distance += restriction->lengths[index];
}
// apply closest result of move
this->point = closest.point;
this->position = closest.position;
return closest.distance;
}
Slic3r::Point SupportOutlineIslandPoint::calc_point(const Position &position, const Restriction &restriction)
{
const Line &line = restriction.lines[position.index];
Point direction = LineUtils::direction(line);
return line.a + direction * position.ratio;
}
SupportOutlineIslandPoint::MoveResult SupportOutlineIslandPoint::create_result(
size_t index, const Point &destination)
{
const Line &line = restriction->lines[index];
double line_ratio_full = LineUtils::foot(line, destination);
double line_ratio = std::clamp(line_ratio_full, 0., 1.);
Position new_position(index, line_ratio);
Point new_point = calc_point(new_position, *restriction);
double point_distance = (new_point - destination).cast<double>().norm();
return MoveResult(new_position, new_point, point_distance);
}
void SupportOutlineIslandPoint::update_result(MoveResult & result,
size_t index,
const Point &destination)
{
const Line &line = restriction->lines[index];
double line_ratio_full = LineUtils::foot(line, destination);
double line_ratio = std::clamp(line_ratio_full, 0., 1.);
Position new_position(index, line_ratio);
Point new_point = calc_point(new_position, *restriction);
Point diff = new_point - destination;
if (abs(diff.x()) > result.distance) return;
if (abs(diff.y()) > result.distance) return;
double point_distance = diff.cast<double>().norm();
if (result.distance > point_distance) {
result.distance = point_distance;
result.position = new_position;
result.point = new_point;
}
}

View File

@ -107,33 +107,157 @@ public:
};
/// <summary>
/// DTO Support point laying on Outline of island
/// Support point laying on Outline of island
/// Restriction to move only on outline
/// </summary>
class SupportOutlineIslandPoint : public SupportIslandPoint
{
public:
// index of line form island outline
size_t index;
// definition of restriction
class Restriction;
struct Position
{
// index of line form island outline - index into Restriction
// adress line inside inner polygon --> SupportOutline
size_t index;
// define position on line by ratio
// from 0 (line point a)
// to 1 (line point b)
float ratio;
Position(size_t index, float ratio) : index(index), ratio(ratio) {}
};
Position position;
// store lines for allowed move - with distance from island source lines
std::shared_ptr<Restriction> restriction;
public:
SupportOutlineIslandPoint(Slic3r::Point point,
size_t index,
Type type = Type::outline)
: SupportIslandPoint(point, type), index(index)
{}
SupportOutlineIslandPoint(Position position,
std::shared_ptr<Restriction> restriction,
Type type = Type::outline);
// return true
bool can_move() const override;
bool can_move() const override { return true; }
/// <summary>
/// Move nearest to destination point
/// only along restriction lines
/// + change current position
/// </summary>
/// <param name="destination">Wanted support position</param>
/// <returns>move distance manhatn</returns>
coord_t move(const Point &destination) override;
coord_t move(const Point &destination) override
{
// TODO: For decide of move need information about
// + island outlines \ May be
// + distance from outline / offseted outlines
// + search distance for allowed move over outlines(count, distance)
assert(false); // Not implemented
return 0;
}
/// <summary>
/// Calculate 2d point belong to line position
/// </summary>
/// <param name="position">Define position on line from restriction</param>
/// <param name="restriction">Hold lines</param>
/// <returns>Position in 2d</returns>
static Point calc_point(const Position & position,
const Restriction &restriction);
/// <summary>
/// Keep data for align support point on bordred of island
/// Define possible move of point along outline
/// IMPROVE: Should contain list of Points on outline.
/// (to keep maximal distance of neighbor points on outline)
/// </summary>
class Restriction
{
public:
// line restriction
// must be connected line.a == prev_line.b && line.b == next_line.a
Lines lines;
// keep stored line lengths
// same size as lines
std::vector<double> lengths;
// maximal distance for search nearest line to destination point during aligning
coord_t max_align_distance;
Restriction(Lines lines,
std::vector<double> lengths,
coord_t max_align_distance)
: lines(lines)
, lengths(lengths)
, max_align_distance(max_align_distance)
{
assert(lines.size() == lengths.size());
}
virtual std::optional<size_t> next_index(size_t index) const = 0;
virtual std::optional<size_t> prev_index(size_t index) const = 0;
};
class RestrictionLineSequence: public Restriction
{
public:
// inherit constructors
using Restriction::Restriction;
virtual std::optional<size_t> next_index(size_t index) const override
{
assert(index < lines.size());
++index;
if (index >= lines.size()) return {}; // index out of range
return index;
}
virtual std::optional<size_t> prev_index(size_t index) const override
{
assert(index < lines.size());
if (index >= lines.size()) return {}; // index out of range
if (index == 0) return {}; // no prev line
return index - 1;
}
};
class RestrictionCircleSequence : public Restriction
{
public:
// inherit constructors
using Restriction::Restriction;
virtual std::optional<size_t> next_index(size_t index) const override
{
assert(index < lines.size());
if (index >= lines.size()) return {}; // index out of range
++index;
if (index == lines.size()) return 0;
return index;
}
virtual std::optional<size_t> prev_index(size_t index) const override
{
assert(index < lines.size());
if (index >= lines.size()) return {}; // index out of range
if (index == 0) return lines.size() - 1;
return index - 1;
}
};
private:
// DTO for result of move
struct MoveResult
{
// define position on restriction line
Position position;
// point laying on restricted line
Point point;
// distance point on restricted line from destination point
double distance;
MoveResult(Position position, Point point, double distance)
: position(position), point(point), distance(distance)
{}
};
MoveResult create_result(size_t index, const Point &destination);
void update_result(MoveResult& result, size_t index, const Point &destination);
};
} // namespace Slic3r::sla