Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pick object to follow when activity window is shown #1604

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 66 additions & 44 deletions libs/common/include/Point.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,50 @@
#include <limits>
#include <type_traits>

namespace detail {

template<class T>
struct type_identity
{
using type = T;
};

/// Convert the type T to a signed type if the condition is true (safe for float types)
template<bool cond, typename T>
using make_signed_if_t =
typename std::conditional_t<cond && !std::is_signed<T>::value, std::make_signed<T>, type_identity<T>>::type;

// clang-format off

/// Creates a mixed type out of types T and U which is
/// the larger type of T & U AND signed iff either is signed
/// Will be a floating point type if either T or U is floating point
template<typename T, typename U>
using mixed_type_t =
make_signed_if_t<
std::is_signed<T>::value || std::is_signed<U>::value,
typename std::conditional_t<
std::is_floating_point<T>::value == std::is_floating_point<U>::value, // both are FP or not FP?
std::conditional<(sizeof(T) > sizeof(U)), T, U>, // Take the larger type
std::conditional<std::is_floating_point<T>::value, T, U> // Take the floating point type
>::type
>;

template<typename T, typename U>
using IsNonLossyOp = std::integral_constant<bool,
// We can do T = T <op> U (except overflow) if:
std::is_floating_point<T>::value || std::is_signed<T>::value || std::is_unsigned<U>::value
>;

// clang-format on

template<typename T, typename U>
using require_nonLossyOp = std::enable_if_t<IsNonLossyOp<T, U>::value>;
template<typename T>
using require_arithmetic = std::enable_if_t<std::is_arithmetic<T>::value>;

} // namespace detail

/// Type for describing a 2D value (position, size, offset...)
/// Note: Combining a signed with an unsigned point will result in a signed type!
/// Allowed operations:
Expand Down Expand Up @@ -54,6 +98,28 @@ struct Point //-V690
static constexpr Point all(const T value) noexcept { return Point(value, value); }
constexpr bool isValid() const noexcept { return *this != Invalid(); }

/// Calculate the euclidean distance to the origin
constexpr auto distance() const noexcept { return std::hypot(x, y); }

/// Calculate the euclidean distance to another point
template<typename U, std::enable_if_t<!(std::is_unsigned<T>::value && std::is_unsigned<U>::value), int> = 0>
constexpr auto distance(const Point<U>& pt) const noexcept
{
using Res = ::detail::mixed_type_t<T, U>;
return std::hypot(static_cast<Res>(x) - static_cast<Res>(pt.x), static_cast<Res>(y) - static_cast<Res>(pt.y));
}

/// Calculate the euclidean distance to another point
template<typename U, std::enable_if_t<std::is_unsigned<T>::value && std::is_unsigned<U>::value, int> = 0>
constexpr auto distance(const Point<U>& pt) const noexcept
{
using Res = ::detail::mixed_type_t<T, U>;
const auto x1 = static_cast<Res>(x), x2 = static_cast<Res>(pt.x);
const auto y1 = static_cast<Res>(y), y2 = static_cast<Res>(pt.y);
// Calculate difference of usigned values without overflow
return std::hypot((x1 > x2 ? x1 - x2 : x2 - x1), (y1 > y2 ? y1 - y2 : y2 - y1));
}

constexpr bool operator==(const Point& second) const noexcept;
constexpr bool operator!=(const Point& second) const noexcept;

Expand Down Expand Up @@ -88,50 +154,6 @@ constexpr bool Point<T>::operator!=(const Point<T>& second) const noexcept
return !(*this == second);
}

namespace detail {

template<class T>
struct type_identity
{
using type = T;
};

/// Convert the type T to a signed type if the condition is true (safe for float types)
template<bool cond, typename T>
using make_signed_if_t =
typename std::conditional_t<cond && !std::is_signed<T>::value, std::make_signed<T>, type_identity<T>>::type;

// clang-format off

/// Creates a mixed type out of types T and U which is
/// the larger type of T & U AND signed iff either is signed
/// Will be a floating point type if either T or U is floating point
template<typename T, typename U>
using mixed_type_t =
make_signed_if_t<
std::is_signed<T>::value || std::is_signed<U>::value,
typename std::conditional_t<
std::is_floating_point<T>::value == std::is_floating_point<U>::value, // both are FP or not FP?
std::conditional<(sizeof(T) > sizeof(U)), T, U>, // Take the larger type
std::conditional<std::is_floating_point<T>::value, T, U> // Take the floating point type
>::type
>;

template<typename T, typename U>
using IsNonLossyOp = std::integral_constant<bool,
// We can do T = T <op> U (except overflow) if:
std::is_floating_point<T>::value || std::is_signed<T>::value || std::is_unsigned<U>::value
>;

// clang-format on

template<typename T, typename U>
using require_nonLossyOp = std::enable_if_t<IsNonLossyOp<T, U>::value>;
template<typename T>
using require_arithmetic = std::enable_if_t<std::is_arithmetic<T>::value>;

} // namespace detail

