Merge branch 'fs_QuadricEdgeCollapse'

This commit is contained in:
Lukas Matena 2021-08-06 18:23:49 +02:00
commit 0ccc791750
23 changed files with 1472 additions and 55 deletions

View file

@ -178,6 +178,8 @@ add_library(libslic3r STATIC
PrintRegion.cpp PrintRegion.cpp
PNGReadWrite.hpp PNGReadWrite.hpp
PNGReadWrite.cpp PNGReadWrite.cpp
QuadricEdgeCollapse.cpp
QuadricEdgeCollapse.hpp
Semver.cpp Semver.cpp
ShortestPath.cpp ShortestPath.cpp
ShortestPath.hpp ShortestPath.hpp

View file

@ -354,7 +354,7 @@ inline void MutableSkipHeapPriorityQueue<T, LessPredicate, IndexSetter, blocking
#endif /* NDEBUG */ #endif /* NDEBUG */
{ {
// Mark as removed from the queue. // Mark as removed from the queue.
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max()); m_index_setter(m_heap[1], std::numeric_limits<size_t>::max());
} }
// Zero'th element is padding, thus non-empty queue must have at least two elements. // Zero'th element is padding, thus non-empty queue must have at least two elements.
if (m_heap.size() > 2) { if (m_heap.size() > 2) {

View file

@ -0,0 +1,654 @@
#include "QuadricEdgeCollapse.hpp"
#include <tuple>
#include "MutablePriorityQueue.hpp"
#include "SimplifyMeshImpl.hpp"
#include <tbb/parallel_for.h>
using namespace Slic3r;
// only private namespace not neccessary be in hpp
namespace QuadricEdgeCollapse {
using Vertices = std::vector<stl_vertex>;
using Triangle = stl_triangle_vertex_indices;
using Indices = std::vector<stl_triangle_vertex_indices>;
using SymMat = SimplifyMesh::implementation::SymetricMatrix<double>;
// smallest error caused by edges, identify smallest edge in triangle
struct Error
{
float value = -1.; // identifying of smallest edge is stored inside of TriangleInfo
uint32_t triangle_index = 0;
Error(float value, uint32_t triangle_index)
: value(value)
, triangle_index(triangle_index)
{}
Error() = default;
};
using Errors = std::vector<Error>;
// merge information together - faster access during processing
struct TriangleInfo {
Vec3f n; // normalized normal - used for check when fliped
// range(0 .. 2),
unsigned char min_index = 0; // identify edge for minimal Error -> lightweight Error structure
TriangleInfo() = default;
bool is_deleted() const { return n.x() > 2.f; }
void set_deleted() { n.x() = 3.f; }
};
using TriangleInfos = std::vector<TriangleInfo>;
struct VertexInfo {
SymMat q; // sum quadric of surround triangles
uint32_t start = 0, count = 0; // vertex neighbor triangles
VertexInfo() = default;
bool is_deleted() const { return count == 0; }
};
using VertexInfos = std::vector<VertexInfo>;
struct EdgeInfo {
uint32_t t_index=0; // triangle index
unsigned char edge = 0; // 0 or 1 or 2
EdgeInfo() = default;
};
using EdgeInfos = std::vector<EdgeInfo>;
// DTO for change neighbors
struct CopyEdgeInfo {
uint32_t start;
uint32_t count;
uint32_t move;
CopyEdgeInfo(uint32_t start, uint32_t count, uint32_t move)
: start(start), count(count), move(move)
{}
};
using CopyEdgeInfos = std::vector<CopyEdgeInfo>;
Vec3d create_normal(const Triangle &triangle, const Vertices &vertices);
std::array<Vec3d,3> create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices);
std::array<double, 3> vertices_error(const SymMat &q, const std::array<Vec3d, 3> &vertices);
double calculate_determinant(const SymMat &q);
double calculate_error(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
Vec3f calculate_vertex(uint32_t id_v1, uint32_t id_v2, const SymMat & q, const Vertices &vertices);
Vec3d calculate_vertex(double det, const SymMat &q);
// calculate error for vertex and quadrics, triangle quadrics and triangle vertex give zero, only pozitive number
double vertex_error(const SymMat &q, const Vec3d &vertex);
SymMat create_quadric(const Triangle &t, const Vec3d& n, const Vertices &vertices);
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors> init(const indexed_triangle_set &its);
std::optional<uint32_t> find_triangle_index1(uint32_t vi, const VertexInfo& v_info,
uint32_t ti, const EdgeInfos& e_infos, const Indices& indices);
bool is_flipped(const Vec3f &new_vertex, uint32_t ti0, uint32_t ti1, const VertexInfo& v_info,
const TriangleInfos &t_infos, const EdgeInfos &e_infos, const indexed_triangle_set &its);
// find edge with smallest error in triangle
Vec3d calculate_3errors(const Triangle &t, const Vertices &vertices, const VertexInfos &v_infos);
Error calculate_error(uint32_t ti, const Triangle& t,const Vertices &vertices, const VertexInfos& v_infos, unsigned char& min_index);
void remove_triangle(EdgeInfos &e_infos, VertexInfo &v_info, uint32_t ti);
void change_neighbors(EdgeInfos &e_infos, VertexInfos &v_infos, uint32_t ti0, uint32_t ti1,
uint32_t vi0, uint32_t vi1, uint32_t vi_top0,
const Triangle &t1, CopyEdgeInfos& infos, EdgeInfos &e_infos1);
void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its);
} // namespace QuadricEdgeCollapse
using namespace QuadricEdgeCollapse;
void Slic3r::its_quadric_edge_collapse(
indexed_triangle_set & its,
uint32_t triangle_count,
float * max_error,
std::function<void(void)> throw_on_cancel,
std::function<void(int)> statusfn)
{
// constants --> may be move to config
const int status_init_size = 10; // in percents
const int check_cancel_period = 16; // how many edge to reduce before call throw_on_cancel
// check input
if (triangle_count >= its.indices.size()) return;
float maximal_error = (max_error == nullptr)? std::numeric_limits<float>::max() : *max_error;
if (maximal_error <= 0.f) return;
if (throw_on_cancel == nullptr) throw_on_cancel = []() {};
if (statusfn == nullptr) statusfn = [](int) {};
TriangleInfos t_infos; // only normals with information about deleted triangle
VertexInfos v_infos;
EdgeInfos e_infos;
Errors errors;
std::tie(t_infos, v_infos, e_infos, errors) = init(its);
throw_on_cancel();
statusfn(status_init_size);
// convert from triangle index to mutable priority queue index
std::vector<size_t> ti_2_mpqi(its.indices.size(), {0});
auto setter = [&ti_2_mpqi](const Error &e, size_t index) { ti_2_mpqi[e.triangle_index] = index; };
auto less = [](const Error &e1, const Error &e2) -> bool { return e1.value < e2.value; };
auto mpq = make_miniheap_mutable_priority_queue<Error, 32, false>(std::move(setter), std::move(less));
//MutablePriorityQueue<Error, decltype(setter), decltype(less)> mpq(std::move(setter), std::move(less));
mpq.reserve(its.indices.size());
for (Error &error :errors) mpq.push(error);
const size_t max_triangle_count_for_one_vertex = 50;
CopyEdgeInfos ceis;
ceis.reserve(max_triangle_count_for_one_vertex);
EdgeInfos e_infos_swap;
e_infos_swap.reserve(max_triangle_count_for_one_vertex);
std::vector<uint32_t> changed_triangle_indices;
changed_triangle_indices.reserve(2 * max_triangle_count_for_one_vertex);
uint32_t actual_triangle_count = its.indices.size();
uint32_t count_triangle_to_reduce = actual_triangle_count - triangle_count;
auto increase_status = [&]() {
double reduced = (actual_triangle_count - triangle_count) /
(double) count_triangle_to_reduce;
double status = status_init_size + (100 - status_init_size) *
(1. - reduced);
statusfn(static_cast<int>(std::round(status)));
};
// modulo for update status
uint32_t status_mod = std::max(uint32_t(16), count_triangle_to_reduce / 100);
uint32_t iteration_number = 0;
float last_collapsed_error = 0.f;
while (actual_triangle_count > triangle_count && !mpq.empty()) {
++iteration_number;
if (iteration_number % status_mod == 0) increase_status();
if (iteration_number % check_cancel_period == 0) throw_on_cancel();
// triangle index 0
Error e = mpq.top(); // copy
if (e.value >= maximal_error) break; // Too big error
mpq.pop();
uint32_t ti0 = e.triangle_index;
TriangleInfo &t_info0 = t_infos[ti0];
if (t_info0.is_deleted()) continue;
assert(t_info0.min_index < 3);
const Triangle &t0 = its.indices[ti0];
uint32_t vi0 = t0[t_info0.min_index];
uint32_t vi1 = t0[(t_info0.min_index+1) %3];
// Need by move of neighbor edge infos in function: change_neighbors
if (vi0 > vi1) std::swap(vi0, vi1);
VertexInfo &v_info0 = v_infos[vi0];
VertexInfo &v_info1 = v_infos[vi1];
assert(!v_info0.is_deleted() && !v_info1.is_deleted());
// new vertex position
SymMat q(v_info0.q);
q += v_info1.q;
Vec3f new_vertex0 = calculate_vertex(vi0, vi1, q, its.vertices);
// set of triangle indices that change quadric
auto ti1_opt = (v_info0.count < v_info1.count)?
find_triangle_index1(vi1, v_info0, ti0, e_infos, its.indices) :
find_triangle_index1(vi0, v_info1, ti0, e_infos, its.indices) ;
if (!ti1_opt.has_value() || // edge has only one triangle
is_flipped(new_vertex0, ti0, *ti1_opt, v_info0, t_infos, e_infos, its) ||
is_flipped(new_vertex0, ti0, *ti1_opt, v_info1, t_infos, e_infos, its)) {
// try other triangle's edge
Vec3d errors = calculate_3errors(t0, its.vertices, v_infos);
Vec3i ord = (errors[0] < errors[1]) ?
((errors[0] < errors[2])?
((errors[1] < errors[2]) ? Vec3i(0, 1, 2) : Vec3i(0, 2, 1)) :
Vec3i(2, 0, 1)):
((errors[1] < errors[2])?
((errors[0] < errors[2]) ? Vec3i(1, 0, 2) : Vec3i(1, 2, 0)) :
Vec3i(2, 1, 0));
if (t_info0.min_index == ord[0]) {
t_info0.min_index = ord[1];
e.value = errors[t_info0.min_index];
} else if (t_info0.min_index == ord[1]) {
t_info0.min_index = ord[2];
e.value = errors[t_info0.min_index];
} else {
// error is changed when surround edge is reduced
t_info0.min_index = 3; // bad index -> invalidate
e.value = maximal_error;
}
// IMPROVE: check mpq top if it is ti1 with same edge
mpq.push(e);
continue;
}
uint32_t ti1 = *ti1_opt;
last_collapsed_error = e.value;
changed_triangle_indices.clear();
changed_triangle_indices.reserve(v_info0.count + v_info1.count - 4);
// for each vertex0 triangles
uint32_t v_info0_end = v_info0.start + v_info0.count;
for (uint32_t di = v_info0.start; di < v_info0_end; ++di) {
assert(di < e_infos.size());
uint32_t ti = e_infos[di].t_index;
if (ti == ti0) continue; // ti0 will be deleted
if (ti == ti1) continue; // ti1 will be deleted
changed_triangle_indices.emplace_back(ti);
}
// for each vertex1 triangles
uint32_t v_info1_end = v_info1.start + v_info1.count;
for (uint32_t di = v_info1.start; di < v_info1_end; ++di) {
assert(di < e_infos.size());
EdgeInfo &e_info = e_infos[di];
uint32_t ti = e_info.t_index;
if (ti == ti0) continue; // ti0 will be deleted
if (ti == ti1) continue; // ti1 will be deleted
Triangle &t = its.indices[ti];
t[e_info.edge] = vi0; // change index
changed_triangle_indices.emplace_back(ti);
}
v_info0.q = q;
// fix neighbors
// vertex index of triangle 0 which is not vi0 nor vi1
uint32_t vi_top0 = t0[(t_info0.min_index + 2) % 3];
const Triangle &t1 = its.indices[ti1];
change_neighbors(e_infos, v_infos, ti0, ti1, vi0, vi1,
vi_top0, t1, ceis, e_infos_swap);
// Change vertex
its.vertices[vi0] = new_vertex0;
// fix errors - must be after set neighbors - v_infos
mpq.remove(ti_2_mpqi[ti1]);
for (uint32_t ti : changed_triangle_indices) {
size_t priority_queue_index = ti_2_mpqi[ti];
TriangleInfo& t_info = t_infos[ti];
t_info.n = create_normal(its.indices[ti], its.vertices).cast<float>(); // recalc normals
mpq[priority_queue_index] = calculate_error(ti, its.indices[ti], its.vertices, v_infos, t_info.min_index);
mpq.update(priority_queue_index);
}
// set triangle(0 + 1) indices as deleted
TriangleInfo &t_info1 = t_infos[ti1];
t_info0.set_deleted();
t_info1.set_deleted();
// triangle counter decrementation
actual_triangle_count-=2;
}
// compact triangle
compact(v_infos, t_infos, e_infos, its);
if (max_error != nullptr) *max_error = last_collapsed_error;
}
Vec3d QuadricEdgeCollapse::create_normal(const Triangle &triangle,
const Vertices &vertices)
{
Vec3d v0 = vertices[triangle[0]].cast<double>();
Vec3d v1 = vertices[triangle[1]].cast<double>();
Vec3d v2 = vertices[triangle[2]].cast<double>();
// n = triangle normal
Vec3d n = (v1 - v0).cross(v2 - v0);
n.normalize();
return n;
}
double QuadricEdgeCollapse::calculate_determinant(const SymMat &q)
{
return q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
}
Vec3d QuadricEdgeCollapse::calculate_vertex(double det, const SymMat &q) {
double det_1 = -1 / det;
double det_x = q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
double det_y = q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
double det_z = q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
return Vec3d(det_1 * det_x, -det_1 * det_y, det_1 * det_z);
}
std::array<Vec3d,3> QuadricEdgeCollapse::create_vertices(uint32_t id_v1, uint32_t id_v2, const Vertices &vertices)
{
Vec3d v0 = vertices[id_v1].cast<double>();
Vec3d v1 = vertices[id_v2].cast<double>();
Vec3d vm = (v0 + v1) / 2.;
return {v0, v1, vm};
}
std::array<double, 3> QuadricEdgeCollapse::vertices_error(
const SymMat &q, const std::array<Vec3d, 3> &vertices)
{
return {
vertex_error(q, vertices[0]),
vertex_error(q, vertices[1]),
vertex_error(q, vertices[2])};
}
double QuadricEdgeCollapse::calculate_error(uint32_t id_v1,
uint32_t id_v2,
const SymMat & q,
const Vertices &vertices)
{
double det = calculate_determinant(q);
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
// can't divide by zero
auto verts = create_vertices(id_v1, id_v2, vertices);
auto errors = vertices_error(q, verts);
return *std::min_element(std::begin(errors), std::end(errors));
}
Vec3d vertex = calculate_vertex(det, q);
return vertex_error(q, vertex);
}
// similar as calculate error but focus on new vertex without calculation of error
Vec3f QuadricEdgeCollapse::calculate_vertex(uint32_t id_v1,
uint32_t id_v2,
const SymMat & q,
const Vertices &vertices)
{
double det = calculate_determinant(q);
if (std::abs(det) < std::numeric_limits<double>::epsilon()) {
// can't divide by zero
auto verts = create_vertices(id_v1, id_v2, vertices);
auto errors = vertices_error(q, verts);
auto mit = std::min_element(std::begin(errors), std::end(errors));
return verts[mit - std::begin(errors)].cast<float>();
}
return calculate_vertex(det, q).cast<float>();
}
double QuadricEdgeCollapse::vertex_error(const SymMat &q, const Vec3d &vertex)
{
const double &x = vertex.x(), &y = vertex.y(), &z = vertex.z();
return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z +
2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z +
2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9];
}
SymMat QuadricEdgeCollapse::create_quadric(const Triangle &t,
const Vec3d & n,
const Vertices &vertices)
{
Vec3d v0 = vertices[t[0]].cast<double>();
return SymMat(n.x(), n.y(), n.z(), -n.dot(v0));
}
std::tuple<TriangleInfos, VertexInfos, EdgeInfos, Errors>
QuadricEdgeCollapse::init(const indexed_triangle_set &its)
{
TriangleInfos t_infos(its.indices.size());
VertexInfos v_infos(its.vertices.size());
{
std::vector<SymMat> triangle_quadrics(its.indices.size());
// calculate normals
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
const Triangle &t = its.indices[i];
TriangleInfo & t_info = t_infos[i];
Vec3d normal = create_normal(t, its.vertices);
t_info.n = normal.cast<float>();
triangle_quadrics[i] = create_quadric(t, normal, its.vertices);
}
}); // END parallel for
// sum quadrics
for (size_t i = 0; i < its.indices.size(); i++) {
const Triangle &t = its.indices[i];
const SymMat & q = triangle_quadrics[i];
for (size_t e = 0; e < 3; e++) {
VertexInfo &v_info = v_infos[t[e]];
v_info.q += q;
++v_info.count; // triangle count
}
}
} // remove triangle quadrics
// set offseted starts
uint32_t triangle_start = 0;
for (VertexInfo &v_info : v_infos) {
v_info.start = triangle_start;
triangle_start += v_info.count;
// set filled vertex to zero
v_info.count = 0;
}
assert(its.indices.size() * 3 == triangle_start);
// calc error
Errors errors(its.indices.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, its.indices.size()),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
const Triangle &t = its.indices[i];
TriangleInfo & t_info = t_infos[i];
errors[i] = calculate_error(i, t, its.vertices, v_infos, t_info.min_index);
}
}); // END parallel for
// create reference
EdgeInfos e_infos(its.indices.size() * 3);
for (size_t i = 0; i < its.indices.size(); i++) {
const Triangle &t = its.indices[i];
for (size_t j = 0; j < 3; ++j) {
VertexInfo &v_info = v_infos[t[j]];
size_t ei = v_info.start + v_info.count;
assert(ei < e_infos.size());
EdgeInfo &e_info = e_infos[ei];
e_info.t_index = i;
e_info.edge = j;
++v_info.count;
}
}
return {t_infos, v_infos, e_infos, errors};
}
std::optional<uint32_t> QuadricEdgeCollapse::find_triangle_index1(uint32_t vi,
const VertexInfo &v_info,
uint32_t ti0,
const EdgeInfos & e_infos,
const Indices & indices)
{
coord_t vi_coord = static_cast<coord_t>(vi);
uint32_t end = v_info.start + v_info.count;
for (uint32_t ei = v_info.start; ei < end; ++ei) {
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue;
const Triangle& t = indices[e_info.t_index];
if (t[(e_info.edge + 1) % 3] == vi_coord ||
t[(e_info.edge + 2) % 3] == vi_coord)
return e_info.t_index;
}
// triangle0 is on border and do NOT have twin edge
return {};
}
bool QuadricEdgeCollapse::is_flipped(const Vec3f & new_vertex,
uint32_t ti0,
uint32_t ti1,
const VertexInfo & v_info,
const TriangleInfos & t_infos,
const EdgeInfos & e_infos,
const indexed_triangle_set &its)
{
static const float thr_pos = 1.0f - std::numeric_limits<float>::epsilon();
static const float thr_neg = -thr_pos;
static const float dot_thr = 0.2f; // Value from simplify mesh cca 80 DEG
// for each vertex triangles
size_t v_info_end = v_info.start + v_info.count;
for (size_t ei = v_info.start; ei < v_info_end; ++ei) {
assert(ei < e_infos.size());
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue; // ti0 will be deleted
if (e_info.t_index == ti1) continue; // ti1 will be deleted
const Triangle &t = its.indices[e_info.t_index];
const Vec3f &normal = t_infos[e_info.t_index].n;
const Vec3f &vf = its.vertices[t[(e_info.edge + 1) % 3]];
const Vec3f &vs = its.vertices[t[(e_info.edge + 2) % 3]];
Vec3f d1 = vf - new_vertex;
d1.normalize();
Vec3f d2 = vs - new_vertex;
d2.normalize();
float dot = d1.dot(d2);
if (dot > thr_pos || dot < thr_neg) return true;
// IMPROVE: propagate new normal
Vec3f n = d1.cross(d2);
n.normalize();
if(n.dot(normal) < dot_thr) return true;
}
return false;
}
Vec3d QuadricEdgeCollapse::calculate_3errors(const Triangle & t,
const Vertices & vertices,
const VertexInfos &v_infos)
{
Vec3d error;
for (size_t j = 0; j < 3; ++j) {
size_t j2 = (j == 2) ? 0 : (j + 1);
uint32_t vi0 = t[j];
uint32_t vi1 = t[j2];
SymMat q(v_infos[vi0].q); // copy
q += v_infos[vi1].q;
error[j] = calculate_error(vi0, vi1, q, vertices);
}
return error;
}
Error QuadricEdgeCollapse::calculate_error(uint32_t ti,
const Triangle & t,
const Vertices & vertices,
const VertexInfos &v_infos,
unsigned char & min_index)
{
Vec3d error = calculate_3errors(t, vertices, v_infos);
// select min error
min_index = (error[0] < error[1]) ? ((error[0] < error[2]) ? 0 : 2) :
((error[1] < error[2]) ? 1 : 2);
return Error(static_cast<float>(error[min_index]), ti);
}
void QuadricEdgeCollapse::remove_triangle(EdgeInfos & e_infos,
VertexInfo &v_info,
uint32_t ti)
{
auto e_info = e_infos.begin() + v_info.start;
auto e_info_end = e_info + v_info.count - 1;
for (; e_info != e_info_end; ++e_info) {
if (e_info->t_index == ti) {
*e_info = *e_info_end;
--v_info.count;
return;
}
}
assert(e_info_end->t_index == ti);
// last triangle is ti
--v_info.count;
}
void QuadricEdgeCollapse::change_neighbors(EdgeInfos & e_infos,
VertexInfos & v_infos,
uint32_t ti0,
uint32_t ti1,
uint32_t vi0,
uint32_t vi1,
uint32_t vi_top0,
const Triangle &t1,
CopyEdgeInfos& infos,
EdgeInfos & e_infos1)
{
// have to copy Edge info from higher vertex index into smaller
assert(vi0 < vi1);
// vertex index of triangle 1 which is not vi0 nor vi1
uint32_t vi_top1 = t1[0];
if (vi_top1 == vi0 || vi_top1 == vi1) {
vi_top1 = t1[1];
if (vi_top1 == vi0 || vi_top1 == vi1) vi_top1 = t1[2];
}
remove_triangle(e_infos, v_infos[vi_top0], ti0);
remove_triangle(e_infos, v_infos[vi_top1], ti1);
VertexInfo &v_info0 = v_infos[vi0];
VertexInfo &v_info1 = v_infos[vi1];
uint32_t new_triangle_count = v_info0.count + v_info1.count - 4;
remove_triangle(e_infos, v_info0, ti0);
remove_triangle(e_infos, v_info0, ti1);
// copy second's edge infos out of e_infos, to free size
e_infos1.clear();
e_infos1.reserve(v_info1.count - 2);
uint32_t v_info_s_end = v_info1.start + v_info1.count;
for (uint32_t ei = v_info1.start; ei < v_info_s_end; ++ei) {
const EdgeInfo &e_info = e_infos[ei];
if (e_info.t_index == ti0) continue;
if (e_info.t_index == ti1) continue;
e_infos1.emplace_back(e_info);
}
v_info1.count = 0;
uint32_t need = (new_triangle_count < v_info0.count)? 0:
(new_triangle_count - v_info0.count);
uint32_t act_vi = vi0 + 1;
VertexInfo *act_v_info = &v_infos[act_vi];
uint32_t act_start = act_v_info->start;
uint32_t last_end = v_info0.start + v_info0.count;
infos.clear();
infos.reserve(need);
while (true) {
uint32_t save = act_start - last_end;
if (save > 0) {
if (save >= need) break;
need -= save;
infos.emplace_back(act_v_info->start, act_v_info->count, need);
} else {
infos.back().count += act_v_info->count;
}
last_end = act_v_info->start + act_v_info->count;
act_v_info->start += need;
++act_vi;
if (act_vi < v_infos.size()) {
act_v_info = &v_infos[act_vi];
act_start = act_v_info->start;
} else
act_start = e_infos.size(); // fix for edge between last two triangles
}
// copy by c_infos
for (uint32_t i = infos.size(); i > 0; --i) {
const CopyEdgeInfo &c_info = infos[i - 1];
for (uint32_t ei = c_info.start + c_info.count - 1; ei >= c_info.start; --ei)
e_infos[ei + c_info.move] = e_infos[ei]; // copy
}
// copy triangle from first info into second
for (uint32_t ei_s = 0; ei_s < e_infos1.size(); ++ei_s) {
uint32_t ei_f = v_info0.start + v_info0.count;
e_infos[ei_f] = e_infos1[ei_s]; // copy
++v_info0.count;
}
}
void QuadricEdgeCollapse::compact(const VertexInfos & v_infos,
const TriangleInfos & t_infos,
const EdgeInfos & e_infos,
indexed_triangle_set &its)
{
uint32_t vi_new = 0;
for (uint32_t vi = 0; vi < v_infos.size(); ++vi) {
const VertexInfo &v_info = v_infos[vi];
if (v_info.is_deleted()) continue; // deleted
uint32_t e_info_end = v_info.start + v_info.count;
for (uint32_t ei = v_info.start; ei < e_info_end; ++ei) {
const EdgeInfo &e_info = e_infos[ei];
// change vertex index
its.indices[e_info.t_index][e_info.edge] = vi_new;
}
// compact vertices
its.vertices[vi_new++] = its.vertices[vi];
}
// remove vertices tail
its.vertices.erase(its.vertices.begin() + vi_new, its.vertices.end());
uint32_t ti_new = 0;
for (uint32_t ti = 0; ti < t_infos.size(); ti++) {
const TriangleInfo &t_info = t_infos[ti];
if (t_info.is_deleted()) continue;
its.indices[ti_new++] = its.indices[ti];
}
its.indices.erase(its.indices.begin() + ti_new, its.indices.end());
}

