Skip to content

Commit efc2bd7

Browse files
DusKing1fontanf
andauthored
Allow compute_intersections supports circular arcs (#185)
* allow compute_intersections supports circular arcs * Move compute_intersections into own file * Add some tests * Add 'strict' parameter and additional tests * Implement strict for line segments and add tests * Add some failing tests * Add more tests * Add more tests * Move is_point_on_arc and is_point_on_line to ShapeElement::contains * Clean tests * Move ShapeElement::contains * Move is_point_strictly_inside_shape to Shape::contains * handle the arc overlap properly * remove duplicated intersection points from the result * not to return start/end point in strict mode * fixed the wrong tests * fixed test 67 * Fix remaining failing test and clean code --------- Co-authored-by: Florian Fontan <dev@florian-fontan.fr>
1 parent 784fee5 commit efc2bd7

File tree

11 files changed

+1218
-102
lines changed

11 files changed

+1218
-102
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
bazel-*
2+
build/*
23
.vscode
34
checker/checker
45
checker/logs/*

include/packingsolver/algorithms/common.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
#define _USE_MATH_DEFINES
44

5+
#ifndef M_PI
6+
#define M_PI 3.14159265358979323846
7+
#endif
8+
59
#include "optimizationtools/utils/output.hpp"
610

711
#include <cstdint>

include/packingsolver/irregular/shape.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ struct ShapeElement
140140
/** Length of the element. */
141141
LengthDbl length() const;
142142

143+
/** Check if a point is on the element. */
144+
bool contains(const Point& point) const;
145+
143146
ShapeElement rotate(Angle angle) const;
144147

145148
ShapeElement axial_symmetry_identity_line() const;
@@ -219,6 +222,11 @@ struct Shape
219222
Angle angle = 0.0,
220223
bool mirror = false) const;
221224

225+
/** Check if the shape contains a given point. */
226+
bool contains(
227+
const Point& point,
228+
bool strict = false) const;
229+
222230
/* Check if the shape is connected and in anticlockwise direction. */
223231
bool check() const;
224232

src/irregular/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ target_sources(PackingSolver_irregular PRIVATE
77
algorithm_formatter.cpp
88
optimize.cpp
99
shape.cpp
10+
shape_element_intersections.cpp
1011
shape_convex_hull.cpp
1112
shape_extract_borders.cpp
1213
shape_self_intersections_removal.cpp

src/irregular/shape.cpp

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,68 @@ LengthDbl ShapeElement::length() const
165165
return -1;
166166
}
167167

