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