View file

@ -0,0 +1,28 @@
// paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf
// sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/
// inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
#include <cstdint>
#include <functional>
#include "TriangleMesh.hpp"
namespace Slic3r {
/// <summary>
/// Simplify mesh by Quadric metric
/// </summary>
/// <param name="its">IN/OUT triangle mesh to be simplified.</param>
/// <param name="triangle_count">Wanted triangle count.</param>
/// <param name="max_error">Maximal Quadric for reduce.
/// When nullptr then max float is used
/// Output: Last used ErrorValue to collapse edge</param>
/// <param name="throw_on_cancel">Could stop process of calculation.</param>
/// <param name="statusfn">Give a feed back to user about progress. Values 1 - 100</param>
void its_quadric_edge_collapse(
indexed_triangle_set & its,
uint32_t triangle_count = 0,
float * max_error = nullptr,
std::function<void(void)> throw_on_cancel = nullptr,
std::function<void(int)> statusfn = nullptr);
} // namespace Slic3r

View file

@ -107,7 +107,7 @@ public:
// Determinant // Determinant
T det(int a11, int a12, int a13, T det(int a11, int a12, int a13,
int a21, int a22, int a23, int a21, int a22, int a23,
int a31, int a32, int a33) int a31, int a32, int a33) const
{ {
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] + T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] - m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -

