|
|
|
|
@@ -9,6 +9,8 @@
|
|
|
|
|
#include "SVG.hpp"
|
|
|
|
|
#include "Point.hpp"
|
|
|
|
|
#include "ClipperUtils.hpp"
|
|
|
|
|
#include "Tesselate.hpp"
|
|
|
|
|
#include "libslic3r.h"
|
|
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <random>
|
|
|
|
|
@@ -121,15 +123,15 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|
|
|
|
Structure& top = structures_new.back();
|
|
|
|
|
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
|
|
|
|
|
// At least it is now using a bounding box check for pre-filtering.
|
|
|
|
|
for (Structure& bottom : structures_old)
|
|
|
|
|
if (top.overlaps(bottom)) {
|
|
|
|
|
top.structures_below.push_back(&bottom);
|
|
|
|
|
for (Structure& bottom : structures_old)
|
|
|
|
|
if (top.overlaps(bottom)) {
|
|
|
|
|
top.structures_below.push_back(&bottom);
|
|
|
|
|
float centroids_dist = (bottom.centroid - top.centroid).norm();
|
|
|
|
|
// Penalization resulting from centroid offset:
|
|
|
|
|
// bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area));
|
|
|
|
|
// bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area));
|
|
|
|
|
bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, 80.f * centroids_dist * centroids_dist / bottom.area));
|
|
|
|
|
// Penalization resulting from increasing polygon area:
|
|
|
|
|
bottom.supports_force *= std::min(1.f, 20.f * bottom.area / top.area);
|
|
|
|
|
bottom.supports_force *= std::min(1.f, 20.f * bottom.area / top.area);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -154,7 +156,7 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|
|
|
|
else
|
|
|
|
|
// Let's see if there's anything that overlaps enough to need supports:
|
|
|
|
|
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
|
|
|
|
|
for (const ExPolygon& p : diff_ex(to_polygons(*s.polygon), offset(s.expolygons_below(), between_layers_offset)))
|
|
|
|
|
for (const ExPolygon& p : diff_ex(to_polygons(*s.polygon), offset(s.expolygons_below(), between_layers_offset)))
|
|
|
|
|
//FIXME is it an island point or not? Vojtech thinks it is.
|
|
|
|
|
uniformly_cover(p, s);
|
|
|
|
|
}
|
|
|
|
|
@@ -163,12 +165,12 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|
|
|
|
for (Structure& s : structures_new) {
|
|
|
|
|
// Areas not supported by the areas below.
|
|
|
|
|
ExPolygons e = diff_ex(to_polygons(*s.polygon), s.polygons_below());
|
|
|
|
|
float e_area = 0.f;
|
|
|
|
|
for (const ExPolygon &ex : e)
|
|
|
|
|
e_area += float(ex.area());
|
|
|
|
|
float e_area = 0.f;
|
|
|
|
|
for (const ExPolygon &ex : e)
|
|
|
|
|
e_area += float(ex.area());
|
|
|
|
|
// Penalization resulting from large diff from the last layer:
|
|
|
|
|
// s.supports_force /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area);
|
|
|
|
|
s.supports_force /= std::max(1.f, 0.17f * (e_area * float(SCALING_FACTOR * SCALING_FACTOR)) / s.area);
|
|
|
|
|
s.supports_force /= std::max(1.f, 0.17f * (e_area * float(SCALING_FACTOR * SCALING_FACTOR)) / s.area);
|
|
|
|
|
|
|
|
|
|
if (s.area * m_config.tear_pressure > s.supports_force) {
|
|
|
|
|
//FIXME Don't calculate area inside the compare function!
|
|
|
|
|
@@ -196,64 +198,204 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng)
|
|
|
|
|
{
|
|
|
|
|
// Triangulate the polygon with holes into triplets of 3D points.
|
|
|
|
|
std::vector<Vec2f> triangles = Slic3r::triangulate_expolygon_2f(expoly);
|
|
|
|
|
|
|
|
|
|
std::vector<Vec2f> out;
|
|
|
|
|
if (! triangles.empty())
|
|
|
|
|
{
|
|
|
|
|
// Calculate area of each triangle.
|
|
|
|
|
std::vector<float> areas;
|
|
|
|
|
areas.reserve(triangles.size() / 3);
|
|
|
|
|
for (size_t i = 0; i < triangles.size(); ) {
|
|
|
|
|
const Vec2f &a = triangles[i ++];
|
|
|
|
|
const Vec2f v1 = triangles[i ++] - a;
|
|
|
|
|
const Vec2f v2 = triangles[i ++] - a;
|
|
|
|
|
areas.emplace_back(0.5f * std::abs(cross2(v1, v2)));
|
|
|
|
|
if (i != 3)
|
|
|
|
|
// Prefix sum of the areas.
|
|
|
|
|
areas.back() += areas[areas.size() - 2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2));
|
|
|
|
|
std::uniform_real_distribution<> random_triangle(0., double(areas.back()));
|
|
|
|
|
std::uniform_real_distribution<> random_float(0., 1.);
|
|
|
|
|
for (size_t i = 0; i < num_samples; ++ i) {
|
|
|
|
|
double r = random_triangle(rng);
|
|
|
|
|
size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
|
|
|
|
|
// Select a random point on the triangle.
|
|
|
|
|
double u = float(sqrt(random_float(rng)));
|
|
|
|
|
double v = float(random_float(rng));
|
|
|
|
|
const Vec2f &a = triangles[idx_triangle ++];
|
|
|
|
|
const Vec2f &b = triangles[idx_triangle++];
|
|
|
|
|
const Vec2f &c = triangles[idx_triangle];
|
|
|
|
|
const Vec2f x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u);
|
|
|
|
|
out.emplace_back(x);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
|
|
|
|
|
{
|
|
|
|
|
std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
|
|
|
|
|
double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
|
|
|
|
|
for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
|
|
|
|
|
const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1];
|
|
|
|
|
const Points pts = contour.equally_spaced_points(point_stepping_scaled);
|
|
|
|
|
for (size_t i = 0; i < pts.size(); ++ i)
|
|
|
|
|
out.emplace_back(unscale<float>(pts[i].x()), unscale<float>(pts[i].y()));
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec2f> &raw_samples, float radius)
|
|
|
|
|
{
|
|
|
|
|
Vec2f corner_min(FLT_MAX, FLT_MAX);
|
|
|
|
|
for (const Vec2f &pt : raw_samples) {
|
|
|
|
|
corner_min.x() = std::min(corner_min.x(), pt.x());
|
|
|
|
|
corner_min.y() = std::min(corner_min.y(), pt.y());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assign the raw samples to grid cells, sort the grid cells lexicographically.
|
|
|
|
|
struct RawSample {
|
|
|
|
|
Vec2f coord;
|
|
|
|
|
Vec2i cell_id;
|
|
|
|
|
};
|
|
|
|
|
std::vector<RawSample> raw_samples_sorted;
|
|
|
|
|
RawSample sample;
|
|
|
|
|
for (const Vec2f &pt : raw_samples) {
|
|
|
|
|
sample.coord = pt;
|
|
|
|
|
sample.cell_id = ((pt - corner_min) / radius).cast<int>();
|
|
|
|
|
raw_samples_sorted.emplace_back(sample);
|
|
|
|
|
}
|
|
|
|
|
std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs)
|
|
|
|
|
{ return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); });
|
|
|
|
|
|
|
|
|
|
struct PoissonDiskGridEntry {
|
|
|
|
|
// Resulting output sample points for this cell:
|
|
|
|
|
enum {
|
|
|
|
|
max_positions = 4
|
|
|
|
|
};
|
|
|
|
|
Vec2f poisson_samples[max_positions];
|
|
|
|
|
int num_poisson_samples = 0;
|
|
|
|
|
|
|
|
|
|
// Index into raw_samples:
|
|
|
|
|
int first_sample_idx;
|
|
|
|
|
int sample_cnt;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct CellIDHash {
|
|
|
|
|
std::size_t operator()(const Vec2i &cell_id) {
|
|
|
|
|
return std::hash<int>()(cell_id.x()) ^ std::hash<int>()(cell_id.y() * 593);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Map from cell IDs to hash_data. Each hash_data points to the range in raw_samples corresponding to that cell.
|
|
|
|
|
// (We could just store the samples in hash_data. This implementation is an artifact of the reference paper, which
|
|
|
|
|
// is optimizing for GPU acceleration that we haven't implemented currently.)
|
|
|
|
|
typedef std::unordered_map<Vec2i, PoissonDiskGridEntry, CellIDHash> Cells;
|
|
|
|
|
std::unordered_map<Vec2i, PoissonDiskGridEntry, CellIDHash> cells;
|
|
|
|
|
{
|
|
|
|
|
Cells::iterator last_cell_id_it;
|
|
|
|
|
Vec2i last_cell_id(-1, -1);
|
|
|
|
|
for (int i = 0; i < raw_samples_sorted.size(); ++ i) {
|
|
|
|
|
const RawSample &sample = raw_samples_sorted[i];
|
|
|
|
|
if (sample.cell_id == last_cell_id) {
|
|
|
|
|
// This sample is in the same cell as the previous, so just increase the count. Cells are
|
|
|
|
|
// always contiguous, since we've sorted raw_samples_sorted by cell ID.
|
|
|
|
|
++ last_cell_id_it->second.sample_cnt;
|
|
|
|
|
} else {
|
|
|
|
|
// This is a new cell.
|
|
|
|
|
PoissonDiskGridEntry data;
|
|
|
|
|
data.first_sample_idx = i;
|
|
|
|
|
data.sample_cnt = 1;
|
|
|
|
|
auto result = cells.insert({sample.cell_id, data});
|
|
|
|
|
last_cell_id = sample.cell_id;
|
|
|
|
|
last_cell_id_it = result.first;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int max_trials = 5;
|
|
|
|
|
const float radius_squared = radius * radius;
|
|
|
|
|
for (int trial = 0; trial < max_trials; ++ trial) {
|
|
|
|
|
// Create sample points for each entry in cells.
|
|
|
|
|
for (auto &it : cells) {
|
|
|
|
|
const Vec2i &cell_id = it.first;
|
|
|
|
|
PoissonDiskGridEntry &cell_data = it.second;
|
|
|
|
|
// This cell's raw sample points start at first_sample_idx. On trial 0, try the first one. On trial 1, try first_sample_idx + 1.
|
|
|
|
|
int next_sample_idx = cell_data.first_sample_idx + trial;
|
|
|
|
|
if (trial >= cell_data.sample_cnt)
|
|
|
|
|
// There are no more points to try for this cell.
|
|
|
|
|
continue;
|
|
|
|
|
const RawSample &candidate = raw_samples_sorted[next_sample_idx];
|
|
|
|
|
// See if this point conflicts with any other points in this cell, or with any points in
|
|
|
|
|
// neighboring cells. Note that it's possible to have more than one point in the same cell.
|
|
|
|
|
bool conflict = false;
|
|
|
|
|
for (int i = -1; i < 2 && ! conflict; ++ i) {
|
|
|
|
|
for (int j = -1; j < 2; ++ j) {
|
|
|
|
|
const auto &it_neighbor = cells.find(cell_id + Vec2i(i, j));
|
|
|
|
|
if (it_neighbor != cells.end()) {
|
|
|
|
|
const PoissonDiskGridEntry &neighbor = it_neighbor->second;
|
|
|
|
|
for (int i_sample = 0; i_sample < neighbor.num_poisson_samples; ++ i_sample)
|
|
|
|
|
if ((neighbor.poisson_samples[i_sample] - candidate.coord).squaredNorm() < radius_squared) {
|
|
|
|
|
conflict = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (! conflict) {
|
|
|
|
|
// Store the new sample.
|
|
|
|
|
assert(cell_data.num_poisson_samples < cell_data.max_positions);
|
|
|
|
|
if (cell_data.num_poisson_samples < cell_data.max_positions)
|
|
|
|
|
cell_data.poisson_samples[cell_data.num_poisson_samples ++] = candidate.coord;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Copy the results to the output.
|
|
|
|
|
std::vector<Vec2f> out;
|
|
|
|
|
for (const auto it : cells)
|
|
|
|
|
for (int i = 0; i < it.second.num_poisson_samples; ++ i)
|
|
|
|
|
out.emplace_back(it.second.poisson_samples[i]);
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SLAAutoSupports::uniformly_cover(const ExPolygon& island, Structure& structure, bool is_new_island, bool just_one)
|
|
|
|
|
{
|
|
|
|
|
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const float density_horizontal = m_config.tear_pressure / m_config.support_force;
|
|
|
|
|
//FIXME why?
|
|
|
|
|
const float poisson_radius = 1.f / (5.f * density_horizontal);
|
|
|
|
|
// const float poisson_radius = 1.f / (15.f * density_horizontal);
|
|
|
|
|
const float samples_per_mm2 = 30.f / (float(M_PI) * poisson_radius * poisson_radius);
|
|
|
|
|
|
|
|
|
|
// We will cover the island another way.
|
|
|
|
|
// For now we'll just place the points randomly not too close to the others.
|
|
|
|
|
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
|
|
|
|
std::random_device rd;
|
|
|
|
|
std::mt19937 gen(rd());
|
|
|
|
|
std::uniform_real_distribution<> dis(0., 1.);
|
|
|
|
|
std::random_device rd;
|
|
|
|
|
std::mt19937 rng(rd());
|
|
|
|
|
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(island, samples_per_mm2, 5.f / poisson_radius, rng);
|
|
|
|
|
std::vector<Vec2f> poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius);
|
|
|
|
|
|
|
|
|
|
std::vector<Vec3d> island_new_points;
|
|
|
|
|
const BoundingBox bb = get_extents(island);
|
|
|
|
|
const int refused_limit = (int)floor(30.f * ((float)bb.size()(0)*bb.size()(1) / (float)island.area()) + 0.5f);
|
|
|
|
|
int refused_points = 0;
|
|
|
|
|
//FIXME this is very inefficient (may be blind) for long narrow polygons (thin crescent, thin ring). Use some search structure: Triangulate the polygon first?
|
|
|
|
|
//FIXME use a low discrepancy sequence, Poisson sampling.
|
|
|
|
|
// Poisson sampling (adapt from 3D to 2D by triangulation): https://github.com/zewt/maya-implicit-skinning/blob/master/src/meshes/vcg_lib/utils_sampling.cpp
|
|
|
|
|
while (refused_points < refused_limit) {
|
|
|
|
|
Point out;
|
|
|
|
|
if (refused_points == 0 && island_new_points.empty()) // first iteration
|
|
|
|
|
out = island.contour.centroid();
|
|
|
|
|
else
|
|
|
|
|
out = Point(bb.min(0) + bb.size()(0) * dis(gen), bb.min(1) + bb.size()(1) * dis(gen));
|
|
|
|
|
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
|
|
|
|
{
|
|
|
|
|
static int irun = 0;
|
|
|
|
|
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(island));
|
|
|
|
|
svg.draw(island);
|
|
|
|
|
for (const Vec2f &pt : raw_samples)
|
|
|
|
|
svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red");
|
|
|
|
|
for (const Vec2f &pt : poisson_samples)
|
|
|
|
|
svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue");
|
|
|
|
|
}
|
|
|
|
|
#endif /* NDEBUG */
|
|
|
|
|
|
|
|
|
|
Vec3d unscaled_out = unscale(out(0), out(1), 0);
|
|
|
|
|
bool add_it = true;
|
|
|
|
|
|
|
|
|
|
if (!island.contour.contains(out))
|
|
|
|
|
add_it = false;
|
|
|
|
|
else
|
|
|
|
|
for (const Polygon& hole : island.holes)
|
|
|
|
|
if (hole.contains(out)) {
|
|
|
|
|
add_it = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (add_it) {
|
|
|
|
|
for (const Vec3d& p : island_new_points) {
|
|
|
|
|
if ((p - unscaled_out).squaredNorm() < 1./(2.4*density_horizontal)) {
|
|
|
|
|
add_it = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (add_it) {
|
|
|
|
|
island_new_points.emplace_back(unscaled_out);
|
|
|
|
|
if (just_one)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
++refused_points;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const Vec3d& p : island_new_points) {
|
|
|
|
|
m_output.emplace_back(float(p(0)), float(p(1)), structure.height, 0.4f, is_new_island);
|
|
|
|
|
assert(! poisson_samples.empty());
|
|
|
|
|
for (const Vec2f &pt : poisson_samples) {
|
|
|
|
|
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.4f, is_new_island);
|
|
|
|
|
structure.supports_force += m_config.support_force;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|