Skip to content

Commit 6cdaad9

Browse files
author
Illia
committed
Implemented project(line, circle)
1 parent 2676412 commit 6cdaad9

File tree

2 files changed

+158
-4
lines changed

2 files changed

+158
-4
lines changed

TEST_Geometry2D.cpp

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ class Test_Geometry2D : public olc::PixelGameEngine
9898
// The clever bit (and a bit new to me - jx9)
9999
using ShapeWrap = std::variant<Point, Line, Rect, Circle, Triangle, Ray>;
100100

101-
101+
enum Mode
102+
{
103+
CircleProject, LineProject, NoProject
104+
};
102105

103106
bool CheckOverlaps(const ShapeWrap& s1, const ShapeWrap& s2)
104107
{
@@ -172,14 +175,21 @@ class Test_Geometry2D : public olc::PixelGameEngine
172175
},
173176

174177
[](const Circle& s1, const Triangle& s2, const Ray& s3)
178+
{
179+
return project(make_internal(s1), make_internal(s2), make_internal(s3));
180+
},
181+
182+
[](const Line& s1, const Circle& s2, const Ray& s3)
175183
{
176184
return project(make_internal(s1), make_internal(s2), make_internal(s3));
177185
}
186+
178187
};
179188

180189
return std::visit(dispatch, s1, s2, s3);
181190
}
182191

192+
183193
std::optional<ray<float>> CheckReflect(const olc::utils::geom2d::ray<float>& s1, const ShapeWrap& s2)
184194
{
185195
const auto dispatch = overloads{
@@ -252,6 +262,7 @@ class Test_Geometry2D : public olc::PixelGameEngine
252262

253263
size_t nSelectedShapeIndex = -1;
254264
olc::vi2d vOldMousePos;
265+
Mode mode = Mode::NoProject;
255266

256267
public:
257268
bool OnUserCreate() override
@@ -283,6 +294,10 @@ class Test_Geometry2D : public olc::PixelGameEngine
283294
if (GetMouse(0).bReleased)
284295
nSelectedShapeIndex = -1;
285296

297+
if (GetKey(olc::Key::C).bPressed) mode = Mode::CircleProject;
298+
if (GetKey(olc::Key::L).bPressed) mode = Mode::LineProject;
299+
if (GetKey(olc::Key::N).bPressed) mode = Mode::NoProject;
300+
286301
// Check for mouse hovered shapes
287302
ShapeWrap mouse{ Point{olc::vf2d(GetMousePos())} };
288303

@@ -345,6 +360,10 @@ class Test_Geometry2D : public olc::PixelGameEngine
345360
bool bRayMode = false;
346361
std::vector<std::optional<olc::v_2d<float>>> projected_circle_left_ray;
347362
std::vector<std::optional<olc::v_2d<float>>> projected_circle_right_ray;
363+
std::vector<std::optional<olc::v_2d<float>>> projected_line_left_ray;
364+
std::vector<std::optional<olc::v_2d<float>>> projected_line_right_ray;
365+
const Line line_to_project{ { { 100.0f, 100.0f }, {140.0f, 70.0f} } };
366+
348367

349368
if (GetMouse(1).bHeld)
350369
{
@@ -373,11 +392,17 @@ class Test_Geometry2D : public olc::PixelGameEngine
373392

374393
for (const auto& shape : vecShapes)
375394
{
376-
if(std::holds_alternative<Rect>(shape) || std::holds_alternative<Triangle>(shape))
395+
if(mode == Mode::CircleProject && (std::holds_alternative<Rect>(shape) || std::holds_alternative<Triangle>(shape)))
377396
{
378397
projected_circle_left_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray1));
379398
projected_circle_right_ray.push_back(CheckProject(Circle{ { { 130.0f, 20.0f }, {150.0f, 20.0f} } }, shape, ray2));
380399
}
400+
401+
if (mode == Mode::LineProject && (std::holds_alternative<Circle>(shape)))
402+
{
403+
projected_line_left_ray.push_back(CheckProject(line_to_project, shape, ray1));
404+
projected_line_right_ray.push_back(CheckProject(line_to_project, shape, ray2));
405+
}
381406
}
382407

383408
}
@@ -412,19 +437,45 @@ class Test_Geometry2D : public olc::PixelGameEngine
412437

413438
for(const auto& projection : projected_circle_left_ray)
414439
{
415-
if (projection.has_value())
440+
if (mode == Mode::CircleProject && projection.has_value())
416441
{
417442
DrawCircle(projection.value(), 20.0f, olc::CYAN);
418443
}
419444
}
420445

