mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	
		
			
				
	
	
		
			746 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			746 lines
		
	
	
	
		
			29 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "../ClipperUtils.hpp"
 | |
| #include "../ExPolygon.hpp"
 | |
| #include "../Surface.hpp"
 | |
| #include "../Geometry.hpp"
 | |
| #include "../Layer.hpp"
 | |
| #include "../Print.hpp"
 | |
| #include "../ShortestPath.hpp"
 | |
| 
 | |
| #include "FillAdaptive.hpp"
 | |
| 
 | |
| // for indexed_triangle_set
 | |
| #include <admesh/stl.h>
 | |
| 
 | |
| #include <cstdlib>
 | |
| #include <cmath>
 | |
| #include <algorithm>
 | |
| 
 | |
| // Boost pool: Don't use mutexes to synchronize memory allocation.
 | |
| #define BOOST_POOL_NO_MT
 | |
| #include <boost/pool/object_pool.hpp>
 | |
| 
 | |
| namespace Slic3r {
 | |
| namespace FillAdaptive {
 | |
| 
 | |
| // Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp
 | |
| // The AABB-Triangle test implementation is based on the pseudo-code in
 | |
| // Christer Ericson's Real-Time Collision Detection, pp. 169-172. It is
 | |
| // practically a standard SAT test.
 | |
| //
 | |
| // Original MathGeoLib benchmark:
 | |
| //    Best: 17.282 nsecs / 46.496 ticks, Avg: 17.804 nsecs, Worst: 18.434 nsecs
 | |
| //
 | |
| //FIXME Vojtech: The MathGeoLib contains a vectorized implementation.
 | |
| template<typename Vector> 
 | |
| bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase<Vector> &aabb)
 | |
| {
 | |
|     using Scalar = typename Vector::Scalar;
 | |
| 
 | |
|     Vector tMin = a.cwiseMin(b.cwiseMin(c));
 | |
|     Vector tMax = a.cwiseMax(b.cwiseMax(c));
 | |
| 
 | |
|     if (tMin.x() >= aabb.max.x() || tMax.x() <= aabb.min.x()
 | |
|         || tMin.y() >= aabb.max.y() || tMax.y() <= aabb.min.y()
 | |
|         || tMin.z() >= aabb.max.z() || tMax.z() <= aabb.min.z())
 | |
|         return false;
 | |
| 
 | |
|     Vector center = (aabb.min + aabb.max) * 0.5f;
 | |
|     Vector h = aabb.max - center;
 | |
| 
 | |
|     const Vector t[3] { b-a, c-a, c-b };
 | |
| 
 | |
|     Vector ac = a - center;
 | |
| 
 | |
|     Vector n = t[0].cross(t[1]);
 | |
|     Scalar s = n.dot(ac);
 | |
|     Scalar r = std::abs(h.dot(n.cwiseAbs()));
 | |
|     if (abs(s) >= r)
 | |
|         return false;
 | |
| 
 | |
|     const Vector at[3] = { t[0].cwiseAbs(), t[1].cwiseAbs(), t[2].cwiseAbs() };
 | |
| 
 | |
|     Vector bc = b - center;
 | |
|     Vector cc = c - center;
 | |
| 
 | |
|     // SAT test all cross-axes.
 | |
|     // The following is a fully unrolled loop of this code, stored here for reference:
 | |
|     /*
 | |
|     Scalar d1, d2, a1, a2;
 | |
|     const Vector e[3] = { DIR_VEC(1, 0, 0), DIR_VEC(0, 1, 0), DIR_VEC(0, 0, 1) };
 | |
|     for(int i = 0; i < 3; ++i)
 | |
|         for(int j = 0; j < 3; ++j)
 | |
|         {
 | |
|             Vector axis = Cross(e[i], t[j]);
 | |
|             ProjectToAxis(axis, d1, d2);
 | |
|             aabb.ProjectToAxis(axis, a1, a2);
 | |
|             if (d2 <= a1 || d1 >= a2) return false;
 | |
|         }
 | |
|     */
 | |
| 
 | |
|     // eX <cross> t[0]
 | |
|     Scalar d1 = t[0].y() * ac.z() - t[0].z() * ac.y();
 | |
|     Scalar d2 = t[0].y() * cc.z() - t[0].z() * cc.y();
 | |
|     Scalar tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.y() * at[0].z() + h.z() * at[0].y());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eX <cross> t[1]
 | |
|     d1 = t[1].y() * ac.z() - t[1].z() * ac.y();
 | |
|     d2 = t[1].y() * bc.z() - t[1].z() * bc.y();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.y() * at[1].z() + h.z() * at[1].y());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eX <cross> t[2]
 | |
