208 lines
6.5 KiB
C++
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);
|
|
}
|