Sun, 02 Feb 2020 00:30:48 +0200
added automated configuration collection
/* * LDForge: LDraw parts authoring CAD * Copyright (C) 2013 - 2020 Teemu Piippo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ #include <GL/glut.h> #include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_clip_space.hpp> #include <QMouseEvent> #include <QMessageBox> #include "partrenderer.h" PartRenderer::PartRenderer( Model* model, DocumentManager* documents, const ldraw::ColorTable& colorTable, QWidget* parent) : QOpenGLWidget{parent}, model{model}, documents{documents}, colorTable{colorTable}, compiler{new gl::Compiler{this->colorTable, this}} { this->setMouseTracking(true); } PartRenderer::~PartRenderer() { } void PartRenderer::initializeGL() { this->initializeOpenGLFunctions(); if (glGetError() != GL_NO_ERROR) { abort(); } this->compiler->initialize(); this->compiler->build(this->model, this->documents, this->renderPreferences); this->initialized = true; this->modelQuaternion = glm::angleAxis(glm::radians(30.0f), glm::vec3{-1, 0, 0}); this->modelQuaternion *= glm::angleAxis(glm::radians(225.0f), glm::vec3{-0, 1, 0}); this->updateViewMatrix(); glLineWidth(2.0); this->update(); } void PartRenderer::resizeGL(int width, int height) { glViewport(0, 0, width, height); this->projectionMatrix = glm::perspective( glm::radians(45.0f), static_cast<float>(width) / static_cast<float>(height), 0.1f, 10000.f); this->compiler->setUniformMatrix("projectionMatrix", this->projectionMatrix); } static GLenum getGlTypeForArrayClass(const gl::ArrayClass vboClass) { switch (vboClass) { case gl::ArrayClass::Lines: case gl::ArrayClass::ConditionalLines: return GL_LINES; case gl::ArrayClass::Triangles: return GL_TRIANGLES; case gl::ArrayClass::Quads: return GL_QUADS; } throw std::runtime_error{"Bad vbo class passed to getGlTypeForVboClass"}; } void PartRenderer::paintGL() { /* glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */ glEnable (GL_DEPTH_TEST); glShadeModel (GL_SMOOTH); glEnable (GL_MULTISAMPLE); glEnable (GL_LINE_SMOOTH); glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); this->renderScene(); } void PartRenderer::renderScene() { const QColor& backgroundColor = this->renderPreferences.backgroundColor; glClearColor( static_cast<float>(backgroundColor.redF()), static_cast<float>(backgroundColor.greenF()), static_cast<float>(backgroundColor.blueF()), 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.0f, 1.0f); switch (this->renderPreferences.style) { case gl::RenderStyle::Normal: this->setFragmentStyle(gl::FragmentStyle::Normal); this->renderAllArrays(); break; case gl::RenderStyle::BfcRedGreen: glEnable(GL_CULL_FACE); glCullFace(GL_BACK); this->setFragmentStyle(gl::FragmentStyle::BfcGreen); renderVao(gl::ArrayClass::Triangles); renderVao(gl::ArrayClass::Quads); glCullFace(GL_FRONT); this->setFragmentStyle(gl::FragmentStyle::BfcRed); renderVao(gl::ArrayClass::Triangles); renderVao(gl::ArrayClass::Quads); glDisable(GL_CULL_FACE); this->setFragmentStyle(gl::FragmentStyle::Normal); renderVao(gl::ArrayClass::Lines); case gl::RenderStyle::RandomColors: this->setFragmentStyle(gl::FragmentStyle::RandomColors); this->renderAllArrays(); break; case gl::RenderStyle::Wireframe: glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); this->setFragmentStyle(gl::FragmentStyle::Normal); this->renderAllArrays(); break; } glDisable(GL_POLYGON_OFFSET_FILL); } void PartRenderer::renderAllArrays() { // Lines need to be rendered last so that anti-aliasing does not interfere with polygon rendering. renderVao(gl::ArrayClass::Triangles); renderVao(gl::ArrayClass::Quads); renderVao(gl::ArrayClass::Lines); } void PartRenderer::updateViewMatrix() { // I'm not quite sure why using the exponent function on the zoom factor causes linear zoom behavior const double z = 2 * std::exp(this->zoom) * (1 + this->compiler->modelDistance()); this->viewMatrix = glm::lookAt(glm::vec3{0, 0, z}, {0, 0, 0}, {0, -1, 0}); this->compiler->setUniformMatrix("viewMatrix", this->viewMatrix); } void PartRenderer::renderVao(const gl::ArrayClass arrayClass) { this->compiler->bindVertexArray(arrayClass); const std::size_t vertexCount = this->compiler->vertexCount(arrayClass); glDrawArrays(getGlTypeForArrayClass(arrayClass), 0, static_cast<GLsizei>(vertexCount)); this->compiler->releaseVertexArray(arrayClass); this->checkForGLErrors(); } void PartRenderer::checkForGLErrors() { GLenum glError; QStringList errors; while ((glError = glGetError()) != GL_NO_ERROR) { const QString glErrorString = QString::fromLatin1(reinterpret_cast<const char*>(::gluErrorString(glError))); errors.append(glErrorString); } if (not errors.isEmpty()) { QMessageBox::critical( this, tr("Rendering error"), QString{"Failed to render.\n%1"}.arg(errors.join("\n"))); } } void PartRenderer::mouseMoveEvent(QMouseEvent* event) { const bool left = event->buttons() & Qt::LeftButton; const QPointF move = pointToPointF(event->pos()) - this->lastMousePosition; if (left and not move.isNull()) { // q_x is the rotation of the brick along the vertical y-axis, because turning the // vertical axis causes horizontal (=x) rotation. Likewise q_y is the rotation of the // brick along the horizontal x-axis, which causes vertical rotation. const auto scalar = 0.006f; const float move_x = static_cast<float>(move.x()); const float move_y = static_cast<float>(move.y()); const glm::quat q_x = glm::angleAxis(scalar * move_x, glm::vec3{0, -1, 0}); const glm::quat q_y = glm::angleAxis(scalar * move_y, glm::vec3{-1, 0, 0}); this->modelQuaternion = q_x * q_y * this->modelQuaternion; this->compiler->setUniformMatrix("modelMatrix", glm::mat4_cast(this->modelQuaternion)); this->update(); } this->lastMousePosition = pointToPointF(event->pos()); } void PartRenderer::wheelEvent(QWheelEvent* event) { static constexpr double WHEEL_STEP = 1 / 1000.0; const double move = (-event->angleDelta().y()) * WHEEL_STEP; this->zoom = std::clamp(this->zoom + move, MIN_ZOOM, MAX_ZOOM); this->updateViewMatrix(); this->update(); } /** * @brief Changes the color of rendered fragments * @param newFragmentStyle new fragment style to use */ void PartRenderer::setFragmentStyle(gl::FragmentStyle newFragmentStyle) { this->compiler->setUniform("fragmentStyle", static_cast<int>(newFragmentStyle)); } /** * @brief Changes the way the scene is rendered * @param newStyle new render style to use */ void PartRenderer::setRenderPreferences(const gl::RenderPreferences& newPreferences) { bool mainColorChanged = this->renderPreferences.mainColor != newPreferences.mainColor; this->renderPreferences = newPreferences; if (mainColorChanged) { this->compiler->build(this->model, this->documents, this->renderPreferences); } this->update(); }