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:
2026-08-05 19:09:40 +02:00
parent d54bf3587b
commit 9aed6d844e
10 changed files with 1531 additions and 1342 deletions

View File

@@ -329,7 +329,11 @@ Buildings and the belt subsystem stay outside any entity model regardless of wha
## 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