View file

@ -57,6 +57,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoPainterBase.hpp GUI/Gizmos/GLGizmoPainterBase.hpp
GUI/Gizmos/GLGizmoSeam.cpp GUI/Gizmos/GLGizmoSeam.cpp
GUI/Gizmos/GLGizmoSeam.hpp GUI/Gizmos/GLGizmoSeam.hpp
GUI/Gizmos/GLGizmoSimplify.cpp
GUI/Gizmos/GLGizmoSimplify.hpp
GUI/Gizmos/GLGizmoMmuSegmentation.cpp GUI/Gizmos/GLGizmoMmuSegmentation.cpp
GUI/Gizmos/GLGizmoMmuSegmentation.hpp GUI/Gizmos/GLGizmoMmuSegmentation.hpp
GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.cpp

View file

@ -657,6 +657,15 @@ wxMenuItem* MenuFactory::append_menu_item_fix_through_netfabb(wxMenu* menu)
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "", wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Fix through the Netfabb"), "",
[](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu, [](wxCommandEvent&) { obj_list()->fix_through_netfabb(); }, "", menu,
[]() {return plater()->can_fix_through_netfabb(); }, plater()); []() {return plater()->can_fix_through_netfabb(); }, plater());
return menu_item;
}
wxMenuItem* MenuFactory::append_menu_item_simplify(wxMenu* menu)
{
wxMenuItem* menu_item = append_menu_item(menu, wxID_ANY, _L("Simplify model"), "",
[](wxCommandEvent&) { obj_list()->simplify(); }, "", menu,
[]() {return plater()->can_simplify(); }, plater());
menu->AppendSeparator(); menu->AppendSeparator();
return menu_item; return menu_item;
@ -874,6 +883,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu)
append_menu_item_scale_selection_to_fit_print_volume(menu); append_menu_item_scale_selection_to_fit_print_volume(menu);
append_menu_item_fix_through_netfabb(menu); append_menu_item_fix_through_netfabb(menu);
append_menu_item_simplify(menu);
append_menu_items_mirror(menu); append_menu_items_mirror(menu);
} }
@ -923,6 +933,7 @@ void MenuFactory::create_part_menu()
append_menu_item_replace_with_stl(menu); append_menu_item_replace_with_stl(menu);
append_menu_item_export_stl(menu); append_menu_item_export_stl(menu);
append_menu_item_fix_through_netfabb(menu); append_menu_item_fix_through_netfabb(menu);
append_menu_item_simplify(menu);
append_menu_items_mirror(menu); append_menu_items_mirror(menu);
append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"), append_menu_item(menu, wxID_ANY, _L("Split"), _L("Split the selected object into individual parts"),

