From d47680bb9ee4b8e55c073b023169a1d38ce07576 Mon Sep 17 00:00:00 2001 From: Tommaso Pecorella Date: Thu, 24 Aug 2023 22:12:14 -0500 Subject: [PATCH] mobility: add corner detection to rectangle Improvements thanks to Gabriel Ferreira (gabrielcarvfer@gmail.com) --- src/mobility/CMakeLists.txt | 1 + src/mobility/model/rectangle.cc | 224 ++++++++++-------- src/mobility/model/rectangle.h | 21 +- .../test/rectangle-closest-border-test.cc | 206 ++++++++++++++++ 4 files changed, 343 insertions(+), 109 deletions(-) create mode 100644 src/mobility/test/rectangle-closest-border-test.cc diff --git a/src/mobility/CMakeLists.txt b/src/mobility/CMakeLists.txt index edab43a26..8722eeb32 100644 --- a/src/mobility/CMakeLists.txt +++ b/src/mobility/CMakeLists.txt @@ -50,6 +50,7 @@ build_lib( test/mobility-trace-test-suite.cc test/ns2-mobility-helper-test-suite.cc test/rand-cart-around-geo-test.cc + test/rectangle-closest-border-test.cc test/steady-state-random-waypoint-mobility-model-test.cc test/waypoint-mobility-model-test.cc ) diff --git a/src/mobility/model/rectangle.cc b/src/mobility/model/rectangle.cc index ef612fe2a..4b30882f6 100644 --- a/src/mobility/model/rectangle.cc +++ b/src/mobility/model/rectangle.cc @@ -23,6 +23,7 @@ #include "ns3/vector.h" #include +#include #include #include @@ -52,126 +53,99 @@ Rectangle::IsInside(const Vector& position) const position.y >= this->yMin; } +bool +Rectangle::IsOnTheBorder(const Vector& position) const +{ + return position.x == this->xMax || position.x == this->xMin || position.y == this->yMax || + position.y == this->yMin; +} + Rectangle::Side Rectangle::GetClosestSide(const Vector& position) const { - if (IsInside(position)) + std::array distanceFromBorders{ + std::abs(position.x - this->xMin), // left border + std::abs(this->xMax - position.x), // right border + std::abs(position.y - this->yMin), // bottom border + std::abs(this->yMax - position.y), // top border + }; + uint8_t flags = 0; + double minDist = std::numeric_limits::max(); + for (int i = 0; i < 4; i++) { - double xMinDist = std::abs(position.x - this->xMin); - double xMaxDist = std::abs(this->xMax - position.x); - double yMinDist = std::abs(position.y - this->yMin); - double yMaxDist = std::abs(this->yMax - position.y); - double minX = std::min(xMinDist, xMaxDist); - double minY = std::min(yMinDist, yMaxDist); - if (minX < minY) + if (distanceFromBorders[i] > minDist) { - if (xMinDist < xMaxDist) - { - return LEFT; - } - else - { - return RIGHT; - } + continue; } - else + // In case we find a border closer to the position, + // we replace it and mark the flag + if (distanceFromBorders[i] < minDist) { - if (yMinDist < yMaxDist) - { - return BOTTOM; - } - else - { - return TOP; - } + minDist = distanceFromBorders[i]; + flags = 0; } + flags |= (0b1000 >> i); } - else + NS_ASSERT(minDist != std::numeric_limits::max()); + Rectangle::Side side; + switch (flags) { - if (position.x < this->xMin) + // LRBT + case 0b1111: + // Every side is equally distant, so choose any + side = TOPSIDE; + break; + case 0b0011: + // Opposing sides are equally distant, so we need to check the other two + // We also need to check if we're inside or outside. + side = TOPSIDE; + if (!IsInside(position)) { - if (position.y < this->yMin) - { - double yDiff = this->yMin - position.y; - double xDiff = this->xMin - position.x; - if (yDiff > xDiff) - { - return BOTTOM; - } - else - { - return LEFT; - } - } - else if (position.y < this->yMax) - { - return LEFT; - } - else - { - double yDiff = position.y - this->yMax; - double xDiff = this->xMin - position.x; - if (yDiff > xDiff) - { - return TOP; - } - else - { - return LEFT; - } - } + side = (distanceFromBorders[0] > distanceFromBorders[1]) ? RIGHTSIDE : LEFTSIDE; } - else if (position.x < this->xMax) + break; + case 0b1100: + // Opposing sides are equally distant, so we need to check the other two + // We also need to check if we're inside or outside. + side = RIGHTSIDE; + if (!IsInside(position)) { - if (position.y < this->yMin) - { - return BOTTOM; - } - else if (position.y < this->yMax) - { - NS_FATAL_ERROR( - "This region should have been reached if the IsInside check was true"); - return TOP; // silence compiler warning - } - else - { - return TOP; - } - } - else - { - if (position.y < this->yMin) - { - double yDiff = this->yMin - position.y; - double xDiff = position.x - this->xMin; - if (yDiff > xDiff) - { - return BOTTOM; - } - else - { - return RIGHT; - } - } - else if (position.y < this->yMax) - { - return RIGHT; - } - else - { - double yDiff = position.y - this->yMax; - double xDiff = position.x - this->xMin; - if (yDiff > xDiff) - { - return TOP; - } - else - { - return RIGHT; - } - } + side = (distanceFromBorders[2] > distanceFromBorders[3]) ? TOPSIDE : BOTTOMSIDE; } + break; + case 0b0001: + case 0b1101: + side = TOPSIDE; + break; + case 0b0010: + case 0b1110: + side = BOTTOMSIDE; + break; + case 0b0100: + case 0b0111: + side = RIGHTSIDE; + break; + case 0b0101: + side = TOPRIGHTCORNER; + break; + case 0b0110: + side = BOTTOMRIGHTCORNER; + break; + case 0b1000: + case 0b1011: + side = LEFTSIDE; + break; + case 0b1001: + side = TOPLEFTCORNER; + break; + case 0b1010: + side = BOTTOMLEFTCORNER; + break; + default: + NS_FATAL_ERROR("Impossible case"); + break; } + return side; } Vector @@ -247,4 +221,44 @@ operator>>(std::istream& is, Rectangle& rectangle) return is; } +/** + * \brief Stream insertion operator. + * + * \param os the stream + * \param side the rectangle side + * \returns a reference to the stream + */ +std::ostream& +operator<<(std::ostream& os, const Rectangle::Side& side) +{ + switch (side) + { + case Rectangle::RIGHTSIDE: + os << "RIGHTSIDE"; + break; + case Rectangle::LEFTSIDE: + os << "LEFTSIDE"; + break; + case Rectangle::TOPSIDE: + os << "TOPSIDE"; + break; + case Rectangle::BOTTOMSIDE: + os << "BOTTOMSIDE"; + break; + case Rectangle::TOPRIGHTCORNER: + os << "TOPRIGHTCORNER"; + break; + case Rectangle::TOPLEFTCORNER: + os << "TOPLEFTCORNER"; + break; + case Rectangle::BOTTOMRIGHTCORNER: + os << "BOTTOMRIGHTCORNER"; + break; + case Rectangle::BOTTOMLEFTCORNER: + os << "BOTTOMLEFTCORNER"; + break; + } + return os; +} + } // namespace ns3 diff --git a/src/mobility/model/rectangle.h b/src/mobility/model/rectangle.h index c846a7a9e..b3ba5117b 100644 --- a/src/mobility/model/rectangle.h +++ b/src/mobility/model/rectangle.h @@ -39,10 +39,14 @@ class Rectangle */ enum Side { - RIGHT, - LEFT, - TOP, - BOTTOM + RIGHTSIDE = 0, + LEFTSIDE, + TOPSIDE, + BOTTOMSIDE, + TOPRIGHTCORNER, + TOPLEFTCORNER, + BOTTOMRIGHTCORNER, + BOTTOMLEFTCORNER }; /** @@ -66,6 +70,14 @@ class Rectangle * It ignores the z coordinate. */ bool IsInside(const Vector& position) const; + /** + * \param position the position to test. + * \return true if the input position is located on the rectable border, false otherwise. + * + * This method compares only the x and y coordinates of the input position. + * It ignores the z coordinate. + */ + bool IsOnTheBorder(const Vector& position) const; /** * \param position the position to test. * \return the side of the rectangle the input position is closest to. @@ -94,6 +106,7 @@ class Rectangle std::ostream& operator<<(std::ostream& os, const Rectangle& rectangle); std::istream& operator>>(std::istream& is, Rectangle& rectangle); +std::ostream& operator<<(std::ostream& os, const Rectangle::Side& side); ATTRIBUTE_HELPER_HEADER(Rectangle); diff --git a/src/mobility/test/rectangle-closest-border-test.cc b/src/mobility/test/rectangle-closest-border-test.cc new file mode 100644 index 000000000..e0ccf3ef0 --- /dev/null +++ b/src/mobility/test/rectangle-closest-border-test.cc @@ -0,0 +1,206 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Gabriel Ferreira + */ + +#include +#include +#include + +using namespace ns3; + +/** + * \ingroup mobility-test + * + * \brief Rectangle detection of closest border to a point, inside or outside. + */ +class RectangleClosestBorderTestSuite : public TestSuite +{ + public: + /** + * Constructor + */ + RectangleClosestBorderTestSuite(); +}; + +/** + * \ingroup mobility-test + * + * \brief TestCase to check the rectangle line intersection + */ +class RectangleClosestBorderTestCase : public TestCase +{ + public: + /** + * \brief Create RectangleClosestBorderTestCase + * \param x Index of the first position to generate + * \param y Index of the second position to generate + * \param rectangle The 2D rectangle + * \param side The expected result of the test + */ + RectangleClosestBorderTestCase(double x, double y, Rectangle rectangle, Rectangle::Side side); + /** + * \brief Builds the test name string based on provided parameter values + * \param x Index of the first position to generate + * \param y Index of the second position to generate + * \param rectangle The 2D rectangle + * \param side The expected result of the test + * + * \return The name string + */ + std::string BuildNameString(double x, double y, Rectangle rectangle, Rectangle::Side side); + /** + * Destructor + */ + ~RectangleClosestBorderTestCase() override; + + private: + /** + * \brief Setup the simulation according to the configuration set by the + * class constructor, run it, and verify the result. + */ + void DoRun() override; + + double m_x{0.0}; //!< X coordinate of the point to be tested + double m_y{0.0}; //!< Y coordinate of the point to be tested + Rectangle m_rectangle; //!< The rectangle to check the intersection with + + /** + * Flag to indicate the intersection. + * True, for intersection, false otherwise. + */ + Rectangle::Side m_side{ns3::Rectangle::TOPSIDE}; +}; + +/** + * This TestSuite tests the intersection of a line segment + * between two 2D positions with a 2D rectangle. It generates two + * positions from a set of predefined positions (see GeneratePosition method), + * and then tests the intersection of a line segments between them with a rectangle + * of predefined dimensions. + */ + +RectangleClosestBorderTestSuite::RectangleClosestBorderTestSuite() + : TestSuite("rectangle-closest-border", UNIT) +{ + // Rectangle in the positive x-plane to check the intersection with. + Rectangle rectangle = Rectangle(0.0, 10.0, 0.0, 10.0); + + /* 2 3 4 + * +----------------------------+ (10,10) + * | 11 16 12 | + * | | + * | | + * 1 | 15 18 17 | 5 + * | | + * | | + * | 10 14 13 | + * +----------------------------+ + * 9 7 + * (0,0) + * + * + * + * 8 + */ + // Left side (1 and 15) + AddTestCase(new RectangleClosestBorderTestCase(-5, 5, rectangle, Rectangle::LEFTSIDE), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(2, 5, rectangle, Rectangle::LEFTSIDE), + TestCase::QUICK); + // Right side (5 and 17) + AddTestCase(new RectangleClosestBorderTestCase(17, 5, rectangle, Rectangle::RIGHTSIDE), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(7, 5, rectangle, Rectangle::RIGHTSIDE), + TestCase::QUICK); + // Bottom side (8 and 14) + AddTestCase(new RectangleClosestBorderTestCase(5, -7, rectangle, Rectangle::BOTTOMSIDE), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(5, 1, rectangle, Rectangle::BOTTOMSIDE), + TestCase::QUICK); + // Top side (3 and 16) + AddTestCase(new RectangleClosestBorderTestCase(5, 15, rectangle, Rectangle::TOPSIDE), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(5, 7, rectangle, Rectangle::TOPSIDE), + TestCase::QUICK); + // Left-Bottom corner (9 and 10) + AddTestCase(new RectangleClosestBorderTestCase(-1, -1, rectangle, Rectangle::BOTTOMLEFTCORNER), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(0, 0, rectangle, Rectangle::BOTTOMLEFTCORNER), + TestCase::QUICK); + // Right-Bottom corner (7 and 13) + AddTestCase(new RectangleClosestBorderTestCase(11, -1, rectangle, Rectangle::BOTTOMRIGHTCORNER), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(9, 1, rectangle, Rectangle::BOTTOMRIGHTCORNER), + TestCase::QUICK); + // Left-Top corner (2 and 11) + AddTestCase(new RectangleClosestBorderTestCase(-1, 11, rectangle, Rectangle::TOPLEFTCORNER), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(1, 9, rectangle, Rectangle::TOPLEFTCORNER), + TestCase::QUICK); + // Right-Top corner (4 and 12) + AddTestCase(new RectangleClosestBorderTestCase(11, 11, rectangle, Rectangle::TOPRIGHTCORNER), + TestCase::QUICK); + AddTestCase(new RectangleClosestBorderTestCase(9, 9, rectangle, Rectangle::TOPRIGHTCORNER), + TestCase::QUICK); + // Central position (18) + AddTestCase(new RectangleClosestBorderTestCase(5, 5, rectangle, Rectangle::TOPSIDE), + TestCase::QUICK); +} + +/** + * \ingroup mobility-test + * Static variable for test initialization + */ +static RectangleClosestBorderTestSuite rectangleClosestBorderTestSuite; + +RectangleClosestBorderTestCase::RectangleClosestBorderTestCase(double x, + double y, + Rectangle rectangle, + Rectangle::Side side) + : TestCase(BuildNameString(x, y, rectangle, side)), + m_x(x), + m_y(y), + m_rectangle(rectangle), + m_side(side) +{ +} + +RectangleClosestBorderTestCase::~RectangleClosestBorderTestCase() +{ +} + +std::string +RectangleClosestBorderTestCase::BuildNameString(double x, + double y, + Rectangle rectangle, + Rectangle::Side side) +{ + std::ostringstream oss; + oss << "Rectangle closest border test : checking" + << " (x,y) = (" << x << "," << y << ") closest border to the rectangle (" << rectangle + << "). The expected side = " << side; + return oss.str(); +} + +void +RectangleClosestBorderTestCase::DoRun() +{ + Vector position(m_x, m_y, 0.0); + Rectangle::Side side = m_rectangle.GetClosestSideOrCorner(position); + + NS_TEST_ASSERT_MSG_EQ(side, m_side, "Unexpected result of rectangle side!"); + Simulator::Destroy(); +}