diff --git a/Libraries/LibGfx/Quad.h b/Libraries/LibGfx/Quad.h index 21a5ae18234..2cfbb0fcc56 100644 --- a/Libraries/LibGfx/Quad.h +++ b/Libraries/LibGfx/Quad.h @@ -6,8 +6,8 @@ #pragma once +#include #include -#include namespace Gfx { @@ -29,20 +29,45 @@ public: Rect bounding_rect() const { - auto top = min(min(m_p1.y(), m_p2.y()), min(m_p3.y(), m_p4.y())); - auto right = max(max(m_p1.x(), m_p2.x()), max(m_p3.x(), m_p4.x())); - auto bottom = max(max(m_p1.y(), m_p2.y()), max(m_p3.y(), m_p4.y())); - auto left = min(min(m_p1.x(), m_p2.x()), min(m_p3.x(), m_p4.x())); - return { left, top, right - left, bottom - top }; + T left = min(min(m_p1.x(), m_p2.x()), min(m_p3.x(), m_p4.x())); + T right = max(max(m_p1.x(), m_p2.x()), max(m_p3.x(), m_p4.x())); + T width = right - left; + + T top = min(min(m_p1.y(), m_p2.y()), min(m_p3.y(), m_p4.y())); + T bottom = max(max(m_p1.y(), m_p2.y()), max(m_p3.y(), m_p4.y())); + T height = bottom - top; + + return { left, top, width, height }; } bool contains(Point point) const { - // FIXME: There's probably a smarter way to do this. - return Triangle(m_p1, m_p2, m_p3).contains(point) - || Triangle(m_p1, m_p3, m_p4).contains(point) - || Triangle(m_p2, m_p4, m_p1).contains(point) - || Triangle(m_p2, m_p4, m_p3).contains(point); + // Even-Odd algorithm: https://www.geeksforgeeks.org/even-odd-method-winding-number-method-inside-outside-test-of-a-polygon/ + // + // 1. "Constructing a line segment between the point (P) to be examined and a known point outside the polygon" + // - We're using horizontal line from (point.x, point.y) to (bounding_rect().left + bounding_rect().width + 1, point.y) + // (i.e. just +1 to right of furthest-right point in quad) + // + // 2. "The number of times the line segment intersects the polygon boundary is then counted." + // - We count the line's intersections with the quad by checking each quad edge for intersection (1-2, 2-3, 3-4, 4-1) + // + // 3. "The point (P) is an internal point if the number of polygon edges intersected by this line is odd; + // otherwise, the point is an external point." + + u8 intersections_with_quad = 0; + auto const quad_points = AK::Array { &m_p1, &m_p2, &m_p3, &m_p4, &m_p1 }; + for (size_t i = 0, j = 1; i < 4 && j < 5; i++, j++) { + if ((quad_points[i]->y() > point.y()) == (quad_points[j]->y() > point.y())) { + continue; + } + + T x_coord_of_intersection_with_edge = (quad_points[j]->x() - quad_points[i]->x()) * (point.y() - quad_points[i]->y()) / (quad_points[j]->y() - quad_points[i]->y()) + quad_points[i]->x(); + if (point.x() < x_coord_of_intersection_with_edge) { + intersections_with_quad++; + } + } + + return (intersections_with_quad % 2) == 1; } private: diff --git a/Tests/LibGfx/CMakeLists.txt b/Tests/LibGfx/CMakeLists.txt index 4c15de43c1c..2666d735d96 100644 --- a/Tests/LibGfx/CMakeLists.txt +++ b/Tests/LibGfx/CMakeLists.txt @@ -5,6 +5,7 @@ set(TEST_SOURCES TestICCProfile.cpp TestImageDecoder.cpp TestImageWriter.cpp + TestQuad.cpp TestRect.cpp TestWOFF.cpp TestWOFF2.cpp diff --git a/Tests/LibGfx/TestQuad.cpp b/Tests/LibGfx/TestQuad.cpp new file mode 100644 index 00000000000..9b0c4a8f0b4 --- /dev/null +++ b/Tests/LibGfx/TestQuad.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, Aaron Van Doren + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +TEST_CASE(quad_points) +{ + uint8_t quad_x_left = 1; + uint8_t quad_x_right = 5; + uint8_t quad_y_top = 10; + uint8_t quad_y_bottom = 6; + + Gfx::Point left_bottom { quad_x_left, quad_y_bottom }; + Gfx::Point left_top { quad_x_left, quad_y_top }; + Gfx::Point right_bottom { quad_x_right, quad_y_bottom }; + Gfx::Point right_top { quad_x_right, quad_y_top }; + + Gfx::Quad quad { left_bottom, left_top, right_bottom, right_top }; + EXPECT_EQ(quad.p1(), left_bottom); + EXPECT_EQ(quad.p2(), left_top); + EXPECT_EQ(quad.p3(), right_bottom); + EXPECT_EQ(quad.p4(), right_top); +} + +TEST_CASE(quad_bounding_rect) +{ + uint8_t quad_width = 5; + uint8_t quad_height = 4; + uint8_t quad_x_left = 0; + uint8_t quad_y_top = 6; + + uint8_t quad_x_right = quad_x_left + quad_width; + uint8_t quad_y_bottom = quad_y_top + quad_height; + + Gfx::Point left_bottom { quad_x_left, quad_y_bottom }; + Gfx::Point left_top { quad_x_left, quad_y_top }; + Gfx::Point right_bottom { quad_x_right, quad_y_bottom }; + Gfx::Point right_top { quad_x_right, quad_y_top }; + Gfx::Quad quad = { left_bottom, left_top, right_top, right_bottom }; + + auto bounding_rect = quad.bounding_rect(); + EXPECT_EQ(bounding_rect.x(), quad_x_left); + EXPECT_EQ(bounding_rect.y(), quad_y_top); + EXPECT_EQ(bounding_rect.width(), quad_width); + EXPECT_EQ(bounding_rect.height(), quad_height); +} + +TEST_CASE(quad_contains) +{ + u8 quad_width = 5; + u8 quad_height = 4; + u8 quad_x_left = 0; + u8 quad_y_top = 6; + + u8 quad_x_right = quad_x_left + quad_width; + u8 quad_y_bottom = quad_y_top + quad_height; + + Gfx::Point left_bottom { quad_x_left, quad_y_bottom }; + Gfx::Point left_top { quad_x_left, quad_y_top }; + Gfx::Point right_bottom { quad_x_right, quad_y_bottom }; + Gfx::Point right_top { quad_x_right, quad_y_top }; + Gfx::Quad quad = { left_bottom, left_top, right_top, right_bottom }; + + Gfx::Point in_bounds_point { 1, 7 }; + EXPECT(quad.contains(in_bounds_point) == true); + + Gfx::Point out_bounds_point { 7, 12 }; + EXPECT(quad.contains(out_bounds_point) == false); +}