View file

@ -92,6 +92,7 @@ private:
wxMenuItem* append_menu_item_printable(wxMenu* menu); wxMenuItem* append_menu_item_printable(wxMenu* menu);
void append_menu_items_osx(wxMenu* menu); void append_menu_items_osx(wxMenu* menu);
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
wxMenuItem* append_menu_item_simplify(wxMenu* menu);
void append_menu_item_export_stl(wxMenu* menu); void append_menu_item_export_stl(wxMenu* menu);
void append_menu_item_reload_from_disk(wxMenu* menu); void append_menu_item_reload_from_disk(wxMenu* menu);
void append_menu_item_replace_with_stl(wxMenu* menu); void append_menu_item_replace_with_stl(wxMenu* menu);

View file

@ -3955,6 +3955,12 @@ void ObjectList::fix_through_netfabb()
update_item_error_icon(obj_idx, vol_idx); update_item_error_icon(obj_idx, vol_idx);
} }
void ObjectList::simplify()
{
GLGizmosManager& gizmos_mgr = wxGetApp().plater()->canvas3D()->get_gizmos_manager();
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}
void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) const
{ {
const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) : const wxDataViewItem item = vol_idx <0 ? m_objects_model->GetItemById(obj_idx) :
@ -3972,6 +3978,8 @@ void ObjectList::update_item_error_icon(const int obj_idx, const int vol_idx) co
// unmark fixed item only // unmark fixed item only
m_objects_model->DeleteWarningIcon(item); m_objects_model->DeleteWarningIcon(item);
} }
else
m_objects_model->AddWarningIcon(item);
} }
void ObjectList::msw_rescale() void ObjectList::msw_rescale()

View file

@ -357,6 +357,7 @@ public:
void split_instances(); void split_instances();
void rename_item(); void rename_item();
void fix_through_netfabb(); void fix_through_netfabb();
void simplify();
void update_item_error_icon(const int obj_idx, int vol_idx) const ; void update_item_error_icon(const int obj_idx, int vol_idx) const ;
void copy_layers_to_clipboard(); void copy_layers_to_clipboard();

View file

