add sample center circles

This commit is contained in:
Filip Sykala 2021-03-10 10:01:45 +01:00 committed by Lukas Matena
parent fb050136ef
commit dd61478fd6
9 changed files with 1004 additions and 203 deletions

View File

@ -2,26 +2,37 @@
#define slic3r_SLA_SuppotstIslands_SampleConfig_hpp_
namespace Slic3r::sla {
/// <summary>
/// Configuration fro sampling voronoi diagram for support point generator
/// Configuration DTO
/// Define where is neccessary to put support point on island
/// Mainly created by SampleConfigFactory
/// </summary>
struct SampleConfig
{
// Maximal distance from edge
double max_distance = 1.; // must be bigger than zero
// Maximal distance between samples on skeleton
double sample_size = 1.; // must be bigger than zero
// distance from edge of skeleton
double start_distance = 0; // support head diameter
// Every point on island has at least one support point in maximum distance
// MUST be bigger than zero
double max_distance = 1.;
// maximal length of longest path in voronoi diagram to be island
// supported only by one single support point this point will be in center
// of path.
// suggestion 1: Smaller than 2 * SampleConfig.start_distance
// suggestion 2: Bigger than 2 * Head diameter
// Support point head radius
// MUST be bigger than zero
double head_radius = 1;
// When it is possible, there will be this minimal distance from outline.
// zero when head should be on outline
double minimal_distance_from_outline = 0.;
// Maximal length of longest path in voronoi diagram to be island
// supported only by one single support point this point will be in center of path.
double max_length_for_one_support_point = 1.;
};
// Maximal length of island supported by 2 points
double max_length_for_two_support_points = 1.;
// Maximal width of line island supported in the middle of line
double max_width_for_center_supportr_line = 1.;
// Maximal width of line island supported by zig zag
double max_width_for_zig_zag_supportr_line = 1.;
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SampleConfig_hpp_

View File

@ -0,0 +1,73 @@
#ifndef slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_
#define slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_
#include <libslic3r/sla/SupportPointGenerator.hpp>
namespace Slic3r::sla {
/// <summary>
/// Factory to create configuration
/// </summary>
class SampleConfigFactory
{
public:
SampleConfigFactory() = delete;
// factory method to iniciate config
static SampleConfig create(const SupportPointGenerator::Config &config)
{
SampleConfig result;
result.max_distance = 100. * config.head_diameter;
result.head_radius = config.head_diameter / 2;
result.minimal_distance_from_outline = config.head_diameter / 2.;
result.max_length_for_one_support_point =
2 * result.minimal_distance_from_outline +
config.head_diameter;
double max_length_for_one_support_point =
2 * result.max_distance +
config.head_diameter +
2 * result.minimal_distance_from_outline;
if (result.max_length_for_one_support_point > max_length_for_one_support_point)
result.max_length_for_one_support_point = max_length_for_one_support_point;
double min_length_for_one_support_point =
2 * config.head_diameter +
2 * result.minimal_distance_from_outline;
if (result.max_length_for_one_support_point < min_length_for_one_support_point)
result.max_length_for_one_support_point = min_length_for_one_support_point;
result.max_length_for_two_support_points =
2 * result.max_distance + 2 * config.head_diameter +
2 * result.minimal_distance_from_outline;
double max_length_for_two_support_points =
2 * result.max_distance +
2 * config.head_diameter +
2 * result.minimal_distance_from_outline;
if (result.max_length_for_two_support_points > max_length_for_two_support_points)
result.max_length_for_two_support_points = max_length_for_two_support_points;
assert(result.max_length_for_two_support_points < result.max_length_for_one_support_point);
result.max_width_for_center_supportr_line = 2 * config.head_diameter;
double min_width_for_center_supportr_line =
config.head_diameter + 2 * result.minimal_distance_from_outline;
if (result.max_width_for_center_supportr_line < min_width_for_center_supportr_line)
result.max_width_for_center_supportr_line = min_width_for_center_supportr_line;
double max_width_for_center_supportr_line = 2 * result.max_distance + config.head_diameter;
if (result.max_width_for_center_supportr_line > max_width_for_center_supportr_line)
result.max_width_for_center_supportr_line = max_width_for_center_supportr_line;
result.max_width_for_zig_zag_supportr_line = sqrt(2*result.max_distance * result.max_distance);
double max_width_for_zig_zag_supportr_line =
2 * result.max_distance +
2 * config.head_diameter +
2 * result.minimal_distance_from_outline;
if (result.max_width_for_zig_zag_supportr_line > max_width_for_zig_zag_supportr_line)
result.max_width_for_zig_zag_supportr_line = max_width_for_zig_zag_supportr_line;
assert(result.max_width_for_zig_zag_supportr_line < result.max_width_for_center_supportr_line);
return result;
}
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SampleConfigFactory_hpp_

View File

@ -0,0 +1,393 @@
#include "SampleIslandUtils.hpp"
#include <cmath>
#include <libslic3r/VoronoiOffset.hpp>
#include "IStackFunction.hpp"
#include "EvaluateNeighbor.hpp"
#include "ParabolaUtils.hpp"
#include "VoronoiGraphUtils.hpp"
#include <magic_enum/magic_enum.hpp>
#include <libslic3r/VoronoiVisualUtils.hpp>
using namespace Slic3r::sla;
Slic3r::Point SampleIslandUtils::get_point_on_path(
const VoronoiGraph::Nodes &path, double distance)
{
const VoronoiGraph::Node *prev_node = nullptr;
double actual_distance = 0.;
for (const VoronoiGraph::Node *node : path) {
if (prev_node == nullptr) { // first call
prev_node = node;
continue;
}
const VoronoiGraph::Node::Neighbor *neighbor =
VoronoiGraphUtils::get_neighbor(prev_node, node);
actual_distance += neighbor->edge_length;
if (actual_distance >= distance) {
// over half point is on
double previous_distance = actual_distance - distance;
double over_ratio = previous_distance / neighbor->edge_length;
double ratio = 1. - over_ratio;
return VoronoiGraphUtils::get_edge_point(neighbor->edge, ratio);
}
prev_node = node;
}
// distance must be inside path
// this means bad input params
assert(false);
return Point(0, 0);
}
SupportIslandPoints SampleIslandUtils::create_side_points(
const VoronoiGraph::Nodes &path, double side_distance)
{
VoronoiGraph::Nodes reverse_path = path; // copy
std::reverse(reverse_path.begin(), reverse_path.end());
return {
{get_point_on_path(path, side_distance), SupportIslandPoint::Type::two_points},
{get_point_on_path(reverse_path, side_distance), SupportIslandPoint::Type::two_points}
};
}
SupportIslandPoints SampleIslandUtils::sample_side_branch(
const VoronoiGraph::Node * first_node,
const VoronoiGraph::Path side_path,
double start_offset,
const CenterLineConfiguration &cfg)
{
assert(cfg.max_sample_distance > start_offset);
double distance = cfg.max_sample_distance - start_offset;
double length = side_path.length - cfg.side_distance - distance;
if (length < 0.) {
VoronoiGraph::Nodes reverse_path = side_path.nodes;
std::reverse(reverse_path.begin(), reverse_path.end());
reverse_path.push_back(first_node);
return {get_point_on_path(reverse_path, cfg.side_distance)};
}
// count of segment between points on main path
size_t segment_count = static_cast<size_t>(
std::ceil(length / cfg.max_sample_distance));
double sample_distance = length / segment_count;
SupportIslandPoints result;
result.reserve(segment_count + 1);
const VoronoiGraph::Node *prev_node = first_node;
for (const VoronoiGraph::Node *node : side_path.nodes) {
const VoronoiGraph::Node::Neighbor *neighbor =
VoronoiGraphUtils::get_neighbor(prev_node, node);
auto side_item = cfg.branches_map.find(node);
if (side_item != cfg.branches_map.end()) {
double start_offset = (distance < sample_distance / 2.) ?
distance :
(sample_distance - distance);
if (side_item->second.top().length > cfg.min_length) {
auto side_samples = sample_side_branches(side_item,
start_offset, cfg);
result.insert(result.end(), side_samples.begin(),
side_samples.end());
}
}
while (distance < neighbor->edge_length) {
double edge_ratio = distance / neighbor->edge_length;
result.emplace_back(VoronoiGraphUtils::get_edge_point(neighbor->edge, edge_ratio),
SupportIslandPoint::Type::center_line);
distance += sample_distance;
}
distance -= neighbor->edge_length;
prev_node = node;
}
assert(fabs(distance - (sample_distance - cfg.side_distance)) < 1e-5);
return result;
}
SupportIslandPoints SampleIslandUtils::sample_side_branches(
const VoronoiGraph::ExPath::SideBranchesMap::const_iterator
& side_branches_iterator,
double start_offset,
const CenterLineConfiguration &cfg)
{
const VoronoiGraph::ExPath::SideBranches &side_branches =
side_branches_iterator->second;
const VoronoiGraph::Node *first_node = side_branches_iterator->first;
if (side_branches.size() == 1)
return sample_side_branch(first_node, side_branches.top(),
start_offset, cfg);
SupportIslandPoints result;
VoronoiGraph::ExPath::SideBranches side_branches_cpy = side_branches;
while (side_branches_cpy.top().length > cfg.min_length) {
auto samples = sample_side_branch(first_node, side_branches_cpy.top(),
start_offset, cfg);
result.insert(result.end(), samples.begin(), samples.end());
side_branches_cpy.pop();
}
return result;
}
std::vector<std::set<const VoronoiGraph::Node *>> create_circles_sets(
const std::vector<VoronoiGraph::Circle> & circles,
const VoronoiGraph::ExPath::ConnectedCircles &connected_circle)
{
std::vector<std::set<const VoronoiGraph::Node *>> result;
std::vector<bool> done_circle(circles.size(), false);
for (size_t circle_index = 0; circle_index < circles.size();
++circle_index) {
if (done_circle[circle_index]) continue;
done_circle[circle_index] = true;
std::set<const VoronoiGraph::Node *> circle_nodes;
const VoronoiGraph::Circle & circle = circles[circle_index];
for (const VoronoiGraph::Node *node : circle.nodes)
circle_nodes.insert(node);
circle_nodes.insert(circle.nodes.begin(), circle.nodes.end());
auto cc = connected_circle.find(circle_index);
if (cc != connected_circle.end()) {
for (const size_t &cc_index : cc->second) {
done_circle[cc_index] = true;
const VoronoiGraph::Circle &circle = circles[cc_index];
circle_nodes.insert(circle.nodes.begin(), circle.nodes.end());
}
}
result.push_back(circle_nodes);
}
return result;
}
SupportIslandPoints SampleIslandUtils::sample_center_line(
const VoronoiGraph::ExPath &path, const CenterLineConfiguration &cfg)
{
const VoronoiGraph::Nodes &nodes = path.nodes;
// like side branch separate first node from path
VoronoiGraph::Path main_path({nodes.begin() + 1, nodes.end()},
path.length);
double start_offset = cfg.max_sample_distance - cfg.side_distance;
SupportIslandPoints result = sample_side_branch(nodes.front(), main_path,
start_offset, cfg);
if (path.circles.empty()) return result;
SupportIslandPoints result_circles = sample_center_circles(path, cfg);
result.insert(result.end(), result_circles.begin(), result_circles.end());
return result;
}
SupportIslandPoints SampleIslandUtils::sample_center_circle(
const std::set<const VoronoiGraph::Node *>& circle_set,
const VoronoiGraph::Nodes& path_nodes,
const CenterLineConfiguration & cfg
)
{
SupportIslandPoints result;
// DTO store information about distance to nearest support point
// and path from start point
struct NodeDistance
{
VoronoiGraph::Nodes nodes; // from act node to start
double distance_from_support_point;
NodeDistance(const VoronoiGraph::Node *node,
double distance_from_support_point)
: nodes({node})
, distance_from_support_point(distance_from_support_point)
{}
};
// depth search
std::stack<NodeDistance> process;
// TODO: propagate distance from support point
// distance from nearest support point
double path_distance = cfg.max_sample_distance / 2;
// path can't be stored in done because it will skip begining of path
std::map<const VoronoiGraph::Node *, double> path_distances;
// path_nodes are already sampled
for (const VoronoiGraph::Node *node : path_nodes) {
// contain
if (circle_set.find(node) != circle_set.end()) {
process.push(NodeDistance(node, path_distance));
path_distances[node] = path_distance;
}
}
// TODO: sample circles out of main path
if (process.empty()) { // TODO: find side branch
}
// when node is sampled in all side branches.
// Value is distance to nearest support point
std::map<const VoronoiGraph::Node *, double> done_distance;
while (!process.empty()) {
NodeDistance nd = process.top(); // copy
process.pop();
const VoronoiGraph::Node *node = nd.nodes.front();
const VoronoiGraph::Node *prev_node =
(nd.nodes.size() > 1) ? nd.nodes[1] : nullptr;
auto done_distance_item = done_distance.find(node);
if (done_distance_item != done_distance.end()) {
if (done_distance_item->second > nd.distance_from_support_point)
done_distance_item->second = nd.distance_from_support_point;
continue;
}
done_distance[node] = nd.distance_from_support_point;
bool is_node_on_path = (path_distances.find(node) != path_distances.end());
double &node_distance = done_distance[node]; // append to done node
for (const auto &neighbor : node->neighbors) {
if (neighbor.node == prev_node) continue;
if (circle_set.find(neighbor.node) == circle_set.end()) continue; // out of circle points
auto path_distance_item = path_distances.find(neighbor.node);
bool is_neighbor_on_path = (path_distance_item != path_distances.end());
if (is_node_on_path && is_neighbor_on_path) continue; // already sampled
auto done_item = done_distance.find(neighbor.node);
bool is_done = done_item != done_distance.end();
if (is_done || is_neighbor_on_path) {
double &done_distance = (is_done)? done_item->second : path_distance_item->second;
double distance = done_distance +
nd.distance_from_support_point +
neighbor.edge_length;
if (distance < cfg.max_sample_distance) continue;
size_t count_supports = static_cast<size_t>(
std::floor(distance / cfg.max_sample_distance));
// distance between support points
double distance_between = distance / (count_supports + 1);
if (distance_between < done_distance) {
// point is calculated to be in done path, SP will be on edge point
result.emplace_back(
VoronoiGraphUtils::get_edge_point(neighbor.edge, 1.),
SupportIslandPoint::Type::center_circle_end);
if (node_distance > neighbor.edge_length)
node_distance = neighbor.edge_length;
done_distance = 0.;
if (count_supports == 1) continue;
count_supports -= 1;
distance -= done_distance;
distance_between = distance / (count_supports + 1);
}
VoronoiGraph::Nodes nodes = nd.nodes; // copy, could be more neighbor
nodes.insert(nodes.begin(), neighbor.node);
for (int i = 1; i <= count_supports; ++i) {
double distance_from_neighbor =
i * (distance_between) -done_distance;
result.emplace_back(
get_point_on_path(nodes, distance_from_neighbor),
SupportIslandPoint::Type::center_circle_end2);
double distance_to_node = neighbor.edge_length -
distance_from_neighbor;
if (distance_to_node > 0. &&
node_distance > distance_to_node)
node_distance = distance_to_node;
}
continue;
}
NodeDistance next_nd = nd; // copy
next_nd.nodes.insert(next_nd.nodes.begin(), neighbor.node);
next_nd.distance_from_support_point += neighbor.edge_length;
// exist place for sample:
while (next_nd.distance_from_support_point >
cfg.max_sample_distance) {
double distance_from_node = next_nd
.distance_from_support_point -
nd.distance_from_support_point;
double ratio = distance_from_node / neighbor.edge_length;
result.emplace_back(VoronoiGraphUtils::get_edge_point(neighbor.edge, ratio),
SupportIslandPoint::Type::center_circle);
next_nd.distance_from_support_point -= cfg.max_sample_distance;
}
process.push(next_nd);
}
}
return result;
}
SupportIslandPoints SampleIslandUtils::sample_center_circles(
const VoronoiGraph::ExPath &path, const CenterLineConfiguration &cfg)
{
// vector of connected circle points
// for detection path from circle
std::vector<std::set<const VoronoiGraph::Node *>> circles_sets =
create_circles_sets(path.circles, path.connected_circle);
if (circles_sets.size() == 1)
return sample_center_circle(circles_sets.front(), path.nodes, cfg);
SupportIslandPoints result;
for (const auto &circle_set : circles_sets) {
SupportIslandPoints circle_result = sample_center_circle(circle_set, path.nodes, cfg);
result.insert(result.end(), circle_result.begin(), circle_result.end());
}
return result;
}
SupportIslandPoints SampleIslandUtils::sample_expath(
const VoronoiGraph::ExPath &path, const SampleConfig &config)
{
// 1) One support point
if (path.length < config.max_length_for_one_support_point) {
// create only one point in center
Point p = get_center_of_path(path.nodes, path.length);
SupportIslandPoint supportIslandPoint(p, SupportIslandPoint::Type::one_center_point);
return {supportIslandPoint};
}
double max_width = VoronoiGraphUtils::get_max_width(path);
if (max_width < config.max_width_for_center_supportr_line) {
// 2) Two support points
if (path.length < config.max_length_for_two_support_points)
return create_side_points(path.nodes,
config.minimal_distance_from_outline);
// othewise sample path
CenterLineConfiguration
centerLineConfiguration(path.side_branches,
2 * config.minimal_distance_from_outline,
config.max_distance,
config.minimal_distance_from_outline);
return sample_center_line(path, centerLineConfiguration);
}
// line of zig zag points
if (max_width < config.max_width_for_zig_zag_supportr_line) {
// return create_zig_zag_points();
}
// TODO: 3) Triangle of points
// eval outline and find three point create almost equilateral triangle
// TODO: divide path to sampled parts
SupportIslandPoints points;
points.push_back(VoronoiGraphUtils::get_offseted_point(
*path.nodes.front(), config.minimal_distance_from_outline));
return points;
}
SupportIslandPoints SampleIslandUtils::sample_voronoi_graph(
const VoronoiGraph & graph,
const SampleConfig & config,
VoronoiGraph::ExPath &longest_path)
{
const VoronoiGraph::Node *start_node =
VoronoiGraphUtils::getFirstContourNode(graph);
// every island has to have a point on contour
assert(start_node != nullptr);
longest_path = VoronoiGraphUtils::create_longest_path(start_node);
// longest_path = create_longest_path_recursive(start_node);
return sample_expath(longest_path, config);
}
void SampleIslandUtils::draw(SVG & svg,
const SupportIslandPoints &supportIslandPoints,
double size,
const char * color,
bool write_type)
{
for (const auto &p : supportIslandPoints) {
svg.draw(p.point, color, size);
if (write_type && p.type != SupportIslandPoint::Type::undefined) {
auto type_name = magic_enum::enum_name(p.type);
Point start = p.point + Point(size, 0.);
svg.draw_text(start, std::string(type_name).c_str(), color);
}
}
}

View File

@ -0,0 +1,119 @@
#ifndef slic3r_SLA_SuppotstIslands_SampleIslandUtils_hpp_
#define slic3r_SLA_SuppotstIslands_SampleIslandUtils_hpp_
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/SVG.hpp>
#include "VoronoiGraph.hpp"
#include "Parabola.hpp"
#include "SampleConfig.hpp"
#include "SupportIslandPoint.hpp"
namespace Slic3r::sla {
/// <summary>
/// Utils class with only static function
/// Function for sampling island by Voronoi Graph.
/// </summary>
class SampleIslandUtils
{
public:
SampleIslandUtils() = delete;
/// <summary>
/// Find point lay on path with distance from first point on path
/// </summary>
/// <param name="path">Neighbor connected Nodes</param>
/// <param name="distance">Distance to final point</param>
/// <returns>Points with distance to first node</returns>
static Point get_point_on_path(const VoronoiGraph::Nodes &path,
double distance);
/// <summary>
/// Find point lay in center of path
/// Distance from this point to front of path
/// is same as distance to back of path
/// </summary>
/// <param name="path">Queue of neighbor nodes.(must be neighbor)</param>
/// <param name="path_length">length of path</param>
/// <returns>Point laying on voronoi diagram</returns>
static Point get_center_of_path(const VoronoiGraph::Nodes &path,
double path_length)
{
return get_point_on_path(path, path_length / 2);
}
// create 2 points on both ends of path with side distance from border
static SupportIslandPoints create_side_points(const VoronoiGraph::Nodes &path, double side_distance);
// DTO with data for sampling line in center
struct CenterLineConfiguration
{
const VoronoiGraph::ExPath::SideBranchesMap &branches_map;
double min_length;
double max_sample_distance;
double side_distance;
CenterLineConfiguration(
const VoronoiGraph::ExPath::SideBranchesMap &branches_map,
double min_length,
double max_sample_distance,
double side_distance)
: branches_map(branches_map)
, min_length(min_length)
, max_sample_distance(max_sample_distance)
, side_distance(side_distance)
{}
};
// create points along path and its side branches(recursively)
static SupportIslandPoints sample_side_branch(
const VoronoiGraph::Node * first_node,
const VoronoiGraph::Path side_path,
double start_offset,
const CenterLineConfiguration &cfg);
static SupportIslandPoints sample_side_branches(
const VoronoiGraph::ExPath::SideBranchesMap::const_iterator& side_branches_iterator,
double start_offset,
const CenterLineConfiguration &cfg);
// create points along path and its side branches(recursively) + colve circles
static SupportIslandPoints sample_center_line(const VoronoiGraph::ExPath &path, const CenterLineConfiguration &cfg);
// create point along multi circles in path
static SupportIslandPoints sample_center_circles(const VoronoiGraph::ExPath &path, const CenterLineConfiguration &cfg);
static SupportIslandPoints sample_center_circle(
const std::set<const VoronoiGraph::Node *> &circle_set,
const VoronoiGraph::Nodes & path_nodes,
const CenterLineConfiguration & cfg);
/// <summary>
/// Decide how to sample path
/// </summary>
/// <param name="path">Path inside voronoi diagram with side branches and circles</param>
/// <param name="config">Definition how to sample</param>
/// <returns>Support points for island</returns>
static SupportIslandPoints sample_expath(
const VoronoiGraph::ExPath &path,
const SampleConfig &config
);
/// <summary>
/// Sample voronoi skeleton
/// </summary>
/// <param name="graph">Inside skeleton of island</param>
/// <param name="config">Params for sampling</param>
/// <param name="longest_path">OUTPUT: longest path in graph</param>
/// <returns>Vector of sampled points or Empty when distance from edge is
/// bigger than max_distance</returns>
static SupportIslandPoints sample_voronoi_graph(
const VoronoiGraph & graph,
const SampleConfig & config,
VoronoiGraph::ExPath &longest_path);
static void draw(SVG & svg,
const SupportIslandPoints &supportIslandPoints,
double size,
const char *color = "lightgreen",
bool write_type = true);
};
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SampleIslandUtils_hpp_

View File

@ -0,0 +1,34 @@
#ifndef slic3r_SLA_SuppotstIslands_SupportIslandPoint_hpp_
#define slic3r_SLA_SuppotstIslands_SupportIslandPoint_hpp_
#include <libslic3r/Point.hpp>
namespace Slic3r::sla {
/// <summary>
/// DTO position with information about source of support point
/// </summary>
struct SupportIslandPoint
{
enum class Type : unsigned char {
one_center_point,
two_points,
center_line,
center_circle,
center_circle_end,
center_circle_end2,
undefined
};
Slic3r::Point point; // 2 point in layer coordinate
Type type;
SupportIslandPoint(Slic3r::Point point, Type type = Type::undefined)
: point(std::move(point)), type(type)
{}
};
using SupportIslandPoints = std::vector<SupportIslandPoint>;
} // namespace Slic3r::sla
#endif // slic3r_SLA_SuppotstIslands_SupportIslandPoint_hpp_

View File

@ -62,8 +62,11 @@ struct VoronoiGraph::Node::Neighbor
double max_width;
public:
Neighbor(const VD::edge_type *edge, const Node *node, double edge_length)
: edge(edge), node(node), edge_length(edge_length)
Neighbor(const VD::edge_type *edge, const Node *node, double edge_length, double max_width)
: edge(edge)
, node(node)
, edge_length(edge_length)
, max_width(max_width)
{}
};

View File

@ -113,8 +113,24 @@ double VoronoiGraphUtils::calculate_max_width(
Point v1{edge.vertex1()->x(), edge.vertex1()->y()};
if (edge.is_linear()) {
// line is initialized by 2 line segments only
assert(!edge.cell()->contains_point());
// edge line could be initialized by 2 points
if (edge.cell()->contains_point()) {
const Line &source_line = lines[edge.cell()->source_index()];
Point source_point;
if (edge.cell()->source_category() ==
boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT)
source_point = source_line.a;
else {
assert(edge.cell()->source_category() ==
boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT);
source_point = source_line.b;
}
Point vec0 = source_point - v0;
Point vec1 = source_point - v1;
double distance0 = sqrt(vec0.x() * vec0.x() + vec0.y() * vec0.y());
double distance1 = sqrt(vec0.x() * vec0.x() + vec0.y() * vec0.y());
return 2 * std::max(distance0, distance1);
}
assert(edge.cell()->contains_segment());
assert(!edge.twin()->cell()->contains_point());
assert(edge.twin()->cell()->contains_segment());
@ -175,10 +191,11 @@ VoronoiGraph VoronoiGraphUtils::getSkeleton(const VD &vd, const Lines &lines)
VoronoiGraph::Node *node0 = getNode(skeleton, v0, &edge, lines);
VoronoiGraph::Node *node1 = getNode(skeleton, v1, &edge, lines);
// TODO: Do not store twice length and max_width.
// add extended Edge to graph, both side
VoronoiGraph::Node::Neighbor neighbor0(&edge, node1, length);
VoronoiGraph::Node::Neighbor neighbor0(&edge, node1, length, max_width);
node0->neighbors.push_back(neighbor0);
VoronoiGraph::Node::Neighbor neighbor1(edge.twin(), node0, length);
VoronoiGraph::Node::Neighbor neighbor1(edge.twin(), node0, length, max_width);
node1->neighbors.push_back(neighbor1);
}
return skeleton;
@ -514,7 +531,8 @@ VoronoiGraph::ExPath VoronoiGraphUtils::create_longest_path(
return longest_path;
}
Slic3r::Point VoronoiGraphUtils::get_edge_point(const VD::edge_type *edge, double ratio)
Slic3r::Point VoronoiGraphUtils::get_edge_point(const VD::edge_type *edge,
double ratio)
{
const VD::vertex_type *v0 = edge->vertex0();
const VD::vertex_type *v1 = edge->vertex1();
@ -524,9 +542,7 @@ Slic3r::Point VoronoiGraphUtils::get_edge_point(const VD::edge_type *edge, doubl
return Point(v1->x(), v1->y());
if (edge->is_linear()) {
Point dir(
v1->x() - v0->x(),
v1->y() - v0->y());
Point dir(v1->x() - v0->x(), v1->y() - v0->y());
// normalize
dir *= ratio;
return Point(v0->x() + dir.x(), v0->y() + dir.y());
@ -541,71 +557,83 @@ Slic3r::Point VoronoiGraphUtils::get_edge_point(const VD::edge_type *edge, doubl
return Point(v0->x() + dir.x(), v0->y() + dir.y());
}
Slic3r::Point VoronoiGraphUtils::get_point_on_path(const VoronoiGraph::Nodes &path, double distance)
const VoronoiGraph::Node *VoronoiGraphUtils::getFirstContourNode(
const VoronoiGraph &graph)
{
const VoronoiGraph::Node *prev_node = nullptr;
double actual_distance = 0.;
for (const VoronoiGraph::Node *node : path) {
if (prev_node == nullptr) { // first call
prev_node = node;
continue;
}
const VoronoiGraph::Node::Neighbor *neighbor = get_neighbor(prev_node, node);
actual_distance += neighbor->edge_length;
if (actual_distance >= distance) {
// over half point is on
double previous_distance = actual_distance - distance;
double over_ratio = previous_distance / neighbor->edge_length;
double ratio = 1. - over_ratio;
return get_edge_point(neighbor->edge, ratio);
}
prev_node = node;
}
// distance must be inside path
// this means bad input params
assert(false);
return Point(0, 0);
}
std::vector<Slic3r::Point> VoronoiGraphUtils::sample_longest_path(
const VoronoiGraph::ExPath &longest_path, const SampleConfig &config)
{
// 1) One support point
if (longest_path.length <
config.max_length_for_one_support_point) { // create only one
// point in center
// sample in center of voronoi
return {get_center_of_path(longest_path.nodes, longest_path.length)};
}
// 2) Two support points
//if (longest_path.length < config.max_distance) {}
std::vector<Point> points;
points.push_back(get_offseted_point(*longest_path.nodes.front(), config.start_distance));
return points;
}
std::vector<Slic3r::Point> VoronoiGraphUtils::sample_voronoi_graph(
const VoronoiGraph & graph,
const SampleConfig & config,
VoronoiGraph::ExPath &longest_path)
{
// first vertex on contour:
const VoronoiGraph::Node *start_node = nullptr;
for (const auto &[key, value] : graph.data) {
const VD::vertex_type & vertex = *key;
Voronoi::VertexCategory category = Voronoi::vertex_category(vertex);
if (category == Voronoi::VertexCategory::OnContour) {
start_node = &value;
break;
return &value;
}
}
// every island has to have a point on contour
assert(start_node != nullptr);
longest_path = create_longest_path(start_node);
// longest_path = create_longest_path_recursive(start_node);
return sample_longest_path(longest_path, config);
return nullptr;
}
double VoronoiGraphUtils::get_max_width(const VoronoiGraph::Nodes &path)
{
double max = 0.;
const VoronoiGraph::Node *prev_node = nullptr;
for (const VoronoiGraph::Node *node : path) {
if (prev_node == nullptr) {
prev_node = node;
continue;
}
const VoronoiGraph::Node::Neighbor *neighbor = get_neighbor(prev_node, node);
if (max < neighbor->max_width) max = neighbor->max_width;
prev_node = node;
}
return max;
}
double VoronoiGraphUtils::get_max_width(
const VoronoiGraph::ExPath &longest_path)
{
double max = get_max_width(longest_path.nodes);
for (const auto &side_branches_item : longest_path.side_branches) {
const VoronoiGraph::Node *prev_node = side_branches_item.first;
VoronoiGraph::ExPath::SideBranches side_branches = side_branches_item.second; // !!! copy
while (!side_branches.empty()) {
const VoronoiGraph::Path &side_path = side_branches.top();
const VoronoiGraph::Node::Neighbor *first_neighbor =
get_neighbor(prev_node, side_path.nodes.front());
double max_side_branch = std::max(
get_max_width(side_path.nodes), first_neighbor->max_width);
if (max < max_side_branch) max = max_side_branch;
side_branches.pop();
}
}
for (const VoronoiGraph::Circle &circle : longest_path.circles) {
const VoronoiGraph::Node::Neighbor *first_neighbor =
get_neighbor(circle.nodes.front(), circle.nodes.back());
double max_circle = std::max(
first_neighbor->max_width, get_max_width(circle.nodes));
if (max < max_circle) max = max_circle;
}
return max;
}
// !!! is slower than go along path
double VoronoiGraphUtils::get_max_width(const VoronoiGraph::Node *node)
{
double max = 0.;
std::set<const VoronoiGraph::Node *> done;
std::queue<const VoronoiGraph::Node *> process;
process.push(node);
while (!process.empty()) {
const VoronoiGraph::Node *actual_node = process.front();
process.pop();
if (done.find(actual_node) != done.end()) continue;
for (const VoronoiGraph::Node::Neighbor& neighbor: actual_node->neighbors) {
if (done.find(neighbor.node) != done.end()) continue;
process.push(neighbor.node);
if (max < neighbor.max_width) max = neighbor.max_width;
}
done.insert(actual_node);
}
return max;
}
void VoronoiGraphUtils::draw(SVG & svg,

View File

@ -11,6 +11,7 @@
#include "VoronoiGraph.hpp"
#include "Parabola.hpp"
#include "SampleConfig.hpp"
#include "SupportIslandPoint.hpp"
namespace Slic3r::sla {
@ -190,48 +191,17 @@ public:
/// </summary>
static Point get_edge_point(const VD::edge_type *edge, double ratio);
/// <summary>
/// Find point lay on path with distance from first point on path
/// </summary>
/// <param name="path">Neighbor connected Nodes</param>
/// <param name="distance">Distance to final point</param>
/// <returns>Points with distance to first node</returns>
static Point get_point_on_path(const VoronoiGraph::Nodes &path, double distance);
static const VoronoiGraph::Node *getFirstContourNode(
const VoronoiGraph &graph);
/// <summary>
/// Find point lay in center of path
/// Distance from this point to front of path
/// is same as distance to back of path
/// Get max width from edge in voronoi graph
/// </summary>
/// <param name="path">Queue of neighbor nodes.(must be neighbor)</param>
/// <param name="path_length">length of path</param>
/// <returns>Point laying on voronoi diagram</returns>
static Point get_center_of_path(const VoronoiGraph::Nodes &path, double path_length)
{ return get_point_on_path(path, path_length / 2); }
/// <summary>
/// decide how to sample longest path
/// </summary>
/// <param name="longest_path">Path inside voronoi diagram with all side branches and circles</param>
/// <param name="config">Definition how to sample</param>
/// <returns>Support points for island</returns>
static std::vector<Point> sample_longest_path(
const VoronoiGraph::ExPath &longest_path,
const SampleConfig &config
);
/// <summary>
/// Sample voronoi skeleton
/// </summary>
/// <param name="graph">Inside skeleton of island</param>
/// <param name="config">Params for sampling</param>
/// <param name="longest_path">OUTPUT: longest path in graph</param>
/// <returns>Vector of sampled points or Empty when distance from edge is
/// bigger than max_distance</returns>
static std::vector<Point> sample_voronoi_graph(
const VoronoiGraph & graph,
const SampleConfig & config,
VoronoiGraph::ExPath &longest_path);
/// <param name="longest_path">Input point to voronoi graph</param>
/// <returns>Maximal widht in graph</returns>
static double get_max_width(const VoronoiGraph::ExPath &longest_path);
static double get_max_width(const VoronoiGraph::Nodes &path);
static double get_max_width(const VoronoiGraph::Node *node);
public: // draw function for debug
static void draw(SVG &svg, const VoronoiGraph &graph, coord_t width);

View File

@ -4,13 +4,16 @@
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/BoundingBox.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/SLA/SupportIslands/SampleConfig.hpp>
#include <libslic3r/SLA/SupportIslands/VoronoiGraphUtils.hpp>
#include <libslic3r/SLA/SupportIslands/SampleIslandUtils.hpp>
#include "sla_test_utils.hpp"
namespace Slic3r { namespace sla {
using namespace Slic3r;
using namespace Slic3r::sla;
TEST_CASE("Overhanging point should be supported", "[SupGen]") {
@ -135,32 +138,105 @@ TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]")
REQUIRE(!pts.empty());
}
// all triangle side are same length
Slic3r::Polygon equilateral_triangle(double size)
{
return {{.0, .0},
{size, .0},
{size / 2., sqrt(size * size - size * size / 4)}};
}
// two side of triangle are same size
Slic3r::Polygon isosceles_triangle(double side, double height)
{
return {{-side / 2, 0.}, {side / 2, 0.}, {.0, height}};
}
Slic3r::Polygon square(double size)
{
double size_2 = size / 2;
return {{-size_2, size_2},
{-size_2, -size_2},
{size_2, -size_2},
{size_2, size_2}};
}
Slic3r::Polygon rect(double x, double y){
return {{.0, y}, {.0, .0}, {x, .0}, {x, y}};
}
Slic3r::Polygon circle(double radius, size_t count_line_segments) {
// CCW: couter clock wise, CW: clock wise
Points circle;
circle.reserve(count_line_segments);
for (size_t i = 0; i < count_line_segments; ++i) {
double alpha = (2 * M_PI * i) / count_line_segments;
double sina = sin(alpha);
double cosa = cos(alpha);
circle.emplace_back(-radius * sina, radius * cosa);
}
return Slic3r::Polygon(circle);
}
Slic3r::Polygon create_cross_roads(double size, double width)
{
auto r1 = rect( 5.3 * size, width);
r1.rotate(3.14/4);
auto r2 = rect(6.1*size, 3/4.*width);
r2.rotate(-3.14 / 5);
auto r3 = rect(7.9*size, 4/5.*width);
r3.translate(Point(-2*size, size));
auto r4 = rect(5 / 6. * width, 5.7 * size);
r4.translate(Point(size,0.));
Polygons rr = union_(Polygons({r1, r2, r3, r4}));
return rr.front();
}
ExPolygon create_trinagle_with_hole(double size)
{
return ExPolygon(equilateral_triangle(size), {{size / 4, size / 4},
{size / 2, size / 2},
{size / 2, size / 4}});
}
ExPolygon create_square_with_hole(double size, double hole_size)
{
assert(sqrt(hole_size *hole_size / 2) < size);
auto hole = square(hole_size);
hole.rotate(M_PI / 4.); // 45
hole.reverse();
return ExPolygon(square(size), hole);
}
ExPolygon create_square_with_4holes(double size, double hole_size) {
auto hole = square(hole_size);
hole.reverse();
double size_4 = size / 4;
auto h1 = hole;
h1.translate(size_4, size_4);
auto h2 = hole;
h2.translate(-size_4, size_4);
auto h3 = hole;
h3.translate(size_4, -size_4);
auto h4 = hole;
h4.translate(-size_4, -size_4);
ExPolygon result(square(size));
result.holes = Polygons({h1, h2, h3, h4});
return result;
}
// boudary of circle
ExPolygon create_disc(double radius, double width, size_t count_line_segments)
{
double width_2 = width / 2;
auto hole = circle(radius-width_2, count_line_segments);
hole.reverse();
return ExPolygon(circle(radius + width_2, count_line_segments), hole);
}
ExPolygons createTestIslands(double size)
{
ExPolygon triangle(
Polygon{{.0, .0},
{size, .0},
{size / 2., sqrt(size * size - size * size / 4)}});
ExPolygon sharp_triangle(
Polygon{{.0, size / 2}, {.0, .0}, {2 * size, .0}});
ExPolygon triangle_with_hole({{.0, .0},
{size, .0},
{size / 2.,
sqrt(size * size - size * size / 4)}},
{{size / 4, size / 4},
{size / 2, size / 2},
{size / 2, size / 4}});
ExPolygon square(Polygon{{.0, size}, {.0, .0}, {size, .0}, {size, size}});
ExPolygon rect(
Polygon{{.0, size}, {.0, .0}, {2 * size, .0}, {2 * size, size}});
ExPolygon rect_with_hole({{-size, size}, // rect CounterClockWise
{-size, -size},
{size, -size},
{size, size}},
{{0., size / 2}, // inside rect ClockWise
{size / 2, 0.},
{0., -size / 2},
{-size / 2, 0.}});
bool useFrogLeg = false;
// need post reorganization of longest path
ExPolygon mountains({{0., 0.},
{size, 0.},
@ -169,28 +245,6 @@ ExPolygons createTestIslands(double size)
{3 * size / 7, 2 * size},
{2 * size / 7, size / 6},
{size / 7, size}});
ExPolygon rect_with_4_hole(Polygon{{0., size}, // rect CounterClockWise
{0., 0.},
{size, 0.},
{size, size}});
// inside rects ClockWise
double size5 = size / 5.;
rect_with_4_hole.holes = Polygons{{{size5, 4 * size5},
{2 * size5, 4 * size5},
{2 * size5, 3 * size5},
{size5, 3 * size5}},
{{3 * size5, 4 * size5},
{4 * size5, 4 * size5},
{4 * size5, 3 * size5},
{3 * size5, 3 * size5}},
{{size5, 2 * size5},
{2 * size5, 2 * size5},
{2 * size5, size5},
{size5, size5}},
{{3 * size5, 2 * size5},
{4 * size5, 2 * size5},
{4 * size5, size5},
{3 * size5, size5}}};
size_t count_cirlce_lines = 16; // test stack overfrow
double r_CCW = size / 2;
@ -207,36 +261,159 @@ ExPolygons createTestIslands(double size)
circle_CW.emplace_back(r_CW * sina, r_CW * cosa);
}
ExPolygon double_circle(circle_CCW, circle_CW);
ExPolygons result = {
// one support point
ExPolygon(equilateral_triangle(size)),
ExPolygon(square(size)),
ExPolygon(rect(size / 2, size)),
ExPolygon(isosceles_triangle(size / 2, 3 * size / 2)), // small sharp triangle
ExPolygon(circle(size/2, 10)),
create_square_with_4holes(size, size / 4),
create_disc(size/4, size / 4, 10),
TriangleMesh mesh = load_model("frog_legs.obj");
TriangleMeshSlicer slicer{&mesh};
std::vector<float> grid({0.1f});
std::vector<ExPolygons> slices;
slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {});
ExPolygon frog_leg = slices.front()[1]; //
// two support points
ExPolygon(isosceles_triangle(size / 2, 3 * size)), // small sharp triangle
ExPolygon(rect(size / 2, 3 * size)),
return {
triangle, square,
sharp_triangle, rect,
rect_with_hole, triangle_with_hole,
rect_with_4_hole, mountains,
double_circle
//, frog_leg
// tiny line support points
ExPolygon(rect(size / 2, 10 * size)), // long line
ExPolygon(create_cross_roads(size, size / 3)),
create_disc(3*size, size / 4, 30),
create_square_with_4holes(5 * size, 5 * size / 2 - size / 3),
// still problem
// three support points
ExPolygon(equilateral_triangle(3 * size)),
ExPolygon(circle(size, 20)),
mountains,
create_trinagle_with_hole(size),
create_square_with_hole(size, size / 2),
create_square_with_hole(size, size / 3)
};
if (useFrogLeg) {
TriangleMesh mesh = load_model("frog_legs.obj");
TriangleMeshSlicer slicer{&mesh};
std::vector<float> grid({0.1f});
std::vector<ExPolygons> slices;
slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {});
ExPolygon frog_leg = slices.front()[1];
result.push_back(frog_leg);
}
return result;
}
std::vector<Point> test_island_sampling(const ExPolygon & island,
Points createNet(const BoundingBox& bounding_box, double distance)
{
Point size = bounding_box.size();
double distance_2 = distance / 2;
int cols1 = static_cast<int>(floor(size.x() / distance))+1;
int cols2 = static_cast<int>(floor((size.x() - distance_2) / distance))+1;
// equilateral triangle height with side distance
double h = sqrt(distance * distance - distance_2 * distance_2);
int rows = static_cast<int>(floor(size.y() / h)) +1;
int rows_2 = rows / 2;
size_t count_points = rows_2 * (cols1 + static_cast<size_t>(cols2));
if (rows % 2 == 1) count_points += cols2;
Points result;
result.reserve(count_points);
bool isOdd = true;
Point offset = bounding_box.min;
double x_max = offset.x() + static_cast<double>(size.x());
double y_max = offset.y() + static_cast<double>(size.y());
for (double y = offset.y(); y <= y_max; y += h) {
double x_offset = offset.x();
if (isOdd) x_offset += distance_2;
isOdd = !isOdd;
for (double x = x_offset; x <= x_max; x += distance) {
result.emplace_back(x, y);
}
}
assert(result.size() == count_points);
return result;
}
// create uniform triangle net and return points laying inside island
Points rasterize(const ExPolygon &island, double distance) {
BoundingBox bb;
for (const Point &pt : island.contour.points) bb.merge(pt);
Points fullNet = createNet(bb, distance);
Points result;
result.reserve(fullNet.size());
std::copy_if(fullNet.begin(), fullNet.end(), std::back_inserter(result),
[&island](const Point &p) { return island.contains(p); });
return result;
}
SupportIslandPoints test_island_sampling(const ExPolygon & island,
const SampleConfig &config)
{
auto points = SupportPointGenerator::uniform_cover_island(island, config);
Points chck_points = rasterize(island, config.head_radius); // TODO: Use resolution of printer
bool is_ok = true;
double max_distance = config.max_distance;
std::vector<double> point_distances(chck_points.size(),
{max_distance + 1});
for (size_t index = 0; index < chck_points.size(); ++index) {
const Point &chck_point = chck_points[index];
double &min_distance = point_distances[index];
bool exist_close_support_point = false;
for (auto &island_point : points) {
Point& p = island_point.point;
Point abs_diff(fabs(p.x() - chck_point.x()),
fabs(p.y() - chck_point.y()));
if (abs_diff.x() < min_distance && abs_diff.y() < min_distance) {
double distance = sqrt((double) abs_diff.x() * abs_diff.x() +
(double) abs_diff.y() * abs_diff.y());
if (min_distance > distance) {
min_distance = distance;
exist_close_support_point = true;
};
}
}
if (!exist_close_support_point) is_ok = false;
}
if (!is_ok) { // visualize
static int counter = 0;
BoundingBox bb;
for (const Point &pt : island.contour.points) bb.merge(pt);
SVG svg("Error" + std::to_string(++counter) + ".svg", bb);
svg.draw(island, "blue", 0.5f);
for (auto p : points)
svg.draw(p.point, "lightgreen", config.head_radius);
for (size_t index = 0; index < chck_points.size(); ++index) {
const Point &chck_point = chck_points[index];
double distance = point_distances[index];
bool isOk = distance < max_distance;
std::string color = (isOk) ? "gray" : "red";
svg.draw(chck_point, color, config.head_radius / 4);
}
}
CHECK(!points.empty());
//CHECK(is_ok);
// all points must be inside of island
for (const auto &point : points) { CHECK(island.contains(point)); }
for (const auto &point : points) { CHECK(island.contains(point.point)); }
return points;
}
SampleConfig create_sample_config(double size) {
SampleConfig cfg;
cfg.max_distance = 3 * size + 0.1;
cfg.head_radius = size / 4;
cfg.minimal_distance_from_outline = cfg.head_radius + size/10;
cfg.max_length_for_one_support_point = 2*size;
cfg.max_length_for_two_support_points = 4*size;
cfg.max_width_for_center_supportr_line = size;
cfg.max_width_for_zig_zag_supportr_line = 2*size;
return cfg;
}
#include <libslic3r/Geometry.hpp>
#include <libslic3r/VoronoiOffset.hpp>
TEST_CASE("Sampling speed test on FrogLegs", "[VoronoiSkeleton]")
{
TriangleMesh mesh = load_model("frog_legs.obj");
@ -245,29 +422,25 @@ TEST_CASE("Sampling speed test on FrogLegs", "[VoronoiSkeleton]")
std::vector<ExPolygons> slices;
slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {});
ExPolygon frog_leg = slices.front()[1];
SampleConfig cfg = create_sample_config(3e7);
double size = 3e7;
SampleConfig cfg;
cfg.max_distance = size + 0.1;
cfg.sample_size = size / 5;
cfg.start_distance = 0.2 * size; // radius of support head
cfg.max_length_for_one_support_point = 3 * size;
using VD = Slic3r::Geometry::VoronoiDiagram;
VD vd;
Lines lines = to_lines(frog_leg);
construct_voronoi(lines.begin(), lines.end(), &vd);
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
for (int i = 0; i < 100; ++i) {
auto points = SupportPointGenerator::uniform_cover_island(
frog_leg, cfg);
VoronoiGraph::ExPath longest_path;
VoronoiGraph skeleton = VoronoiGraphUtils::getSkeleton(vd, lines);
auto samples = SampleIslandUtils::sample_voronoi_graph(skeleton, cfg, longest_path);
}
}
TEST_CASE("Small islands should be supported in center", "[SupGen][VoronoiSkeleton]")
{
double size = 3e7;
SampleConfig cfg;
cfg.max_distance = size + 0.1;
cfg.sample_size = size / 5;
cfg.start_distance = 0.2 * size; // radius of support head
cfg.max_length_for_one_support_point = 3 * size;
double size = 3e7;
SampleConfig cfg = create_sample_config(size);
ExPolygons islands = createTestIslands(size);
for (auto &island : islands) {
auto points = test_island_sampling(island, cfg);
@ -275,10 +448,7 @@ TEST_CASE("Small islands should be supported in center", "[SupGen][VoronoiSkelet
island.rotate(angle);
auto pointsR = test_island_sampling(island, cfg);
for (Point &p : pointsR) p.rotate(-angle);
//for (Point &p : pointsR) p.rotate(-angle);
// points should be equal to pointsR
}
}
}} // namespace Slic3r::sla