extract WorldRenderer
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();
|
||||||
@@ -267,17 +204,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.
|
||||||
@@ -298,12 +224,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);
|
||||||
@@ -312,22 +232,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.
|
||||||
@@ -336,6 +240,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;
|
||||||
@@ -367,11 +275,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