2023-01-04 14:37:54 +03:00
|
|
|
|
#include <cmath>
|
|
|
|
|
#include "ball.hpp"
|
2023-01-04 14:56:03 +03:00
|
|
|
|
#include "brick_grid.hpp"
|
|
|
|
|
#include "paddle.hpp"
|
2023-01-04 14:37:54 +03:00
|
|
|
|
|
|
|
|
|
// Вспомагательные функции для работы с векторами типа 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);
|
|
|
|
|
}
|