mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-11 10:02:27 +01:00
2061 lines
68 KiB
C++
2061 lines
68 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2025 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "Viewport.h"
|
|
|
|
#include "../Context.h"
|
|
#include "../Diagnostic.h"
|
|
#include "../Game.h"
|
|
#include "../GameState.h"
|
|
#include "../Input.h"
|
|
#include "../OpenRCT2.h"
|
|
#include "../config/Config.h"
|
|
#include "../core/Guard.hpp"
|
|
#include "../core/JobPool.h"
|
|
#include "../core/Numerics.hpp"
|
|
#include "../drawing/Drawing.h"
|
|
#include "../drawing/IDrawingEngine.h"
|
|
#include "../entity/EntityList.h"
|
|
#include "../entity/Guest.h"
|
|
#include "../entity/PatrolArea.h"
|
|
#include "../entity/Staff.h"
|
|
#include "../interface/Cursors.h"
|
|
#include "../object/LargeSceneryEntry.h"
|
|
#include "../object/SmallSceneryEntry.h"
|
|
#include "../object/WallSceneryEntry.h"
|
|
#include "../paint/Paint.h"
|
|
#include "../profiling/Profiling.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/RideData.h"
|
|
#include "../ride/TrackDesign.h"
|
|
#include "../ride/Vehicle.h"
|
|
#include "../ui/WindowManager.h"
|
|
#include "../world/Climate.h"
|
|
#include "../world/Map.h"
|
|
#include "../world/tile_element/LargeSceneryElement.h"
|
|
#include "../world/tile_element/SmallSceneryElement.h"
|
|
#include "../world/tile_element/TileElement.h"
|
|
#include "../world/tile_element/WallElement.h"
|
|
#include "Colour.h"
|
|
#include "Window.h"
|
|
#include "Window_internal.h"
|
|
|
|
#include <cstring>
|
|
#include <list>
|
|
#include <unordered_map>
|
|
|
|
namespace OpenRCT2
|
|
{
|
|
using namespace OpenRCT2::Numerics;
|
|
|
|
enum : uint8_t
|
|
{
|
|
IMAGE_TYPE_DEFAULT = 0,
|
|
IMAGE_TYPE_REMAP = (1 << 1),
|
|
IMAGE_TYPE_TRANSPARENT = (1 << 2),
|
|
};
|
|
|
|
uint8_t gShowGridLinesRefCount;
|
|
uint8_t gShowLandRightsRefCount;
|
|
uint8_t gShowConstructionRightsRefCount;
|
|
|
|
static std::list<Viewport> _viewports;
|
|
Viewport* g_music_tracking_viewport;
|
|
|
|
static std::unique_ptr<JobPool> _paintJobs;
|
|
static std::vector<PaintSession*> _paintColumns;
|
|
|
|
InteractionInfo::InteractionInfo(const PaintStruct* ps)
|
|
: Loc(ps->MapPos)
|
|
, Element(ps->Element)
|
|
, Entity(ps->Entity)
|
|
, interactionType(ps->InteractionItem)
|
|
{
|
|
}
|
|
|
|
static void ViewportPaintWeatherGloom(DrawPixelInfo& dpi);
|
|
static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi);
|
|
static void ViewportUpdateFollowSprite(WindowBase* window);
|
|
static void ViewportUpdateSmartFollowEntity(WindowBase* window);
|
|
static void ViewportUpdateSmartFollowStaff(WindowBase* window, const Staff& peep);
|
|
static void ViewportUpdateSmartFollowVehicle(WindowBase* window);
|
|
static void ViewportInvalidate(const Viewport* viewport, const ScreenRect& screenRect);
|
|
|
|
/**
|
|
* This is not a viewport function. It is used to setup many variables for
|
|
* multiple things.
|
|
* rct2: 0x006E6EAC
|
|
*/
|
|
void ViewportInitAll()
|
|
{
|
|
if (!gOpenRCT2NoGraphics)
|
|
{
|
|
ColoursInitMaps();
|
|
}
|
|
|
|
WindowInitAll();
|
|
|
|
// ?
|
|
InputResetFlags();
|
|
InputSetState(InputState::Reset);
|
|
gPressedWidget.window_classification = WindowClass::Null;
|
|
gPickupPeepImage = ImageId();
|
|
ResetTooltipNotShown();
|
|
gMapSelectFlags = 0;
|
|
ClearPatrolAreaToRender();
|
|
TextinputCancel();
|
|
}
|
|
|
|
/**
|
|
* Converts between 3d point of a sprite to 2d coordinates for centring on that
|
|
* sprite
|
|
* rct2: 0x006EB0C1
|
|
* x : ax
|
|
* y : bx
|
|
* z : cx
|
|
* out_x : ax
|
|
* out_y : bx
|
|
*/
|
|
std::optional<ScreenCoordsXY> centre_2d_coordinates(const CoordsXYZ& loc, Viewport* viewport)
|
|
{
|
|
// If the start location was invalid
|
|
// propagate the invalid location to the output.
|
|
// This fixes a bug that caused the game to enter an infinite loop.
|
|
if (loc.IsNull())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto screenCoord = Translate3DTo2DWithZ(viewport->rotation, loc);
|
|
screenCoord.x -= viewport->ViewWidth() / 2;
|
|
screenCoord.y -= viewport->ViewHeight() / 2;
|
|
return { screenCoord };
|
|
}
|
|
|
|
CoordsXYZ Focus::GetPos() const
|
|
{
|
|
return std::visit(
|
|
[](auto&& arg) {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
if constexpr (std::is_same_v<T, Focus::CoordinateFocus>)
|
|
return arg;
|
|
else if constexpr (std::is_same_v<T, Focus::EntityFocus>)
|
|
{
|
|
auto* centreEntity = GetEntity(arg);
|
|
if (centreEntity != nullptr)
|
|
{
|
|
return CoordsXYZ{ centreEntity->x, centreEntity->y, centreEntity->z };
|
|
}
|
|
else
|
|
{
|
|
LOG_ERROR("Invalid entity for focus.");
|
|
return CoordsXYZ{};
|
|
}
|
|
}
|
|
},
|
|
data);
|
|
}
|
|
|
|
/**
|
|
* Viewport will look at sprite or at coordinates as specified in flags 0b_1X
|
|
* for sprite 0b_0X for coordinates
|
|
*
|
|
* rct2: 0x006EB009
|
|
* x: ax
|
|
* y: eax (top 16)
|
|
* width: bx
|
|
* height: ebx (top 16)
|
|
* zoom: cl (8 bits)
|
|
* centre_x: edx lower 16 bits
|
|
* centre_y: edx upper 16 bits
|
|
* centre_z: ecx upper 16 bits
|
|
* sprite: edx lower 16 bits
|
|
* flags: edx top most 2 bits 0b_X1 for zoom clear see below for 2nd bit.
|
|
* w: esi
|
|
*/
|
|
void ViewportCreate(WindowBase* w, const ScreenCoordsXY& screenCoords, int32_t width, int32_t height, const Focus& focus)
|
|
{
|
|
Viewport* viewport = nullptr;
|
|
if (_viewports.size() >= kMaxViewportCount)
|
|
{
|
|
LOG_ERROR("No more viewport slots left to allocate.");
|
|
return;
|
|
}
|
|
|
|
auto itViewport = _viewports.insert(_viewports.end(), Viewport{});
|
|
|
|
viewport = &*itViewport;
|
|
viewport->pos = screenCoords;
|
|
viewport->width = width;
|
|
viewport->height = height;
|
|
|
|
const auto zoom = focus.zoom;
|
|
viewport->zoom = zoom;
|
|
viewport->flags = 0;
|
|
viewport->rotation = GetCurrentRotation();
|
|
|
|
if (Config::Get().general.AlwaysShowGridlines)
|
|
viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
|
|
w->viewport = viewport;
|
|
|
|
CoordsXYZ centrePos = focus.GetPos();
|
|
w->viewport_target_sprite = std::visit(
|
|
[](auto&& arg) {
|
|
using T = std::decay_t<decltype(arg)>;
|
|
if constexpr (std::is_same_v<T, Focus::CoordinateFocus>)
|
|
return EntityId::GetNull();
|
|
else if constexpr (std::is_same_v<T, Focus::EntityFocus>)
|
|
return arg;
|
|
},
|
|
focus.data);
|
|
|
|
auto centreLoc = centre_2d_coordinates(centrePos, viewport);
|
|
if (!centreLoc.has_value())
|
|
{
|
|
LOG_ERROR("Invalid location for viewport.");
|
|
return;
|
|
}
|
|
w->savedViewPos = *centreLoc;
|
|
viewport->viewPos = *centreLoc;
|
|
}
|
|
|
|
void ViewportRemove(Viewport* viewport)
|
|
{
|
|
auto it = std::find_if(_viewports.begin(), _viewports.end(), [viewport](const auto& vp) { return &vp == viewport; });
|
|
if (it == _viewports.end())
|
|
{
|
|
LOG_ERROR("Unable to remove viewport: %p", viewport);
|
|
return;
|
|
}
|
|
_viewports.erase(it);
|
|
}
|
|
|
|
static Viewport* ViewportGetMain()
|
|
{
|
|
auto mainWindow = WindowGetMain();
|
|
if (mainWindow == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
return mainWindow->viewport;
|
|
}
|
|
|
|
void ViewportsInvalidate(int32_t x, int32_t y, int32_t z0, int32_t z1, ZoomLevel maxZoom)
|
|
{
|
|
for (auto& vp : _viewports)
|
|
{
|
|
if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom })
|
|
{
|
|
int32_t x1, y1, x2, y2;
|
|
|
|
x += 16;
|
|
y += 16;
|
|
auto screenCoord = Translate3DTo2DWithZ(vp.rotation, CoordsXYZ{ x, y, 0 });
|
|
|
|
x1 = screenCoord.x - 32;
|
|
y1 = screenCoord.y - 32 - z1;
|
|
x2 = screenCoord.x + 32;
|
|
y2 = screenCoord.y + 32 - z0;
|
|
|
|
ViewportInvalidate(&vp, ScreenRect{ { x1, y1 }, { x2, y2 } });
|
|
}
|
|
}
|
|
}
|
|
|
|
void ViewportsInvalidate(const CoordsXYZ& pos, int32_t width, int32_t minHeight, int32_t maxHeight, ZoomLevel maxZoom)
|
|
{
|
|
for (auto& vp : _viewports)
|
|
{
|
|
if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom })
|
|
{
|
|
auto screenCoords = Translate3DTo2DWithZ(vp.rotation, pos);
|
|
auto screenPos = ScreenRect(
|
|
screenCoords - ScreenCoordsXY{ width, minHeight }, screenCoords + ScreenCoordsXY{ width, maxHeight });
|
|
|
|
ViewportInvalidate(&vp, screenPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ViewportsInvalidate(const ScreenRect& screenRect, ZoomLevel maxZoom)
|
|
{
|
|
for (auto& vp : _viewports)
|
|
{
|
|
if (maxZoom == ZoomLevel{ -1 } || vp.zoom <= ZoomLevel{ maxZoom })
|
|
{
|
|
ViewportInvalidate(&vp, screenRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00689174
|
|
* edx is assumed to be (and always is) the current rotation, so it is not
|
|
* needed as parameter.
|
|
*/
|
|
CoordsXYZ ViewportAdjustForMapHeight(const ScreenCoordsXY& startCoords, uint8_t rotation)
|
|
{
|
|
int32_t height = 0;
|
|
|
|
CoordsXY pos{};
|
|
for (int32_t i = 0; i < 6; i++)
|
|
{
|
|
pos = ViewportPosToMapPos(startCoords, height, rotation);
|
|
height = TileElementHeight(pos);
|
|
|
|
// HACK: This is to prevent the x and y values being set to values outside
|
|
// of the map. This can happen when the height is larger than the map size.
|
|
auto mapSizeMinus2 = GetMapSizeMinus2();
|
|
if (pos.x > mapSizeMinus2.x && pos.y > mapSizeMinus2.y)
|
|
{
|
|
static constexpr CoordsXY kCorr[] = {
|
|
{ -1, -1 },
|
|
{ 1, -1 },
|
|
{ 1, 1 },
|
|
{ -1, 1 },
|
|
};
|
|
pos.x += kCorr[rotation].x * height;
|
|
pos.y += kCorr[rotation].y * height;
|
|
}
|
|
}
|
|
|
|
return { pos, height };
|
|
}
|
|
|
|
/*
|
|
* rct2: 0x006E7FF3
|
|
*/
|
|
static void ViewportRedrawAfterShift(
|
|
DrawPixelInfo& dpi, WindowBase* window, const WindowBase* originalWindow, const ScreenCoordsXY shift,
|
|
const ScreenRect& drawRect)
|
|
{
|
|
// sub-divide by intersecting windows
|
|
if (window != nullptr)
|
|
{
|
|
// skip current window and non-intersecting windows
|
|
if (window == originalWindow || drawRect.GetRight() <= window->windowPos.x
|
|
|| drawRect.GetLeft() >= window->windowPos.x + window->width || drawRect.GetBottom() <= window->windowPos.y
|
|
|| drawRect.GetTop() >= window->windowPos.y + window->height)
|
|
{
|
|
auto itWindowPos = WindowGetIterator(window);
|
|
auto itNextWindow = itWindowPos != g_window_list.end() ? std::next(itWindowPos) : g_window_list.end();
|
|
ViewportRedrawAfterShift(
|
|
dpi, itNextWindow == g_window_list.end() ? nullptr : itNextWindow->get(), originalWindow, shift, drawRect);
|
|
return;
|
|
}
|
|
|
|
if (drawRect.GetLeft() < window->windowPos.x)
|
|
{
|
|
ScreenRect leftRect = { drawRect.Point1, { window->windowPos.x, drawRect.GetBottom() } };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, leftRect);
|
|
|
|
ScreenRect rightRect = { { window->windowPos.x, drawRect.GetTop() }, drawRect.Point2 };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, rightRect);
|
|
}
|
|
else if (drawRect.GetRight() > window->windowPos.x + window->width)
|
|
{
|
|
ScreenRect leftRect = { drawRect.Point1, { window->windowPos.x + window->width, drawRect.GetBottom() } };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, leftRect);
|
|
|
|
ScreenRect rightRect = { { window->windowPos.x + window->width, drawRect.GetTop() }, drawRect.Point2 };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, rightRect);
|
|
}
|
|
else if (drawRect.GetTop() < window->windowPos.y)
|
|
{
|
|
ScreenRect topRect = { drawRect.Point1, { drawRect.GetRight(), window->windowPos.y } };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, topRect);
|
|
|
|
ScreenRect bottomRect = { { drawRect.GetLeft(), window->windowPos.y }, drawRect.Point2 };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, bottomRect);
|
|
}
|
|
else if (drawRect.GetBottom() > window->windowPos.y + window->height)
|
|
{
|
|
ScreenRect topRect = { drawRect.Point1, { drawRect.GetRight(), window->windowPos.y + window->height } };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, topRect);
|
|
|
|
ScreenRect bottomRect = { { drawRect.GetLeft(), window->windowPos.y + window->height }, drawRect.Point2 };
|
|
ViewportRedrawAfterShift(dpi, window, originalWindow, shift, bottomRect);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto left = drawRect.GetLeft();
|
|
auto right = drawRect.GetRight();
|
|
auto top = drawRect.GetTop();
|
|
auto bottom = drawRect.GetBottom();
|
|
|
|
// if moved more than the draw rectangle size
|
|
if (abs(shift.x) < drawRect.GetWidth() && abs(shift.y) < drawRect.GetHeight())
|
|
{
|
|
// update whole block ?
|
|
DrawingEngineCopyRect(
|
|
drawRect.GetLeft(), drawRect.GetTop(), drawRect.GetWidth(), drawRect.GetHeight(), shift.x, shift.y);
|
|
|
|
if (shift.x > 0)
|
|
{
|
|
// draw left
|
|
auto _right = left + shift.x;
|
|
WindowDrawAll(dpi, left, top, _right, bottom);
|
|
left += shift.x;
|
|
}
|
|
else if (shift.x < 0)
|
|
{
|
|
// draw right
|
|
auto _left = right + shift.x;
|
|
WindowDrawAll(dpi, _left, top, right, bottom);
|
|
right += shift.x;
|
|
}
|
|
|
|
if (shift.y > 0)
|
|
{
|
|
// draw top
|
|
bottom = top + shift.y;
|
|
WindowDrawAll(dpi, left, top, right, bottom);
|
|
}
|
|
else if (shift.y < 0)
|
|
{
|
|
// draw bottom
|
|
top = bottom + shift.y;
|
|
WindowDrawAll(dpi, left, top, right, bottom);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// redraw whole draw rectangle
|
|
WindowDrawAll(dpi, left, top, right, bottom);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ViewportShiftPixels(DrawPixelInfo& dpi, WindowBase* window, Viewport* viewport, int32_t x_diff, int32_t y_diff)
|
|
{
|
|
// This loop redraws all parts covered by transparent windows.
|
|
auto it = WindowGetIterator(window);
|
|
for (; it != g_window_list.end(); it++)
|
|
{
|
|
auto w = it->get();
|
|
if (!(w->flags & WF_TRANSPARENT))
|
|
continue;
|
|
if (w->viewport == viewport)
|
|
continue;
|
|
|
|
if (viewport->pos.x + viewport->width <= w->windowPos.x)
|
|
continue;
|
|
if (w->windowPos.x + w->width <= viewport->pos.x)
|
|
continue;
|
|
|
|
if (viewport->pos.y + viewport->height <= w->windowPos.y)
|
|
continue;
|
|
if (w->windowPos.y + w->height <= viewport->pos.y)
|
|
continue;
|
|
|
|
auto left = w->windowPos.x;
|
|
auto right = w->windowPos.x + w->width;
|
|
auto top = w->windowPos.y;
|
|
auto bottom = w->windowPos.y + w->height;
|
|
|
|
if (left < viewport->pos.x)
|
|
left = viewport->pos.x;
|
|
if (right > viewport->pos.x + viewport->width)
|
|
right = viewport->pos.x + viewport->width;
|
|
|
|
if (top < viewport->pos.y)
|
|
top = viewport->pos.y;
|
|
if (bottom > viewport->pos.y + viewport->height)
|
|
bottom = viewport->pos.y + viewport->height;
|
|
|
|
if (left >= right)
|
|
continue;
|
|
if (top >= bottom)
|
|
continue;
|
|
|
|
WindowDrawAll(dpi, left, top, right, bottom);
|
|
}
|
|
|
|
ViewportRedrawAfterShift(
|
|
dpi, window, window, { x_diff, y_diff },
|
|
{ viewport->pos, { viewport->pos.x + viewport->width, viewport->pos.y + viewport->height } });
|
|
}
|
|
|
|
static void ViewportMove(const ScreenCoordsXY& coords, WindowBase* w, Viewport* viewport)
|
|
{
|
|
auto zoom = viewport->zoom;
|
|
|
|
// Note: do not do the subtraction and then divide!
|
|
// Note: Due to arithmetic shift != /zoom a shift will have to be used
|
|
// hopefully when 0x006E7FF3 is finished this can be converted to /zoom.
|
|
auto x_diff = viewport->zoom.ApplyInversedTo(viewport->viewPos.x) - viewport->zoom.ApplyInversedTo(coords.x);
|
|
auto y_diff = viewport->zoom.ApplyInversedTo(viewport->viewPos.y) - viewport->zoom.ApplyInversedTo(coords.y);
|
|
|
|
viewport->viewPos = coords;
|
|
|
|
// If no change in viewing area
|
|
if ((!x_diff) && (!y_diff))
|
|
return;
|
|
|
|
if (w->flags & WF_7)
|
|
{
|
|
int32_t left = std::max<int32_t>(viewport->pos.x, 0);
|
|
int32_t top = std::max<int32_t>(viewport->pos.y, 0);
|
|
int32_t right = std::min<int32_t>(viewport->pos.x + viewport->width, ContextGetWidth());
|
|
int32_t bottom = std::min<int32_t>(viewport->pos.y + viewport->height, ContextGetHeight());
|
|
|
|
if (left >= right)
|
|
return;
|
|
if (top >= bottom)
|
|
return;
|
|
|
|
if (DrawingEngineHasDirtyOptimisations())
|
|
{
|
|
DrawPixelInfo& dpi = DrawingEngineGetDpi();
|
|
WindowDrawAll(dpi, left, top, right, bottom);
|
|
return;
|
|
}
|
|
}
|
|
|
|
Viewport view_copy = *viewport;
|
|
|
|
if (viewport->pos.x < 0)
|
|
{
|
|
viewport->width += viewport->pos.x;
|
|
viewport->viewPos.x -= zoom.ApplyTo(viewport->pos.x);
|
|
viewport->pos.x = 0;
|
|
}
|
|
|
|
int32_t eax = viewport->pos.x + viewport->width - ContextGetWidth();
|
|
if (eax > 0)
|
|
{
|
|
viewport->width -= eax;
|
|
}
|
|
|
|
if (viewport->width <= 0)
|
|
{
|
|
*viewport = view_copy;
|
|
return;
|
|
}
|
|
|
|
if (viewport->pos.y < 0)
|
|
{
|
|
viewport->height += viewport->pos.y;
|
|
viewport->viewPos.y -= zoom.ApplyTo(viewport->pos.y);
|
|
viewport->pos.y = 0;
|
|
}
|
|
|
|
eax = viewport->pos.y + viewport->height - ContextGetHeight();
|
|
if (eax > 0)
|
|
{
|
|
viewport->height -= eax;
|
|
}
|
|
|
|
if (viewport->height <= 0)
|
|
{
|
|
*viewport = view_copy;
|
|
return;
|
|
}
|
|
|
|
if (DrawingEngineHasDirtyOptimisations())
|
|
{
|
|
DrawPixelInfo& dpi = DrawingEngineGetDpi();
|
|
ViewportShiftPixels(dpi, w, viewport, x_diff, y_diff);
|
|
}
|
|
|
|
*viewport = view_copy;
|
|
}
|
|
|
|
// rct2: 0x006E7A15
|
|
static void ViewportSetUndergroundFlag(int32_t underground, WindowBase* window, Viewport* viewport)
|
|
{
|
|
if (window->classification != WindowClass::MainWindow
|
|
|| (window->classification == WindowClass::MainWindow && !window->viewport_smart_follow_sprite.IsNull()))
|
|
{
|
|
if (!underground)
|
|
{
|
|
int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
viewport->flags &= ~VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
if (!bit)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int32_t bit = viewport->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
viewport->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
if (bit)
|
|
return;
|
|
}
|
|
window->Invalidate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006E7A3A
|
|
*/
|
|
void ViewportUpdatePosition(WindowBase* window)
|
|
{
|
|
window->OnResize();
|
|
|
|
Viewport* viewport = window->viewport;
|
|
if (viewport == nullptr)
|
|
return;
|
|
|
|
if (!window->viewport_smart_follow_sprite.IsNull())
|
|
{
|
|
ViewportUpdateSmartFollowEntity(window);
|
|
}
|
|
|
|
if (!window->viewport_target_sprite.IsNull())
|
|
{
|
|
ViewportUpdateFollowSprite(window);
|
|
return;
|
|
}
|
|
|
|
ViewportSetUndergroundFlag(0, window, viewport);
|
|
|
|
auto viewportMidPoint = ScreenCoordsXY{ window->savedViewPos.x + viewport->ViewWidth() / 2,
|
|
window->savedViewPos.y + viewport->ViewHeight() / 2 };
|
|
|
|
auto mapCoord = ViewportPosToMapPos(viewportMidPoint, 0, viewport->rotation);
|
|
|
|
// Clamp to the map minimum value
|
|
int32_t at_map_edge = 0;
|
|
if (mapCoord.x < kMapMinimumXY)
|
|
{
|
|
mapCoord.x = kMapMinimumXY;
|
|
at_map_edge = 1;
|
|
}
|
|
if (mapCoord.y < kMapMinimumXY)
|
|
{
|
|
mapCoord.y = kMapMinimumXY;
|
|
at_map_edge = 1;
|
|
}
|
|
|
|
// Clamp to the map maximum value (scenario specific)
|
|
auto mapSizeMinus2 = GetMapSizeMinus2();
|
|
if (mapCoord.x > mapSizeMinus2.x)
|
|
{
|
|
mapCoord.x = mapSizeMinus2.x;
|
|
at_map_edge = 1;
|
|
}
|
|
if (mapCoord.y > mapSizeMinus2.y)
|
|
{
|
|
mapCoord.y = mapSizeMinus2.y;
|
|
at_map_edge = 1;
|
|
}
|
|
|
|
if (at_map_edge)
|
|
{
|
|
auto centreLoc = centre_2d_coordinates({ mapCoord, 0 }, viewport);
|
|
if (centreLoc.has_value())
|
|
{
|
|
window->savedViewPos = centreLoc.value();
|
|
}
|
|
}
|
|
|
|
auto windowCoords = window->savedViewPos;
|
|
if (window->flags & WF_SCROLLING_TO_LOCATION)
|
|
{
|
|
// Moves the viewport if focusing in on an item
|
|
uint8_t flags = 0;
|
|
windowCoords.x -= viewport->viewPos.x;
|
|
if (windowCoords.x < 0)
|
|
{
|
|
windowCoords.x = -windowCoords.x;
|
|
flags |= 1;
|
|
}
|
|
windowCoords.y -= viewport->viewPos.y;
|
|
if (windowCoords.y < 0)
|
|
{
|
|
windowCoords.y = -windowCoords.y;
|
|
flags |= 2;
|
|
}
|
|
windowCoords.x = (windowCoords.x + 7) / 8;
|
|
windowCoords.y = (windowCoords.y + 7) / 8;
|
|
|
|
// If we are at the final zoom position
|
|
if (!windowCoords.x && !windowCoords.y)
|
|
{
|
|
window->flags &= ~WF_SCROLLING_TO_LOCATION;
|
|
}
|
|
if (flags & 1)
|
|
{
|
|
windowCoords.x = -windowCoords.x;
|
|
}
|
|
if (flags & 2)
|
|
{
|
|
windowCoords.y = -windowCoords.y;
|
|
}
|
|
windowCoords.x += viewport->viewPos.x;
|
|
windowCoords.y += viewport->viewPos.y;
|
|
}
|
|
|
|
ViewportMove(windowCoords, window, viewport);
|
|
}
|
|
|
|
void ViewportUpdateFollowSprite(WindowBase* window)
|
|
{
|
|
if (!window->viewport_target_sprite.IsNull() && window->viewport != nullptr)
|
|
{
|
|
auto* sprite = GetEntity(window->viewport_target_sprite);
|
|
if (sprite == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gLegacyScene != LegacyScene::titleSequence)
|
|
{
|
|
int32_t height = (TileElementHeight({ sprite->x, sprite->y }))-16;
|
|
int32_t underground = sprite->z < height;
|
|
ViewportSetUndergroundFlag(underground, window, window->viewport);
|
|
}
|
|
|
|
auto centreLoc = centre_2d_coordinates(sprite->GetLocation(), window->viewport);
|
|
if (centreLoc.has_value())
|
|
{
|
|
window->savedViewPos = *centreLoc;
|
|
ViewportMove(*centreLoc, window, window->viewport);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ViewportUpdateSmartFollowEntity(WindowBase* window)
|
|
{
|
|
auto entity = TryGetEntity(window->viewport_smart_follow_sprite);
|
|
if (entity == nullptr || entity->Type == EntityType::Null)
|
|
{
|
|
window->viewport_smart_follow_sprite = EntityId::GetNull();
|
|
window->viewport_target_sprite = EntityId::GetNull();
|
|
return;
|
|
}
|
|
|
|
switch (entity->Type)
|
|
{
|
|
case EntityType::Vehicle:
|
|
ViewportUpdateSmartFollowVehicle(window);
|
|
break;
|
|
|
|
case EntityType::Guest:
|
|
{
|
|
auto* guest = entity->As<Guest>();
|
|
if (guest == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
ViewportUpdateSmartFollowGuest(window, *guest);
|
|
break;
|
|
}
|
|
case EntityType::Staff:
|
|
{
|
|
auto* staff = entity->As<Staff>();
|
|
if (staff == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
ViewportUpdateSmartFollowStaff(window, *staff);
|
|
break;
|
|
}
|
|
default: // All other types don't need any "smart" following; steam particle, duck, money effect, etc.
|
|
window->focus = Focus(window->viewport_smart_follow_sprite);
|
|
window->viewport_target_sprite = window->viewport_smart_follow_sprite;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ViewportUpdateSmartFollowGuest(WindowBase* window, const Guest& peep)
|
|
{
|
|
Focus focus = Focus(peep.Id);
|
|
window->viewport_target_sprite = peep.Id;
|
|
|
|
if (peep.State == PeepState::Picked)
|
|
{
|
|
window->viewport_smart_follow_sprite = EntityId::GetNull();
|
|
window->viewport_target_sprite = EntityId::GetNull();
|
|
window->focus = std::nullopt; // No focus
|
|
return;
|
|
}
|
|
|
|
bool overallFocus = true;
|
|
if (peep.State == PeepState::OnRide || peep.State == PeepState::EnteringRide
|
|
|| (peep.State == PeepState::LeavingRide && peep.x == kLocationNull))
|
|
{
|
|
auto ride = GetRide(peep.CurrentRide);
|
|
if (ride != nullptr && (ride->lifecycleFlags & RIDE_LIFECYCLE_ON_TRACK))
|
|
{
|
|
auto train = GetEntity<Vehicle>(ride->vehicles[peep.CurrentTrain]);
|
|
if (train != nullptr)
|
|
{
|
|
const auto car = train->GetCar(peep.CurrentCar);
|
|
if (car != nullptr)
|
|
{
|
|
focus = Focus(car->Id);
|
|
overallFocus = false;
|
|
window->viewport_target_sprite = car->Id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (peep.x == kLocationNull && overallFocus)
|
|
{
|
|
auto ride = GetRide(peep.CurrentRide);
|
|
if (ride != nullptr)
|
|
{
|
|
auto xy = ride->overallView.ToTileCentre();
|
|
CoordsXYZ coordFocus;
|
|
coordFocus.x = xy.x;
|
|
coordFocus.y = xy.y;
|
|
coordFocus.z = TileElementHeight(xy) + (4 * kCoordsZStep);
|
|
focus = Focus(coordFocus);
|
|
window->viewport_target_sprite = EntityId::GetNull();
|
|
}
|
|
}
|
|
|
|
window->focus = focus;
|
|
}
|
|
|
|
void ViewportUpdateSmartFollowStaff(WindowBase* window, const Staff& peep)
|
|
{
|
|
if (peep.State == PeepState::Picked)
|
|
{
|
|
window->viewport_smart_follow_sprite = EntityId::GetNull();
|
|
window->viewport_target_sprite = EntityId::GetNull();
|
|
window->focus = std::nullopt;
|
|
return;
|
|
}
|
|
|
|
window->focus = Focus(window->viewport_smart_follow_sprite);
|
|
window->viewport_target_sprite = window->viewport_smart_follow_sprite;
|
|
}
|
|
|
|
void ViewportUpdateSmartFollowVehicle(WindowBase* window)
|
|
{
|
|
window->focus = Focus(window->viewport_smart_follow_sprite);
|
|
window->viewport_target_sprite = window->viewport_smart_follow_sprite;
|
|
}
|
|
|
|
static void ViewportRotateSingleInternal(WindowBase& w, int32_t direction)
|
|
{
|
|
auto* viewport = w.viewport;
|
|
if (viewport == nullptr)
|
|
return;
|
|
|
|
auto windowPos = ScreenCoordsXY{ (viewport->width >> 1), (viewport->height >> 1) } + viewport->pos;
|
|
|
|
// has something to do with checking if middle of the viewport is obstructed
|
|
Viewport* other;
|
|
auto mapXYCoords = ScreenGetMapXY(windowPos, &other);
|
|
CoordsXYZ coords{};
|
|
|
|
// other != viewport probably triggers on viewports in ride or guest window?
|
|
// mapXYCoords is nullopt if middle of viewport is obstructed by another window?
|
|
if (!mapXYCoords.has_value() || other != viewport)
|
|
{
|
|
auto viewPos = ScreenCoordsXY{ (viewport->ViewWidth() >> 1), (viewport->ViewHeight() >> 1) } + viewport->viewPos;
|
|
|
|
coords = ViewportAdjustForMapHeight(viewPos, viewport->rotation);
|
|
}
|
|
else
|
|
{
|
|
coords.x = mapXYCoords->x;
|
|
coords.y = mapXYCoords->y;
|
|
coords.z = TileElementHeight(coords);
|
|
}
|
|
|
|
viewport->rotation = (viewport->rotation + direction) & 3;
|
|
|
|
auto centreLoc = centre_2d_coordinates(coords, viewport);
|
|
|
|
if (centreLoc.has_value())
|
|
{
|
|
w.savedViewPos = centreLoc.value();
|
|
viewport->viewPos = *centreLoc;
|
|
}
|
|
|
|
w.Invalidate();
|
|
w.OnViewportRotate();
|
|
}
|
|
|
|
void ViewportRotateSingle(WindowBase* window, int32_t direction)
|
|
{
|
|
ViewportRotateSingleInternal(*window, direction);
|
|
}
|
|
|
|
void ViewportRotateAll(int32_t direction)
|
|
{
|
|
WindowVisitEach([direction](WindowBase* w) {
|
|
auto* viewport = w->viewport;
|
|
if (viewport == nullptr)
|
|
return;
|
|
if (viewport->flags & VIEWPORT_FLAG_INDEPEDENT_ROTATION)
|
|
return;
|
|
ViewportRotateSingleInternal(*w, direction);
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00685C02
|
|
* ax: left
|
|
* bx: top
|
|
* dx: right
|
|
* esi: viewport
|
|
* edi: dpi
|
|
* ebp: bottom
|
|
*/
|
|
void ViewportRender(DrawPixelInfo& dpi, const Viewport* viewport)
|
|
{
|
|
if (viewport->flags & VIEWPORT_FLAG_RENDERING_INHIBITED)
|
|
return;
|
|
|
|
if (dpi.x + dpi.width <= viewport->pos.x)
|
|
return;
|
|
if (dpi.y + dpi.height <= viewport->pos.y)
|
|
return;
|
|
if (dpi.x >= viewport->pos.x + viewport->width)
|
|
return;
|
|
if (dpi.y >= viewport->pos.y + viewport->height)
|
|
return;
|
|
|
|
ViewportPaint(viewport, dpi);
|
|
}
|
|
|
|
static void ViewportFillColumn(PaintSession& session)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
PaintSessionGenerate(session);
|
|
PaintSessionArrange(session);
|
|
}
|
|
|
|
static void ViewportPaintColumn(PaintSession& session)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
if (session.ViewFlags
|
|
& (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_UNDERGROUND_INSIDE
|
|
| VIEWPORT_FLAG_CLIP_VIEW)
|
|
&& (~session.ViewFlags & VIEWPORT_FLAG_TRANSPARENT_BACKGROUND))
|
|
{
|
|
uint8_t colour = COLOUR_AQUAMARINE;
|
|
if (session.ViewFlags & VIEWPORT_FLAG_HIDE_ENTITIES)
|
|
{
|
|
colour = COLOUR_BLACK;
|
|
}
|
|
GfxClear(session.DPI, colour);
|
|
}
|
|
|
|
PaintDrawStructs(session);
|
|
|
|
if (Config::Get().general.RenderWeatherGloom && !gTrackDesignSaveMode
|
|
&& !(session.ViewFlags & VIEWPORT_FLAG_HIDE_ENTITIES) && !(session.ViewFlags & VIEWPORT_FLAG_HIGHLIGHT_PATH_ISSUES))
|
|
{
|
|
ViewportPaintWeatherGloom(session.DPI);
|
|
}
|
|
|
|
if (session.PSStringHead != nullptr)
|
|
{
|
|
PaintDrawMoneyStructs(session.DPI, session.PSStringHead);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00685CBF
|
|
* eax: left
|
|
* ebx: top
|
|
* edx: right
|
|
* esi: viewport
|
|
* edi: dpi
|
|
* ebp: bottom
|
|
*/
|
|
static void ViewportPaint(const Viewport* viewport, DrawPixelInfo& dpi)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
const int32_t offsetX = dpi.x - viewport->pos.x;
|
|
const int32_t offsetY = dpi.y - viewport->pos.y;
|
|
const int32_t worldX = viewport->zoom.ApplyInversedTo(viewport->viewPos.x) + std::max(0, offsetX);
|
|
const int32_t worldY = viewport->zoom.ApplyInversedTo(viewport->viewPos.y) + std::max(0, offsetY);
|
|
const int32_t width = std::min(viewport->pos.x + viewport->width, dpi.x + dpi.width) - std::max(viewport->pos.x, dpi.x);
|
|
const int32_t height = std::min(viewport->pos.y + viewport->height, dpi.y + dpi.height)
|
|
- std::max(viewport->pos.y, dpi.y);
|
|
|
|
DrawPixelInfo worldDpi;
|
|
worldDpi.DrawingEngine = dpi.DrawingEngine;
|
|
worldDpi.bits = dpi.bits + std::max(0, -offsetX) + std::max(0, -offsetY) * dpi.LineStride();
|
|
worldDpi.x = worldX;
|
|
worldDpi.y = worldY;
|
|
worldDpi.width = width;
|
|
worldDpi.height = height;
|
|
worldDpi.pitch = dpi.LineStride() - worldDpi.width;
|
|
worldDpi.zoom_level = viewport->zoom;
|
|
|
|
_paintColumns.clear();
|
|
|
|
bool useMultithreading = Config::Get().general.MultiThreading;
|
|
if (useMultithreading && _paintJobs == nullptr)
|
|
{
|
|
_paintJobs = std::make_unique<JobPool>();
|
|
}
|
|
else if (useMultithreading == false && _paintJobs != nullptr)
|
|
{
|
|
_paintJobs.reset();
|
|
}
|
|
|
|
bool useParallelDrawing = false;
|
|
if (useMultithreading && dpi.DrawingEngine->GetFlags().has(DrawingEngineFlag::parallelDrawing))
|
|
{
|
|
useParallelDrawing = true;
|
|
}
|
|
|
|
const int32_t columnWidth = worldDpi.zoom_level.ApplyInversedTo(kCoordsXYStep);
|
|
const int32_t rightBorder = worldDpi.x + worldDpi.width;
|
|
const int32_t alignedX = floor2(worldDpi.x, columnWidth);
|
|
|
|
// Generate and sort columns.
|
|
for (int32_t x = alignedX; x < rightBorder; x += columnWidth)
|
|
{
|
|
PaintSession* session = PaintSessionAlloc(worldDpi, viewport->flags, viewport->rotation);
|
|
_paintColumns.push_back(session);
|
|
|
|
DrawPixelInfo& columnDpi = session->DPI;
|
|
if (x >= columnDpi.x)
|
|
{
|
|
const int32_t leftPitch = x - columnDpi.x;
|
|
columnDpi.width = columnDpi.width - leftPitch;
|
|
columnDpi.bits += leftPitch;
|
|
columnDpi.pitch += leftPitch;
|
|
columnDpi.x = x;
|
|
}
|
|
|
|
int32_t paintRight = columnDpi.x + columnDpi.width;
|
|
if (paintRight >= x + columnWidth)
|
|
{
|
|
const int32_t rightPitch = paintRight - x - columnWidth;
|
|
paintRight -= rightPitch;
|
|
columnDpi.pitch += rightPitch;
|
|
}
|
|
columnDpi.width = paintRight - columnDpi.x;
|
|
|
|
if (useMultithreading)
|
|
{
|
|
_paintJobs->AddTask([session]() -> void { ViewportFillColumn(*session); });
|
|
}
|
|
else
|
|
{
|
|
ViewportFillColumn(*session);
|
|
}
|
|
}
|
|
|
|
if (useMultithreading)
|
|
{
|
|
_paintJobs->Join();
|
|
}
|
|
|
|
// Paint columns.
|
|
for (auto* session : _paintColumns)
|
|
{
|
|
if (useParallelDrawing)
|
|
{
|
|
_paintJobs->AddTask([session]() -> void { ViewportPaintColumn(*session); });
|
|
}
|
|
else
|
|
{
|
|
ViewportPaintColumn(*session);
|
|
}
|
|
}
|
|
if (useParallelDrawing)
|
|
{
|
|
_paintJobs->Join();
|
|
}
|
|
|
|
// Release resources.
|
|
for (auto* session : _paintColumns)
|
|
{
|
|
PaintSessionFree(session);
|
|
}
|
|
}
|
|
|
|
static void ViewportPaintWeatherGloom(DrawPixelInfo& dpi)
|
|
{
|
|
auto paletteId = ClimateGetWeatherGloomPaletteId(getGameState().weatherCurrent);
|
|
if (paletteId != FilterPaletteID::PaletteNull)
|
|
{
|
|
auto x = dpi.x;
|
|
auto y = dpi.y;
|
|
auto w = dpi.width;
|
|
auto h = dpi.height;
|
|
GfxFilterRect(dpi, ScreenRect(x, y, x + w, y + h), paletteId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0068958D
|
|
*/
|
|
std::optional<CoordsXY> ScreenPosToMapPos(const ScreenCoordsXY& screenCoords, int32_t* direction)
|
|
{
|
|
auto mapCoords = ScreenGetMapXY(screenCoords, nullptr);
|
|
if (!mapCoords.has_value())
|
|
return std::nullopt;
|
|
|
|
int32_t my_direction;
|
|
int32_t dist_from_centre_x = abs(mapCoords->x % 32);
|
|
int32_t dist_from_centre_y = abs(mapCoords->y % 32);
|
|
if (dist_from_centre_x > 8 && dist_from_centre_x < 24 && dist_from_centre_y > 8 && dist_from_centre_y < 24)
|
|
{
|
|
my_direction = 4;
|
|
}
|
|
else
|
|
{
|
|
auto mod_x = mapCoords->x & 0x1F;
|
|
auto mod_y = mapCoords->y & 0x1F;
|
|
if (mod_x <= 16)
|
|
{
|
|
if (mod_y < 16)
|
|
{
|
|
my_direction = 2;
|
|
}
|
|
else
|
|
{
|
|
my_direction = 3;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (mod_y < 16)
|
|
{
|
|
my_direction = 1;
|
|
}
|
|
else
|
|
{
|
|
my_direction = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (direction != nullptr)
|
|
*direction = my_direction;
|
|
return { mapCoords->ToTileStart() };
|
|
}
|
|
|
|
[[nodiscard]] ScreenCoordsXY Viewport::ScreenToViewportCoord(const ScreenCoordsXY& screenCoords) const
|
|
{
|
|
ScreenCoordsXY ret;
|
|
ret.x = (zoom.ApplyTo(screenCoords.x - pos.x)) + viewPos.x;
|
|
ret.y = (zoom.ApplyTo(screenCoords.y - pos.y)) + viewPos.y;
|
|
return ret;
|
|
}
|
|
|
|
void Viewport::Invalidate() const
|
|
{
|
|
ViewportInvalidate(this, { viewPos, viewPos + ScreenCoordsXY{ ViewWidth(), ViewHeight() } });
|
|
}
|
|
|
|
CoordsXY ViewportPosToMapPos(const ScreenCoordsXY& coords, int32_t z, uint8_t rotation)
|
|
{
|
|
// Reverse of Translate3DTo2DWithZ
|
|
CoordsXY ret = { coords.y - coords.x / 2 + z, coords.y + coords.x / 2 + z };
|
|
auto inverseRotation = DirectionFlipXAxis(rotation);
|
|
return ret.Rotate(inverseRotation);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664689
|
|
*/
|
|
void ShowGridlines()
|
|
{
|
|
if (gShowGridLinesRefCount == 0)
|
|
{
|
|
WindowBase* mainWindow = WindowGetMain();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_GRIDLINES))
|
|
{
|
|
mainWindow->viewport->flags |= VIEWPORT_FLAG_GRIDLINES;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
gShowGridLinesRefCount++;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006646B4
|
|
*/
|
|
void HideGridlines()
|
|
{
|
|
if (gShowGridLinesRefCount > 0)
|
|
gShowGridLinesRefCount--;
|
|
|
|
if (gShowGridLinesRefCount == 0)
|
|
{
|
|
WindowBase* mainWindow = WindowGetMain();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!Config::Get().general.AlwaysShowGridlines)
|
|
{
|
|
mainWindow->viewport->flags &= ~VIEWPORT_FLAG_GRIDLINES;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664E8E
|
|
*/
|
|
void ShowLandRights()
|
|
{
|
|
if (gShowLandRightsRefCount == 0)
|
|
{
|
|
WindowBase* mainWindow = WindowGetMain();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP))
|
|
{
|
|
mainWindow->viewport->flags |= VIEWPORT_FLAG_LAND_OWNERSHIP;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
gShowLandRightsRefCount++;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664EB9
|
|
*/
|
|
void HideLandRights()
|
|
{
|
|
if (gShowLandRightsRefCount > 0)
|
|
gShowLandRightsRefCount--;
|
|
|
|
if (gShowLandRightsRefCount == 0)
|
|
{
|
|
WindowBase* mainWindow = WindowGetMain();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (mainWindow->viewport->flags & VIEWPORT_FLAG_LAND_OWNERSHIP)
|
|
{
|
|
mainWindow->viewport->flags &= ~VIEWPORT_FLAG_LAND_OWNERSHIP;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664EDD
|
|
*/
|
|
void ShowConstructionRights()
|
|
{
|
|
if (gShowConstructionRightsRefCount == 0)
|
|
{
|
|
WindowBase* mainWindow = WindowGetMain();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (!(mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS))
|
|
{
|
|
mainWindow->viewport->flags |= VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
gShowConstructionRightsRefCount++;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00664F08
|
|
*/
|
|
void HideConstructionRights()
|
|
{
|
|
if (gShowConstructionRightsRefCount > 0)
|
|
gShowConstructionRightsRefCount--;
|
|
|
|
if (gShowConstructionRightsRefCount == 0)
|
|
{
|
|
WindowBase* mainWindow = WindowGetMain();
|
|
if (mainWindow != nullptr)
|
|
{
|
|
if (mainWindow->viewport->flags & VIEWPORT_FLAG_CONSTRUCTION_RIGHTS)
|
|
{
|
|
mainWindow->viewport->flags &= ~VIEWPORT_FLAG_CONSTRUCTION_RIGHTS;
|
|
mainWindow->Invalidate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006CB70A
|
|
*/
|
|
void ViewportSetVisibility(ViewportVisibility mode)
|
|
{
|
|
WindowBase* window = WindowGetMain();
|
|
|
|
if (window != nullptr)
|
|
{
|
|
Viewport* vp = window->viewport;
|
|
uint32_t invalidate = 0;
|
|
|
|
switch (mode)
|
|
{
|
|
case ViewportVisibility::Default:
|
|
{ // Set all these flags to 0, and invalidate if any were active
|
|
uint32_t mask = VIEWPORT_FLAG_UNDERGROUND_INSIDE | VIEWPORT_FLAG_HIDE_RIDES | VIEWPORT_FLAG_HIDE_SCENERY
|
|
| VIEWPORT_FLAG_HIDE_PATHS | VIEWPORT_FLAG_LAND_HEIGHTS | VIEWPORT_FLAG_TRACK_HEIGHTS
|
|
| VIEWPORT_FLAG_PATH_HEIGHTS | VIEWPORT_FLAG_HIDE_GUESTS | VIEWPORT_FLAG_HIDE_STAFF
|
|
| VIEWPORT_FLAG_HIDE_BASE | VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_VEHICLES
|
|
| VIEWPORT_FLAG_HIDE_SUPPORTS | VIEWPORT_FLAG_HIDE_VEGETATION;
|
|
|
|
invalidate += vp->flags & mask;
|
|
vp->flags &= ~mask;
|
|
break;
|
|
}
|
|
case ViewportVisibility::UndergroundViewOn: // 6CB79D
|
|
case ViewportVisibility::UndergroundViewGhostOn: // 6CB7C4
|
|
// Set underground on, invalidate if it was off
|
|
invalidate += !(vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE);
|
|
vp->flags |= VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
break;
|
|
case ViewportVisibility::TrackHeights: // 6CB7EB
|
|
// Set track heights on, invalidate if off
|
|
invalidate += !(vp->flags & VIEWPORT_FLAG_TRACK_HEIGHTS);
|
|
vp->flags |= VIEWPORT_FLAG_TRACK_HEIGHTS;
|
|
break;
|
|
case ViewportVisibility::UndergroundViewOff: // 6CB7B1
|
|
case ViewportVisibility::UndergroundViewGhostOff: // 6CB7D8
|
|
// Set underground off, invalidate if it was on
|
|
invalidate += vp->flags & VIEWPORT_FLAG_UNDERGROUND_INSIDE;
|
|
vp->flags &= ~(static_cast<uint16_t>(VIEWPORT_FLAG_UNDERGROUND_INSIDE));
|
|
break;
|
|
}
|
|
if (invalidate != 0)
|
|
window->Invalidate();
|
|
}
|
|
}
|
|
|
|
static bool IsCursorIdVegetation(CursorID cursor)
|
|
{
|
|
switch (cursor)
|
|
{
|
|
case CursorID::TreeDown:
|
|
case CursorID::FlowerDown:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool IsTileElementVegetation(const TileElement* tileElement)
|
|
{
|
|
switch (tileElement->GetType())
|
|
{
|
|
case TileElementType::SmallScenery:
|
|
{
|
|
auto sceneryItem = tileElement->AsSmallScenery();
|
|
auto sceneryEntry = sceneryItem->GetEntry();
|
|
if (sceneryEntry != nullptr
|
|
&& (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_IS_TREE) || IsCursorIdVegetation(sceneryEntry->tool_id)))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case TileElementType::LargeScenery:
|
|
{
|
|
auto sceneryItem = tileElement->AsLargeScenery();
|
|
auto sceneryEntry = sceneryItem->GetEntry();
|
|
if (sceneryEntry != nullptr && IsCursorIdVegetation(sceneryEntry->tool_id))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case TileElementType::Wall:
|
|
{
|
|
auto sceneryItem = tileElement->AsWall();
|
|
auto sceneryEntry = sceneryItem->GetEntry();
|
|
if (sceneryEntry != nullptr && IsCursorIdVegetation(sceneryEntry->tool_id))
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
VisibilityKind GetPaintStructVisibility(const PaintStruct* ps, uint32_t viewFlags)
|
|
{
|
|
// the cut-away view is active and see-through is activated
|
|
auto cutAwayViewWithTransparency = (viewFlags & VIEWPORT_FLAG_CLIP_VIEW)
|
|
&& (viewFlags & VIEWPORT_FLAG_CLIP_VIEW_SEE_THROUGH);
|
|
|
|
// the element is above the cut-off height
|
|
auto clipped = cutAwayViewWithTransparency && ps->Element == nullptr && ps->Entity != nullptr
|
|
&& ps->Entity->GetLocation().z > (gClipHeight * kCoordsZStep);
|
|
|
|
// the entity is above the cut-off height
|
|
clipped |= cutAwayViewWithTransparency && ps->Element != nullptr
|
|
&& (ps->Element->GetBaseZ() > gClipHeight * kCoordsZStep);
|
|
|
|
switch (ps->InteractionItem)
|
|
{
|
|
case ViewportInteractionItem::Entity:
|
|
if (ps->Entity != nullptr)
|
|
{
|
|
switch (ps->Entity->Type)
|
|
{
|
|
case EntityType::Vehicle:
|
|
{
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_VEHICLES || clipped)
|
|
{
|
|
return (viewFlags & VIEWPORT_FLAG_INVISIBLE_VEHICLES) ? VisibilityKind::Hidden
|
|
: VisibilityKind::Partial;
|
|
}
|
|
// Rides without track can technically have a 'vehicle':
|
|
// these should be hidden if 'hide rides' is enabled
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_RIDES || clipped)
|
|
{
|
|
auto vehicle = ps->Entity->As<Vehicle>();
|
|
if (vehicle == nullptr)
|
|
break;
|
|
|
|
auto ride = vehicle->GetRide();
|
|
if (ride != nullptr && !ride->getRideTypeDescriptor().HasFlag(RtdFlag::hasTrack))
|
|
{
|
|
return (viewFlags & VIEWPORT_FLAG_INVISIBLE_RIDES) ? VisibilityKind::Hidden
|
|
: VisibilityKind::Partial;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EntityType::Guest:
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_GUESTS)
|
|
{
|
|
return VisibilityKind::Hidden;
|
|
}
|
|
else if (clipped)
|
|
{
|
|
return VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
case EntityType::Staff:
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_STAFF)
|
|
{
|
|
return VisibilityKind::Hidden;
|
|
}
|
|
else if (clipped)
|
|
{
|
|
return VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
default:
|
|
if (clipped)
|
|
{
|
|
return VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case ViewportInteractionItem::Ride:
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_RIDES || clipped)
|
|
{
|
|
return (viewFlags & VIEWPORT_FLAG_INVISIBLE_RIDES) ? VisibilityKind::Hidden : VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
case ViewportInteractionItem::Footpath:
|
|
case ViewportInteractionItem::PathAddition:
|
|
case ViewportInteractionItem::Banner:
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_PATHS || clipped)
|
|
{
|
|
return (viewFlags & VIEWPORT_FLAG_INVISIBLE_PATHS) ? VisibilityKind::Hidden : VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
case ViewportInteractionItem::Scenery:
|
|
case ViewportInteractionItem::LargeScenery:
|
|
case ViewportInteractionItem::Wall:
|
|
if (ps->Element != nullptr)
|
|
{
|
|
if (IsTileElementVegetation(ps->Element))
|
|
{
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_VEGETATION || clipped)
|
|
{
|
|
return (viewFlags & VIEWPORT_FLAG_INVISIBLE_VEGETATION) ? VisibilityKind::Hidden
|
|
: VisibilityKind::Partial;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (viewFlags & VIEWPORT_FLAG_HIDE_SCENERY || clipped)
|
|
{
|
|
return (viewFlags & VIEWPORT_FLAG_INVISIBLE_SCENERY) ? VisibilityKind::Hidden
|
|
: VisibilityKind::Partial;
|
|
}
|
|
}
|
|
}
|
|
if (ps->InteractionItem == ViewportInteractionItem::Wall
|
|
&& (viewFlags & VIEWPORT_FLAG_UNDERGROUND_INSIDE || clipped))
|
|
{
|
|
return VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
default:
|
|
if (clipped)
|
|
{
|
|
return VisibilityKind::Partial;
|
|
}
|
|
break;
|
|
}
|
|
return VisibilityKind::Visible;
|
|
}
|
|
|
|
/**
|
|
* Checks if a PaintStruct sprite type is in the filter mask.
|
|
*/
|
|
static bool PSInteractionTypeIsInFilter(PaintStruct* ps, uint16_t filter)
|
|
{
|
|
if (ps->InteractionItem != ViewportInteractionItem::None && ps->InteractionItem != ViewportInteractionItem::Label
|
|
&& ps->InteractionItem <= ViewportInteractionItem::Banner)
|
|
{
|
|
auto mask = EnumToFlag(ps->InteractionItem);
|
|
if (filter & mask)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* rct2: 0x00679236, 0x00679662, 0x00679B0D, 0x00679FF1
|
|
*/
|
|
static bool IsPixelPresentBMP(
|
|
const uint32_t imageType, const G1Element* g1, const int32_t x, const int32_t y, const PaletteMap& paletteMap)
|
|
{
|
|
uint8_t* index = g1->offset + (y * g1->width) + x;
|
|
|
|
// Needs investigation as it has no consideration for pure BMP maps.
|
|
if (!(g1->flags & G1_FLAG_HAS_TRANSPARENCY))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (imageType & IMAGE_TYPE_REMAP)
|
|
{
|
|
return paletteMap[*index] != 0;
|
|
}
|
|
|
|
if (imageType & IMAGE_TYPE_TRANSPARENT)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (*index != 0);
|
|
}
|
|
|
|
/**
|
|
* rct2: 0x0067933B, 0x00679788, 0x00679C4A, 0x0067A117
|
|
*/
|
|
static bool IsPixelPresentRLE(const uint8_t* imgData, const int32_t x, const int32_t y)
|
|
{
|
|
uint16_t lineOffset;
|
|
std::memcpy(&lineOffset, &imgData[y * sizeof(uint16_t)], sizeof(uint16_t));
|
|
const uint8_t* data8 = imgData + lineOffset;
|
|
|
|
bool lastDataLine = false;
|
|
while (!lastDataLine)
|
|
{
|
|
int32_t numPixels = *data8++;
|
|
uint8_t pixelRunStart = *data8++;
|
|
lastDataLine = numPixels & 0x80;
|
|
numPixels &= 0x7F;
|
|
data8 += numPixels;
|
|
|
|
if (pixelRunStart <= x && x < pixelRunStart + numPixels)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* rct2: 0x00679074
|
|
*/
|
|
static bool IsSpriteInteractedWithPaletteSet(
|
|
DrawPixelInfo& dpi, ImageId imageId, const ScreenCoordsXY& coords, const PaletteMap& paletteMap,
|
|
const uint8_t imageType)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
const G1Element* g1 = GfxGetG1Element(imageId);
|
|
if (g1 == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ZoomLevel zoomLevel = dpi.zoom_level;
|
|
ScreenCoordsXY interactionPoint{ dpi.WorldX(), dpi.WorldY() };
|
|
ScreenCoordsXY origin = coords;
|
|
|
|
if (dpi.zoom_level > ZoomLevel{ 0 })
|
|
{
|
|
if (g1->flags & G1_FLAG_NO_ZOOM_DRAW)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
while (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE && zoomLevel > ZoomLevel{ 0 })
|
|
{
|
|
imageId = imageId.WithIndex(imageId.GetIndex() - g1->zoomed_offset);
|
|
g1 = GfxGetG1Element(imageId);
|
|
if (g1 == nullptr || g1->flags & G1_FLAG_NO_ZOOM_DRAW)
|
|
{
|
|
return false;
|
|
}
|
|
zoomLevel = zoomLevel - 1;
|
|
interactionPoint.x >>= 1;
|
|
interactionPoint.y >>= 1;
|
|
origin.x >>= 1;
|
|
origin.y >>= 1;
|
|
}
|
|
}
|
|
|
|
origin.x += g1->x_offset;
|
|
origin.y += g1->y_offset;
|
|
interactionPoint -= origin;
|
|
|
|
if (interactionPoint.x < 0 || interactionPoint.y < 0 || interactionPoint.x >= g1->width
|
|
|| interactionPoint.y >= g1->height)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (g1->flags & G1_FLAG_RLE_COMPRESSION)
|
|
{
|
|
return IsPixelPresentRLE(g1->offset, interactionPoint.x, interactionPoint.y);
|
|
}
|
|
|
|
if (!(g1->flags & G1_FLAG_1))
|
|
{
|
|
return IsPixelPresentBMP(imageType, g1, interactionPoint.x, interactionPoint.y, paletteMap);
|
|
}
|
|
|
|
Guard::Assert(false, "Invalid image type encountered.");
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00679023
|
|
*/
|
|
|
|
static bool IsSpriteInteractedWith(DrawPixelInfo& dpi, ImageId imageId, const ScreenCoordsXY& coords)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
auto paletteMap = PaletteMap::GetDefault();
|
|
uint8_t imageType;
|
|
if (imageId.HasPrimary() || imageId.IsRemap())
|
|
{
|
|
imageType = IMAGE_TYPE_REMAP;
|
|
uint8_t paletteIndex;
|
|
if (imageId.HasSecondary())
|
|
{
|
|
paletteIndex = imageId.GetPrimary();
|
|
}
|
|
else
|
|
{
|
|
paletteIndex = imageId.GetRemap();
|
|
}
|
|
if (auto pm = GetPaletteMapForColour(paletteIndex); pm.has_value())
|
|
{
|
|
paletteMap = pm.value();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
imageType = IMAGE_TYPE_DEFAULT;
|
|
}
|
|
return IsSpriteInteractedWithPaletteSet(dpi, imageId, coords, paletteMap, imageType);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0068862C
|
|
*/
|
|
InteractionInfo SetInteractionInfoFromPaintSession(PaintSession* session, uint32_t viewFlags, uint16_t filter)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
InteractionInfo info{};
|
|
|
|
PaintStruct* ps = session->PaintHead;
|
|
while (ps != nullptr)
|
|
{
|
|
PaintStruct* old_ps = ps;
|
|
PaintStruct* next_ps = ps;
|
|
while (next_ps != nullptr)
|
|
{
|
|
ps = next_ps;
|
|
if (IsSpriteInteractedWith(session->DPI, ps->image_id, ps->ScreenPos))
|
|
{
|
|
if (PSInteractionTypeIsInFilter(ps, filter)
|
|
&& GetPaintStructVisibility(ps, viewFlags) == VisibilityKind::Visible)
|
|
{
|
|
info = { ps };
|
|
}
|
|
}
|
|
next_ps = ps->Children;
|
|
}
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wnull-dereference"
|
|
for (AttachedPaintStruct* attached_ps = ps->Attached; attached_ps != nullptr; attached_ps = attached_ps->NextEntry)
|
|
{
|
|
if (IsSpriteInteractedWith(session->DPI, attached_ps->image_id, ps->ScreenPos + attached_ps->RelativePos))
|
|
{
|
|
if (PSInteractionTypeIsInFilter(ps, filter)
|
|
&& GetPaintStructVisibility(ps, viewFlags) == VisibilityKind::Visible)
|
|
{
|
|
info = { ps };
|
|
}
|
|
}
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
|
|
ps = old_ps->NextQuadrantEntry;
|
|
}
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00685ADC
|
|
* screenX: eax
|
|
* screenY: ebx
|
|
* flags: edx
|
|
* x: ax
|
|
* y: cx
|
|
* interactionType: bl
|
|
* tileElement: edx
|
|
* viewport: edi
|
|
*/
|
|
InteractionInfo GetMapCoordinatesFromPos(const ScreenCoordsXY& screenCoords, int32_t flags)
|
|
{
|
|
auto* windowMgr = Ui::GetWindowManager();
|
|
WindowBase* window = windowMgr->FindFromPoint(screenCoords);
|
|
return GetMapCoordinatesFromPosWindow(window, screenCoords, flags);
|
|
}
|
|
|
|
InteractionInfo GetMapCoordinatesFromPosWindow(WindowBase* window, const ScreenCoordsXY& screenCoords, int32_t flags)
|
|
{
|
|
InteractionInfo info{};
|
|
if (window == nullptr || window->viewport == nullptr)
|
|
{
|
|
return info;
|
|
}
|
|
|
|
Viewport* viewport = window->viewport;
|
|
auto viewLoc = screenCoords;
|
|
viewLoc -= viewport->pos;
|
|
if (viewLoc.x >= 0 && viewLoc.x < static_cast<int32_t>(viewport->width) && viewLoc.y >= 0
|
|
&& viewLoc.y < static_cast<int32_t>(viewport->height))
|
|
{
|
|
viewLoc.x = viewport->zoom.ApplyTo(viewLoc.x);
|
|
viewLoc.y = viewport->zoom.ApplyTo(viewLoc.y);
|
|
viewLoc += viewport->viewPos;
|
|
if (viewport->zoom > ZoomLevel{ 0 })
|
|
{
|
|
viewLoc.x &= viewport->zoom.ApplyTo(0xFFFFFFFF) & 0xFFFFFFFF;
|
|
viewLoc.y &= viewport->zoom.ApplyTo(0xFFFFFFFF) & 0xFFFFFFFF;
|
|
}
|
|
DrawPixelInfo dpi;
|
|
dpi.zoom_level = viewport->zoom;
|
|
dpi.x = viewport->zoom.ApplyInversedTo(viewLoc.x);
|
|
dpi.y = viewport->zoom.ApplyInversedTo(viewLoc.y);
|
|
dpi.height = 1;
|
|
dpi.width = 1;
|
|
|
|
PaintSession* session = PaintSessionAlloc(dpi, viewport->flags, viewport->rotation);
|
|
PaintSessionGenerate(*session);
|
|
PaintSessionArrange(*session);
|
|
info = SetInteractionInfoFromPaintSession(session, viewport->flags, flags & 0xFFFF);
|
|
PaintSessionFree(session);
|
|
}
|
|
return info;
|
|
}
|
|
|
|
/**
|
|
* screenRect represents 2D map coordinates at zoom 0.
|
|
*/
|
|
void ViewportInvalidate(const Viewport* viewport, const ScreenRect& screenRect)
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
// if unknown viewport visibility, use the containing window to discover the status
|
|
if (viewport->visibility == VisibilityCache::unknown)
|
|
{
|
|
auto windowManager = Ui::GetWindowManager();
|
|
auto owner = windowManager->GetOwner(viewport);
|
|
if (owner != nullptr && owner->classification != WindowClass::MainWindow)
|
|
{
|
|
// note, window_is_visible will update viewport->visibility, so this should have a low hit count
|
|
if (!WindowIsVisible(*owner))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (viewport->visibility == VisibilityCache::covered)
|
|
return;
|
|
|
|
auto zoom = viewport->zoom;
|
|
auto viewPos = viewport->viewPos;
|
|
auto viewportScreenPos = viewport->pos;
|
|
|
|
ScreenRect invalidRect = { { zoom.ApplyInversedTo(screenRect.GetLeft() - viewPos.x),
|
|
zoom.ApplyInversedTo(screenRect.GetTop() - viewPos.y) },
|
|
{ zoom.ApplyInversedTo(screenRect.GetRight() - viewPos.x),
|
|
zoom.ApplyInversedTo(screenRect.GetBottom() - viewPos.y) } };
|
|
|
|
if (invalidRect.GetTop() >= viewport->height || invalidRect.GetBottom() <= 0 || invalidRect.GetLeft() >= viewport->width
|
|
|| invalidRect.GetRight() <= 0)
|
|
{
|
|
return;
|
|
}
|
|
invalidRect.Point1 += viewportScreenPos;
|
|
invalidRect.Point2 += viewportScreenPos;
|
|
GfxSetDirtyBlocks(invalidRect);
|
|
}
|
|
|
|
static Viewport* ViewportFindFromPoint(const ScreenCoordsXY& screenCoords)
|
|
{
|
|
auto* windowMgr = Ui::GetWindowManager();
|
|
WindowBase* w = windowMgr->FindFromPoint(screenCoords);
|
|
if (w == nullptr)
|
|
return nullptr;
|
|
|
|
Viewport* viewport = w->viewport;
|
|
if (viewport == nullptr)
|
|
return nullptr;
|
|
|
|
if (viewport->ContainsScreen(screenCoords))
|
|
return viewport;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00688972
|
|
* In:
|
|
* screen_x: eax
|
|
* screen_y: ebx
|
|
* Out:
|
|
* x: ax
|
|
* y: bx
|
|
* tile_element: edx ?
|
|
* viewport: edi
|
|
*/
|
|
std::optional<CoordsXY> ScreenGetMapXY(const ScreenCoordsXY& screenCoords, Viewport** viewport)
|
|
{
|
|
auto* windowMgr = Ui::GetWindowManager();
|
|
|
|
// This will get the tile location but we will need the more accuracy
|
|
WindowBase* window = windowMgr->FindFromPoint(screenCoords);
|
|
if (window == nullptr || window->viewport == nullptr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
auto myViewport = window->viewport;
|
|
auto info = GetMapCoordinatesFromPosWindow(window, screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain));
|
|
if (info.interactionType == ViewportInteractionItem::None)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto start_vp_pos = myViewport->ScreenToViewportCoord(screenCoords);
|
|
CoordsXY cursorMapPos = info.Loc.ToTileCentre();
|
|
|
|
// Iterates the cursor location to work out exactly where on the tile it is
|
|
for (int32_t i = 0; i < 5; i++)
|
|
{
|
|
int32_t z = TileElementHeight(cursorMapPos);
|
|
cursorMapPos = ViewportPosToMapPos(start_vp_pos, z, myViewport->rotation);
|
|
cursorMapPos.x = std::clamp(cursorMapPos.x, info.Loc.x, info.Loc.x + 31);
|
|
cursorMapPos.y = std::clamp(cursorMapPos.y, info.Loc.y, info.Loc.y + 31);
|
|
}
|
|
|
|
if (viewport != nullptr)
|
|
*viewport = myViewport;
|
|
|
|
return cursorMapPos;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006894D4
|
|
*/
|
|
std::optional<CoordsXY> ScreenGetMapXYWithZ(const ScreenCoordsXY& screenCoords, int32_t z)
|
|
{
|
|
Viewport* viewport = ViewportFindFromPoint(screenCoords);
|
|
if (viewport == nullptr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto vpCoords = viewport->ScreenToViewportCoord(screenCoords);
|
|
auto mapPosition = ViewportPosToMapPos(vpCoords, z, viewport->rotation);
|
|
if (!MapIsLocationValid(mapPosition))
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return mapPosition;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00689604
|
|
*/
|
|
std::optional<CoordsXY> ScreenGetMapXYQuadrant(const ScreenCoordsXY& screenCoords, uint8_t* quadrant)
|
|
{
|
|
auto mapCoords = ScreenGetMapXY(screenCoords, nullptr);
|
|
if (!mapCoords.has_value())
|
|
return std::nullopt;
|
|
|
|
*quadrant = MapGetTileQuadrant(*mapCoords);
|
|
return mapCoords->ToTileStart();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0068964B
|
|
*/
|
|
std::optional<CoordsXY> ScreenGetMapXYQuadrantWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* quadrant)
|
|
{
|
|
auto mapCoords = ScreenGetMapXYWithZ(screenCoords, z);
|
|
if (!mapCoords.has_value())
|
|
return std::nullopt;
|
|
|
|
*quadrant = MapGetTileQuadrant(*mapCoords);
|
|
return mapCoords->ToTileStart();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00689692
|
|
*/
|
|
std::optional<CoordsXY> ScreenGetMapXYSide(const ScreenCoordsXY& screenCoords, uint8_t* side)
|
|
{
|
|
auto mapCoords = ScreenGetMapXY(screenCoords, nullptr);
|
|
if (!mapCoords.has_value())
|
|
return std::nullopt;
|
|
|
|
*side = MapGetTileSide(*mapCoords);
|
|
return mapCoords->ToTileStart();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006896DC
|
|
*/
|
|
std::optional<CoordsXY> ScreenGetMapXYSideWithZ(const ScreenCoordsXY& screenCoords, int32_t z, uint8_t* side)
|
|
{
|
|
auto mapCoords = ScreenGetMapXYWithZ(screenCoords, z);
|
|
if (!mapCoords.has_value())
|
|
return std::nullopt;
|
|
|
|
*side = MapGetTileSide(*mapCoords);
|
|
return mapCoords->ToTileStart();
|
|
}
|
|
|
|
ScreenCoordsXY Translate3DTo2DWithZ(int32_t rotation, const CoordsXYZ& pos)
|
|
{
|
|
auto rotated = pos.Rotate(rotation);
|
|
// Use right shift to avoid issues like #9301
|
|
return ScreenCoordsXY{ rotated.y - rotated.x, ((rotated.x + rotated.y) >> 1) - pos.z };
|
|
}
|
|
|
|
/**
|
|
* Get current viewport rotation.
|
|
*
|
|
* If an invalid rotation is detected and DEBUG_LEVEL_1 is enabled, an error
|
|
* will be reported.
|
|
*
|
|
* @returns rotation in range 0-3 (inclusive)
|
|
*/
|
|
uint8_t GetCurrentRotation()
|
|
{
|
|
auto* viewport = ViewportGetMain();
|
|
if (viewport == nullptr)
|
|
{
|
|
LOG_VERBOSE("No viewport found! Will return 0.");
|
|
return 0;
|
|
}
|
|
uint8_t rotation = viewport->rotation;
|
|
uint8_t rotation_masked = rotation & 3;
|
|
#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
|
|
if (rotation != rotation_masked)
|
|
{
|
|
LOG_ERROR(
|
|
"Found wrong rotation %d! Will return %d instead.", static_cast<uint32_t>(rotation),
|
|
static_cast<uint32_t>(rotation_masked));
|
|
}
|
|
#endif // DEBUG_LEVEL_1
|
|
return rotation_masked;
|
|
}
|
|
|
|
int32_t GetHeightMarkerOffset()
|
|
{
|
|
// Height labels in units
|
|
if (Config::Get().general.ShowHeightAsUnits)
|
|
return 0;
|
|
|
|
// Height labels in feet
|
|
if (Config::Get().general.MeasurementFormat == MeasurementFormat::Imperial)
|
|
return 1 * 256;
|
|
|
|
// Height labels in metres
|
|
return 2 * 256;
|
|
}
|
|
|
|
void ViewportSetSavedView()
|
|
{
|
|
WindowBase* w = WindowGetMain();
|
|
if (w != nullptr)
|
|
{
|
|
Viewport* viewport = w->viewport;
|
|
auto& gameState = getGameState();
|
|
|
|
gameState.savedView = ScreenCoordsXY{ viewport->ViewWidth() / 2, viewport->ViewHeight() / 2 } + viewport->viewPos;
|
|
|
|
gameState.savedViewZoom = viewport->zoom;
|
|
gameState.savedViewRotation = viewport->rotation;
|
|
}
|
|
}
|
|
} // namespace OpenRCT2
|
|
|
|
ZoomLevel ZoomLevel::min()
|
|
{
|
|
return ZoomLevel{ -2 };
|
|
}
|