@ -51,6 +51,11 @@ bool GLGizmoMmuSegmentation::on_is_selectable() const
&& wxGetApp().get_mode() != comSimple && wxGetApp().extruders_edited_cnt() > 1); && wxGetApp().get_mode() != comSimple && wxGetApp().extruders_edited_cnt() > 1);
} }
bool GLGizmoMmuSegmentation::on_is_activable() const
{
return GLGizmoPainterBase::on_is_activable() && wxGetApp().extruders_edited_cnt() > 1;
}
static std::vector<std::array<float, 4>> get_extruders_colors() static std::vector<std::array<float, 4>> get_extruders_colors()
{ {
unsigned char rgb_color[3] = {}; unsigned char rgb_color[3] = {};

View file

@ -109,6 +109,7 @@ protected:
std::string on_get_name() const override; std::string on_get_name() const override;
bool on_is_selectable() const override; bool on_is_selectable() const override;
bool on_is_activable() const override;
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override; wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;

View file

@ -520,7 +520,7 @@ bool GLGizmoPainterBase::on_is_activable() const
const Selection& selection = m_parent.get_selection(); const Selection& selection = m_parent.get_selection();
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
|| !selection.is_single_full_instance()) || !selection.is_single_full_instance() || wxGetApp().get_mode() == comSimple)
return false; return false;
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside. // Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.

View file

