LCOV - code coverage report
Current view: top level - cell - CollisionDetector.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 94 97 96.9 %
Date: 2025-12-26 22:55:38 Functions: 11 11 100.0 %

          Line data    Source code
       1             : #include "CollisionDetector.hpp"
       2             : #include "Disc.hpp"
       3             : #include "MathUtils.hpp"
       4             : 
       5             : #include <algorithm>
       6             : 
       7             : namespace cell
       8             : {
       9             : 
      10             : DiscTypeMap<int> CollisionDetector::collisionCounts_;
      11             : 
      12          21 : CollisionDetector::CollisionDetector(const DiscTypeRegistry& discTypeRegistry,
      13          21 :                                      const MembraneTypeRegistry& membraneTypeRegistry)
      14          21 :     : discTypeRegistry_(discTypeRegistry)
      15          21 :     , membraneTypeRegistry_(membraneTypeRegistry)
      16             : {
      17          21 : }
      18             : 
      19          21 : void CollisionDetector::setParams(Params params)
      20             : {
      21          21 :     params_ = std::move(params);
      22          21 : }
      23             : 
      24          11 : void CollisionDetector::buildMembraneIndex()
      25             : {
      26          11 :     membraneEntries_.clear();
      27          11 :     const auto& membranes = *params_.membranes;
      28          11 :     membraneEntries_.reserve(membranes.size());
      29             : 
      30          23 :     for (std::size_t i = 0; i < membranes.size(); ++i)
      31          12 :         membraneEntries_.push_back(createEntry(membranes[i], membraneTypeRegistry_, i, EntryType::Membrane));
      32             : 
      33          11 :     std::sort(membraneEntries_.begin(), membraneEntries_.end(), entryComparator_);
      34          11 : }
      35             : 
      36          13 : void CollisionDetector::buildDiscIndex()
      37             : {
      38          13 :     discEntries_.clear();
      39          13 :     const auto& discs = *params_.discs;
      40             :     // We guess 100 intruding discs
      41          13 :     discEntries_.reserve(discs.size() + 100);
      42             : 
      43          39 :     for (std::size_t i = 0; i < discs.size(); ++i)
      44          26 :         discEntries_.push_back(createEntry(discs[i], discTypeRegistry_, i, EntryType::Disc));
      45             : 
      46          13 :     std::sort(discEntries_.begin(), discEntries_.end(), entryComparator_);
      47          13 : }
      48             : 
      49          13 : void CollisionDetector::addIntrudingDiscsToIndex()
      50             : {
      51          13 :     const auto& intrudingDiscs = *params_.intrudingDiscs;
      52          13 :     const auto oldSize = discEntries_.size();
      53             : 
      54          18 :     for (std::size_t i = 0; i < intrudingDiscs.size(); ++i)
      55           5 :         discEntries_.push_back(createEntry(*intrudingDiscs[i], discTypeRegistry_, i, EntryType::IntrudingDisc));
      56             : 
      57          13 :     auto mid = discEntries_.begin() + static_cast<ptrdiff_t>(oldSize);
      58          13 :     std::sort(mid, discEntries_.end(), entryComparator_);
      59          13 :     std::inplace_merge(discEntries_.begin(), mid, discEntries_.end(), entryComparator_);
      60          13 : }
      61             : 
      62          13 : std::vector<CollisionDetector::Collision> CollisionDetector::detectDiscMembraneCollisions()
      63             : {
      64          13 :     std::vector<Collision> collisions;
      65          13 :     collisions.reserve(static_cast<std::size_t>(static_cast<double>(discEntries_.size()) * 0.05));
      66             : 
      67          13 :     std::size_t startJ = 0;
      68             : 
      69          39 :     for (const auto& entry : discEntries_)
      70             :     {
      71          26 :         auto* disc = &(*params_.discs)[entry.index];
      72             : 
      73          26 :         if (!discIsContainedByMembrane(entry))
      74           1 :             collisions.push_back(Collision{.disc = disc,
      75           1 :                                            .membrane = params_.containingMembrane,
      76             :                                            .type = CollisionType::DiscContainingMembrane,
      77           1 :                                            .allowedToPass = canGoThrough(disc, params_.containingMembrane,
      78             :                                                                          CollisionType::DiscContainingMembrane)});
      79             : 
      80          26 :         if (startJ == membraneEntries_.size())
      81          16 :             continue;
      82             : 
      83          12 :         while (startJ < membraneEntries_.size() && membraneEntries_[startJ].maxX < entry.minX)
      84           2 :             ++startJ;
      85             : 
      86          17 :         for (std::size_t j = startJ; j < membraneEntries_.size(); ++j)
      87             :         {
      88           8 :             if (membraneEntries_[j].minX > entry.maxX)
      89           1 :                 break;
      90             : 
      91           7 :             if (mathutils::circlesOverlap(entry.position, entry.radius, membraneEntries_[j].position,
      92           7 :                                           membraneEntries_[j].radius))
      93             :             {
      94           7 :                 auto* membrane = &(*params_.membranes)[membraneEntries_[j].index];
      95           7 :                 collisions.push_back(
      96           7 :                     Collision{.disc = disc,
      97             :                               .membrane = membrane,
      98             :                               .type = CollisionType::DiscChildMembrane,
      99           7 :                               .allowedToPass = canGoThrough(disc, membrane, CollisionType::DiscChildMembrane)});
     100             :             }
     101             :         }
     102             :     }
     103             : 
     104          13 :     return collisions;
     105           0 : }
     106             : 
     107          13 : std::vector<CollisionDetector::Collision> CollisionDetector::detectDiscDiscCollisions()
     108             : {
     109          13 :     std::vector<Collision> collisions;
     110          13 :     collisions.reserve(static_cast<std::size_t>(static_cast<double>(discEntries_.size()) * 0.1));
     111             : 
     112          28 :     const auto getDiscPointer = [&](const Entry& entry)
     113             :     {
     114          28 :         if (entry.type == EntryType::IntrudingDisc)
     115           4 :             return (*params_.intrudingDiscs)[entry.index];
     116             : 
     117          24 :         return &(*params_.discs)[entry.index];
     118          13 :     };
     119             : 
     120          44 :     for (std::size_t i = 0; i < discEntries_.size(); ++i)
     121             :     {
     122          31 :         const auto& entry1 = discEntries_[i];
     123          41 :         for (std::size_t j = i + 1; j < discEntries_.size(); ++j)
     124             :         {
     125          22 :             const auto& entry2 = discEntries_[j];
     126          22 :             if (entry2.minX > entry1.maxX)
     127          12 :                 break;
     128             : 
     129             :             // TODO ignore collision if it's 2 intruders from the same child membrane to avoid double update (or maybe
     130             :             // that's not a problem?)
     131             : 
     132          10 :             if (!mathutils::circlesOverlap(entry1.position, entry1.radius, entry2.position, entry2.radius,
     133             :                                            MinOverlap{1e-2}))
     134           3 :                 continue;
     135             : 
     136           7 :             collisions.push_back(Collision{
     137           7 :                 .disc = getDiscPointer(entry1), .otherDisc = getDiscPointer(entry2), .type = CollisionType::DiscDisc});
     138             : 
     139           7 :             ++collisionCounts_[getDiscPointer(entry1)->getTypeID()];
     140           7 :             ++collisionCounts_[getDiscPointer(entry2)->getTypeID()];
     141             :         }
     142             :     }
     143             : 
     144          26 :     return collisions;
     145           0 : }
     146             : 
     147           6 : DiscTypeMap<int> CollisionDetector::getAndResetCollisionCounts()
     148             : {
     149           6 :     auto tmp = std::move(collisionCounts_);
     150           6 :     collisionCounts_.clear();
     151             : 
     152           6 :     return tmp;
     153             : }
     154             : 
     155          26 : bool CollisionDetector::discIsContainedByMembrane(const Entry& entry)
     156             : {
     157          26 :     auto disc = (*params_.discs)[entry.index];
     158             : 
     159          26 :     return mathutils::circleIsFullyContainedByCircle(
     160          26 :         disc.getPosition(), discTypeRegistry_.getByID(disc.getTypeID()).getRadius(),
     161          26 :         params_.containingMembrane->getPosition(),
     162          78 :         membraneTypeRegistry_.getByID(params_.containingMembrane->getTypeID()).getRadius());
     163             : }
     164             : 
     165           8 : bool CollisionDetector::canGoThrough(Disc* disc, Membrane* membrane,
     166             :                                      CollisionDetector::CollisionType collisionType) const
     167             : {
     168             :     using CollisionType = CollisionDetector::CollisionType;
     169             : 
     170             :     const auto permeability =
     171           8 :         membraneTypeRegistry_.getByID(membrane->getTypeID()).getPermeabilityFor(disc->getTypeID());
     172             : 
     173           8 :     if (permeability == MembraneType::Permeability::Bidirectional ||
     174           4 :         (collisionType == CollisionType::DiscChildMembrane && permeability == MembraneType::Permeability::Inward) ||
     175           0 :         (collisionType == CollisionType::DiscContainingMembrane && permeability == MembraneType::Permeability::Outward))
     176           5 :         return true;
     177             : 
     178           3 :     return false;
     179             : }
     180             : 
     181             : } // namespace cell

Generated by: LCOV version 1.14