#include "tracking_view.h" #include "../../utilities/vector_utils.hpp" TrackingView::TrackingView(TrackingViewOptions options) : options(options), view(nullptr), hasViewChanged(false), trackables({}) {; marker = new CircleObject(DB_CIRCLE_RADIUS, 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; } 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; } sf::Vector2f TrackingView::getSize() const { return view->getSize(); } sf::Vector2f TrackingView::getWindowSize() const { auto size = Game::getInstance()->window->getSize(); return {static_cast(size.x), static_cast(size.y)}; } 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 delta) { if (options.softFollowSpeed != 0) { // Soft delta *= options.softFollowSpeed * FRAME_TIME.asSeconds(); } view->move(delta); hasViewChanged = true; } void TrackingView::draw(sf::RenderWindow *window) { if (!DEVELOPER_MODE) { return; } marker->draw(window); } void TrackingView::addTrackable(const std::shared_ptr& trackable) { trackables.push_back(trackable); } void TrackingView::processTrackableStates() { // Remove trackables that have ended tracking std::remove_if(trackables.begin(), trackables.end(), [](std::shared_ptr 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 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; }