holesome/src/game/camera/tracking_view.cpp

290 lines
7.3 KiB
C++
Raw Normal View History

2023-05-23 23:38:37 +02:00
#include "tracking_view.h"
#include "../../utilities/vector_utils.hpp"
#include "../player/player_collection.hpp"
2023-05-09 20:50:50 +02:00
TrackingView::TrackingView(TrackingViewOptions options) : options(options),
view(nullptr),
hasViewChanged(false),
trackables({})
{
2023-06-21 20:42:42 +02:00
initializeView();
marker = new CircleObject(DB_CIRCLE_RADIUS, sf::Color::Yellow);
}
2023-05-23 23:38:37 +02:00
TrackingView::~TrackingView()
2023-05-09 20:50:50 +02:00
{
2023-05-10 11:03:14 +02:00
delete view;
delete marker;
2023-05-09 20:50:50 +02:00
}
2023-05-24 14:00:51 +02:00
void TrackingView::lateUpdate()
2023-05-09 20:50:50 +02:00
{
processTrackableStates();
if (!trackables.empty())
{
followTrackables();
adjustSizeToTrackables();
}
// Update window if necessary
if (hasViewChanged)
2023-05-09 20:50:50 +02:00
{
2023-06-21 20:42:42 +02:00
this->setViewForWindow();
hasViewChanged = false;
2023-05-09 20:50:50 +02:00
}
2023-05-10 11:03:14 +02:00
}
2023-05-24 14:00:51 +02:00
void TrackingView::initializeView()
2023-05-10 11:03:14 +02:00
{
2023-05-24 14:00:51 +02:00
auto size = Game::getInstance()->window->getView().getSize();
view = new sf::View({0, 0}, size);
2023-05-24 15:37:42 +02:00
hasViewChanged = true;
2023-05-09 20:50:50 +02:00
}
void TrackingView::setSize(sf::Vector2f newSize)
{
// Is different?
auto size = getSize();
if (size.x == newSize.x && size.y == newSize.y)
{
// Nothing to do
return;
}
bool didAspectRationChange = false;
auto prevRatio = size.x / size.y;
auto newRatio = newSize.x / newSize.y;
if (abs(prevRatio - newRatio) > 0.001)
{
didAspectRationChange = true;
}
if (options.softResizeSpeed != 0 && !didAspectRationChange)
{
// Smooth out transition to new size
newSize = size + (newSize - size) * options.softResizeSpeed * FRAME_TIME.asSeconds();
}
view->setSize(newSize);
hasViewChanged = true;
}
2023-05-23 23:38:37 +02:00
sf::Vector2f TrackingView::getSize() const
{
return view->getSize();
}
sf::Vector2f TrackingView::getWindowSize() const
{
auto size = Game::getInstance()->window->getSize();
return {static_cast<float>(size.x), static_cast<float>(size.y)};
}
2023-05-23 23:38:37 +02:00
sf::Vector2f TrackingView::getCenter() const
{
return view->getCenter();
}
void TrackingView::followTrackables()
{
auto trackingPoint = getTrackingArea().getCenter();
2023-06-17 21:25:33 +02:00
if (DB_TRACKING_VIEW_CENTER)
{
2023-06-17 19:48:51 +02:00
marker->coordinates->setIsometric(IsometricCoordinates(trackingPoint));
2023-05-24 15:21:53 +02:00
}
2023-05-24 15:21:53 +02:00
// Calculate distance to target to check how to handle it
auto currentCenter = view->getCenter();
auto vectorToTarget = trackingPoint - currentCenter;
auto distanceToTarget = length(vectorToTarget);
2023-05-24 15:21:53 +02:00
if (distanceToTarget <= getRadius(options.freeMoveThreshold))
2023-05-24 15:21:53 +02:00
{
// Nothing to do
return;
}
2023-05-24 15:36:49 +02:00
// Targets are outside of free move radius, follow them
auto deltaToDesiredView = normalize(vectorToTarget);
// Reduce delta to edge of free-move area to make it less jaring
deltaToDesiredView *= distanceToTarget - getRadius(options.freeMoveThreshold);
2023-05-24 15:36:49 +02:00
moveCenter(deltaToDesiredView);
}
void TrackingView::moveCenter(sf::Vector2<float> delta)
{
if (options.softFollowSpeed != 0)
{
// Soft
delta *= options.softFollowSpeed * FRAME_TIME.asSeconds();
}
view->move(delta);
hasViewChanged = true;
}
void TrackingView::draw(sf::RenderWindow *window)
{
2023-06-17 21:25:33 +02:00
if (!DB_TRACKING_VIEW_CENTER)
{
2023-05-24 15:21:53 +02:00
return;
}
marker->draw(window);
}
void TrackingView::addTrackable(const std::shared_ptr<ITrackable> &trackable)
{
trackables.push_back(trackable);
}
void TrackingView::processTrackableStates()
{
// Remove trackables that have ended tracking
std::remove_if(trackables.begin(), trackables.end(), [](std::shared_ptr<ITrackable> trackable)
{
return trackable->getTrackableState() == TrackableState::END_TRACKING;
});
}
TrackingArea TrackingView::getTrackingArea() const
{
auto initialTrackable = trackables[0];
TrackingArea area = {
initialTrackable->getTrackablePosition() - initialTrackable->getTrackableSize() / 2.f,
initialTrackable->getTrackablePosition() + initialTrackable->getTrackableSize() / 2.f
};
// Find min and max point coordinates for x- and y-axis over all trackables
for (auto trackable: trackables)
{
auto trackableCoordinates = trackable->getTrackablePosition();
auto trackableSize = trackable->getTrackableSize();
auto topLeft = trackableCoordinates - trackableSize / 2.f;
auto bottomRight = trackableCoordinates + trackableSize / 2.f;
if (topLeft.x < area.topLeft.x)
{
area.topLeft.x = topLeft.x;
}
if (bottomRight.x > area.bottomRight.x)
{
area.bottomRight.x = bottomRight.x;
}
if (topLeft.y < area.topLeft.y)
{
area.topLeft.y = topLeft.y;
}
if (bottomRight.y > area.bottomRight.y)
{
area.bottomRight.y = bottomRight.y;
}
}
return area;
2023-05-24 15:21:53 +02:00
}
void TrackingView::adjustSizeToTrackables()
{
// Calculate new view size
auto newViewSize = getTrackingArea().getSize();
newViewSize = applyPadding(newViewSize);
newViewSize = restrainToBounds(newViewSize);
// Extend view to match aspect ratio
auto windowSize = getWindowSize();
auto aspectRatio = windowSize.x / windowSize.y;
if (newViewSize.x / newViewSize.y > aspectRatio)
{
// Extend x-axis
newViewSize.y = newViewSize.x / aspectRatio;
} else
{
// Extend y-axis
newViewSize.x = newViewSize.y * aspectRatio;
}
setSize(newViewSize);
}
sf::Vector2f TrackingView::restrainToBounds(sf::Vector2f viewSize) const
{
// x-axis
if (options.minViewSize.x > 0 && viewSize.x < options.minViewSize.x)
{
viewSize.x = options.minViewSize.x;
} else if (options.maxViewSize.x > 0 && viewSize.x > options.maxViewSize.x)
{
viewSize.x = options.maxViewSize.x;
}
// y-axis
if (options.minViewSize.y > 0 && viewSize.y < options.minViewSize.y)
{
viewSize.y = options.minViewSize.y;
} else if (options.maxViewSize.y > 0 && viewSize.y > options.maxViewSize.y)
{
viewSize.y = options.maxViewSize.y;
}
return viewSize;
}
sf::Vector2f TrackingView::applyPadding(sf::Vector2f viewSize) const
{
auto padding = options.viewSizePadding;
if (padding.x <= 1)
{
padding.x *= viewSize.x;
}
if (padding.y <= 1)
{
padding.y *= viewSize.y;
}
return viewSize + padding;
}
float TrackingView::getRadius(float threshold) const
{
if (threshold <= 0)
{
return 0;
}
if (threshold > 1)
{
return threshold;
}
auto windowSize = getWindowSize();
float smallestSide = std::min(windowSize.x, windowSize.y);
// Only half of the side, since we are calculating radius
return smallestSide / 2.f * threshold;
}
void TrackingView::removeTrackable(const std::shared_ptr<ITrackable> &trackable)
{
trackables.erase(
std::remove_if(trackables.begin(), trackables.end(), [&trackable](const std::shared_ptr<ITrackable> &t)
{
return t == trackable;
}), trackables.end());
}
2023-06-21 20:42:42 +02:00
void TrackingView::setViewport(sf::Vector2f center, sf::Vector2f size)
{
// TODO: Resize content for viewport
view->setViewport(sf::FloatRect(center.x, center.y, size.x, size.y));
}
void TrackingView::setViewForWindow()
{
Game::getInstance()->window->setView(*this->view);
}