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++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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);
}