Commit 916678ca authored by VAN TOLL Wouter's avatar VAN TOLL Wouter

Merge branch 'GUI' into 'master'

Merge of the GUI branch

See merge request !102
parents ab558227 7224853b
......@@ -44,3 +44,7 @@ CMakeLists.txt.user*
# Documentation files generated by Doxygen
/html
# UMANS simulation output
/output
/screenshots
\ No newline at end of file
......@@ -18,21 +18,24 @@
# Website: https://project.inria.fr/crowdscience/
# See the file AUTHORS.md for a list of all contributors.
cmake_minimum_required( VERSION 2.8 )
cmake_minimum_required( VERSION 3.10 )
project( UMANS )
set (CMAKE_CXX_STANDARD 17)
#set(EXECUTABLE_OUTPUT_PATH bin/${CMAKE_BUILD_TYPE})
# === Include 3rd-party code
file( GLOB_RECURSE src_All src/*)
file( GLOB_RECURSE src_Engine src/Engine/* src/3rd-party/* )
file( GLOB_RECURSE src_Library src/Library/* )
file( GLOB_RECURSE src_ConsoleApplication src/ConsoleApplication/* )
file( GLOB_RECURSE src_GUI src/GUI/* )
include_directories( ./include )
include_directories(./3rd-party/tinyxml/ ./3rd-party/nanoflann/ ./3rd-party/ORCA/)
# --- ensure that certain directories are recognized in "#include <...>" lines
include_directories( ./src/ ./src/Engine/ )
#link_directories( ./lib/${CMAKE_BUILD_TYPE} )
# === Include the GUI application (which requires Qt)?
file( GLOB_RECURSE source_files src/* include/*)
file( GLOB_RECURSE 3rd_party 3rd-party/*)
option(UMANS_USE_GUI_APPLICATION "Include a GUI demo application (which relies on Qt)" ON)
# === Use multithreading?
......@@ -53,31 +56,66 @@ endif()
# === Engine project: a static library that performs the simulation
add_library(Engine STATIC ${source_files} ${3rd_party})
add_library(Engine STATIC ${src_Engine})
# === Console application
if (WIN32)
add_executable(UMANS_ConsoleApplication_Windows main.cpp)
target_link_libraries(UMANS_ConsoleApplication_Windows Engine)
add_executable(UMANS-ConsoleApplication-Windows ${src_ConsoleApplication})
target_link_libraries(UMANS-ConsoleApplication-Windows Engine)
else()
add_executable(UMANS_ConsoleApplication_Linux main.cpp)
target_link_libraries(UMANS_ConsoleApplication_Linux Engine)
add_executable(UMANS-ConsoleApplication-Linux ${src_ConsoleApplication})
target_link_libraries(UMANS-ConsoleApplication-Linux Engine)
endif()
# === Dynamic library (e.g. a DLL on Windows) to include into other programs
add_library(UMANS_Library SHARED "APIFunctions.h" "APIFunctions.cpp")
target_link_libraries(UMANS_Library Engine)
add_library(UMANS-Library SHARED ${src_Library})
target_link_libraries(UMANS-Library Engine)
# === Qt demo project (if enabled)
if (UMANS_USE_GUI_APPLICATION)
# necessary: gives the path to GUI src files to qt autogen files
include_directories( ./src/GUI/ )
# set autogen variables for qt
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# find relevant packages
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(OpenGL REQUIRED)
# --- include the Qt resources, so that the application icon gets shown
set(UI_RESOURCES src/GUI/UMANSQtGuiApplication.qrc)
qt5_add_resources(UI_RESOURCES_RCC ${UI_RESOURCES})
# --- define the project itself
add_executable(UMANS-GUI src/GUI/main.cpp ${src_GUI} ${UI_RESOURCES_RCC} ${UI_HEADERS})
# --- the project depends on some libraries
target_link_libraries(UMANS-GUI Engine Qt5::Core Qt5::Widgets ${OPENGL_LIBRARIES})
# --- post-build command: copy the required Qt DLLs, to prevent runtime errors
add_custom_command(TARGET UMANS-GUI POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Core> $<TARGET_FILE_DIR:UMANS-GUI>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Gui> $<TARGET_FILE_DIR:UMANS-GUI>
COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:Qt5::Widgets> $<TARGET_FILE_DIR:UMANS-GUI>
)
endif (UMANS_USE_GUI_APPLICATION)
# === Clean up the project's directory structure
foreach(_source IN ITEMS ${source_files})
foreach(_source IN ITEMS ${src_All})
get_filename_component(_source_path "${_source}" PATH)
file(RELATIVE_PATH _source_path_rel "${CMAKE_CURRENT_SOURCE_DIR}" "${_source_path}")
string(REPLACE "include/" "" _source_path_rel ${_source_path_rel})
string(REPLACE "src/" "" _source_path_rel ${_source_path_rel})
file(RELATIVE_PATH _source_path_rel "${CMAKE_CURRENT_SOURCE_DIR}/src/" "${_source_path}")
string(REPLACE "/" "\\" _group_path "${_source_path_rel}")
source_group("${_group_path}" FILES "${_source}")
endforeach()
source_group("3rd-party" FILES ${3rd_party})
......@@ -36,27 +36,35 @@ Please cite this publication when referring to UMANS in your work.
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/).
2. Download and install [Qt](https://www.qt.io/) (optional) and [CMake](https://cmake.org/) (required).
(Qt is only needed if you want to compile the *UMANS-GUI* application.)
3. Use CMake to turn the codebase into a programming project for your IDE/compiler of choice.
(You can also disable the UMANS-GUI project here. If you disable it, you do not need a Qt installation.)
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.
This should build several applications (see the [Main projects](#main-projects) section).
5. If you have compiled the UMANS-GUI program, run it and see an example simulation in action!
6. 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.
7. 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:
This library is used by three main applications:
- *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).
- *UMANS-GUI*: A demo program with a (Qt) GUI, in which users can change the simulation with basic mouse and keyboard interaction.
# Third-party code / licenses
UMANS includes the following third-party code:
UMANS relies on the following third-party code:
### Qt
- Website: https://www.qt.io/
- License: Open Source (https://doc.qt.io/Qt-5/opensourcelicense.html)
### nanoflann (kd-trees)
- Source code: https://github.com/jlblancoc/nanoflann
......@@ -69,6 +77,10 @@ UMANS includes the following third-party code:
### 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)
### earcut (Polygon triangulation)
- Source code: https://github.com/mapbox/earcut.hpp
- License: ISC (https://github.com/mapbox/earcut.hpp/blob/master/LICENSE)
# Copyright statement
......
ISC License
Copyright (c) 2015, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
\ No newline at end of file
This diff is collapsed.
......@@ -104,7 +104,7 @@ int main( int argc, char * argv[] )
if (cs != nullptr)
{
if (outputFolder != "")
cs->SetCSVOutputDirectory(outputFolder);
cs->StartCSVOutput(outputFolder, false); // false = don't save any files until the simulation ends
cs->RunSimulationUntilEnd();
}
......
......@@ -23,6 +23,7 @@
#include <core/agent.h>
#include <core/worldBase.h>
#include <tools/Matrix.h>
#include <tools/HelperFunctions.h>
using namespace std;
......@@ -42,7 +43,7 @@ float Importance(float ttc)
float t0 = 5, I0 = 10, a = -I0 / t0;
float I = a * ttc + I0;
return v2D_clamp(I, 0.1f, 10.f);
return HelperFunctions::Clamp<float>(I, 0.1f, 10.f);
//return 1;
}
......@@ -133,9 +134,9 @@ Vector2D FOEAvoidance::GetGradient(const Vector2D& velocity, Agent* agent, const
// TODO: check neighboring obstacles
// ...
gradtheta = v2D_clamp(gradtheta, -0.1f, 0.1f);
gradtheta = HelperFunctions::Clamp(gradtheta, -0.1f, 0.1f);
//gradtheta = -gradtheta;
//gradv = v2D_clamp(gradv, -1.f, 1.f);
//gradv = HelperFunctions::Clamp(gradv, -1.f, 1.f);
//gradv = 0;
return -1 * R*(velocity.magnitude()*Vector2D(sin(gradtheta), 1 - cos(gradtheta)) + Vector2D(0, gradv));
......
......@@ -32,7 +32,7 @@ const ORCALibrary::Solution& ORCA::GetOrcaSolutionForAgent(Agent* agent, const W
// run ORCA and store the solution in the agent
ORCALibrary::Solver solver;
agent->SetOrcaSolution(
solver.solveOrcaProgram(*agent, timeHorizon, world->GetCurrentTime(), world->GetDeltaTime(), agent->getNeighbors(), range_)
solver.solveOrcaProgram(*agent, timeHorizon, (float)world->GetCurrentTime(), world->GetDeltaTime(), agent->getNeighbors(), range_)
);
}
......
......@@ -236,7 +236,7 @@ Vector2D TtcaDca::GetGradient(const Vector2D& velocity, Agent* agent, const Worl
GradS += GradThS_movement.second;
// clamp the change in angle
//GradTh = v2D_clamp(GradTh, -1.f, 1.f);
//GradTh = clampVector(GradTh, -1.f, 1.f);
Vector2D Gradient;
......
......@@ -24,7 +24,7 @@
#include <vector>
#include <core/agent.h>
#include <nanoflann.hpp>
#include <3rd-party/nanoflann/nanoflann.hpp>
/// <summary>A 2-dimensional KD-tree of agent positions (using the *nanoflann* library), which can be used for nearest-neighbor queries.</summary>
/// <remarks>Note: The KD-tree does not automatically change over time as the agents move.
......
......@@ -23,7 +23,6 @@
#include <core/worldBase.h>
#include <tools/Matrix.h>
#include "tinyxml2.h"
CostFunction::CostFunction() {}
CostFunction::~CostFunction() {}
......
......@@ -23,7 +23,8 @@
#define LIB_COST_FUNCTION_PARAMETERS_H
#include <string>
#include "tinyxml2.h"
#include <3rd-party/tinyxml/tinyxml2.h>
class Vector2D;
/// <summary>A helper class that represents an XML element with cost-function parameters.
......
......@@ -35,11 +35,11 @@ CrowdSimulator::CrowdSimulator(bool isConsoleApplication)
end_time_ = MaxFloat;
}
void CrowdSimulator::SetCSVOutputDirectory(const std::string &dirname)
void CrowdSimulator::StartCSVOutput(const std::string &dirname, bool flushImmediately)
{
// create the CSV writer if it did not exist yet
if (writer_ == nullptr)
writer_ = new CSVWriter();
writer_ = new CSVWriter(flushImmediately);
// try to set the directory
if (!writer_->SetOutputDirectory(dirname))
......@@ -51,6 +51,15 @@ void CrowdSimulator::SetCSVOutputDirectory(const std::string &dirname)
}
}
void CrowdSimulator::StopCSVOutput()
{
if (writer_ != nullptr)
{
delete writer_;
writer_ = nullptr;
}
}
CrowdSimulator::~CrowdSimulator()
{
// delete the CSV writer?
......@@ -69,11 +78,11 @@ void CrowdSimulator::RunSimulationSteps(int nrSteps)
{
world_->DoStep();
float t = world_->GetCurrentTime();
const auto& agents = world_->GetAgents();
if (writer_ != nullptr)
{
double t = world_->GetCurrentTime();
const auto& agents = world_->GetAgents();
std::map<int, Vector2D> poss;
for (const Agent* agent : agents)
poss[(int)agent->getID()] = agent->getPosition();
......@@ -123,25 +132,25 @@ std::string getFolder(const std::string& filename)
bool CrowdSimulator::FromConfigFile_loadWorld(const tinyxml2::XMLElement* worldElement)
{
// load the world type
WorldBase::Type worldType = WorldBase::Type::UNKNOWN;
WorldBase::Type worldType = WorldBase::Type::UNKNOWN_WORLD_TYPE;
const char* type = worldElement->Attribute("type");
if (type != nullptr)
worldType = WorldBase::StringToWorldType(type);
if (worldType == WorldBase::Type::UNKNOWN)
if (worldType == WorldBase::Type::UNKNOWN_WORLD_TYPE)
{
std::cerr << "Warning: No valid world type specified in the XML file. Selecting default type (Infinite)." << std::endl;
worldType = WorldBase::Type::INFINITE;
worldType = WorldBase::Type::INFINITE_WORLD;
}
if (worldType == WorldBase::Type::INFINITE)
if (worldType == WorldBase::Type::INFINITE_WORLD)
{
world_ = std::make_unique<WorldInfinite>();
std::cout << "Created Infinite world." << std::endl;
}
// for toric worlds, load the width and height
else if (worldType == WorldBase::Type::TORIC)
else if (worldType == WorldBase::Type::TORIC_WORLD)
{
float width = -1, height = -1;
worldElement->QueryFloatAttribute("width", &width);
......@@ -503,6 +512,7 @@ CrowdSimulator* CrowdSimulator::FromConfigFile(const std::string& filename, bool
}
CrowdSimulator* crowdsimulator = new CrowdSimulator(isConsoleApplication);
crowdsimulator->scenarioFilename_ = filename;
//
// --- Read the world parameters
......
......@@ -51,6 +51,8 @@ private:
/// Each agent uses one of these policies for its navigation.</summary>
std::map<int, Policy*> policies_;
std::string scenarioFilename_;
public:
/// <summary>Creates a new CrowdSimulator object by loading a given configuration file.</summary>
......@@ -64,9 +66,16 @@ public:
/// <summary>Destroys this CrowdSimulator object.</summary>
~CrowdSimulator();
const std::string& GetScenarioFilename() const { return scenarioFilename_; }
/// <summary>Prepares this CrowdSimulator for writing simulation output (as CSV files) to the given directory.</summary>
/// <param name="dirname">The name of the directory to use for output.</param>
void SetCSVOutputDirectory(const std::string& dirname);
/// <param name="flushImmediately">Whether or not the CSV writer should write its output files as fast as possible.
/// If it is true, the output files will be updated after each simulation frame.
/// If it is false, the data to write will be cached, and files will be written when the CrowdSimulator gets destroyed.</param>
void StartCSVOutput(const std::string& dirname, bool flushImmediately);
void StopCSVOutput();
/// <summary>Runs the given number of simulation steps.</summary>
/// <param name="nrSteps">The number of simulation steps to run; should be at least 1, otherwise nothing happens.</param>
......
......@@ -61,10 +61,10 @@ Vector2D Policy::ComputeNewVelocity(Agent* agent, WorldBase * world)
}
// clamp to a maximum acceleration
acceleration = v2D_clamp(acceleration, agent->getMaximumAcceleration());
acceleration = clampVector(acceleration, agent->getMaximumAcceleration());
// use this to compute a new velocity, clamped to a maximum speed
return v2D_clamp(currentVelocity + acceleration * dt, agent->getMaximumSpeed());
return clampVector(currentVelocity + acceleration * dt, agent->getMaximumSpeed());
}
Vector2D Policy::getAccelerationFromGradient(Agent* agent, WorldBase * world)
......
......@@ -22,7 +22,7 @@
#ifndef LIB_POLICY_H
#define LIB_POLICY_H
#include "tinyxml2.h"
#include <3rd-party/tinyxml/tinyxml2.h>
#include <core/costFunction.h>
#include <tools/vector2D.h>
#include <vector>
......
......@@ -28,11 +28,11 @@ using namespace std;
WorldBase::Type WorldBase::StringToWorldType(const std::string& type)
{
if (type == "Infinite")
return Type::INFINITE;
return Type::INFINITE_WORLD;
else if (type == "Toric")
return Type::TORIC;
return Type::TORIC_WORLD;
else
return Type::UNKNOWN;
return Type::UNKNOWN_WORLD_TYPE;
}
WorldBase::WorldBase(WorldBase::Type type) : type_(type)
......@@ -198,7 +198,7 @@ void WorldBase::addAgentToList(Agent* agent)
agents_.push_back(agent);
}
bool WorldBase::RemoveAgent(int id)
bool WorldBase::RemoveAgent(size_t id)
{
// find out if the agent with this ID exists
auto positionInList = agentPositionsInVector.find(id);
......
......@@ -62,7 +62,7 @@ class WorldBase
public:
/// <summary>An enum containing the types of world in which a simulation takes place.</summary>
enum Type { UNKNOWN, INFINITE, TORIC };
enum Type { UNKNOWN_WORLD_TYPE, INFINITE_WORLD, TORIC_WORLD };
static Type StringToWorldType(const std::string& type);
private:
......@@ -81,9 +81,6 @@ private:
/// <summary>A list of agents (sorted by time) that will be added to the simulation in the future.</summary>
AgentQueue agentsToAdd;
/// <summary>A kd-tree of agent positions, used for nearest-neighbor queries.</summary>
AgentKDTree* agentKDTree;
/// <summary>A mapping from agent IDs to positions in the agents_ list.</summary>
/// <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.
......@@ -103,11 +100,14 @@ protected:
/// <summary>A list containing all agents that are currently in the crowd.</summary>
std::vector<Agent*> agents_;
/// <summary>A kd-tree of agent positions, used for nearest-neighbor queries.</summary>
AgentKDTree* agentKDTree;
/// <summary>The length (in seconds) of a simulation step.</summary>
float delta_time_;
/// <summary>The simulation time (in seconds) that has passed so far.</summary>
float time_;
double time_;
public:
......@@ -120,9 +120,13 @@ public:
/// <returns>A non-mutable reference to the list of Agent objects.</returns>
inline const std::vector<Agent*>& GetAgents() const { return agents_; }
/// <summary>Returns the list of obstacles.</summary>
/// <returns>A non-mutable reference to the list of Polygon2D objects, each representing one obstacle in the world.</returns>
inline const std::vector<Polygon2D>& GetObstacles() const { return obstacles_; }
/// <summary>Returns the current simulation time (in seconds).</summary>
/// <returns>The time (in seconds) that has been simulated since the simulation started.</returns>
inline float GetCurrentTime() const { return time_; }
inline double GetCurrentTime() const { return time_; }
/// <summary>Returns the duration of a single simulation time step (in seconds), i.e. the time that is simulated in a single execution of DoStep().</summary>
/// <returns>The durection of a single simulation time step (in seconds).</summary>
......@@ -192,7 +196,7 @@ public:
/// <summary>Tries to remove the agent with the given ID from the simulation.</summary>
/// <param name="id">The ID of the agent to remove.</param>
/// <returns>true if the agent was successfully removed; false otherwise, i.e. if the agent with the given ID does not exist.</returns>
bool RemoveAgent(int id);
bool RemoveAgent(size_t id);
/// @}
#pragma endregion
......
......@@ -24,16 +24,20 @@
using namespace nanoflann;
using namespace std;
WorldInfinite::WorldInfinite() : WorldBase(INFINITE)
WorldInfinite::WorldInfinite() : WorldBase(INFINITE_WORLD)
{
}
NeighborList WorldInfinite::ComputeNeighbors(const Vector2D& position, float search_radius, const Agent* queryingAgent) const
{
const auto& agents = computeNeighboringAgents_Flat(position, search_radius, queryingAgent);
vector<PhantomAgent> phantoms;
for (const Agent* agent : agents)
phantoms.push_back(PhantomAgent(agent->getPosition(), agent->getVelocity(), agent));
vector<PhantomAgent> phantoms;
if (agentKDTree != nullptr)