325 lines
10 KiB
C++
325 lines
10 KiB
C++
#include "opengl_widget.hh"
|
|
|
|
#include "load_obj.hh"
|
|
|
|
#include <QMouseEvent>
|
|
#include <QOpenGLBuffer>
|
|
#include <QSurfaceFormat>
|
|
|
|
|
|
static void GLAPIENTRY opengl_debug_cb(GLenum source, GLenum type, GLuint id,
|
|
GLenum severity, GLsizei length,
|
|
const GLchar* message,
|
|
const void* userParam) {
|
|
(void) source; (void) type; (void) id; (void) severity; (void) length;
|
|
(void) userParam;
|
|
// Those are a bit too verbose
|
|
if (!QString((const char *) message).startsWith("Shader Stats:")) {
|
|
qDebug() << "OpenGL debug output:" << message;
|
|
}
|
|
}
|
|
|
|
|
|
OpenGLWidget *OpenGLWidget::instance = nullptr;
|
|
|
|
|
|
OpenGLWidget::OpenGLWidget(QWidget *parent)
|
|
:QOpenGLWidget(parent),
|
|
move_timer(this) {
|
|
OpenGLWidget::instance = this;
|
|
QSurfaceFormat format;
|
|
format.setProfile(QSurfaceFormat::CoreProfile);
|
|
format.setDepthBufferSize(24);
|
|
format.setSamples(4);
|
|
setFormat(format);
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
trans.translate(0, -10, -10);
|
|
rot.rotate(30, QVector3D(1, 0, 0));
|
|
rot_start = rot;
|
|
move_timer.setTimerType(Qt::PreciseTimer);
|
|
connect(&move_timer, &QTimer::timeout, this, &OpenGLWidget::move);
|
|
move_timer.start(16);
|
|
}
|
|
|
|
|
|
OpenGLWidget::~OpenGLWidget() {
|
|
OpenGLWidget::instance = nullptr;
|
|
}
|
|
|
|
|
|
void OpenGLWidget::loadSkybox() {
|
|
// Shader program
|
|
if (!skybox_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/skybox.vert")) {
|
|
qFatal("Error compiling skybox.vert: %s", skybox_program.log().toLocal8Bit().constData());
|
|
}
|
|
if (!skybox_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/skybox.frag")) {
|
|
qFatal("Error compiling skybox.frag: %s", skybox_program.log().toLocal8Bit().constData());
|
|
}
|
|
skybox_program.bindAttributeLocation("in_pos", 0);
|
|
if (!skybox_program.link()) {
|
|
qFatal("Error linking the skybox shader program: %s", skybox_program.log().toLocal8Bit().constData());
|
|
}
|
|
skybox_program.bind();
|
|
skybox_program.setUniformValue("skybox", 0);
|
|
|
|
QVector<GLfloat> skybox_verts {
|
|
-1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0,
|
|
-1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0,
|
|
1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0,
|
|
-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0,
|
|
-1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0,
|
|
-1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0
|
|
};
|
|
// QVector<GLfloat> skybox_verts = load_obj(":/mdl/cube.obj", 0);
|
|
|
|
// VAO
|
|
glGenVertexArrays(1, &skybox_vao);
|
|
glBindVertexArray(skybox_vao);
|
|
glGenBuffers(1, &skybox_vbo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, skybox_verts.size() * sizeof (GLfloat), skybox_verts.data(), GL_STATIC_DRAW);
|
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
|
|
glEnableVertexAttribArray(0);
|
|
glBindVertexArray(0);
|
|
|
|
// Skybox texture images
|
|
QVector<QImage> skybox_img {
|
|
QImage(":/img/clouds1_west.jpg").convertToFormat(QImage::Format_RGB888),
|
|
QImage(":/img/clouds1_east.jpg").convertToFormat(QImage::Format_RGB888),
|
|
QImage(":/img/clouds1_up.jpg").convertToFormat(QImage::Format_RGB888),
|
|
QImage(":/img/clouds1_down.jpg").convertToFormat(QImage::Format_RGB888),
|
|
QImage(":/img/clouds1_south.jpg").convertToFormat(QImage::Format_RGB888),
|
|
QImage(":/img/clouds1_north.jpg").convertToFormat(QImage::Format_RGB888)
|
|
};
|
|
size_t width = skybox_img[0].width();
|
|
size_t height = skybox_img[0].height();
|
|
glGenTextures(1, &skybox_tex);
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_tex);
|
|
for (int i = 0; i < 6; i++) {
|
|
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE,
|
|
(const GLvoid *) skybox_img[i].constBits());
|
|
}
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
|
|
|
skybox_program.release();
|
|
}
|
|
|
|
|
|
void OpenGLWidget::loadGround() {
|
|
QOpenGLTexture *ground_tex = new QOpenGLTexture(QImage(":/img/ground.jpg").mirrored());
|
|
ground_tex->generateMipMaps();
|
|
ground_tex->setMagnificationFilter(QOpenGLTexture::Linear);
|
|
ground_tex->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
|
|
ground_tex->setWrapMode(QOpenGLTexture::MirroredRepeat);
|
|
ground = new OpenGLMesh({
|
|
-1000, 0, 1000, 0, 1000, 0, 0, 1000,
|
|
1000, 0, -1000, 0, 1000, 0, 1000, 0,
|
|
-1000, 0, -1000, 0, 1000, 0, 0, 0,
|
|
1000, 0, -1000, 0, 1000, 0, 1000, 0,
|
|
-1000, 0, 1000, 0, 1000, 0, 0, 1000,
|
|
1000, 0, 1000, 0, 1000, 0, 1000, 1000,
|
|
}, ground_tex, &main_program);
|
|
}
|
|
|
|
|
|
void OpenGLWidget::initializeGL() {
|
|
initializeOpenGLFunctions();
|
|
GLint major, minor;
|
|
glGetIntegerv(GL_MAJOR_VERSION, &major);
|
|
glGetIntegerv(GL_MINOR_VERSION, &minor);
|
|
qDebug("OpenGL version %d.%d", major, minor);
|
|
|
|
glEnable(GL_DEBUG_OUTPUT);
|
|
glDebugMessageCallback(opengl_debug_cb, 0);
|
|
|
|
if (!main_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/main.vert")) {
|
|
qFatal("Error compiling main.vert: %s", main_program.log().toLocal8Bit().constData());
|
|
}
|
|
if (!main_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/main.frag")) {
|
|
qFatal("Error compiling main.frag: %s", main_program.log().toLocal8Bit().constData());
|
|
}
|
|
main_program.bindAttributeLocation("in_pos", 0);
|
|
main_program.bindAttributeLocation("in_norm", 1);
|
|
main_program.bindAttributeLocation("in_uv", 2);
|
|
if (!main_program.link()) {
|
|
qFatal("Error linking the main shader program: %s", main_program.log().toLocal8Bit().constData());
|
|
}
|
|
main_program.bind();
|
|
main_program.setUniformValue("tex", 0);
|
|
main_program.release();
|
|
|
|
if (!line_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/line.vert")) {
|
|
qFatal("Error compiling line.vert: %s", line_program.log().toLocal8Bit().constData());
|
|
}
|
|
if (!line_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/line.frag")) {
|
|
qFatal("Error compiling line.frag: %s", line_program.log().toLocal8Bit().constData());
|
|
}
|
|
line_program.bindAttributeLocation("in_pos", 0);
|
|
if (!line_program.link()) {
|
|
qFatal("Error linking the line shader program: %s", line_program.log().toLocal8Bit().constData());
|
|
}
|
|
line_program.bind();
|
|
line_program.setUniformValue("color", 0, 0, 0);
|
|
line_program.release();
|
|
|
|
loadSkybox();
|
|
loadGround();
|
|
|
|
glClearColor(1, 1, 1, 0);
|
|
glCullFace(GL_BACK);
|
|
|
|
emit initialized();
|
|
}
|
|
|
|
|
|
void OpenGLWidget::resizeGL(int w, int h) {
|
|
proj.setToIdentity();
|
|
proj.perspective(FOV, (float) w/h, .01, 1000);
|
|
}
|
|
|
|
|
|
void OpenGLWidget::paintGL() {
|
|
glEnable(GL_CULL_FACE);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
QMatrix4x4 view = rot * trans;
|
|
|
|
glDepthMask(GL_FALSE);
|
|
glDepthFunc(GL_LEQUAL);
|
|
skybox_program.bind();
|
|
skybox_program.setUniformValue("proj", proj);
|
|
skybox_program.setUniformValue("view", view);
|
|
glBindVertexArray(skybox_vao);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_tex);
|
|
glDrawArrays(GL_TRIANGLES, 0, 36);
|
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
|
skybox_program.release();
|
|
glDepthMask(GL_TRUE);
|
|
glDepthFunc(GL_LESS);
|
|
|
|
line_program.bind();
|
|
line_program.setUniformValue("proj", proj);
|
|
line_program.setUniformValue("view", view);
|
|
line_program.release();
|
|
|
|
main_program.bind();
|
|
main_program.setUniformValue("proj", proj);
|
|
main_program.setUniformValue("view", view);
|
|
main_program.setUniformValue("highlight", false);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glEnable(GL_POLYGON_OFFSET_FILL);
|
|
glPolygonOffset(1, 1);
|
|
ground->draw(this, QMatrix4x4());
|
|
glDisable(GL_POLYGON_OFFSET_FILL);
|
|
main_program.release();
|
|
if (painter) painter->draw(this);
|
|
}
|
|
|
|
|
|
void OpenGLWidget::mousePressEvent(QMouseEvent *e) {
|
|
if (e->button() == Qt::LeftButton) {
|
|
mouse_pos = e->pos();
|
|
}
|
|
}
|
|
|
|
|
|
void OpenGLWidget::mouseReleaseEvent(QMouseEvent *e) {
|
|
(void) e;
|
|
rot_start = rot;
|
|
}
|
|
|
|
|
|
void OpenGLWidget::mouseMoveEvent(QMouseEvent *e) {
|
|
if (!(e->buttons() & Qt::LeftButton)) return;
|
|
QPoint delta = e->pos() - mouse_pos;
|
|
rot = rot_start;
|
|
rot.rotate(delta.x() / 5., 0, 1, 0);
|
|
rot.rotate(delta.y() / 5., QVector3D(1, 0, 0) * rot);
|
|
update();
|
|
}
|
|
|
|
|
|
bool OpenGLWidget::keyEvent(QKeyEvent *e, bool press) {
|
|
if (e->isAutoRepeat()) return false;
|
|
/* would do wasd if qt had proper support for layout-independent
|
|
input, but it doesnt :< */
|
|
switch (e->key()) {
|
|
case Qt::Key_Up:
|
|
move_forward = press;
|
|
break;
|
|
case Qt::Key_Down:
|
|
move_back = press;
|
|
break;
|
|
case Qt::Key_Left:
|
|
move_left = press;
|
|
break;
|
|
case Qt::Key_Right:
|
|
move_right = press;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
update();
|
|
return true;
|
|
}
|
|
|
|
|
|
void OpenGLWidget::keyPressEvent(QKeyEvent *e) {
|
|
if (!keyEvent(e, true)) QOpenGLWidget::keyPressEvent(e);
|
|
}
|
|
|
|
|
|
void OpenGLWidget::keyReleaseEvent(QKeyEvent *e) {
|
|
if (!keyEvent(e, false)) QOpenGLWidget::keyReleaseEvent(e);
|
|
}
|
|
|
|
|
|
void OpenGLWidget::focusOutEvent(QFocusEvent *e) {
|
|
Q_UNUSED(e);
|
|
move_forward = move_back = move_left = move_right = false;
|
|
}
|
|
|
|
|
|
void OpenGLWidget::move() {
|
|
QMatrix4x4 rotation = rot.inverted();
|
|
if (move_forward) trans.translate(-(rotation * QVector3D(0, 0, -1)));
|
|
if (move_back) trans.translate(-(rotation * QVector3D(0, 0, 1)));
|
|
if (move_left) trans.translate(-(rotation * QVector3D(-1, 0, 0)));
|
|
if (move_right) trans.translate(-(rotation * QVector3D(1, 0, 0)));
|
|
update();
|
|
}
|
|
|
|
|
|
void OpenGLWidget::setPainter(const Painter *p) {
|
|
painter = p;
|
|
}
|
|
|
|
|
|
QOpenGLShaderProgram *OpenGLWidget::getMainProgram() {
|
|
return &main_program;
|
|
}
|
|
|
|
QOpenGLShaderProgram *OpenGLWidget::getLineProgram() {
|
|
return &line_program;
|
|
}
|
|
|
|
|
|
bool OpenGLWidget::project(const QVector3D &p, QPoint &point) const {
|
|
QMatrix4x4 view = rot * trans;
|
|
QVector3D projected = proj * view * p;
|
|
if (projected.x() < -1 || projected.x() > 1
|
|
|| projected.y() < -1 || projected.y() > 1
|
|
|| projected.z() < -1 || projected.z() > 1) {
|
|
return false;
|
|
}
|
|
point.setX((projected.x() / 2 + .5) * (float) width());
|
|
point.setY(((projected.y() / 2 - .5) * -1) * (float) height());
|
|
return true;
|
|
}
|