Commit f6b39d9f authored by Fabien Grzeskowiak's avatar Fabien Grzeskowiak

Merge branch 'master' of gitlab.inria.fr:OCSR/UMANS

parents e8332bb3 0346b9f9
......@@ -54,12 +54,12 @@ namespace ORCALibrary
{
inline float det(const Vector2D &vector1, const Vector2D &vector2)
{
return (float)(vector1.x() * vector2.y() - vector1.y() * vector2.x());
return (float)(vector1.x * vector2.y - vector1.y * vector2.x);
}
/* Search for the best new velocity. */
Solution Solver::solveOrcaProgram(const Agent& agent,
const float timeHorizon, const float currentTime, const float simulationTimeStep, const NeighborList& neighbors) const
const float timeHorizon, const float currentTime, const float simulationTimeStep, const NeighborList& neighbors, const float maxDistance) const
{
const Vector2D& prefVelocity_ = agent.getPreferredVelocity();
const float maxSpeed_ = agent.getMaximumSpeed();
......@@ -75,7 +75,7 @@ namespace ORCALibrary
Solution result;
createAgentOrcaLines(agent, result.orcaLines, timeHorizon, simulationTimeStep, neighbors.first);
createAgentOrcaLines(agent, result.orcaLines, timeHorizon, simulationTimeStep, neighbors.first, maxDistance);
// TODO: include obstacle lines
// ...
......@@ -95,17 +95,20 @@ namespace ORCALibrary
}
void Solver::createAgentOrcaLines(const Agent& agent, std::vector<Line>& orcaLines_, const float timeHorizon_, const float simulationTimeStep,
const AgentNeighborList& agentNeighbors_) const
const AgentNeighborList& agentNeighbors_, const float maxDistance) const
{
const Vector2D& position_ = agent.getPosition();
const Vector2D& velocity_ = agent.getVelocity();
const float maxSpeed_ = agent.getMaximumSpeed();
const float radius_ = agent.getRadius();
const float radiusSq = radius_ * radius_;
const float maxDistSq = maxDistance * maxDistance;
/* Create agent ORCA lines. */
for (size_t i = 0; i < agentNeighbors_.size(); ++i) {
const PhantomAgent& other = agentNeighbors_[i];
for (const PhantomAgent& other : agentNeighbors_)
{
if (distanceSquared(other.position, position_) > maxDistSq)
continue;
const Vector2D& relativePosition = other.position - position_;
const Vector2D& relativeVelocity = velocity_ - other.velocity;
......@@ -129,7 +132,7 @@ namespace ORCALibrary
const float wLength = std::sqrt(wLengthSq);
const Vector2D& unitW = w / wLength;
line.direction = Vector2D(unitW.y(), -unitW.x());
line.direction = Vector2D(unitW.y, -unitW.x);
u = unitW * (combinedRadius / timeHorizon_ - wLength);
}
else {
......@@ -138,11 +141,11 @@ namespace ORCALibrary
if (det(relativePosition, w) > 0.0f) {
/* Project on left leg. */
line.direction = Vector2D(relativePosition.x() * leg - relativePosition.y() * combinedRadius, relativePosition.x() * combinedRadius + relativePosition.y() * leg) / distSq;
line.direction = Vector2D(relativePosition.x * leg - relativePosition.y * combinedRadius, relativePosition.x * combinedRadius + relativePosition.y * leg) / distSq;
}
else {
/* Project on right leg. */
line.direction = Vector2D(relativePosition.x() * leg + relativePosition.y() * combinedRadius, -relativePosition.x() * combinedRadius + relativePosition.y() * leg) * -1 / distSq;
line.direction = Vector2D(relativePosition.x * leg + relativePosition.y * combinedRadius, -relativePosition.x * combinedRadius + relativePosition.y * leg) * -1 / distSq;
}
const float dotProduct2 = relativeVelocity.dot(line.direction);
......@@ -159,7 +162,7 @@ namespace ORCALibrary
const float wLength = w.sqrMagnitude();
const Vector2D unitW = w / wLength;
line.direction = Vector2D(unitW.y(), -unitW.x());
line.direction = Vector2D(unitW.y, -unitW.x);
u = unitW * (combinedRadius / simulationTimeStep - wLength);
}
......@@ -309,7 +312,7 @@ namespace ORCALibrary
const Vector2D tempResult = result;
if (linearProgram2(projLines, radius, Vector2D(-lines[i].direction.y(), lines[i].direction.x()), true, result) < projLines.size()) {
if (linearProgram2(projLines, radius, Vector2D(-lines[i].direction.y, lines[i].direction.x), true, result) < projLines.size()) {
/* This should in principle not happen. The result is by definition
* already in the feasible region of this linear program. If it fails,
* it is due to small floating point error, and the current result is
......
......@@ -65,13 +65,13 @@ namespace ORCALibrary
{
public:
Solution solveOrcaProgram(const Agent& agent,
const float timeHorizon, const float currentTime, const float simulationTimeStep, const NeighborList& neighbors) const;
const float timeHorizon, const float currentTime, const float simulationTimeStep, const NeighborList& neighbors, const float maxDistance) const;
private:
/*void createObstacleOrcaLines(const Agent& agent,
std::vector<Line>& orcaLines_, const float timeHorizonObst_, const float simulationTimeStep, const ObstacleNeighborList& obstacleNeighbors_) const;*/
std::vector<Line>& orcaLines_, const float timeHorizonObst_, const float simulationTimeStep, const ObstacleNeighborList& obstacleNeighbors_, const float maxDistance) const;*/
void createAgentOrcaLines(const Agent& agent,
std::vector<Line>& orcaLines_, const float timeHorizon_, const float simulationTimeStep, const AgentNeighborList& agentNeighbors_) const;
std::vector<Line>& orcaLines_, const float timeHorizon_, const float simulationTimeStep, const AgentNeighborList& agentNeighbors_, const float maxDistance) const;
};
/**
......
......@@ -36,6 +36,14 @@ extern "C"
AgentData* agentData;
size_t agentDataSize;
void resizeAgentData(bool deleteOldData = true)
{
if (deleteOldData)
delete[] agentData;
agentDataSize = (size_t)pow(2, 1 + (int)ceil(log2(cs->GetWorld()->GetAgents().size())));
agentData = new AgentData[agentDataSize];
}
API_FUNCTION bool StartSimulation(const char* configFileName, int numberOfThreads)
{
std::setlocale(LC_NUMERIC, "en_US.UTF-8");
......@@ -53,8 +61,7 @@ extern "C"
cs->GetWorld()->SetNumberOfThreads(numberOfThreads);
// prepare the agentData array
agentDataSize = std::max((size_t)4, cs->GetWorld()->GetAgents().size() * 2);
agentData = new AgentData[agentDataSize];
resizeAgentData(false);
return true;
}
......@@ -85,28 +92,20 @@ extern "C"
const auto& agents = cs->GetWorld()->GetAgents();
// check if the AgentData array is large enough; resize it if necessary
if (agents.size() > agentDataSize)
{
delete[] agentData;
agentDataSize *= 2;
agentData = new AgentData[agentDataSize];
}
else if (!agents.empty() && agents.size() * 4 < agentDataSize)
{
delete[] agentData;
agentDataSize /= 4;
agentData = new AgentData[agentDataSize];
}
if (agents.size() > agentDataSize || agents.size() * 4 < agentDataSize)
resizeAgentData(true);
// fill the AgentData array with the current agent data
#pragma omp parallel for
for (int i = 0; i < (int)agents.size(); ++i)
{
agentData[i].id = (int)agents[i]->getID();
agentData[i].x = agents[i]->getPosition().x();
agentData[i].y = agents[i]->getPosition().y();
agentData[i].vx = agents[i]->getVelocity().x();
agentData[i].vy = agents[i]->getVelocity().y();
agentData[i].position_x = agents[i]->getPosition().x;
agentData[i].position_y = agents[i]->getPosition().y;
agentData[i].velocity_x = agents[i]->getVelocity().x;
agentData[i].velocity_y = agents[i]->getVelocity().y;
agentData[i].viewingDirection_x = agents[i]->getViewingDirection().x;
agentData[i].viewingDirection_y = agents[i]->getViewingDirection().y;
}
// store references to these results, for the client program to use
......@@ -125,12 +124,13 @@ extern "C"
auto agent = cs->GetWorld()->GetAgent(agentData[i].id);
if (agent != nullptr)
{
Vector2D position(agentData[i].x, agentData[i].y);
Vector2D velocity(agentData[i].vx, agentData[i].vy);
Vector2D position(agentData[i].position_x, agentData[i].position_y);
Vector2D velocity(agentData[i].velocity_x, agentData[i].velocity_y);
Vector2D viewingDirection(agentData[i].viewingDirection_x, agentData[i].viewingDirection_y);
// override the agent's position, velocity, and viewing direction
agent->setPosition(position);
agent->setVelocity_ExternalApplication(velocity, velocity.getnormalized());
agent->setVelocity_ExternalApplication(velocity, viewingDirection);
}
}
......
......@@ -34,13 +34,17 @@
/// The unique ID of the agent.
int id;
/// The x coordinate of the agent at the current time.
float x;
float position_x;
/// The y coordinate of the agent at the current time.
float y;
float position_y;
/// The x component of the agent's velocity at the current time.
float vx;
float velocity_x;
/// The y component of the agent's velocity at the current time.
float vy;
float velocity_y;
/// The x component of the agent's viewing direction at the current time.
float viewingDirection_x;
/// The y component of the agent's viewing direction at the current time.
float viewingDirection_y;
};
/// <summary>Sets up a simulation based on a configuration file.
......
......@@ -17,6 +17,20 @@ Some parts of the project may still use this old name.
Since 2020, the term "OCSR" refers to the collective of open crowd-simulation resources developed at Inria Rennes.
These resources include UMANS (a simulation engine) and ChAOS (a visualization application).
# Reference
The UMANS software has been described in the following scientific publication:
> Wouter van Toll, Fabien Grzeskowiak, Axel López, Javad Amirian, Florian Berton,
> Julien Bruneau, Beatriz Cabrero Daniel, Alberto Jovane, Julien Pettré.
>
> [*"Generalized Microscopic Crowd Simulation using Costs in Velocity Space"*](https://project.inria.fr/crowdscience/generalized-microscopic-crowd-simulation-using-costs-in-velocity-space-i3d-2020/).
>
> In ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games, 2020.
Please cite this publication when referring to UMANS in your work.
# Getting started
To start using UMANS, perform the following steps:
......@@ -24,17 +38,21 @@ To start using UMANS, perform the following steps:
1. Clone or download the code from [our GitLab repository](https://gitlab.inria.fr/OCSR/UMANS/).
2. Download and install [CMake](https://cmake.org/).
3. Use CMake to turn the codebase into a programming project for your IDE/compiler of choice.
(This step only succeeds if the libraries from step 2 have been properly installed.)
4. Compile the code in your IDE/compiler.
This should build two applications (see the [Main projects](#main-projects) section).
5. Test the UMANS console application by running it from the command line, with the desired arguments.
If you run the program without any arguments, it will print more detailed usage instructions.
6. Inspect the CSV files that the application has produced as output.
Each CSV file contains the trajectory of one agent in the crowd.
You could consider using the [ChAOS](https://gitlab.inria.fr/OCSR/ChAOS/) software to visualize the crowd in 3D.
# Main projects
In the source code of UMANS, the simulation engine itself is contained in a static library named *Engine*.
This library can be used to compile two main applications:
- *OCSR-ConsoleApplication*: A command-line-only program that can run a crowd-simulation scenario and report the results in various ways.
- *OCSR-Library*: A dynamic library with an API that can be integrated into other software (e.g. as a DLL).
- *UMANS-ConsoleApplication*: A command-line-only program that can run a crowd-simulation scenario and report the results in various ways.
- *UMANS-Library*: A dynamic library with an API that can be integrated into other software (e.g. as a DLL).
# Third-party code / licenses
......@@ -48,6 +66,10 @@ UMANS includes the following third-party code:
- Source code: https://github.com/leethomason/tinyxml2
- License: zlib (https://github.com/leethomason/tinyxml2/blob/master/LICENSE.txt)
### ORCA (one of the collision-avoidance algorithms in UMANS)
- Source code: https://github.com/snape/RVO2
- License: Apache (https://github.com/snape/RVO2/blob/master/LICENSE)
# Copyright statement
UMANS: Unified Microscopic Agent Navigation Simulator
......
<?xml version="1.0" encoding="utf-8"?>
<Simulation delta_time="0.1" end_time="100"/>
<Agents>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="-5" y="0"/>
<goal x="50000" y="0"/>
<Policy id="0"/>
</Agent>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="7.788" y="-0.2296"/>
<goal x="-49992.212" y="-0.2296"/>
......@@ -51,9 +56,4 @@
<goal x="-49993.9783" y="-0.3397"/>
<Policy id="0"/>
</Agent>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="-5" y="0"/>
<goal x="50000" y="0"/>
<Policy id="0"/>
</Agent>
</Agents>
<?xml version="1.0" encoding="utf-8"?>
<Agents>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="3.5355339059327378" y="3.5355339059327373"/>
<goal x="-35355.33905932739" y="-35355.33905932737"/>
<Policy id="0"/>
</Agent>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="7.788" y="-0.2296"/>
<goal x="-49992.212" y="-0.2296"/>
......@@ -50,9 +55,4 @@
<goal x="-49993.9783" y="-0.3397"/>
<Policy id="0"/>
</Agent>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="3.5355339059327378" y="3.5355339059327373"/>
<goal x="-35355.33905932739" y="-35355.33905932737"/>
<Policy id="0"/>
</Agent>
</Agents>
<?xml version="1.0" encoding="utf-8"?>
<Simulation delta_time="0.1" end_time="100"/>
<Agents>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="0" y="5"/>
<goal x="0" y="-50000"/>
<Policy id="0"/>
</Agent>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="7.788" y="-0.2296"/>
<goal x="-49992.212" y="-0.2296"/>
......@@ -51,9 +56,4 @@
<goal x="-49993.9783" y="-0.3397"/>
<Policy id="0"/>
</Agent>
<Agent rad="0.3" pref_speed="1.3" max_speed="1.6">
<pos x="0" y="5"/>
<goal x="0" y="-50000"/>
<Policy id="0"/>
</Agent>
</Agents>
......@@ -56,7 +56,7 @@ public:
void parseParameters(const CostFunctionParameters & params) override;
private:
float getDistanceToCollisionAtPreferredSpeed(const Vector2D& direction, const Agent* agent, const NeighborList& neighbors) const;
float getDistanceToCollisionAtPreferredSpeed(const Vector2D& direction, const Agent* agent) const;
};
#endif //LIB_MOUSSAID_H
......@@ -77,7 +77,7 @@ public:
private:
std::pair<float, float> ComputeT1andT2(const Vector2D& origin, const Vector2D& velocity, const Vector2D& point, const float radius) const;
NeighborAvoidanceRange ComputeNeighborAvoidanceRange(const Vector2D& direction, const Agent* agent, const PhantomAgent& neighbor) const;
NeighborAvoidanceRangeList ComputeAllNeighborAvoidanceRanges(const Vector2D& direction, const Agent* agent, const NeighborList& neighbors) const;
NeighborAvoidanceRangeList ComputeAllNeighborAvoidanceRanges(const Vector2D& direction, const Agent* agent) const;
float GetDirectionCost(const Vector2D& direction, const Agent* agent, const NeighborAvoidanceRangeList& avoidanceTimes) const;
float GetSpeedDeviationCost(const float speed, const Vector2D& direction, const Agent* agent, const NeighborAvoidanceRangeList& avoidanceRanges) const;
......
......@@ -53,8 +53,8 @@ private:
// "if/else's" are actually solved at compile time.
inline double kdtree_get_pt(const size_t idx, const size_t dim) const
{
if (dim == 0) return agentPositions[idx].second.x();
else return agentPositions[idx].second.y();
if (dim == 0) return agentPositions[idx].second.x;
else return agentPositions[idx].second.y;
}
// Optional bounding-box computation: return false to default to a standard bbox computation loop.
......
......@@ -162,13 +162,6 @@ public:
/// Methods that compute and return a result based on the agent's internal state.
/// @{
/// <summary>Returns the neighbors that are within a given range of this agent.</summary>
/// <remarks>This method uses the pre-computed list of neighbors and filters it by the given distance.
/// The method does *not* perform a new nearest-neighbor query.</remarks>
/// <param name="range">A distance in meters; this method will return all neighbors within 'range' meters from the agent.</param>
/// <returns>A copy of the agent's NeighborList containing only the neighbors that lie witihn the given distance.</returns>
NeighborList getNeighborSubsetInRange(float range) const;
/// <summary>Checks and returns whether the agent has reached its goal position.</summary>
/// <returns>true if the agent's current position is sufficiently close to its goal; false otherwise.</returns>
bool hasReachedGoal() const;
......
......@@ -220,6 +220,8 @@ protected:
/// <param name="velocity">The hypothetical velocity of the querying agent.</param>
/// <param name="radius">The radius (in meters) of the querying agent.</param>
/// <param name="neighbors">A list of neighboring agents and obstacles.</param>
/// <param name="maximumDistance">The maximum distance between the query position and a neighboring object.
/// Any objects farther away will be ignored.</param>
/// <param name="ignoreCurrentCollisions">Whether or not to ignore any collisions that are already happening now.</param>
/// <returns>The smallest time to collision (in seconds) of the querying agent with all neighbors in the list.
/// This value may be 0 if there is already a collision with any of the neighboring objects.
......@@ -228,7 +230,7 @@ protected:
/// <seealso cref="ComputeTimeToCollision"/>
float ComputeTimeToFirstCollision(
const Vector2D& position, const Vector2D& velocity, const float radius,
const NeighborList& neighbors, bool ignoreCurrentCollisions = true) const;
const NeighborList& neighbors, float maximumDistance, bool ignoreCurrentCollisions) const;
/// <summary>Computes the expected time at which the distance between two disk-shaped objects is minimal, and the value of this distance.</summary>
/// <param name="position1">The current position of object 1.</param>
......
......@@ -26,7 +26,8 @@
#include <memory>
#include <string>
#include <queue>
#include <limits> // std::reference_wrapper
#include <limits>
#include <unordered_map>
#include <tools/Polygon2D.h>
#include <core/agent.h>
......@@ -87,7 +88,7 @@ private:
/// <remarks>Because agents can be removed during the simulation, the ID of an agent is not necessarily the same
/// as its position in the list. This is why we need this extra administration.
/// (We could also put all agents directly in a map, but then we could not loop over the agents in parallel with OpenMP.)</remarks>
std::map<size_t, size_t> agentPositionsInVector;
std::unordered_map<size_t, size_t> agentPositionsInVector;
/// <summary>The agent ID that will be used for the next agent that gets added.</summary>
size_t nextUnusedAgentID;
......
......@@ -51,7 +51,7 @@ public:
/// <param name="v1">A 2D vector containing the desired left column of the matrix.</param>
/// <param name="v2">A 2D vector containing the desired right column of the matrix.</param>
Matrix(const Vector2D& v1, const Vector2D& v2)
: a11_(v1.x()), a12_(v2.x()), a21_(v1.y()), a22_(v2.y()) {}
: a11_(v1.x), a12_(v2.x), a21_(v1.y), a22_(v2.y) {}
/// <summary>Computes and returns the transposed version of this Matrix.</summary>
/// <returns>A Matrix object similar to the current one, but with a21 and a12 swapped.</return>
......@@ -113,12 +113,12 @@ inline Matrix operator*(const Matrix& lhs, const float &rhs)
inline Vector2D operator*(const Matrix& lhs, const Vector2D& rhs)
{
return Vector2D(lhs.a11()*rhs.x() + lhs.a12()*rhs.y(), lhs.a21()*rhs.x() + lhs.a22()*rhs.y());
return Vector2D(lhs.a11()*rhs.x + lhs.a12()*rhs.y, lhs.a21()*rhs.x + lhs.a22()*rhs.y);
}
inline Matrix outerProduct(const Vector2D& lhs, const Vector2D& rhs)
{
return Matrix(lhs.x()*rhs.x(), lhs.x()*rhs.y(), lhs.y()*rhs.x(), lhs.y()*rhs.y());
return Matrix(lhs.x*rhs.x, lhs.x*rhs.y, lhs.y*rhs.x, lhs.y*rhs.y);
}
#endif // LIB_MATRIX_H
......@@ -30,41 +30,35 @@ const double PI = 3.1415926535897;
/// <summary>A 2D vector, used for representing positions, velocities, etc.</summary>
class Vector2D
{
private:
public:
/// <summary>The x component of this vector.</summary>
float x_;
float x;
/// <summary>The y component of this vector.</summary>
float y_;
float y;
public:
/// <summary>Creates a Vector2D with both components set to zero.</summary>
Vector2D() : x_(0.0f), y_(0.0f) {}
Vector2D() : x(0.0f), y(0.0f) {}
/// <summary>Creates a Vector2D with the given x and y components.</summary>
/// <param name="x">The desired x component of the vector.</param>
/// <param name="y">The desired y component of the vector.</param>
Vector2D(float x, float y) : x_(x), y_(y) { }
/// <summary>Retrieves the x component of this Vector2D.</summary>
inline float x() const { return x_; }
/// <summary>Retrieves the y component of this Vector2D.</summary>
inline float y() const { return y_; }
Vector2D(float x, float y) : x(x), y(y) { }
/// <summary>Computes and returns the magnitude of this Vector2D.</summary>
/// <returns>The vector's magnitude, i.e. sqrt(x*x + y*y).</returns>
inline float magnitude() const { return sqrtf(x_ * x_ + y_ * y_); }
inline float magnitude() const { return sqrtf(x * x + y * y); }
/// <summary>Computes and returns the squared magnitude of this Vector2D.</summary>
/// <returns>The vector's squared magnitude, i.e. x*x + y*y.</returns>
inline float sqrMagnitude() const { return x_ * x_ + y_ * y_; }
inline float sqrMagnitude() const { return x * x + y * y; }
/// <summary>Normalizes this Vector2D to unit length, by dividing both x and y by the magnitude.</summary>
inline void normalize()
{
{
float mag = magnitude();
if (mag > 0)
{
x_ /= mag;
y_ /= mag;
x /= mag;
y /= mag;
}
}
......@@ -72,7 +66,7 @@ public:
/// <returns>A Vector2D with the same direction as the current vector, but with unit length.</returns>
inline Vector2D getnormalized() const
{
Vector2D Result(x_, y_);
Vector2D Result(x, y);
Result.normalize();
return Result;
}
......@@ -81,17 +75,8 @@ public:
/// <param name="other">Another vector.</param>
/// <returns>The dot product of the current Vector2D and 'other', i.e. x*other.x + y*other.y.</returns>
inline float dot(const Vector2D& other) const
{
return x_ * other.x_ + y_ * other.y_;
}
/// <summary>Sets the x and y components of this Vector2D to the given values.</summary>
/// <param name="x">The desired new x component of this vector.</param>
/// <param name="y">The desired new y component of this vector.</param>
void set(float x, float y)
{
x_ = x;
y_ = y;
return x * other.x + y * other.y;
}
/// <summary>Adds another Vector2D to the current Vector2D.</summary>
......@@ -99,24 +84,24 @@ public:
/// <returns>A reference to the current Vector2D after the operation has been performed.</returns>
inline Vector2D& operator+=(const Vector2D& rhs)
{
x_ += rhs.x(); y_ += rhs.y();
x += rhs.x; y += rhs.y;
return *this;
}
/// <summary>Subtracts another Vector2D to the current Vector2D.</summary>
/// <param name="rhs">Another vector.</param>
/// <returns>A reference to the current Vector2D after the operation has been performed.</returns>
inline Vector2D& operator-=(const Vector2D& rhs)
{
x_ -= rhs.x(); y_ -= rhs.y();
x -= rhs.x; y -= rhs.y;
return *this;
}
/// <summary>Checks and returns whether this Vector2D is the zero vector.</summary>
/// <returns>true if both x and y are 0; false otherwise.</returns>
inline bool isZero() const
{
return x_ == 0 && y_ == 0;
{
return x == 0 && y == 0;
}
};
......@@ -124,44 +109,54 @@ typedef std::pair<Vector2D, Vector2D> LineSegment2D;
#pragma region [Arithmetic operators]
inline Vector2D operator-(Vector2D lhs, Vector2D rhs)
inline Vector2D operator-(const Vector2D& lhs, const Vector2D& rhs)
{
return Vector2D(lhs.x() - rhs.x(), lhs.y() - rhs.y());
return Vector2D(lhs.x - rhs.x, lhs.y - rhs.y);
}
inline Vector2D operator+(Vector2D lhs, Vector2D rhs)
inline Vector2D operator+(const Vector2D& lhs, const Vector2D& rhs)
{
return Vector2D(lhs.x() + rhs.x(), lhs.y() + rhs.y());
return Vector2D(lhs.x + rhs.x, lhs.y + rhs.y);
}
inline Vector2D operator*(float lhs, Vector2D rhs)
inline Vector2D operator*(float lhs, const Vector2D& rhs)
{
return Vector2D(rhs.x() * lhs, rhs.y() * lhs);
return Vector2D(rhs.x * lhs, rhs.y * lhs);
}
inline Vector2D operator*(Vector2D lhs, float rhs)
inline Vector2D operator*(const Vector2D& lhs, float rhs)
{
return rhs * lhs;
}
inline Vector2D operator/(Vector2D lhs, float rhs)
inline Vector2D operator/(const Vector2D& lhs, float rhs)
{
return (1.0f / rhs) * lhs;
}
inline bool operator==(const Vector2D& p, const Vector2D& q)
{
return p.x() == q.x() && p.y() == q.y();
return p.x == q.x && p.y == q.y;
}
inline bool operator!=(const Vector2D& p, const Vector2D& q)
{
return p.x() != q.x() ||