168+
bool ShapeElement::contains(const Point& point) const
169+
{
170+
switch (type) {
171+
case ShapeElementType::LineSegment: {
172+
// Calculate the squared length of the line segment
173+
LengthDbl line_length_squared = std::pow(end.x - start.x, 2) + std::pow(end.y - start.y, 2);
174+
175+
// If the line segment is actually a point
176+
if (equal(line_length_squared, 0.0)) {
177+
return equal(point.x, start.x) && equal(point.y, start.y);
178+
}
179+
180+
// Calculate parameter t, representing the position of the point on the line segment (between 0 and 1 indicates on the segment)
181+
LengthDbl t = ((point.x - start.x) * (end.x - start.x) + (point.y - start.y) * (end.y - start.y)) / line_length_squared;
182+
183+
// If t is outside the [0,1] range, the point is not on the line segment
184+
if (strictly_lesser(t, 0.0) || strictly_greater(t, 1.0)) {
185+
return false;
186+
}
187+
188+
// Calculate the projection point
189+
Point projection;
190+
projection.x = start.x + t * (end.x - start.x);
191+
projection.y = start.y + t * (end.y - start.y);
192+
193+
// Check if the distance to the projection point is small enough
194+
LengthDbl distance_squared = std::pow(point.x - projection.x, 2) + std::pow(point.y - projection.y, 2);
195+
return equal(distance_squared, 0.0);
196+
} case ShapeElementType::CircularArc: {
197+
// Check if point lies on circle
198+
if (!equal(distance(point, this->center), distance(this->start, this->center))) {
199+
return false;
200+
}
201+
202+
// Calculate angles
203+
Angle point_angle = angle_radian(point - this->center);
204+
Angle start_angle = angle_radian(this->start - this->center);
205+
Angle end_angle = angle_radian(this->end - this->center);
206+
207+
// Normalize angles
208+
if (this->anticlockwise) {
209+
if (end_angle <= start_angle) {
210+
end_angle += 2 * M_PI;
211+
}
212+
if (point_angle < start_angle) {
213+
point_angle += 2 * M_PI;
214+
}
215+
return point_angle >= start_angle && point_angle <= end_angle;
216+
} else {
217+
if (start_angle <= end_angle) {
218+
start_angle += 2 * M_PI;
219+
}
220+
if (point_angle < end_angle) {
221+
point_angle += 2 * M_PI;
222+
}
223+
return point_angle >= end_angle && point_angle <= start_angle;
224+
}
225+
}
226+
}
227+
return -1;
228+
}
229+
168230
std::string ShapeElement::to_string() const
169231
{
170232
switch (type) {
@@ -537,6 +599,95 @@ std::pair<Point, Point> Shape::compute_min_max(
537599
return {{x_min, y_min}, {x_max, y_max}};
538600
}
539601

602+
bool Shape::contains(
603+
const Point& point,
604+
bool strict) const
605+
{
606+
if (this->elements.empty())
607+
return false;
608+
609+
// First check if the point lies on any boundary.
610+
for (const ShapeElement& element : this->elements)
611+
if (element.contains(point))
612+
return (strict)? false: true;
613+
614+
// Then use the ray-casting algorithm to check if the point is inside
615+
int intersection_count = 0;
616+
for (const ShapeElement& element: this->elements) {
617+
if (element.type == ShapeElementType::LineSegment) {
618+
// Handle the special case of horizontal line segments
619+
if (equal(element.start.y, element.end.y)) {
620+
// Horizontal line segment: if the point's y coordinate equals the segment's y coordinate,
621+
// and the x coordinate is within the segment range, the point is on the segment
622+
if (equal(point.y, element.start.y)
623+
&& (!strictly_lesser(point.x, std::min(element.start.x, element.end.x)))
624+
&& (!strictly_greater(point.x, std::max(element.start.x, element.end.x)))) {
625+
// Already checked in the previous section, no need to handle here
626+
continue;
627+
}
628+
}
629+
630+
// Standard ray-casting algorithm for line segment checking
631+
// Cast a ray to the right from the point, count intersections with segments
632+
bool cond1 = (strictly_greater(element.start.y, point.y) != strictly_greater(element.end.y, point.y));
633+
bool cond2 = strictly_lesser(
634+
point.x,
635+
(element.end.x - element.start.x) * (point.y - element.start.y)
636+
/ (element.end.y - element.start.y) + element.start.x);
637+
638+
if (cond1 && cond2) {
639+
intersection_count++;
640+
}
641+
} else if (element.type == ShapeElementType::CircularArc) {
642+
// Circular arc checking is more complex
643+
LengthDbl dx = point.x - element.center.x;
644+
LengthDbl dy = point.y - element.center.y;
645+
LengthDbl distance = std::sqrt(dx * dx + dy * dy);
646+
647+
LengthDbl radius = std::sqrt(
648+
std::pow(element.start.x - element.center.x, 2)
649+
+ std::pow(element.start.y - element.center.y, 2));
650+
651+
// If the point is inside the circle and to the left of the center, there may be intersections with a ray to the right
652+
if (strictly_lesser(distance, radius) && strictly_lesser(point.x, element.center.x)) {
653+
LengthDbl start_angle = angle_radian({element.start.x - element.center.x, element.start.y - element.center.y});
654+
LengthDbl end_angle = angle_radian({element.end.x - element.center.x, element.end.y - element.center.y});
655+
656+
// Ensure angles are in the correct range
657+
if (element.anticlockwise && end_angle <= start_angle) {
658+
end_angle += 2 * M_PI;
659+
} else if (!element.anticlockwise && start_angle <= end_angle) {
660+
start_angle += 2 * M_PI;
661+
}
662+
663+
// Calculate the point's line-of-sight angle (angle between the line from point to center and the horizontal)
664+
LengthDbl point_angle = angle_radian({dx, dy});
665+
if (strictly_lesser(point_angle, 0)) {
666+
point_angle += 2 * M_PI; // Adjust angle to [0, 2π)
667+
}
668+
669+
// Calculate the intersection angle of the ray to the right with the circle
670+
LengthDbl ray_angle = 0; // Angle of ray to the right is 0
671+
672+
// Check if the ray intersects the arc
673+
bool intersects_arc;
674+
if (element.anticlockwise) {
675+
intersects_arc = (!strictly_lesser(ray_angle, start_angle) && !strictly_greater(ray_angle, end_angle));
676+
} else {
677+
intersects_arc = (!strictly_greater(ray_angle, start_angle) && !strictly_lesser(ray_angle, end_angle));
678+
}
679+
680+
if (intersects_arc) {
681+
intersection_count++;
682+
}
683+
}
684+
}
685+
}
686+
687+
// If the number of intersections is odd, the point is inside the shape
688+
return (intersection_count % 2 == 1);
689+
}
690+
540691
Shape& Shape::shift(
541692
LengthDbl x,
542693
LengthDbl y)

0 commit comments

Comments
 (0)