@@ -1409,7 +1409,7 @@ namespace olc::utils::geom2d
14091409 // project(t,c)
14101410 // project a line, onto a circle, via a ray (i.e. how far along the ray can the line travel until it contacts the circle?)
14111411 template <typename T1, typename T2, typename T3>
1412- inline std::optional<olc::v_2d<T1>> project (const line<T1>& l, const circle<T2>& c, const ray<T3>& q)
1412+ inline std::optional<olc::v_2d<T1>> project (const line<T1>& l, const circle<T2>& c, const ray<T3>& q, const double & end_length= 0.5 )
14131413 {
14141414 // The ray intersects the line in the midpoint at all times
14151415 // The function returns a projected midpoint
@@ -1420,80 +1420,167 @@ namespace olc::utils::geom2d
14201420 auto [rx, ry] = q.direction ;
14211421 const auto & [ox, oy] = q.origin ;
14221422 const auto & [slope, intercept] = l.coefficients ();
1423+
1424+ bool is_vertical = false ;
1425+ bool is_horizontal = false ;
1426+
1427+ if (slope == 0 ) is_horizontal = true ;
1428+ if (slope == std::numeric_limits<double >::infinity ()) is_vertical = true ;
1429+
14231430 const auto & length = l.vector ().mag ();
1431+ const auto & vec = l.vector ().norm ();
1432+ const auto & start_length = end_length - 1 ;
14241433
1425- // First, we find two points on the cirlce that correspond to the tangent of the same slope
1426- // as the given line
1427- const double y_t1 = r / std::sqrt (slope * slope + 1 ) + b;
1428- const double y_t2 = - r / std::sqrt (slope * slope + 1 ) + b;
1429- std::vector<olc::v_2d<double >> tangent_points{ { {a - (y_t1 - b) * slope, y_t1}, {a - (y_t2 - b) * slope, y_t2} } };
1434+ // First, we find two points on the cirlce that correspond to
1435+ // the tangent of the same as the given line
1436+ std::vector<olc::v_2d<double >> tangent_points;
1437+ if (is_vertical)
1438+ {
1439+ const double y_t1 = b, y_t2 = b;
1440+ tangent_points.assign ({{a - r, y_t1},{a + r, y_t2}});
1441+ }
1442+ else if (is_horizontal)
1443+ {
1444+ const double x_t1 = a, x_t2 = a;
1445+ tangent_points.assign ({ {x_t1, b + r},{x_t2, b - r} });
1446+ }
1447+ else
1448+ {
1449+ const double y_t1 = r / std::sqrt (slope * slope + 1 ) + b;
1450+ const double y_t2 = -r / std::sqrt (slope * slope + 1 ) + b;
1451+ tangent_points.assign ({ {a - (y_t1 - b) * slope, y_t1}, {a - (y_t2 - b) * slope, y_t2} });
1452+ }
14301453
14311454 // Check if the ray intersects the tangent line along its way and not from behind
1432- const auto & v = (tangent_points[0 ] - q.origin ).norm ();
1433- if (v.dot (q.direction ) < 0 ) return {};
1455+ if ((tangent_points[0 ] - q.origin ).norm ().dot (q.direction ) < 0 ) return {};
1456+
1457+ // If the ray is parallel to both tangent lines - special case
1458+ if ((is_vertical && std::abs (rx) < epsilon) ||
1459+ (is_horizontal && std::abs (ry) < epsilon) ||
1460+ std::abs (slope - ry / rx) < epsilon)
1461+ {
1462+ // The line is going through the origin of the ray
1463+ const std::vector<std::pair<olc::v_2d<double >, double >> sides{
1464+ {{q.origin + vec * end_length * length}, end_length},
1465+ {{q.origin + vec * start_length * length}, start_length} };
1466+
1467+ const auto & closest = *std::min_element (sides.begin (), sides.end (),
1468+ [&c](const auto & lhs, const auto & rhs)
1469+ {
1470+ return (lhs.first - c.pos ).mag2 () < (rhs.first - c.pos ).mag2 ();
1471+ });
14341472
1435- // The line is first being projected onto both tangent lines, the tangent point and the intersections between the ray and
1436- // the tangent line are stored as well
1473+ // The closest end of the line is transported onto the circle,
1474+ // Return value is determined via the length of that side
1475+ const auto & intersections = intersects (q, c);
1476+ if (!intersections.empty ()) return intersections[0 ] - closest.second * length * vec;
1477+ return {};
1478+ }
1479+
1480+ // The line is first being projected onto both tangent lines,
1481+ // the tangent point and the intersections between the and the tangent line are stored as well
14371482 std::vector<std::pair<olc::v_2d<double >, std::pair<olc::v_2d<double >, olc::v_2d<double >>>> side_points;
14381483 std::vector<olc::v_2d<T1>> possible_points;
14391484
14401485 for (const auto & tangent_point : tangent_points)
14411486 {
1487+ std::vector<std::pair<olc::v_2d<double >, double >> possible_closest_side;
1488+
1489+ double xl;
1490+ double yl;
1491+
14421492 const auto & [xt, yt] = tangent_point;
1443- const double xl = (ry / rx * ox - xt * slope + yt - oy) / (ry / rx - slope);
1444- const double yl = (xl - xt) * slope + yt;
1445- // Creating two points on a tangent line that correspond to the start and the end of a projected line
1446- const double x_side_1 = xl - 0.5 * length * l.vector ().norm ().x ;
1447- const double x_side_2 = xl + 0.5 * length * l.vector ().norm ().x ;
1448- const double y_side_1 = (x_side_1 - xl) * slope + yl;
1449- const double y_side_2 = (x_side_2 - xl) * slope + yl;
1493+ if (is_vertical)
1494+ {
1495+ xl = xt;
1496+ yl = (xl - ox) * (ry / rx) + oy;
1497+ }
1498+ else if (is_horizontal)
1499+ {
1500+ yl = yt;
1501+ xl = (yl - oy) * (rx / ry) + ox;
1502+ }
1503+ else
1504+ {
1505+ xl = (ry / rx * ox - xt * slope + yt - oy) / (ry / rx - slope);
1506+ yl = (xl - xt) * slope + yt;
1507+ }
1508+ const v_2d<double > intersection_point{xl, yl};
1509+ // Creating two points on a tangent line that correspond to
1510+ // the start and the end of a projected line
1511+
1512+ const double x_side_end = xl + end_length * length * vec.x ;
1513+ const double y_side_end = yl + end_length * length * vec.y ;
1514+ const double x_side_start = xl + start_length * length * vec.x ;
1515+ const double y_side_start = yl + start_length * length * vec.y ;
1516+
1517+ possible_closest_side.push_back ({ { x_side_end, y_side_end }, std::abs (end_length * length) });
1518+ possible_closest_side.push_back ({ { x_side_start, y_side_start }, std::abs (start_length * length) });
1519+
1520+ const auto & closest_side_to_tangent = *std::min_element (possible_closest_side.begin (), possible_closest_side.end (),
1521+ [&tangent_point, &intersection_point](const auto & lhs, const auto & rhs)
1522+ {
1523+ return (lhs.first - intersection_point).dot (tangent_point - intersection_point) >
1524+ (rhs.first - intersection_point).dot (tangent_point - intersection_point);
1525+ });
1526+
1527+ // Only add a tangent-ray intersection point if the distance between
1528+ // it and the tangent point is less than or equal the length of side.
1529+ // It means the line can lie on the tangent
1530+ if ((tangent_point - intersection_point).mag () <= closest_side_to_tangent.second )
1531+ possible_points.push_back (intersection_point);
1532+
1533+ side_points.push_back ({ {x_side_end, y_side_end}, {{xl, yl}, {xt, yt}} });
1534+ side_points.push_back ({ {x_side_start, y_side_start}, {{xl, yl}, {xt, yt}} });
14501535
1451- side_points.push_back ({ {x_side_1, y_side_1}, {{xl, yl}, {xt, yt}} });
1452- side_points.push_back ({ {x_side_2, y_side_2}, {{xl, yl}, {xt, yt}} });
14531536 }
14541537
14551538
1456- for (const auto & [side_point, pair ] : side_points)
1539+ for (const auto & [side_point, pair_intersection_tangent ] : side_points)
14571540 {
14581541 const auto & [x1, y1] = side_point;
1459- const auto & tangent_intersection_point = pair.first ;
1460- const auto & tangent_point = pair.second ;
1461-
1462- // We search for a scalar s such that:
1463- // x_new = x_side + s * rx
1464- // y_new = y_side + s * ry
1465- // (x_new - a)^2 + (y_new - b)^2 = r^2
1466- //
1467- // Where (x_new, y_new) is a point on the circle that is obtained by transporting a line projected onto the
1468- // tangent via a ray q
1469- //
1470- // For each tangent line and for each side of the line being transported, there can be up to 4 transported lines
1542+ const auto & tangent_intersection_point = pair_intersection_tangent.first ;
1543+ const auto & tangent_point = pair_intersection_tangent.second ;
1544+
1545+ /* We search for a scalar s such that:
1546+ x_new = x_side + s * rx
1547+ y_new = y_side + s * ry
1548+ (x_new - a)^2 + (y_new - b)^2 = r^2
1549+
1550+ Where (x_new, y_new) is a point on the circle that is
1551+ obtained by transporting a line projected onto tangent via a ray q
1552+
1553+ In a regular (non-parallel to the tangent, non-vertical or horizontal) situation,
1554+ There will be up to 4 possible lines projected onto the circle.
1555+ Each end point of the line can be projected so that the line is either outside the circle
1556+ or, respectively, inside */
14711557
14721558 const double D = (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) * (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) -
14731559 4 * (rx * rx + ry * ry) * ((x1 - a) * (x1 - a) + (y1 - b) * (y1 - b) - r * r);
14741560 if (D > 0 )
14751561 {
14761562 const double s1 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) + std::sqrt (D)) / (2 * (rx * rx + ry * ry));
1477- const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt (D)) / (2 * (rx * rx + ry * ry));
1563+ const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt (D)) / (2 * (rx * rx + ry * ry))
1564+
14781565 const olc::v_2d<T1> p1{ tangent_intersection_point + s1 * q.direction };
14791566 const olc::v_2d<T1> p2{ tangent_intersection_point + s2 * q.direction };
1480- // Only add a point is it is not inside the circle
1481- !contains (c, p1) ? possible_points.push_back (p1) : (void )0 ;
1482- !contains (c, p2) ? possible_points.push_back (p2) : (void )0 ;
1483- // Only add a tangent-ray intersection point if the distance between it and the tangent point is less than of equal
1484- // half length of the line. It means the line can lie on the tangent
1485- (tangent_intersection_point - tangent_point).mag () <= 0.5 * length ? possible_points.push_back (tangent_intersection_point) : (void )0 ;
1567+
1568+ possible_points.push_back (p1);
1569+ possible_points.push_back (p2);
14861570 }
14871571 else if (D == 0 )
14881572 {
14891573 const double s = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b)) / (2 * (rx * rx + ry * ry));
14901574 const olc::v_2d<T1> p{ tangent_intersection_point + s * q.direction };
1491- !contains (c, p) ? possible_points.push_back (p): (void )0 ;
1575+
1576+ possible_points.push_back (p);
14921577 }
14931578 }
14941579
14951580 if (possible_points.empty ()) return {};
14961581
1582+
1583+
14971584 // Compare by the distance to the origin
14981585 return *std::min_element (possible_points.begin (), possible_points.end (),
14991586 [&q](const auto & lhs, const auto & rhs)
0 commit comments