extract the world<->widget transform into WorldCoordinates

This commit is contained in:
2026-08-05 19:39:47 +02:00
parent 4f7fdb8a4c
commit 2af09d9eb1
8 changed files with 475 additions and 211 deletions

View File

@@ -14,6 +14,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/DisplayName.h
${CMAKE_CURRENT_SOURCE_DIR}/BeltDragPath.h
${CMAKE_CURRENT_SOURCE_DIR}/TunnelCompletion.h
${CMAKE_CURRENT_SOURCE_DIR}/WorldCoordinates.h
PARENT_SCOPE
)
@@ -25,6 +26,7 @@ SET(SRCS
${CMAKE_CURRENT_SOURCE_DIR}/DisplayName.cpp
${CMAKE_CURRENT_SOURCE_DIR}/BeltDragPath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/TunnelCompletion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/WorldCoordinates.cpp
PARENT_SCOPE
)

View File

@@ -0,0 +1,78 @@
#include "WorldCoordinates.h"
#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)
{
if (worldHeight_tiles > 0)
{
m_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;
}
float WorldCoordinates::getTilePx() const
{
return m_tilePx;
}
float WorldCoordinates::getViewportWidthTiles() const
{
return m_viewportWidthTiles;
}
float WorldCoordinates::getViewLeftTiles() const
{
return m_viewLeftTiles;
}
QPointF WorldCoordinates::worldToWidget(QVector2D worldPos) const
{
return QPointF(
static_cast<qreal>((worldPos.x() - m_viewLeftTiles) * m_tilePx),
static_cast<qreal>(worldPos.y() * m_tilePx));
}
QPointF WorldCoordinates::tileToWidget(QPoint tile) const
{
return worldToWidget(QVector2D(static_cast<float>(tile.x()),
static_cast<float>(tile.y())));
}
QPoint WorldCoordinates::widgetToTile(QPoint widgetPoint) const
{
const QVector2D world = widgetToWorld(widgetPoint);
return QPoint(static_cast<int>(std::floor(world.x())),
static_cast<int>(std::floor(world.y())));
}
QVector2D WorldCoordinates::widgetToWorld(QPoint widgetPoint) const
{
return QVector2D(
static_cast<float>(widgetPoint.x()) / m_tilePx + m_viewLeftTiles,
static_cast<float>(widgetPoint.y()) / m_tilePx);
}
QRectF WorldCoordinates::tileRect(QPoint tile) const
{
const QPointF topLeft = tileToWidget(tile);
return QRectF(topLeft.x(), topLeft.y(),
static_cast<qreal>(m_tilePx), static_cast<qreal>(m_tilePx));
}
QRect WorldCoordinates::getViewportRect() const
{
const int left = static_cast<int>(std::floor(m_viewLeftTiles)) - 1;
const int top = 0;
const int right = static_cast<int>(
std::ceil(m_viewLeftTiles + m_viewportWidthTiles)) + 1;
const int bottom = m_worldHeightTiles;
return QRect(left, top, right - left, bottom - top);
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <QPoint>
#include <QPointF>
#include <QRect>
#include <QRectF>
#include <QSize>
#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.
//
// 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.
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);
// Side length of one tile in pixels. Degenerate world heights yield 1.0 so
// callers never divide 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.
float getViewLeftTiles() const;
QPointF worldToWidget(QVector2D worldPos) const;
QPointF tileToWidget(QPoint tile) const;
QPoint widgetToTile(QPoint widgetPoint) const;
QVector2D widgetToWorld(QPoint widgetPoint) const;
// Widget-space rect covering the whole of `tile`.
QRectF tileRect(QPoint tile) const;
// Tile-space rect of everything currently on screen, widened by one column on
// each side so items straddling an edge are still drawn.
QRect getViewportRect() const;
private:
float m_tilePx;
float m_viewportWidthTiles;
float m_viewLeftTiles;
int m_worldHeightTiles;
};