Reveal port items in a thin margin at machine edges

Port items (emerging output, incoming input) are now drawn over the
buildings but clipped to the whole view minus each machine's interior
(footprint inset by 0.2 tile). An item therefore shows only near the port
edge — emerging from / sinking into the machine — and stays visible in the
~0.4-tile band at a seam between two directly-coupled buildings
(REQ-MAT-DIRECT-COUPLE), instead of being fully occluded and invisible.

Edges are inset only where the neighbouring cell is not part of the same
building, so interior seams stay filled for multi-cell/L-shaped footprints.
Transport tiles (belt/splitter/tunnel) are not machines and never occlude,
so belt items stay fully visible. Rendering only; no sim change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZR44tA8sn4dPqDzAVXyps
This commit is contained in:
2026-07-14 20:28:32 +02:00
parent 3d2f043f7e
commit 3304170c6b

View File

@@ -20,6 +20,7 @@
#include <QPainter>
#include <QPen>
#include <QPolygonF>
#include <QRegion>
#include <QStringList>
#include <QTimer>
@@ -402,12 +403,12 @@ void GameWorldView::paintGL()
painter.setRenderHint(QPainter::Antialiasing, false);
drawTiles(painter);
// Port items are drawn before the buildings so the building body occludes the
// portion still inside the footprint: items appear to slide out of the output
// port (REQ-MAT-OUTPUT-EMERGE) and into the input port (REQ-MAT-INPUT-INTAKE)
// rather than popping in or out of existence.
drawPortItems(painter);
drawBuildings(painter);
// Port items are drawn over the buildings but clipped to a thin margin at each
// machine's edges (see drawPortItems), so items appear to emerge from / sink
// into the port and stay visible while crossing directly between two touching
// buildings (REQ-MAT-OUTPUT-EMERGE, REQ-MAT-INPUT-INTAKE, REQ-MAT-DIRECT-COUPLE).
drawPortItems(painter);
drawCopyConfigFeedback(painter);
drawStations(painter);
drawBeltItems(painter);
@@ -1162,9 +1163,39 @@ void GameWorldView::drawPortItems(QPainter& painter)
{
const float halfPx = tilePx() * 0.5f * 0.5f;
// Shared with belt items (REQ-GW-TILE-SIZE): a half-tile filled square with an
// outline, occluded by the building drawn afterwards so the item slides out of
// (REQ-MAT-OUTPUT-EMERGE) or into (REQ-MAT-INPUT-INTAKE) the port.
// Port items are drawn over the buildings (drawBuildings runs first) but clipped
// to a thin margin at each machine's edges: the clip region is the whole view
// minus every machine's interior (its footprint inset by kPortMarginTiles). So a
// transiting item shows only near the port edge — appearing to emerge from / sink
// into the machine (REQ-MAT-OUTPUT-EMERGE, REQ-MAT-INPUT-INTAKE) and staying
// visible in the ~2×margin band at a seam between two touching buildings
// (REQ-MAT-DIRECT-COUPLE). Transport tiles are not machines and never occlude, so
// items on belts stay fully visible.
constexpr double kPortMarginTiles = 0.2;
const double margin = kPortMarginTiles * static_cast<double>(tilePx());
QRegion clip(rect());
for (const Building& b : m_sim->buildings().allBuildings())
{
if (b.type == BuildingType::Belt || b.type == BuildingType::Splitter
|| b.type == BuildingType::TunnelEntry || b.type == BuildingType::TunnelExit)
{
continue;
}
const std::set<QPoint, QPointCompare> cells(b.bodyCells.begin(), b.bodyCells.end());
for (const QPoint& cell : b.bodyCells)
{
// Inset an edge only where the neighbouring cell is not part of the same
// building, so interior cell seams stay filled (handles L-shaped footprints).
const double l = cells.count(cell + QPoint(-1, 0)) ? 0.0 : margin;
const double t = cells.count(cell + QPoint( 0, -1)) ? 0.0 : margin;
const double r = cells.count(cell + QPoint( 1, 0)) ? 0.0 : margin;
const double d = cells.count(cell + QPoint( 0, 1)) ? 0.0 : margin;
clip = clip.subtracted(QRegion(tileRect(cell).adjusted(l, t, -r, -d).toRect()));
}
}
// Shared with belt items (REQ-GW-TILE-SIZE): a half-tile filled square + outline.
const std::function<void(const ItemType&, QPointF)> drawItem =
[&](const ItemType& type, QPointF worldPos)
{
@@ -1175,16 +1206,19 @@ void GameWorldView::drawPortItems(QPainter& painter)
const QPointF center = worldToWidget(
QVector2D(static_cast<float>(worldPos.x()),
static_cast<float>(worldPos.y())));
const QRectF rect(center.x() - halfPx, center.y() - halfPx,
halfPx * 2, halfPx * 2);
painter.fillRect(rect, it->second.fill);
const QRectF itemRect(center.x() - halfPx, center.y() - halfPx,
halfPx * 2, halfPx * 2);
painter.fillRect(itemRect, it->second.fill);
painter.setPen(QPen(it->second.outline, 1));
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect);
painter.drawRect(itemRect);
};
painter.save();
painter.setClipRegion(clip);
m_sim->buildings().forEachEmergingItem(drawItem);
m_sim->buildings().forEachIncomingItem(drawItem);
painter.restore();
}
void GameWorldView::drawBeltItems(QPainter& painter)