|     d1 = t[2].y() * ac.z() - t[2].z() * ac.y();
 | |
|     d2 = t[2].y() * bc.z() - t[2].z() * bc.y();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.y() * at[2].z() + h.z() * at[2].y());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eY <cross> t[0]
 | |
|     d1 = t[0].z() * ac.x() - t[0].x() * ac.z();
 | |
|     d2 = t[0].z() * cc.x() - t[0].x() * cc.z();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.x() * at[0].z() + h.z() * at[0].x());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eY <cross> t[1]
 | |
|     d1 = t[1].z() * ac.x() - t[1].x() * ac.z();
 | |
|     d2 = t[1].z() * bc.x() - t[1].x() * bc.z();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.x() * at[1].z() + h.z() * at[1].x());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eY <cross> t[2]
 | |
|     d1 = t[2].z() * ac.x() - t[2].x() * ac.z();
 | |
|     d2 = t[2].z() * bc.x() - t[2].x() * bc.z();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.x() * at[2].z() + h.z() * at[2].x());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eZ <cross> t[0]
 | |
|     d1 = t[0].x() * ac.y() - t[0].y() * ac.x();
 | |
|     d2 = t[0].x() * cc.y() - t[0].y() * cc.x();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.y() * at[0].x() + h.x() * at[0].y());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eZ <cross> t[1]
 | |
|     d1 = t[1].x() * ac.y() - t[1].y() * ac.x();
 | |
|     d2 = t[1].x() * bc.y() - t[1].y() * bc.x();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.y() * at[1].x() + h.x() * at[1].y());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // eZ <cross> t[2]
 | |
|     d1 = t[2].x() * ac.y() - t[2].y() * ac.x();
 | |
|     d2 = t[2].x() * bc.y() - t[2].y() * bc.x();
 | |
|     tc = (d1 + d2) * 0.5f;
 | |
|     r = std::abs(h.y() * at[2].x() + h.x() * at[2].y());
 | |
|     if (r + std::abs(tc - d1) < std::abs(tc))
 | |
|         return false;
 | |
| 
 | |
|     // No separating axis exists, the AABB and triangle intersect.
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static double dist2_to_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &p)
 | |
| {
 | |
|     double out = std::numeric_limits<double>::max();
 | |
|     const Vec3d v1 = b - a;
 | |
|     auto        l1 = v1.squaredNorm();
 | |
|     const Vec3d v2 = c - b;
 | |
|     auto        l2 = v2.squaredNorm();
 | |
|     const Vec3d v3 = a - c;
 | |
|     auto        l3 = v3.squaredNorm();
 | |
| 
 | |
|     // Is the triangle valid?
 | |
|     if (l1 > 0. && l2 > 0. && l3 > 0.) 
 | |
|     {
 | |
|         // 1) Project point into the plane of the triangle.
 | |
|         const Vec3d n = v1.cross(v2);
 | |
|         double d = (p - a).dot(n);
 | |
|         const Vec3d foot_pt = p - n * d / n.squaredNorm();
 | |
| 
 | |
|         // 2) Maximum projection of n.
 | |
|         int proj_axis;
 | |
|         n.array().cwiseAbs().maxCoeff(&proj_axis);
 | |
| 
 | |
|         // 3) Test whether the foot_pt is inside the triangle.
 | |
|         {
 | |
|             auto inside_triangle = [](const Vec2d& v1, const Vec2d& v2, const Vec2d& v3, const Vec2d& pt) {
 | |
|                 const double d1 = cross2(v1, pt);
 | |
|                 const double d2 = cross2(v2, pt);
 | |
|                 const double d3 = cross2(v3, pt);
 | |
|                 // Testing both CCW and CW orientations.
 | |
|                 return (d1 >= 0. && d2 >= 0. && d3 >= 0.) || (d1 <= 0. && d2 <= 0. && d3 <= 0.);
 | |
|             };
 | |
|             bool inside;
 | |
|             switch (proj_axis) {
 | |
|             case 0: 
 | |
|                 inside = inside_triangle({v1.y(), v1.z()}, {v2.y(), v2.z()}, {v3.y(), v3.z()}, {foot_pt.y(), foot_pt.z()}); break;
 | |
|             case 1: 
 | |
|                 inside = inside_triangle({v1.z(), v1.x()}, {v2.z(), v2.x()}, {v3.z(), v3.x()}, {foot_pt.z(), foot_pt.x()}); break;
 | |
|             default: 
 | |
|                 assert(proj_axis == 2);
 | |
|                 inside = inside_triangle({v1.x(), v1.y()}, {v2.x(), v2.y()}, {v3.x(), v3.y()}, {foot_pt.x(), foot_pt.y()}); break;
 | |
|             }
 | |
|             if (inside)
 | |
|                 return (p - foot_pt).squaredNorm();
 | |
|         }
 | |
| 
 | |
|         // 4) Find minimum distance to triangle vertices and edges.
 | |
|         out = std::min((p - a).squaredNorm(), std::min((p - b).squaredNorm(), (p - c).squaredNorm()));
 | |
|         auto t = (p - a).dot(v1);
 | |
|         if (t > 0. && t < l1)
 | |
|             out = std::min(out, (a + v1 * (t / l1) - p).squaredNorm());
 | |
|         t = (p - b).dot(v2);
 | |
|         if (t > 0. && t < l2)
 | |
|             out = std::min(out, (b + v2 * (t / l2) - p).squaredNorm());
 | |
|         t = (p - c).dot(v3);
 | |
|         if (t > 0. && t < l3)
 | |
|             out = std::min(out, (c + v3 * (t / l3) - p).squaredNorm());
 | |
|     }
 | |
| 
 | |
|     return out;
 | |
| }
 | |
