mobility: add corner detection to rectangle
Improvements thanks to Gabriel Ferreira (gabrielcarvfer@gmail.com)
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
206
src/mobility/test/rectangle-closest-border-test.cc
Normal file
206
src/mobility/test/rectangle-closest-border-test.cc
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user