| 111 | } |
| 112 | |
| 113 | bool PickingSystem::pickAtom2D(glm::ivec2 screenPos, float tolerance, AtomHit& hit) const { |
| 114 | BaseRenderer* rend = renderer->get(); |
| 115 | float bestDistSqr = std::numeric_limits<float>::max(); |
| 116 | size_t bestIndex = static_cast<size_t>(-1); |
| 117 | |
| 118 | for (size_t i = 0; i < atomStorage->size(); ++i) { |
| 119 | const glm::vec3 worldPos = displayAtomPos(i); |
| 120 | const glm::ivec2 atomScreen{rend->camera.worldToScreen(worldPos)}; |
| 121 | const glm::ivec2 d = atomScreen - screenPos; |
| 122 | const float distSqr = static_cast<float>(d.x * d.x + d.y * d.y); |
| 123 | |
| 124 | // радиус атома в экранных пикселях |
| 125 | const float atomRadius = AtomData::getProps(atomStorage->type(i)).radius; |
| 126 | const float screenRadius = atomRadius * rend->camera.getZoom() + tolerance; |
| 127 | |
| 128 | if (distSqr < screenRadius * screenRadius && distSqr < bestDistSqr) { |
| 129 | bestDistSqr = distSqr; |
| 130 | bestIndex = i; |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | if (bestIndex == static_cast<size_t>(-1)) { |
| 135 | return false; |
| 136 | } |
| 137 | |
| 138 | hit = {bestIndex, atomStorage->atomId(bestIndex), std::sqrt(bestDistSqr)}; |
| 139 | return true; |
| 140 | } |
| 141 | |
| 142 | // 3D: ray cast — ищем ближайший атом вдоль луча |
| 143 | bool PickingSystem::pickAtom3D(glm::ivec2 screenPos, AtomHit& hit) const { |
nothing calls this directly
no test coverage detected