diff --git a/gamemanager.cpp b/gamemanager.cpp index c059a50..7b5c5f8 100644 --- a/gamemanager.cpp +++ b/gamemanager.cpp @@ -1059,7 +1059,7 @@ void GameManager::commandBuildRoadToTileSelected(int targetX, int targetY, Matri // Pathfind from current tile to target tile std::vector> route = Pathfinder::findPath( selected->tileX, selected->tileY, targetX, targetY, - MovementDomain::Land, mapPoints, numPoints05, &selected->config + mapPoints, numPoints05, MovementDomain::Land, &selected->config ); if (route.empty()) { diff --git a/gamemanager.h b/gamemanager.h index cb427a7..f8be1c4 100644 --- a/gamemanager.h +++ b/gamemanager.h @@ -24,23 +24,6 @@ enum class ActionMode { BuildRoad }; -struct TileRoad { - QString roadTypeId = "dirt"; // "dirt", "stone", "rail" - int tileX = 0; - int tileY = 0; - int playerId = 1; - unsigned char connectionMask = 0; // 6 bits: bit e (0..5) is 1 if connected to neighbor across edge e - QVector3D worldPos; - - bool hasConnection(int edge) const { - return (connectionMask & (1 << (edge % 6))) != 0; - } - void setConnection(int edge, bool connected) { - if (connected) connectionMask |= (1 << (edge % 6)); - else connectionMask &= ~(1 << (edge % 6)); - } -}; - struct TileImprovement { QString improvementId; // "farm" int tileX; diff --git a/qscenerenderer.cpp b/qscenerenderer.cpp index 7b2b271..61fae17 100644 --- a/qscenerenderer.cpp +++ b/qscenerenderer.cpp @@ -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, m_point, m_numPoints05, MovementDomain::Land, &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); diff --git a/qscenerenderer.h b/qscenerenderer.h index 30c1c0c..0e2a2bc 100644 --- a/qscenerenderer.h +++ b/qscenerenderer.h @@ -57,7 +57,6 @@ protected: void drawFloatingTexts(QPainter &painter, const QMatrix4x4 &vMatrix); // City & Territory Overlays void drawCityBorders(QPainter &painter, const QMatrix4x4 &vMatrix); - void drawRoadsOverlay(QPainter &painter, const QMatrix4x4 &vMatrix); void drawImprovementsOverlay(QPainter &painter, const QMatrix4x4 &vMatrix); void drawCityViewOverlay(QPainter &painter, const QMatrix4x4 &vMatrix); bool screenToHex(const QPoint &screenPos, const QMatrix4x4 &vMatrix, int &outX, int &outY); diff --git a/roadconfig.h b/roadconfig.h index 534373e..8d0a9f1 100644 --- a/roadconfig.h +++ b/roadconfig.h @@ -4,6 +4,24 @@ #include #include #include +#include + +struct TileRoad { + QString roadTypeId = "dirt"; // "dirt", "stone", "rail" + int tileX = 0; + int tileY = 0; + int playerId = 1; + unsigned char connectionMask = 0; // 6 bits: bit e (0..5) is 1 if connected to neighbor across edge e + QVector3D worldPos; + + bool hasConnection(int edge) const { + return (connectionMask & (1 << (edge % 6))) != 0; + } + void setConnection(int edge, bool connected) { + if (connected) connectionMask |= (1 << (edge % 6)); + else connectionMask &= ~(1 << (edge % 6)); + } +}; struct RoadTypeConfig { QString id = "dirt"; diff --git a/unitrenderer.cpp b/unitrenderer.cpp index e187247..31c8c91 100644 --- a/unitrenderer.cpp +++ b/unitrenderer.cpp @@ -9,13 +9,17 @@ struct VertexPN { }; UnitRenderer::UnitRenderer() - : m_program(nullptr), m_vertexCount(0), m_initialized(false) + : m_program(nullptr), m_vertexCount(0), m_initialized(false), m_roadBuffersInitialized(false) { } UnitRenderer::~UnitRenderer() { if (m_program) delete m_program; + if (m_roadBuffersInitialized) { + m_roadVao.destroy(); + m_roadVbo.destroy(); + } } void UnitRenderer::initialize() @@ -29,6 +33,12 @@ void UnitRenderer::initialize() m_program->link(); buildCylinderMesh(); + + m_roadVao.create(); + m_roadVbo.create(); + m_roadVbo.setUsagePattern(QOpenGLBuffer::DynamicDraw); + m_roadBuffersInitialized = true; + m_initialized = true; } @@ -86,6 +96,220 @@ void UnitRenderer::buildCylinderMesh() m_vbo.release(); } +void UnitRenderer::renderRoads( + const std::map, TileRoad> &roads, + const RoadConfigManager &roadConfigs, + Matrix2D *mapPoints, + int numPoints05, + const QMatrix4x4 &projection, + const QMatrix4x4 &vMatrix, + const QVector3D &lightDir, + int cityViewRadius, + int cityViewTileX, + int cityViewTileY) +{ + if (!m_initialized || !m_program || roads.empty()) return; + + if (!m_roadBuffersInitialized) { + m_roadVao.create(); + m_roadVbo.create(); + m_roadVbo.setUsagePattern(QOpenGLBuffer::DynamicDraw); + m_roadBuffersInitialized = true; + } + + float meanderAmp = roadConfigs.meanderAmplitude(); + int subdivisions = std::max(2, roadConfigs.meanderSubdivisions()); + + struct RoadBatchGeometry { + std::vector casingVertices; + std::vector surfaceVertices; + }; + std::unordered_map batches; + + for (const auto &pair : roads) { + const TileRoad &road = pair.second; + if (cityViewRadius >= 0) { + if (Pathfinder::hexDistance(cityViewTileX, cityViewTileY, road.tileX, road.tileY) > cityViewRadius) { + continue; + } + } + + const RoadTypeConfig *rCfg = roadConfigs.getRoadType(road.roadTypeId); + float roadW = rCfg ? rCfg->worldWidth : 0.11f; + float casingW = roadW + 0.035f; + + QVector2D center = Pathfinder::hexToWorld(road.tileX, road.tileY); + + RoadBatchGeometry &batch = batches[road.roadTypeId.toStdString()]; + + // If isolated tile with no connections, draw small circular depot / disk + if (road.connectionMask == 0) { + float gz = Unit::getTerrainHeight(center.x(), center.y(), mapPoints, numPoints05); + const int capSectors = 12; + const float pi = 3.14159265359f; + float rSurf = roadW * 0.9f; + float rCase = casingW * 0.9f; + + for (int s = 0; s < capSectors; ++s) { + float a1 = 2.0f * pi * s / capSectors; + float a2 = 2.0f * pi * (s + 1) / capSectors; + batch.casingVertices.push_back({center.x(), center.y(), gz + 0.012f, 0, 0, 1}); + batch.casingVertices.push_back({center.x() + std::cos(a1) * rCase, center.y() + std::sin(a1) * rCase, gz + 0.012f, 0, 0, 1}); + batch.casingVertices.push_back({center.x() + std::cos(a2) * rCase, center.y() + std::sin(a2) * rCase, gz + 0.012f, 0, 0, 1}); + } + for (int s = 0; s < capSectors; ++s) { + float a1 = 2.0f * pi * s / capSectors; + float a2 = 2.0f * pi * (s + 1) / capSectors; + batch.surfaceVertices.push_back({center.x(), center.y(), gz + 0.016f, 0, 0, 1}); + batch.surfaceVertices.push_back({center.x() + std::cos(a1) * rSurf, center.y() + std::sin(a1) * rSurf, gz + 0.016f, 0, 0, 1}); + batch.surfaceVertices.push_back({center.x() + std::cos(a2) * rSurf, center.y() + std::sin(a2) * rSurf, gz + 0.016f, 0, 0, 1}); + } + continue; + } + + // Center junction cap (circular disk covering junction gaps) + { + float gz = Unit::getTerrainHeight(center.x(), center.y(), mapPoints, numPoints05); + const int capSectors = 12; + const float pi = 3.14159265359f; + float rSurf = (roadW * 0.5f) * 1.15f; + float rCase = (casingW * 0.5f) * 1.15f; + + for (int s = 0; s < capSectors; ++s) { + float a1 = 2.0f * pi * s / capSectors; + float a2 = 2.0f * pi * (s + 1) / capSectors; + batch.casingVertices.push_back({center.x(), center.y(), gz + 0.012f, 0, 0, 1}); + batch.casingVertices.push_back({center.x() + std::cos(a1) * rCase, center.y() + std::sin(a1) * rCase, gz + 0.012f, 0, 0, 1}); + batch.casingVertices.push_back({center.x() + std::cos(a2) * rCase, center.y() + std::sin(a2) * rCase, gz + 0.012f, 0, 0, 1}); + + batch.surfaceVertices.push_back({center.x(), center.y(), gz + 0.016f, 0, 0, 1}); + batch.surfaceVertices.push_back({center.x() + std::cos(a1) * rSurf, center.y() + std::sin(a1) * rSurf, gz + 0.016f, 0, 0, 1}); + batch.surfaceVertices.push_back({center.x() + std::cos(a2) * rSurf, center.y() + std::sin(a2) * rSurf, gz + 0.016f, 0, 0, 1}); + } + } + + // For each connected edge, generate meandering ribbon quad strip + for (int e = 0; e < 6; ++e) { + if (!road.hasConnection(e)) continue; + + QVector2D endP = center + getHexEdgeMidpoint(e); + QVector2D dir = endP - center; + float len = dir.length(); + QVector2D perp = (len > 0.0001f) ? QVector2D(-dir.y() / len, dir.x() / len) : QVector2D(0, 0); + + float seed = std::sin(float(road.tileX * 41 + road.tileY * 113 + e * 29)) * 43758.5453f; + float meanderSign = (seed - std::floor(seed) > 0.5f) ? 1.0f : -1.0f; + + const float pi = 3.14159265359f; + std::vector spinePoints; + spinePoints.reserve(subdivisions + 1); + + for (int i = 0; i <= subdivisions; ++i) { + float t = (float)i / (float)subdivisions; + float s = meanderSign * meanderAmp * std::sin(t * pi); + spinePoints.push_back(center + dir * t + perp * s); + } + + float halfSurf = roadW * 0.5f; + float halfCase = casingW * 0.5f; + + for (int i = 0; i < subdivisions; ++i) { + QVector2D p0 = spinePoints[i]; + QVector2D p1 = spinePoints[i + 1]; + + QVector2D segDir = p1 - p0; + float segLen = segDir.length(); + QVector2D segPerp = (segLen > 0.0001f) ? QVector2D(-segDir.y() / segLen, segDir.x() / segLen) : perp; + + float z0 = Unit::getTerrainHeight(p0.x(), p0.y(), mapPoints, numPoints05); + float z1 = Unit::getTerrainHeight(p1.x(), p1.y(), mapPoints, numPoints05); + + float cz0 = z0 + 0.012f; + float cz1 = z1 + 0.012f; + QVector3D cL0(p0.x() - segPerp.x() * halfCase, p0.y() - segPerp.y() * halfCase, cz0); + QVector3D cR0(p0.x() + segPerp.x() * halfCase, p0.y() + segPerp.y() * halfCase, cz0); + QVector3D cL1(p1.x() - segPerp.x() * halfCase, p1.y() - segPerp.y() * halfCase, cz1); + QVector3D cR1(p1.x() + segPerp.x() * halfCase, p1.y() + segPerp.y() * halfCase, cz1); + + batch.casingVertices.push_back({cL0.x(), cL0.y(), cL0.z(), 0, 0, 1}); + batch.casingVertices.push_back({cR0.x(), cR0.y(), cR0.z(), 0, 0, 1}); + batch.casingVertices.push_back({cR1.x(), cR1.y(), cR1.z(), 0, 0, 1}); + + batch.casingVertices.push_back({cL0.x(), cL0.y(), cL0.z(), 0, 0, 1}); + batch.casingVertices.push_back({cR1.x(), cR1.y(), cR1.z(), 0, 0, 1}); + batch.casingVertices.push_back({cL1.x(), cL1.y(), cL1.z(), 0, 0, 1}); + + float sz0 = z0 + 0.016f; + float sz1 = z1 + 0.016f; + QVector3D sL0(p0.x() - segPerp.x() * halfSurf, p0.y() - segPerp.y() * halfSurf, sz0); + QVector3D sR0(p0.x() + segPerp.x() * halfSurf, p0.y() + segPerp.y() * halfSurf, sz0); + QVector3D sL1(p1.x() - segPerp.x() * halfSurf, p1.y() - segPerp.y() * halfSurf, sz1); + QVector3D sR1(p1.x() + segPerp.x() * halfSurf, p1.y() + segPerp.y() * halfSurf, sz1); + + batch.surfaceVertices.push_back({sL0.x(), sL0.y(), sL0.z(), 0, 0, 1}); + batch.surfaceVertices.push_back({sR0.x(), sR0.y(), sR0.z(), 0, 0, 1}); + batch.surfaceVertices.push_back({sR1.x(), sR1.y(), sR1.z(), 0, 0, 1}); + + batch.surfaceVertices.push_back({sL0.x(), sL0.y(), sL0.z(), 0, 0, 1}); + batch.surfaceVertices.push_back({sR1.x(), sR1.y(), sR1.z(), 0, 0, 1}); + batch.surfaceVertices.push_back({sL1.x(), sL1.y(), sL1.z(), 0, 0, 1}); + } + } + } + + if (batches.empty()) return; + + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-2.0f, -2.0f); + + m_program->bind(); + m_program->setUniformValue("u_lightDir", lightDir); + QMatrix4x4 model; + m_program->setUniformValue("modelMatrix", model); + QMatrix4x4 mvp = projection * vMatrix; + m_program->setUniformValue("MVP", mvp); + + m_roadVao.bind(); + m_roadVbo.bind(); + + for (auto &bPair : batches) { + const RoadTypeConfig *rCfg = roadConfigs.getRoadType(QString::fromStdString(bPair.first)); + QColor caseCol = rCfg ? rCfg->getBorderColor() : QColor(107, 75, 35); + QColor surfCol = rCfg ? rCfg->getColor() : QColor(196, 154, 98); + + RoadBatchGeometry &geom = bPair.second; + + // Draw Casing + if (!geom.casingVertices.empty()) { + m_roadVbo.allocate(geom.casingVertices.data(), (int)(geom.casingVertices.size() * sizeof(VertexPN))); + m_program->enableAttributeArray(0); + m_program->setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(VertexPN)); + m_program->enableAttributeArray(1); + m_program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, sizeof(VertexPN)); + m_program->setUniformValue("u_color", QVector4D(caseCol.redF(), caseCol.greenF(), caseCol.blueF(), 1.0f)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)geom.casingVertices.size()); + } + + // Draw Surface + if (!geom.surfaceVertices.empty()) { + m_roadVbo.allocate(geom.surfaceVertices.data(), (int)(geom.surfaceVertices.size() * sizeof(VertexPN))); + m_program->enableAttributeArray(0); + m_program->setAttributeBuffer(0, GL_FLOAT, 0, 3, sizeof(VertexPN)); + m_program->enableAttributeArray(1); + m_program->setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, sizeof(VertexPN)); + m_program->setUniformValue("u_color", QVector4D(surfCol.redF(), surfCol.greenF(), surfCol.blueF(), 1.0f)); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)geom.surfaceVertices.size()); + } + } + + m_roadVbo.release(); + m_roadVao.release(); + m_program->release(); + glDisable(GL_POLYGON_OFFSET_FILL); +} + void UnitRenderer::renderUnits( const std::vector> &units, const std::vector &cities, diff --git a/unitrenderer.h b/unitrenderer.h index 3b4e87b..031c721 100644 --- a/unitrenderer.h +++ b/unitrenderer.h @@ -10,9 +10,13 @@ #include #include #include +#include #include "unit.h" #include "player.h" #include "city.h" +#include "roadconfig.h" +#include "matrix2d.h" +#include "terrain.h" class UnitRenderer : protected QOpenGLFunctions_4_0_Core { public: @@ -20,6 +24,18 @@ public: ~UnitRenderer(); void initialize(); + void renderRoads( + const std::map, TileRoad> &roads, + const RoadConfigManager &roadConfigs, + Matrix2D *mapPoints, + int numPoints05, + const QMatrix4x4 &projection, + const QMatrix4x4 &vMatrix, + const QVector3D &lightDir, + int cityViewRadius = -1, + int cityViewTileX = -1, + int cityViewTileY = -1 + ); void renderUnits( const std::vector> &units, const std::vector &cities, @@ -49,6 +65,10 @@ private: QOpenGLVertexArrayObject m_vao; int m_vertexCount; bool m_initialized; + + QOpenGLBuffer m_roadVbo; + QOpenGLVertexArrayObject m_roadVao; + bool m_roadBuffersInitialized; }; #endif // UNITRENDERER_H