diff --git a/include/InsetOrderOptimizer.h b/include/InsetOrderOptimizer.h index 3b242d8900..3a28040376 100644 --- a/include/InsetOrderOptimizer.h +++ b/include/InsetOrderOptimizer.h @@ -56,6 +56,7 @@ class InsetOrderOptimizer const size_t wall_x_extruder_nr, const ZSeamConfig& z_seam_config, const std::vector& paths, + const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams = {}); /*! @@ -107,6 +108,7 @@ class InsetOrderOptimizer const ZSeamConfig& z_seam_config_; const std::vector& paths_; const LayerIndex layer_nr_; + const Point2LL model_center_point_; // Center of the model (= all meshes) axis-aligned bounding-box. Shape disallowed_areas_for_seams_; std::vector> inset_polys_; // vector of vectors holding the inset polygons @@ -119,8 +121,12 @@ class InsetOrderOptimizer * 'best' vertex on that polygon. Under certain circumstances, the seam-placing algorithm can * however still deviate from this, for example when the seam-point placed here isn't suppored * by the layer below. + * + * \param closed_line The polygon to insert the seam point in. (It's assumed to be closed at least.) + * + * \return The index of the inserted seam point, or std::nullopt if no seam point was inserted. */ - void insertSeamPoint(ExtrusionLine& closed_line); + std::optional insertSeamPoint(ExtrusionLine& closed_line); /*! * Determine if the paths should be reversed diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index cf54338a06..be8e61873b 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -130,10 +130,11 @@ class PathOrderOptimizer * Add a new polygon to be optimized. * \param polygon The polygon to optimize. */ - void addPolygon(const Path& polygon) + void addPolygon(const Path& polygon, std::optional force_start_index = std::nullopt) { constexpr bool is_closed = true; paths_.emplace_back(polygon, is_closed); + paths_.back().force_start_index_ = force_start_index; } /*! @@ -695,6 +696,12 @@ class PathOrderOptimizer return vert; } + if (path.force_start_index_.has_value()) + { + // Start index already known, since we forced it, return. + return path.force_start_index_.value(); + } + // Precompute segments lengths because we are going to need them multiple times std::vector segments_sizes(path.converted_->size()); coord_t total_length = 0; diff --git a/include/SkeletalTrapezoidationJoint.h b/include/SkeletalTrapezoidationJoint.h index 059f6d50d1..3aa2ff88e3 100644 --- a/include/SkeletalTrapezoidationJoint.h +++ b/include/SkeletalTrapezoidationJoint.h @@ -51,7 +51,7 @@ class SkeletalTrapezoidationJoint { beading_ = storage; } - std::shared_ptr getBeading() + std::shared_ptr getBeading() const { return beading_.lock(); } diff --git a/include/WallToolPaths.h b/include/WallToolPaths.h index ff1df0a63a..1ef9db161b 100644 --- a/include/WallToolPaths.h +++ b/include/WallToolPaths.h @@ -110,9 +110,9 @@ class WallToolPaths static void stitchToolPaths(std::vector& toolpaths, const Settings& settings); /*! - * Remove polylines shorter than half the smallest line width along that polyline. + * Remove polylines shorter than half the smallest line width along that polyline, if that polyline isn't part of an outer wall. */ - static void removeSmallLines(std::vector& toolpaths); + static void removeSmallFillLines(std::vector& toolpaths); /*! * Simplifies the variable-width toolpaths by calling the simplify on every line in the toolpath using the provided diff --git a/include/path_ordering.h b/include/path_ordering.h index 5a5de96e17..0a7fb1af13 100644 --- a/include/path_ordering.h +++ b/include/path_ordering.h @@ -76,6 +76,12 @@ struct PathOrdering */ bool backwards_; + /*! + * Force the start point of the path to be at a specific location. + * Will only happen if not empty, and this point is actually on the path. + */ + std::optional force_start_index_; + /*! * Get vertex data from the custom path type. * diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 669e6829d0..be95224cbf 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -425,6 +425,11 @@ class SliceDataStorage : public NoCopy const int extruder_nr = -1, const bool include_models = true) const; + /*! + * Get the axis-aligned bounding-box of the complete model (all meshes). + */ + AABB3D getModelBoundingBox() const; + /*! * Get the extruders used. * diff --git a/include/utils/linearAlg2D.h b/include/utils/linearAlg2D.h index b9f08fb45b..56beab5608 100644 --- a/include/utils/linearAlg2D.h +++ b/include/utils/linearAlg2D.h @@ -67,6 +67,21 @@ class LinearAlg2D return -1; } + /*! + * A single-shot line-segment/line-segment intersection that returns the parameters and doesn't require a grid-calculation beforehand. + * + * \param p1 The start point of the first line segment. + * \param p2 The end point of the first line segment. + * \param p3 The start point of the second line segment. + * \param p4 The end point of the second line segment. + * \param t The parameter of the intersection on the first line segment (intersection = p1 + t * (p2 - p1)). + * \param u The parameter of the intersection on the second line segment (intersection = p3 + u * (p4 - p3)). + * + * \return Whether the two line segments intersect. + */ + static bool segmentSegmentIntersection(const Point2LL& p1, const Point2LL& p2, const Point2LL& p3, const Point2LL& p4, float& t, float& u); + static bool lineLineIntersection(const Point2LL& p1, const Point2LL& p2, const Point2LL& p3, const Point2LL& p4, float& t, float& u); + static bool lineLineIntersection(const Point2LL& a, const Point2LL& b, const Point2LL& c, const Point2LL& d, Point2LL& output); /*! diff --git a/include/utils/polygonUtils.h b/include/utils/polygonUtils.h index 825bc62e79..7ed9d92787 100644 --- a/include/utils/polygonUtils.h +++ b/include/utils/polygonUtils.h @@ -674,7 +674,16 @@ class PolygonUtils * \param a_step The angle between segments of the circle. * \return A new Polygon containing the circle. */ - static Polygon makeCircle(const Point2LL mid, const coord_t radius, const AngleRadians a_step = std::numbers::pi / 8); + template + static T makeCircle(const Point2LL& mid, const coord_t radius, const AngleRadians a_step = std::numbers::pi / 8, VA... args) + { + T circle; + for (double a = 0; a < 2 * std::numbers::pi; a += a_step) + { + circle.emplace_back(mid + Point2LL(radius * cos(a), radius * sin(a)), args...); + } + return circle; + } /*! * Create a "wheel" shape. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 43bb607980..de322d3679 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -709,7 +709,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) base_extruder_nr, base_extruder_nr, z_seam_config, - raft_paths); + raft_paths, + storage.getModelBoundingBox().flatten().getMiddle()); wall_orderer.addToLayer(); } @@ -868,7 +869,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) interface_extruder_nr, interface_extruder_nr, z_seam_config, - raft_paths); + raft_paths, + storage.getModelBoundingBox().flatten().getMiddle()); wall_orderer.addToLayer(); } @@ -1035,7 +1037,8 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) surface_extruder_nr, surface_extruder_nr, z_seam_config, - raft_paths); + raft_paths, + storage.getModelBoundingBox().flatten().getMiddle()); wall_orderer.addToLayer(); } @@ -2328,7 +2331,8 @@ bool FffGcodeWriter::processSingleLayerInfill( extruder_nr, extruder_nr, z_seam_config, - tool_paths); + tool_paths, + storage.getModelBoundingBox().flatten().getMiddle()); added_something |= wall_orderer.addToLayer(); } } @@ -2797,7 +2801,8 @@ bool FffGcodeWriter::processInsets( mesh.settings.get("wall_0_extruder_nr").extruder_nr_, mesh.settings.get("wall_x_extruder_nr").extruder_nr_, z_seam_config, - part.wall_toolpaths); + part.wall_toolpaths, + storage.getModelBoundingBox().flatten().getMiddle()); added_something |= wall_orderer.addToLayer(); } return added_something; @@ -3222,7 +3227,8 @@ void FffGcodeWriter::processSkinPrintFeature( skin_extruder_nr, skin_extruder_nr, z_seam_config, - skin_paths); + skin_paths, + storage.getModelBoundingBox().flatten().getMiddle()); added_something |= wall_orderer.addToLayer(); } } @@ -3513,6 +3519,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer extruder_nr, z_seam_config, wall_toolpaths, + storage.getModelBoundingBox().flatten().getMiddle(), disallowed_area_for_seams); added_something |= wall_orderer.addToLayer(); } @@ -3693,7 +3700,8 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer extruder_nr, extruder_nr, z_seam_config, - wall_toolpaths_here); + wall_toolpaths_here, + storage.getModelBoundingBox().flatten().getMiddle()); added_something |= wall_orderer.addToLayer(); } } @@ -3828,7 +3836,8 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, con roof_extruder_nr, roof_extruder_nr, z_seam_config, - roof_paths); + roof_paths, + storage.getModelBoundingBox().flatten().getMiddle()); wall_orderer.addToLayer(); } gcode_layer.addLinesByOptimizer(roof_lines, current_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); @@ -3941,7 +3950,8 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L bottom_extruder_nr, bottom_extruder_nr, z_seam_config, - bottom_paths); + bottom_paths, + storage.getModelBoundingBox().flatten().getMiddle()); wall_orderer.addToLayer(); } gcode_layer.addLinesByOptimizer( diff --git a/src/InsetOrderOptimizer.cpp b/src/InsetOrderOptimizer.cpp index c093aae5d2..ad036f62b9 100644 --- a/src/InsetOrderOptimizer.cpp +++ b/src/InsetOrderOptimizer.cpp @@ -51,6 +51,7 @@ InsetOrderOptimizer::InsetOrderOptimizer( const size_t wall_x_extruder_nr, const ZSeamConfig& z_seam_config, const std::vector& paths, + const Point2LL& model_center_point, const Shape& disallowed_areas_for_seams) : gcode_writer_(gcode_writer) , storage_(storage) @@ -71,6 +72,7 @@ InsetOrderOptimizer::InsetOrderOptimizer( , z_seam_config_(z_seam_config) , paths_(paths) , layer_nr_(gcode_layer.getLayerNr()) + , model_center_point_(model_center_point) , disallowed_areas_for_seams_{ disallowed_areas_for_seams } { } @@ -114,11 +116,13 @@ bool InsetOrderOptimizer::addToLayer() { if (line.is_closed_) { + std::optional force_start; if (! settings_.get("z_seam_on_vertex")) { - insertSeamPoint(line); + // If the user indicated that we may deviate from the vertices for the seam, we can insert a seam point, if needed. + force_start = insertSeamPoint(line); } - order_optimizer.addPolygon(&line); + order_optimizer.addPolygon(&line, force_start); } else { @@ -168,7 +172,7 @@ bool InsetOrderOptimizer::addToLayer() return added_something; } -void InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed_line) +std::optional InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed_line) { assert(closed_line.is_closed_); assert(closed_line.size() >= 3); @@ -183,30 +187,77 @@ void InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed_line) request_point = gcode_layer_.getLastPlannedPositionOrStartingPosition(); break; default: - return; + return std::nullopt; } - // NOTE: Maybe rewrite this once we can use C++23 ranges::views::adjacent + // Find the 'closest' point on the polygon to the request_point. + Point2LL closest_point; size_t closest_junction_idx = 0; coord_t closest_distance_sqd = std::numeric_limits::max(); - for (const auto& [i, junction] : closed_line.junctions_ | ranges::views::enumerate) + bool should_reclaculate_closest = false; + if (z_seam_config_.type_ == EZSeamType::USER_SPECIFIED) { - const auto& next_junction = closed_line.junctions_[(i + 1) % closed_line.junctions_.size()]; - const coord_t distance_sqd = LinearAlg2D::getDist2FromLineSegment(junction.p_, request_point, next_junction.p_); - if (distance_sqd < closest_distance_sqd) + // For user-defined seams you usually don't _actually_ want the _closest_ point, per-se, + // since you want the seam-line to be continuous in 3D space. + // To that end, take the center of the 3D model (not of the current polygon, as that would give the same problems) + // and project the point along the ray from the center to the request_point. + + const Point2LL ray_origin = model_center_point_; + request_point = ray_origin + (request_point - ray_origin) * 10; + + for (const auto& [i, junction] : closed_line.junctions_ | ranges::views::enumerate) + { + // NOTE: Maybe rewrite this once we can use C++23 ranges::views::adjacent + const auto& next_junction = closed_line.junctions_[(i + 1) % closed_line.junctions_.size()]; + + float t, u; + if (LinearAlg2D::segmentSegmentIntersection(ray_origin, request_point, junction.p_, next_junction.p_, t, u)) + { + const Point2LL intersection = ray_origin + (request_point - ray_origin) * t; + const coord_t distance_sqd = vSize2(request_point - intersection); + if (distance_sqd < closest_distance_sqd) + { + closest_point = intersection; + closest_distance_sqd = distance_sqd; + closest_junction_idx = i; + } + } + } + } + if (closest_distance_sqd >= std::numeric_limits::max()) + { + // If it the method isn't 'user-defined', or the attempt to do user-defined above failed + // (since we don't take the center of the polygon, but of the model, there's a chance there's no intersection), + // then just find the closest point on the polygon. + + for (const auto& [i, junction] : closed_line.junctions_ | ranges::views::enumerate) { - closest_distance_sqd = distance_sqd; - closest_junction_idx = i; + const auto& next_junction = closed_line.junctions_[(i + 1) % closed_line.junctions_.size()]; + const coord_t distance_sqd = LinearAlg2D::getDist2FromLineSegment(junction.p_, request_point, next_junction.p_); + if (distance_sqd < closest_distance_sqd) + { + closest_distance_sqd = distance_sqd; + closest_junction_idx = i; + } } + should_reclaculate_closest = true; } const auto& start_pt = closed_line.junctions_[closest_junction_idx]; const auto& end_pt = closed_line.junctions_[(closest_junction_idx + 1) % closed_line.junctions_.size()]; - const auto closest_point = LinearAlg2D::getClosestOnLineSegment(request_point, start_pt.p_, end_pt.p_); + if (should_reclaculate_closest) + { + // In the second case (see above) the closest point hasn't actually been calculated yet, + // since in that case we'de need the start and end points. So do that here. + closest_point = LinearAlg2D::getClosestOnLineSegment(request_point, start_pt.p_, end_pt.p_); + } constexpr coord_t smallest_dist_sqd = 25; if (vSize2(closest_point - start_pt.p_) <= smallest_dist_sqd || vSize2(closest_point - end_pt.p_) <= smallest_dist_sqd) { - return; + // Early out if the closest point is too close to the start or end point. + // NOTE: Maybe return the index here anyway, since this is the point the current caller would want to force the seam to. + // However, then the index returned would have a caveat that it _can_ point to an already exisiting point then. + return std::nullopt; } // NOTE: This could also be done on a single axis (skipping the implied sqrt), but figuring out which one and then using the right values became a bit messy/verbose. @@ -216,6 +267,7 @@ void InsetOrderOptimizer::insertSeamPoint(ExtrusionLine& closed_line) const coord_t w = ((end_pt.w_ * end_dist) / total_dist) + ((start_pt.w_ * start_dist) / total_dist); closed_line.junctions_.insert(closed_line.junctions_.begin() + closest_junction_idx + 1, ExtrusionJunction(closest_point, w, start_pt.perimeter_index_)); + return closest_junction_idx + 1; } InsetOrderOptimizer::value_type InsetOrderOptimizer::getRegionOrder(const std::vector& extrusion_lines, const bool outer_to_inner) diff --git a/src/SkeletalTrapezoidation.cpp b/src/SkeletalTrapezoidation.cpp index 46454c3eb6..2bb4f2ccca 100644 --- a/src/SkeletalTrapezoidation.cpp +++ b/src/SkeletalTrapezoidation.cpp @@ -17,6 +17,7 @@ #include "utils/VoronoiUtils.h" #include "utils/linearAlg2D.h" #include "utils/macros.h" +#include "utils/polygonUtils.h" #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX \ 1000 // A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing @@ -2184,39 +2185,83 @@ void SkeletalTrapezoidation::generateLocalMaximaSingleBeads() { std::vector& generated_toolpaths = *p_generated_toolpaths; - for (auto& node : graph_.nodes) + const auto addCircleToToolpath = [&](const Point2LL& center, coord_t width, size_t inset_index) + { + if (inset_index >= generated_toolpaths.size()) + { + generated_toolpaths.resize(inset_index + 1); + } + constexpr bool is_odd = true; + generated_toolpaths[inset_index].emplace_back(inset_index, is_odd); + ExtrusionLine& line = generated_toolpaths[inset_index].back(); + // total area to be extruded is pi*(w/2)^2 = pi*w*w/4 + // Width a constant extrusion width w, that would be a length of pi*w/4 + // If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r + // So our circle needs to be such that r=w/8 + const coord_t r = width / 8; + constexpr coord_t n_segments = 6; + const auto circle = PolygonUtils::makeCircle>(center, r, 2 * std::numbers::pi / n_segments, width, inset_index); + line.junctions_.insert(line.junctions_.end(), circle.begin(), circle.end()); + }; + + Point2LL local_maxima_accumulator; + coord_t width_accumulator = 0; + size_t accumulator_count = 0; + + for (const auto& node : graph_.nodes) { if (! node.data_.hasBeading()) { continue; } - Beading& beading = node.data_.getBeading()->beading_; - if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true) && ! node.isCentral()) + const Beading& beading = node.data_.getBeading()->beading_; + if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true)) { const size_t inset_index = beading.bead_widths.size() / 2; - constexpr bool is_odd = true; - if (inset_index >= generated_toolpaths.size()) + const coord_t width = beading.bead_widths[inset_index]; + local_maxima_accumulator += node.p_; + width_accumulator += width; + ++accumulator_count; + if (! node.isCentral()) { - generated_toolpaths.resize(inset_index + 1); + addCircleToToolpath(node.p_, width, inset_index); } - generated_toolpaths[inset_index].emplace_back(inset_index, is_odd); - ExtrusionLine& line = generated_toolpaths[inset_index].back(); - const coord_t width = beading.bead_widths[inset_index]; - // total area to be extruded is pi*(w/2)^2 = pi*w*w/4 - // Width a constant extrusion width w, that would be a length of pi*w/4 - // If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r - // So our circle needs to be such that r=w/8 - const coord_t r = width / 8; - constexpr coord_t n_segments = 6; - for (coord_t segment = 0; segment < n_segments; segment++) + } + } + + if (accumulator_count > 0) + { + bool replace_with_local_maxima = generated_toolpaths.empty() || generated_toolpaths[0].empty(); + coord_t total_path_length = 0; + if (! replace_with_local_maxima) + { + coord_t min_width = std::numeric_limits::max(); + for (const auto& line : generated_toolpaths[0]) { - double a = 2.0 * std::numbers::pi / n_segments * segment; - line.junctions_.emplace_back(node.p_ + Point2LL(r * cos(a), r * sin(a)), width, inset_index); + total_path_length += line.length(); + for (const ExtrusionJunction& j : line) + { + min_width = std::min(min_width, j.w_); + } } + replace_with_local_maxima |= total_path_length <= min_width / 2; + } + if (replace_with_local_maxima) + { + const coord_t width = width_accumulator / accumulator_count; + local_maxima_accumulator = local_maxima_accumulator / accumulator_count; + if (generated_toolpaths.empty()) + { + generated_toolpaths.emplace_back(); + } + else + { + generated_toolpaths[0].clear(); + } + addCircleToToolpath(local_maxima_accumulator, width, 0); } } } - // // ^^^^^^^^^^^^^^^^^^^^^ // TOOLPATH GENERATION diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 8203ccb2ce..15a6b03cf8 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -185,7 +185,8 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage extruder_nr, extruder_nr, z_seam_config, - ironing_paths); + ironing_paths, + storage.getModelBoundingBox().flatten().getMiddle()); wall_orderer.addToLayer(); added = true; } diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index b144d0ebfb..c34aaf8d69 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -181,7 +181,7 @@ const std::vector& WallToolPaths::generate() scripta::PointVDI{ "width", &ExtrusionJunction::w_ }, scripta::PointVDI{ "perimeter_index", &ExtrusionJunction::perimeter_index_ }); - removeSmallLines(toolpaths_); + removeSmallFillLines(toolpaths_); scripta::log( "toolpaths_2", toolpaths_, @@ -274,13 +274,17 @@ void WallToolPaths::stitchToolPaths(std::vector& toolpaths, } } -void WallToolPaths::removeSmallLines(std::vector& toolpaths) +void WallToolPaths::removeSmallFillLines(std::vector& toolpaths) { for (VariableWidthLines& inset : toolpaths) { for (size_t line_idx = 0; line_idx < inset.size(); line_idx++) { ExtrusionLine& line = inset[line_idx]; + if (line.is_outer_wall()) + { + continue; + } coord_t min_width = std::numeric_limits::max(); for (const ExtrusionJunction& j : line) { diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index b50bcc5c26..d89edfbceb 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -376,6 +376,16 @@ Shape SliceDataStorage::getLayerOutlines( } } +AABB3D SliceDataStorage::getModelBoundingBox() const +{ + AABB3D bounding_box; + for (const auto& mesh : meshes) + { + bounding_box.include(mesh->bounding_box); + } + return bounding_box; +} + std::vector SliceDataStorage::getExtrudersUsed() const { std::vector ret; diff --git a/src/utils/linearAlg2D.cpp b/src/utils/linearAlg2D.cpp index 64b50cee10..3fccba9e00 100644 --- a/src/utils/linearAlg2D.cpp +++ b/src/utils/linearAlg2D.cpp @@ -272,28 +272,43 @@ Point3Matrix LinearAlg2D::rotateAround(const Point2LL& middle, double rotation) return Point3Matrix::translate(middle).compose(rotation_matrix_homogeneous).compose(Point3Matrix::translate(-middle)); } -bool LinearAlg2D::lineLineIntersection(const Point2LL& a, const Point2LL& b, const Point2LL& c, const Point2LL& d, Point2LL& output) +bool LinearAlg2D::lineLineIntersection(const Point2LL& p1, const Point2LL& p2, const Point2LL& p3, const Point2LL& p4, float& t, float& u) { - // Adapted from Apex: https://github.com/Ghostkeeper/Apex/blob/eb75f0d96e36c7193d1670112826842d176d5214/include/apex/line_segment.hpp#L91 - // Adjusted to work with lines instead of line segments. - const Point2LL l1_delta = b - a; - const Point2LL l2_delta = d - c; - const coord_t divisor = cross(l1_delta, l2_delta); // Pre-compute divisor needed for the intersection check. - if (divisor == 0) + const float x1mx2 = p1.X - p2.X; + const float x1mx3 = p1.X - p3.X; + const float x3mx4 = p3.X - p4.X; + const float y1my2 = p1.Y - p2.Y; + const float y1my3 = p1.Y - p3.Y; + const float y3my4 = p3.Y - p4.Y; + + t = x1mx3 * y3my4 - y1my3 * x3mx4; + u = x1mx3 * y1my2 - y1my3 * x1mx2; + const float div = x1mx2 * y3my4 - y1my2 * x3mx4; + if (div == 0.0f) { - // The lines are parallel if the cross product of their directions is zero. return false; } - // Create a parametric representation of each line. - // We'll equate the parametric equations to each other to find the intersection then. - // Parametric equation is L = P + Vt (where P and V are a starting point and directional vector). - // We'll map the starting point of one line onto the parameter system of the other line. - // Then using the divisor we can see whether and where they cross. - const Point2LL starts_delta = a - c; - const coord_t l1_parametric = cross(l2_delta, starts_delta); - Point2LL result = a + Point2LL(round_divide_signed(l1_parametric * l1_delta.X, divisor), round_divide_signed(l1_parametric * l1_delta.Y, divisor)); + // NOTE: In theory the comparison 0 <= par <= 1 can now done without division for each parameter (as an early-out), + // but this is easier & when the intersection _does_ happen and we want the normalized parameters returned anyway. + t /= div; + u /= div; + return true; +} + +bool LinearAlg2D::segmentSegmentIntersection(const Point2LL& p1, const Point2LL& p2, const Point2LL& p3, const Point2LL& p4, float& t, float& u) +{ + return lineLineIntersection(p1, p2, p3, p4, t, u) && t >= 0.0f && u >= 0.0f && t <= 1.0f && u <= 1.0f; +} +bool LinearAlg2D::lineLineIntersection(const Point2LL& a, const Point2LL& b, const Point2LL& c, const Point2LL& d, Point2LL& output) +{ + float t, u; + if (! lineLineIntersection(a, b, c, d, t, u)) + { + return false; + } + const Point2LL result = a + (b - a) * t; if (std::abs(result.X) > std::numeric_limits::max() || std::abs(result.Y) > std::numeric_limits::max()) { // Intersection is so far away that it could lead to integer overflows. diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 84f367c586..3890055558 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1386,16 +1386,6 @@ double PolygonUtils::relativeHammingDistance(const Shape& poly_a, const Shape& p return hamming_distance / total_area; } -Polygon PolygonUtils::makeCircle(const Point2LL mid, const coord_t radius, const AngleRadians a_step) -{ - Polygon circle; - for (double a = 0; a < 2 * std::numbers::pi; a += a_step) - { - circle.emplace_back(mid + Point2LL(radius * cos(a), radius * sin(a))); - } - return circle; -} - Polygon PolygonUtils::makeWheel(const Point2LL& mid, const coord_t inner_radius, const coord_t outer_radius, const size_t semi_nb_spokes, const size_t arc_angle_resolution) { Polygon wheel;