/// Compute the element wise minimum
template<typename T>
constexpr Point<T> elMin(const Point<T>& lhs, const Point<T>& rhs) noexcept
Expand Down
154 changes: 154 additions & 0 deletions libs/s25main/PickedMovableObject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#include "PickedMovableObject.h"
#include "desktops/dskGameInterface.h"
#include "drivers/VideoDriverWrapper.h"
#include "world/GameWorldBase.h"
#include "world/GameWorldView.h"
#include "nodeObjs/noMovable.h"
#include <cmath>

using namespace std::chrono_literals;

namespace {
constexpr unsigned PickRadius = 2;
constexpr unsigned TrackRadius = 5;
constexpr auto PickedObjectExpiration = 5s;

// Make (ab-)use of CheckPointsInRadius() easier
constexpr bool CheckPointsBreak = true;
constexpr bool CheckPointsContinue = false;
} // namespace

PickedMovableObject PickedMovableObject::pick(const GameWorldView& gwv, MapPoint mapPt, DrawPoint drawPt, bool expire)
{
// DEBUG REMOVE BEFORE MERGE
unsigned i = 1;

const auto offset = gwv.GetOffset();
const auto center = gwv.GetSize() / 2.f;
const auto zoomFactor = gwv.GetZoomFactor();
const auto& world = gwv.GetWorld();
const auto worldSize = world.GetSize() * DrawPoint(TR_W, TR_H);
auto minDistance = std::numeric_limits<float>::max();

PickedMovableObject pmo;

world.CheckPointsInRadius(
mapPt, PickRadius,
[&](MapPoint curPt, unsigned) {
if(gwv.GetViewer().GetVisibility(curPt) != Visibility::Visible)
return CheckPointsContinue;

DrawPoint curDrawPt = world.GetNodePos(curPt);
if(curDrawPt.x < offset.x)
curDrawPt.x += worldSize.x;
if(curDrawPt.y < offset.y)
curDrawPt.y += worldSize.y;
curDrawPt -= offset;
for(const noBase& obj : world.GetFigures(curPt))
{
const auto* movable = dynamic_cast<const noMovable*>(&obj);
if(!movable)
continue;

DrawPoint objDrawPt = curDrawPt;
if(movable->IsMoving())
objDrawPt += movable->CalcWalkingRelative();
objDrawPt = DrawPoint((objDrawPt - center) * zoomFactor + center);

// DEBUG REMOVE BEFORE MERGE
dskGameInterface::SetDebugPoint(i++, objDrawPt, 8, 4, MakeColor(255, 255, 0, 255));

auto diff = Point<float>(objDrawPt - drawPt);
float distance = std::sqrt(diff.x * diff.x + diff.y * diff.y);
falbrechtskirchinger marked this conversation as resolved.
Show resolved Hide resolved
if(distance < minDistance)
{
pmo.id_ = movable->GetObjId();
pmo.mapPt_ = curPt;
pmo.drawPt_ = objDrawPt; // TODO Do we need the unwrapped point here or was this always wrong?
minDistance = distance;
}
}

return CheckPointsContinue;
},
true);

if(pmo.isValid() && expire)
pmo.expiration_.start();

return pmo;
}

PickedMovableObject PickedMovableObject::pickAtCursor(const GameWorldView& gwv, bool expire)
{
return pick(gwv, gwv.GetSelectedPt(), DrawPoint(VIDEODRIVER.GetMousePos()), expire);
}

