extract WorldRenderer
The last seam of the decomposition, and the one the earlier ones were groundwork for: every draw method already took a WorldCoordinates, read mode state through BuildModeController and selection through SelectionController, and used the shared shapes in WorldPrimitives, so the move needed almost no rewriting. paintGL is now a call sequence. The split is the world-space / screen-space line already drawn by the WorldCoordinates work: the renderer draws everything positioned in tiles, while the pause and deconstruct vignettes and the replay overlay - which never took a WorldCoordinates because they are anchored to the viewport - stay with the widget. WorldRenderFrame is what keeps the renderer independent of the widget. It reads the simulation directly, but the rest of what it draws is interaction state the widget owns: the selection, the active build mode, live beams, the copy-settings feedback, the box-select rectangle. Those are gathered per frame and passed by reference, so the renderer holds no copy a later click could invalidate, and it knows nothing about input. Three more queries had to stop belonging to the view first, because the renderer and the click path both need them: buildingsInBox and collectTunnelTiles move to FactoryQueries, and makeTunnelLookup with the TunnelTileMap and QPointCompare it needs move to TunnelCompletion, which already owned that concept. Two small things fell out of leaving QWidget. The port-item clip region used QWidget::rect() and now takes the painter's own viewport. drawDebugOverlay puts translated text on screen, so the renderer declares tr() via Q_DECLARE_TR_FUNCTIONS rather than becoming a QObject. drawDebugOverlay stays in the renderer despite being screen-anchored: it is drawn mid-sequence, so moving it out would put it on top of the ships instead of under them. The simulation reference is non-const only because EntityAdmin's component accessors are; the renderer never writes it. Draw order is unchanged, and so is behaviour. GameWorldView.cpp is 1431 lines, from 3265 when this branch started. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
@@ -329,7 +329,11 @@ Buildings and the belt subsystem stay outside any entity model regardless of wha
|
|||||||
|
|
||||||
## Rendering
|
## Rendering
|
||||||
|
|
||||||
The game world is rendered by a single `GameWorldView` widget that inherits `QOpenGLWidget` and uses `QPainter` for all drawing. This gives the same imperative paint API as a plain `QWidget` with GPU acceleration, comfortably handling the expected scale (hundreds of ships, thousands of belt items) without blocking the main thread on CPU rasterization.
|
The game world is drawn into a single `GameWorldView` widget that inherits `QOpenGLWidget` and uses `QPainter` for all drawing. This gives the same imperative paint API as a plain `QWidget` with GPU acceleration, comfortably handling the expected scale (hundreds of ships, thousands of belt items) without blocking the main thread on CPU rasterization.
|
||||||
|
|
||||||
|
The drawing itself lives in `WorldRenderer`, not in the widget. `paintGL` is a call sequence: build the frame's `WorldCoordinates`, hand the renderer a `WorldRenderFrame`, then draw the screen-anchored chrome. The split is the world-space / screen-space line: the renderer draws everything positioned in tiles, while the pause and deconstruct vignettes and the replay overlay — which are anchored to the viewport and never took a `WorldCoordinates` — stay with the widget.
|
||||||
|
|
||||||
|
`WorldRenderFrame` is what makes the renderer independent of the widget. The renderer reads the simulation directly, but everything else it draws is interaction state the widget owns — the selection, the active build mode, live beams, the copy-settings feedback, the box-select rectangle. Those are gathered into the frame each `paintGL` and passed by reference, so the renderer keeps no copy that a later click could invalidate. The renderer knows nothing about input: the widget resolves clicks and hit-tests, and the renderer only draws the result.
|
||||||
|
|
||||||
### Render Loop
|
### Render Loop
|
||||||
|
|
||||||
|
|||||||
@@ -153,3 +153,13 @@ std::optional<QPoint> findTunnelPartner(const TunnelLookup& lookup, QPoint tile,
|
|||||||
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TunnelLookup makeTunnelLookup(const TunnelTileMap& tunnels)
|
||||||
|
{
|
||||||
|
return [&tunnels](QPoint tile) -> std::optional<TunnelTileInfo>
|
||||||
|
{
|
||||||
|
const TunnelTileMap::const_iterator it = tunnels.find(tile);
|
||||||
|
if (it == tunnels.end()) { return std::nullopt; }
|
||||||
|
return it->second;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
@@ -9,6 +10,17 @@
|
|||||||
#include "BuildingType.h"
|
#include "BuildingType.h"
|
||||||
#include "Rotation.h"
|
#include "Rotation.h"
|
||||||
|
|
||||||
|
// QPoint has no operator<, so an explicit ordering is needed to key a map or set
|
||||||
|
// by tile.
|
||||||
|
struct QPointCompare
|
||||||
|
{
|
||||||
|
bool operator()(const QPoint& a, const QPoint& b) const
|
||||||
|
{
|
||||||
|
if (a.x() != b.x()) { return a.x() < b.x(); }
|
||||||
|
return a.y() < b.y();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// A tunnel building occupying a single tile: whether it is an entry or an exit and
|
// A tunnel building occupying a single tile: whether it is an entry or an exit and
|
||||||
// the direction it faces. Used by the tunnel pairing scan (REQ-BLD-TUNNEL-PAIR) and
|
// the direction it faces. Used by the tunnel pairing scan (REQ-BLD-TUNNEL-PAIR) and
|
||||||
// the unified tunnel build mode (REQ-BLD-TUNNEL-MODE).
|
// the unified tunnel build mode (REQ-BLD-TUNNEL-MODE).
|
||||||
@@ -22,6 +34,13 @@ struct TunnelTileInfo
|
|||||||
// direction, or std::nullopt when the tile holds no tunnel building.
|
// direction, or std::nullopt when the tile holds no tunnel building.
|
||||||
using TunnelLookup = std::function<std::optional<TunnelTileInfo>(QPoint)>;
|
using TunnelLookup = std::function<std::optional<TunnelTileInfo>(QPoint)>;
|
||||||
|
|
||||||
|
// Tunnel entries/exits indexed by their single-cell tile (REQ-BLD-TUNNEL-MODE).
|
||||||
|
using TunnelTileMap = std::map<QPoint, TunnelTileInfo, QPointCompare>;
|
||||||
|
|
||||||
|
// Wraps a tunnel tile index in the lookup functor the helpers below take. The
|
||||||
|
// returned functor references `tunnels`, which must outlive it.
|
||||||
|
TunnelLookup makeTunnelLookup(const TunnelTileMap& tunnels);
|
||||||
|
|
||||||
// Steps from `start` in `stepDir` over the tiles at distance 1..maxDistance and
|
// Steps from `start` in `stepDir` over the tiles at distance 1..maxDistance and
|
||||||
// returns the first tile whose tunnel faces `targetFacing`. Tunnel buildings facing
|
// returns the first tile whose tunnel faces `targetFacing`. Tunnel buildings facing
|
||||||
// any other direction are skipped, mirroring the "stop at the first same-direction
|
// any other direction are skipped, mirroring the "stop at the first same-direction
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "FactoryQueries.h"
|
#include "FactoryQueries.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
#include "PortGeometry.h"
|
#include "PortGeometry.h"
|
||||||
@@ -179,3 +180,61 @@ getSiteSplitterInfo(const FactoryState& state, const GameConfig& config, Buildin
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<BuildingId> buildingsInBox(const FactoryState& state,
|
||||||
|
QPoint cornerA, QPoint cornerB)
|
||||||
|
{
|
||||||
|
const int x0 = std::min(cornerA.x(), cornerB.x());
|
||||||
|
const int y0 = std::min(cornerA.y(), cornerB.y());
|
||||||
|
const int x1 = std::max(cornerA.x(), cornerB.x());
|
||||||
|
const int y1 = std::max(cornerA.y(), cornerB.y());
|
||||||
|
|
||||||
|
const auto covers = [&](const std::vector<QPoint>& bodyCells)
|
||||||
|
{
|
||||||
|
for (const QPoint& cell : bodyCells)
|
||||||
|
{
|
||||||
|
if (cell.x() >= x0 && cell.x() <= x1
|
||||||
|
&& cell.y() >= y0 && cell.y() <= y1)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<BuildingId> ids;
|
||||||
|
for (const Building& building : getAllBuildings(state))
|
||||||
|
{
|
||||||
|
if (covers(building.bodyCells)) { ids.push_back(building.id); }
|
||||||
|
}
|
||||||
|
for (const ConstructionSite& site : getAllSites(state))
|
||||||
|
{
|
||||||
|
if (covers(site.bodyCells)) { ids.push_back(site.id); }
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
TunnelTileMap collectTunnelTiles(const FactoryState& state)
|
||||||
|
{
|
||||||
|
// Index every tunnel entry/exit — built or still a construction site — by its
|
||||||
|
// single-cell tile, so a just-placed tunnel (not yet constructed) is matchable
|
||||||
|
// (REQ-BLD-TUNNEL-MODE, REQ-BLD-TUNNEL-SELECT-HIGHLIGHT).
|
||||||
|
TunnelTileMap tunnels;
|
||||||
|
for (const Building& building : getAllBuildings(state))
|
||||||
|
{
|
||||||
|
if (building.type == BuildingType::TunnelEntry
|
||||||
|
|| building.type == BuildingType::TunnelExit)
|
||||||
|
{
|
||||||
|
tunnels[building.anchor] = TunnelTileInfo{building.type, building.rotation};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const ConstructionSite& site : getAllSites(state))
|
||||||
|
{
|
||||||
|
if (site.type == BuildingType::TunnelEntry
|
||||||
|
|| site.type == BuildingType::TunnelExit)
|
||||||
|
{
|
||||||
|
tunnels[site.anchor] = TunnelTileInfo{site.type, site.rotation};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tunnels;
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include "FactoryState.h"
|
#include "FactoryState.h"
|
||||||
#include "GameConfig.h"
|
#include "GameConfig.h"
|
||||||
#include "Port.h"
|
#include "Port.h"
|
||||||
|
#include "TunnelCompletion.h"
|
||||||
|
|
||||||
// Queries and operations over the factory's world data that need nothing but that
|
// Queries and operations over the factory's world data that need nothing but that
|
||||||
// data — no config, no belts, no RNG. Free functions rather than BuildingSystem
|
// data — no config, no belts, no RNG. Free functions rather than BuildingSystem
|
||||||
@@ -69,3 +70,13 @@ std::vector<Port> getInputPorts(const FactoryState& state, const GameConfig& con
|
|||||||
std::optional<BeltSystem::SplitterInfo> getSiteSplitterInfo(const FactoryState& state,
|
std::optional<BeltSystem::SplitterInfo> getSiteSplitterInfo(const FactoryState& state,
|
||||||
const GameConfig& config,
|
const GameConfig& config,
|
||||||
BuildingId id);
|
BuildingId id);
|
||||||
|
|
||||||
|
// Ids of all buildings and construction sites whose footprint intersects the tile
|
||||||
|
// box spanned by the two (unordered) corner tiles (REQ-UI-MULTI-SELECT,
|
||||||
|
// REQ-BLD-DECONSTRUCT-BOX).
|
||||||
|
std::vector<BuildingId> buildingsInBox(const FactoryState& state,
|
||||||
|
QPoint cornerA, QPoint cornerB);
|
||||||
|
|
||||||
|
// Every tunnel entry and exit, built or still a construction site, indexed by its
|
||||||
|
// single-cell tile. Shared by the placement preview and the selection highlight.
|
||||||
|
TunnelTileMap collectTunnelTiles(const FactoryState& state);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.h
|
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/InputMapper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/InputMapper.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WorldPrimitives.h
|
${CMAKE_CURRENT_SOURCE_DIR}/WorldPrimitives.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/WorldRenderer.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.h
|
||||||
@@ -32,6 +33,7 @@ SET(SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/InputMapper.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/InputMapper.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WorldPrimitives.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/WorldPrimitives.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/WorldRenderer.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectedBuildingPanel.cpp
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -61,6 +61,7 @@
|
|||||||
#include "VisualsConfig.h"
|
#include "VisualsConfig.h"
|
||||||
#include "WorldCamera.h"
|
#include "WorldCamera.h"
|
||||||
#include "WorldCoordinates.h"
|
#include "WorldCoordinates.h"
|
||||||
|
#include "WorldRenderer.h"
|
||||||
|
|
||||||
struct Command;
|
struct Command;
|
||||||
struct ParsedReplay;
|
struct ParsedReplay;
|
||||||
@@ -68,19 +69,6 @@ class ItemIconCache;
|
|||||||
class ReplayPlayer;
|
class ReplayPlayer;
|
||||||
class Simulation;
|
class Simulation;
|
||||||
class QPainter;
|
class QPainter;
|
||||||
class QSvgRenderer;
|
|
||||||
|
|
||||||
struct QPointCompare
|
|
||||||
{
|
|
||||||
bool operator()(const QPoint& a, const QPoint& b) const
|
|
||||||
{
|
|
||||||
if (a.x() != b.x()) { return a.x() < b.x(); }
|
|
||||||
return a.y() < b.y();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tunnel entries/exits indexed by their single-cell tile (REQ-BLD-TUNNEL-MODE).
|
|
||||||
using TunnelTileMap = std::map<QPoint, TunnelTileInfo, QPointCompare>;
|
|
||||||
|
|
||||||
class GameWorldView : public QOpenGLWidget,
|
class GameWorldView : public QOpenGLWidget,
|
||||||
public CombinedEventHandler<BeamFiredEvent,
|
public CombinedEventHandler<BeamFiredEvent,
|
||||||
@@ -159,29 +147,8 @@ private:
|
|||||||
// Used to pre-validate placements whose UI follow-up depends on success.
|
// Used to pre-validate placements whose UI follow-up depends on success.
|
||||||
bool canAfford(BuildingType type) const;
|
bool canAfford(BuildingType type) const;
|
||||||
|
|
||||||
// World-space drawing takes the frame's transform (see getCoordinates()) rather
|
// Screen-anchored chrome, drawn after the world (see WorldRenderer): these
|
||||||
// than reaching for the scroll position and viewport size itself; the
|
// need no world transform, which is exactly why they stayed here.
|
||||||
// screen-space draws below need neither.
|
|
||||||
void drawTiles(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawPortItems(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawBuildings(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawSelectionHighlights(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawCopyConfigFeedback(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawStations(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawBeltItems(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
// Draws a single item centered at widget-space `center`, spanning `halfPx` in
|
|
||||||
// each direction (a half-tile). Uses the item's icon when one exists
|
|
||||||
// (REQ-UI-ITEM-ICON), otherwise falls back to the colored square from
|
|
||||||
// visuals.toml. Shared by drawBeltItems and drawPortItems.
|
|
||||||
void drawWorldItem(QPainter& painter, const std::string& itemId,
|
|
||||||
QPointF center, float halfPx);
|
|
||||||
void drawDebris(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawShips(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawDebugSensorRanges(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawDebugTargetLines(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawDebugOverlay(QPainter& painter);
|
|
||||||
void drawBeams(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawOverlays(QPainter& painter, const WorldCoordinates& coordinates);
|
|
||||||
void drawScreenSpace(QPainter& painter);
|
void drawScreenSpace(QPainter& painter);
|
||||||
// Vignette-style border shown while the game is paused (speed 0x, REQ-UI-PAUSE-BORDER)
|
// Vignette-style border shown while the game is paused (speed 0x, REQ-UI-PAUSE-BORDER)
|
||||||
// to make the paused state hard to miss: a black frame whose alpha fades from 50% at
|
// to make the paused state hard to miss: a black frame whose alpha fades from 50% at
|
||||||
@@ -197,17 +164,14 @@ private:
|
|||||||
void drawVignetteBorder(QPainter& painter, const QColor& edgeColor);
|
void drawVignetteBorder(QPainter& painter, const QColor& edgeColor);
|
||||||
void drawReplayOverlay(QPainter& painter);
|
void drawReplayOverlay(QPainter& painter);
|
||||||
|
|
||||||
|
// Gathers the interaction state the renderer needs for this frame.
|
||||||
|
WorldRenderFrame makeRenderFrame() const;
|
||||||
|
|
||||||
// The world <-> widget transform for the current viewport size and scroll
|
// The world <-> widget transform for the current viewport size and scroll
|
||||||
// position. Cheap to build and deliberately not cached: it is a snapshot that
|
// position. Cheap to build and deliberately not cached: it is a snapshot that
|
||||||
// a resize or a scroll invalidates, so every user takes a fresh one.
|
// a resize or a scroll invalidates, so every user takes a fresh one.
|
||||||
WorldCoordinates getCoordinates() const;
|
WorldCoordinates getCoordinates() const;
|
||||||
|
|
||||||
// Widget-space rectangle covering a building or construction site's footprint,
|
|
||||||
// or nullopt if the id resolves to neither. Shared by the selection highlight
|
|
||||||
// and the copy-settings feedback (REQ-BLD-COPY-CONFIG-FEEDBACK).
|
|
||||||
std::optional<QRectF> footprintWidgetRect(const WorldCoordinates& coordinates,
|
|
||||||
BuildingId id) const;
|
|
||||||
|
|
||||||
float getAsteroidLeftEdge() const;
|
float getAsteroidLeftEdge() const;
|
||||||
float getEnemyStationRightEdge() const;
|
float getEnemyStationRightEdge() const;
|
||||||
// The camera's current pan limits, read fresh from the simulation each frame:
|
// The camera's current pan limits, read fresh from the simulation each frame:
|
||||||
@@ -218,36 +182,9 @@ private:
|
|||||||
bool canPlaceBuildingHere(BuildingType type, QPoint anchor, Rotation rot) const;
|
bool canPlaceBuildingHere(BuildingType type, QPoint anchor, Rotation rot) const;
|
||||||
std::optional<BuildingId> buildingAtTile(QPoint tile) const;
|
std::optional<BuildingId> buildingAtTile(QPoint tile) const;
|
||||||
std::optional<BuildingId> siteAtTile(QPoint tile) const;
|
std::optional<BuildingId> siteAtTile(QPoint tile) const;
|
||||||
// Ids of all buildings and construction sites whose footprint intersects
|
|
||||||
// the tile box spanned by the two (unordered) corner tiles.
|
|
||||||
std::vector<BuildingId> buildingsInBox(QPoint cornerA, QPoint cornerB) const;
|
|
||||||
|
|
||||||
void drawPortGlyph(QPainter& painter, const WorldCoordinates& coordinates,
|
|
||||||
QPoint tile, Rotation direction, const QColor& color,
|
|
||||||
bool centered);
|
|
||||||
|
|
||||||
void drawBuildingGhost(QPainter& painter, const WorldCoordinates& coordinates,
|
|
||||||
BuildingType type,
|
|
||||||
QPoint anchorTile, Rotation rotation, bool valid,
|
|
||||||
bool showPortTargetGlyphs);
|
|
||||||
|
|
||||||
// Loads the per-building world icons (REQ-UI-WORLD-ICON) from
|
|
||||||
// <configDir>/../icons/buildings once at construction. Only the building
|
|
||||||
// types with a world icon are loaded (production buildings, HQ, stations);
|
|
||||||
// belts, splitters, and tunnels are deliberately excluded so their
|
|
||||||
// orientation stays readable. The SVG's chip background is stripped; the
|
|
||||||
// glyph is pre-rendered in both white and dark ink for auto-contrast.
|
|
||||||
void loadBuildingIcons(const std::string& configDir);
|
|
||||||
// Draws a building's world icon glyph centered in box, choosing the white or
|
|
||||||
// dark pre-rendered variant by fill luminance so it stays legible. Returns
|
|
||||||
// false if the type has no world icon (caller falls back to the text glyph).
|
|
||||||
bool drawBuildingIcon(QPainter& painter, const WorldCoordinates& coordinates,
|
|
||||||
BuildingType type,
|
|
||||||
const QRectF& box, const QColor& fill) const;
|
|
||||||
|
|
||||||
void placeBlueprintAtTile(QPoint center);
|
void placeBlueprintAtTile(QPoint center);
|
||||||
|
|
||||||
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
|
||||||
// Drops despawned or fully-collected debris from the selection
|
// Drops despawned or fully-collected debris from the selection
|
||||||
// (REQ-UI-DEBRIS-CLICK-SELECT). Called each frame from onFrame().
|
// (REQ-UI-DEBRIS-CLICK-SELECT). Called each frame from onFrame().
|
||||||
void pruneDespawnedDebris();
|
void pruneDespawnedDebris();
|
||||||
@@ -266,17 +203,6 @@ private:
|
|||||||
// ghost tile, rotation, and sub-tile cursor position, storing both on the build
|
// ghost tile, rotation, and sub-tile cursor position, storing both on the build
|
||||||
// mode controller. Only meaningful in tunnel mode (REQ-BLD-TUNNEL-MODE).
|
// mode controller. Only meaningful in tunnel mode (REQ-BLD-TUNNEL-MODE).
|
||||||
void updateTunnelGhost();
|
void updateTunnelGhost();
|
||||||
// Indexes every tunnel entry/exit — built or still a construction site — by its
|
|
||||||
// single-cell tile. Shared by the placement preview and the selection highlight.
|
|
||||||
TunnelTileMap collectTunnelTiles() const;
|
|
||||||
// Wraps a tunnel tile index in the lookup functor the TunnelCompletion helpers
|
|
||||||
// take. The returned functor references `tunnels`, which must outlive it.
|
|
||||||
static TunnelLookup makeTunnelLookup(const TunnelTileMap& tunnels);
|
|
||||||
// Draws the green connection highlight for every selected tunnel end that has a
|
|
||||||
// matching end (REQ-BLD-TUNNEL-SELECT-HIGHLIGHT).
|
|
||||||
void drawSelectedTunnelConnections(QPainter& painter,
|
|
||||||
const WorldCoordinates& coordinates);
|
|
||||||
|
|
||||||
// Belt drag placement (REQ-BLD-BELT-DRAG).
|
// Belt drag placement (REQ-BLD-BELT-DRAG).
|
||||||
// Recomputes the drag path from its anchor to cursorTile using the current ghost
|
// Recomputes the drag path from its anchor to cursorTile using the current ghost
|
||||||
// orientation, and stores it on the build mode controller.
|
// orientation, and stores it on the build mode controller.
|
||||||
@@ -297,12 +223,6 @@ private:
|
|||||||
// The mode transitions themselves live on m_buildMode.
|
// The mode transitions themselves live on m_buildMode.
|
||||||
void rotateGhost(bool clockwise);
|
void rotateGhost(bool clockwise);
|
||||||
|
|
||||||
struct ActiveBeam
|
|
||||||
{
|
|
||||||
BeamFiredEvent event;
|
|
||||||
QVector2D targetOffset;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Beam lifetime in game ticks so beams freeze with the simulation when
|
// Beam lifetime in game ticks so beams freeze with the simulation when
|
||||||
// paused or slowed, instead of fading on wall-clock time (REQ-SHP-FIRING-BEAM).
|
// paused or slowed, instead of fading on wall-clock time (REQ-SHP-FIRING-BEAM).
|
||||||
static constexpr Tick kBeamLifetimeTicks = secondsToTicks(0.3);
|
static constexpr Tick kBeamLifetimeTicks = secondsToTicks(0.3);
|
||||||
@@ -311,22 +231,6 @@ private:
|
|||||||
const GameConfig* m_config;
|
const GameConfig* m_config;
|
||||||
const VisualsConfig* m_visuals;
|
const VisualsConfig* m_visuals;
|
||||||
|
|
||||||
// World icon glyph renderers per building type (REQ-UI-WORLD-ICON), in a
|
|
||||||
// white and a dark variant so drawBuildingIcon can auto-contrast against the
|
|
||||||
// building's fill. Rendered as vector at the view scale each draw so they
|
|
||||||
// stay crisp. Populated once by loadBuildingIcons().
|
|
||||||
struct BuildingIconRenderers
|
|
||||||
{
|
|
||||||
std::unique_ptr<QSvgRenderer> white;
|
|
||||||
std::unique_ptr<QSvgRenderer> dark;
|
|
||||||
};
|
|
||||||
std::map<BuildingType, BuildingIconRenderers> m_buildingIcons;
|
|
||||||
|
|
||||||
// Per-item icon cache (REQ-UI-ITEM-ICON), shared window-wide and owned by
|
|
||||||
// MainWindow. Shared draw path for belt and port items; pixmaps are cached
|
|
||||||
// per target size.
|
|
||||||
ItemIconCache* m_itemIcons;
|
|
||||||
|
|
||||||
// Funnels all player input into the single Simulation::apply chokepoint.
|
// Funnels all player input into the single Simulation::apply chokepoint.
|
||||||
CommandManager m_commandManager;
|
CommandManager m_commandManager;
|
||||||
// A Reset command was enqueued; reset the view after the next drain applies it.
|
// A Reset command was enqueued; reset the view after the next drain applies it.
|
||||||
@@ -335,6 +239,10 @@ private:
|
|||||||
// live input is ignored (the CommandManager is in replay mode).
|
// live input is ignored (the CommandManager is in replay mode).
|
||||||
std::unique_ptr<ReplayPlayer> m_replayPlayer;
|
std::unique_ptr<ReplayPlayer> m_replayPlayer;
|
||||||
|
|
||||||
|
// Draws the world; this widget supplies the interaction state each frame and
|
||||||
|
// keeps only the screen-anchored chrome for itself.
|
||||||
|
std::unique_ptr<WorldRenderer> m_renderer;
|
||||||
|
|
||||||
TickDriver m_tickDriver;
|
TickDriver m_tickDriver;
|
||||||
QElapsedTimer m_frameTimer;
|
QElapsedTimer m_frameTimer;
|
||||||
std::mt19937 m_rng;
|
std::mt19937 m_rng;
|
||||||
@@ -366,11 +274,6 @@ private:
|
|||||||
// pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in
|
// pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in
|
||||||
// wall-clock time so the flash plays at a fixed length regardless of game speed
|
// wall-clock time so the flash plays at a fixed length regardless of game speed
|
||||||
// (and while paused).
|
// (and while paused).
|
||||||
struct CopyConfigFlash
|
|
||||||
{
|
|
||||||
BuildingId id;
|
|
||||||
qint64 remainingMs;
|
|
||||||
};
|
|
||||||
std::vector<CopyConfigFlash> m_copyConfigFlashes;
|
std::vector<CopyConfigFlash> m_copyConfigFlashes;
|
||||||
static constexpr qint64 kCopyFlashDurationMs = 300;
|
static constexpr qint64 kCopyFlashDurationMs = 300;
|
||||||
|
|
||||||
|
|||||||
1219
src/ui/WorldRenderer.cpp
Normal file
1219
src/ui/WorldRenderer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
183
src/ui/WorldRenderer.h
Normal file
183
src/ui/WorldRenderer.h
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QPoint>
|
||||||
|
#include <QPointF>
|
||||||
|
#include <QRectF>
|
||||||
|
#include <QVector2D>
|
||||||
|
|
||||||
|
#include "BeamFiredEvent.h"
|
||||||
|
#include "BuildModeController.h"
|
||||||
|
#include "BuildingConfig.h"
|
||||||
|
#include "BuildingId.h"
|
||||||
|
#include "BuildingType.h"
|
||||||
|
#include "Rotation.h"
|
||||||
|
#include "SelectionController.h"
|
||||||
|
#include "VisualsConfig.h"
|
||||||
|
#include "WorldCoordinates.h"
|
||||||
|
|
||||||
|
class ItemIconCache;
|
||||||
|
class Simulation;
|
||||||
|
class QPainter;
|
||||||
|
class QSvgRenderer;
|
||||||
|
|
||||||
|
// A beam still being drawn. Lifetime is counted in game ticks so beams freeze with
|
||||||
|
// the simulation when it is paused or slowed (REQ-SHP-FIRING-BEAM); the view owns
|
||||||
|
// the ageing, the renderer only draws what it is given.
|
||||||
|
struct ActiveBeam
|
||||||
|
{
|
||||||
|
BeamFiredEvent event;
|
||||||
|
QVector2D targetOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Brief outline flash shown on a building when settings are copied from it or
|
||||||
|
// pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in
|
||||||
|
// wall-clock time so the flash plays at a fixed length regardless of game speed
|
||||||
|
// (and while paused).
|
||||||
|
struct CopyConfigFlash
|
||||||
|
{
|
||||||
|
BuildingId id;
|
||||||
|
qint64 remainingMs;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Everything the renderer draws that the simulation does not know about: what the
|
||||||
|
// player has selected, which build mode is active, and the other transient bits of
|
||||||
|
// interaction state the view owns. Assembled fresh each frame and passed by
|
||||||
|
// reference, so the renderer holds no copy that could go stale.
|
||||||
|
struct WorldRenderFrame
|
||||||
|
{
|
||||||
|
const SelectionController& selection;
|
||||||
|
const BuildModeController& buildMode;
|
||||||
|
const std::vector<ActiveBeam>& beams;
|
||||||
|
const std::optional<BuildingConfig>& copiedConfig;
|
||||||
|
const std::vector<CopyConfigFlash>& copyConfigFlashes;
|
||||||
|
bool isBoxSelecting;
|
||||||
|
QPoint boxStartTile;
|
||||||
|
QPoint boxCurrentTile;
|
||||||
|
bool isDebugDrawEnabled;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draws the game world: terrain, buildings, items, ships, effects and the build
|
||||||
|
// overlays, in back-to-front order (see the Layer Order section of
|
||||||
|
// docs/architecture.md).
|
||||||
|
//
|
||||||
|
// Reads the simulation and never writes it, and knows nothing about input — the
|
||||||
|
// view resolves clicks and owns the interaction state, and hands the parts the
|
||||||
|
// renderer needs over in a WorldRenderFrame. Screen-anchored chrome that is not
|
||||||
|
// part of the world (the pause and deconstruct vignettes, the replay overlay)
|
||||||
|
// stays with the view, which is also why those never took a WorldCoordinates.
|
||||||
|
class WorldRenderer
|
||||||
|
{
|
||||||
|
// Not a QObject, but drawDebugOverlay puts text on screen, so it still needs
|
||||||
|
// tr() for translation (REQ-UI-...): this declares the tr() overloads without
|
||||||
|
// dragging in the meta-object system.
|
||||||
|
Q_DECLARE_TR_FUNCTIONS(WorldRenderer)
|
||||||
|
|
||||||
|
public:
|
||||||
|
// `itemIcons` is the window-wide per-item icon cache (REQ-UI-ITEM-ICON); not
|
||||||
|
// owned, must outlive this renderer. `configDir` is used once, to load the
|
||||||
|
// per-building world icons.
|
||||||
|
WorldRenderer(Simulation& sim, const VisualsConfig& visuals,
|
||||||
|
ItemIconCache* itemIcons, const std::string& configDir);
|
||||||
|
~WorldRenderer();
|
||||||
|
|
||||||
|
void render(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void drawTiles(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawBuildings(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawSelectionHighlights(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawCopyConfigFeedback(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawPortItems(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawStations(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawBeltItems(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawDebris(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawShips(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawDebugSensorRanges(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawDebugTargetLines(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawBeams(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawSelectedTunnelConnections(QPainter& painter,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
void drawOverlays(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
const WorldRenderFrame& frame);
|
||||||
|
// Screen-anchored, but drawn mid-world so ships and effects still paint over
|
||||||
|
// it, as they always have.
|
||||||
|
void drawDebugOverlay(QPainter& painter);
|
||||||
|
|
||||||
|
// Draws a single item centered at widget-space `center`, spanning `halfPx` in
|
||||||
|
// each direction (a half-tile). Uses the item's icon when one exists
|
||||||
|
// (REQ-UI-ITEM-ICON), otherwise falls back to the colored square from
|
||||||
|
// visuals.toml. Shared by drawBeltItems and drawPortItems.
|
||||||
|
void drawWorldItem(QPainter& painter, const std::string& itemId,
|
||||||
|
QPointF center, float halfPx);
|
||||||
|
void drawPortGlyph(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
QPoint tile, Rotation direction, const QColor& color,
|
||||||
|
bool centered);
|
||||||
|
void drawBuildingGhost(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
BuildingType type, QPoint anchorTile, Rotation rotation,
|
||||||
|
bool valid, bool showPortTargetGlyphs);
|
||||||
|
|
||||||
|
// Loads the per-building world icons (REQ-UI-WORLD-ICON) from
|
||||||
|
// <configDir>/../icons/buildings once at construction. Only the building
|
||||||
|
// types with a world icon are loaded (production buildings, HQ, stations);
|
||||||
|
// belts, splitters, and tunnels are deliberately excluded so their
|
||||||
|
// orientation stays readable. The SVG's chip background is stripped; the
|
||||||
|
// glyph is pre-rendered in both white and dark ink for auto-contrast.
|
||||||
|
void loadBuildingIcons(const std::string& configDir);
|
||||||
|
// Draws a building's world icon glyph centered in box, choosing the white or
|
||||||
|
// dark pre-rendered variant by fill luminance so it stays legible. Returns
|
||||||
|
// false if the type has no world icon (caller falls back to the text glyph).
|
||||||
|
bool drawBuildingIcon(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
|
BuildingType type, const QRectF& box,
|
||||||
|
const QColor& fill) const;
|
||||||
|
|
||||||
|
// Widget-space rectangle covering a building or construction site's footprint,
|
||||||
|
// or nullopt if the id resolves to neither. Shared by the selection highlight
|
||||||
|
// and the copy-settings feedback (REQ-BLD-COPY-CONFIG-FEEDBACK).
|
||||||
|
std::optional<QRectF> footprintWidgetRect(const WorldCoordinates& coordinates,
|
||||||
|
BuildingId id) const;
|
||||||
|
|
||||||
|
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
||||||
|
|
||||||
|
// Non-const only because EntityAdmin's component accessors are; the renderer
|
||||||
|
// reads the simulation and never writes it.
|
||||||
|
Simulation& m_sim;
|
||||||
|
const VisualsConfig& m_visuals;
|
||||||
|
|
||||||
|
// Per-item icon cache (REQ-UI-ITEM-ICON), shared window-wide and owned by
|
||||||
|
// MainWindow. Shared draw path for belt and port items; pixmaps are cached
|
||||||
|
// per target size.
|
||||||
|
ItemIconCache* m_itemIcons;
|
||||||
|
|
||||||
|
// World icon glyph renderers per building type (REQ-UI-WORLD-ICON), in a
|
||||||
|
// white and a dark variant so drawBuildingIcon can auto-contrast against the
|
||||||
|
// building's fill. Rendered as vector at the view scale each draw so they
|
||||||
|
// stay crisp. Populated once by loadBuildingIcons().
|
||||||
|
struct BuildingIconRenderers
|
||||||
|
{
|
||||||
|
std::unique_ptr<QSvgRenderer> white;
|
||||||
|
std::unique_ptr<QSvgRenderer> dark;
|
||||||
|
};
|
||||||
|
std::map<BuildingType, BuildingIconRenderers> m_buildingIcons;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user