use WorldCoordinates in ArenaView too

This commit is contained in:
2026-08-05 19:42:37 +02:00
parent 2af09d9eb1
commit fa9dbd62ad
7 changed files with 208 additions and 120 deletions

View File

@@ -1,21 +1,61 @@
#include "WorldCoordinates.h"
#include <algorithm>
#include <cmath>
WorldCoordinates::WorldCoordinates(QSize widgetSize_px, int worldHeight_tiles,
float viewCenterX_tiles)
: m_tilePx(1.0f)
, m_viewportWidthTiles(0.0f)
, m_viewLeftTiles(0.0f)
, m_worldHeightTiles(worldHeight_tiles)
namespace
{
// A zero-height widget, a not-yet-shown widget, or a degenerate world size
// would otherwise make every conversion divide by zero.
float sanitizeTilePx(float tilePx)
{
return tilePx > 0.0f ? tilePx : 1.0f;
}
}
WorldCoordinates WorldCoordinates::scrolling(QSize widgetSize_px,
int worldHeight_tiles,
float viewCenterX_tiles)
{
float tilePx = 1.0f;
if (worldHeight_tiles > 0)
{
m_tilePx = static_cast<float>(widgetSize_px.height())
/ static_cast<float>(worldHeight_tiles);
tilePx = static_cast<float>(widgetSize_px.height())
/ static_cast<float>(worldHeight_tiles);
}
m_viewportWidthTiles = static_cast<float>(widgetSize_px.width()) / m_tilePx;
m_viewLeftTiles = viewCenterX_tiles - m_viewportWidthTiles / 2.0f;
tilePx = sanitizeTilePx(tilePx);
const float viewportWidthTiles = static_cast<float>(widgetSize_px.width()) / tilePx;
return WorldCoordinates(tilePx, static_cast<float>(widgetSize_px.width()),
viewCenterX_tiles - viewportWidthTiles / 2.0f,
worldHeight_tiles);
}
WorldCoordinates WorldCoordinates::fitToWorld(QSize widgetSize_px,
int worldWidth_tiles,
int worldHeight_tiles)
{
float tilePx = 1.0f;
if (worldWidth_tiles > 0 && worldHeight_tiles > 0)
{
// The tighter of the two fits, so the whole world stays on screen.
tilePx = std::min(
static_cast<float>(widgetSize_px.height()) / static_cast<float>(worldHeight_tiles),
static_cast<float>(widgetSize_px.width()) / static_cast<float>(worldWidth_tiles));
}
tilePx = sanitizeTilePx(tilePx);
return WorldCoordinates(tilePx, static_cast<float>(widgetSize_px.width()),
0.0f, worldHeight_tiles);
}
WorldCoordinates::WorldCoordinates(float tilePx, float viewportWidth_px,
float viewLeft_tiles, int worldHeight_tiles)
: m_tilePx(tilePx)
, m_viewportWidthTiles(viewportWidth_px / tilePx)
, m_viewLeftTiles(viewLeft_tiles)
, m_worldHeightTiles(worldHeight_tiles)
{
}
float WorldCoordinates::getTilePx() const

View File

@@ -8,28 +8,37 @@
#include <QVector2D>
// Immutable snapshot of the world <-> widget transform for one viewport state
// (REQ-GW-COORDS, REQ-GW-TILE-SIZE). Tiles are square and sized so the world
// height exactly fills the viewport height; there is no zoom (REQ-UI-NO-ZOOM),
// so the only free parameter is the horizontal scroll position.
// (REQ-GW-COORDS). Tiles are square; the two factories below differ only in how
// the tile size and the left edge are derived, and everything downstream of that
// is shared.
//
// The transform is a value: it is constructed from the viewport size, the world
// height, and the view center, and never observes them again. A caller therefore
// builds one per frame (or per event) rather than holding one across a resize or
// a scroll, which would silently go stale.
// The transform is a value: it is constructed from the viewport size and the view
// state, and never observes them again. A caller therefore builds one per frame
// (or per event) rather than holding one across a resize or a scroll, which would
// silently go stale.
class WorldCoordinates
{
public:
// `viewCenterX_tiles` is the world X at the center of the viewport, matching
// how the scroll position is stored and clamped (REQ-GW-SCROLL-LIMIT).
WorldCoordinates(QSize widgetSize_px, int worldHeight_tiles,
float viewCenterX_tiles);
// The scrolling game world: the tile size is whatever makes the world height
// exactly fill the viewport height (REQ-GW-TILE-SIZE, no zoom per
// REQ-UI-NO-ZOOM), and the view pans horizontally. `viewCenterX_tiles` is the
// world X at the center of the viewport, matching how the scroll position is
// stored and clamped (REQ-GW-SCROLL-LIMIT).
static WorldCoordinates scrolling(QSize widgetSize_px, int worldHeight_tiles,
float viewCenterX_tiles);
// Side length of one tile in pixels. Degenerate world heights yield 1.0 so
// callers never divide by zero.
// A whole world shown at once with no scrolling, as the balancing tool's arena
// does: the tile size is whichever axis is the tighter fit, so nothing is cut
// off, and the world origin sits at the widget's top-left. A viewport wider
// than the fitted world leaves empty space to the right rather than centering.
static WorldCoordinates fitToWorld(QSize widgetSize_px, int worldWidth_tiles,
int worldHeight_tiles);
// Side length of one tile in pixels. Always positive: a degenerate world or
// viewport size falls back to 1.0 so no conversion below divides by zero.
float getTilePx() const;
float getViewportWidthTiles() const;
// World X (tiles) at the left edge of the viewport, derived from the view
// center the constructor was given.
// World X (tiles) at the left edge of the viewport.
float getViewLeftTiles() const;
QPointF worldToWidget(QVector2D worldPos) const;
@@ -44,6 +53,9 @@ public:
QRect getViewportRect() const;
private:
WorldCoordinates(float tilePx, float viewportWidth_px, float viewLeft_tiles,
int worldHeight_tiles);
float m_tilePx;
float m_viewportWidthTiles;
float m_viewLeftTiles;