PickedMovableObject PickedMovableObject::pickAtViewCenter(const GameWorldView& gwv, bool expire)
{
const auto centerMapPt = gwv.GetWorld().MakeMapPoint((gwv.GetFirstPt() + gwv.GetLastPt()) / 2);
const auto centerDrawPt = DrawPoint(gwv.GetSize() / 2u);
return pick(gwv, centerMapPt, centerDrawPt, expire);
}

bool PickedMovableObject::isValid() const
{
return id_ != 0 && (!expiration_.isRunning() || expiration_.getElapsed() < PickedObjectExpiration);
}

void PickedMovableObject::cancelExpiration()
{
expiration_.stop();
}

void PickedMovableObject::invalidate()
{
id_ = 0;
}

bool PickedMovableObject::track(const GameWorldView& gwv)
{
if(!isValid())
return false;

const auto& world = gwv.GetWorld();
const auto success = world.CheckPointsInRadius(
mapPt_, TrackRadius,
[&](MapPoint curPt, unsigned) {
if(gwv.GetViewer().GetVisibility(curPt) != Visibility::Visible)
return CheckPointsContinue;

for(const noBase& obj : world.GetFigures(curPt))
{
if(obj.GetObjId() != id_)
continue;

const auto& movable = dynamic_cast<const noMovable&>(obj);
mapPt_ = curPt;
drawPt_ = world.GetNodePos(curPt);
if(movable.IsMoving())
drawPt_ += movable.CalcWalkingRelative();

return CheckPointsBreak;
}
return CheckPointsContinue;
},
true);

if(!success)
invalidate();

return success;
}

bool PickedMovableObject::track(GameWorldView& gwv, bool moveTo)
{
const auto success = track(gwv);
if(success && moveTo)
gwv.MoveTo(drawPt_ - gwv.GetSize() / 2u);
return success;
}
36 changes: 36 additions & 0 deletions libs/s25main/PickedMovableObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (C) 2005 - 2023 Settlers Freaks (sf-team at siedler25.org)
//
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "DrawPoint.h"
#include "Timer.h"
#include "gameTypes/MapCoordinates.h"

class GameWorldView;

class PickedMovableObject
{
public:
static PickedMovableObject pick(const GameWorldView& gwv, MapPoint mapPt, DrawPoint drawPt, bool expire);
static PickedMovableObject pickAtCursor(const GameWorldView& gwv, bool expire);
static PickedMovableObject pickAtViewCenter(const GameWorldView& gwv, bool expire);

PickedMovableObject() = default;

unsigned id() const { return id_; }
bool isValid() const;

void cancelExpiration();
void invalidate();

bool track(const GameWorldView& gwv);
bool track(GameWorldView& gwv, bool moveTo);

private:
unsigned id_ = 0;
MapPoint mapPt_{};
DrawPoint drawPt_{};
Timer expiration_{};
};
14 changes: 14 additions & 0 deletions libs/s25main/Window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,20 @@ void Window::DrawLine(DrawPoint pt1, DrawPoint pt2, unsigned short width, unsign
VIDEODRIVER.GetRenderer()->DrawLine(pt1, pt2, width, color);
}

// DEBUG REMOVE BEFORE MERGE
void Window::DrawCross(DrawPoint pt, unsigned short length, unsigned short width, unsigned color)
{
auto* renderer = VIDEODRIVER.GetRenderer();

DrawPoint pt1 = pt - DrawPoint(-length, length);
DrawPoint pt2 = pt - DrawPoint(length, -length);
DrawPoint pt3 = pt - DrawPoint(length, length);
DrawPoint pt4 = pt - DrawPoint(-length, -length);
falbrechtskirchinger marked this conversation as resolved.
Show resolved Hide resolved

renderer->DrawLine(pt1, pt2, width, color);
renderer->DrawLine(pt3, pt4, width, color);
}

void Window::Msg_PaintBefore()
{
animations_.update(VIDEODRIVER.GetTickCount());
Expand Down
2 changes: 2 additions & 0 deletions libs/s25main/Window.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ class Window
static void DrawRectangle(const Rect& rect, unsigned color);
/// Zeichnet eine Linie
static void DrawLine(DrawPoint pt1, DrawPoint pt2, unsigned short width, unsigned color);
// DEBUG REMOVE BEFORE MERGE
static void DrawCross(DrawPoint pt, unsigned short length, unsigned short width, unsigned color);

// GUI-Notify-Messages

Expand Down
Loading