Add sf::Shape::getGeometricCenter()

This adds

    virtual Vector2f getGeometricCenter() const = 0;

Signed-off-by: Ted Lyngmo <ted@lyncon.se>
This commit is contained in:
Ted Lyngmo 2023-04-24 09:25:44 +02:00 committed by Chris Thrasher
parent f95d159711
commit 9855552f64
10 changed files with 270 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -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
///

View File

@ -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

View File

@ -78,4 +78,11 @@ Vector2f RectangleShape::getPoint(std::size_t index) const
}
}
////////////////////////////////////////////////////////////
Vector2f RectangleShape::getGeometricCenter() const
{
return m_size / 2.f;
}
} // namespace sf

View File

@ -29,8 +29,7 @@
#include <SFML/Graphics/Shape.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <cmath>
#include <algorithm>
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
{

View File

@ -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));
}
}
}

View File

@ -1,5 +1,8 @@
#include <SFML/Graphics/ConvexShape.hpp>
// Other 1st party headers
#include <SFML/Graphics/CircleShape.hpp>
#include <catch2/catch_test_macros.hpp>
#include <SystemUtil.hpp>
@ -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));
}
}
}

View File

@ -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);
}
}

View File

@ -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")