@ -0,0 +1,322 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoSimplify.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/QuadricEdgeCollapse.hpp"
#include <chrono>
#include <thread>
namespace Slic3r::GUI {
GLGizmoSimplify::GLGizmoSimplify(GLCanvas3D & parent,
const std::string &icon_filename,
unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, -1)
, state(State::settings)
, is_valid_result(false)
, progress(0)
, volume(nullptr)
, obj_index(0)
, need_reload(false)
{}
GLGizmoSimplify::~GLGizmoSimplify() {
state = State::canceling;
if (worker.joinable()) worker.join();
}
bool GLGizmoSimplify::on_init()
{
//m_grabbers.emplace_back();
//m_shortcut_key = WXK_CONTROL_C;
return true;
}
std::string GLGizmoSimplify::on_get_name() const
{
return (_L("Simplify")).ToUTF8().data();
}
void GLGizmoSimplify::on_render() {}
void GLGizmoSimplify::on_render_for_picking() {}
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
{
const int min_triangle_count = 4; // tetrahedron
const int max_char_in_name = 25;
create_gui_cfg();
int flag = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoCollapse;
m_imgui->begin(on_get_name(), flag);
const Selection &selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
ModelObject *obj = wxGetApp().plater()->model().objects[object_idx];
ModelVolume *act_volume = obj->volumes.front();
// Check selection of new volume
// Do not reselect object when processing
if (act_volume != volume && state == State::settings) {
obj_index = object_idx; // to remember correct object
volume = act_volume;
original_its = {};
const TriangleMesh &tm = volume->mesh();
c.wanted_percent = 50.; // default value
c.update_percent(tm.its.indices.size());
is_valid_result = false;
// set window position
ImVec2 pos = ImGui::GetMousePos();
pos.x -= gui_cfg->window_offset;
pos.y -= gui_cfg->window_offset;
ImGui::SetWindowPos(pos, ImGuiCond_Always);
}
size_t triangle_count = volume->mesh().its.indices.size();
// already reduced mesh
if (original_its.has_value())
triangle_count = original_its->indices.size();
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Mesh name") + ":");
ImGui::SameLine(gui_cfg->top_left_width);
std::string name = volume->name;
if (name.length() > max_char_in_name)
name = name.substr(0, max_char_in_name-3) + "...";
m_imgui->text(name);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _L("Triangles") + ":");
ImGui::SameLine(gui_cfg->top_left_width);
m_imgui->text(std::to_string(triangle_count));
ImGui::Separator();
ImGui::Text(_L("Limit by triangles").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
// First initialization + fix triangle count
if (m_imgui->checkbox("##UseCount", c.use_count)) {
if (!c.use_count) c.use_error = true;
is_valid_result = false;
}
m_imgui->disabled_begin(!c.use_count);
ImGui::Text(_L("Triangle count").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
int wanted_count = c.wanted_count;
ImGui::SetNextItemWidth(gui_cfg->input_width);
if (ImGui::SliderInt("##triangle_count", &wanted_count, min_triangle_count, triangle_count, "%d")) {
c.wanted_count = static_cast<uint32_t>(wanted_count);
if (c.wanted_count < min_triangle_count)
c.wanted_count = min_triangle_count;
if (c.wanted_count > triangle_count)
c.wanted_count = triangle_count;
c.update_count(triangle_count);
is_valid_result = false;
}
ImGui::Text(_L("Ratio").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
const char * precision = (c.wanted_percent > 10)? "%.0f": ((c.wanted_percent > 1)? "%.1f":"%.2f");
float step = (c.wanted_percent > 10)? 1.f: ((c.wanted_percent > 1)? 0.1f : 0.01f);
if (ImGui::InputFloat("%", &c.wanted_percent, step, 10*step, precision)) {
if (c.wanted_percent > 100.f) c.wanted_percent = 100.f;
c.update_percent(triangle_count);
if (c.wanted_count < min_triangle_count) {
c.wanted_count = min_triangle_count;
c.update_count(triangle_count);
}
is_valid_result = false;
}
m_imgui->disabled_end(); // use_count
ImGui::NewLine();
ImGui::Text(_L("Limit by error").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
if (m_imgui->checkbox("##UseError", c.use_error)) {
if (!c.use_error) c.use_count = true;
is_valid_result = false;
}
m_imgui->disabled_begin(!c.use_error);
ImGui::Text(_L("Max. error").c_str());
ImGui::SameLine(gui_cfg->bottom_left_width);
ImGui::SetNextItemWidth(gui_cfg->input_small_width);
if (ImGui::InputFloat("##maxError", &c.max_error, 0.01f, .1f, "%.2f")) {
if (c.max_error < 0.f) c.max_error = 0.f;
is_valid_result = false;
}
m_imgui->disabled_end(); // use_error
if (state == State::settings) {
if (m_imgui->button(_L("Cancel"))) {
if (original_its.has_value()) {
set_its(*original_its);
state = State::close_on_end;
} else {
close();
}
}
ImGui::SameLine(gui_cfg->bottom_left_width);
if (m_imgui->button(_L("Preview"))) {
state = State::simplifying;
// simplify but not aply on mesh
process();
}
ImGui::SameLine();
if (m_imgui->button(_L("Apply"))) {
if (!is_valid_result) {
state = State::close_on_end;
process();
} else {
// use preview and close
close();
}
}
} else {
m_imgui->disabled_begin(state == State::canceling);
if (m_imgui->button(_L("Cancel"))) state = State::canceling;
m_imgui->disabled_end();
ImGui::SameLine(gui_cfg->bottom_left_width);
// draw progress bar
char buf[32];
sprintf(buf, L("Process %d / 100"), progress);
ImGui::ProgressBar(progress / 100., ImVec2(gui_cfg->input_width, 0.f), buf);
}
m_imgui->end();
if (need_reload) {
need_reload = false;
// Reload visualization of mesh - change VBO, FBO on GPU
m_parent.reload_scene(true); // deactivate gizmo??
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
if (state == State::close_on_end) {
// fix hollowing, sla support points, modifiers, ...
auto plater = wxGetApp().plater();
plater->changed_mesh(obj_index); // deactivate gizmo??
// changed_mesh cause close();
//close();
}
// change from simplifying | aply
state = State::settings;
// Fix warning icon in object list
wxGetApp().obj_list()->update_item_error_icon(obj_index, -1);
}
}
void GLGizmoSimplify::close() {
volume = nullptr;
// close gizmo == open it again
GLGizmosManager &gizmos_mgr = m_parent.get_gizmos_manager();
gizmos_mgr.open_gizmo(GLGizmosManager::EType::Simplify);
}
void GLGizmoSimplify::process()
{
class SimplifyCanceledException : public std::exception {
public:
const char* what() const throw() { return L("Model simplification has been canceled"); }
};
if (!original_its.has_value())
original_its = volume->mesh().its; // copy
auto plater = wxGetApp().plater();
plater->take_snapshot(_L("Simplify ") + volume->name);
plater->clear_before_change_mesh(obj_index);
progress = 0;
if (worker.joinable()) worker.join();
worker = std::thread([&]() {
// store original triangles
uint32_t triangle_count = (c.use_count) ? c.wanted_count : 0;
float max_error = (c.use_error) ? c.max_error : std::numeric_limits<float>::max();
std::function<void(void)> throw_on_cancel = [&]() {
if (state == State::canceling) {
throw SimplifyCanceledException();
}
};
std::function<void(int)> statusfn = [&](int percent) {
progress = percent;
m_parent.schedule_extra_frame(0);
};
indexed_triangle_set collapsed;
if (last_error.has_value()) {
// is chance to continue with last reduction
const indexed_triangle_set &its = volume->mesh().its;
uint32_t last_triangle_count = static_cast<uint32_t>(its.indices.size());
if ((!c.use_count || triangle_count <= last_triangle_count) &&
(!c.use_error || c.max_error <= *last_error)) {
collapsed = its; // small copy
} else {
collapsed = *original_its; // copy
}
} else {
collapsed = *original_its; // copy
}
try {
its_quadric_edge_collapse(collapsed, triangle_count, &max_error, throw_on_cancel, statusfn);
set_its(collapsed);
is_valid_result = true;
last_error = max_error;
} catch (SimplifyCanceledException &) {
state = State::settings;
}
// need to render last status fn
// without sleep it freezes until mouse move
std::this_thread::sleep_for(std::chrono::milliseconds(50));
m_parent.schedule_extra_frame(0);
});
}
void GLGizmoSimplify::set_its(indexed_triangle_set &its) {
auto tm = std::make_unique<TriangleMesh>(its);
tm->repair();
volume->set_mesh(std::move(tm));
volume->set_new_unique_id();
volume->get_object()->invalidate_bounding_box();
need_reload = true;
}
bool GLGizmoSimplify::on_is_activable() const
{
return !m_parent.get_selection().is_empty();
}
void GLGizmoSimplify::create_gui_cfg() {
if (gui_cfg.has_value()) return;
int space_size = m_imgui->calc_text_size(":MM").x;
GuiCfg cfg;
cfg.top_left_width = std::max(m_imgui->calc_text_size(_L("Mesh name")).x,
m_imgui->calc_text_size(_L("Triangles")).x)
+ space_size;
cfg.bottom_left_width =
std::max(
std::max(m_imgui->calc_text_size(_L("Limit by triangles")).x,
std::max(m_imgui->calc_text_size(_L("Triangle count")).x,
m_imgui->calc_text_size(_L("Ratio")).x)),
std::max(m_imgui->calc_text_size(_L("Limit by error")).x,
m_imgui->calc_text_size(_L("Max. error")).x)) + space_size;
cfg.input_width = cfg.bottom_left_width;
cfg.input_small_width = cfg.input_width * 0.8;
cfg.window_offset = cfg.input_width;
gui_cfg = cfg;
}
} // namespace Slic3r::GUI

View file

@ -0,0 +1,89 @@
#ifndef slic3r_GLGizmoSimplify_hpp_
#define slic3r_GLGizmoSimplify_hpp_
#include "GLGizmoBase.hpp"
#include "libslic3r/Model.hpp"
#include <thread>
#include <optional>
namespace Slic3r {
namespace GUI {
class GLGizmoSimplify : public GLGizmoBase
{
enum class State {
settings,
simplifying, // start processing
canceling, // canceled
successfull, // successful simplified
close_on_end
} state;
bool is_valid_result; // differ what to do in apply
int progress;
ModelVolume *volume;
size_t obj_index;
std::optional<indexed_triangle_set> original_its;
std::optional<float> last_error; // for use previous reduction
bool need_reload; // after simplify, glReload must be on main thread
std::thread worker;
public:
GLGizmoSimplify(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
virtual ~GLGizmoSimplify();
protected:
virtual bool on_init() override;
virtual std::string on_get_name() const override;
virtual void on_render() override;
virtual void on_render_for_picking() override;
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
virtual bool on_is_activable() const override;
virtual bool on_is_selectable() const override { return false; }
private:
void close();
void process();
void set_its(indexed_triangle_set &its);
struct Configuration
{
bool use_count = true;
// minimal triangle count
float wanted_percent = 50.f;
uint32_t wanted_count = 0; // initialize by percents
bool use_error = false;
// maximal quadric error
float max_error = 1.;
void update_count(size_t triangle_count)
{
wanted_percent = (float) wanted_count / triangle_count * 100.f;
}
void update_percent(size_t triangle_count)
{
wanted_count = static_cast<uint32_t>(
std::round(triangle_count * wanted_percent / 100.f));
}
} c;
// This configs holds GUI layout size given by translated texts.
// etc. When language changes, GUI is recreated and this class constructed again,
// so the change takes effect. (info by GLGizmoFdmSupports.hpp)
struct GuiCfg
{
int top_left_width = 100;
int bottom_left_width = 100;
int input_width = 100;
int input_small_width = 80;
int window_offset = 100;
};
std::optional<GuiCfg> gui_cfg;
void create_gui_cfg();
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoSimplify_hpp_

View file

@ -20,6 +20,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
@ -50,14 +51,7 @@ std::vector<size_t> GLGizmosManager::get_selectable_idxs() const
return out; return out;
} }
std::vector<size_t> GLGizmosManager::get_activable_idxs() const
{
std::vector<size_t> out;
for (size_t i=0; i<m_gizmos.size(); ++i)
if (m_gizmos[i]->is_activable())
out.push_back(i);
return out;
}
size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const size_t GLGizmosManager::get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const
{ {
@ -110,6 +104,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7)); m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8)); m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "fdm_supports.svg", 9)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "fdm_supports.svg", 9));
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent)); m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
@ -169,7 +164,7 @@ void GLGizmosManager::refresh_on_off_state()
return; return;
if (m_current != Undefined if (m_current != Undefined
&& (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) { && ! m_gizmos[m_current]->is_activable()) {
activate_gizmo(Undefined); activate_gizmo(Undefined);
update_data(); update_data();
} }
@ -187,7 +182,7 @@ void GLGizmosManager::reset_all_states()
bool GLGizmosManager::open_gizmo(EType type) bool GLGizmosManager::open_gizmo(EType type)
{ {
int idx = int(type); int idx = int(type);
if (m_gizmos[idx]->is_selectable() && m_gizmos[idx]->is_activable()) { if (m_gizmos[idx]->is_activable()) {
activate_gizmo(m_current == idx ? Undefined : (EType)idx); activate_gizmo(m_current == idx ? Undefined : (EType)idx);
update_data(); update_data();
return true; return true;
@ -304,7 +299,7 @@ bool GLGizmosManager::handle_shortcut(int key)
auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(), auto it = std::find_if(m_gizmos.begin(), m_gizmos.end(),
[key](const std::unique_ptr<GLGizmoBase>& gizmo) { [key](const std::unique_ptr<GLGizmoBase>& gizmo) {
int gizmo_key = gizmo->get_shortcut_key(); int gizmo_key = gizmo->get_shortcut_key();
return gizmo->is_selectable() return gizmo->is_activable()
&& ((gizmo_key == key - 64) || (gizmo_key == key - 96)); && ((gizmo_key == key - 64) || (gizmo_key == key - 96));
}); });
@ -1077,6 +1072,7 @@ void GLGizmosManager::do_render_overlay() const
float u_offset = 1.0f / (float)tex_width; float u_offset = 1.0f / (float)tex_width;
float v_offset = 1.0f / (float)tex_height; float v_offset = 1.0f / (float)tex_height;
float current_y = FLT_MAX;
for (size_t idx : selectable_idxs) for (size_t idx : selectable_idxs)
{ {
GLGizmoBase* gizmo = m_gizmos[idx].get(); GLGizmoBase* gizmo = m_gizmos[idx].get();
@ -1090,12 +1086,18 @@ void GLGizmosManager::do_render_overlay() const
float u_right = u_left + du - u_offset; float u_right = u_left + du - u_offset;
GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } });
if (idx == m_current) { if (idx == m_current || current_y == FLT_MAX) {
float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height(); // The FLT_MAX trick is here so that even non-selectable but activable
gizmo->render_input_window(width, 0.5f * cnv_h - zoomed_top_y * zoom, toolbar_top); // gizmos are passed some meaningful value.
current_y = 0.5f * cnv_h - zoomed_top_y * zoom;
} }
zoomed_top_y -= zoomed_stride_y; zoomed_top_y -= zoomed_stride_y;
} }
if (m_current != Undefined) {
float toolbar_top = cnv_h - wxGetApp().plater()->get_view_toolbar().get_height();
m_gizmos[m_current]->render_input_window(width, current_y, toolbar_top);
}
} }
float GLGizmosManager::get_scaled_total_height() const float GLGizmosManager::get_scaled_total_height() const

View file

