From 00e9f07a03042de25438ec5922618dbb87f86be6 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 8 Aug 2018 16:24:10 +0200 Subject: [PATCH 1/4] Improved robustness of slicing when the slicing plane crosses a horizontal plane of an object exactly. Should improve Github issues #831, #895, #1102 --- xs/src/libslic3r/Fill/FillHoneycomb.cpp | 4 +- xs/src/libslic3r/Model.cpp | 1 + xs/src/libslic3r/Polyline.hpp | 2 + xs/src/libslic3r/TriangleMesh.cpp | 240 ++++++++++++++++-------- xs/src/libslic3r/TriangleMesh.hpp | 27 ++- 5 files changed, 188 insertions(+), 86 deletions(-) diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index aa0e0f6b0..aa52856ae 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -86,8 +86,8 @@ void FillHoneycomb::_fill_surface_single( Polylines paths; { Polylines p; - for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) - p.push_back((Polyline)(*it)); + for (Polygon &poly : polygons) + p.emplace_back(poly.points); paths = intersection_pl(p, to_polygons(expolygon)); } diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index bceeea258..f08736f4e 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -613,6 +613,7 @@ const BoundingBoxf3& ModelObject::bounding_box() const BoundingBoxf3 raw_bbox; for (const ModelVolume *v : this->volumes) if (! v->modifier) + // mesh.bounding_box() returns a cached value. raw_bbox.merge(v->mesh.bounding_box()); BoundingBoxf3 bb; for (const ModelInstance *i : this->instances) diff --git a/xs/src/libslic3r/Polyline.hpp b/xs/src/libslic3r/Polyline.hpp index b64743d84..123ca5d2c 100644 --- a/xs/src/libslic3r/Polyline.hpp +++ b/xs/src/libslic3r/Polyline.hpp @@ -19,6 +19,8 @@ public: Polyline() {}; Polyline(const Polyline &other) : MultiPoint(other.points) {} Polyline(Polyline &&other) : MultiPoint(std::move(other.points)) {} + explicit Polyline(const Points &points) : MultiPoint(points) {} + explicit Polyline(Points &&points) : MultiPoint(std::move(points)) {} Polyline& operator=(const Polyline &other) { points = other.points; return *this; } Polyline& operator=(Polyline &&other) { points = std::move(other.points); return *this; } static Polyline new_scale(std::vector points) { diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 45e4b6f5d..b83e0dae6 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -696,8 +696,7 @@ TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : } } -void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const +void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; @@ -800,35 +799,37 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); IntersectionLine il; - if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il)) { + if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { boost::lock_guard l(*lines_mutex); if (il.edge_type == feHorizontal) { - // Insert all three edges of the face. - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; - const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; - for (int j = 0; j < 3; ++ j) { - int a_id = vertices[j % 3]; - int b_id = vertices[(j+1) % 3]; - if (reverse) - std::swap(a_id, b_id); - const stl_vertex *a = &this->v_scaled_shared[a_id]; - const stl_vertex *b = &this->v_scaled_shared[b_id]; - il.a.x = a->x; - il.a.y = a->y; - il.b.x = b->x; - il.b.y = b->y; - il.a_id = a_id; - il.b_id = b_id; - (*lines)[layer_idx].push_back(il); - } + // Insert all marked edges of the face. The marked edges do not share an edge with another horizontal face + // (they may not have a nighbor, or their neighbor is vertical) + const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; + const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; + uint32_t edge_mask = IntersectionLine::EDGE0; + for (int j = 0; j < 3; ++ j, edge_mask <<= 1) + if (il.flags & edge_mask) { + int a_id = vertices[j % 3]; + int b_id = vertices[(j+1) % 3]; + if (reverse) + std::swap(a_id, b_id); + const stl_vertex *a = &this->v_scaled_shared[a_id]; + const stl_vertex *b = &this->v_scaled_shared[b_id]; + il.a.x = a->x; + il.a.y = a->y; + il.b.x = b->x; + il.b.y = b->y; + il.a_id = a_id; + il.b_id = b_id; + (*lines)[layer_idx].emplace_back(il); + } } else - (*lines)[layer_idx].push_back(il); + (*lines)[layer_idx].emplace_back(il); } } } -void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const +void TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { std::vector layers_p; this->slice(z, &layers_p); @@ -848,8 +849,18 @@ TriangleMeshSlicer::slice(const std::vector &z, std::vector* BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; } +static inline float cross_product(const stl_vertex *a, const stl_vertex *b, const stl_vertex *c) +{ + float v1_x = b->x - a->x; + float v1_y = b->y - a->y; + float v2_x = c->x - a->x; + float v2_y = c->y - a->y; + float dir = (b->x - a->x) * (c->y - a->y) - (b->y - a->y) * (c->x - a->x); + return dir; +} + // Return true, if the facet has been sliced and line_out has been filled. -bool TriangleMeshSlicer::slice_facet( +TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const @@ -877,22 +888,61 @@ bool TriangleMeshSlicer::slice_facet( const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; + // We may ignore this edge for slicing purposes, but we may still use it for object cutting. + FacetSliceType result = Slicing; + const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; + // Mark neighbor edges, which do not have a neighbor. + uint32_t edges = 0; + uint32_t mask = IntersectionLine::EDGE0; + for (int nbr_idx = 2; nbr_idx != 5; ++ nbr_idx, mask <<= 1) + // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no + // opposite face, add it to the edges to process when slicing. + if (nbr.neighbor[nbr_idx % 3] == -1) + // Mark this edge. + edges |= mask; + // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. + result = (edges == 0) ? Cutting : Slicing; + line_out->flags |= edges; if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); std::swap(a_id, b_id); } - } else if (v0.z < slice_z || v1.z < slice_z || v2.z < slice_z) { - // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. - line_out->edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); } else { - // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. - line_out->edge_type = feBottom; + // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. + int nbr_idx = (j + 2) % 3; + int nbr_face = nbr.neighbor[nbr_idx]; + // Is the third vertex below the cutting plane? + bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z; + // Is this a concave corner? + if (nbr_face == -1) { +#ifdef _DEBUG + printf("Face has no neighbor!\n"); +#endif + } else { + int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; + const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; + if (c->z > slice_z) { + // If an edge resides on a cutting plane, and none of the two triangles are coplanar with the cutting plane, + // igore the lower triangle. + if (third_below) + result = Cutting; + } else if (c->z == slice_z) { + // A vertical face shares edge with a horizontal face. Verify, whether the shared corner is convex or concave. + float dir = cross_product(a, b, c); + if (third_below ? (dir < 0.) : (dir > 0.)) + result = Cutting; + } + } + if (third_below) { + line_out->edge_type = feTop; + std::swap(a, b); + std::swap(a_id, b_id); + } else + line_out->edge_type = feBottom; } line_out->a.x = a->x; line_out->a.y = a->y; @@ -900,7 +950,7 @@ bool TriangleMeshSlicer::slice_facet( line_out->b.y = b->y; line_out->a_id = a_id; line_out->b_id = b_id; - return true; + return result; } if (a->z == slice_z) { @@ -935,7 +985,7 @@ bool TriangleMeshSlicer::slice_facet( assert(num_points == 2 || num_points == 3); if (num_points < 3) // This triangle touches the cutting plane with a single vertex. Ignore it. - return false; + return NoSlice; // Erase one of the duplicate points. -- num_points; for (int i = points_on_layer[1]; i < num_points; ++ i) @@ -945,52 +995,86 @@ bool TriangleMeshSlicer::slice_facet( // Facets must intersect each plane 0 or 2 times. assert(num_points == 0 || num_points == 2); if (num_points == 2) { - line_out->edge_type = feNone; + line_out->edge_type = feGeneral; line_out->a = (Point)points[1]; line_out->b = (Point)points[0]; line_out->a_id = points[1].point_id; line_out->b_id = points[0].point_id; line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; - return true; + // General slicing position, use the segment for both slicing and object cutting. + return Slicing; + } + return NoSlice; +} + +//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing +// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. +// So the following code makes only sense now to handle degenerate meshes with more than two faces +// sharing a single edge. +static inline void remove_tangent_edges(std::vector &lines) +{ + std::vector by_vertex_pair; + by_vertex_pair.reserve(lines.size()); + for (IntersectionLine& line : lines) + if (line.edge_type != feGeneral && line.a_id != -1) + // This is a face edge. Check whether there is its neighbor stored in lines. + by_vertex_pair.emplace_back(&line); + auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { + // Sort vertices of l1, l2 lexicographically + int l1a = l1->a_id; + int l1b = l1->b_id; + int l2a = l2->a_id; + int l2b = l2->b_id; + if (l1a > l1b) + std::swap(l1a, l1b); + if (l2a > l2b) + std::swap(l2a, l2b); + // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. + return l1a < l2a || (l1a == l2a && l1b < l2b); + }; + std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); + for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { + IntersectionLine &l1 = **line; + if (! l1.skip()) { + // Iterate as long as line and line2 edges share the same end points. + for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { + // Lines must share the end points. + assert(! edges_lower_sorted(*line, *line2)); + assert(! edges_lower_sorted(*line2, *line)); + IntersectionLine &l2 = **line2; + if (l2.skip()) + continue; + if (l1.a_id == l2.a_id) { + assert(l1.b_id == l2.b_id); + l2.set_skip(); + // If they are both oriented upwards or downwards (like a 'V'), + // then we can remove both edges from this layer since it won't + // affect the sliced shape. + // If one of them is oriented upwards and the other is oriented + // downwards, let's only keep one of them (it doesn't matter which + // one since all 'top' lines were reversed at slicing). + if (l1.edge_type == l2.edge_type) { + l1.set_skip(); + break; + } + } else { + assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); + // If this edge joins two horizontal facets, remove both of them. + if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) { + l1.set_skip(); + l2.set_skip(); + break; + } + } + } + } } - return false; } void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { - // Remove tangent edges. - //FIXME This is O(n^2) in rare cases when many faces intersect the cutting plane. - for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++ line) - if (! line->skip && line->edge_type != feNone) { - // This line is af facet edge. There may be a duplicate line with the same end vertices. - // If the line is is an edge connecting two facets, find another facet edge - // having the same endpoints but in reverse order. - for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++ line2) - if (! line2->skip && line2->edge_type != feNone) { - // Are these facets adjacent? (sharing a common edge on this layer) - if (line->a_id == line2->a_id && line->b_id == line2->b_id) { - line2->skip = true; - /* if they are both oriented upwards or downwards (like a 'V') - then we can remove both edges from this layer since it won't - affect the sliced shape */ - /* if one of them is oriented upwards and the other is oriented - downwards, let's only keep one of them (it doesn't matter which - one since all 'top' lines were reversed at slicing) */ - if (line->edge_type == line2->edge_type) { - line->skip = true; - break; - } - } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { - /* if this edge joins two horizontal facets, remove both of them */ - if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { - line->skip = true; - line2->skip = true; - break; - } - } - } - } + remove_tangent_edges(lines); struct OpenPolyline { OpenPolyline() {}; @@ -1013,7 +1097,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo by_edge_a_id.reserve(lines.size()); by_a_id.reserve(lines.size()); for (IntersectionLine &line : lines) { - if (! line.skip) { + if (! line.skip()) { if (line.edge_a_id != -1) by_edge_a_id.emplace_back(&line); if (line.a_id != -1) @@ -1030,13 +1114,13 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // take first spare line and start a new loop IntersectionLine *first_line = nullptr; for (; it_line_seed != lines.end(); ++ it_line_seed) - if (! it_line_seed->skip) { + if (! it_line_seed->skip()) { first_line = &(*it_line_seed ++); break; } if (first_line == nullptr) break; - first_line->skip = true; + first_line->set_skip(); Points loop_pts; loop_pts.emplace_back(first_line->a); IntersectionLine *last_line = first_line; @@ -1057,7 +1141,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (it_begin != by_edge_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip) { + if (! (*it_line)->skip()) { next_line = *it_line; break; } @@ -1069,7 +1153,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if (it_begin != by_a_id.end()) { auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip) { + if (! (*it_line)->skip()) { next_line = *it_line; break; } @@ -1100,7 +1184,7 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo */ loop_pts.emplace_back(next_line->a); last_line = next_line; - next_line->skip = true; + next_line->set_skip(); } } } @@ -1192,8 +1276,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo if ((ip1.edge_id != -1 && ip1.edge_id == ip2.edge_id) || (ip1.point_id != -1 && ip1.point_id == ip2.point_id)) { // The current loop is complete. Add it to the output. - assert(opl.points.front().point_id == opl.points.back().point_id); - assert(opl.points.front().edge_id == opl.points.back().edge_id); + //assert(opl.points.front().point_id == opl.points.back().point_id); + //assert(opl.points.front().edge_id == opl.points.back().edge_id); // Remove the duplicate last point. opl.points.pop_back(); if (opl.points.size() >= 3) { @@ -1393,7 +1477,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) // intersect facet with cutting plane IntersectionLine line; - if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line)) { + if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) { // Save intersection lines for generating correct triangulations. if (line.edge_type == feTop) { lower_lines.push_back(line); diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index c700784a5..3714f5e9e 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -76,7 +76,7 @@ private: enum FacetEdgeType { // A general case, the cutting plane intersect a face at two different edges. - feNone, + feGeneral, // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. feTop, // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. @@ -110,6 +110,11 @@ public: class IntersectionLine : public Line { public: + IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {} + + bool skip() const { return (this->flags & SKIP) != 0; } + void set_skip() { this->flags |= SKIP; } + // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. // Vertex indices of the line end points. @@ -118,11 +123,16 @@ public: // Source mesh edges of the line end points. int edge_a_id; int edge_b_id; - // feNone, feTop, feBottom, feHorizontal + // feGeneral, feTop, feBottom, feHorizontal FacetEdgeType edge_type; - // Used by TriangleMeshSlicer::make_loops() to skip duplicate edges. - bool skip; - IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feNone), skip(false) {}; + // Used by TriangleMeshSlicer::slice() to skip duplicate edges. + enum { + EDGE0 = 1, + EDGE1 = 2, + EDGE2 = 4, + SKIP = 8, + }; + uint32_t flags; }; typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; @@ -133,7 +143,12 @@ public: TriangleMeshSlicer(TriangleMesh* _mesh); void slice(const std::vector &z, std::vector* layers) const; void slice(const std::vector &z, std::vector* layers) const; - bool slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, + enum FacetSliceType { + NoSlice = 0, + Slicing = 1, + Cutting = 2 + }; + FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, const float min_z, const float max_z, IntersectionLine *line_out) const; void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; From 0ea45576327fc974655dccc2234e269f6ac60bb8 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Thu, 9 Aug 2018 21:15:49 +0200 Subject: [PATCH 2/4] Improved accuracy of slicing (triangle cutting) code, improved debugging outputs and asserts of the slicing code. Disabled detection of concave corners with horizontal faces, as too often there were found models with badly triangulated faces, see for example GH issue #895. --- xs/src/libslic3r/TriangleMesh.cpp | 97 ++++++++++++++++++++----------- xs/src/libslic3r/Utils.hpp | 1 + xs/src/libslic3r/utils.cpp | 10 ++++ xs/xsp/XS.xsp | 5 ++ 4 files changed, 78 insertions(+), 35 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index b83e0dae6..0941c11ef 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -15,16 +15,20 @@ #include +// for SLIC3R_DEBUG_SLICE_PROCESSING +#include "libslic3r.h" + #if 0 #define DEBUG #define _DEBUG #undef NDEBUG + #define SLIC3R_DEBUG +// #define SLIC3R_TRIANGLEMESH_DEBUG #endif #include -#ifdef SLIC3R_DEBUG -// #define SLIC3R_TRIANGLEMESH_DEBUG +#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) #include "SVG.hpp" #endif @@ -758,8 +762,22 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectory; il.a_id = a_id; il.b_id = b_id; + assert(il.a != il.b); (*lines)[layer_idx].emplace_back(il); } } else @@ -849,16 +868,6 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectorx - a->x; - float v1_y = b->y - a->y; - float v2_x = c->x - a->x; - float v2_y = c->y - a->y; - float dir = (b->x - a->x) * (c->y - a->y) - (b->y - a->y) * (c->x - a->x); - return dir; -} - // Return true, if the facet has been sliced and line_out has been filled. TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( float slice_z, const stl_facet &facet, const int facet_idx, @@ -873,10 +882,10 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Reorder vertices so that the first one is the one with lowest Z. // This is needed to get all intersection lines in a consistent order // (external on the right of the line) - int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); - for (int j = i; j - i < 3; ++ j) { // loop through facet edges + const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; + int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); + for (int j = i; j - i < 3; ++j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; - const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; const stl_vertex *a = &this->v_scaled_shared[a_id]; @@ -888,6 +897,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( const stl_vertex &v0 = this->v_scaled_shared[vertices[0]]; const stl_vertex &v1 = this->v_scaled_shared[vertices[1]]; const stl_vertex &v2 = this->v_scaled_shared[vertices[2]]; + const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = Slicing; const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; @@ -897,23 +907,23 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( // Mark neighbor edges, which do not have a neighbor. uint32_t edges = 0; uint32_t mask = IntersectionLine::EDGE0; - for (int nbr_idx = 2; nbr_idx != 5; ++ nbr_idx, mask <<= 1) + for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx, mask <<= 1) // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no // opposite face, add it to the edges to process when slicing. - if (nbr.neighbor[nbr_idx % 3] == -1) + if (nbr.neighbor[nbr_idx] == -1) // Mark this edge. edges |= mask; // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. result = (edges == 0) ? Cutting : Slicing; line_out->flags |= edges; - if (this->mesh->stl.facet_start[facet_idx].normal.z < 0) { + if (normal.z < 0) { // If normal points downwards this is a bottom horizontal facet so we reverse its point order. std::swap(a, b); std::swap(a_id, b_id); } } else { // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. - int nbr_idx = (j + 2) % 3; + int nbr_idx = j % 3; int nbr_face = nbr.neighbor[nbr_idx]; // Is the third vertex below the cutting plane? bool third_below = v0.z < slice_z || v1.z < slice_z || v2.z < slice_z; @@ -923,19 +933,25 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( printf("Face has no neighbor!\n"); #endif } else { + assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); + assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; - if (c->z > slice_z) { - // If an edge resides on a cutting plane, and none of the two triangles are coplanar with the cutting plane, - // igore the lower triangle. - if (third_below) - result = Cutting; - } else if (c->z == slice_z) { - // A vertical face shares edge with a horizontal face. Verify, whether the shared corner is convex or concave. - float dir = cross_product(a, b, c); - if (third_below ? (dir < 0.) : (dir > 0.)) - result = Cutting; - } +// double side = double(normal.x) * (double(c->x) - double(a->x)) + double(normal.y) * (double(c->y) - double(a->y)); +// assert(c->z != slice_z || side != 0.); +// double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); + result = + (c->z == slice_z) ? + // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. + // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, + // and leth the code downstream hopefully handle it. + Slicing : + // Failing tests: Ignore concave corners for slicing. + // (((normal_nbr < 0) == third_below) ? Cutting : Slicing) : + // or + // (((this->mesh->stl.facet_start[nbr_face].normal.z < 0) == third_below) ? Cutting : Slicing) : + // For a pair of faces touching exactly at the cutting plane, ignore the face with a higher index. + (facet_idx < nbr_face) ? Slicing : Cutting; } if (third_below) { line_out->edge_type = feTop; @@ -950,6 +966,7 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->b.y = b->y; line_out->a_id = a_id; line_out->b_id = b_id; + assert(line_out->a != line_out->b); return result; } @@ -970,8 +987,9 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. IntersectionPoint &point = points[num_points ++]; - point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); - point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); + double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); + point.x = float(double(b->x) + (double(a->x) - double(b->x)) * t); + point.y = float(double(b->y) + (double(a->y) - double(b->y)) * t); point.edge_id = edge_id; } } @@ -1003,7 +1021,11 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; // General slicing position, use the segment for both slicing and object cutting. - return Slicing; + // In a degenerate case where a plane cuts the triangle very close to its vertex, it is possible, that + // a zero length edge is created. In that case the zero length edge could be safely ignored + // as the polyline will still be connected, because both the sliced edges of the triangle will be + // sliced the same way at the neighbor triangles. + return (line_out->a == line_out->b) ? NoSlice : Slicing; } return NoSlice; } @@ -1074,6 +1096,11 @@ static inline void remove_tangent_edges(std::vector &lines) void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { +#ifdef _DEBUG + for (const Line &l : lines_src) + assert(l.a != l.b); +#endif /* _DEBUG */ + remove_tangent_edges(lines); struct OpenPolyline { diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 349222854..fd63d412a 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -9,6 +9,7 @@ namespace Slic3r { extern void set_logging_level(unsigned int level); extern void trace(unsigned int level, const char *message); +extern void disable_multi_threading(); // Set a path with GUI resource files. void set_var_dir(const std::string &path); diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 55164bbdd..4ca4f69fd 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace Slic3r { static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error; @@ -82,6 +84,14 @@ void trace(unsigned int level, const char *message) (::boost::log::keywords::severity = severity)) << message; } +void disable_multi_threading() +{ + // Disable parallelization so the Shiny profiler works + static tbb::task_scheduler_init *tbb_init = nullptr; + if (tbb_init == nullptr) + tbb_init = new tbb::task_scheduler_init(1); +} + static std::string g_var_dir; void set_var_dir(const std::string &dir) diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index e900532aa..04969a7f9 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -48,6 +48,11 @@ trace(level, message) CODE: Slic3r::trace(level, message); +void +disable_multi_threading() + CODE: + Slic3r::disable_multi_threading(); + void set_var_dir(dir) char *dir; From b67f32a94d83619f7b2056c22a0e55233238160f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 10 Aug 2018 14:10:28 +0200 Subject: [PATCH 3/4] Slicing improvement for slicing degenerated meshes: Add edges of parallel folded horizontal faces into the output contours, but ignore them when taking seeds for contour extraction. FIXME: Single vertices touching a plane are not handled correctly, they create zero length edges. --- xs/src/libslic3r/TriangleMesh.cpp | 145 +++++++++++++++++++++--------- xs/src/libslic3r/TriangleMesh.hpp | 18 +++- 2 files changed, 118 insertions(+), 45 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 0941c11ef..be554aef3 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -223,7 +223,6 @@ TriangleMesh::repair() { BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } - float TriangleMesh::volume() { if (this->stl.stats.volume == -1) @@ -427,7 +426,7 @@ bool TriangleMesh::has_multiple_patches() const facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -479,8 +478,7 @@ size_t TriangleMesh::number_of_patches() const return num_bodies; } -TriangleMeshPtrs -TriangleMesh::split() const +TriangleMeshPtrs TriangleMesh::split() const { TriangleMeshPtrs meshes; std::set seen_facets; @@ -532,8 +530,7 @@ TriangleMesh::split() const return meshes; } -void -TriangleMesh::merge(const TriangleMesh &mesh) +void TriangleMesh::merge(const TriangleMesh &mesh) { // reset stats and metadata int number_of_facets = this->stl.stats.number_of_facets; @@ -587,8 +584,7 @@ Polygon TriangleMesh::convex_hull() return Slic3r::Geometry::convex_hull(pp); } -BoundingBoxf3 -TriangleMesh::bounding_box() const +BoundingBoxf3 TriangleMesh::bounding_box() const { BoundingBoxf3 bb; bb.defined = true; @@ -601,8 +597,7 @@ TriangleMesh::bounding_box() const return bb; } -void -TriangleMesh::require_shared_vertices() +void TriangleMesh::require_shared_vertices() { BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - start"; if (!this->repaired) @@ -611,10 +606,23 @@ TriangleMesh::require_shared_vertices() BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - stl_generate_shared_vertices"; stl_generate_shared_vertices(&(this->stl)); } +#ifdef _DEBUG + // Verify validity of neighborship data. + for (int facet_idx = 0; facet_idx < stl.stats.number_of_facets; ++facet_idx) { + const stl_neighbors &nbr = stl.neighbors_start[facet_idx]; + const int *vertices = stl.v_indices[facet_idx].vertex; + for (int nbr_idx = 0; nbr_idx < 3; ++nbr_idx) { + int nbr_face = this->stl.neighbors_start[facet_idx].neighbor[nbr_idx]; + if (nbr_face != -1) { + assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == vertices[(nbr_idx + 1) % 3]); + assert(stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == vertices[nbr_idx]); + } + } + } +#endif /* _DEBUG */ BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end"; } - TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh) { @@ -778,11 +786,14 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vector FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z, facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif + #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ // find layer extents std::vector::const_iterator min_layer, max_layer; min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z - #ifdef SLIC3R_DEBUG + #ifdef SLIC3R_TRIANGLEMESH_DEBUG printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif + #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { std::vector::size_type layer_idx = it - z.begin(); @@ -824,9 +835,8 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectormesh->stl.v_indices[facet_idx].vertex; const bool reverse = this->mesh->stl.facet_start[facet_idx].normal.z < 0; - uint32_t edge_mask = IntersectionLine::EDGE0; - for (int j = 0; j < 3; ++ j, edge_mask <<= 1) - if (il.flags & edge_mask) { + for (int j = 0; j < 3; ++ j) + if (il.flags & ((IntersectionLine::EDGE0_NO_NEIGHBOR | IntersectionLine::EDGE0_FOLD) << j)) { int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; if (reverse) @@ -840,6 +850,8 @@ void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vectoredge_type = feHorizontal; // Mark neighbor edges, which do not have a neighbor. uint32_t edges = 0; - uint32_t mask = IntersectionLine::EDGE0; - for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx, mask <<= 1) + for (int nbr_idx = 0; nbr_idx != 3; ++ nbr_idx) { // If the neighbor with an edge starting with a vertex idx (nbr_idx - 2) shares no // opposite face, add it to the edges to process when slicing. - if (nbr.neighbor[nbr_idx] == -1) - // Mark this edge. - edges |= mask; + if (nbr.neighbor[nbr_idx] == -1) { + // Mark this edge to be added to the slice. + edges |= (IntersectionLine::EDGE0_NO_NEIGHBOR << nbr_idx); + } +#if 1 + else if (normal.z > 0) { + // Produce edges for opposite faced overlapping horizontal faces aka folds. + // This method often produces connecting lines (noise) at the cutting plane. + // Produce the edges for the top facing face of the pair of top / bottom facing faces. + + // Index of a neighbor face. + const int nbr_face = nbr.neighbor[nbr_idx]; + const int *nbr_vertices = this->mesh->stl.v_indices[nbr_face].vertex; + int idx_vertex_opposite = nbr_vertices[nbr.which_vertex_not[nbr_idx]]; + const stl_vertex *c2 = &this->v_scaled_shared[idx_vertex_opposite]; + if (c2->z == slice_z) { + // Edge shared by facet_idx and nbr_face. + int a_id = vertices[nbr_idx]; + int b_id = vertices[(nbr_idx + 1) % 3]; + int c1_id = vertices[(nbr_idx + 2) % 3]; + const stl_vertex *a = &this->v_scaled_shared[a_id]; + const stl_vertex *b = &this->v_scaled_shared[b_id]; + const stl_vertex *c1 = &this->v_scaled_shared[c1_id]; + // Verify that the two neighbor faces share a common edge. + assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 1) % 3] == b_id); + assert(nbr_vertices[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); + double n1 = (double(c1->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c1->y) - double(a->y)) * (double(b->x) - double(a->x)); + double n2 = (double(c2->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c2->y) - double(a->y)) * (double(b->x) - double(a->x)); + if (n1 * n2 > 0) + // The two faces overlap. This indicates an invalid mesh geometry (non-manifold), + // but these are the real world objects, and leaving out these edges leads to missing contours. + edges |= (IntersectionLine::EDGE0_FOLD << nbr_idx); + } + } +#endif + } // Use some edges of this triangle for slicing only if at least one of its edge does not have an opposite face. result = (edges == 0) ? Cutting : Slicing; line_out->flags |= edges; @@ -937,21 +981,31 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( assert(this->mesh->stl.v_indices[nbr_face].vertex[(nbr.which_vertex_not[nbr_idx] + 2) % 3] == a_id); int idx_vertex_opposite = this->mesh->stl.v_indices[nbr_face].vertex[nbr.which_vertex_not[nbr_idx]]; const stl_vertex *c = &this->v_scaled_shared[idx_vertex_opposite]; -// double side = double(normal.x) * (double(c->x) - double(a->x)) + double(normal.y) * (double(c->y) - double(a->y)); -// assert(c->z != slice_z || side != 0.); -// double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); - result = - (c->z == slice_z) ? - // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. - // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, - // and leth the code downstream hopefully handle it. - Slicing : - // Failing tests: Ignore concave corners for slicing. - // (((normal_nbr < 0) == third_below) ? Cutting : Slicing) : - // or - // (((this->mesh->stl.facet_start[nbr_face].normal.z < 0) == third_below) ? Cutting : Slicing) : - // For a pair of faces touching exactly at the cutting plane, ignore the face with a higher index. - (facet_idx < nbr_face) ? Slicing : Cutting; + if (c->z == slice_z) { + double normal_nbr = (double(c->x) - double(a->x)) * (double(b->y) - double(a->y)) - (double(c->y) - double(a->y)) * (double(b->x) - double(a->x)); +#if 0 + if ((normal_nbr < 0) == third_below) { + printf("Flipped normal?\n"); + } +#endif + result = + // A vertical face shares edge with a horizontal face. Verify, whether the shared edge makes a convex or concave corner. + // Unfortunately too often there are flipped normals, which brake our assumption. Let's rather return every edge, + // and leth the code downstream hopefully handle it. + #if 1 + // Ignore concave corners for slicing. + // This method has the unfortunate property, that folds in a horizontal plane create concave corners, + // leading to broken contours, if these concave corners are not replaced by edges of the folds, see above. + ((normal_nbr < 0) == third_below) ? Cutting : Slicing; + #else + // Use concave corners for slicing. This leads to the test 01_trianglemesh.t "slicing a top tangent plane includes its area" failing, + // and rightly so. + Slicing; + #endif + } else { + // For a pair of faces touching exactly at the cutting plane, ignore one of them. An arbitrary rule is to ignore the face with a higher index. + result = (facet_idx < nbr_face) ? Slicing : Cutting; + } } if (third_below) { line_out->edge_type = feTop; @@ -1021,11 +1075,17 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; // General slicing position, use the segment for both slicing and object cutting. +#if 0 // In a degenerate case where a plane cuts the triangle very close to its vertex, it is possible, that // a zero length edge is created. In that case the zero length edge could be safely ignored // as the polyline will still be connected, because both the sliced edges of the triangle will be // sliced the same way at the neighbor triangles. return (line_out->a == line_out->b) ? NoSlice : Slicing; +#else + // The chaining code primarily relies on the IDs of the edges. + // Even though there may be a zero length edge generated, it is still important, + return Slicing; +#endif } return NoSlice; } @@ -1096,8 +1156,10 @@ static inline void remove_tangent_edges(std::vector &lines) void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { -#ifdef _DEBUG - for (const Line &l : lines_src) +#if 0 +//FIXME the slice_facet() creates zero length edges. +//#ifdef _DEBUG + for (const Line &l : lines) assert(l.a != l.b); #endif /* _DEBUG */ @@ -1141,7 +1203,8 @@ void TriangleMeshSlicer::make_loops(std::vector &lines, Polygo // take first spare line and start a new loop IntersectionLine *first_line = nullptr; for (; it_line_seed != lines.end(); ++ it_line_seed) - if (! it_line_seed->skip()) { + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { first_line = &(*it_line_seed ++); break; } diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 3714f5e9e..89742fd4c 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -114,6 +114,9 @@ public: bool skip() const { return (this->flags & SKIP) != 0; } void set_skip() { this->flags |= SKIP; } + + bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } + void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } // Inherits Point a, b // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. @@ -127,10 +130,17 @@ public: FacetEdgeType edge_type; // Used by TriangleMeshSlicer::slice() to skip duplicate edges. enum { - EDGE0 = 1, - EDGE1 = 2, - EDGE2 = 4, - SKIP = 8, + // Triangle edge added, because it has no neighbor. + EDGE0_NO_NEIGHBOR = 0x001, + EDGE1_NO_NEIGHBOR = 0x002, + EDGE2_NO_NEIGHBOR = 0x004, + // Triangle edge added, because it makes a fold with another horizontal edge. + EDGE0_FOLD = 0x010, + EDGE1_FOLD = 0x020, + EDGE2_FOLD = 0x040, + // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). + NO_SEED = 0x100, + SKIP = 0x200, }; uint32_t flags; }; From 13ce087606838d66a65f6f0a1db41ea71f8a623f Mon Sep 17 00:00:00 2001 From: bubnikv Date: Fri, 10 Aug 2018 17:37:09 +0200 Subject: [PATCH 4/4] Another improvement in robustness of mesh slicing. --- xs/src/libslic3r/TriangleMesh.cpp | 117 +++++++++++++++++------------- 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index be554aef3..65a31fada 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -469,7 +469,7 @@ size_t TriangleMesh::number_of_patches() const facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (! facet_visited[neighbor_idx]) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) facet_queue[facet_queue_cnt ++] = neighbor_idx; } } @@ -787,7 +787,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, std::vectormesh->stl.v_indices[facet_idx].vertex; - int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); - for (int j = i; j - i < 3; ++j) { // loop through facet edges + int i = (facet.vertex[1].z == min_z) ? 1 : ((facet.vertex[2].z == min_z) ? 2 : 0); + for (int j = i; j - i < 3; ++j) { // loop through facet edges int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; int a_id = vertices[j % 3]; int b_id = vertices[(j+1) % 3]; @@ -1026,46 +1025,57 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( if (a->z == slice_z) { // Only point a alings with the cutting plane. - points_on_layer[num_points_on_layer ++] = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x = a->x; - point.y = a->y; - point.point_id = a_id; + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x = a->x; + point.y = a->y; + point.point_id = a_id; + } } else if (b->z == slice_z) { // Only point b alings with the cutting plane. - points_on_layer[num_points_on_layer ++] = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x = b->x; - point.y = b->y; - point.point_id = b_id; + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x = b->x; + point.y = b->y; + point.point_id = b_id; + } } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { // A general case. The face edge intersects the cutting plane. Calculate the intersection point. - IntersectionPoint &point = points[num_points ++]; - double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); - point.x = float(double(b->x) + (double(a->x) - double(b->x)) * t); - point.y = float(double(b->y) + (double(a->y) - double(b->y)) * t); - point.edge_id = edge_id; + assert(a_id != b_id); + // Sort the edge to give a consistent answer. + if (a_id > b_id) { + std::swap(a_id, b_id); + std::swap(a, b); + } + IntersectionPoint &point = points[num_points]; + double t = (double(slice_z) - double(b->z)) / (double(a->z) - double(b->z)); + if (t <= 0.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point.x = a->x; + point.y = a->y; + point_on_layer = num_points ++; + point.point_id = a_id; + } + } else if (t >= 1.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point.x = b->x; + point.y = b->y; + point_on_layer = num_points ++; + point.point_id = b_id; + } + } else { + point.x = coord_t(floor(double(b->x) + (double(a->x) - double(b->x)) * t + 0.5)); + point.y = coord_t(floor(double(b->y) + (double(a->y) - double(b->y)) * t + 0.5)); + point.edge_id = edge_id; + ++ num_points; + } } } - // We can't have only one point on layer because each vertex gets detected - // twice (once for each edge), and we can't have three points on layer, - // because we assume this code is not getting called for horizontal facets. - assert(num_points_on_layer == 0 || num_points_on_layer == 2); - if (num_points_on_layer > 0) { - assert(points[points_on_layer[0]].point_id == points[points_on_layer[1]].point_id); - assert(num_points == 2 || num_points == 3); - if (num_points < 3) - // This triangle touches the cutting plane with a single vertex. Ignore it. - return NoSlice; - // Erase one of the duplicate points. - -- num_points; - for (int i = points_on_layer[1]; i < num_points; ++ i) - points[i] = points[i + 1]; - } - - // Facets must intersect each plane 0 or 2 times. - assert(num_points == 0 || num_points == 2); + // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. + assert(num_points < 3); if (num_points == 2) { line_out->edge_type = feGeneral; line_out->a = (Point)points[1]; @@ -1074,18 +1084,27 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( line_out->b_id = points[0].point_id; line_out->edge_a_id = points[1].edge_id; line_out->edge_b_id = points[0].edge_id; + // Not a zero lenght edge. + //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + //assert(line_out->a != line_out->b); + // The plane cuts at least one edge in a general position. + assert(line_out->a_id == -1 || line_out->b_id == -1); + assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); // General slicing position, use the segment for both slicing and object cutting. #if 0 - // In a degenerate case where a plane cuts the triangle very close to its vertex, it is possible, that - // a zero length edge is created. In that case the zero length edge could be safely ignored - // as the polyline will still be connected, because both the sliced edges of the triangle will be - // sliced the same way at the neighbor triangles. - return (line_out->a == line_out->b) ? NoSlice : Slicing; -#else - // The chaining code primarily relies on the IDs of the edges. - // Even though there may be a zero length edge generated, it is still important, - return Slicing; + if (line_out->a_id != -1 && line_out->b_id != -1) { + // Solving a degenerate case, where both the intersections snapped to an edge. + // Correctly classify the face as below or above based on the position of the 3rd point. + int i = vertices[0]; + if (i == line_out->a_id || i == line_out->b_id) + i = vertices[1]; + if (i == line_out->a_id || i == line_out->b_id) + i = vertices[2]; + assert(i != line_out->a_id && i != line_out->b_id); + line_out->edge_type = (this->v_scaled_shared[i].z < slice_z) ? feTop : feBottom; + } #endif + return Slicing; } return NoSlice; } @@ -1157,7 +1176,7 @@ static inline void remove_tangent_edges(std::vector &lines) void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { #if 0 -//FIXME the slice_facet() creates zero length edges. +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. //#ifdef _DEBUG for (const Line &l : lines) assert(l.a != l.b);