diff --git a/config/roads.yaml b/config/roads.yaml index 8ef8636..e761b53 100644 --- a/config/roads.yaml +++ b/config/roads.yaml @@ -12,7 +12,10 @@ types: color: "#c49a62" border_color: "#6b4b23" width: 3.5 - world_width: 0.11 + world_width: 0.068 + border_width: 0.022 + texture_scale: 2.4 + opacity: 0.90 cost: 5 stone: id: "stone" @@ -21,7 +24,10 @@ types: color: "#9aa1a8" border_color: "#464b50" width: 4.0 - world_width: 0.13 + world_width: 0.078 + border_width: 0.020 + texture_scale: 2.8 + opacity: 0.94 cost: 15 rail: id: "rail" @@ -30,5 +36,8 @@ types: color: "#3a3c40" border_color: "#181a1c" width: 4.5 - world_width: 0.15 + world_width: 0.086 + border_width: 0.024 + texture_scale: 3.0 + opacity: 0.96 cost: 30 diff --git a/qscenerenderer.cpp b/qscenerenderer.cpp index a3e0c1d..9c8a7ac 100644 --- a/qscenerenderer.cpp +++ b/qscenerenderer.cpp @@ -3079,25 +3079,11 @@ void QSceneRenderer::paintGL() } } - // 2. Render 3D Roads (above terrain elevation, writes depth so units and cities render on top) + // 2. Render 3D Units & Cities (Cylinders & Bastions; roads are rendered directly in the tile shader texture) 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(), diff --git a/roadconfig.cpp b/roadconfig.cpp index 88fa7bc..1823d98 100644 --- a/roadconfig.cpp +++ b/roadconfig.cpp @@ -17,7 +17,10 @@ RoadConfigManager::RoadConfigManager() dirt.colorStr = "#c49a62"; dirt.borderColorStr = "#6b4b23"; dirt.width = 3.5f; - dirt.worldWidth = 0.11f; + dirt.worldWidth = 0.068f; + dirt.borderWidth = 0.022f; + dirt.textureScale = 2.4f; + dirt.opacity = 0.90f; dirt.cost = 5; m_types["dirt"] = dirt; @@ -28,7 +31,10 @@ RoadConfigManager::RoadConfigManager() stone.colorStr = "#9aa1a8"; stone.borderColorStr = "#464b50"; stone.width = 4.0f; - stone.worldWidth = 0.13f; + stone.worldWidth = 0.078f; + stone.borderWidth = 0.020f; + stone.textureScale = 2.8f; + stone.opacity = 0.94f; stone.cost = 15; m_types["stone"] = stone; @@ -39,7 +45,10 @@ RoadConfigManager::RoadConfigManager() rail.colorStr = "#3a3c40"; rail.borderColorStr = "#181a1c"; rail.width = 4.5f; - rail.worldWidth = 0.15f; + rail.worldWidth = 0.086f; + rail.borderWidth = 0.024f; + rail.textureScale = 3.0f; + rail.opacity = 0.96f; rail.cost = 30; m_types["rail"] = rail; } @@ -75,7 +84,10 @@ bool RoadConfigManager::loadConfigFile(const QString &filePath) rCfg.colorStr = it.value().hasChild("color") ? it.value()["color"].asString("#c49a62") : "#c49a62"; rCfg.borderColorStr = it.value().hasChild("border_color") ? it.value()["border_color"].asString("#6b4b23") : "#6b4b23"; rCfg.width = it.value().hasChild("width") ? it.value()["width"].asFloat(3.5f) : 3.5f; - rCfg.worldWidth = it.value().hasChild("world_width") ? it.value()["world_width"].asFloat(0.11f) : 0.11f; + rCfg.worldWidth = it.value().hasChild("world_width") ? it.value()["world_width"].asFloat(0.068f) : 0.068f; + rCfg.borderWidth = it.value().hasChild("border_width") ? it.value()["border_width"].asFloat(0.022f) : 0.022f; + rCfg.textureScale = it.value().hasChild("texture_scale") ? it.value()["texture_scale"].asFloat(2.4f) : 2.4f; + rCfg.opacity = it.value().hasChild("opacity") ? it.value()["opacity"].asFloat(0.90f) : 0.90f; rCfg.cost = it.value().hasChild("cost") ? it.value()["cost"].asInt(5) : 5; m_types[rCfg.id] = rCfg; } diff --git a/roadconfig.h b/roadconfig.h index 8d0a9f1..97d732f 100644 --- a/roadconfig.h +++ b/roadconfig.h @@ -30,7 +30,10 @@ struct RoadTypeConfig { QString colorStr = "#c49a62"; QString borderColorStr = "#6b4b23"; float width = 3.5f; - float worldWidth = 0.11f; + float worldWidth = 0.068f; + float borderWidth = 0.022f; + float textureScale = 2.4f; + float opacity = 0.90f; int cost = 5; QColor getColor() const { return QColor(colorStr); } diff --git a/shaders/simple.frag b/shaders/simple.frag index 9200858..23c65f6 100644 --- a/shaders/simple.frag +++ b/shaders/simple.frag @@ -17,6 +17,16 @@ uniform int isFarm; uniform float farmTextureScale; uniform int farmMappingMode; +uniform int hasRoad; +uniform int roadMask; +uniform int roadType; +uniform float roadRadius; +uniform float roadBorderWidth; +uniform float roadTextureScale; +uniform float roadOpacity; +uniform vec3 roadColor; +uniform vec3 roadBorderColor; + uniform float time; uniform int riverMask; @@ -79,6 +89,19 @@ const vec2 EDGE_MIDPOINTS[6] = vec2[6]( vec2(-0.375, -0.21650635) ); +float bezierCurveDist(vec2 p, vec2 p0, vec2 p2) { + vec2 s0 = p0; + vec2 s1 = 0.5625 * p0 + 0.0625 * p2; + vec2 s2 = 0.2500 * p0 + 0.2500 * p2; + vec2 s3 = 0.0625 * p0 + 0.5625 * p2; + vec2 s4 = p2; + float d0 = distToSegment(p, s0, s1); + float d1 = distToSegment(p, s1, s2); + float d2 = distToSegment(p, s2, s3); + float d3 = distToSegment(p, s3, s4); + return min(min(d0, d1), min(d2, d3)); +} + float getRiverDistance(vec2 p, int mask) { if (mask == 0) return 999.0; @@ -92,18 +115,7 @@ float getRiverDistance(vec2 p, int mask) { } if (count == 2) { - vec2 p0 = EDGE_MIDPOINTS[edges[0]]; - vec2 p2 = EDGE_MIDPOINTS[edges[1]]; - vec2 s0 = p0; - vec2 s1 = 0.5625 * p0 + 0.0625 * p2; - vec2 s2 = 0.2500 * p0 + 0.2500 * p2; - vec2 s3 = 0.0625 * p0 + 0.5625 * p2; - vec2 s4 = p2; - float d0 = distToSegment(p, s0, s1); - float d1 = distToSegment(p, s1, s2); - float d2 = distToSegment(p, s2, s3); - float d3 = distToSegment(p, s3, s4); - return min(min(d0, d1), min(d2, d3)); + return bezierCurveDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]]); } else { float minDist = 999.0; for (int i = 0; i < count; ++i) { @@ -114,6 +126,35 @@ float getRiverDistance(vec2 p, int mask) { } } +float getRoadDistance(vec2 p, int mask) { + int edges[6]; + int count = 0; + for (int e = 0; e < 6; ++e) { + if ((mask & (1 << e)) != 0) { + edges[count] = e; + count++; + } + } + + if (count == 0) { + // Unconnected road on a single tile: short natural country track segment at tile center + return distToSegment(p, vec2(-0.14, -0.05), vec2(0.14, 0.05)); + } else if (count == 1) { + return distToSegment(p, EDGE_MIDPOINTS[edges[0]], vec2(0.0)); + } else if (count == 2) { + return bezierCurveDist(p, EDGE_MIDPOINTS[edges[0]], EDGE_MIDPOINTS[edges[1]]); + } else { + float minDist = 999.0; + for (int i = 0; i < count; ++i) { + minDist = min(minDist, distToSegment(p, EDGE_MIDPOINTS[edges[i]], vec2(0.0))); + for (int j = i + 1; j < count; ++j) { + minDist = min(minDist, bezierCurveDist(p, EDGE_MIDPOINTS[edges[i]], EDGE_MIDPOINTS[edges[j]])); + } + } + return minDist; + } +} + void main() { vec3 norm = normalize(vNormal); @@ -276,6 +317,61 @@ void main() { vec4 nonSnowLand = mix(biomeGround, cliffRockColor, rockWeight); vec4 landColor = mix(nonSnowLand, snowColor, snowWeight); + // 6b. Road Texture Integration (blended directly into the tile surface texture) + if (hasRoad == 1 && oceanWaterFac < 0.95) { + float rawRoadDist = getRoadDistance(fragVert.xy, roadMask); + float rScale = max(0.5, roadTextureScale); + vec2 uvRoad = vWorldPos.xy * rScale; + + vec3 soilTex = mix(texture(texSand, uvRoad).rgb, texture(texTundra, uvRoad * 1.45).rgb, 0.45); + vec3 stoneTex = texture(texRock, uvRoad * 1.35).rgb; + + // Subtle organic edge roughness from soil texture grain so road borders blend naturally into terrain + float edgeGrain = (soilTex.r - 0.5) * 0.008; + float dRoad = max(0.0, rawRoadDist + edgeGrain); + + float rCore = max(0.014, roadRadius); + float rOuter = rCore + max(0.006, roadBorderWidth); + + float roadCoreFac = 1.0 - smoothstep(rCore * 0.50, rCore, dRoad); + float roadTotalFac = 1.0 - smoothstep(rCore * 0.65, rOuter, dRoad); + + if (roadTotalFac > 0.001) { + vec3 roadSurf; + if (roadType == 1) { + // Stone / Paved road: rich stone texture modulated by configured colors + vec3 stoneCenter = mix(stoneTex, roadColor, 0.55) * (0.75 + 0.50 * stoneTex.g); + vec3 stoneEdge = mix(stoneTex, roadBorderColor, 0.65) * 0.85; + roadSurf = mix(stoneEdge, stoneCenter, roadCoreFac); + } else if (roadType == 2) { + // Railroad: gravel ballast bed with dark twin rail tracks + vec3 ballast = mix(stoneTex, roadBorderColor, 0.60); + float railLine = smoothstep(rCore * 0.35, rCore * 0.50, rawRoadDist) + * (1.0 - smoothstep(rCore * 0.55, rCore * 0.72, rawRoadDist)); + roadSurf = mix(ballast, roadColor * (0.8 + 0.4 * stoneTex.r), max(roadCoreFac * 0.6, railLine)); + } else { + // Dirt road: natural packed earth & wheel ruts blended with soil texture + float rutPattern = smoothstep(rCore * 0.25, rCore * 0.65, rawRoadDist); + vec3 packedEarth = mix(soilTex, roadColor, 0.52) * (0.78 + 0.45 * soilTex.r); + vec3 wornShoulder = mix(soilTex, roadBorderColor, 0.60) * (0.72 + 0.40 * soilTex.g); + vec3 innerTrack = mix(packedEarth, wornShoulder, rutPattern * 0.45); + roadSurf = mix(wornShoulder, innerTrack, roadCoreFac); + } + + // If crossing a river, render a wooden/stone bridge deck and suppress river water under the bridge + if (riverWaterFac > 0.01) { + vec3 bridgeCol = (roadType >= 1) + ? mix(stoneTex, vec3(0.58, 0.56, 0.53), 0.65) + : mix(soilTex, vec3(0.55, 0.38, 0.20), 0.70); + roadSurf = mix(roadSurf, bridgeCol, riverWaterFac); + waterFac *= (1.0 - roadTotalFac * 0.95); + } + + float blendAlpha = roadTotalFac * clamp(roadOpacity, 0.0, 1.0) * (1.0 - oceanWaterFac); + landColor.rgb = mix(landColor.rgb, roadSurf, blendAlpha); + } + } + // 7. Combine Land and Sea vec4 surfaceColor = mix(landColor, waterColor, waterFac); diff --git a/tile.cpp b/tile.cpp index c552d83..47f28ac 100644 --- a/tile.cpp +++ b/tile.cpp @@ -127,31 +127,31 @@ void Tile::init(QSceneRenderer* s, int x, int y) buildHeights(); if (Tile::m_image_grass == NULL) - Tile::m_image_grass = new QImage("/home/bart/project/tiletest/images/grass.jpg"); + Tile::m_image_grass = new QImage("/home/bart/git/Ascent-of-Man/images/grass.jpg"); if (Tile::m_image_border == NULL) - Tile::m_image_border = new QImage("/home/bart/project/tiletest/images/border.png"); + Tile::m_image_border = new QImage("/home/bart/git/Ascent-of-Man/images/border.png"); if (Tile::m_image_rock == NULL) - Tile::m_image_rock = new QImage("/home/bart/project/tiletest/images/Texturelabs_Stone_136XL_seamless.jpg"); + Tile::m_image_rock = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Stone_136XL_seamless.jpg"); if (Tile::m_image_sand == NULL) - Tile::m_image_sand = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_126XL_seamless.jpg"); + Tile::m_image_sand = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Soil_126XL_seamless.jpg"); if (Tile::m_image_water == NULL) - Tile::m_image_water = new QImage("/home/bart/project/tiletest/images/Texturelabs_Water_152XL_seamless.jpg"); + Tile::m_image_water = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Water_152XL_seamless.jpg"); if (Tile::m_image_snow == NULL) - Tile::m_image_snow = new QImage("/home/bart/project/tiletest/images/Texturelabs_Water_155XL_seamless.jpg"); + Tile::m_image_snow = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Water_155XL_seamless.jpg"); if (Tile::m_image_typemap == NULL) - Tile::m_image_typemap = new QImage("/home/bart/project/tiletest/images/typemap.png"); + Tile::m_image_typemap = new QImage("/home/bart/git/Ascent-of-Man/images/typemap.png"); if (Tile::m_image_sahara == NULL) - Tile::m_image_sahara = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_133L_seamless.jpg"); + Tile::m_image_sahara = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Soil_133L_seamless.jpg"); if (Tile::m_image_tundra == NULL) - Tile::m_image_tundra = new QImage("/home/bart/project/tiletest/images/Texturelabs_Soil_135XL_seamless.jpg"); + Tile::m_image_tundra = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Soil_135XL_seamless.jpg"); if (Tile::m_image_redsand == NULL) - Tile::m_image_redsand = new QImage("/home/bart/project/tiletest/images/Texturelabs_RedSand_seamless.jpg"); + Tile::m_image_redsand = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_RedSand_seamless.jpg"); if (Tile::m_image_redrock == NULL) - Tile::m_image_redrock = new QImage("/home/bart/project/tiletest/images/Texturelabs_RedRock_seamless.jpg"); + Tile::m_image_redrock = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_RedRock_seamless.jpg"); if (Tile::m_image_forest == NULL) - Tile::m_image_forest = new QImage("/home/bart/project/tiletest/images/Texturelabs_Forest_seamless.jpg"); + Tile::m_image_forest = new QImage("/home/bart/git/Ascent-of-Man/images/Texturelabs_Forest_seamless.jpg"); if (Tile::m_image_farm == NULL) - Tile::m_image_farm = new QImage("/home/bart/project/tiletest/images/farm.jpeg"); + Tile::m_image_farm = new QImage("/home/bart/git/Ascent-of-Man/images/farm.jpeg"); if (Tile::m_texture_grass == NULL) { m_texture_grass = new QOpenGLTexture(*m_image_grass); @@ -247,8 +247,8 @@ void Tile::init(QSceneRenderer* s, int x, int y) if (first){ m_program = new QOpenGLShaderProgram(); - m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/project/tiletest/shaders/simple.vert"); - m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/project/tiletest/shaders/simple.frag"); + m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/git/Ascent-of-Man/shaders/simple.vert"); + m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/git/Ascent-of-Man/shaders/simple.frag"); m_program->link(); } m_program->bind(); @@ -361,6 +361,34 @@ void Tile::paint(QMatrix4x4 p, QVector3D& cameraPosition,QVector3D& cameraUpDire m_program->setUniformValue("farmTextureScale", fScale); m_program->setUniformValue("farmMappingMode", mappingMode); + const TileRoad *road = GameManager::instance().getRoadAt(m_x, m_y); + if (road != nullptr) { + const RoadTypeConfig *rCfg = GameManager::instance().roadConfigs().getRoadType(road->roadTypeId); + int rType = 0; + if (road->roadTypeId == "stone") rType = 1; + else if (road->roadTypeId == "rail") rType = 2; + + float rRad = rCfg ? (rCfg->worldWidth * 0.5f) : 0.034f; + float rBorder = rCfg ? rCfg->borderWidth : 0.022f; + float rScale = rCfg ? rCfg->textureScale : 2.4f; + float rOpacity = rCfg ? rCfg->opacity : 0.90f; + QColor col = rCfg ? rCfg->getColor() : QColor("#c49a62"); + QColor bCol = rCfg ? rCfg->getBorderColor() : QColor("#6b4b23"); + + m_program->setUniformValue("hasRoad", 1); + m_program->setUniformValue("roadMask", (int)road->connectionMask); + m_program->setUniformValue("roadType", rType); + m_program->setUniformValue("roadRadius", rRad); + m_program->setUniformValue("roadBorderWidth", rBorder); + m_program->setUniformValue("roadTextureScale", rScale); + m_program->setUniformValue("roadOpacity", rOpacity); + m_program->setUniformValue("roadColor", QVector3D(col.redF(), col.greenF(), col.blueF())); + m_program->setUniformValue("roadBorderColor", QVector3D(bCol.redF(), bCol.greenF(), bCol.blueF())); + } else { + m_program->setUniformValue("hasRoad", 0); + m_program->setUniformValue("roadMask", 0); + } + m_program->setUniformValue("time", time); m_program->setUniformValue("riverMask", (int)m_riverMask); m_object.bind();