@@ -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+
168230std::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+
540691Shape& Shape::shift (
541692 LengthDbl x,
542693 LengthDbl y)
0 commit comments