diff --git a/examples/olcPGE3_Geometry2D.cpp b/examples/olcPGE3_Geometry2D.cpp new file mode 100644 index 0000000..a7caf96 --- /dev/null +++ b/examples/olcPGE3_Geometry2D.cpp @@ -0,0 +1,446 @@ +#define OLC_PGE3_APPLICATION +#include "olcPixelGameEngine3.h" + +#include "utilities/olcUTIL3_Geometry2D.h" + +#include + + +using namespace olc::utils::geom2d; + +// INSTRUCTIONS +// ~~~~~~~~~~~~ +// +// A convoluted test file to make sure all the tests do what we think they do. +// Drag shapes with left mouse button +// Active Shape = GREEN +// Overlapped Shapes = YELLOW +// Contained Shapes = MAGENTA +// Intersection Points = RED +// +// Cast Rays with right mouse button (like lasers pew pew) +// Ray - DASHED CYAN + +// Still not sure why the STL doesn't have this... +template +struct overloads : Ts... { using Ts::operator()...; }; + +template +overloads(Ts...) -> overloads; + +class Example_Geometry2D : public olc::PixelGameEngine +{ +public: + Example_Geometry2D() + { + // Name your application + sAppName = "Testing Geometry2D Utilities"; + } + + // So what's going on here? Why are we redefining these base types? Are they not included in the header? + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // + // Yes they are, and they can be used as expected. The challenge starts if you want to have a container filled + // with different shapes. The shapes do not share a common base class. The utility library is not really intended + // to be used this way - instead it's expected the user keep track of which shapes are interacting with which and + // call the appropriate function. This "kinda" happens automatically at compile time as all the functions have + // exhaustive overrides to allow all the permutations of test. + // + // However for this test application I wanted a whole bunch of shapes the user can interact with. 'starfreakclone' + // reworked my explicit (and lengthy) functions of comparisons and tests into some clever template-fu as seen here. + // + // "NOTE!! NEED A TEMPLATE GURU - IM SURE THIS MESS CAN BE TIDIED UP" - javidx9 + // "Never fear! We are here!" - starfreakclone, fux & eight&& + + struct Point + { + olc::vf2d points[1]; // the point + }; + + struct Line + { + olc::vf2d points[2]; // start, end + }; + + struct Rect + { + olc::vf2d points[2]; // pos top left, pos bottom right + }; + + struct Circle + { + olc::vf2d points[2]; // center pos, pos on circumference + }; + + struct Triangle + { + olc::vf2d points[3]; // the three points + }; + + struct Ray + { + olc::vf2d points[2]; // origin, direction + }; + + // Create desired shapes using a sequence of points + static auto make_internal(const Point& p) { return p.points[0]; } + static auto make_internal(const Line& p) { return line{ p.points[0], p.points[1] }; } + static auto make_internal(const Rect& p) { return rect{ p.points[0], (p.points[1] - p.points[0]) }; } + static auto make_internal(const Circle& p) { return circle{ p.points[0], (p.points[1]-p.points[0]).mag() }; } + static auto make_internal(const Triangle& p) { return triangle{ p.points[0], p.points[1], p.points[2] }; } + static auto make_internal(const Ray& p) { return ray{ p.points[0], (p.points[1]-p.points[0]).norm() }; } + + // The clever bit (and a bit new to me - jx9) + using ShapeWrap = std::variant; + + + + bool CheckOverlaps(const ShapeWrap& s1, const ShapeWrap& s2) + { + const auto dispatch = overloads{ + [](const auto& lhs, const auto& rhs) + { + return overlaps(make_internal(lhs), make_internal(rhs)); + }, + + // Any combination of 'Ray' does not work because 'overlaps' is not implemented for it. + [](const Ray&, const auto&) { return false; }, + [](const auto&, const Ray&) { return false; }, + [](const Ray&, const Ray&) { return false; } + }; + + return std::visit(dispatch, s1, s2); + } + + bool CheckContains(const ShapeWrap& s1, const ShapeWrap& s2) + { + const auto dispatch = overloads{ + [](const auto& lhs, const auto& rhs) + { + return contains(make_internal(lhs), make_internal(rhs)); + }, + // Any combination of 'Ray' does not work because 'contains' is not implemented for it. + [](const Ray&, const auto&) { return false; }, + [](const auto&, const Ray&) { return false; }, + [](const Ray&, const Ray&) { return false; } + }; + + return std::visit(dispatch, s1, s2); + } + + std::vector CheckIntersects(const ShapeWrap& s1, const ShapeWrap& s2) + { + const auto dispatch = overloads{ + [](const auto& lhs, const auto& rhs) + { + return intersects(make_internal(lhs), make_internal(rhs)); + }, + + // Any combination of 'Ray' does not work because 'intersects' is not implemented for it. + //[](const Ray&, const auto&) { return std::vector{}; }, - Ray Intersections are implemented - tut tut :P + + // Ray vs Ray - needed explicitly because... + [](const Ray& lhs, const Ray& rhs) + { + return intersects(make_internal(lhs), make_internal(rhs)); + }, + + // ...Shape vs Ray - Dont exist but this treats all f(x,ray) as invalid + [](const auto&, const Ray&) { return std::vector{}; }, + }; + + return std::visit(dispatch, s1, s2); + } + + std::optional> CheckReflect(const olc::utils::geom2d::ray& s1, const ShapeWrap& s2) + { + const auto dispatch = overloads{ + [&](const auto& a) -> std::optional> + { + return reflect(s1, make_internal(a)); + } + }; + + return std::visit(dispatch, s2); + } + + olc::v_2d GetClosest(const olc::v_2d p, const ShapeWrap& s) + { + const auto dispatch = overloads{ + [&](const auto& a) -> olc::v_2d + { + return closest(make_internal(a), p); + } + }; + + return std::visit(dispatch, s); + } + + void draw_internal(const Point& x, const olc::Pixel col) + { + const auto p = make_internal(x); + draw.Pixel(p, col); + } + + void draw_internal(const Line& x, const olc::Pixel col) + { + const auto l = make_internal(x); + draw.Line(l.start, l.end, col); + } + + void draw_internal(const Rect& x, const olc::Pixel col) + { + const auto r = make_internal(x); + draw.Rect(r.pos, r.size, col); + } + + void draw_internal(const Circle& x, const olc::Pixel col) + { + const auto c = make_internal(x); + draw.Circle(c.pos, int32_t(c.radius), col); + } + + void draw_internal(const Triangle& x, const olc::Pixel col) + { + const auto t = make_internal(x); + draw.Triangle(t.pos[0], t.pos[1], t.pos[2], col); + } + + void draw_internal(const Ray& x, const olc::Pixel col) + { + const auto t = make_internal(x); + draw.Line(t.origin, t.origin+t.direction * 1000.0f, col, 0xF0F0F0F0); + } + + void DrawShape(const ShapeWrap& shape, const olc::Pixel col = olc::Colour::WHITE) + { + std::visit([&](const auto& x) + { + draw_internal(x, col); + }, shape); + } + + std::vector vecShapes; + + size_t nSelectedShapeIndex = -1; + olc::vi2d vOldMousePos; + +public: + bool OnUserCreate() override + { + vecShapes.push_back({ Point{ { { 250.0f, 10.0f } } } }); + + vecShapes.push_back({ Line{ { { 20.0f, 10.0f }, {50.0f, 70.0f} } } }); + vecShapes.push_back({ Line{ { { 80.0f, 10.0f }, {10.0f, 20.0f} } } }); + + vecShapes.push_back({ Rect{ { { 80.0f, 10.0f }, {110.0f, 60.0f} } } }); + + vecShapes.push_back({ Circle{ { { 130.0f, 20.0f }, {170.0f, 20.0f} } } }); + vecShapes.push_back({ Circle{ { { 330.0f, 300.0f }, {420.0f, 300.0f} } } }); + vecShapes.push_back({ Circle{ { { 330.0f, 300.0f }, {400.0f, 300.0f} } } }); + + vecShapes.push_back({ Triangle{{ {50.0f, 100.0f}, {10.0f, 150.0f}, {90.0f, 150.0f}} }}); + vecShapes.push_back({ Triangle{{ {350.0f, 200.0f}, {500.0f, 150.0f}, {450.0f, 400.0f}} }}); + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + olc::hw::Button LeftClick = mouse.GetButton(0); + olc::hw::Button RightClick = mouse.GetButton(1); + + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + olc::vf2d vMouseDelta = mouse.GetPosition() - vOldMousePos; + vOldMousePos = mouse.GetPosition(); + + + + if (LeftClick.bReleased) + nSelectedShapeIndex = -1; + + // Check for mouse hovered shapes + ShapeWrap mouse{ Point{ vOldMousePos } }; + + if (nSelectedShapeIndex < vecShapes.size() && LeftClick.bHeld) + { + // Visit the selected shape and offset. + std::visit([&](auto& shape) + { + for (auto& p : shape.points) + { + p += vMouseDelta; + } + }, vecShapes[nSelectedShapeIndex]); + } + + size_t nMouseIndex = 0; + for (const auto& shape : vecShapes) + { + const int shapeMargin = 5; + if (CheckContains(shape, mouse) || (GetClosest(vOldMousePos, shape) - vOldMousePos).mag() < shapeMargin) + { + break; + } + + nMouseIndex++; + } + + if (nMouseIndex < vecShapes.size() && LeftClick.bPressed) + nSelectedShapeIndex = nMouseIndex; + + // Check Contains + std::vector vContains; + std::vector vOverlaps; + std::vector vIntersections; + if (nSelectedShapeIndex < vecShapes.size()) + { + for (size_t i = 0; i < vecShapes.size(); i++) + { + if (i == nSelectedShapeIndex) continue; // No self check + + const auto& vTargetShape = vecShapes[i]; + + const auto vPoints = CheckIntersects(vecShapes[nSelectedShapeIndex], vTargetShape); + vIntersections.insert(vIntersections.end(), vPoints.begin(), vPoints.end()); + + if(CheckContains(vecShapes[nSelectedShapeIndex], vTargetShape)) + vContains.push_back(i); + + if (CheckOverlaps(vecShapes[nSelectedShapeIndex], vTargetShape)) + vOverlaps.push_back(i); + } + } + + + ShapeWrap ray1, ray2; + + + + bool bRayMode = false; + if (RightClick.bHeld) + { + // Enable Ray Mode + bRayMode = true; + + ray1 = { Ray{{ { 10.0f, 10.0f }, vOldMousePos} }}; + ray2 = { Ray{{ { float(ScreenSize().x - 10), 10.0f }, vOldMousePos} }}; + + + for (size_t i = 0; i < vecShapes.size(); i++) + { + const auto& vTargetShape = vecShapes[i]; + + const auto vPoints1 = CheckIntersects(ray1, vTargetShape); + vIntersections.insert(vIntersections.end(), vPoints1.begin(), vPoints1.end()); + + const auto vPoints2 = CheckIntersects(ray2, vTargetShape); + vIntersections.insert(vIntersections.end(), vPoints2.begin(), vPoints2.end()); + + } + + const auto vPoints3 = CheckIntersects(ray2, ray1); + vIntersections.insert(vIntersections.end(), vPoints3.begin(), vPoints3.end()); + + + } + + // Draw All Shapes + for (const auto& shape : vecShapes) + DrawShape(shape); + + + // Draw Overlaps + for (const auto& shape_idx : vOverlaps) + DrawShape(vecShapes[shape_idx], olc::Colour::YELLOW); + + // Draw Contains + for (const auto& shape_idx : vContains) + DrawShape(vecShapes[shape_idx], olc::Colour::MAGENTA); + + // Draw Manipulated Shape + if (nSelectedShapeIndex < vecShapes.size()) + DrawShape(vecShapes[nSelectedShapeIndex], olc::Colour::GREEN); + else if (nMouseIndex < vecShapes.size()) + DrawShape(vecShapes[nMouseIndex], olc::Colour::DARK_GREEN); + + // Draw Intersections + for (const auto& intersection : vIntersections) + draw.FilledCircle(intersection, 3, olc::Colour::RED); + + if (bRayMode) + { + DrawShape(ray1, olc::Colour::CYAN); + DrawShape(ray2, olc::Colour::CYAN); + } + + // Laser beam + ray ray_laser{ {10.0f, 300.0f}, {1.0f, 0.0f} }; + bool ray_stop = false; + int nBounces = 100; + size_t last_hit_index = -1; + + + ray ray_reflected; + + while (!ray_stop && nBounces > 0) + { + // Find closest + ray_stop = true; + size_t closest_hit_index = -1; + float fClosestDistance = 10000000.0f; + + for (size_t i = 0; i < vecShapes.size(); i++) + { + // Dont check against origin shape + if (i == last_hit_index) continue; + + const auto& vTargetShape = vecShapes[i]; + auto hit = CheckReflect(ray_laser, vTargetShape); + if (hit.has_value()) + { + float d = (ray_laser.origin - hit.value().origin).mag(); + if (d < fClosestDistance) + { + fClosestDistance = d; + closest_hit_index = i; + ray_reflected = hit.value(); + } + } + } + + if (closest_hit_index != -1) + { + draw.Line(ray_laser.origin, ray_reflected.origin, olc::Pixel(rand() % 155 + 100, 0, 0)); + ray_laser = ray_reflected; + ray_stop = false; + last_hit_index = closest_hit_index; + nBounces--; + } + + if (ray_stop) + { + // Ray didnt hit anything + nBounces = 0; + draw.Line(ray_laser.origin, ray_laser.origin + ray_laser.direction * 1000.0f, olc::Pixel(rand() % 155 + 100, 0, 0)); + } + } + + + return true; + } +}; + +int main() +{ + Example_Geometry2D demo; + + olc::PGEConfig cfg; + cfg.vScreenSize = {512, 480}; + cfg.vPixelSize = {2, 2}; + + if (demo.Construct(cfg)) + demo.Start(); + return 0; +} \ No newline at end of file diff --git a/utilities/olcUTIL3_Geometry2D.h b/utilities/olcUTIL3_Geometry2D.h index 400422c..f8899de 100644 --- a/utilities/olcUTIL3_Geometry2D.h +++ b/utilities/olcUTIL3_Geometry2D.h @@ -1073,8 +1073,8 @@ namespace olc::utils::geom2d template inline constexpr bool contains(const rect& r, const olc::v_2d& p) { - return !(p.x < r.pos.x || p.y < r.pos.y || - p.x >(r.pos.x + r.size.x) || p.y >(r.pos.y + r.size.y)); + return !((p.x) < r.pos.x || (p.y) < r.pos.y || + (p.x) > (r.pos.x + r.size.x) || (p.y) > (r.pos.y + r.size.y)); } // contains(c,p)