LCOV - code coverage report
Current view: top level - cell - ReactionEngine.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 82 88 93.2 %
Date: 2025-12-06 00:15:40 Functions: 11 11 100.0 %

          Line data    Source code
       1             : #include "ReactionEngine.hpp"
       2             : #include "Disc.hpp"
       3             : #include "MathUtils.hpp"
       4             : #include "Reaction.hpp"
       5             : 
       6             : namespace cell
       7             : {
       8             : 
       9          10 : ReactionEngine::ReactionEngine(const DiscTypeRegistry& discTypeRegistry, const AbstractReactionTable& reactionTable)
      10          10 :     : discTypeRegistry_(discTypeRegistry)
      11          10 :     , transformations_(&reactionTable.getTransformations())
      12          10 :     , decompositions_(&reactionTable.getDecompositions())
      13          10 :     , combinations_(&reactionTable.getCombinations())
      14          10 :     , exchanges_(&reactionTable.getExchanges())
      15             : {
      16          10 : }
      17             : 
      18          15 : bool ReactionEngine::transformationReaction(Disc* disc, double dt) const
      19             : {
      20          15 :     const Reaction* reaction = selectUnimolecularReaction(*transformations_, disc->getTypeID(), dt);
      21          15 :     if (!reaction)
      22          14 :         return false;
      23             : 
      24           1 :     disc->setType(reaction->getProduct1());
      25             : 
      26           1 :     return true;
      27             : }
      28             : 
      29          14 : std::optional<Disc> ReactionEngine::decompositionReaction(Disc* d1, double dt) const
      30             : {
      31          14 :     const Reaction* reaction = selectUnimolecularReaction(*decompositions_, d1->getTypeID(), dt);
      32          14 :     if (!reaction)
      33          13 :         return {};
      34             : 
      35           1 :     double v = mathutils::abs(d1->getVelocity());
      36           1 :     if (v == 0)
      37             :     {
      38             :         // If the disc is stationary and wants to split apart, we'll give it a random velocity to do so
      39           0 :         d1->setVelocity(
      40           0 :             sf::Vector2d{mathutils::getRandomNumber<double>(-10, 10), mathutils::getRandomNumber<double>(-10, 10)});
      41           0 :         v = mathutils::abs(d1->getVelocity());
      42             :     }
      43             : 
      44           1 :     const sf::Vector2d eductNormalizedVelocity = d1->getVelocity() / v;
      45           1 :     const sf::Vector2d n{-eductNormalizedVelocity.y, eductNormalizedVelocity.x};
      46             : 
      47           1 :     d1->setType(reaction->getProduct1());
      48           1 :     d1->setVelocity(v * n);
      49             : 
      50           1 :     Disc product2(reaction->getProduct2());
      51           1 :     product2.setPosition(d1->getPosition());
      52           1 :     product2.setVelocity(-v * n);
      53             : 
      54           1 :     const auto R1 = discTypeRegistry_.getByID(d1->getTypeID()).getRadius();
      55           1 :     const auto R2 = discTypeRegistry_.getByID(product2.getTypeID()).getRadius();
      56           1 :     const auto overlap = R1 + R2 + 1e-6; // Discs at same position always have maximum overlap R1 + R2
      57             : 
      58           1 :     d1->move(0.5 * overlap * n);
      59           1 :     product2.move(-0.5 * overlap * n);
      60             : 
      61           1 :     return product2;
      62             : }
      63             : 
      64           5 : bool ReactionEngine::combinationReaction(Disc* d1, Disc* d2) const
      65             : {
      66             :     const Reaction* reaction =
      67           5 :         selectBimolecularReaction(*combinations_, std::make_pair(d1->getTypeID(), d2->getTypeID()));
      68           5 :     if (!reaction)
      69           2 :         return false;
      70             : 
      71           3 :     const auto& resultType = discTypeRegistry_.getByID(reaction->getProduct1());
      72           3 :     const auto* d1Type = &discTypeRegistry_.getByID(d1->getTypeID());
      73           3 :     const auto* d2Type = &discTypeRegistry_.getByID(d2->getTypeID());
      74             : 
      75             :     // For reactions of type A + B -> C, we keep the one closer in size to C and destroy the other
      76           3 :     if (std::abs(resultType.getRadius() - d1Type->getRadius()) > std::abs(resultType.getRadius() - d2Type->getRadius()))
      77             :     {
      78           0 :         std::swap(d1, d2);
      79           0 :         std::swap(d1Type, d2Type);
      80             :     }
      81             : 
      82           6 :     d1->setVelocity((d1Type->getMass() * d1->getVelocity() + d2Type->getMass() * d2->getVelocity()) /
      83           6 :                     resultType.getMass());
      84           3 :     d1->setType(reaction->getProduct1());
      85             : 
      86           3 :     d2->markDestroyed();
      87             : 
      88           3 :     return true;
      89             : }
      90             : 
      91           2 : bool ReactionEngine::exchangeReaction(Disc* d1, Disc* d2) const
      92             : {
      93           2 :     const Reaction* reaction = selectBimolecularReaction(*exchanges_, std::make_pair(d1->getTypeID(), d2->getTypeID()));
      94           2 :     if (!reaction)
      95           1 :         return false;
      96             : 
      97           1 :     const auto& d1Type = discTypeRegistry_.getByID(d1->getTypeID());
      98           1 :     const auto& d2Type = discTypeRegistry_.getByID(d2->getTypeID());
      99           1 :     const auto& product1Type = discTypeRegistry_.getByID(reaction->getProduct1());
     100           1 :     const auto& product2Type = discTypeRegistry_.getByID(reaction->getProduct2());
     101             : 
     102           1 :     d1->scaleVelocity(std::sqrt(d1Type.getMass() / product1Type.getMass()));
     103           1 :     d1->setType(reaction->getProduct1());
     104             : 
     105           1 :     d2->scaleVelocity(std::sqrt(d2Type.getMass() / product2Type.getMass()));
     106           1 :     d2->setType(reaction->getProduct2());
     107             : 
     108           1 :     return true;
     109             : }
     110             : 
     111          15 : std::optional<Disc> ReactionEngine::applyUnimolecularReactions(Disc& disc, double dt) const
     112             : {
     113             :     // TODO random shuffle all reactions
     114          15 :     if (transformationReaction(&disc, dt))
     115           1 :         return {};
     116             : 
     117          14 :     return decompositionReaction(&disc, dt);
     118             : }
     119             : 
     120           9 : void ReactionEngine::applyBimolecularReactions(CollisionDetector::DetectedCollisions& detectedCollisions) const
     121             : {
     122          27 :     for (auto collisionType :
     123          36 :          {CollisionDetector::CollisionType::DiscDisc, CollisionDetector::CollisionType::DiscIntrudingDisc})
     124             :     {
     125          18 :         auto indexes = detectedCollisions.indexes.find(collisionType);
     126          18 :         if (indexes == detectedCollisions.indexes.end())
     127          14 :             continue;
     128             : 
     129           9 :         for (std::size_t index : indexes->second)
     130             :         {
     131           5 :             const auto& collision = detectedCollisions.collisions[index];
     132           5 :             if (collision.isInvalidatedByDestroyedDisc())
     133           0 :                 continue;
     134             : 
     135           5 :             if (combinationReaction(collision.disc, collision.otherDisc))
     136           3 :                 continue;
     137             :             else
     138           2 :                 exchangeReaction(collision.disc, collision.otherDisc);
     139             :         }
     140             :     }
     141           9 : }
     142             : 
     143          29 : const Reaction* ReactionEngine::selectUnimolecularReaction(const SingleLookupMap& map, const DiscTypeID& key,
     144             :                                                            double dt) const
     145             : {
     146          58 :     return selectReaction(
     147           2 :         map, key, [&](const Reaction& reaction)
     148          60 :         { return mathutils::getRandomNumber<double>(0, 1) <= 1 - std::pow(1 - reaction.getProbability(), dt); });
     149             : }
     150             : 
     151           7 : const Reaction* ReactionEngine::selectBimolecularReaction(const PairLookupMap& map,
     152             :                                                           const std::pair<DiscTypeID, DiscTypeID>& key) const
     153             : {
     154          14 :     return selectReaction(map, key, [](const Reaction& reaction)
     155          18 :                           { return mathutils::getRandomNumber<double>(0, 1) <= reaction.getProbability(); });
     156             : }
     157             : 
     158             : } // namespace cell

Generated by: LCOV version 1.14