#include #include "ball.hpp" #include "brick_grid.hpp" #include "paddle.hpp" // Вспомагательные функции для работы с векторами типа sf::Vector2f float operator*(const sf::Vector2f& first, const sf::Vector2f& second) { return first.x * second.x + first.y * second.y; } float norm(sf::Vector2f a) { return std::sqrt(a.x * a.x + a.y * a.y); } float sqnorm(sf::Vector2f a) { return a.x * a.x + a.y * a.y; } Ball::Ball(float radius, sf::Vector2f position, sf::Vector2f velocity) : radius(radius), position(position), velocity(velocity) {} void Ball::update(float dt) { position += velocity * dt; } void Ball::draw(sf::RenderWindow& window) { static sf::CircleShape shape {}; shape.setRadius(radius); shape.setOrigin(radius, radius); shape.setFillColor(color); shape.setPosition(position); window.draw(shape); } std::pair Ball::findClosestPoint(const sf::FloatRect& rect) const { float left = rect.left; float right = rect.left + rect.width; float bottom = rect.top + rect.height; float top = rect.top; sf::Vector2f d; if (position.x < left) d.x = left; else if (position.x > right) d.x = right; else d.x = position.x; if (position.y > bottom) d.y = bottom; else if (position.y < top) d.y = top; else d.y = position.y; d -= position; bool isColiding = sqnorm(d) < radius * radius; return {d, isColiding}; } bool Ball::handleRectCollision(const sf::FloatRect& rect) { auto [d, isColiding] = findClosestPoint(rect); if (!isColiding) return false; float closestPointNorm = norm(d); // Если расстояние == 0, то это значит, что шарик за 1 фрейм зашёл центром внутрь блока // Отражаем шарик от блока if (closestPointNorm < 1e-4) { if (std::fabs(velocity.x) > std::fabs(velocity.y)) velocity.x *= -1; else velocity.y *= -1; } // Если расстояние != 0, но шарик касается блока, то мы можем просчитать отражение более точно // Отражение от углов и по касательной. else if (closestPointNorm < radius) { position -= d * ((radius - closestPointNorm) / closestPointNorm); velocity -= 2.0f * d * (d * velocity) / (closestPointNorm * closestPointNorm); } return true; } void Ball::handleWallsCollision(sf::FloatRect boundary) { if (position.x < boundary.left + radius) { position.x = boundary.left + radius; velocity.x *= -1; } if (position.x > boundary.left + boundary.width - radius) { position.x = boundary.left + boundary.width - radius; velocity.x *= -1; } if (position.y < boundary.top + radius) { position.y = boundary.top + radius; velocity.y *= -1; } } std::pair Ball::handleBrickGridCollision(const BrickGrid& brickGrid) { auto [gridColumns, gridRows] = brickGrid.getGridSizes(); auto [brickWidth, brickHeight] = brickGrid.getBrickSizes(); auto [left, top, width, height] = brickGrid.getBorder(); // Определяем координаты блоков с которыми шарик может пересечься int brickColumnStart = std::clamp(static_cast((position.x - left - radius) / brickWidth), 0, gridColumns); int brickColumnFinish = std::clamp(static_cast((position.x - left + radius) / brickWidth) + 1, 0, gridColumns); int brickRowStart = std::clamp(static_cast((position.y - top - radius) / brickHeight), 0, gridRows); int brickRowFinish = std::clamp(static_cast((position.y - top + radius) / brickHeight) + 1, 0, gridRows); // Если шарик находится вне сетки блоков, то выходим if (brickColumnStart == brickColumnFinish || brickRowStart == brickRowFinish) return {-1, -1}; // Находим ближайший к центру шарика активный пересекаемый шариком блок float closestSqNorm = width * width + height * height; std::pair closestBrickIndexes = {-1, -1}; for (int i = brickColumnStart; i < brickColumnFinish; ++i) { for (int j = brickRowStart; j < brickRowFinish; ++j) { if (!brickGrid.isBrickActive({i, j})) continue; sf::FloatRect rect {left + i * brickWidth, top + j * brickHeight, brickWidth, brickHeight}; auto [d, isColiding] = findClosestPoint(rect); if (!isColiding) continue; if (sqnorm(d) < closestSqNorm) { closestSqNorm = sqnorm(d); closestBrickIndexes = {i, j}; } } } // Если такого не нашли, то возвращаем {-1, -1} if (closestBrickIndexes.first == -1) return closestBrickIndexes; // Упруго сталкиваем шарик с найденым блоком sf::FloatRect rect {left + closestBrickIndexes.first * brickWidth, top + closestBrickIndexes.second * brickHeight, brickWidth, brickHeight}; handleRectCollision(rect); // Возвращаем координаты блока в сетки блоков return closestBrickIndexes; } // Обрабатываем столкновения шарика с ракеткой void Ball::handlePaddleCollision(const Paddle& paddle) { auto [d, isColiding] = findClosestPoint(paddle.getBorder()); if (!isColiding) return; // Столкновение не упругое // Угол отражения зависит от места на ракетке, куда стукнулся шарик // Если шарик стукнулся в левую часть ракетки, то он должен полететь влево. // Если в правую часть ракетки, то вправо. const float pi = 3.14159265; float velocityAngle = (position.x - paddle.position.x) / (paddle.size.x + 2 * radius) * (0.8 * pi) + pi / 2; float velocityNorm = norm(velocity); velocity.x = - velocityNorm * std::cos(velocityAngle); velocity.y = - velocityNorm * std::sin(velocityAngle); }