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