mobility: add corner detection to rectangle

Improvements thanks to Gabriel Ferreira (gabrielcarvfer@gmail.com)
This commit is contained in:
Tommaso Pecorella
2023-08-24 22:12:14 -05:00
parent 4164725e71
commit d47680bb9e
4 changed files with 343 additions and 109 deletions

View File

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

View File

@@ -23,6 +23,7 @@
#include "ns3/vector.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <sstream>
@@ -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<double, 4> 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<double>::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<double>::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

View File

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

View File

@@ -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 <gabrielcarvfer@gmail.com>
*/
#include <ns3/rectangle.h>
#include <ns3/simulator.h>
#include <ns3/test.h>
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();
}