mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 10:52:02 +08:00
462 lines
17 KiB
C++
462 lines
17 KiB
C++
#include "LineUtils.hpp"
|
|
#include <libslic3r/Geometry.hpp>
|
|
#include <libslic3r/Geometry/Circle.hpp>
|
|
#include <functional>
|
|
#include "VectorUtils.hpp"
|
|
#include "PointUtils.hpp"
|
|
|
|
using namespace Slic3r::sla;
|
|
|
|
// sort counter clock wise lines
|
|
void LineUtils::sort_CCW(Lines &lines, const Point& center)
|
|
{
|
|
std::function<double(const Line &)> calc = [¢er](const Line &line) {
|
|
Point p = line.a - center;
|
|
return std::atan2(p.y(), p.x());
|
|
};
|
|
VectorUtils::sort_by(lines, calc);
|
|
}
|
|
|
|
bool LineUtils::is_parallel_y(const Line &line) {
|
|
coord_t x_change = line.a.x() - line.b.x();
|
|
return (x_change == 0);
|
|
}
|
|
bool LineUtils::is_parallel_y(const Linef &line)
|
|
{
|
|
double x_change = line.a.x() - line.b.x();
|
|
return (fabs(x_change) < std::numeric_limits<double>::epsilon());
|
|
}
|
|
|
|
std::optional<Slic3r::Line> LineUtils::crop_ray(const Line & ray,
|
|
const Point ¢er,
|
|
double radius)
|
|
{
|
|
if (is_parallel_y(ray)) {
|
|
coord_t x = ray.a.x();
|
|
coord_t diff = x - center.x();
|
|
coord_t abs_diff = abs(diff);
|
|
if (abs_diff > radius) return {};
|
|
// create cross points
|
|
double move_y = sqrt(radius * radius - static_cast<double>(x) * x);
|
|
coord_t y = static_cast<coord_t>(std::round(move_y));
|
|
coord_t cy = center.y();
|
|
Point first(x, cy + y);
|
|
Point second(x,cy - y);
|
|
return Line(first, second);
|
|
} else {
|
|
Line moved_line(ray.a - center, ray.b - center);
|
|
double a, b, c;
|
|
std::tie(a, b, c) = get_param(moved_line);
|
|
std::pair<Vec2d, Vec2d> points;
|
|
int count = Slic3r::Geometry::ray_circle_intersections(
|
|
radius, a, b, c, points);
|
|
if (count != 2) return {};
|
|
return Line(points.first.cast<coord_t>() + center,
|
|
points.second.cast<coord_t>() + center);
|
|
}
|
|
}
|
|
std::optional<Slic3r::Linef> LineUtils::crop_ray(const Linef &ray,
|
|
const Point ¢er,
|
|
double radius)
|
|
{
|
|
Vec2d center_d = center.cast<double>();
|
|
if (is_parallel_y(ray)) {
|
|
double x = ray.a.x();
|
|
double diff = x - center_d.x();
|
|
double abs_diff = fabs(diff);
|
|
if (abs_diff > radius) return {};
|
|
// create cross points
|
|
double y = sqrt(radius * radius - x * x);
|
|
Vec2d first(x, y);
|
|
Vec2d second(x, -y);
|
|
return Linef(first + center_d,
|
|
second + center_d);
|
|
} else {
|
|
Linef moved_line(ray.a - center_d, ray.b - center_d);
|
|
double a, b, c;
|
|
std::tie(a, b, c) = get_param(moved_line);
|
|
std::pair<Vec2d, Vec2d> points;
|
|
int count = Slic3r::Geometry::ray_circle_intersections(radius, a, b,
|
|
c, points);
|
|
if (count != 2) return {};
|
|
return Linef(points.first + center_d, points.second + center_d);
|
|
}
|
|
}
|
|
|
|
std::optional<Slic3r::Line> LineUtils::crop_half_ray(const Line & half_ray,
|
|
const Point ¢er,
|
|
double radius)
|
|
{
|
|
std::optional<Line> segment = crop_ray(half_ray, center, radius);
|
|
if (!segment.has_value()) return {};
|
|
Point dir = LineUtils::direction(half_ray);
|
|
using fnc = std::function<bool(const Point &)>;
|
|
fnc use_point_x = [&half_ray, &dir](const Point &p) -> bool {
|
|
return (p.x() > half_ray.a.x()) == (dir.x() > 0);
|
|
};
|
|
fnc use_point_y = [&half_ray, &dir](const Point &p) -> bool {
|
|
return (p.y() > half_ray.a.y()) == (dir.y() > 0);
|
|
};
|
|
bool use_x = PointUtils::is_majorit_x(dir);
|
|
fnc use_point = (use_x) ? use_point_x : use_point_y;
|
|
bool use_a = use_point(segment->a);
|
|
bool use_b = use_point(segment->b);
|
|
if (!use_a && !use_b) return {};
|
|
if (use_a && use_b) return segment;
|
|
return Line(half_ray.a, (use_a)?segment->a : segment->b);
|
|
}
|
|
|
|
std::optional<Slic3r::Linef> LineUtils::crop_half_ray(const Linef & half_ray,
|
|
const Point ¢er,
|
|
double radius)
|
|
{
|
|
std::optional<Linef> segment = crop_ray(half_ray, center, radius);
|
|
if (!segment.has_value()) return {};
|
|
Vec2d dir = half_ray.b - half_ray.a;
|
|
using fnc = std::function<bool(const Vec2d &)>;
|
|
fnc use_point_x = [&half_ray, &dir](const Vec2d &p) -> bool {
|
|
return (p.x() > half_ray.a.x()) == (dir.x() > 0);
|
|
};
|
|
fnc use_point_y = [&half_ray, &dir](const Vec2d &p) -> bool {
|
|
return (p.y() > half_ray.a.y()) == (dir.y() > 0);
|
|
};
|
|
bool use_x = PointUtils::is_majorit_x(dir);
|
|
fnc use_point = (use_x) ? use_point_x : use_point_y;
|
|
bool use_a = use_point(segment->a);
|
|
bool use_b = use_point(segment->b);
|
|
if (!use_a && !use_b) return {};
|
|
if (use_a && use_b) return segment;
|
|
return Linef(half_ray.a, (use_a) ? segment->a : segment->b);
|
|
}
|
|
|
|
std::optional<Slic3r::Line> LineUtils::crop_line(const Line & line,
|
|
const Point ¢er,
|
|
double radius)
|
|
{
|
|
std::optional<Line> segment = crop_ray(line, center, radius);
|
|
if (!segment.has_value()) return {};
|
|
|
|
Point dir = line.b - line.a;
|
|
using fnc = std::function<bool(const Point &)>;
|
|
fnc use_point_x = [&line, &dir](const Point &p) -> bool {
|
|
return (dir.x() > 0) ? (p.x() > line.a.x()) && (p.x() < line.b.x()) :
|
|
(p.x() < line.a.x()) && (p.x() > line.b.x());
|
|
};
|
|
fnc use_point_y = [&line, &dir](const Point &p) -> bool {
|
|
return (dir.y() > 0) ? (p.y() > line.a.y()) && (p.y() < line.b.y()) :
|
|
(p.y() < line.a.y()) && (p.y() > line.b.y());
|
|
};
|
|
bool use_x = PointUtils::is_majorit_x(dir);
|
|
fnc use_point = (use_x) ? use_point_x : use_point_y;
|
|
bool use_a = use_point(segment->a);
|
|
bool use_b = use_point(segment->b);
|
|
if (!use_a && !use_b) return {};
|
|
if (use_a && use_b) return segment;
|
|
bool same_dir = (use_x) ?
|
|
((dir.x() > 0) == ((segment->b.x() - segment->a.x()) > 0)) :
|
|
((dir.y() > 0) == ((segment->b.y() - segment->a.y()) > 0)) ;
|
|
if (use_a) {
|
|
if (same_dir)
|
|
return Line(segment->a, line.b);
|
|
else
|
|
return Line(line.a, segment->a);
|
|
} else { // use b
|
|
if (same_dir)
|
|
return Line(line.a, segment->b);
|
|
else
|
|
return Line(segment->b, line.b);
|
|
}
|
|
}
|
|
|
|
std::optional<Slic3r::Linef> LineUtils::crop_line(const Linef & line,
|
|
const Point ¢er,
|
|
double radius)
|
|
{
|
|
std::optional<Linef> segment = crop_ray(line, center, radius);
|
|
if (!segment.has_value()) return {};
|
|
|
|
Vec2d dir = line.b - line.a;
|
|
using fnc = std::function<bool(const Vec2d &)>;
|
|
fnc use_point_x = [&line, &dir](const Vec2d &p) -> bool {
|
|
return (dir.x() > 0) ? (p.x() > line.a.x()) && (p.x() < line.b.x()) :
|
|
(p.x() < line.a.x()) && (p.x() > line.b.x());
|
|
};
|
|
fnc use_point_y = [&line, &dir](const Vec2d &p) -> bool {
|
|
return (dir.y() > 0) ? (p.y() > line.a.y()) && (p.y() < line.b.y()) :
|
|
(p.y() < line.a.y()) && (p.y() > line.b.y());
|
|
};
|
|
bool use_x = PointUtils::is_majorit_x(dir);
|
|
fnc use_point = (use_x) ? use_point_x : use_point_y;
|
|
bool use_a = use_point(segment->a);
|
|
bool use_b = use_point(segment->b);
|
|
if (!use_a && !use_b) return {};
|
|
if (use_a && use_b) return segment;
|
|
bool same_dir = (use_x) ? ((dir.x() > 0) ==
|
|
((segment->b.x() - segment->a.x()) > 0)) :
|
|
((dir.y() > 0) ==
|
|
((segment->b.y() - segment->a.y()) > 0));
|
|
if (use_a) {
|
|
if (same_dir)
|
|
return Linef(segment->a, line.b);
|
|
else
|
|
return Linef(line.a, segment->a);
|
|
} else { // use b
|
|
if (same_dir)
|
|
return Linef(line.a, segment->b);
|
|
else
|
|
return Linef(segment->b, line.b);
|
|
}
|
|
}
|
|
|
|
|
|
std::tuple<double, double, double> LineUtils::get_param(const Line &line) {
|
|
Vector normal = line.normal();
|
|
double a = normal.x();
|
|
double b = normal.y();
|
|
double c = -a * line.a.x() - b * line.a.y();
|
|
return {a, b, c};
|
|
}
|
|
|
|
std::tuple<double, double, double> LineUtils::get_param(const Linef &line)
|
|
{
|
|
Vec2d direction = line.b - line.a;
|
|
Vec2d normal(-direction.y(), direction.x());
|
|
double a = normal.x();
|
|
double b = normal.y();
|
|
double c = -a * line.a.x() - b * line.a.y();
|
|
return {a, b, c};
|
|
}
|
|
|
|
void LineUtils::draw(SVG & svg,
|
|
const Line &line,
|
|
const char *color,
|
|
coordf_t stroke_width,
|
|
const char *name,
|
|
bool side_points,
|
|
const char *color_a,
|
|
const char *color_b)
|
|
{
|
|
svg.draw(line, color, stroke_width);
|
|
bool use_name = name != nullptr;
|
|
if (use_name) {
|
|
Point middle = line.a/2 + line.b/2;
|
|
svg.draw_text(middle, name, color);
|
|
}
|
|
if (side_points) {
|
|
std::string name_a = (use_name) ? "A" : (std::string("A_") + name);
|
|
std::string name_b = (use_name) ? "B" : (std::string("B_") + name);
|
|
svg.draw_text(line.a, name_a.c_str(), color_a);
|
|
svg.draw_text(line.b, name_b.c_str(), color_b);
|
|
}
|
|
}
|
|
|
|
double LineUtils::perp_distance(const Linef &line, Vec2d p)
|
|
{
|
|
Vec2d v = line.b - line.a; // direction
|
|
Vec2d va = p - line.a;
|
|
return std::abs(cross2(v, va)) / v.norm();
|
|
}
|
|
|
|
bool LineUtils::is_parallel(const Line &first, const Line &second)
|
|
{
|
|
Point dir1 = direction(first);
|
|
Point dir2 = direction(second);
|
|
coord_t cross(
|
|
static_cast<int64_t>(dir1.x()) * dir2.y() -
|
|
static_cast<int64_t>(dir2.x()) * dir1.y()
|
|
);
|
|
return (cross == 0);
|
|
}
|
|
|
|
std::optional<Slic3r::Vec2d> LineUtils::intersection(const Line &ray1, const Line &ray2)
|
|
{
|
|
const Vec2d v1 = direction(ray1).cast<double>();
|
|
const Vec2d v2 = direction(ray2).cast<double>();
|
|
double denom = cross2(v1, v2);
|
|
if (fabs(denom) < std::numeric_limits<float>::epsilon()) return {};
|
|
|
|
const Vec2d v12 = (ray1.a - ray2.a).cast<double>();
|
|
double nume = cross2(v2, v12);
|
|
double t = nume / denom;
|
|
return (ray1.a.cast<double>() + t * v1);
|
|
}
|
|
|
|
bool LineUtils::belongs(const Line &line, const Point &point, double benevolence)
|
|
{
|
|
const Point &a = line.a;
|
|
const Point &b = line.b;
|
|
auto is_in_interval = [](coord_t value, coord_t from, coord_t to) -> bool
|
|
{
|
|
if (from < to) {
|
|
// from < value < to
|
|
if (from > value || to < value) return false;
|
|
} else {
|
|
// to < value < from
|
|
if (from < value || to > value) return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (!is_in_interval(point.x(), a.x(), b.x()) ||
|
|
!is_in_interval(point.y(), a.y(), b.y()) )
|
|
{ // out of interval
|
|
return false;
|
|
}
|
|
double distance = line.perp_distance_to(point);
|
|
if (distance < benevolence) return true;
|
|
return false;
|
|
}
|
|
|
|
Slic3r::Point LineUtils::direction(const Line &line)
|
|
{
|
|
return line.b - line.a;
|
|
}
|
|
|
|
Slic3r::Point LineUtils::middle(const Line &line) {
|
|
// division before adding to prevent data type overflow
|
|
return line.a / 2 + line.b / 2;
|
|
}
|
|
|
|
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;
|
|
auto insert = [&](size_t line_index, size_t connected, bool connect_by_a){
|
|
auto item = line_connection.find(line_index);
|
|
if (item == line_connection.end()) {
|
|
// create new
|
|
line_connection[line_index] = (connect_by_a) ?
|
|
std::pair<size_t, size_t>(connected, bad_index) :
|
|
std::pair<size_t, size_t>(bad_index, connected);
|
|
} else {
|
|
std::pair<size_t, size_t> &pair = item->second;
|
|
size_t &ref_index = (connect_by_a) ? pair.first : pair.second;
|
|
assert(ref_index == bad_index);
|
|
ref_index = connected;
|
|
}
|
|
};
|
|
|
|
auto inserts = [&](size_t i1, size_t i2)->bool{
|
|
bool is_l1_a_connect = true; // false => l1_b_connect
|
|
const Slic3r::Line &l1 = lines[i1];
|
|
const Slic3r::Line &l2 = lines[i2];
|
|
if (!PointUtils::is_equal(l1.a, l2.b)) return false;
|
|
if (!PointUtils::is_equal(l1.b, l2.a)) return false;
|
|
else is_l1_a_connect = false;
|
|
insert(i1, i2, is_l1_a_connect);
|
|
insert(i2, i1, !is_l1_a_connect);
|
|
return true;
|
|
};
|
|
|
|
std::vector<size_t> not_finished;
|
|
size_t prev_index = lines.size() - 1;
|
|
for (size_t index = 0; index < lines.size(); ++index) {
|
|
if (!inserts(prev_index, index)) {
|
|
bool found_index = false;
|
|
bool found_prev_index = false;
|
|
not_finished.erase(std::remove_if(not_finished.begin(),
|
|
not_finished.end(),
|
|
[&](const size_t ¬_finished_index) {
|
|
if (!found_index && inserts(index, not_finished_index)) {
|
|
found_index = true;
|
|
return true;
|
|
}
|
|
if (!found_prev_index && inserts(prev_index, not_finished_index)) {
|
|
found_prev_index = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}),
|
|
not_finished.end());
|
|
if (!found_index) not_finished.push_back(index);
|
|
if (!found_prev_index) not_finished.push_back(prev_index);
|
|
}
|
|
prev_index = index;
|
|
}
|
|
assert(not_finished.empty());
|
|
return line_connection;
|
|
}
|
|
|
|
Slic3r::BoundingBox LineUtils::create_bounding_box(const Lines &lines) {
|
|
Points pts;
|
|
pts.reserve(lines.size()*2);
|
|
for (const Line &line : lines) {
|
|
pts.push_back(line.a);
|
|
pts.push_back(line.b);
|
|
}
|
|
return BoundingBox(pts);
|
|
}
|
|
|
|
std::map<size_t, size_t> LineUtils::create_line_connection_over_b(const Lines &lines)
|
|
{
|
|
std::map<size_t, size_t> line_connection;
|
|
auto inserts = [&](size_t i1, size_t i2) -> bool {
|
|
const Line &l1 = lines[i1];
|
|
const Line &l2 = lines[i2];
|
|
if (!PointUtils::is_equal(l1.b, l2.a))
|
|
return false;
|
|
assert(line_connection.find(i1) == line_connection.end());
|
|
line_connection[i1] = i2;
|
|
return true;
|
|
};
|
|
|
|
std::vector<size_t> not_finished_a;
|
|
std::vector<size_t> not_finished_b;
|
|
size_t prev_index = lines.size() - 1;
|
|
for (size_t index = 0; index < lines.size(); ++index) {
|
|
if (!inserts(prev_index, index)) {
|
|
bool found_b = false;
|
|
not_finished_b.erase(std::remove_if(not_finished_b.begin(), not_finished_b.end(),
|
|
[&](const size_t ¬_finished_index) {
|
|
if (!found_b && inserts(prev_index, not_finished_index)) {
|
|
found_b = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}),not_finished_b.end());
|
|
if (!found_b) not_finished_a.push_back(prev_index);
|
|
|
|
bool found_a = false;
|
|
not_finished_a.erase(std::remove_if(not_finished_a.begin(), not_finished_a.end(),
|
|
[&](const size_t ¬_finished_index) {
|
|
if (!found_a && inserts(not_finished_index, index)) {
|
|
found_a = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}),not_finished_a.end());
|
|
if (!found_a) not_finished_b.push_back(index);
|
|
}
|
|
prev_index = index;
|
|
}
|
|
assert(not_finished_a.empty());
|
|
assert(not_finished_b.empty());
|
|
return line_connection;
|
|
}
|
|
|
|
void LineUtils::draw(SVG & svg,
|
|
const Lines &lines,
|
|
const char * color,
|
|
coordf_t stroke_width,
|
|
bool ord,
|
|
bool side_points,
|
|
const char * color_a,
|
|
const char * color_b)
|
|
{
|
|
for (const auto &line : lines) {
|
|
draw(svg, line, color, stroke_width,
|
|
(ord) ? std::to_string(&line - &lines.front()).c_str() : nullptr,
|
|
side_points, color_a, color_b);
|
|
}
|
|
} |