Program Listing for File MechanicalLayer.cpp
↰ Return to documentation for file (src/MechanicalLayer.cpp)
/*
Copyright 2025 Institute of Light and Matter, CNRS UMR 5306, University Claude Bernard Lyon 1
Contributors: Oscar DUFOUR, Maxime STAPELLE, Alexandre NICOLAS
This software is a computer program designed to generate a realistic crowd from anthropometric data and
simulate the mechanical interactions that occur within it and with obstacles.
This software is governed by the CeCILL-B license under French law and abiding by the rules of distribution
of free software. You can use, modify and/ or redistribute the software under the terms of the CeCILL-B
license as circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info".
As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by
the license, users are provided only with a limited warranty and the software's author, the holder of the
economic rights, and the successive licensors have only limited liability.
In this respect, the user's attention is drawn to the risks associated with loading, using, modifying
and/or developing or reproducing the software by the user in light of its specific status of free software,
that may mean that it is complicated to manipulate, and that also therefore means that it is reserved
for developers and experienced professionals having in-depth computer knowledge. Users are therefore
encouraged to load and test the software's suitability as regards their requirements in conditions enabling
the security of their systems and/or data to be ensured and, more generally, to use and operate it in the
same conditions as regards security.
The fact that you are presently reading this means that you have had knowledge of the CeCILL-B license and that
you accept its terms.
*/
#include "MechanicalLayer.h"
#include <sys/stat.h>
#include <array>
#include <fstream>
#include <map>
#include <set>
#include <string>
#include <tuple>
#include <unordered_set>
#include <vector>
#include "../3rdparty/tinyxml/tinyxml2.h"
using std::list, std::map, std::set, std::vector, std::string, std::tuple, std::pair, std::cout, std::cerr, std::endl, std::ofstream,
std::fmin, std::runtime_error;
MechanicalLayer::MechanicalLayer(list<Agent*>& mech_active_agents)
: nb_active_agents(mech_active_agents.size()),
nb_active_shapes(0),
vgn(nb_active_agents),
vgnp1(nb_active_agents),
rgn(nb_active_agents),
rgnp1(nb_active_agents),
delta(0),
thetn(nb_active_agents),
thetnp1(nb_active_agents),
wn(nb_active_agents),
wnp1(nb_active_agents),
wdesired(nb_active_agents),
Fp(nb_active_agents),
Forthon(nb_active_agents),
Ftn(nb_active_agents),
taun(nb_active_agents),
neighbours(nb_active_agents),
active_shapeIDagent(0),
active_shapeIDshape_crowd(0),
radius(0),
size_agents(nb_active_agents),
neighbours_shape(nb_active_agents),
agentIDshape(nb_active_agents + 1, 0),
masses(nb_active_agents),
mois(nb_active_agents),
damping(nb_active_agents)
{
/* Preliminary definitions and initialisation */
// Sort mechanically active agents to have agent/shapes in ascending order
mech_active_agents.sort([](auto const& a, auto const& b) { return (a->_id) < (b->_id); });
unsigned cpt_agent = 0;
for (Agent* agent : mech_active_agents)
{
for (unsigned cpt_shape(0); cpt_shape < agent->_nb_shapes; cpt_shape++)
{
active_shapeIDagent.push_back(cpt_agent);
active_shapeIDshape_crowd.push_back(agent->_ids_shapes[cpt_shape]);
}
agentIds[agent->_id] = cpt_agent;
agentActiveIds.push_back(agent->_id);
vector<double2> delta_gtos_agent = agent->get_delta_gtos();
delta.insert(delta.end(), (delta_gtos_agent).begin(), (delta_gtos_agent).end());
radius.insert(radius.end(), (agent->_radius_shapes).begin(), (agent->_radius_shapes).end());
size_agents[cpt_agent] = agent->_nb_shapes;
nb_active_shapes += agent->_nb_shapes;
rgn[cpt_agent] = double2(agent->_x, agent->_y);
thetn[cpt_agent] = agent->_theta;
vgn[cpt_agent] = double2(agent->_vx, agent->_vy);
wn[cpt_agent] = agent->_w;
masses[cpt_agent] = agent->_mass;
mois[cpt_agent] = agent->_moi;
damping[cpt_agent] = agentProperties[agent->_id];
rgnp1[cpt_agent] = double2(agent->_x, agent->_y);
thetnp1[cpt_agent] = agent->_theta;
vgnp1[cpt_agent] = double2(agent->_vx, agent->_vy);
wnp1[cpt_agent] = agent->_w;
// The "F" here have the dimension of an acceleration
Fp[cpt_agent] = agent->_Fp;
Forthon[cpt_agent] = double2(0., 0.);
Ftn[cpt_agent] = double2(0., 0.);
taun[cpt_agent] = 0.;
wdesired[cpt_agent] = agent->_w_des;
cpt_agent++;
}
// Get the correspondence between agent and shapes (ie the edges)
unsigned length(0);
for (size_t a = 0; a < nb_active_agents; ++a)
{
length += size_agents[a];
agentIDshape[a + 1] = length;
}
// Get neighbouring shapes id of each agent
cpt_agent = 0;
for (const Agent* agent : mech_active_agents)
{
unsigned cpt_agent2 = 0;
for (const Agent* agent2 : mech_active_agents)
{
if (cpt_agent2 > cpt_agent)
{
// Include as neighbour if within 5*(r1+r2) where r1 and r2 are the size of the body (the radius of the smallest
// circle containing all the shapes)
if (const double distance = !(rgn[cpt_agent] - rgn[cpt_agent2]); distance < 5.0 * (agent->_radius + agent2->_radius))
{
for (unsigned cpt_shape(agentIDshape[cpt_agent2]); cpt_shape < agentIDshape[cpt_agent2] + size_agents[cpt_agent2];
++cpt_shape)
neighbours_shape[cpt_agent].push_back(cpt_shape);
for (unsigned cpt_shape(agentIDshape[cpt_agent]); cpt_shape < agentIDshape[cpt_agent] + size_agents[cpt_agent];
++cpt_shape)
neighbours_shape[cpt_agent2].push_back(cpt_shape);
}
}
cpt_agent2++;
}
cpt_agent++;
}
/* Check if an Interactions File already exists */
const string interactionsFile = pathDynamic + "AgentInteractions.xml";
struct stat buffer{};
if (stat(interactionsFile.c_str(), &buffer) != -1)
if (readInteractionsInputFile(interactionsFile) == EXIT_FAILURE)
throw runtime_error("Issue while reading interactions file");
/* MECHANICAL Loop */
for (unsigned t = 0; t < static_cast<unsigned>(dt / dt_mech); t++)
{
loop();
}
/* Update the positions and velocities of mechanically active agents */
cpt_agent = 0;
for (Agent* agent : mech_active_agents)
{
agent->_x = rgn[cpt_agent].first;
agent->_y = rgn[cpt_agent].second;
agent->_vx = vgn[cpt_agent].first;
agent->_vy = vgn[cpt_agent].second;
agent->_theta = thetn[cpt_agent];
agent->_w = wn[cpt_agent];
cpt_agent++;
}
/* Output the interactions file */
generateInteractionsOutputFile(interactionsFile, existsContacts());
}
MechanicalLayer::~MechanicalLayer() // destructor transfers computed data to the agents
= default;
int MechanicalLayer::readInteractionsInputFile(const std::string& interactionsFile)
{
tinyxml2::XMLDocument document;
document.LoadFile(interactionsFile.data());
if (document.ErrorID() != 0)
{
cerr << "Error: Could not load or parse XML file " << interactionsFile << endl;
return EXIT_FAILURE;
}
// Read the Interactions block
const tinyxml2::XMLElement* interactionsElement = document.FirstChildElement("Interactions");
if (!interactionsElement)
{
cerr << "Error: interactions must be embedded in \"Interactions\" tag!" << endl;
return EXIT_FAILURE;
}
const tinyxml2::XMLElement* agent1Element = interactionsElement->FirstChildElement("Agent");
if (!agent1Element)
{
cerr << "Error: no Agent tag present in " << interactionsFile << endl;
return EXIT_FAILURE;
}
while (agent1Element)
{
const char* agent1ExternId = nullptr;
if (agent1Element->QueryStringAttribute("Id", &agent1ExternId) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: Agents must have an Id in file " << interactionsFile << endl;
return EXIT_FAILURE;
}
// Interactions with other agents
const tinyxml2::XMLElement* agent2Element = agent1Element->FirstChildElement("Agent");
while (agent2Element)
{
const char* agent2ExternId = nullptr;
if (agent2Element->QueryStringAttribute("Id", &agent2ExternId) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: Agents must have an Id in file " << interactionsFile << endl;
return EXIT_FAILURE;
}
// Read interactions
const tinyxml2::XMLElement* interactionElement = agent2Element->FirstChildElement("Interaction");
while (interactionElement)
{
int32_t shapeParent;
int32_t shapeChild;
if (interactionElement->QueryIntAttribute("ParentShape", &shapeParent) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no shape identifier in interaction between agents in " << interactionsFile << endl;
return EXIT_FAILURE;
}
if (interactionElement->QueryIntAttribute("ChildShape", &shapeChild) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no shape identifier in interaction between agents in " << interactionsFile << endl;
return EXIT_FAILURE;
}
const char* buffer = nullptr;
if (interactionElement->QueryStringAttribute("TangentialRelativeDisplacement", &buffer) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no tangential relative displacement in one of the interactions of " << interactionsFile << endl;
return EXIT_FAILURE;
}
auto [rcSlip, inputSlip] = parse2DComponents(buffer);
if (rcSlip != EXIT_SUCCESS)
{
cerr << "Error: Could not parse components of tangential relative displacement in " << interactionsFile << endl;
return EXIT_FAILURE;
}
uint32_t cpt_shape = agentIDshape[agentIds[agentMap[agent1ExternId]]] + shapeParent;
uint32_t cpt_shape_neigh = agentIDshape[agentIds[agentMap[agent2ExternId]]] + shapeChild;
slip[{cpt_shape, cpt_shape_neigh}] = inputSlip;
slip[{cpt_shape_neigh, cpt_shape}] = -1 * inputSlip;
interactionElement = interactionElement->NextSiblingElement("Interaction");
}
agent2Element = agent2Element->NextSiblingElement("Agent");
}
// Interactions with walls
const tinyxml2::XMLElement* wallElement = agent1Element->FirstChildElement("Wall");
while (wallElement)
{
int32_t shape;
if (wallElement->QueryIntAttribute("ShapeId", &shape) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no shape identifier in interaction between agent and wall in " << interactionsFile << endl;
return EXIT_FAILURE;
}
int iobs, iwall;
if (wallElement->QueryIntAttribute("WallId", &iobs) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no obstacle identifier in interaction between agent and wall in " << interactionsFile << endl;
return EXIT_FAILURE;
}
if (wallElement->QueryIntAttribute("CornerId", &iwall) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no wall identifier in interaction between agent and wall in " << interactionsFile << endl;
return EXIT_FAILURE;
}
const char* buffer = nullptr;
if (wallElement->QueryStringAttribute("TangentialRelativeDisplacement", &buffer) != tinyxml2::XML_SUCCESS)
{
cerr << "Error: no tangential relative displacement in interaction between agent and wall in " << interactionsFile
<< endl;
return EXIT_FAILURE;
}
auto [rcSlipWall, inputSlipWall] = parse2DComponents(buffer);
if (rcSlipWall != EXIT_SUCCESS)
{
cerr << "Error: Could not parse components of tangential relative displacement between agent and wall in "
<< interactionsFile << endl;
return EXIT_FAILURE;
}
uint32_t cpt_shape = agentIDshape[agentIds[agentMap[agent1ExternId]]] + shape;
slip_wall[{cpt_shape, iobs, iwall}] = inputSlipWall;
wallElement = wallElement->NextSiblingElement("Wall");
}
agent1Element = agent1Element->NextSiblingElement("Agent");
}
return EXIT_SUCCESS;
}
tuple<double2, double2, double> MechanicalLayer::get_interactions(unsigned cpt_shape, bool AtTimen)
{
unsigned cpt_agent = active_shapeIDagent[cpt_shape];
double UnmZetadt = 1.0 - dt_mech * damping[cpt_agent].first;
double2 delta_GtoS = AtTimen ? delta[cpt_shape] : delta[cpt_shape] + ((thetnp1[cpt_agent] - thetn[cpt_agent]) ^ delta[cpt_shape]);
double2 posagent = AtTimen ? rgn[cpt_agent] : rgnp1[cpt_agent]; // Center of mass of the agent
double2 posshape = posagent + delta_GtoS; // Center of mass of the shape
double angvel = // Angular velocity of the shape at time n and trial angular velocity for the time n+1
AtTimen ? wn[cpt_agent] : wn[cpt_agent] + dt_mech * taun[cpt_agent];
double2 velagent = // Velocity of the shape (v_shape(t+dt) = v_CM(t+dt))
AtTimen ? vgn[cpt_agent] : UnmZetadt * vgn[cpt_agent] + dt_mech * (Fp[cpt_agent] + Forthon[cpt_agent] + Ftn[cpt_agent]);
double2 velshape = velagent + (angvel ^ delta_GtoS);
double torq = 0.;
double2 fortho(0., 0.);
double2 ft(0., 0.);
/* Interactions between agents */
for (unsigned cpt_shape_neigh : neighbours_shape[cpt_agent])
{
unsigned cpt_neigh = active_shapeIDagent[cpt_shape_neigh];
double2 delta_GtoS_neigh = AtTimen
? delta[cpt_shape_neigh]
: delta[cpt_shape_neigh] + ((thetnp1[cpt_neigh] - thetn[cpt_neigh]) ^ delta[cpt_shape_neigh]);
double2 posagent_neigh = AtTimen ? rgn[cpt_neigh] : rgnp1[cpt_neigh];
double2 posshape_neigh = posagent_neigh + delta_GtoS_neigh;
double2 r_ij = posshape - posshape_neigh;
double distance(!r_ij);
double h(radius[cpt_shape] + radius[cpt_shape_neigh] - distance); // Indentation
// If the two shapes are in contact:
if (h > 0.)
{
double angvel_neigh = AtTimen ? wn[cpt_neigh] : wn[cpt_neigh] + dt_mech * taun[cpt_neigh];
double2 velagent_neigh = // Velocity of the CM of the neighbouring pedestrian neighbour
AtTimen ? vgn[cpt_neigh]
: UnmZetadt * vgn[cpt_neigh] + dt_mech * (Fp[cpt_neigh] + Forthon[cpt_neigh] + Ftn[cpt_neigh]);
double2 velshape_neigh = velagent_neigh + (angvel_neigh ^ delta_GtoS_neigh);
double2 n_ij;
if (distance == 0.)
n_ij = double2(0., 0.);
else
n_ij = (1. / distance) * r_ij;
double2 dcGshape = -(radius[cpt_shape] - h / 2.) * n_ij; // From the center of mass G of the shape
// towards c (the contact point)
double2 dcGshapeneigh = +(radius[cpt_shape_neigh] - h / 2.) * n_ij;
double2 dcG = delta[cpt_shape] + dcGshape; // Vector distance from CM of the agent to
// c = vector distance from CM agent to CM shape +
// distance from CM shape to c
double2 v_ci = velshape + (angvel ^ dcGshape); // Velocity of i at the contact point
double2 v_cj = velshape_neigh + (angvel_neigh ^ dcGshapeneigh); // Velocity of j at the contact point
double2 vij = v_ci - v_cj;
double2 vortho_ij = (vij % n_ij) * n_ij;
double2 vt_ij = vij - vortho_ij;
// If the map does not contain this pair ie the slip is not initialized, we initialize it
// Otherwise: we increment it
if (!slip.contains({cpt_shape, cpt_shape_neigh}))
slip[{cpt_shape, cpt_shape_neigh}] = double2(0., 0.);
else
{
double2 slip_prime = slip[{cpt_shape, cpt_shape_neigh}];
// Rotation of the slip to take into account the rotation of the contact reference frame
// from t to t+dt_mech (D.R. Vyas, J.M. Ottino, R.M. Lueptow et al. 2025)
double2 slip_projected = slip_prime - (slip_prime % n_ij) * n_ij;
double2 slip_new = slip_prime;
if ((!slip_projected) > 0.)
slip_new = (!slip_prime / !slip_projected) * slip_projected;
slip[{cpt_shape, cpt_shape_neigh}] = slip_new + dt_mech * vt_ij;
}
// For the output Interactions file:
// We will only put the N(N-1)/2 pairs, ie cpt_shape_neigh>cpt_shape
if (!interactionsOutput.contains({cpt_shape_neigh, cpt_shape}))
interactionsOutput[{cpt_shape, cpt_shape_neigh}][SLIP] = slip[{cpt_shape, cpt_shape_neigh}];
uint32_t shapeMaterialId = shapesMaterial[active_shapeIDshape_crowd[cpt_shape]];
uint32_t shapeNeighbourMaterialId = shapesMaterial[active_shapeIDshape_crowd[cpt_shape_neigh]];
/* Normal interactions */
double k_n = binaryProperties[STIFFNESS_NORMAL][shapeMaterialId][shapeNeighbourMaterialId];
double Gamma_n = binaryProperties[DAMPING_NORMAL][shapeMaterialId][shapeNeighbourMaterialId];
double2 fnij_elastic = k_n * h * n_ij;
double2 fnij_viscous = -Gamma_n * vortho_ij;
double2 fnij = fnij_elastic + fnij_viscous;
fortho = fortho + fnij;
if (!interactionsOutput.contains({cpt_shape_neigh, cpt_shape}))
interactionsOutput[{cpt_shape, cpt_shape_neigh}][FORCE_ORTHO] = fnij;
/* Tangential interactions */
double k_t = binaryProperties[STIFFNESS_TANGENTIAL][shapeMaterialId][shapeNeighbourMaterialId];
double Gamma_t = binaryProperties[DAMPING_TANGENTIAL][shapeMaterialId][shapeNeighbourMaterialId];
double2 ftij_static = -k_t * slip[{cpt_shape, cpt_shape_neigh}] - Gamma_t * vt_ij;
double mu_dyn = binaryProperties[FRICTION_SLIDING][shapeMaterialId][shapeNeighbourMaterialId];
double2 t_vij = double2(0., 0.);
double2 ftij = double2(0., 0.);
if ((!ftij_static) > mu_dyn * !fnij)
{
t_vij = (1. / !ftij_static) * ftij_static;
ftij = mu_dyn * !fnij * t_vij;
slip[{cpt_shape, cpt_shape_neigh}] = -(1. / k_t) * (mu_dyn * !fnij * t_vij + Gamma_t * vt_ij);
}
else
ftij = ftij_static;
ft = ft + ftij;
if (!interactionsOutput.contains({cpt_shape_neigh, cpt_shape}))
interactionsOutput[{cpt_shape, cpt_shape_neigh}][FORCE_TAN] = ftij;
/* Torque */
double torqnij = (1. ^ dcG) % fnij;
double torqtij = (1. ^ dcG) % ftij;
double torqij = torqnij + torqtij;
torq = torq + torqij;
}
else
{
if (slip.contains({cpt_shape, cpt_shape_neigh}))
{
slip.erase({cpt_shape, cpt_shape_neigh});
if (interactionsOutput.contains({cpt_shape, cpt_shape_neigh}))
interactionsOutput.erase({cpt_shape, cpt_shape_neigh});
else if (interactionsOutput.contains({cpt_shape_neigh, cpt_shape}))
interactionsOutput.erase({cpt_shape_neigh, cpt_shape});
}
}
}
/* Interactions with walls */
int iobs = 0;
for (vector<double2> const& wall_it : listObstacles)
{
int iwall = 0;
for (auto it = wall_it.begin(); next(it) != wall_it.end(); ++it)
{
auto [distance, closestPoint] = get_distance_to_wall_and_closest_point(*it, *(next(it)), posshape);
double h = radius[cpt_shape] - distance;
// If the shape is in contact with the wall:
if (h > 0.)
{
double2 r_iw = posshape - closestPoint; // Vector starting on the wall and going towards the shape
double2 n_iw;
if (distance == 0.)
n_iw = double2(0., 0.);
else
n_iw = (1. / distance) * r_iw;
double2 dcGshape = -(radius[cpt_shape] - h / 2.) * n_iw;
double2 dcG = delta[cpt_shape] + dcGshape; // Distance from the CM G to the contact point c
double2 v_ci = velshape + (angvel ^ dcGshape);
double2 viw = v_ci - double2(0., 0.);
double2 vortho_iw = (viw % n_iw) * n_iw;
double2 vt_iw = viw - vortho_iw;
// If the map does not contain this pair ie the slip is not initialized, we initialize it
// Otherwise: we increment it
if (!slip_wall.contains({cpt_shape, iobs, iwall}))
slip_wall[{cpt_shape, iobs, iwall}] = double2(0., 0.);
else
{
double2 slip_wall_prime = slip_wall[{cpt_shape, iobs, iwall}];
// Rotation of the slip to take into account the rotation of the contact reference frame
// from t to t+dt_mech (D.R. Vyas, J.M. Ottino, R.M. Lueptow et al. 2025)
double2 slip_wall_projected = slip_wall_prime - (slip_wall_prime % n_iw) * n_iw;
double2 slip_wall_new = slip_wall_prime;
if ((!slip_wall_projected) > 0.0)
{
slip_wall_new = (!slip_wall_prime / !slip_wall_projected) * slip_wall_projected;
}
slip_wall[{cpt_shape, iobs, iwall}] = slip_wall[{cpt_shape, iobs, iwall}] + dt_mech * vt_iw;
}
// For the Interactions output file:
interactionsOutputWall[{cpt_shape, iobs, iwall}][SLIP] = slip_wall[{cpt_shape, iobs, iwall}];
uint32_t shapeMaterialId = shapesMaterial[active_shapeIDshape_crowd[cpt_shape]];
uint32_t obstacleMaterialId = obstaclesMaterial[iobs];
/* Normal interactions */
double k_n_wall = binaryProperties[STIFFNESS_NORMAL][shapeMaterialId][obstacleMaterialId];
double Gamma_n_wall = binaryProperties[DAMPING_NORMAL][shapeMaterialId][obstacleMaterialId];
double2 fniw_elastic = k_n_wall * h * n_iw;
double2 fniw_viscous = -Gamma_n_wall * vortho_iw;
double2 fniw = fniw_elastic + fniw_viscous;
fortho = fortho + fniw;
interactionsOutputWall[{cpt_shape, iobs, iwall}][FORCE_ORTHO] = fniw;
/* Tangential interactions */
double k_t_wall = binaryProperties[STIFFNESS_TANGENTIAL][shapeMaterialId][obstacleMaterialId];
double Gamma_t_wall = binaryProperties[DAMPING_TANGENTIAL][shapeMaterialId][obstacleMaterialId];
double2 ftiw_static = -k_t_wall * slip_wall[{cpt_shape, iobs, iwall}] - Gamma_t_wall * vt_iw;
double2 t_viw = double2(0., 0.);
double2 ftiw = double2(0., 0.);
double mu_dyn_wall = binaryProperties[FRICTION_SLIDING][shapeMaterialId][obstacleMaterialId];
if ((!ftiw_static) > mu_dyn_wall * !fniw)
{
t_viw = (1.0 / !ftiw_static) * ftiw_static;
ftiw = mu_dyn_wall * !fniw * t_viw;
slip_wall[{cpt_shape, iobs, iwall}] = -(1.0 / k_t_wall) * (mu_dyn_wall * !fniw * t_viw + Gamma_t_wall * vt_iw);
}
else
{
ftiw = ftiw_static;
}
ft = ft + ftiw;
interactionsOutputWall[{cpt_shape, iobs, iwall}][FORCE_TAN] = ftiw;
/* Torque */
double torqniw = (1. ^ dcG) % fniw;
double torqtiw = (1. ^ dcG) % ftiw;
double torqiw = torqniw + torqtiw;
torq = torq + torqiw;
}
else
{
if (slip_wall.contains({cpt_shape, iobs, iwall}))
{
slip_wall.erase({cpt_shape, iobs, iwall});
interactionsOutputWall.erase({cpt_shape, iobs, iwall});
}
}
iwall++;
}
iobs++;
}
return {fortho, ft, torq};
}
void MechanicalLayer::loop()
{
// Reset the forces and torques
for (unsigned cpt_agent = 0; cpt_agent < nb_active_agents; cpt_agent++)
{
Forthon[cpt_agent] = double2(0., 0.);
Ftn[cpt_agent] = double2(0., 0.);
taun[cpt_agent] = 0.;
}
// Loop over shapes for forces and momentum
// Calculation is done at time n
for (unsigned cpt_shape = 0; cpt_shape < nb_active_shapes; cpt_shape++)
{
auto Motion = get_interactions(cpt_shape, true);
const unsigned cpt_agent(active_shapeIDagent[cpt_shape]);
Forthon[cpt_agent] = // Resultant of normal forces (applied on the contact point)
Forthon[cpt_agent] + (1. / masses[cpt_agent]) * get<0>(Motion);
Ftn[cpt_agent] = // Resultant of tangential forces (applied on the contact point)
Ftn[cpt_agent] + (1.0 / masses[cpt_agent]) * get<1>(Motion);
taun[cpt_agent] = taun[cpt_agent] + (1.0 / mois[cpt_agent]) * get<2>(Motion); // Resultant of torques
}
// Loop over agents for positions
for (unsigned cpt_agent = 0; cpt_agent < nb_active_agents; cpt_agent++)
{
double UnmZetadt2 = 1.0 - 0.5 * dt_mech * damping[cpt_agent].first;
taun[cpt_agent] = taun[cpt_agent] + (wdesired[cpt_agent] - wn[cpt_agent]) * damping[cpt_agent].second;
// Update positions with velocity Verlet algorithm
rgnp1[cpt_agent] = rgn[cpt_agent] + UnmZetadt2 * dt_mech * vgn[cpt_agent] +
0.5 * dt_mech * dt_mech * (Fp[cpt_agent] + Forthon[cpt_agent] + Ftn[cpt_agent]);
thetnp1[cpt_agent] = thetn[cpt_agent] + dt_mech * wn[cpt_agent] + 0.5 * dt_mech * dt_mech * taun[cpt_agent];
}
// Loop over shapes for velocities
// Calculation is done at time n+1
vector<double2> forthonp1(nb_active_agents, double2(0, 0));
vector<double2> ftnp1(nb_active_agents, double2(0, 0));
vector<double> taunp1(nb_active_agents, 0.);
for (unsigned cpt_shape = 0; cpt_shape < nb_active_shapes; cpt_shape++)
{
auto Motion = get_interactions(cpt_shape, false);
const unsigned cpt_agent(active_shapeIDagent[cpt_shape]);
forthonp1[cpt_agent] = forthonp1[cpt_agent] + get<0>(Motion);
ftnp1[cpt_agent] = ftnp1[cpt_agent] + get<1>(Motion);
taunp1[cpt_agent] = taunp1[cpt_agent] + get<2>(Motion);
}
// Loop over agents for velocities
for (unsigned cpt_agent = 0; cpt_agent < nb_active_agents; cpt_agent++)
{
double UnmZetadt2 = 1.0 - 0.5 * dt_mech * damping[cpt_agent].first;
double UnpZetadt2 = 1.0 + 0.5 * dt_mech * damping[cpt_agent].first;
double wn_trial = wn[cpt_agent] + dt_mech * taun[cpt_agent];
taunp1[cpt_agent] = taunp1[cpt_agent] + (wdesired[cpt_agent] - wn_trial) * damping[cpt_agent].second;
// Update velocities
vgnp1[cpt_agent] =
1.0 / UnpZetadt2 *
(UnmZetadt2 * vgn[cpt_agent] +
0.5 * dt_mech * (2. * Fp[cpt_agent] + Forthon[cpt_agent] + Ftn[cpt_agent] + forthonp1[cpt_agent] + ftnp1[cpt_agent]));
wnp1[cpt_agent] = wn[cpt_agent] + 0.5 * dt_mech * (taun[cpt_agent] + taunp1[cpt_agent]);
}
// Update relative positions of the shapes
for (unsigned cpt_shape = 0; cpt_shape < nb_active_shapes; cpt_shape++)
{
const unsigned cpt_agent(active_shapeIDagent[cpt_shape]);
const double delta_theta = thetnp1[cpt_agent] - thetn[cpt_agent];
const double old_x = delta[cpt_shape].first;
const double old_y = delta[cpt_shape].second;
delta[cpt_shape].first = old_x * std::cos(delta_theta) - old_y * std::sin(delta_theta);
delta[cpt_shape].second = old_x * std::sin(delta_theta) + old_y * std::cos(delta_theta);
}
// Update position, velocity, orientation, angular velocity of each agent
for (unsigned cpt_agent = 0; cpt_agent < nb_active_agents; cpt_agent++)
{
{
rgn[cpt_agent] = rgnp1[cpt_agent];
vgn[cpt_agent] = vgnp1[cpt_agent];
thetn[cpt_agent] = thetnp1[cpt_agent];
wn[cpt_agent] = wnp1[cpt_agent];
}
}
}
pair<bool, bool> MechanicalLayer::existsContacts()
{
bool agentContact = false;
bool wallContact = false;
for (auto const& [key, value] : slip)
{
if (value != double2(0., 0.))
{
agentContact = true;
break;
}
}
for (auto const& [key, value] : slip_wall)
{
if (value != double2(0., 0.))
{
wallContact = true;
break;
}
}
return {agentContact, wallContact};
}
void MechanicalLayer::generateInteractionsOutputFile(const string& interactionsFile, const pair<bool, bool>& exists)
{
if (!exists.first && !exists.second)
{
return;
}
ofstream outputDoc;
outputDoc.open(interactionsFile);
outputDoc << R"(<?xml version="1.0" encoding="utf-8"?>)" << endl;
outputDoc << "<Interactions>" << endl;
/* Loop over active agents */
set<unsigned> parent; // Variable to remember if we have opening tags for parents
set<pair<unsigned, unsigned>> parentChild; // Variable to remember if we have an opening child tag
for (uint32_t a = 0; a < nb_active_agents; a++)
{
// First, collisions with agents
if (exists.first && !interactionsOutput.empty())
{
for (auto iterator = interactionsOutput.begin(); iterator != interactionsOutput.end();)
{
auto shape = iterator->first.first;
const uint32_t agent = active_shapeIDagent[shape];
if (agent > a)
break;
// If we're here, agent = a
auto output = iterator->second;
if (output[SLIP] == double2(0., 0.) && output[FORCE_ORTHO] == double2(0., 0.) && output[FORCE_TAN] == double2(0., 0.))
continue;
if (!parent.contains(a))
{
if (!parent.empty())
outputDoc << " </Agent>" << endl;
outputDoc << " <Agent Id=\"" << agentMapInverse[agentActiveIds[agent]] << "\">" << endl;
parent.insert(a);
}
auto shapeNeighbour = iterator->first.second;
const uint32_t neighbour = active_shapeIDagent[shapeNeighbour];
if (!parentChild.contains({agent, neighbour}))
{
if (!parentChild.empty() && parentChild.rbegin()->first == agent)
// We have switched to another child within the same agent -> insert child closing tag
outputDoc << " </Agent>" << endl;
outputDoc << " <Agent Id=\"" << agentMapInverse[agentActiveIds[neighbour]] << "\">" << endl;
parentChild.insert({agent, neighbour});
}
outputDoc << " <Interaction ParentShape=\"" << (shape - agentIDshape[agentActiveIds[agent]]) << "\" "
<< "ChildShape=\"" << (shapeNeighbour - agentIDshape[agentActiveIds[neighbour]]) << "\" ";
if (output[SLIP] != double2(0., 0.))
outputDoc << "TangentialRelativeDisplacement=\"" << output[SLIP].first << "," << output[SLIP].second << "\" ";
if (output[FORCE_ORTHO] != double2(0., 0.))
outputDoc << "Fn=\"" << output[FORCE_ORTHO].first << "," << output[FORCE_ORTHO].second << "\" ";
if (output[FORCE_TAN] != double2(0., 0.))
outputDoc << "Ft=\"" << output[FORCE_TAN].first << "," << output[FORCE_TAN].second << "\" ";
outputDoc << "/>" << endl;
interactionsOutput.erase(iterator++);
}
if (!parentChild.empty() && parentChild.rbegin()->first == a)
// If there were entries for the current agent, we need to close the last Agent child
outputDoc << " </Agent>" << endl;
}
// Second, collision with walls
if (exists.second && !interactionsOutputWall.empty())
{
for (auto iterator = interactionsOutputWall.begin(); iterator != interactionsOutputWall.end();)
{
auto key = iterator->first;
const uint32_t shape = get<0>(key);
const uint32_t agent = active_shapeIDagent[shape];
// If the current element of interactionsOutputWall is not the same as the last parent, end
if (agent > a)
break;
auto output = iterator->second;
if (output[0] == double2(0., 0.) && output[1] == double2(0., 0.) && output[2] == double2(0., 0.))
continue;
if (!parent.contains(a))
{
if (!parent.empty())
outputDoc << " </Agent>" << endl;
outputDoc << " <Agent Id=\"" << agentMapInverse[agentActiveIds[agent]] << "\">" << endl;
parent.insert(a);
}
outputDoc << " <Wall ShapeId=\"" << (shape - agentIDshape[agentActiveIds[agent]]) << "\" "
<< "WallId=\"" << get<1>(key) << "\" CornerId=\"" << get<2>(key) << "\" ";
outputDoc << "TangentialRelativeDisplacement=\"" << output[SLIP].first << "," << output[SLIP].second << "\" ";
if (output[FORCE_ORTHO] != double2(0., 0.))
outputDoc << "Fn=\"" << output[FORCE_ORTHO].first << "," << output[FORCE_ORTHO].second << "\" ";
if (output[FORCE_TAN] != double2(0., 0.))
outputDoc << "Ft=\"" << output[FORCE_TAN].first << "," << output[FORCE_TAN].second << "\" ";
outputDoc << "/>" << endl;
// Erase the entry in slip_wall to make the next sequential search "easier"
interactionsOutputWall.erase(iterator++);
}
}
}
outputDoc << " </Agent>" << endl;
outputDoc << "</Interactions>";
outputDoc.close();
}