| 
 | |
| // Ordering of children cubes.
 | |
| static const std::array<Vec3d, 8> child_centers {
 | |
|     Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1,  1, -1), Vec3d( 1,  1, -1),
 | |
|     Vec3d(-1, -1,  1), Vec3d( 1, -1,  1), Vec3d(-1,  1,  1), Vec3d( 1,  1,  1)
 | |
| };
 | |
| 
 | |
| // Traversal order of octree children cells for three infill directions,
 | |
| // so that a single line will be discretized in a strictly monotonic order.
 | |
| static constexpr std::array<std::array<int, 8>, 3> child_traversal_order {
 | |
|     std::array<int, 8>{ 2, 3, 0, 1, 6, 7, 4, 5 },
 | |
|     std::array<int, 8>{ 4, 0, 6, 2, 5, 1, 7, 3 },
 | |
|     std::array<int, 8>{ 1, 5, 0, 4, 3, 7, 2, 6 },
 | |
| };
 | |
| 
 | |
| struct Cube
 | |
| {
 | |
|     Vec3d center;
 | |
| #ifndef NDEBUG
 | |
|     Vec3d center_octree;
 | |
| #endif // NDEBUG
 | |
|     std::array<Cube*, 8> children {}; // initialized to nullptrs
 | |
|     Cube(const Vec3d ¢er) : center(center) {}
 | |
| };
 | |
| 
 | |
| struct CubeProperties
 | |
| {
 | |
|     double edge_length;     // Lenght of edge of a cube
 | |
|     double height;          // Height of rotated cube (standing on the corner)
 | |
|     double diagonal_length; // Length of diagonal of a cube a face
 | |
|     double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created
 | |
|     double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created
 | |
| };
 | |
| 
 | |
| struct Octree
 | |
| {
 | |
|     // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool,
 | |
|     // perfect for building up our octree.
 | |
|     boost::object_pool<Cube>    pool;
 | |
|     Cube*                       root_cube { nullptr };
 | |
|     Vec3d                       origin;
 | |
|     std::vector<CubeProperties> cubes_properties;
 | |
| 
 | |
|     Octree(const Vec3d &origin, const std::vector<CubeProperties> &cubes_properties)
 | |
|         : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {}
 | |
| 
 | |
|     void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth);
 | |
| };
 | |
| 
 | |
| void OctreeDeleter::operator()(Octree *p) {
 | |
|     delete p;
 | |
| }
 | |
| 
 | |
| std::pair<double, double> adaptive_fill_line_spacing(const PrintObject &print_object)
 | |
