holesome/src/game/camera/tracking_view.cpp

259 lines
6.3 KiB
C++

#include "tracking_view.h"
#include "../../utilities/vector_utils.hpp"
TrackingView::TrackingView(TrackingViewOptions options) : options(options),
view(nullptr),
hasViewChanged(false)
{
trackables = std::vector<ITrackable *>();
marker = new CircleObject(2, sf::Color::Yellow);
Game::getInstance()->registerView(this);
}
TrackingView::~TrackingView()
{
delete view;
delete marker;
}
void TrackingView::lateUpdate()
{
// Initialize if necessary
if (view == nullptr)
{
initializeView();
}
processTrackableStates();
if (!trackables.empty())
{
followTrackables();
adjustSizeToTrackables();
}
// Update window if necessary
if (hasViewChanged)
{
Game::getInstance()->window->setView(*this->view);
hasViewChanged = false;
}
}
void TrackingView::initializeView()
{
auto size = Game::getInstance()->window->getView().getSize();
view = new sf::View({0, 0}, size);
hasViewChanged = true;
}
void TrackingView::setSize(sf::Vector2f newSize)
{
// Is different?
auto size = getSize();
if (size.x == newSize.x && size.y == newSize.y)
{
// Nothing to do
return;
}
if (options.softResizeSpeed != 0) {
// Smooth out transition to new size
newSize = size + (newSize - size) * options.softResizeSpeed * FRAME_TIME.asSeconds();
}
view->setSize(newSize);
hasViewChanged = true;
}
sf::Vector2f TrackingView::getSize() const
{
return view->getSize();
}
sf::Vector2f TrackingView::getCenter() const
{
return view->getCenter();
}
void TrackingView::followTrackables()
{
auto trackingPoint = getTrackingArea().getCenter();
if (DEVELOPER_MODE)
{
marker->coordinates.set(IsometricCoordinates(trackingPoint));
}
// Calculate distance to target to check how to handle it
auto currentCenter = view->getCenter();
auto vectorToTarget = trackingPoint - currentCenter;
auto distanceToTarget = length(vectorToTarget);
if (distanceToTarget <= getRadius(options.freeMoveThreshold))
{
// Nothing to do
return;
}
// 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);
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) const
{
if (!DEVELOPER_MODE)
{
return;
}
marker->draw(window);
}
void TrackingView::addTrackable(ITrackable *trackable)
{
trackables.push_back(trackable);
}
void TrackingView::processTrackableStates()
{
// Remove trackables that have ended tracking
std::remove_if(trackables.begin(), trackables.end(), [](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;
}
void TrackingView::adjustSizeToTrackables()
{
// Calculate new view size
auto newViewSize = getTrackingArea().getSize();
newViewSize = applyPadding(newViewSize);
newViewSize = restrainToBounds(newViewSize);
// Extend view to match aspect ratio
auto currentViewSize = getSize();
auto aspectRatio = currentViewSize.x / currentViewSize.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 viewSize = getSize();
float smallestSide = std::min(viewSize.x, viewSize.y);
// Only half of the side, since we are calculating radius
return smallestSide / 2.f * threshold;
}