421446
for (const auto& projection : projected_circle_right_ray)
422447
{
423-
if (projection.has_value())
448+
if (mode == Mode::CircleProject && projection.has_value())
424449
{
425450
DrawCircle(projection.value(), 20.0f, olc::RED);
426451
}
427452
}
453+
454+
for (const auto& projection : projected_line_left_ray)
455+
{
456+
if (mode == Mode::LineProject && projection.has_value())
457+
{
458+
const auto& vec = make_internal(line_to_project).vector().norm();
459+
const auto& half_length = 0.5 * make_internal(line_to_project).vector().mag();
460+
const olc::vf2d start = projection.value() - vec * half_length;
461+
const olc::vf2d end = projection.value() + vec * half_length;
462+
Line line_to_draw{ {{start}, {end}} };
463+
DrawShape(line_to_draw, olc::CYAN);
464+
}
465+
}
466+
467+
for (const auto& projection : projected_line_right_ray)
468+
{
469+
if (mode == Mode::LineProject && projection.has_value())
470+
{
471+
const auto& vec = make_internal(line_to_project).vector().norm();
472+
const auto& half_length = 0.5 * make_internal(line_to_project).vector().mag();
473+
const olc::vf2d start = projection.value() - vec * half_length;
474+
const olc::vf2d end = projection.value() + vec * half_length;
475+
Line line_to_draw{ {{start}, {end}} };
476+
DrawShape(line_to_draw, olc::RED);
477+
}
478+
}
428479
}
429480

430481
// Laser beam

olcUTIL_Geometry2D.h

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,102 @@ namespace olc::utils::geom2d
14061406
}
14071407

14081408

1409+
// project(t,c)
1410+
// 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?)
1411+
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)
1413+
{
1414+
// The ray intersects the line in the midpoint at all times
1415+
// The function returns a projected midpoint
1416+
1417+
// initialize variables for readable mathematics
1418+
const auto& [a, b] = c.pos;
1419+
const auto& r = c.radius;
1420+
auto [rx, ry] = q.direction;
1421+
const auto& [ox, oy] = q.origin;
1422+
const auto& [slope, intercept] = l.coefficients();
1423+
const auto& length = l.vector().mag();
1424+
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} } };
1430+
1431+
// 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{};
1434+
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
1437+
std::vector<std::pair<olc::v_2d<double>, std::pair<olc::v_2d<double>, olc::v_2d<double>>>> side_points;
1438+
std::vector<olc::v_2d<T1>> possible_points;
1439+
1440+
for (const auto& tangent_point : tangent_points)
1441+
{
1442+
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;
1450+
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}} });
1453+
}
1454+
1455+
1456+
for (const auto& [side_point, pair] : side_points)
1457+
{
1458+
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
1471+
1472+
const double D = (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) * (2 * rx * (x1 - a) + 2 * ry * (y1 - b)) -
1473+
4 * (rx * rx + ry * ry) * ((x1 - a) * (x1 - a) + (y1 - b) * (y1 - b) - r * r);
1474+
if (D > 0)
1475+
{
1476+
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));
1478+
const olc::v_2d<T1> p1{ tangent_intersection_point + s1 * q.direction };
1479+
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;
1486+
}
1487+
else if (D == 0)
1488+
{
1489+
const double s = (-2 * rx * (x1 - a) - 2 * ry * (y1 - b)) / (2 * (rx * rx + ry * ry));
1490+
const olc::v_2d<T1> p{ tangent_intersection_point + s * q.direction };
1491+
!contains(c, p) ? possible_points.push_back(p): (void)0;
1492+
}
1493+
}
14091494

1495+
if (possible_points.empty()) return {};
1496+
1497+
// Compare by the distance to the origin
1498+
return *std::min_element(possible_points.begin(), possible_points.end(),
1499+
[&q](const auto& lhs, const auto& rhs)
1500+
{
1501+
return (lhs - q.origin).mag2() < (rhs - q.origin).mag2();
1502+
});
1503+
1504+
}
14101505

14111506

14121507

@@ -1923,6 +2018,14 @@ namespace olc::utils::geom2d
19232018
return internal::filter_duplicate_points(intersections);
19242019
}
19252020

2021+
// project(t,c)
2022+
// 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?)
2023+
template<typename T1, typename T2, typename T3>
2024+
inline std::optional<olc::v_2d<T1>> project(const triangle<T1>& t, const circle<T2>& c, const ray<T3> q)
2025+
{
2026+
2027+
}
2028+
19262029

19272030
// envelope_c(c)
19282031
// Return circle that fully encapsulates a point

0 commit comments

Comments
 (0)