#ifndef OpenGLModelWidget_H
#define OpenGLModelWidget_H
#include <QOpenGLWidget>
#include <QOpenGlFunctions_3_3_Core>
#include <QMatrix4x4>
#include <QVector2D>
#include <QVector3D>
#include <QMouseEvent>
#include <array>
#include "glm/glm.hpp"
#include "glm/ext.hpp"
struct Vertex {
glm::vec3 position;
glm::vec3 normal;
};
class COpenGLModelWidget : public QOpenGLWidget, QOpenGLFunctions_3_3_Core
{
Q_OBJECT
public:
COpenGLModelWidget(QWidget*parent = nullptr);
virtual ~COpenGLModelWidget();
void LoadOBJModel(const std::string& strModelFile);
void SetShaderFilePath(const std::string& strVertFilePath, const std::string& strfragFilePath);
protected:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void mouseMoveEvent(QMouseEvent* event);
void mousePressEvent(QMouseEvent* event);
void wheelEvent(QWheelEvent* event);
void mouseReleaseEvent(QMouseEvent* event);
void keyPressEvent(QKeyEvent* event);
void keyReleaseEvent(QKeyEvent* event);
private:
void AutoNormal();
std::string GetFileConTent(const std::string& strFilePath) const;
glm::vec3 ComputeNormalBiased(glm::vec3 const& a, glm::vec3 const& b,
glm::vec3 const& c);
glm::mat4x4 GetViewMatrix() const;
glm::mat4x4 GetProjectionMatrix() const;
void NormalizeAngle(int &nAngle);
void Orbit(glm::vec2 delta, bool isDrift);
void Zoom(float delta, bool isHitchcock);
void Pan(glm::vec2 delta);
private:
std::vector<Vertex> m_vecVertices;
std::vector<glm::uvec3> m_vecFaces;
unsigned int m_unVAO;
unsigned int m_unVBO;
unsigned int m_unEBO;
unsigned int m_unShaderProgram;
std::string m_strFragFilePath;
std::string m_strVertFilePath;
glm::vec2 lastpos;
bool moving = false;
glm::vec3 eye = { 0, 0, 5 };
glm::vec3 lookat = { 0, 0, 0 };
glm::vec3 up_vector = { 0, 1, 0 };
glm::vec3 keep_up_axis = { 0, 1, 0 };
float focal_len = 40.0f;
float film_height = 24.0f;
float film_width = 32.0f;
float zoom_speed = 0.2f;
float orbit_speed = 1.0f;
float drift_speed = 1.0f;
float pan_speed = 2.0f;
int zoom_axis = 1;
glm::vec2 mousePos;
};
#endif
#include "../Include/OpenGLModelWidget.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <QMatrix3x3>
COpenGLModelWidget::COpenGLModelWidget(QWidget* parent)
: QOpenGLWidget(parent)
{
}
COpenGLModelWidget::~COpenGLModelWidget()
{
makeCurrent();
glDeleteBuffers(1, &m_unVBO);
glDeleteVertexArrays(1, &m_unVAO);
glDeleteProgram(m_unShaderProgram);
doneCurrent();
}
void COpenGLModelWidget::LoadOBJModel(const std::string& strModelFile)
{
std::ifstream file(strModelFile);
if (!file.is_open()) {
std::cerr << "Failed to open file: " << strModelFile << std::endl;
return;
}
std::string line;
while (std::getline(file, line))
{
if (line.substr(0, 2) == "v ")
{
std::istringstream s(line.substr(2));
glm::vec3 vertex;
s >> vertex.x >> vertex.y >> vertex.z;
m_vecVertices.push_back({ vertex, {} });
}
else if (line.substr(0, 3) == "vn ")
{
std::istringstream s(line.substr(2));
}
else if (line.substr(0, 3) == "vt ")
{
}
else if (line.substr(0, 2) == "f ")
{
std::istringstream s(line.substr(2));
std::string splitted;
std::vector<unsigned int> indices;
while (std::getline(s, splitted, ' '))
{
unsigned int index = 1;
std::istringstream(splitted) >> index;
indices.push_back(index - 1);
}
for (size_t i = 2; i < indices.size(); i++)
{
glm::uvec3 face =
glm::uvec3(indices[0], indices[i - 1], indices[i]);
m_vecFaces.push_back(face);
}
}
}
file.close();
std::cout << strModelFile << ": Loaded " << m_vecVertices.size() << " vertices, "
<< m_vecFaces.size() << " faces.\n";
AutoNormal();
}
void COpenGLModelWidget::SetShaderFilePath(const std::string& strVertFilePath, const std::string& strFragFilePath)
{
m_strVertFilePath = strVertFilePath;
m_strFragFilePath = strFragFilePath;
}
void COpenGLModelWidget::initializeGL()
{
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
glEnable(GL_MULTISAMPLE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
m_unShaderProgram = glCreateProgram();
unsigned int unVertexShader = glCreateShader(GL_VERTEX_SHADER);
std::string str = GetFileConTent(m_strVertFilePath);
const char* srcList[1] = { str.c_str() };
int srcLenList[1] = { (int)str.size() };
glShaderSource(unVertexShader, 1, srcList, srcLenList);
glCompileShader(unVertexShader);
int nCompileStatus;
glGetShaderiv(unVertexShader, GL_COMPILE_STATUS, &nCompileStatus);
if (nCompileStatus == GL_FALSE) {
int infoLen = 0;
glGetShaderiv(unVertexShader, GL_INFO_LOG_LENGTH, &infoLen);
if (infoLen > 1) {
char* infoLog = new char[infoLen];
glGetShaderInfoLog(unVertexShader, infoLen, NULL, infoLog);
std::cerr << "Vertex shader compile error: " << infoLog << std::endl;
delete[] infoLog;
}
glDeleteShader(unVertexShader);
return;
}
glAttachShader(m_unShaderProgram, unVertexShader);
unsigned int unFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
str = GetFileConTent(m_strFragFilePath);
srcList[0] = str.c_str();
srcLenList[0] = (int)str.size();
glShaderSource(unFragmentShader, 1, srcList, srcLenList);
glCompileShader(unFragmentShader);
glGetShaderiv(unFragmentShader, GL_COMPILE_STATUS, &nCompileStatus);
if (nCompileStatus == GL_FALSE) {
int nInfoLen = 0;
glGetShaderiv(unFragmentShader, GL_INFO_LOG_LENGTH, &nInfoLen);
if (nInfoLen > 1) {
char* infoLog = new char[nInfoLen];
glGetShaderInfoLog(unFragmentShader, nInfoLen, NULL, infoLog);
std::cerr << "Fragment shader compile error: " << infoLog << std::endl;
delete[] infoLog;
}
glDeleteShader(unFragmentShader);
return;
}
glAttachShader(m_unShaderProgram, unFragmentShader);
glLinkProgram(m_unShaderProgram);
glGetProgramiv(m_unShaderProgram, GL_LINK_STATUS, &nCompileStatus);
if (nCompileStatus == GL_FALSE)
{
int nInfoLen = 0;
glGetShaderiv(unFragmentShader, GL_INFO_LOG_LENGTH, &nInfoLen);
if (nInfoLen > 1) {
char* infoLog = new char[nInfoLen];
glGetShaderInfoLog(unFragmentShader, nInfoLen, NULL, infoLog);
std::cerr << "glAttachShader fail " << infoLog << std::endl;
delete[] infoLog;
}
glDeleteShader(unFragmentShader);
return;
}
glDeleteShader(unVertexShader);
glDeleteShader(unFragmentShader);
glGenVertexArrays(1, &m_unVAO);
glGenBuffers(1, &m_unVBO);
glBindVertexArray(m_unVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_unVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * m_vecVertices.size(), m_vecVertices.data(), GL_STATIC_DRAW);
glGenBuffers(1, &m_unEBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_unEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::array<unsigned int, 3>) * m_vecFaces.size(), m_vecFaces.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
void COpenGLModelWidget::paintGL()
{
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
auto projection = GetProjectionMatrix();
auto view = GetViewMatrix();
glm::mat4x4 model(1.0f);
glUseProgram(m_unShaderProgram);
glUniformMatrix4fv(glGetUniformLocation(m_unShaderProgram, "uniModel"), 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(glGetUniformLocation(m_unShaderProgram, "uniView"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(m_unShaderProgram, "uniProjection"), 1, GL_FALSE, glm::value_ptr(projection));
glm::vec3 lightDir = glm::normalize(glm::vec3(mousePos.x, mousePos.y, 1));
int location = glGetUniformLocation(m_unShaderProgram, "uniLightDir");
glUniform3fv(location, 1, glm::value_ptr(lightDir));
glBindVertexArray(m_unVAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_unEBO);
glDrawElements(GL_TRIANGLES, m_vecFaces.size() * 3,
GL_UNSIGNED_INT, (void*)0);
}
void COpenGLModelWidget::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
}
void COpenGLModelWidget::mouseMoveEvent(QMouseEvent* event)
{
if (event)
{
double xpos = event->x();
double ypos = event->y();
float x = (float)(2 * xpos / width() - 1);
float y = (float)(2 * (height() - ypos) / height() - 1);
glm::vec2 pos(x, y);
mousePos = pos;
moving = true;
auto delta = glm::fract((pos - lastpos) * 0.5f + 0.5f) * 2.0f - 1.0f;
if (event->buttons() == Qt::LeftButton && event->modifiers() == Qt::NoModifier)
{
Orbit(delta, false);
}
else if (event->buttons() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier)
{
Orbit(delta, true);
}
else if (event->buttons() == Qt::LeftButton && event->modifiers() == Qt::ShiftModifier)
{
Pan(delta);
}
else
{
moving = false;
}
lastpos = pos;
update();
}
}
void COpenGLModelWidget::Orbit(glm::vec2 delta, bool isDrift)
{
if (isDrift) {
delta *= -drift_speed;
delta *= std::atan(film_height / (2 * focal_len));
}
else {
delta *= orbit_speed;
}
auto angle_X_inc = delta.x;
auto angle_Y_inc = delta.y;
auto rotation_pivot = isDrift ? eye : lookat;
auto front_vector = glm::normalize(lookat - eye);
auto right_vector = glm::normalize(glm::cross(front_vector, up_vector));
up_vector = glm::normalize(glm::cross(right_vector, front_vector));
glm::mat4x4 rotation_matrixX = glm::rotate(glm::mat4x4(1), -angle_X_inc, up_vector);
glm::mat4x4 rotation_matrixY = glm::rotate(glm::mat4x4(1), angle_Y_inc, right_vector);
auto transformation = glm::translate(glm::mat4x4(1), rotation_pivot)
* rotation_matrixY * rotation_matrixX
* glm::translate(glm::mat4x4(1), -rotation_pivot);
eye = glm::vec3(transformation * glm::vec4(eye, 1));
lookat = glm::vec3(transformation * glm::vec4(lookat, 1));
float right_o_up = glm::dot(right_vector, keep_up_axis);
float right_handness = glm::dot(glm::cross(keep_up_axis, right_vector), front_vector);
float angle_Z_err = glm::asin(right_o_up);
angle_Z_err *= glm::atan(right_handness);
glm::mat4x4 rotation_matrixZ = glm::rotate(glm::mat4x4(1), angle_Z_err, front_vector);
up_vector = glm::mat3x3(rotation_matrixZ) * up_vector;
}
void COpenGLModelWidget::Zoom(float delta, bool isHitchcock)
{
float inv_zoom_factor = glm::exp(- zoom_speed * delta);
eye = (eye - lookat) * inv_zoom_factor + lookat;
if (isHitchcock) {
focal_len *= inv_zoom_factor;
}
}
void COpenGLModelWidget::Pan(glm::vec2 delta)
{
delta *= -pan_speed;
auto front_vector = glm::normalize(lookat - eye);
auto right_vector = glm::normalize(glm::cross(front_vector, up_vector));
auto fixed_up_vector = glm::normalize(glm::cross(right_vector, front_vector));
auto delta3d = delta.x * right_vector + delta.y * fixed_up_vector;
eye += delta3d;
lookat += delta3d;
}
void COpenGLModelWidget::mousePressEvent(QMouseEvent* event)
{
if (event)
{
double xpos = event->x();
double ypos = event->y();
float x = (float)(2 * xpos / width() - 1);
float y = (float)(2 * (height() - ypos) / height() - 1);
mousePos.x = x;
mousePos.y = y;
}
}
void COpenGLModelWidget::wheelEvent(QWheelEvent* event)
{
if(event)
{
double xoffset = event->angleDelta().x();
double yoffset = event->angleDelta().y();
float deltax = xoffset < 0 ? -1 : xoffset > 0 ? 1 : 0;
float deltay = yoffset < 0 ? -1 : yoffset > 0 ? 1 : 0;
glm::vec2 delta(deltax, deltay);
if(event->modifiers() == Qt::ShiftModifier)
{
Zoom(delta[zoom_axis], true);
}
else if(event->modifiers() == Qt::NoModifier)
{
Zoom(delta[zoom_axis], false);
}
else
{
moving = false;
}
update();
}
}
void COpenGLModelWidget::mouseReleaseEvent(QMouseEvent* event)
{
Q_UNUSED(event);
}
void COpenGLModelWidget::keyPressEvent(QKeyEvent* event)
{
}
void COpenGLModelWidget::keyReleaseEvent(QKeyEvent* event)
{
}
std::string COpenGLModelWidget::GetFileConTent(const std::string& strFilePath) const
{
std::ifstream file(strFilePath);
if (!file.is_open())
{
std::cerr << "Failed to open file: " << strFilePath << std::endl;
return {};
}
return std::string{
std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>()};
}
glm::vec3 COpenGLModelWidget::ComputeNormalBiased(glm::vec3 const& a, glm::vec3 const& b,
glm::vec3 const& c)
{
glm::vec3 ab = b - a;
glm::vec3 ac = c - a;
glm::vec3 n = glm::cross(ab, ac);
auto nlen = glm::length(n);
if (nlen != 0) [[likely]]
{
auto labc = glm::length(ab) * glm::length(ac);
if (labc >= nlen) [[likely]]
{
n *= glm::asin(nlen / labc) / nlen;
}
else
{
n *= 1.0f / nlen;
}
}
return n;
}
glm::mat4x4 COpenGLModelWidget::GetViewMatrix() const
{
return glm::lookAt(eye, lookat, up_vector);
}
glm::mat4x4 COpenGLModelWidget::GetProjectionMatrix() const
{
auto fov = 2 * std::atan(film_height / (2 * focal_len));
auto aspect = (float)width() / height();
return glm::perspective(fov, aspect, 0.01f, 100.0f);
}
void COpenGLModelWidget::NormalizeAngle(int& nAngle)
{
while (nAngle < 0)
{
nAngle += 360 * 16;
}
while (nAngle > 360 * 16)
{
nAngle -= 360 * 16;
}
}
void COpenGLModelWidget::AutoNormal()
{
for (auto& v : m_vecVertices)
{
v.normal = glm::vec3();
}
for (auto const& face : m_vecFaces) {
auto& a = m_vecVertices[face[0]];
auto& b = m_vecVertices[face[1]];
auto& c = m_vecVertices[face[2]];
a.normal += ComputeNormalBiased(a.position, b.position, c.position);
b.normal += ComputeNormalBiased(b.position, c.position, a.position);
c.normal += ComputeNormalBiased(c.position, a.position, b.position);
}
for (auto& v : m_vecVertices)
{
v.normal = glm::normalize(v.normal);
}
}