@ -69,6 +69,7 @@ public:
FdmSupports, FdmSupports,
Seam, Seam,
MmuSegmentation, MmuSegmentation,
Simplify,
Undefined Undefined
}; };
@ -101,7 +102,6 @@ private:
std::pair<EType, bool> m_highlight; // bool true = higlightedShown, false = highlightedHidden std::pair<EType, bool> m_highlight; // bool true = higlightedShown, false = highlightedHidden
std::vector<size_t> get_selectable_idxs() const; std::vector<size_t> get_selectable_idxs() const;
std::vector<size_t> get_activable_idxs() const;
size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const; size_t get_gizmo_idx_from_mouse(const Vec2d& mouse_pos) const;
void activate_gizmo(EType type); void activate_gizmo(EType type);

View file

@ -1697,6 +1697,24 @@ wxBitmap ObjectDataViewModel::GetVolumeIcon(const Slic3r::ModelVolumeType vol_ty
return *bmp; return *bmp;
} }
void ObjectDataViewModel::AddWarningIcon(const wxDataViewItem& item)
{
if (!item.IsOk())
return;
ObjectDataViewModelNode *node = static_cast<ObjectDataViewModelNode*>(item.GetID());
if (node->GetType() & itObject) {
node->SetBitmap(m_warning_bmp);
return;
}
if (node->GetType() & itVolume) {
node->SetBitmap(GetVolumeIcon(node->GetVolumeType(), true));
node->GetParent()->SetBitmap(m_warning_bmp);
return;
}
}
void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/) void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object/* = false*/)
{ {
if (!item.IsOk()) if (!item.IsOk())

View file

@ -376,6 +376,7 @@ public:
wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type, wxBitmap GetVolumeIcon(const Slic3r::ModelVolumeType vol_type,
const bool is_marked = false); const bool is_marked = false);
void AddWarningIcon(const wxDataViewItem& item);
void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false); void DeleteWarningIcon(const wxDataViewItem& item, const bool unmark_object = false);
t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const; t_layer_height_range GetLayerRangeByItem(const wxDataViewItem& item) const;

View file

@ -1771,6 +1771,7 @@ struct Plater::priv
bool can_arrange() const; bool can_arrange() const;
bool can_layers_editing() const; bool can_layers_editing() const;
bool can_fix_through_netfabb() const; bool can_fix_through_netfabb() const;
bool can_simplify() const;
bool can_set_instance_to_object() const; bool can_set_instance_to_object() const;
bool can_mirror() const; bool can_mirror() const;
bool can_reload_from_disk() const; bool can_reload_from_disk() const;
@ -3495,44 +3496,10 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
// size_t snapshot_time = undo_redo_stack().active_snapshot_time(); // size_t snapshot_time = undo_redo_stack().active_snapshot_time();
Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb")); Plater::TakeSnapshot snapshot(q, _L("Fix through NetFabb"));
q->clear_before_change_mesh(obj_idx);
ModelObject* mo = model.objects[obj_idx]; ModelObject* mo = model.objects[obj_idx];
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
// may be different and they would make no sense.
bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
mv->supported_facets.clear();
mv->seam_facets.clear();
mv->mmu_segmentation_facets.clear();
}
if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
notification_manager->push_notification(
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
NotificationManager::NotificationLevel::RegularNotification,
_u8L("Custom supports and seams were removed after repairing the mesh."));
// _u8L("Undo the repair"),
// [this, snapshot_time](wxEvtHandler*){
// // Make sure the snapshot is still available and that
// // we are in the main stack and not in a gizmo-stack.
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
// undo_redo_to(snapshot_time);
// else
// notification_manager->push_notification(
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
// NotificationManager::NotificationLevel::RegularNotification,
// _u8L("Cannot undo to before the mesh repair!"));
// return true;
// });
}
fix_model_by_win10_sdk_gui(*mo, vol_idx); fix_model_by_win10_sdk_gui(*mo, vol_idx);
sla::reproject_points_and_holes(mo); q->changed_mesh(obj_idx);
this->update();
this->object_list_changed();
this->schedule_background_process();
} }
void Plater::priv::set_current_panel(wxPanel* panel) void Plater::priv::set_current_panel(wxPanel* panel)
@ -4304,6 +4271,12 @@ bool Plater::priv::can_fix_through_netfabb() const
return model.objects[obj_idx]->get_mesh_errors_count() > 0; return model.objects[obj_idx]->get_mesh_errors_count() > 0;
} }
bool Plater::priv::can_simplify() const
{
return true;
}
bool Plater::priv::can_increase_instances() const bool Plater::priv::can_increase_instances() const
{ {
if (m_ui_jobs.is_any_running() if (m_ui_jobs.is_any_running()
@ -6135,6 +6108,51 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology)
return ret; return ret;
} }
void Plater::clear_before_change_mesh(int obj_idx)
{
ModelObject* mo = model().objects[obj_idx];
// If there are custom supports/seams/mmu segmentation, remove them. Fixed mesh
// may be different and they would make no sense.
bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
mv->supported_facets.clear();
mv->seam_facets.clear();
mv->mmu_segmentation_facets.clear();
}
if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.
get_notification_manager()->push_notification(
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
NotificationManager::NotificationLevel::RegularNotification,
_u8L("Custom supports and seams were removed after repairing the mesh."));
// _u8L("Undo the repair"),
// [this, snapshot_time](wxEvtHandler*){
// // Make sure the snapshot is still available and that
// // we are in the main stack and not in a gizmo-stack.
// if (undo_redo_stack().has_undo_snapshot(snapshot_time)
// && q->canvas3D()->get_gizmos_manager().get_current() == nullptr)
// undo_redo_to(snapshot_time);
// else
// notification_manager->push_notification(
// NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
// NotificationManager::NotificationLevel::RegularNotification,
// _u8L("Cannot undo to before the mesh repair!"));
// return true;
// });
}
}
void Plater::changed_mesh(int obj_idx)
{
ModelObject* mo = model().objects[obj_idx];
sla::reproject_points_and_holes(mo);
update();
p->object_list_changed();
p->schedule_background_process();
}
void Plater::changed_object(int obj_idx) void Plater::changed_object(int obj_idx)
{ {
if (obj_idx < 0) if (obj_idx < 0)
@ -6402,6 +6420,7 @@ bool Plater::can_increase_instances() const { return p->can_increase_instances()
bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); } bool Plater::can_decrease_instances() const { return p->can_decrease_instances(); }
bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); } bool Plater::can_set_instance_to_object() const { return p->can_set_instance_to_object(); }
bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); } bool Plater::can_fix_through_netfabb() const { return p->can_fix_through_netfabb(); }
bool Plater::can_simplify() const { return p->can_simplify(); }
bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); } bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); } bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
bool Plater::can_arrange() const { return p->can_arrange(); } bool Plater::can_arrange() const { return p->can_arrange(); }

View file

@ -226,6 +226,10 @@ public:
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false); void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
void reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages = false); void reslice_SLA_hollowing(const ModelObject &object, bool postpone_error_messages = false);
void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false); void reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &object, bool postpone_error_messages = false);
void clear_before_change_mesh(int obj_idx);
void changed_mesh(int obj_idx);
void changed_object(int obj_idx); void changed_object(int obj_idx);
void changed_objects(const std::vector<size_t>& object_idxs); void changed_objects(const std::vector<size_t>& object_idxs);
void schedule_background_process(bool schedule = true); void schedule_background_process(bool schedule = true);
@ -306,6 +310,7 @@ public:
bool can_decrease_instances() const; bool can_decrease_instances() const;
bool can_set_instance_to_object() const; bool can_set_instance_to_object() const;
bool can_fix_through_netfabb() const; bool can_fix_through_netfabb() const;
bool can_simplify() const;
bool can_split_to_objects() const; bool can_split_to_objects() const;
bool can_split_to_volumes() const; bool can_split_to_volumes() const;
bool can_arrange() const; bool can_arrange() const;

View file

