#include "unitrenderer.h" #include #include struct VertexPN { float x, y, z; float nx, ny, nz; }; UnitRenderer::UnitRenderer() : m_program(nullptr), m_vertexCount(0), m_initialized(false) { } UnitRenderer::~UnitRenderer() { if (m_program) delete m_program; } void UnitRenderer::initialize() { if (m_initialized) return; initializeOpenGLFunctions(); m_program = new QOpenGLShaderProgram(); m_program->addShaderFromSourceFile(QOpenGLShader::Vertex, "/home/bart/project/tiletest/shaders/unit.vert"); m_program->addShaderFromSourceFile(QOpenGLShader::Fragment, "/home/bart/project/tiletest/shaders/unit.frag"); m_program->link(); buildCylinderMesh(); m_initialized = true; } void UnitRenderer::buildCylinderMesh() { std::vector vertices; const int sectors = 24; const float pi = 3.14159265359f; for (int i = 0; i < sectors; ++i) { float a1 = 2.0f * pi * i / sectors; float a2 = 2.0f * pi * (i + 1) / sectors; float c1 = std::cos(a1), s1 = std::sin(a1); float c2 = std::cos(a2), s2 = std::sin(a2); // --- Side Quad (2 Triangles) --- // Tri 1 vertices.push_back({c1, s1, 0.0f, c1, s1, 0.0f}); vertices.push_back({c2, s2, 0.0f, c2, s2, 0.0f}); vertices.push_back({c2, s2, 1.0f, c2, s2, 0.0f}); // Tri 2 vertices.push_back({c1, s1, 0.0f, c1, s1, 0.0f}); vertices.push_back({c2, s2, 1.0f, c2, s2, 0.0f}); vertices.push_back({c1, s1, 1.0f, c1, s1, 0.0f}); // --- Top Cap --- vertices.push_back({0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f}); vertices.push_back({c1, s1, 1.0f, 0.0f, 0.0f, 1.0f}); vertices.push_back({c2, s2, 1.0f, 0.0f, 0.0f, 1.0f}); // --- Bottom Cap --- vertices.push_back({0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f}); vertices.push_back({c2, s2, 0.0f, 0.0f, 0.0f, -1.0f}); vertices.push_back({c1, s1, 0.0f, 0.0f, 0.0f, -1.0f}); } m_vertexCount = (int)vertices.size(); m_vao.create(); m_vao.bind(); m_vbo.create(); m_vbo.bind(); m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw); m_vbo.allocate(vertices.data(), (int)(vertices.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_vao.release(); m_vbo.release(); } void UnitRenderer::renderUnits( const std::vector> &units, const std::vector &cities, const PlayerManager &players, const QMatrix4x4 &projection, const QMatrix4x4 &vMatrix, const QVector3D &lightDir, int selectedUnitId) { if (!m_initialized || !m_program) return; glEnable(GL_DEPTH_TEST); m_program->bind(); m_program->setUniformValue("u_lightDir", lightDir); m_vao.bind(); // 1. Render Cities (as stylized hexagonal / cylindrical city bastions) for (const auto &c : cities) { const Player *p = players.getPlayer(c.playerId); QColor col = p ? p->primaryColor : QColor(100, 100, 100); QMatrix4x4 model; model.translate(c.worldPos.x(), c.worldPos.y(), c.worldPos.z()); model.scale(0.30f, 0.30f, 0.45f); QMatrix4x4 mvp = projection * vMatrix * model; m_program->setUniformValue("modelMatrix", model); m_program->setUniformValue("MVP", mvp); m_program->setUniformValue("u_color", QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f)); glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); } // 2. Render Units (Cylinders in player primary color) const float unitRadius = 0.18f; const float unitHeight = 0.36f; for (const auto &unit : units) { if (unit->isDead()) continue; const Player *player = players.getPlayer(unit->playerId); QColor col = player ? player->primaryColor : QColor(200, 200, 200); // Flash white/red if attacked if (unit->attackFlashTimer > 0.0f) { float f = unit->attackFlashTimer / 0.5f; col = QColor( (int)(col.red() * (1.0f - f) + 255 * f), (int)(col.green() * (1.0f - f) + 255 * f), (int)(col.blue() * (1.0f - f) + 255 * f) ); } QMatrix4x4 model; model.translate(unit->worldPos.x(), unit->worldPos.y(), unit->worldPos.z()); model.scale(unitRadius, unitRadius, unitHeight); QMatrix4x4 mvp = projection * vMatrix * model; m_program->setUniformValue("modelMatrix", model); m_program->setUniformValue("MVP", mvp); m_program->setUniformValue("u_color", QVector4D(col.redF(), col.greenF(), col.blueF(), 1.0f)); glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); // Ground selection base disk if selected if (unit->id == selectedUnitId) { QMatrix4x4 selModel; selModel.translate(unit->worldPos.x(), unit->worldPos.y(), unit->worldPos.z()); selModel.scale(unitRadius * 1.6f, unitRadius * 1.6f, 0.04f); QMatrix4x4 selMvp = projection * vMatrix * selModel; m_program->setUniformValue("modelMatrix", selModel); m_program->setUniformValue("MVP", selMvp); m_program->setUniformValue("u_color", QVector4D(1.0f, 0.9f, 0.2f, 0.9f)); glDrawArrays(GL_TRIANGLES, 0, m_vertexCount); } } m_vao.release(); m_program->release(); } QPointF UnitRenderer::projectToScreen( const QVector3D &worldPoint, const QMatrix4x4 &projection, const QMatrix4x4 &vMatrix, int viewportW, int viewportH, bool &inFront) { QMatrix4x4 vp = projection * vMatrix; QVector4D clip = vp.map(QVector4D(worldPoint, 1.0f)); inFront = (clip.w() > 0.01f); if (!inFront) { return QPointF(-9999, -9999); } QVector3D ndc = clip.toVector3DAffine(); float sx = (ndc.x() + 1.0f) * 0.5f * viewportW; float sy = (1.0f - (ndc.y() + 1.0f) * 0.5f) * viewportH; return QPointF(sx, sy); }