| {
 | |
|     // Output, spacing for icAdaptiveCubic and icSupportCubic
 | |
|     double  adaptive_line_spacing = 0.;
 | |
|     double  support_line_spacing = 0.;
 | |
| 
 | |
|     enum class Tristate {
 | |
|         Yes,
 | |
|         No,
 | |
|         Maybe
 | |
|     };
 | |
|     struct RegionFillData {
 | |
|         Tristate        has_adaptive_infill;
 | |
|         Tristate        has_support_infill;
 | |
|         double          density;
 | |
|         double          extrusion_width;
 | |
|     };
 | |
|     std::vector<RegionFillData> region_fill_data;
 | |
|     region_fill_data.reserve(print_object.print()->regions().size());
 | |
|     bool                       build_octree                   = false;
 | |
|     const std::vector<double> &nozzle_diameters               = print_object.print()->config().nozzle_diameter.values;
 | |
|     double                     max_nozzle_diameter            = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end());
 | |
|     double                     default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, max_nozzle_diameter);
 | |
|     for (const PrintRegion *region : print_object.print()->regions()) {
 | |
|         const PrintRegionConfig &config   = region->config();
 | |
|         bool                     nonempty = config.fill_density > 0;
 | |
|         bool                     has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic;
 | |
|         bool                     has_support_infill  = nonempty && config.fill_pattern == ipSupportCubic;
 | |
|         region_fill_data.push_back(RegionFillData({
 | |
|             has_adaptive_infill ? Tristate::Maybe : Tristate::No,
 | |
|             has_support_infill ? Tristate::Maybe : Tristate::No,
 | |
|             config.fill_density,
 | |
|             config.infill_extrusion_width != 0. ? config.infill_extrusion_width : default_infill_extrusion_width
 | |
|         }));
 | |
|         build_octree |= has_adaptive_infill || has_support_infill;
 | |
|     }
 | |
| 
 | |
|     if (build_octree) {
 | |
|         // Compute the average of above parameters over all layers
 | |
|         for (const Layer *layer : print_object.layers())
 | |
|             for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) {
 | |
|                 RegionFillData &rd = region_fill_data[region_id];
 | |
|                 if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
 | |
|                     rd.has_adaptive_infill = Tristate::Yes;
 | |
|                 if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty())
 | |
|                     rd.has_support_infill = Tristate::Yes;
 | |
|             }
 | |
| 
 | |
|         double  adaptive_fill_density           = 0.;
 | |
|         double  adaptive_infill_extrusion_width = 0.;
 | |
|         int     adaptive_cnt                    = 0;
 | |
|         double  support_fill_density            = 0.;
 | |
|         double  support_infill_extrusion_width  = 0.;
 | |
|         int     support_cnt                     = 0;
 | |
| 
 | |
|         for (const RegionFillData &rd : region_fill_data) {
 | |
|             if (rd.has_adaptive_infill == Tristate::Yes) {
 | |
|                 adaptive_fill_density           += rd.density;
 | |
|                 adaptive_infill_extrusion_width += rd.extrusion_width;
 | |
|                 ++ adaptive_cnt;
 | |
|             } else if (rd.has_support_infill == Tristate::Yes) {
 | |
|                 support_fill_density           += rd.density;
 | |
|                 support_infill_extrusion_width += rd.extrusion_width;
 | |
|                 ++ support_cnt;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         auto to_line_spacing = [](int cnt, double density, double extrusion_width) {
 | |
|             if (cnt) {
 | |
|                 density         /= double(cnt);
 | |
|                 extrusion_width /= double(cnt);
 | |
|                 return extrusion_width / ((density / 100.0f) * 0.333333333f);
 | |
|             } else
 | |
|                 return 0.;
 | |
|         };
 | |
|         adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width);
 | |
|         support_line_spacing  = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width);
 | |
|     }
 | |
| 
 | |
|     return std::make_pair(adaptive_line_spacing, support_line_spacing);
 | |
| }
 | |
| 
 | |
| // Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion
 | |
| // (Digital Differential Analyzer).
 | |
| struct FillContext
 | |
| {
 | |
|     // The angles have to agree with child_traversal_order.
 | |
|     static constexpr double direction_angles[3] {
 | |
|         0.,
 | |
|         (2.0 * M_PI) / 3.0,
 | |
|         -(2.0 * M_PI) / 3.0
 | |
|     };
 | |
| 
 | |
|     FillContext(const Octree &octree, double z_position, int direction_idx) :
 | |
|         cubes_properties(octree.cubes_properties),
 | |
|         z_position(z_position),
 | |
|         traversal_order(child_traversal_order[direction_idx]),
 | |
|         cos_a(cos(direction_angles[direction_idx])),
 | |
|         sin_a(sin(direction_angles[direction_idx]))
 | |
|     {
 | |
|         static constexpr auto unused = std::numeric_limits<coord_t>::max();
 | |
|         temp_lines.assign((1 << octree.cubes_properties.size()) - 1, Line(Point(unused, unused), Point(unused, unused)));
 | |
|     }
 | |
| 
 | |
|     // Rotate the point, uses the same convention as Point::rotate().
 | |
|     Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); }
 | |