@ -4,6 +4,8 @@
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
using namespace Slic3r;
TEST_CASE("Split empty mesh", "[its_split][its]") { TEST_CASE("Split empty mesh", "[its_split][its]") {
using namespace Slic3r; using namespace Slic3r;
@ -100,3 +102,148 @@ TEST_CASE("Split two watertight meshes", "[its_split][its]") {
debug_write_obj(res, "parts_watertight"); debug_write_obj(res, "parts_watertight");
} }
#include <libslic3r/QuadricEdgeCollapse.hpp>
static float triangle_area(const Vec3f &v0, const Vec3f &v1, const Vec3f &v2)
{
Vec3f ab = v1 - v0;
Vec3f ac = v2 - v0;
return ab.cross(ac).norm() / 2.f;
}
static float triangle_area(const Vec3crd &triangle_inices, const std::vector<Vec3f> &vertices)
{
return triangle_area(vertices[triangle_inices[0]],
vertices[triangle_inices[1]],
vertices[triangle_inices[2]]);
}
static std::mt19937 create_random_generator() {
std::random_device rd;
std::mt19937 gen(rd());
return gen;
}
std::vector<Vec3f> its_sample_surface(const indexed_triangle_set &its,
double sample_per_mm2,
std::mt19937 random_generator = create_random_generator())
{
std::vector<Vec3f> samples;
std::uniform_real_distribution<float> rand01(0.f, 1.f);
for (const auto &triangle_indices : its.indices) {
float area = triangle_area(triangle_indices, its.vertices);
float countf;
float fractional = std::modf(area * sample_per_mm2, &countf);
int count = static_cast<int>(countf);
float generate = rand01(random_generator);
if (generate < fractional) ++count;
if (count == 0) continue;
const Vec3f &v0 = its.vertices[triangle_indices[0]];
const Vec3f &v1 = its.vertices[triangle_indices[1]];
const Vec3f &v2 = its.vertices[triangle_indices[2]];
for (int c = 0; c < count; c++) {
// barycentric coordinate
Vec3f b;
b[0] = rand01(random_generator);
b[1] = rand01(random_generator);
if ((b[0] + b[1]) > 1.f) {
b[0] = 1.f - b[0];
b[1] = 1.f - b[1];
}
b[2] = 1.f - b[0] - b[1];
Vec3f pos;
for (int i = 0; i < 3; i++) {
pos[i] = b[0] * v0[i] + b[1] * v1[i] + b[2] * v2[i];
}
samples.push_back(pos);
}
}
return samples;
}
#include "libslic3r/AABBTreeIndirect.hpp"
// return Average abs distance to original
float compare(const indexed_triangle_set &original,
const indexed_triangle_set &simplified,
double sample_per_mm2)
{
// create ABBTree
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
original.vertices, original.indices);
unsigned int init = 0;
std::mt19937 rnd(init);
auto samples = its_sample_surface(simplified, sample_per_mm2, rnd);
float sumDistance = 0;
for (const Vec3f &sample : samples) {
size_t hit_idx;
Vec3f hit_point;
float distance2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
original.vertices, original.indices, tree, sample, hit_idx,
hit_point);
sumDistance += sqrt(distance2);
}
return sumDistance / samples.size();
}
TEST_CASE("Reduce one edge by Quadric Edge Collapse", "[its]")
{
indexed_triangle_set its;
its.vertices = {Vec3f(-1.f, 0.f, 0.f), Vec3f(0.f, 1.f, 0.f),
Vec3f(1.f, 0.f, 0.f), Vec3f(0.f, 0.f, 1.f),
// vertex to be removed
Vec3f(0.9f, .1f, -.1f)};
its.indices = {Vec3i(1, 0, 3), Vec3i(2, 1, 3), Vec3i(0, 2, 3),
Vec3i(0, 1, 4), Vec3i(1, 2, 4), Vec3i(2, 0, 4)};
// edge to remove is between vertices 2 and 4 on trinagles 4 and 5
indexed_triangle_set its_ = its; // copy
// its_write_obj(its, "tetrhedron_in.obj");
uint32_t wanted_count = its.indices.size() - 1;
its_quadric_edge_collapse(its, wanted_count);
// its_write_obj(its, "tetrhedron_out.obj");
CHECK(its.indices.size() == 4);
CHECK(its.vertices.size() == 4);
for (size_t i = 0; i < 3; i++) {
CHECK(its.indices[i] == its_.indices[i]);
}
for (size_t i = 0; i < 4; i++) {
if (i == 2) continue;
CHECK(its.vertices[i] == its_.vertices[i]);
}
const Vec3f &v = its.vertices[2]; // new vertex
const Vec3f &v2 = its_.vertices[2]; // moved vertex
const Vec3f &v4 = its_.vertices[4]; // removed vertex
for (size_t i = 0; i < 3; i++) {
bool is_between = (v[i] < v4[i] && v[i] > v2[i]) ||
(v[i] > v4[i] && v[i] < v2[i]);
CHECK(is_between);
}
float avg_distance = compare(its_, its, 10);
CHECK(avg_distance < 8e-3f);
}
#include "test_utils.hpp"
TEST_CASE("Simplify mesh by Quadric edge collapse to 5%", "[its]")
{
TriangleMesh mesh = load_model("frog_legs.obj");
double original_volume = its_volume(mesh.its);
uint32_t wanted_count = mesh.its.indices.size() * 0.05;
REQUIRE_FALSE(mesh.empty());
indexed_triangle_set its = mesh.its; // copy
float max_error = std::numeric_limits<float>::max();
its_quadric_edge_collapse(its, wanted_count, &max_error);
//its_write_obj(its, "frog_legs_qec.obj");
CHECK(its.indices.size() <= wanted_count);
double volume = its_volume(its);
CHECK(fabs(original_volume - volume) < 33.);
float avg_distance = compare(mesh.its, its, 10);
CHECK(avg_distance < 0.022f); // 0.02022 | 0.0199614074
}

View file

@ -339,3 +339,104 @@ TEST_CASE("Mutable priority queue - reshedule first", "[MutableSkipHeapPriorityQ
} }
} }
} }
TEST_CASE("Mutable priority queue - first pop", "[MutableSkipHeapPriorityQueue]")
{
struct MyValue{
int id;
float val;
};
size_t count = 50000;
std::vector<size_t> idxs(count, {0});
std::vector<bool> dels(count, false);
auto q = make_miniheap_mutable_priority_queue<MyValue, 16, true>(
[&](MyValue &v, size_t idx) {
idxs[v.id] = idx;
},
[](MyValue &l, MyValue &r) { return l.val < r.val; });
q.reserve(count);
for (size_t id = 0; id < count; id++) {
MyValue mv;
mv.id = id;
mv.val = rand();
q.push(mv);
}
MyValue it = q.top(); // copy
q.pop();
bool valid = (it.id != 0) && (idxs[0] < 3 * count);
CHECK(valid);
}
TEST_CASE("Mutable priority queue complex", "[MutableSkipHeapPriorityQueue]")
{
struct MyValue {
size_t id;
float val;
};
size_t count = 5000;
std::vector<size_t> idxs(count, {0});
std::vector<bool> dels(count, false);
auto q = make_miniheap_mutable_priority_queue<MyValue, 16, true>(
[&](MyValue &v, size_t idx) { idxs[v.id] = idx; },
[](MyValue &l, MyValue &r) { return l.val < r.val; });
q.reserve(count);
auto rand_val = [&]()->float { return (rand() % 53) / 10.f; };
size_t ord = 0;
for (size_t id = 0; id < count; id++) {
MyValue mv;
mv.id = ord++;
mv.val = rand_val();
q.push(mv);
}
auto check = [&]()->bool{
for (size_t i = 0; i < idxs.size(); ++i) {
if (dels[i]) continue;
size_t qid = idxs[i];
if (qid > 3*count) {
return false;
}
MyValue &mv = q[qid];
if (mv.id != i) {
return false; // ERROR
}
}
return true;
};
CHECK(check()); // initial check
auto get_valid_id = [&]()->int {
int id = 0;
do {
id = rand() % count;
} while (dels[id]);
return id;
};
for (size_t i = 0; i < 100; i++) {
MyValue it = q.top(); // copy
q.pop();
dels[it.id] = true;
CHECK(check());
if (i % 20 == 0) {
it.val = rand_val();
q.push(it);
dels[it.id] = false;
CHECK(check());
continue;
}
int id = get_valid_id();
q.remove(idxs[id]);
dels[id] = true;
CHECK(check());
for (size_t j = 0; j < 5; j++) {
int id = get_valid_id();
size_t qid = idxs[id];
MyValue &mv = q[qid];
mv.val = rand_val();
q.update(qid);
CHECK(check());
}
}
}