From 26764125ab8ef21d6455a21a570ce1248b524150 Mon Sep 17 00:00:00 2001 From: Illia Date: Sat, 10 Feb 2024 10:41:47 -0600 Subject: [PATCH 1/6] Implemented project(c,t) (project circle onto triangle via a ray). Updated a demo for testing --- TEST_Geometry2D.cpp | 52 ++++++++++++++++++++++++++++++++++++++++++++ olcUTIL_Geometry2D.h | 38 +++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/TEST_Geometry2D.cpp b/TEST_Geometry2D.cpp index fbdb6ee..c7e55aa 100644 --- a/TEST_Geometry2D.cpp +++ b/TEST_Geometry2D.cpp @@ -8,6 +8,7 @@ #include "third_party/olcPGEX_QuickGUI.h" #include +#include using namespace olc::utils::geom2d; @@ -156,6 +157,29 @@ class Test_Geometry2D : public olc::PixelGameEngine return std::visit(dispatch, s1, s2); } + std::optional> CheckProject(const ShapeWrap& s1, const ShapeWrap& s2, const ShapeWrap& s3) + { + const auto dispatch = overloads{ + + [](const auto& s1, const auto& s2, const auto& s3) + { + return std::optional>{}; + }, + + [](const Circle& s1, const Rect& s2, const Ray& s3) + { + return project(make_internal(s1), make_internal(s2), make_internal(s3)); + }, + + [](const Circle& s1, const Triangle& s2, const Ray& s3) + { + return project(make_internal(s1), make_internal(s2), make_internal(s3)); + } + }; + + return std::visit(dispatch, s1, s2, s3); + } + std::optional> CheckReflect(const olc::utils::geom2d::ray& s1, const ShapeWrap& s2) { const auto dispatch = overloads{ @@ -319,6 +343,9 @@ class Test_Geometry2D : public olc::PixelGameEngine bool bRayMode = false; + std::vector>> projected_circle_left_ray; + std::vector>> projected_circle_right_ray; + if (GetMouse(1).bHeld) { // Enable Ray Mode @@ -344,6 +371,15 @@ class Test_Geometry2D : public olc::PixelGameEngine vIntersections.insert(vIntersections.end(), vPoints3.begin(), vPoints3.end()); + for (const auto& shape : vecShapes) + { + if(std::holds_alternative(shape) || std::holds_alternative(shape)) + { + projected_circle_left_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray1)); + projected_circle_right_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray2)); + } + } + } // Draw All Shapes @@ -373,6 +409,22 @@ class Test_Geometry2D : public olc::PixelGameEngine { DrawShape(ray1, olc::CYAN); DrawShape(ray2, olc::CYAN); + + for(const auto& projection : projected_circle_left_ray) + { + if (projection.has_value()) + { + DrawCircle(projection.value(), 20.0f, olc::CYAN); + } + } + + for (const auto& projection : projected_circle_right_ray) + { + if (projection.has_value()) + { + DrawCircle(projection.value(), 20.0f, olc::RED); + } + } } // Laser beam diff --git a/olcUTIL_Geometry2D.h b/olcUTIL_Geometry2D.h index 7e3be8a..c9d7240 100644 --- a/olcUTIL_Geometry2D.h +++ b/olcUTIL_Geometry2D.h @@ -164,7 +164,7 @@ | closest | closest | | | | | | overlaps | overlaps | overlaps | overlaps | overlaps | | | intersects | intersects | intersects | intersects | intersects | | - | project | project | | project | | | + | project | project | project | project | project | | ---------+--------------+--------------+--------------+--------------+--------------+--------------+ TRIANGLE | contains | contains | contains | contains | contains | | | closest | | | | | | @@ -2184,8 +2184,35 @@ namespace olc::utils::geom2d template inline std::optional> project(const circle& c, const triangle& t, const ray& q) { - // TODO: - return std::nullopt; + const auto s1 = project(c, t.side(0), q); + const auto s2 = project(c, t.side(1), q); + const auto s3 = project(c, t.side(2), q); + + std::vector> vAllIntersections; + if (s1.has_value()) vAllIntersections.push_back(s1.value()); + if (s2.has_value()) vAllIntersections.push_back(s2.value()); + if (s3.has_value()) vAllIntersections.push_back(s3.value()); + + if (vAllIntersections.size() == 0) + { + // No intersections at all, so + return std::nullopt; + } + + // Find closest + double dClosest = std::numeric_limits::max(); + olc::v_2d vClosest; + for (const auto& vContact : vAllIntersections) + { + double dDistance = (vContact - q.origin).mag2(); + if (dDistance < dClosest) + { + dClosest = dDistance; + vClosest = vContact; + } + } + + return vClosest; } @@ -2457,10 +2484,11 @@ namespace olc::utils::geom2d if (s2 < 0) return { q.origin + q.direction * s1 }; - return { q.origin + q.direction * std::min(s1, s2) }; + const auto& [min_dist, max_dist] = std::minmax(s1, s2); + return { q.origin + q.direction * min_dist, q.origin + q.direction * max_dist }; } } - + // intersects(q,r) // Get intersection points where a ray intersects a rectangle template From 6cdaad977c63c34635477cf20ef684f8973a6553 Mon Sep 17 00:00:00 2001 From: Illia Date: Sun, 11 Feb 2024 18:15:41 -0600 Subject: [PATCH 2/6] Implemented project(line, circle) --- TEST_Geometry2D.cpp | 59 +++++++++++++++++++++++-- olcUTIL_Geometry2D.h | 103 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 4 deletions(-) diff --git a/TEST_Geometry2D.cpp b/TEST_Geometry2D.cpp index c7e55aa..b430af9 100644 --- a/TEST_Geometry2D.cpp +++ b/TEST_Geometry2D.cpp @@ -98,7 +98,10 @@ class Test_Geometry2D : public olc::PixelGameEngine // The clever bit (and a bit new to me - jx9) using ShapeWrap = std::variant; - + enum Mode + { + CircleProject, LineProject, NoProject + }; bool CheckOverlaps(const ShapeWrap& s1, const ShapeWrap& s2) { @@ -172,14 +175,21 @@ class Test_Geometry2D : public olc::PixelGameEngine }, [](const Circle& s1, const Triangle& s2, const Ray& s3) + { + return project(make_internal(s1), make_internal(s2), make_internal(s3)); + }, + + [](const Line& s1, const Circle& s2, const Ray& s3) { return project(make_internal(s1), make_internal(s2), make_internal(s3)); } + }; return std::visit(dispatch, s1, s2, s3); } + std::optional> CheckReflect(const olc::utils::geom2d::ray& s1, const ShapeWrap& s2) { const auto dispatch = overloads{ @@ -252,6 +262,7 @@ class Test_Geometry2D : public olc::PixelGameEngine size_t nSelectedShapeIndex = -1; olc::vi2d vOldMousePos; + Mode mode = Mode::NoProject; public: bool OnUserCreate() override @@ -283,6 +294,10 @@ class Test_Geometry2D : public olc::PixelGameEngine if (GetMouse(0).bReleased) nSelectedShapeIndex = -1; + if (GetKey(olc::Key::C).bPressed) mode = Mode::CircleProject; + if (GetKey(olc::Key::L).bPressed) mode = Mode::LineProject; + if (GetKey(olc::Key::N).bPressed) mode = Mode::NoProject; + // Check for mouse hovered shapes ShapeWrap mouse{ Point{olc::vf2d(GetMousePos())} }; @@ -345,6 +360,10 @@ class Test_Geometry2D : public olc::PixelGameEngine bool bRayMode = false; std::vector>> projected_circle_left_ray; std::vector>> projected_circle_right_ray; + std::vector>> projected_line_left_ray; + std::vector>> projected_line_right_ray; + const Line line_to_project{ { { 100.0f, 100.0f }, {140.0f, 70.0f} } }; + if (GetMouse(1).bHeld) { @@ -373,11 +392,17 @@ class Test_Geometry2D : public olc::PixelGameEngine for (const auto& shape : vecShapes) { - if(std::holds_alternative(shape) || std::holds_alternative(shape)) + if(mode == Mode::CircleProject && (std::holds_alternative(shape) || std::holds_alternative(shape))) { projected_circle_left_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray1)); projected_circle_right_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray2)); } + + if (mode == Mode::LineProject && (std::holds_alternative(shape))) + { + projected_line_left_ray.push_back(CheckProject(line_to_project, shape, ray1)); + projected_line_right_ray.push_back(CheckProject(line_to_project, shape, ray2)); + } } } @@ -412,7 +437,7 @@ class Test_Geometry2D : public olc::PixelGameEngine for(const auto& projection : projected_circle_left_ray) { - if (projection.has_value()) + if (mode == Mode::CircleProject && projection.has_value()) { DrawCircle(projection.value(), 20.0f, olc::CYAN); } @@ -420,11 +445,37 @@ class Test_Geometry2D : public olc::PixelGameEngine for (const auto& projection : projected_circle_right_ray) { - if (projection.has_value()) + if (mode == Mode::CircleProject && projection.has_value()) { DrawCircle(projection.value(), 20.0f, olc::RED); } } + + for (const auto& projection : projected_line_left_ray) + { + if (mode == Mode::LineProject && projection.has_value()) + { + const auto& vec = make_internal(line_to_project).vector().norm(); + const auto& half_length = 0.5 * make_internal(line_to_project).vector().mag(); + const olc::vf2d start = projection.value() - vec * half_length; + const olc::vf2d end = projection.value() + vec * half_length; + Line line_to_draw{ {{start}, {end}} }; + DrawShape(line_to_draw, olc::CYAN); + } + } + + for (const auto& projection : projected_line_right_ray) + { + if (mode == Mode::LineProject && projection.has_value()) + { + const auto& vec = make_internal(line_to_project).vector().norm(); + const auto& half_length = 0.5 * make_internal(line_to_project).vector().mag(); + const olc::vf2d start = projection.value() - vec * half_length; + const olc::vf2d end = projection.value() + vec * half_length; + Line line_to_draw{ {{start}, {end}} }; + DrawShape(line_to_draw, olc::RED); + } + } } // Laser beam diff --git a/olcUTIL_Geometry2D.h b/olcUTIL_Geometry2D.h index c9d7240..41fa46c 100644 --- a/olcUTIL_Geometry2D.h +++ b/olcUTIL_Geometry2D.h @@ -1406,7 +1406,102 @@ namespace olc::utils::geom2d } + // project(t,c) + // 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?) + template + inline std::optional> project(const line& l, const circle& c, const ray& q) + { + // The ray intersects the line in the midpoint at all times + // The function returns a projected midpoint + + // initialize variables for readable mathematics + const auto& [a, b] = c.pos; + const auto& r = c.radius; + auto [rx, ry] = q.direction; + const auto& [ox, oy] = q.origin; + const auto& [slope, intercept] = l.coefficients(); + const auto& length = l.vector().mag(); + + // First, we find two points on the cirlce that correspond to the tangent of the same slope + // as the given line + const double y_t1 = r / std::sqrt(slope * slope + 1) + b; + const double y_t2 = - r / std::sqrt(slope * slope + 1) + b; + std::vector> tangent_points{ { {a - (y_t1 - b) * slope, y_t1}, {a - (y_t2 - b) * slope, y_t2} } }; + + // Check if the ray intersects the tangent line along its way and not from behind + const auto& v = (tangent_points[0] - q.origin).norm(); + if (v.dot(q.direction) < 0) return{}; + + // The line is first being projected onto both tangent lines, the tangent point and the intersections between the ray and + // the tangent line are stored as well + std::vector, std::pair, olc::v_2d>>> side_points; + std::vector> possible_points; + + for (const auto& tangent_point : tangent_points) + { + const auto& [xt, yt] = tangent_point; + const double xl = (ry / rx * ox - xt * slope + yt - oy) / (ry / rx - slope); + const double yl = (xl - xt) * slope + yt; + // Creating two points on a tangent line that correspond to the start and the end of a projected line + const double x_side_1 = xl - 0.5 * length * l.vector().norm().x; + const double x_side_2 = xl + 0.5 * length * l.vector().norm().x; + const double y_side_1 = (x_side_1 - xl) * slope + yl; + const double y_side_2 = (x_side_2 - xl) * slope + yl; + + side_points.push_back({ {x_side_1, y_side_1}, {{xl, yl}, {xt, yt}} }); + side_points.push_back({ {x_side_2, y_side_2}, {{xl, yl}, {xt, yt}} }); + } + + + for (const auto& [side_point, pair] : side_points) + { + const auto& [x1, y1] = side_point; + const auto& tangent_intersection_point = pair.first; + const auto& tangent_point = pair.second; + + // We search for a scalar s such that: + // x_new = x_side + s * rx + // y_new = y_side + s * ry + // (x_new - a)^2 + (y_new - b)^2 = r^2 + // + // Where (x_new, y_new) is a point on the circle that is obtained by transporting a line projected onto the + // tangent via a ray q + // + // For each tangent line and for each side of the line being transported, there can be up to 4 transported lines + + const double D = (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) * (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) - + 4 * (rx * rx + ry * ry) * ((x1 - a) * (x1 - a) + (y1 - b) * (y1 - b) - r * r); + if (D > 0) + { + const double s1 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) + std::sqrt(D)) / (2 * (rx * rx + ry * ry)); + const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt(D)) / (2 * (rx * rx + ry * ry)); + const olc::v_2d p1{ tangent_intersection_point + s1 * q.direction }; + const olc::v_2d p2{ tangent_intersection_point + s2 * q.direction }; + // Only add a point is it is not inside the circle + !contains(c, p1) ? possible_points.push_back(p1) : (void)0; + !contains(c, p2) ? possible_points.push_back(p2) : (void)0; + // Only add a tangent-ray intersection point if the distance between it and the tangent point is less than of equal + // half length of the line. It means the line can lie on the tangent + (tangent_intersection_point - tangent_point).mag() <= 0.5 * length ? possible_points.push_back(tangent_intersection_point) : (void)0; + } + else if (D == 0) + { + const double s = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b)) / (2 * (rx * rx + ry * ry)); + const olc::v_2d p{ tangent_intersection_point + s * q.direction }; + !contains(c, p) ? possible_points.push_back(p): (void)0; + } + } + if (possible_points.empty()) return {}; + + // Compare by the distance to the origin + return *std::min_element(possible_points.begin(), possible_points.end(), + [&q](const auto& lhs, const auto& rhs) + { + return (lhs - q.origin).mag2() < (rhs - q.origin).mag2(); + }); + + } @@ -1923,6 +2018,14 @@ namespace olc::utils::geom2d return internal::filter_duplicate_points(intersections); } + // project(t,c) + // project a triangle, onto a circle, via a ray (i.e. how far along the ray can the triangle travel until it contacts the circle?) + template + inline std::optional> project(const triangle& t, const circle& c, const ray q) + { + + } + // envelope_c(c) // Return circle that fully encapsulates a point From aaebf43e761dfc309e104675983b7f140bb97e4e Mon Sep 17 00:00:00 2001 From: Illia Date: Mon, 12 Feb 2024 21:23:15 -0600 Subject: [PATCH 3/6] Non-center line projection and edge cases --- TEST_Geometry2D.cpp | 20 +++--- olcUTIL_Geometry2D.h | 167 ++++++++++++++++++++++++++++++++----------- 2 files changed, 138 insertions(+), 49 deletions(-) diff --git a/TEST_Geometry2D.cpp b/TEST_Geometry2D.cpp index b430af9..78e11ca 100644 --- a/TEST_Geometry2D.cpp +++ b/TEST_Geometry2D.cpp @@ -160,7 +160,7 @@ class Test_Geometry2D : public olc::PixelGameEngine return std::visit(dispatch, s1, s2); } - std::optional> CheckProject(const ShapeWrap& s1, const ShapeWrap& s2, const ShapeWrap& s3) + std::optional> CheckProject(const ShapeWrap& s1, const ShapeWrap& s2, const ShapeWrap& s3, const double& end_length = 0.5) { const auto dispatch = overloads{ @@ -179,10 +179,10 @@ class Test_Geometry2D : public olc::PixelGameEngine return project(make_internal(s1), make_internal(s2), make_internal(s3)); }, - [](const Line& s1, const Circle& s2, const Ray& s3) + [&](const Line& s1, const Circle& s2, const Ray& s3) { - return project(make_internal(s1), make_internal(s2), make_internal(s3)); - } + return project(make_internal(s1), make_internal(s2), make_internal(s3), end_length); + }, }; @@ -361,8 +361,9 @@ class Test_Geometry2D : public olc::PixelGameEngine std::vector>> projected_circle_left_ray; std::vector>> projected_circle_right_ray; std::vector>> projected_line_left_ray; + const double left_line_end_length = 0.1; std::vector>> projected_line_right_ray; - const Line line_to_project{ { { 100.0f, 100.0f }, {140.0f, 70.0f} } }; + const Line line_to_project{ { { 100.0f, 100.0f }, {130.0f, 150.0f} } }; if (GetMouse(1).bHeld) @@ -400,7 +401,7 @@ class Test_Geometry2D : public olc::PixelGameEngine if (mode == Mode::LineProject && (std::holds_alternative(shape))) { - projected_line_left_ray.push_back(CheckProject(line_to_project, shape, ray1)); + projected_line_left_ray.push_back(CheckProject(line_to_project, shape, ray1, left_line_end_length)); projected_line_right_ray.push_back(CheckProject(line_to_project, shape, ray2)); } } @@ -456,9 +457,10 @@ class Test_Geometry2D : public olc::PixelGameEngine if (mode == Mode::LineProject && projection.has_value()) { const auto& vec = make_internal(line_to_project).vector().norm(); - const auto& half_length = 0.5 * make_internal(line_to_project).vector().mag(); - const olc::vf2d start = projection.value() - vec * half_length; - const olc::vf2d end = projection.value() + vec * half_length; + const auto& end_length = left_line_end_length * make_internal(line_to_project).vector().mag(); + const auto& start_length = (left_line_end_length - 1) * make_internal(line_to_project).vector().mag(); + const olc::vf2d start = projection.value() + vec * end_length; + const olc::vf2d end = projection.value() + vec * start_length; Line line_to_draw{ {{start}, {end}} }; DrawShape(line_to_draw, olc::CYAN); } diff --git a/olcUTIL_Geometry2D.h b/olcUTIL_Geometry2D.h index 41fa46c..98c5a67 100644 --- a/olcUTIL_Geometry2D.h +++ b/olcUTIL_Geometry2D.h @@ -1409,7 +1409,7 @@ namespace olc::utils::geom2d // project(t,c) // 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?) template - inline std::optional> project(const line& l, const circle& c, const ray& q) + inline std::optional> project(const line& l, const circle& c, const ray& q, const double& end_length=0.5) { // The ray intersects the line in the midpoint at all times // The function returns a projected midpoint @@ -1420,80 +1420,167 @@ namespace olc::utils::geom2d auto [rx, ry] = q.direction; const auto& [ox, oy] = q.origin; const auto& [slope, intercept] = l.coefficients(); + + bool is_vertical = false; + bool is_horizontal = false; + + if (slope == 0) is_horizontal = true; + if (slope == std::numeric_limits::infinity()) is_vertical = true; + const auto& length = l.vector().mag(); + const auto& vec = l.vector().norm(); + const auto& start_length = end_length - 1; - // First, we find two points on the cirlce that correspond to the tangent of the same slope - // as the given line - const double y_t1 = r / std::sqrt(slope * slope + 1) + b; - const double y_t2 = - r / std::sqrt(slope * slope + 1) + b; - std::vector> tangent_points{ { {a - (y_t1 - b) * slope, y_t1}, {a - (y_t2 - b) * slope, y_t2} } }; + // First, we find two points on the cirlce that correspond to + // the tangent of the same as the given line + std::vector> tangent_points; + if (is_vertical) + { + const double y_t1 = b, y_t2 = b; + tangent_points.assign({{a - r, y_t1},{a + r, y_t2}}); + } + else if (is_horizontal) + { + const double x_t1 = a, x_t2 = a; + tangent_points.assign({ {x_t1, b + r},{x_t2, b - r} }); + } + else + { + const double y_t1 = r / std::sqrt(slope * slope + 1) + b; + const double y_t2 = -r / std::sqrt(slope * slope + 1) + b; + tangent_points.assign({ {a - (y_t1 - b) * slope, y_t1}, {a - (y_t2 - b) * slope, y_t2} }); + } // Check if the ray intersects the tangent line along its way and not from behind - const auto& v = (tangent_points[0] - q.origin).norm(); - if (v.dot(q.direction) < 0) return{}; + if ((tangent_points[0] - q.origin).norm().dot(q.direction) < 0) return{}; + + // If the ray is parallel to both tangent lines - special case + if ((is_vertical && std::abs(rx) < epsilon) || + (is_horizontal && std::abs(ry) < epsilon) || + std::abs(slope - ry / rx) < epsilon) + { + // The line is going through the origin of the ray + const std::vector, double>> sides{ + {{q.origin + vec * end_length * length}, end_length}, + {{q.origin + vec * start_length * length}, start_length} }; + + const auto& closest = *std::min_element(sides.begin(), sides.end(), + [&c](const auto& lhs, const auto& rhs) + { + return (lhs.first - c.pos).mag2() < (rhs.first - c.pos).mag2(); + }); - // The line is first being projected onto both tangent lines, the tangent point and the intersections between the ray and - // the tangent line are stored as well + // The closest end of the line is transported onto the circle, + // Return value is determined via the length of that side + const auto& intersections = intersects(q, c); + if (!intersections.empty()) return intersections[0] - closest.second * length * vec; + return {}; + } + + // The line is first being projected onto both tangent lines, + // the tangent point and the intersections between the and the tangent line are stored as well std::vector, std::pair, olc::v_2d>>> side_points; std::vector> possible_points; for (const auto& tangent_point : tangent_points) { + std::vector, double>> possible_closest_side; + + double xl; + double yl; + const auto& [xt, yt] = tangent_point; - const double xl = (ry / rx * ox - xt * slope + yt - oy) / (ry / rx - slope); - const double yl = (xl - xt) * slope + yt; - // Creating two points on a tangent line that correspond to the start and the end of a projected line - const double x_side_1 = xl - 0.5 * length * l.vector().norm().x; - const double x_side_2 = xl + 0.5 * length * l.vector().norm().x; - const double y_side_1 = (x_side_1 - xl) * slope + yl; - const double y_side_2 = (x_side_2 - xl) * slope + yl; + if (is_vertical) + { + xl = xt; + yl = (xl - ox) * (ry / rx) + oy; + } + else if (is_horizontal) + { + yl = yt; + xl = (yl - oy) * (rx / ry) + ox; + } + else + { + xl = (ry / rx * ox - xt * slope + yt - oy) / (ry / rx - slope); + yl = (xl - xt) * slope + yt; + } + const v_2d intersection_point{xl, yl}; + // Creating two points on a tangent line that correspond to + // the start and the end of a projected line + + const double x_side_end = xl + end_length * length * vec.x; + const double y_side_end = yl + end_length * length * vec.y; + const double x_side_start = xl + start_length * length * vec.x; + const double y_side_start = yl + start_length * length * vec.y; + + possible_closest_side.push_back({ { x_side_end, y_side_end }, std::abs(end_length * length) }); + possible_closest_side.push_back({ { x_side_start, y_side_start }, std::abs(start_length * length) }); + + const auto& closest_side_to_tangent = *std::min_element(possible_closest_side.begin(), possible_closest_side.end(), + [&tangent_point, &intersection_point](const auto& lhs, const auto& rhs) + { + return (lhs.first - intersection_point).dot(tangent_point - intersection_point) > + (rhs.first - intersection_point).dot(tangent_point - intersection_point); + }); + + // Only add a tangent-ray intersection point if the distance between + // it and the tangent point is less than or equal the length of side. + // It means the line can lie on the tangent + if ((tangent_point - intersection_point).mag() <= closest_side_to_tangent.second) + possible_points.push_back(intersection_point); + + side_points.push_back({ {x_side_end, y_side_end}, {{xl, yl}, {xt, yt}} }); + side_points.push_back({ {x_side_start, y_side_start}, {{xl, yl}, {xt, yt}} }); - side_points.push_back({ {x_side_1, y_side_1}, {{xl, yl}, {xt, yt}} }); - side_points.push_back({ {x_side_2, y_side_2}, {{xl, yl}, {xt, yt}} }); } - for (const auto& [side_point, pair] : side_points) + for (const auto& [side_point, pair_intersection_tangent] : side_points) { const auto& [x1, y1] = side_point; - const auto& tangent_intersection_point = pair.first; - const auto& tangent_point = pair.second; - - // We search for a scalar s such that: - // x_new = x_side + s * rx - // y_new = y_side + s * ry - // (x_new - a)^2 + (y_new - b)^2 = r^2 - // - // Where (x_new, y_new) is a point on the circle that is obtained by transporting a line projected onto the - // tangent via a ray q - // - // For each tangent line and for each side of the line being transported, there can be up to 4 transported lines + const auto& tangent_intersection_point = pair_intersection_tangent.first; + const auto& tangent_point = pair_intersection_tangent.second; + + /* We search for a scalar s such that: + x_new = x_side + s * rx + y_new = y_side + s * ry + (x_new - a)^2 + (y_new - b)^2 = r^2 + + Where (x_new, y_new) is a point on the circle that is + obtained by transporting a line projected onto tangent via a ray q + + In a regular (non-parallel to the tangent, non-vertical or horizontal) situation, + There will be up to 4 possible lines projected onto the circle. + Each end point of the line can be projected so that the line is either outside the circle + or, respectively, inside */ const double D = (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) * (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) - 4 * (rx * rx + ry * ry) * ((x1 - a) * (x1 - a) + (y1 - b) * (y1 - b) - r * r); if (D > 0) { const double s1 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) + std::sqrt(D)) / (2 * (rx * rx + ry * ry)); - const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt(D)) / (2 * (rx * rx + ry * ry)); + const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt(D)) / (2 * (rx * rx + ry * ry)) + const olc::v_2d p1{ tangent_intersection_point + s1 * q.direction }; const olc::v_2d p2{ tangent_intersection_point + s2 * q.direction }; - // Only add a point is it is not inside the circle - !contains(c, p1) ? possible_points.push_back(p1) : (void)0; - !contains(c, p2) ? possible_points.push_back(p2) : (void)0; - // Only add a tangent-ray intersection point if the distance between it and the tangent point is less than of equal - // half length of the line. It means the line can lie on the tangent - (tangent_intersection_point - tangent_point).mag() <= 0.5 * length ? possible_points.push_back(tangent_intersection_point) : (void)0; + + possible_points.push_back(p1); + possible_points.push_back(p2); } else if (D == 0) { const double s = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b)) / (2 * (rx * rx + ry * ry)); const olc::v_2d p{ tangent_intersection_point + s * q.direction }; - !contains(c, p) ? possible_points.push_back(p): (void)0; + + possible_points.push_back(p); } } if (possible_points.empty()) return {}; + + // Compare by the distance to the origin return *std::min_element(possible_points.begin(), possible_points.end(), [&q](const auto& lhs, const auto& rhs) From 2c01c13538a2174386be66649b4f23515eb565af Mon Sep 17 00:00:00 2001 From: Illia Date: Mon, 12 Feb 2024 21:37:06 -0600 Subject: [PATCH 4/6] Non-center line projection on circle and edge cases --- olcUTIL_Geometry2D.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/olcUTIL_Geometry2D.h b/olcUTIL_Geometry2D.h index 98c5a67..330bfff 100644 --- a/olcUTIL_Geometry2D.h +++ b/olcUTIL_Geometry2D.h @@ -1560,7 +1560,7 @@ namespace olc::utils::geom2d if (D > 0) { const double s1 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) + std::sqrt(D)) / (2 * (rx * rx + ry * ry)); - const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt(D)) / (2 * (rx * rx + ry * ry)) + const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt(D)) / (2 * (rx * rx + ry * ry)); const olc::v_2d p1{ tangent_intersection_point + s1 * q.direction }; const olc::v_2d p2{ tangent_intersection_point + s2 * q.direction }; From adae49aa05499bc63ff5b499d0e21106e4d97af5 Mon Sep 17 00:00:00 2001 From: Illia Date: Wed, 14 Feb 2024 21:46:38 -0600 Subject: [PATCH 5/6] Implemented project(traingle, circle), re-done project(line, circle) --- TEST_Geometry2D.cpp | 62 +++++++++- olcUTIL_Geometry2D.h | 281 +++++++++++-------------------------------- 2 files changed, 131 insertions(+), 212 deletions(-) diff --git a/TEST_Geometry2D.cpp b/TEST_Geometry2D.cpp index 78e11ca..0b6fdb5 100644 --- a/TEST_Geometry2D.cpp +++ b/TEST_Geometry2D.cpp @@ -100,7 +100,7 @@ class Test_Geometry2D : public olc::PixelGameEngine enum Mode { - CircleProject, LineProject, NoProject + CircleProject, LineProject, TriangleProject, NoProject }; bool CheckOverlaps(const ShapeWrap& s1, const ShapeWrap& s2) @@ -160,7 +160,8 @@ class Test_Geometry2D : public olc::PixelGameEngine return std::visit(dispatch, s1, s2); } - std::optional> CheckProject(const ShapeWrap& s1, const ShapeWrap& s2, const ShapeWrap& s3, const double& end_length = 0.5) + std::optional> CheckProject(const ShapeWrap& s1, const ShapeWrap& s2, const ShapeWrap& s3, + const double& end_length = 0.5) { const auto dispatch = overloads{ @@ -189,6 +190,24 @@ class Test_Geometry2D : public olc::PixelGameEngine return std::visit(dispatch, s1, s2, s3); } + std::optional> CheckProjectTriangle(const ShapeWrap& s1, const ShapeWrap& s2, const ShapeWrap& s3) + { + const auto dispatch = overloads{ + + [](const auto& s1, const auto& s2, const auto& s3) + { + return std::optional>{}; + }, + + [](const Triangle& s1, const Circle& s2, const Ray& s3) + { + return project(make_internal(s1), make_internal(s2), make_internal(s3)); + } + }; + + return std::visit(dispatch, s1, s2, s3); + } + std::optional> CheckReflect(const olc::utils::geom2d::ray& s1, const ShapeWrap& s2) { @@ -296,6 +315,7 @@ class Test_Geometry2D : public olc::PixelGameEngine if (GetKey(olc::Key::C).bPressed) mode = Mode::CircleProject; if (GetKey(olc::Key::L).bPressed) mode = Mode::LineProject; + if (GetKey(olc::Key::T).bPressed) mode = Mode::TriangleProject; if (GetKey(olc::Key::N).bPressed) mode = Mode::NoProject; // Check for mouse hovered shapes @@ -363,7 +383,10 @@ class Test_Geometry2D : public olc::PixelGameEngine std::vector>> projected_line_left_ray; const double left_line_end_length = 0.1; std::vector>> projected_line_right_ray; - const Line line_to_project{ { { 100.0f, 100.0f }, {130.0f, 150.0f} } }; + std::vector>> projected_triangle_left_ray; + std::vector>> projected_triangle_right_ray; + const Line line_to_project{ { { 100.0f, 100.0f }, {130.0f, 100.0f} } }; + const Triangle triangle_to_project{ { {50.0f, 100.0f}, {10.0f, 150.0f}, {90.0f, 150.0f}} }; if (GetMouse(1).bHeld) @@ -399,11 +422,17 @@ class Test_Geometry2D : public olc::PixelGameEngine projected_circle_right_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray2)); } - if (mode == Mode::LineProject && (std::holds_alternative(shape))) + else if (mode == Mode::LineProject && (std::holds_alternative(shape))) { projected_line_left_ray.push_back(CheckProject(line_to_project, shape, ray1, left_line_end_length)); projected_line_right_ray.push_back(CheckProject(line_to_project, shape, ray2)); } + + else if (mode == Mode::TriangleProject && (std::holds_alternative(shape))) + { + projected_triangle_left_ray.push_back(CheckProjectTriangle(triangle_to_project, shape, ray1)); + projected_triangle_right_ray.push_back(CheckProjectTriangle(triangle_to_project, shape, ray2)); + } } } @@ -478,6 +507,31 @@ class Test_Geometry2D : public olc::PixelGameEngine DrawShape(line_to_draw, olc::RED); } } + + for (const auto& projection : projected_triangle_left_ray) + { + if (mode == Mode::TriangleProject && projection.has_value()) + { + const auto p0 = projection.value().pos[0], + p1 = projection.value().pos[1], + p2 = projection.value().pos[2]; + + DrawTriangle(p0, p1, p2, olc::CYAN); + } + } + + for (const auto& projection : projected_triangle_right_ray) + { + if (mode == Mode::TriangleProject && projection.has_value()) + { + const auto p0 = projection.value().pos[0], + p1 = projection.value().pos[1], + p2 = projection.value().pos[2]; + + DrawTriangle(p0, p1, p2, olc::RED); + } + } + } // Laser beam diff --git a/olcUTIL_Geometry2D.h b/olcUTIL_Geometry2D.h index 330bfff..389b7d1 100644 --- a/olcUTIL_Geometry2D.h +++ b/olcUTIL_Geometry2D.h @@ -1406,198 +1406,41 @@ namespace olc::utils::geom2d } - // project(t,c) + // project(l,c) // 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?) template inline std::optional> project(const line& l, const circle& c, const ray& q, const double& end_length=0.5) { - // The ray intersects the line in the midpoint at all times - // The function returns a projected midpoint - - // initialize variables for readable mathematics - const auto& [a, b] = c.pos; - const auto& r = c.radius; - auto [rx, ry] = q.direction; - const auto& [ox, oy] = q.origin; - const auto& [slope, intercept] = l.coefficients(); - - bool is_vertical = false; - bool is_horizontal = false; + // The ray intersects the line in the point p + // |------p--------------| + // ^ ^ + // | | + // start end + // |<-end length->| + // The function returns a projected point - if (slope == 0) is_horizontal = true; - if (slope == std::numeric_limits::infinity()) is_vertical = true; + if (contains(c, q.origin)) return std::nullopt; const auto& length = l.vector().mag(); const auto& vec = l.vector().norm(); const auto& start_length = end_length - 1; - // First, we find two points on the cirlce that correspond to - // the tangent of the same as the given line - std::vector> tangent_points; - if (is_vertical) - { - const double y_t1 = b, y_t2 = b; - tangent_points.assign({{a - r, y_t1},{a + r, y_t2}}); - } - else if (is_horizontal) - { - const double x_t1 = a, x_t2 = a; - tangent_points.assign({ {x_t1, b + r},{x_t2, b - r} }); - } - else - { - const double y_t1 = r / std::sqrt(slope * slope + 1) + b; - const double y_t2 = -r / std::sqrt(slope * slope + 1) + b; - tangent_points.assign({ {a - (y_t1 - b) * slope, y_t1}, {a - (y_t2 - b) * slope, y_t2} }); - } - - // Check if the ray intersects the tangent line along its way and not from behind - if ((tangent_points[0] - q.origin).norm().dot(q.direction) < 0) return{}; - - // If the ray is parallel to both tangent lines - special case - if ((is_vertical && std::abs(rx) < epsilon) || - (is_horizontal && std::abs(ry) < epsilon) || - std::abs(slope - ry / rx) < epsilon) - { - // The line is going through the origin of the ray - const std::vector, double>> sides{ - {{q.origin + vec * end_length * length}, end_length}, - {{q.origin + vec * start_length * length}, start_length} }; - - const auto& closest = *std::min_element(sides.begin(), sides.end(), - [&c](const auto& lhs, const auto& rhs) - { - return (lhs.first - c.pos).mag2() < (rhs.first - c.pos).mag2(); - }); + // Projecting the circle on the line that is going through the origin of the ray + const line line_on_the_origin{ q.origin + end_length * length * vec, q.origin + start_length * length * vec}; + const ray q_reverse{ c.pos, -q.direction }; - // The closest end of the line is transported onto the circle, - // Return value is determined via the length of that side - const auto& intersections = intersects(q, c); - if (!intersections.empty()) return intersections[0] - closest.second * length * vec; - return {}; - } + const auto& circle_on_line = project(c, line_on_the_origin, q_reverse); - // The line is first being projected onto both tangent lines, - // the tangent point and the intersections between the and the tangent line are stored as well - std::vector, std::pair, olc::v_2d>>> side_points; - std::vector> possible_points; + if (!circle_on_line.has_value()) return std::nullopt; - for (const auto& tangent_point : tangent_points) - { - std::vector, double>> possible_closest_side; + const double distance = (circle_on_line.value() - c.pos).mag(); - double xl; - double yl; - - const auto& [xt, yt] = tangent_point; - if (is_vertical) - { - xl = xt; - yl = (xl - ox) * (ry / rx) + oy; - } - else if (is_horizontal) - { - yl = yt; - xl = (yl - oy) * (rx / ry) + ox; - } - else - { - xl = (ry / rx * ox - xt * slope + yt - oy) / (ry / rx - slope); - yl = (xl - xt) * slope + yt; - } - const v_2d intersection_point{xl, yl}; - // Creating two points on a tangent line that correspond to - // the start and the end of a projected line - - const double x_side_end = xl + end_length * length * vec.x; - const double y_side_end = yl + end_length * length * vec.y; - const double x_side_start = xl + start_length * length * vec.x; - const double y_side_start = yl + start_length * length * vec.y; - - possible_closest_side.push_back({ { x_side_end, y_side_end }, std::abs(end_length * length) }); - possible_closest_side.push_back({ { x_side_start, y_side_start }, std::abs(start_length * length) }); - - const auto& closest_side_to_tangent = *std::min_element(possible_closest_side.begin(), possible_closest_side.end(), - [&tangent_point, &intersection_point](const auto& lhs, const auto& rhs) - { - return (lhs.first - intersection_point).dot(tangent_point - intersection_point) > - (rhs.first - intersection_point).dot(tangent_point - intersection_point); - }); - - // Only add a tangent-ray intersection point if the distance between - // it and the tangent point is less than or equal the length of side. - // It means the line can lie on the tangent - if ((tangent_point - intersection_point).mag() <= closest_side_to_tangent.second) - possible_points.push_back(intersection_point); - - side_points.push_back({ {x_side_end, y_side_end}, {{xl, yl}, {xt, yt}} }); - side_points.push_back({ {x_side_start, y_side_start}, {{xl, yl}, {xt, yt}} }); - - } - - - for (const auto& [side_point, pair_intersection_tangent] : side_points) - { - const auto& [x1, y1] = side_point; - const auto& tangent_intersection_point = pair_intersection_tangent.first; - const auto& tangent_point = pair_intersection_tangent.second; - - /* We search for a scalar s such that: - x_new = x_side + s * rx - y_new = y_side + s * ry - (x_new - a)^2 + (y_new - b)^2 = r^2 - - Where (x_new, y_new) is a point on the circle that is - obtained by transporting a line projected onto tangent via a ray q - - In a regular (non-parallel to the tangent, non-vertical or horizontal) situation, - There will be up to 4 possible lines projected onto the circle. - Each end point of the line can be projected so that the line is either outside the circle - or, respectively, inside */ - - const double D = (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) * (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) - - 4 * (rx * rx + ry * ry) * ((x1 - a) * (x1 - a) + (y1 - b) * (y1 - b) - r * r); - if (D > 0) - { - const double s1 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) + std::sqrt(D)) / (2 * (rx * rx + ry * ry)); - const double s2 = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b) - std::sqrt(D)) / (2 * (rx * rx + ry * ry)); - - const olc::v_2d p1{ tangent_intersection_point + s1 * q.direction }; - const olc::v_2d p2{ tangent_intersection_point + s2 * q.direction }; - - possible_points.push_back(p1); - possible_points.push_back(p2); - } - else if (D == 0) - { - const double s = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b)) / (2 * (rx * rx + ry * ry)); - const olc::v_2d p{ tangent_intersection_point + s * q.direction }; - - possible_points.push_back(p); - } - } - - if (possible_points.empty()) return {}; - - - - // Compare by the distance to the origin - return *std::min_element(possible_points.begin(), possible_points.end(), - [&q](const auto& lhs, const auto& rhs) - { - return (lhs - q.origin).mag2() < (rhs - q.origin).mag2(); - }); + // Move the projection back so that the centre of the circle is in it's original position + return q.origin + distance * q.direction; } - - - - - - - // ================================================================================================================ // RECTANGLE ====================================================================================================== @@ -1683,7 +1526,8 @@ namespace olc::utils::geom2d // Inspired by this (very clever btw) // https://stackoverflow.com/questions/45370692/circle-rectangle-collision-response // But modified to work :P - double overlap = (olc::v_2d{ std::clamp(c.pos.x, r.pos.x, r.pos.x + r.size.x), std::clamp(c.pos.y, r.pos.y, r.pos.y + r.size.y) } - c.pos).mag2(); + double overlap = (olc::v_2d{ std::clamp(c.pos.x, r.pos.x, r.pos.x + r.size.x), + std::clamp(c.pos.y, r.pos.y, r.pos.y + r.size.y) } - c.pos).mag2(); if (std::isnan(overlap)) overlap = 0; return (overlap - (c.radius * c.radius)) < 0; } @@ -2108,9 +1952,36 @@ namespace olc::utils::geom2d // project(t,c) // project a triangle, onto a circle, via a ray (i.e. how far along the ray can the triangle travel until it contacts the circle?) template - inline std::optional> project(const triangle& t, const circle& c, const ray q) + inline std::optional> project(const triangle& t, const circle& c, const ray q) { - + // The ray is going through the point of median inersections + + if (contains(c, q.origin)) return std::nullopt; + + const auto side_0_mid = t.side(0).rpoint(0.5 * t.side(0).length()); + const auto side_1_mid = t.side(1).rpoint(0.5 * t.side(1).length()); + + const auto& median_point = intersects(line{side_0_mid, t.pos[2]}, line{side_1_mid, t.pos[0]}); + if (median_point.empty()) return std::nullopt; + + const auto displace = (q.origin - median_point[0]); + const triangle triangle_on_the_origin{t.pos[0] + displace, t.pos[1] + displace, t.pos[2] + displace}; + + // Projecting the circle on the line that is going through the origin of the ray + const ray q_reverse{ c.pos, -q.direction }; + + const auto& circle_on_triangle = project(c, triangle_on_the_origin, q_reverse); + + if (!circle_on_triangle.has_value()) return std::nullopt; + + const double distance = (circle_on_triangle.value() - c.pos).mag(); + + const triangle projected{ triangle_on_the_origin.pos[0] + distance * q.direction.norm(), + triangle_on_the_origin.pos[1] + distance * q.direction.norm(), + triangle_on_the_origin.pos[2] + distance * q.direction.norm() }; + + // Move the projection back so that the centre of the circle is in it's original position + return projected; } @@ -2289,6 +2160,10 @@ namespace olc::utils::geom2d template inline std::optional> project(const circle& c, const line& l, const ray& q) { + + // There should be no projection if the shapes overlap + if (overlaps(c, l)) return std::nullopt; + // Treat line segment as capsule with radius that of the circle // and treat the circle as a point @@ -2315,18 +2190,11 @@ namespace olc::utils::geom2d return std::nullopt; } - // Find closest - double dClosest = std::numeric_limits::max(); - olc::v_2d vClosest; - for (const auto& vContact : vAllIntersections) - { - double dDistance = (vContact - q.origin).mag2(); - if (dDistance < dClosest) + const auto& vClosest = *std::min_element(vAllIntersections.begin(), vAllIntersections.end(), + [&q](const auto& lhs, const auto& rhs) { - dClosest = dDistance; - vClosest = vContact; - } - } + return (lhs - q.origin).mag2() < (rhs - q.origin).mag2(); + }); return vClosest; } @@ -2336,6 +2204,11 @@ namespace olc::utils::geom2d template inline std::optional> project(const circle& c, const rect& r, const ray& q) { + + const auto& closest_to_origin = closest(r, q.origin); + + if ((q.origin - closest_to_origin).mag() < c.radius) return std::nullopt; + const auto s1 = project(c, r.top(), q); const auto s2 = project(c, r.bottom(), q); const auto s3 = project(c, r.left(), q); @@ -2354,17 +2227,11 @@ namespace olc::utils::geom2d } // Find closest - double dClosest = std::numeric_limits::max(); - olc::v_2d vClosest; - for (const auto& vContact : vAllIntersections) - { - double dDistance = (vContact - q.origin).mag2(); - if (dDistance < dClosest) + const auto& vClosest = *std::min_element(vAllIntersections.begin(), vAllIntersections.end(), + [&q](const auto& lhs, const auto& rhs) { - dClosest = dDistance; - vClosest = vContact; - } - } + return (lhs - q.origin).mag2() < (rhs - q.origin).mag2(); + }); return vClosest; } @@ -2374,6 +2241,10 @@ namespace olc::utils::geom2d template inline std::optional> project(const circle& c, const triangle& t, const ray& q) { + const auto& closest_to_origin = closest(t, q.origin); + + if ((q.origin - closest_to_origin).mag() < c.radius) return std::nullopt; + const auto s1 = project(c, t.side(0), q); const auto s2 = project(c, t.side(1), q); const auto s3 = project(c, t.side(2), q); @@ -2390,17 +2261,11 @@ namespace olc::utils::geom2d } // Find closest - double dClosest = std::numeric_limits::max(); - olc::v_2d vClosest; - for (const auto& vContact : vAllIntersections) - { - double dDistance = (vContact - q.origin).mag2(); - if (dDistance < dClosest) + const auto& vClosest = *std::min_element(vAllIntersections.begin(), vAllIntersections.end(), + [&q](const auto& lhs, const auto& rhs) { - dClosest = dDistance; - vClosest = vContact; - } - } + return (lhs - q.origin).mag2() < (rhs - q.origin).mag2(); + }); return vClosest; } From 78dfeceaf96bc0a60f422b5b724e27f6cdaecaec Mon Sep 17 00:00:00 2001 From: Illia Date: Thu, 15 Feb 2024 15:25:45 -0600 Subject: [PATCH 6/6] Fixed the overlap bug --- TEST_Geometry2D.cpp | 2 +- olcUTIL_Geometry2D.h | 147 ++++++++++++++++++++++--------------------- 2 files changed, 77 insertions(+), 72 deletions(-) diff --git a/TEST_Geometry2D.cpp b/TEST_Geometry2D.cpp index 0b6fdb5..3d39c64 100644 --- a/TEST_Geometry2D.cpp +++ b/TEST_Geometry2D.cpp @@ -386,7 +386,7 @@ class Test_Geometry2D : public olc::PixelGameEngine std::vector>> projected_triangle_left_ray; std::vector>> projected_triangle_right_ray; const Line line_to_project{ { { 100.0f, 100.0f }, {130.0f, 100.0f} } }; - const Triangle triangle_to_project{ { {50.0f, 100.0f}, {10.0f, 150.0f}, {90.0f, 150.0f}} }; + const Triangle triangle_to_project{ { {65.0f, 107.0f}, {18.0f, 100.0f}, {80.0f, 170.0f}} }; if (GetMouse(1).bHeld) diff --git a/olcUTIL_Geometry2D.h b/olcUTIL_Geometry2D.h index 389b7d1..0652db3 100644 --- a/olcUTIL_Geometry2D.h +++ b/olcUTIL_Geometry2D.h @@ -1406,39 +1406,7 @@ namespace olc::utils::geom2d } - // project(l,c) - // 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?) - template - inline std::optional> project(const line& l, const circle& c, const ray& q, const double& end_length=0.5) - { - // The ray intersects the line in the point p - // |------p--------------| - // ^ ^ - // | | - // start end - // |<-end length->| - // The function returns a projected point - - if (contains(c, q.origin)) return std::nullopt; - - const auto& length = l.vector().mag(); - const auto& vec = l.vector().norm(); - const auto& start_length = end_length - 1; - - // Projecting the circle on the line that is going through the origin of the ray - const line line_on_the_origin{ q.origin + end_length * length * vec, q.origin + start_length * length * vec}; - const ray q_reverse{ c.pos, -q.direction }; - - const auto& circle_on_line = project(c, line_on_the_origin, q_reverse); - - if (!circle_on_line.has_value()) return std::nullopt; - - const double distance = (circle_on_line.value() - c.pos).mag(); - - // Move the projection back so that the centre of the circle is in it's original position - return q.origin + distance * q.direction; - - } + // ================================================================================================================ @@ -1949,40 +1917,6 @@ namespace olc::utils::geom2d return internal::filter_duplicate_points(intersections); } - // project(t,c) - // project a triangle, onto a circle, via a ray (i.e. how far along the ray can the triangle travel until it contacts the circle?) - template - inline std::optional> project(const triangle& t, const circle& c, const ray q) - { - // The ray is going through the point of median inersections - - if (contains(c, q.origin)) return std::nullopt; - - const auto side_0_mid = t.side(0).rpoint(0.5 * t.side(0).length()); - const auto side_1_mid = t.side(1).rpoint(0.5 * t.side(1).length()); - - const auto& median_point = intersects(line{side_0_mid, t.pos[2]}, line{side_1_mid, t.pos[0]}); - if (median_point.empty()) return std::nullopt; - - const auto displace = (q.origin - median_point[0]); - const triangle triangle_on_the_origin{t.pos[0] + displace, t.pos[1] + displace, t.pos[2] + displace}; - - // Projecting the circle on the line that is going through the origin of the ray - const ray q_reverse{ c.pos, -q.direction }; - - const auto& circle_on_triangle = project(c, triangle_on_the_origin, q_reverse); - - if (!circle_on_triangle.has_value()) return std::nullopt; - - const double distance = (circle_on_triangle.value() - c.pos).mag(); - - const triangle projected{ triangle_on_the_origin.pos[0] + distance * q.direction.norm(), - triangle_on_the_origin.pos[1] + distance * q.direction.norm(), - triangle_on_the_origin.pos[2] + distance * q.direction.norm() }; - - // Move the projection back so that the centre of the circle is in it's original position - return projected; - } // envelope_c(c) @@ -2161,8 +2095,9 @@ namespace olc::utils::geom2d inline std::optional> project(const circle& c, const line& l, const ray& q) { - // There should be no projection if the shapes overlap - if (overlaps(c, l)) return std::nullopt; + // There should be no projection if the shapes overlap + // (The circle) is positioned at the origin of the ray + if (overlaps(circle{q.origin, c.radius}, l)) return std::nullopt; // Treat line segment as capsule with radius that of the circle // and treat the circle as a point @@ -2206,7 +2141,8 @@ namespace olc::utils::geom2d { const auto& closest_to_origin = closest(r, q.origin); - + // Since the projected circle lies ON the rectangle, the closest point + // is allowed to be on the rectangle, but not inside if ((q.origin - closest_to_origin).mag() < c.radius) return std::nullopt; const auto s1 = project(c, r.top(), q); @@ -2242,7 +2178,6 @@ namespace olc::utils::geom2d inline std::optional> project(const circle& c, const triangle& t, const ray& q) { const auto& closest_to_origin = closest(t, q.origin); - if ((q.origin - closest_to_origin).mag() < c.radius) return std::nullopt; const auto s1 = project(c, t.side(0), q); @@ -2270,6 +2205,76 @@ namespace olc::utils::geom2d return vClosest; } + // project(l,c) + // 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?) + template + inline std::optional> project(const line& l, const circle& c, const ray& q, const double& end_length = 0.5) + { + // The ray intersects the line in the point p + // |------p--------------| + // ^ ^ + // | | + // start end + // |<-end length->| + // The function returns a projected point + + if (contains(c, q.origin)) return std::nullopt; + + const auto& length = l.vector().mag(); + const auto& vec = l.vector().norm(); + const auto& start_length = end_length - 1; + + // Projecting the circle on the line that is going through the origin of the ray + const line line_on_the_origin{ q.origin + end_length * length * vec, q.origin + start_length * length * vec }; + const ray q_reverse{ c.pos, -q.direction }; + + const auto& circle_on_line = project(c, line_on_the_origin, q_reverse); + + if (!circle_on_line.has_value()) return std::nullopt; + + const double distance = (circle_on_line.value() - c.pos).mag(); + + // Move the projection back so that the centre of the circle is in it's original position + return q.origin + distance * q.direction; + + } + + + // project(t,c) + // project a triangle, onto a circle, via a ray (i.e. how far along the ray can the triangle travel until it contacts the circle?) + template + inline std::optional> project(const triangle& t, const circle& c, const ray q) + { + // The ray is going through the point of median inersections + + if (contains(c, q.origin)) return std::nullopt; + + const auto side_0_mid = t.side(0).rpoint(0.5 * t.side(0).length()); + const auto side_1_mid = t.side(1).rpoint(0.5 * t.side(1).length()); + + const auto& median_point = intersects(line{side_0_mid, t.pos[2]}, line{side_1_mid, t.pos[0]}); + if (median_point.empty()) return std::nullopt; + + const auto displace = (q.origin - median_point[0]); + const triangle triangle_on_the_origin{ t.pos[0] + displace, t.pos[1] + displace, t.pos[2] + displace }; + + // Projecting the circle on the line that is going through the origin of the ray + const ray q_reverse{ c.pos, -q.direction }; + + const auto& circle_on_triangle = project(c, triangle_on_the_origin, q_reverse); + + if (!circle_on_triangle.has_value()) return std::nullopt; + + const double distance = (circle_on_triangle.value() - c.pos).mag(); + + const triangle projected{ triangle_on_the_origin.pos[0] + distance * q.direction.norm(), + triangle_on_the_origin.pos[1] + distance * q.direction.norm(), + triangle_on_the_origin.pos[2] + distance * q.direction.norm() }; + + // Move the projection back so that the centre of the circle is in it's original position + return projected; + } + // RAYS =================================================================================================================