From 9855552f6431016f1953e36b2f058fbd239ed725 Mon Sep 17 00:00:00 2001 From: Ted Lyngmo Date: Mon, 24 Apr 2023 09:25:44 +0200 Subject: [PATCH] Add `sf::Shape::getGeometricCenter()` This adds virtual Vector2f getGeometricCenter() const = 0; Signed-off-by: Ted Lyngmo --- include/SFML/Graphics/CircleShape.hpp | 12 ++ include/SFML/Graphics/RectangleShape.hpp | 12 ++ include/SFML/Graphics/Shape.hpp | 12 ++ src/SFML/Graphics/CircleShape.cpp | 7 ++ src/SFML/Graphics/RectangleShape.cpp | 7 ++ src/SFML/Graphics/Shape.cpp | 53 ++++++++- test/Graphics/CircleShape.test.cpp | 19 +++ test/Graphics/ConvexShape.test.cpp | 145 +++++++++++++++++++++++ test/Graphics/RectangleShape.test.cpp | 3 + test/Graphics/Shape.test.cpp | 3 +- 10 files changed, 270 insertions(+), 3 deletions(-) diff --git a/include/SFML/Graphics/CircleShape.hpp b/include/SFML/Graphics/CircleShape.hpp index f12bcf88d..5119f4bc7 100644 --- a/include/SFML/Graphics/CircleShape.hpp +++ b/include/SFML/Graphics/CircleShape.hpp @@ -105,6 +105,18 @@ public: //////////////////////////////////////////////////////////// Vector2f getPoint(std::size_t index) const override; + //////////////////////////////////////////////////////////// + /// \brief Get the geometric center of the circle + /// + /// The returned point is in local coordinates, that is, + /// the shape's transforms (position, rotation, scale) are + /// not taken into account. + /// + /// \return The geometric center of the shape + /// + //////////////////////////////////////////////////////////// + Vector2f getGeometricCenter() const override; + private: //////////////////////////////////////////////////////////// // Member data diff --git a/include/SFML/Graphics/RectangleShape.hpp b/include/SFML/Graphics/RectangleShape.hpp index 4e4fca517..f048c193c 100644 --- a/include/SFML/Graphics/RectangleShape.hpp +++ b/include/SFML/Graphics/RectangleShape.hpp @@ -93,6 +93,18 @@ public: //////////////////////////////////////////////////////////// Vector2f getPoint(std::size_t index) const override; + //////////////////////////////////////////////////////////// + /// \brief Get the geometric center of the rectangle + /// + /// The returned point is in local coordinates, that is, + /// the shape's transforms (position, rotation, scale) are + /// not taken into account. + /// + /// \return The geometric center of the shape + /// + //////////////////////////////////////////////////////////// + Vector2f getGeometricCenter() const override; + private: //////////////////////////////////////////////////////////// // Member data diff --git a/include/SFML/Graphics/Shape.hpp b/include/SFML/Graphics/Shape.hpp index 6901309d9..754f4a1d7 100644 --- a/include/SFML/Graphics/Shape.hpp +++ b/include/SFML/Graphics/Shape.hpp @@ -208,6 +208,18 @@ public: //////////////////////////////////////////////////////////// virtual Vector2f getPoint(std::size_t index) const = 0; + //////////////////////////////////////////////////////////// + /// \brief Get the geometric center of the shape + /// + /// The returned point is in local coordinates, that is, + /// the shape's transforms (position, rotation, scale) are + /// not taken into account. + /// + /// \return The geometric center of the shape + /// + //////////////////////////////////////////////////////////// + virtual Vector2f getGeometricCenter() const; + //////////////////////////////////////////////////////////// /// \brief Get the local bounding rectangle of the entity /// diff --git a/src/SFML/Graphics/CircleShape.cpp b/src/SFML/Graphics/CircleShape.cpp index 029e65feb..f600398ec 100644 --- a/src/SFML/Graphics/CircleShape.cpp +++ b/src/SFML/Graphics/CircleShape.cpp @@ -75,4 +75,11 @@ Vector2f CircleShape::getPoint(std::size_t index) const return Vector2f(m_radius, m_radius) + Vector2f(m_radius, angle); } + +//////////////////////////////////////////////////////////// +Vector2f CircleShape::getGeometricCenter() const +{ + return Vector2f(m_radius, m_radius); +} + } // namespace sf diff --git a/src/SFML/Graphics/RectangleShape.cpp b/src/SFML/Graphics/RectangleShape.cpp index 549a370b4..d1dc8d9fe 100644 --- a/src/SFML/Graphics/RectangleShape.cpp +++ b/src/SFML/Graphics/RectangleShape.cpp @@ -78,4 +78,11 @@ Vector2f RectangleShape::getPoint(std::size_t index) const } } + +//////////////////////////////////////////////////////////// +Vector2f RectangleShape::getGeometricCenter() const +{ + return m_size / 2.f; +} + } // namespace sf diff --git a/src/SFML/Graphics/Shape.cpp b/src/SFML/Graphics/Shape.cpp index 3bfe50100..08e0736da 100644 --- a/src/SFML/Graphics/Shape.cpp +++ b/src/SFML/Graphics/Shape.cpp @@ -29,8 +29,7 @@ #include #include -#include - +#include namespace { @@ -130,6 +129,56 @@ float Shape::getOutlineThickness() const } +//////////////////////////////////////////////////////////// +Vector2f Shape::getGeometricCenter() const +{ + const auto count = getPointCount(); + + switch (count) + { + case 0: + assert(false); + return Vector2f{}; + case 1: + return getPoint(0); + case 2: + return (getPoint(0) + getPoint(1)) / 2.f; + default: // more than two points + Vector2f centroid; + float twiceArea = 0; + + auto previousPoint = getPoint(count - 1); + for (std::size_t i = 0; i < count; ++i) + { + const auto currentPoint = getPoint(i); + const float product = previousPoint.cross(currentPoint); + twiceArea += product; + centroid += (currentPoint + previousPoint) * product; + + previousPoint = currentPoint; + } + + if (twiceArea != 0.f) + { + return centroid / 3.f / twiceArea; + } + + // Fallback for no area - find the center of the bounding box + auto minPoint = getPoint(0); + auto maxPoint = minPoint; + for (std::size_t i = 1; i < count; ++i) + { + const auto currentPoint = getPoint(i); + minPoint.x = std::min(minPoint.x, currentPoint.x); + maxPoint.x = std::max(maxPoint.x, currentPoint.x); + minPoint.y = std::min(minPoint.y, currentPoint.y); + maxPoint.y = std::max(maxPoint.y, currentPoint.y); + } + return (maxPoint + minPoint) / 2.f; + } +} + + //////////////////////////////////////////////////////////// FloatRect Shape::getLocalBounds() const { diff --git a/test/Graphics/CircleShape.test.cpp b/test/Graphics/CircleShape.test.cpp index 594e8c28c..a4bc826dc 100644 --- a/test/Graphics/CircleShape.test.cpp +++ b/test/Graphics/CircleShape.test.cpp @@ -22,6 +22,7 @@ TEST_CASE("[Graphics] sf::CircleShape") CHECK(circle.getPointCount() == 30); for (std::size_t i = 0; i < circle.getPointCount(); ++i) CHECK(circle.getPoint(i) == sf::Vector2f(0, 0)); + CHECK(circle.getGeometricCenter() == sf::Vector2f(0, 0)); } SECTION("Radius constructor") @@ -59,6 +60,7 @@ TEST_CASE("[Graphics] sf::CircleShape") CHECK(circle.getPoint(27) == Approx(sf::Vector2f(6.183218002f, 2.864748001f))); CHECK(circle.getPoint(28) == Approx(sf::Vector2f(8.898950577f, 1.296818733f))); CHECK(circle.getPoint(29) == Approx(sf::Vector2f(11.881320953f, 0.327786446f))); + CHECK(circle.getGeometricCenter() == sf::Vector2f(15.f, 15.f)); } SECTION("Radius and point count constructor") @@ -74,6 +76,7 @@ TEST_CASE("[Graphics] sf::CircleShape") CHECK(circle.getPoint(5) == Approx(sf::Vector2f(1.464466095f, 8.535533905f))); CHECK(circle.getPoint(6) == Approx(sf::Vector2f(0.000000000f, 4.999999523f))); CHECK(circle.getPoint(7) == Approx(sf::Vector2f(1.464465857f, 1.464466572f))); + CHECK(circle.getGeometricCenter() == sf::Vector2f(5.f, 5.f)); } SECTION("Set radius") @@ -88,6 +91,7 @@ TEST_CASE("[Graphics] sf::CircleShape") CHECK(circle.getPoint(3) == Approx(sf::Vector2f(10.000000000f, 20.000000000f))); CHECK(circle.getPoint(4) == Approx(sf::Vector2f(1.339746475f, 15.000000000f))); CHECK(circle.getPoint(5) == Approx(sf::Vector2f(1.339745522f, 5.000000000f))); + CHECK(circle.getGeometricCenter() == sf::Vector2f(10.f, 10.f)); } SECTION("Set point count") @@ -100,6 +104,7 @@ TEST_CASE("[Graphics] sf::CircleShape") CHECK(circle.getPoint(1) == Approx(sf::Vector2f(8.000000000f, 4.000000000f))); CHECK(circle.getPoint(2) == Approx(sf::Vector2f(3.999999762f, 8.000000000f))); CHECK(circle.getPoint(3) == Approx(sf::Vector2f(0.000000000f, 3.999999762f))); + CHECK(circle.getGeometricCenter() == sf::Vector2f(4.f, 4.f)); } SECTION("Equilateral triangle") @@ -110,5 +115,19 @@ TEST_CASE("[Graphics] sf::CircleShape") CHECK(triangle.getPoint(0) == Approx(sf::Vector2f(1.999999881f, 0.000000000f))); CHECK(triangle.getPoint(1) == Approx(sf::Vector2f(3.732050896f, 3.000000000f))); CHECK(triangle.getPoint(2) == Approx(sf::Vector2f(0.267949224f, 3.000000000f))); + CHECK(triangle.getGeometricCenter() == sf::Vector2f(2.f, 2.f)); + } + + SECTION("Geometric center") + { + SECTION("2 points") + { + CHECK(sf::CircleShape(2.f, 2).getGeometricCenter() == sf::Vector2f(2.f, 2.f)); + } + + SECTION("3 points") + { + CHECK(sf::CircleShape(4.f, 3).getGeometricCenter() == sf::Vector2f(4.f, 4.f)); + } } } diff --git a/test/Graphics/ConvexShape.test.cpp b/test/Graphics/ConvexShape.test.cpp index fb9f357e5..1756cde52 100644 --- a/test/Graphics/ConvexShape.test.cpp +++ b/test/Graphics/ConvexShape.test.cpp @@ -1,5 +1,8 @@ #include +// Other 1st party headers +#include + #include #include @@ -45,4 +48,146 @@ TEST_CASE("[Graphics] sf::ConvexShape") convex.setPoint(0, {3, 4}); CHECK(convex.getPoint(0) == sf::Vector2f(3, 4)); } + + SECTION( + "Construct clockwise ConvexShapes from CircleShapes to verify that they get approx. the same geometric center") + { + sf::ConvexShape convex; + for (unsigned int i = 2; i < 10; ++i) + { + const sf::CircleShape circle(4.f, i); + convex.setPointCount(i); + for (unsigned int j = 0; j < i; ++j) + { + convex.setPoint(j, circle.getPoint(j)); + } + CHECK(convex.getGeometricCenter() == Approx(circle.getGeometricCenter())); + } + } + + SECTION( + "Construct counterclockwise ConvexShapes from CircleShapes to verify that they get approx. the same geometric " + "center") + { + sf::ConvexShape convex; + for (unsigned int i = 2; i < 10; ++i) + { + const sf::CircleShape circle(4.f, i); + convex.setPointCount(i); + for (unsigned int j = 0; j < i; ++j) + { + convex.setPoint(i - 1 - j, circle.getPoint(j)); + } + CHECK(convex.getGeometricCenter() == Approx(circle.getGeometricCenter())); + } + } + + SECTION("Geometric center for one point") + { + sf::ConvexShape convex(1); + convex.setPoint(0, {1.f, 1.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(1.f, 1.f)); + } + + SECTION("Geometric center for two points") + { + sf::ConvexShape convex(2); + convex.setPoint(0, {0.f, 0.f}); + convex.setPoint(1, {4.f, 2.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(2.f, 1.f)); + } + + SECTION("Geometric center for three points with a small area") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {-100000.f, 0.f}); + convex.setPoint(1, {100000.f, 0.f}); + convex.setPoint(2, {100000.f, 0.000001f}); + CHECK(convex.getGeometricCenter() == Approx(sf::Vector2f(100000.f / 3.f, 0.f))); + } + + SECTION("Geometric center for aligned points") + { + SECTION("Geometric center for partly aligned points") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {-100.f, 0.f}); + convex.setPoint(1, {0.f, 0.f}); + convex.setPoint(2, {100.f, 1.f}); + CHECK(convex.getGeometricCenter() == Approx(sf::Vector2f(0.f, 1.f / 3.f))); + } + + SECTION("Geometric center for aligned points with the two furthest apart not first and last") + { + sf::ConvexShape convex(4); + convex.setPoint(0, {-50.f, -50.f}); + convex.setPoint(1, {-150.f, -150.f}); + convex.setPoint(2, {150.f, 150.f}); + convex.setPoint(3, {50.f, 50.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(0.f, 0.f)); + } + + SECTION("Geometric center for aligned points increasing x and y") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {1.f, 1.f}); + convex.setPoint(1, {5.f, 3.f}); + convex.setPoint(2, {9.f, 5.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(5.f, 3.f)); + } + + SECTION("Geometric center for aligned points increasing x, decreasing y") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {1.f, 5.f}); + convex.setPoint(1, {5.f, 3.f}); + convex.setPoint(2, {9.f, 1.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(5.f, 3.f)); + } + + SECTION("Geometric center for aligned points decreasing x and y") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {9.f, 5.f}); + convex.setPoint(1, {5.f, 3.f}); + convex.setPoint(2, {1.f, 1.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(5.f, 3.f)); + } + + SECTION("Geometric center for aligned points decreasing x, increasing y") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {9.f, 1.f}); + convex.setPoint(1, {5.f, 3.f}); + convex.setPoint(2, {1.f, 5.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(5.f, 3.f)); + } + + SECTION("Geometric center for aligned points with the same x value") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {1.f, 2.f}); + convex.setPoint(1, {1.f, 3.f}); + convex.setPoint(2, {1.f, 1.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(1.f, 2.f)); + } + + SECTION("Geometric center for aligned points with the same y value") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {2.f, 5.f}); + convex.setPoint(1, {3.f, 5.f}); + convex.setPoint(2, {1.f, 5.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(2.f, 5.f)); + } + + SECTION("Geometric center for aligned points out of order") + { + sf::ConvexShape convex(3); + convex.setPoint(0, {5.f, 3.f}); + convex.setPoint(1, {1.f, 5.f}); + convex.setPoint(2, {9.f, 1.f}); + CHECK(convex.getGeometricCenter() == sf::Vector2f(5.f, 3.f)); + } + } } diff --git a/test/Graphics/RectangleShape.test.cpp b/test/Graphics/RectangleShape.test.cpp index eeae52356..cb676c4ad 100644 --- a/test/Graphics/RectangleShape.test.cpp +++ b/test/Graphics/RectangleShape.test.cpp @@ -24,6 +24,7 @@ TEST_CASE("[Graphics] sf::RectangleShape") CHECK(rectangle.getPoint(1) == sf::Vector2f(0, 0)); CHECK(rectangle.getPoint(2) == sf::Vector2f(0, 0)); CHECK(rectangle.getPoint(3) == sf::Vector2f(0, 0)); + CHECK(rectangle.getGeometricCenter() == sf::Vector2f(0, 0)); } SECTION("Size constructor") @@ -35,6 +36,7 @@ TEST_CASE("[Graphics] sf::RectangleShape") CHECK(rectangle.getPoint(1) == sf::Vector2f(9, 0)); CHECK(rectangle.getPoint(2) == sf::Vector2f(9, 8)); CHECK(rectangle.getPoint(3) == sf::Vector2f(0, 8)); + CHECK(rectangle.getGeometricCenter() == sf::Vector2f(9.f, 8.f) / 2.f); } SECTION("Set size") @@ -42,5 +44,6 @@ TEST_CASE("[Graphics] sf::RectangleShape") sf::RectangleShape rectangle({7, 6}); rectangle.setSize({5, 4}); CHECK(rectangle.getSize() == sf::Vector2f(5, 4)); + CHECK(rectangle.getGeometricCenter() == sf::Vector2f(5.f, 4.f) / 2.f); } } diff --git a/test/Graphics/Shape.test.cpp b/test/Graphics/Shape.test.cpp index 5a2e820c1..a3c063b4e 100644 --- a/test/Graphics/Shape.test.cpp +++ b/test/Graphics/Shape.test.cpp @@ -88,13 +88,14 @@ TEST_CASE("[Graphics] sf::Shape") CHECK(triangleShape.getOutlineThickness() == 3.14f); } - SECTION("Virtual functions: getPoint, getPointCount") + SECTION("Virtual functions: getPoint, getPointCount, getGeometricCenter") { const TriangleShape triangleShape({2, 2}); CHECK(triangleShape.getPointCount() == 3); CHECK(triangleShape.getPoint(0) == sf::Vector2f(1, 0)); CHECK(triangleShape.getPoint(1) == sf::Vector2f(0, 2)); CHECK(triangleShape.getPoint(2) == sf::Vector2f(2, 2)); + CHECK(triangleShape.getGeometricCenter() == sf::Vector2f(1.f, 4.f / 3.f)); } SECTION("Get bounds")