| 
 | |
|     const std::vector<CubeProperties>  &cubes_properties;
 | |
|     // Top of the current layer.
 | |
|     const double                        z_position;
 | |
|     // Order of traversal for this line direction.
 | |
|     const std::array<int, 8>            traversal_order;
 | |
|     // Rotation of the generated line for this line direction.
 | |
|     const double                        cos_a;
 | |
|     const double                        sin_a;
 | |
| 
 | |
|     // Linearized tree spanning a single Octree wall, used to connect lines spanning
 | |
|     // neighboring Octree cells. Unused lines have the Line::a::x set to infinity.
 | |
|     std::vector<Line>                   temp_lines;
 | |
|     // Final output
 | |
|     std::vector<Line>                   output_lines;
 | |
| };
 | |
| 
 | |
| static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 };
 | |
| 
 | |
| Eigen::Quaterniond transform_to_world()
 | |
| {
 | |
|     return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX());
 | |
| }
 | |
| 
 | |
| Eigen::Quaterniond transform_to_octree()
 | |
| {
 | |
|     return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ());
 | |
| }
 | |
| 
 | |
| #ifndef NDEBUG
 | |
| // Verify that the traversal order of the octree children matches the line direction,
 | |
| // therefore the infill line may get extended with O(1) time & space complexity.
 | |
| static bool verify_traversal_order(
 | |
|     FillContext  &context,
 | |
|     const Cube   *cube,
 | |
|     int           depth,
 | |
|     const Vec2d  &line_from,
 | |
|     const Vec2d  &line_to)
 | |
| {
 | |
|     std::array<Vec3d, 8> c;
 | |
|     Eigen::Quaterniond to_world = transform_to_world();
 | |
|     for (int i = 0; i < 8; ++i) {
 | |
|         int j = context.traversal_order[i];
 | |
|         Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.)));
 | |
|         assert(!cube->children[j] || cube->children[j]->center.isApprox(cntr));
 | |
|         c[i] = cntr;
 | |
|     }
 | |
|     std::array<Vec3d, 10> dirs = {
 | |
|         c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0],
 | |
|         c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4]
 | |
|     };
 | |
|     assert(std::abs(dirs[4].z()) < 0.005);
 | |
|     assert(std::abs(dirs[9].z()) < 0.005);
 | |
|     assert(dirs[0].isApprox(dirs[3]));
 | |
|     assert(dirs[1].isApprox(dirs[2]));
 | |
|     assert(dirs[5].isApprox(dirs[8]));
 | |
|     assert(dirs[6].isApprox(dirs[7]));
 | |
|     Vec3d line_dir = Vec3d(line_to.x() - line_from.x(), line_to.y() - line_from.y(), 0.).normalized();
 | |
