You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

184 lines
6.4 KiB
C++

#include <cmath>
#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<sf::Vector2f, bool> 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<int, int> 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<int>((position.x - left - radius) / brickWidth), 0, gridColumns);
int brickColumnFinish = std::clamp(static_cast<int>((position.x - left + radius) / brickWidth) + 1, 0, gridColumns);
int brickRowStart = std::clamp(static_cast<int>((position.y - top - radius) / brickHeight), 0, gridRows);
int brickRowFinish = std::clamp(static_cast<int>((position.y - top + radius) / brickHeight) + 1, 0, gridRows);
// Если шарик находится вне сетки блоков, то выходим
if (brickColumnStart == brickColumnFinish || brickRowStart == brickRowFinish)
return {-1, -1};
// Находим ближайший к центру шарика активный пересекаемый шариком блок
float closestSqNorm = width * width + height * height;
std::pair<int, int> 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);
}