Implement tile-targeted road route construction and OpenGL 3D layered road rendering

This commit is contained in:
bart
2026-11-08 22:45:12 +01:00
parent c47673575b
commit 745089df7c
6 changed files with 421 additions and 25 deletions
+158 -6
View File
@@ -862,7 +862,107 @@ void QSceneRenderer::drawPathAndRange(QPainter &painter, Unit *unit, const QMatr
}
}
// 2. Attack range visualization if Attack mode is active
// 2. Road Route visualization if unit is constructing a multi-tile road route
if (unit->hasRoadRoute()) {
QPainterPath routeLine;
bool first = true;
for (size_t i = unit->roadRouteIndex; i < unit->roadRoute.size(); ++i) {
auto hex = unit->roadRoute[i];
QVector2D w = Pathfinder::hexToWorld(hex.first, hex.second);
float z = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
bool inFront = false;
QPointF sPos = UnitRenderer::projectToScreen(QVector3D(w.x(), w.y(), z + 0.06f), Projection, vMatrix, width(), height(), inFront);
if (inFront) {
if (first) {
routeLine.moveTo(sPos);
first = false;
} else {
routeLine.lineTo(sPos);
}
painter.setPen(QPen(QColor(140, 95, 30, 200), 1.2));
painter.setBrush(QColor(255, 215, 60, 220));
painter.drawEllipse(sPos, 3.5, 3.5);
}
}
painter.setPen(QPen(QColor(255, 195, 45, 230), 2.8, Qt::DashLine));
painter.setBrush(Qt::NoBrush);
painter.drawPath(routeLine);
auto destHex = unit->roadRoute.back();
QVector2D destW = Pathfinder::hexToWorld(destHex.first, destHex.second);
float destZ = Unit::getTerrainHeight(destW.x(), destW.y(), m_point, m_numPoints05);
bool inFront = false;
QPointF sDest = UnitRenderer::projectToScreen(QVector3D(destW.x(), destW.y(), destZ + 0.06f), Projection, vMatrix, width(), height(), inFront);
if (inFront) {
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.008f) + 1.0f) * 0.5f;
painter.setPen(QPen(QColor(255, 215, 50, (int)(180 + pulse * 75)), 2.2));
painter.setBrush(QColor(255, 190, 40, 45));
painter.drawEllipse(sDest, 14.0f, 14.0f);
painter.drawLine(sDest.x() - 18, sDest.y(), sDest.x() + 18, sDest.y());
painter.drawLine(sDest.x(), sDest.y() - 18, sDest.x(), sDest.y() + 18);
}
}
// 3. Road Build targeting preview when in ActionMode::BuildRoad
if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad && unit->config.canBuildRoad()) {
int hx = -1, hy = -1;
if (screenToHex(m_mouseCurrentPos, vMatrix, hx, hy) && (hx != unit->tileX || hy != unit->tileY)) {
if (TerrainMovementManager::instance().isHexPassable(hx, hy, MovementDomain::Land, m_point, m_numPoints05, &unit->config)) {
auto preview = Pathfinder::findPath(unit->tileX, unit->tileY, hx, hy, MovementDomain::Land, m_point, m_numPoints05, &unit->config);
if (preview.size() > 1) {
QPainterPath previewLine;
bool first = true;
for (const auto &p : preview) {
QVector2D w = Pathfinder::hexToWorld(p.first, p.second);
float z = Unit::getTerrainHeight(w.x(), w.y(), m_point, m_numPoints05);
bool inFront = false;
QPointF sPos = UnitRenderer::projectToScreen(QVector3D(w.x(), w.y(), z + 0.06f), Projection, vMatrix, width(), height(), inFront);
if (inFront) {
if (first) {
previewLine.moveTo(sPos);
first = false;
} else {
previewLine.lineTo(sPos);
}
painter.setPen(QPen(QColor(160, 110, 40, 200), 1.0));
painter.setBrush(QColor(255, 230, 90, 230));
painter.drawEllipse(sPos, 3.5, 3.5);
}
}
painter.setPen(QPen(QColor(255, 220, 60, 240), 3.0, Qt::DashLine));
painter.setBrush(Qt::NoBrush);
painter.drawPath(previewLine);
// Destination reticle
QVector2D destW = Pathfinder::hexToWorld(hx, hy);
float destZ = Unit::getTerrainHeight(destW.x(), destW.y(), m_point, m_numPoints05);
bool inFront = false;
QPointF sDest = UnitRenderer::projectToScreen(QVector3D(destW.x(), destW.y(), destZ + 0.06f), Projection, vMatrix, width(), height(), inFront);
if (inFront) {
float pulse = (std::sin(m_elapsedTimer.elapsed() * 0.01f) + 1.0f) * 0.5f;
painter.setPen(QPen(QColor(255, 225, 70, (int)(180 + pulse * 75)), 2.5));
painter.setBrush(QColor(255, 210, 50, 50));
painter.drawEllipse(sDest, 16.0f, 16.0f);
painter.drawLine(sDest.x() - 22, sDest.y(), sDest.x() + 22, sDest.y());
painter.drawLine(sDest.x(), sDest.y() - 22, sDest.x(), sDest.y() + 22);
QString targetTip = Localization::instance().get("ui.unit.btn_road_route", "Route (%1 hexes)").arg(preview.size() - 1);
painter.setFont(QFont("sans-serif", 8, QFont::Bold));
QFontMetrics fm = painter.fontMetrics();
int tw = fm.horizontalAdvance(targetTip) + 16;
QRect tipRect((int)sDest.x() - tw / 2, (int)sDest.y() - 34, tw, 20);
painter.setBrush(QColor(20, 24, 34, 230));
painter.setPen(QPen(QColor(255, 215, 60), 1.2));
painter.drawRoundedRect(tipRect, 4, 4);
painter.setPen(QColor(255, 235, 120));
painter.drawText(tipRect, Qt::AlignCenter, targetTip);
}
}
}
}
}
// 4. Attack range visualization if Attack mode is active
if (GameManager::instance().currentActionMode() == ActionMode::Attack) {
int range = unit->config.attackRange;
for (const auto &u : GameManager::instance().getUnits()) {
@@ -2367,7 +2467,19 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
} else if (ab.actionId == "boerderij_bouwen" || ab.actionId == "build_farm") {
GameManager::instance().commandBuildFarmSelected(m_point, m_numPoints05);
} else if (ab.actionId == "build_road" || ab.actionId == "weg_bouwen") {
GameManager::instance().commandBuildRoadSelected(m_point, m_numPoints05);
Unit *sel = GameManager::instance().getSelectedUnit();
if (sel && sel->hasRoadRoute()) {
sel->clearRoadRoute();
sel->cancelBuilding();
GameManager::instance().setActionMode(ActionMode::None);
GameManager::instance().addFloatingText(
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
} else if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad) {
GameManager::instance().setActionMode(ActionMode::None);
} else {
GameManager::instance().setActionMode(ActionMode::BuildRoad);
}
}
update();
return;
@@ -2422,6 +2534,11 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
GameManager::instance().setActionMode(ActionMode::None);
update();
return;
} else if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad) {
GameManager::instance().commandBuildRoadToTileSelected(hitX, hitY, m_point, m_numPoints05);
GameManager::instance().setActionMode(ActionMode::None);
update();
return;
} else if (GameManager::instance().currentActionMode() == ActionMode::Attack) {
Unit *enemy = GameManager::instance().getUnitAt(hitX, hitY);
if (enemy && enemy->playerId != selected->playerId) {
@@ -2450,6 +2567,11 @@ void QSceneRenderer::mousePressEvent(QMouseEvent *event)
update();
return;
} else if (event->button() == Qt::RightButton) {
if (GameManager::instance().currentActionMode() == ActionMode::BuildRoad) {
GameManager::instance().setActionMode(ActionMode::None);
update();
return;
}
// Right click: Attack enemy unit or move selected unit directly to hex!
if (selected) {
Unit *enemy = GameManager::instance().getUnitAt(hitX, hitY);
@@ -2627,11 +2749,28 @@ void QSceneRenderer::keyPressEvent(QKeyEvent *e)
GameManager::instance().commandBuildFarmSelected(m_point, m_numPoints05);
}
else if (e->key() == Qt::Key_R && sel && sel->config.canBuildRoad()) {
GameManager::instance().commandBuildRoadSelected(m_point, m_numPoints05);
if (sel->hasRoadRoute()) {
sel->clearRoadRoute();
sel->cancelBuilding();
GameManager::instance().setActionMode(ActionMode::None);
GameManager::instance().addFloatingText(
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
} else {
GameManager::instance().setActionMode(
GameManager::instance().currentActionMode() == ActionMode::BuildRoad ? ActionMode::None : ActionMode::BuildRoad
);
}
}
else if (e->key() == Qt::Key_Escape) {
if (GameManager::instance().currentActionMode() != ActionMode::None) {
GameManager::instance().setActionMode(ActionMode::None);
} else if (sel && sel->hasRoadRoute()) {
sel->clearRoadRoute();
sel->cancelBuilding();
GameManager::instance().addFloatingText(
Localization::instance().get("ui.unit.road_route_cancelled", "Road route cancelled"),
sel->worldPos + QVector3D(0, 0, 0.8f), QColor(255, 120, 100));
} else if (sel && sel->hasQueuedAction()) {
sel->clearActionQueue();
GameManager::instance().addFloatingText(
@@ -2787,11 +2926,25 @@ void QSceneRenderer::paintGL()
}
}
// 2. Render 3D Units & Cities (Cylinders & Bastions)
// 2. Render 3D Roads (above terrain elevation, writes depth so units and cities render on top)
float fSunAngle = 45.0f;
QVector3D vSunPos(-sin(fSunAngle * PI / 180.0f), -cos(fSunAngle * PI / 180.0f), 0.6f);
vSunPos.normalize();
m_unitRenderer.renderRoads(
GameManager::instance().getTileRoads(),
GameManager::instance().roadConfigs(),
m_point,
m_numPoints05,
Projection,
vMatrix,
vSunPos,
cityViewRadius,
cityView ? cityView->tileX : -1,
cityView ? cityView->tileY : -1
);
// 3. Render 3D Units & Cities (Cylinders & Bastions)
Unit *selected = GameManager::instance().getSelectedUnit();
m_unitRenderer.renderUnits(
GameManager::instance().getUnits(),
@@ -2806,10 +2959,9 @@ void QSceneRenderer::paintGL()
cityView ? cityView->tileY : -1
);
// 3. Render 2D UI & HUD Overlays via QPainter
// 4. Render 2D UI & HUD Overlays via QPainter
QPainter painter(this);
drawCityBorders(painter, vMatrix);
drawRoadsOverlay(painter, vMatrix);
drawImprovementsOverlay(painter, vMatrix);
if (!cityView) {
drawPathAndRange(painter, selected, vMatrix);