|     for (auto& dir : dirs) {
 | |
|         double d = dir.normalized().dot(line_dir);
 | |
|         assert(d > 0.7);
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| #endif // NDEBUG
 | |
| 
 | |
| static void generate_infill_lines_recursive(
 | |
|     FillContext     &context,
 | |
|     const Cube      *cube,
 | |
|     // Address of this wall in the octree,  used to address context.temp_lines.
 | |
|     int              address,
 | |
|     int              depth)
 | |
| {
 | |
|     assert(cube != nullptr);
 | |
| 
 | |
|     const std::vector<CubeProperties> &cubes_properties = context.cubes_properties;
 | |
|     const double z_diff     = context.z_position - cube->center.z();
 | |
|     const double z_diff_abs = std::abs(z_diff);
 | |
| 
 | |
|     if (z_diff_abs > cubes_properties[depth].height / 2.)
 | |
|         return;
 | |
| 
 | |
|     if (z_diff_abs < cubes_properties[depth].line_z_distance) {
 | |
|         // Discretize a single wall splitting the cube into two.
 | |
|         const double zdist = cubes_properties[depth].line_z_distance;
 | |
|         Vec2d from(
 | |
|             0.5 * cubes_properties[depth].diagonal_length * (zdist - z_diff_abs) / zdist,
 | |
|             cubes_properties[depth].line_xy_distance - (zdist + z_diff) / sqrt(2.));
 | |
|         Vec2d to(-from.x(), from.y());
 | |
|         from = context.rotate(from);
 | |
|         to   = context.rotate(to);
 | |
|         // Relative to cube center
 | |
|         const Vec2d offset(cube->center.x(), cube->center.y());
 | |
|         from += offset;
 | |
|         to   += offset;
 | |
|         // Verify that the traversal order of the octree children matches the line direction,
 | |
|         // therefore the infill line may get extended with O(1) time & space complexity.
 | |
|         assert(verify_traversal_order(context, cube, depth, from, to));
 | |
|         // Either extend an existing line or start a new one.
 | |
|         Line &last_line = context.temp_lines[address];
 | |
|         Line  new_line(Point::new_scale(from), Point::new_scale(to));
 | |
|         if (last_line.a.x() == std::numeric_limits<coord_t>::max()) {
 | |
|             last_line.a = new_line.a;
 | |
|         } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough
 | |
|             context.output_lines.emplace_back(last_line);
 | |
|             last_line.a = new_line.a;
 | |
|         }
 | |
|         last_line.b = new_line.b;
 | |
|     }
 | |
| 
 | |
|     // left child index
 | |
|     address = address * 2 + 1;
 | |
|     -- depth;
 | |
|     size_t i = 0;
 | |
|     for (const int child_idx : context.traversal_order) {
 | |
|         const Cube *child = cube->children[child_idx];
 | |
|         if (child != nullptr)
 | |
|             generate_infill_lines_recursive(context, child, address, depth);
 | |
|         if (++ i == 4)
 | |
|             // right child index
 | |
|             ++ address;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #ifndef NDEBUG
 | |
| //    #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
 | |
| #endif
 | |
| 
 | |
| #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
 | |
| static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path)
 | |
| {
 | |
|     BoundingBox bbox = get_extents(expoly);
 | |
|     bbox.offset(scale_(3.));
 | |
| 
 | |
|     ::Slic3r::SVG svg(path, bbox);
 | |
|     svg.draw(expoly);
 | |
|     svg.draw_outline(expoly, "green");
 | |
|     svg.draw(polylines, "red");
 | |
|     static constexpr double trim_length = scale_(0.4);
 | |
|     for (Polyline polyline : polylines) {
 | |
|         Vec2d a = polyline.points.front().cast<double>();
 | |
|         Vec2d d = polyline.points.back().cast<double>();
 | |
|         if (polyline.size() == 2) {
 | |
|             Vec2d v = d - a;
 | |
|             double l = v.norm();
 | |
|             if (l > 2. * trim_length) {
 | |
|                 a += v * trim_length / l;
 | |
|                 d -= v * trim_length / l;
 | |
|                 polyline.points.front() = a.cast<coord_t>();
 | |
|                 polyline.points.back() = d.cast<coord_t>();
 | |
|             } else
 | |
|                 polyline.points.clear();
 | |
|         } else if (polyline.size() > 2) {
 | |
|             Vec2d b = polyline.points[1].cast<double>();
 | |
|             Vec2d c = polyline.points[polyline.points.size() - 2].cast<double>();
 | |
|             Vec2d v = b - a;
 | |
|             double l = v.norm();
 | |
|             if (l > trim_length) {
 | |
|                 a += v * trim_length / l;
 | |
|                 polyline.points.front() = a.cast<coord_t>();
 | |
|             } else
 | |
|                 polyline.points.erase(polyline.points.begin());
 | |
|             v = d - c;
 | |
|             l = v.norm();
 | |
|             if (l > trim_length)
 | |
|                 polyline.points.back() = (d - v * trim_length / l).cast<coord_t>();
 | |
|             else
 | |
|                 polyline.points.pop_back();
 | |
|         }
 | |
|         svg.draw(polyline, "black");
 | |
|     }
 | |
| }
 | |
| #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
 | |
| 
 | |
| void Filler::_fill_surface_single(
 | |
|     const FillParams &             params,
 | |
|     unsigned int                   thickness_layers,
 | |
|     const std::pair<float, Point> &direction,
 | |
|     ExPolygon                     &expolygon,
 | |
|     Polylines                     &polylines_out)
 | |
| {
 | |
|     assert (this->adapt_fill_octree);
 | |
| 
 | |
|     Polylines all_polylines;
 | |
|     {
 | |
|         // 3 contexts for three directions of infill lines
 | |
|         std::array<FillContext, 3> contexts { 
 | |
|             FillContext { *adapt_fill_octree, this->z, 0 },
 | |
|             FillContext { *adapt_fill_octree, this->z, 1 },
 | |
|             FillContext { *adapt_fill_octree, this->z, 2 }
 | |
|         };
 | |
|         // Generate the infill lines along the octree cells, merge touching lines of the same direction.
 | |
|         size_t num_lines = 0;
 | |
|         for (auto &context : contexts) {
 | |
|             generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1);
 | |
|             num_lines += context.output_lines.size() + context.temp_lines.size();
 | |
|         }
 | |
|         // Collect the lines.
 | |
|         std::vector<Line> lines;
 | |
|         lines.reserve(num_lines);
 | |
|         for (auto &context : contexts) {
 | |
|             append(lines, context.output_lines);
 | |
|             for (const Line &line : context.temp_lines)
 | |
|                 if (line.a.x() != std::numeric_limits<coord_t>::max())
 | |
|                     lines.emplace_back(line);
 | |
|         }
 | |
| #if 0
 | |
|         // Chain touching line segments, convert lines to polylines.
 | |
|         //all_polylines = chain_lines(lines, 300.); // SCALED_EPSILON is 100 and it is not enough
 | |
| #else
 | |
|         // Convert lines to polylines.
 | |
|         all_polylines.reserve(lines.size());
 | |
|         std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; });
 | |
