Files
Ascent-of-Man/unitrenderer.cpp
T
2026-11-08 17:06:47 +01:00

208 lines
6.5 KiB
C++

#include "unitrenderer.h"
#include "pathfinder.h"
#include <cmath>
#include <vector>
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<VertexPN> 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<std::shared_ptr<Unit>> &units,
const std::vector<City> &cities,
const PlayerManager &players,
const QMatrix4x4 &projection,
const QMatrix4x4 &vMatrix,
const QVector3D &lightDir,
int selectedUnitId,
int cityViewRadius,
int cityViewTileX,
int cityViewTileY)
{
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) {
if (cityViewRadius >= 0) {
if (c.tileX != cityViewTileX || c.tileY != cityViewTileY) {
continue;
}
}
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;
if (cityViewRadius >= 0) {
if (Pathfinder::hexDistance(cityViewTileX, cityViewTileY, unit->tileX, unit->tileY) > cityViewRadius) {
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);
}