| #endif
 | |
|     }
 | |
| 
 | |
|     // Crop all polylines
 | |
|     all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon));
 | |
| 
 | |
| #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
 | |
|     {
 | |
|         static int iRun = 0;
 | |
|         export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++));
 | |
|     }
 | |
| #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
 | |
| 
 | |
|     if (params.dont_connect || all_polylines.size() <= 1)
 | |
|         append(polylines_out, std::move(all_polylines));
 | |
|     else
 | |
|         connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params);
 | |
| 
 | |
| #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT
 | |
|     {
 | |
|         static int iRun = 0;
 | |
|         export_infill_lines_to_svg(expolygon, polylines_out, debug_out_path("FillAdaptive-final-%d.svg", iRun ++));
 | |
|     }
 | |
| #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */
 | |
| }
 | |
| 
 | |
| static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er)
 | |
| {
 | |
|     const auto p = (bbox.min - center);
 | |
|     const auto s = bbox.size();
 | |
|     double r2max = 0.;
 | |
|     for (int i = 0; i < 8; ++ i)
 | |
|         r2max = std::max(r2max, (p + Vec3d(s.x() * double(i & 1), s.y() * double(i & 2), s.z() * double(i & 4))).squaredNorm());
 | |
|     return sqrt(r2max);
 | |
| }
 | |
| 
 | |
| static std::vector<CubeProperties> make_cubes_properties(double max_cube_edge_length, double line_spacing)
 | |
| {
 | |
|     max_cube_edge_length += EPSILON;
 | |
| 
 | |
|     std::vector<CubeProperties> cubes_properties;
 | |
|     for (double edge_length = line_spacing * 2.;; edge_length *= 2.)
 | |
|     {
 | |
|         CubeProperties props{};
 | |
|         props.edge_length = edge_length;
 | |
|         props.height = edge_length * sqrt(3);
 | |
|         props.diagonal_length = edge_length * sqrt(2);
 | |
|         props.line_z_distance = edge_length / sqrt(3);
 | |
|         props.line_xy_distance = edge_length / sqrt(6);
 | |
|         cubes_properties.emplace_back(props);
 | |
|         if (edge_length > max_cube_edge_length)
 | |
|             break;
 | |
|     }
 | |
|     return cubes_properties;
 | |
| }
 | |
| 
 | |
| static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &up)
 | |
| {
 | |
|     // Calculate triangle normal.
 | |
|     auto n = (b - a).cross(c - b);
 | |
|     return n.dot(up) > 0.707 * n.norm();
 | |
| }
 | |
| 
 | |
| static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot)
 | |
| {
 | |
| #ifndef NDEBUG
 | |
|     current_cube->center_octree = current_cube->center;
 | |
| #endif // NDEBUG
 | |
|     current_cube->center = rot * current_cube->center;
 | |
|     for (auto *child : current_cube->children)
 | |
|         if (child)
 | |
|             transform_center(child, rot);
 | |
| }
 | |
| 
 | |
| OctreePtr build_octree(
 | |
|     // Mesh is rotated to the coordinate system of the octree.
 | |
|     const indexed_triangle_set  &triangle_mesh,
 | |
|     // Overhang triangles extracted from fill surfaces with stInternalBridge type,
 | |
|     // rotated to the coordinate system of the octree.
 | |
|     const std::vector<Vec3d>    &overhang_triangles, 
 | |
|     coordf_t                     line_spacing,
 | |
|     bool                         support_overhangs_only)
 | |
| {
 | |
|     assert(line_spacing > 0);
 | |
|     assert(! std::isnan(line_spacing));
 | |
| 
 | |
|     BoundingBox3Base<Vec3f>     bbox(triangle_mesh.vertices);
 | |
|     Vec3d                       cube_center      = bbox.center().cast<double>();
 | |
|     std::vector<CubeProperties> cubes_properties = make_cubes_properties(double(bbox.size().maxCoeff()), line_spacing);
 | |
|     auto                        octree           = OctreePtr(new Octree(cube_center, cubes_properties));
 | |
| 
 | |
|     if (cubes_properties.size() > 1) {
 | |
|         Octree *octree_ptr = octree.get();
 | |
|         double edge_length_half = 0.5 * cubes_properties.back().edge_length;
 | |
|         Vec3d  diag_half(edge_length_half, edge_length_half, edge_length_half);
 | |
|         int    max_depth = int(cubes_properties.size()) - 1;
 | |
|         auto process_triangle = [octree_ptr, max_depth, diag_half](const Vec3d &a, const Vec3d &b, const Vec3d &c) {
 | |
|             octree_ptr->insert_triangle(
 | |
|                 a, b, c,
 | |
|                 octree_ptr->root_cube,
 | |
|                 BoundingBoxf3(octree_ptr->root_cube->center - diag_half, octree_ptr->root_cube->center + diag_half),
 | |
|                 max_depth);
 | |
|         };
 | |
|         auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d();
 | |
|         for (auto &tri : triangle_mesh.indices) {
 | |
|             auto a = triangle_mesh.vertices[tri[0]].cast<double>();
 | |
|             auto b = triangle_mesh.vertices[tri[1]].cast<double>();
 | |
|             auto c = triangle_mesh.vertices[tri[2]].cast<double>();
 | |
|             if (! support_overhangs_only || is_overhang_triangle(a, b, c, up_vector))
 | |
|                 process_triangle(a, b, c);
 | |
|         }
 | |
|         for (size_t i = 0; i < overhang_triangles.size(); i += 3)
 | |
|             process_triangle(overhang_triangles[i], overhang_triangles[i + 1], overhang_triangles[i + 2]);
 | |
|         {
 | |
|             // Transform the octree to world coordinates to reduce computation when extracting infill lines.
 | |
|             auto rot = transform_to_world().toRotationMatrix();
 | |
|             transform_center(octree->root_cube, rot);
 | |
|             octree->origin = rot * octree->origin;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return octree;
 | |
| }
 | |
| 
 | |
| void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth)
 | |
| {
 | |
|     assert(current_cube);
 | |
|     assert(depth > 0);
 | |
| 
 | |
|     // Squared radius of a sphere around the child cube.
 | |
|     const double r2_cube = Slic3r::sqr(0.5 * this->cubes_properties[-- depth].height + EPSILON);
 | |
| 
 | |
|     for (size_t i = 0; i < 8; ++ i) {
 | |
|         const Vec3d &child_center_dir = child_centers[i];
 | |
|         // Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors.
 | |
|         // We will rather densify the octree a bit more than necessary instead of missing a triangle.
 | |
|         BoundingBoxf3 bbox;
 | |
|         for (int k = 0; k < 3; ++ k) {
 | |
|             if (child_center_dir[k] == -1.) {
 | |
|                 bbox.min[k] = current_bbox.min[k];
 | |
|                 bbox.max[k] = current_cube->center[k] + EPSILON;
 | |
|             } else {
 | |
|                 bbox.min[k] = current_cube->center[k] - EPSILON;
 | |
|                 bbox.max[k] = current_bbox.max[k];
 | |
|             }
 | |
|         }
 | |
|         Vec3d child_center = current_cube->center + (child_center_dir * (this->cubes_properties[depth].edge_length / 2.));
 | |
|         //if (dist2_to_triangle(a, b, c, child_center) < r2_cube) {
 | |
|         if (triangle_AABB_intersects(a, b, c, bbox)) {
 | |
|             if (! current_cube->children[i])
 | |
|                 current_cube->children[i] = this->pool.construct(child_center);
 | |
|             if (depth > 0)
 | |
|                 this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| } // namespace FillAdaptive
 | |
| } // namespace Slic3r
 | 
