From 7d8e8c4bc57993b95da00d79e7b1b1a07703260b Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sat, 10 Mar 2018 14:57:14 +0800 Subject: [PATCH] Test QOpenGLWidget example with meshlite --- README.md | 5 + dust3d.pro | 21 ++++ src/main.cpp | 28 +++++ src/mainwindow.cpp | 9 ++ src/mainwindow.h | 17 +++ src/mesh.cpp | 57 +++++++++ src/mesh.h | 31 +++++ src/modelingwidget.cpp | 280 +++++++++++++++++++++++++++++++++++++++++ src/modelingwidget.h | 71 +++++++++++ 9 files changed, 519 insertions(+) create mode 100644 README.md create mode 100644 dust3d.pro create mode 100644 src/main.cpp create mode 100644 src/mainwindow.cpp create mode 100644 src/mainwindow.h create mode 100644 src/mesh.cpp create mode 100644 src/mesh.h create mode 100644 src/modelingwidget.cpp create mode 100644 src/modelingwidget.h diff --git a/README.md b/README.md new file mode 100644 index 00000000..c85774b8 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +Build +-------- +``` +$ qmake -spec macx-xcode +``` \ No newline at end of file diff --git a/dust3d.pro b/dust3d.pro new file mode 100644 index 00000000..86223dbd --- /dev/null +++ b/dust3d.pro @@ -0,0 +1,21 @@ +QT += core widgets opengl +CONFIG += debug + +INCLUDEPATH += src + +SOURCES += src/mainwindow.cpp +HEADERS += src/mainwindow.h + +SOURCES += src/modelingwidget.cpp +HEADERS += src/modelingwidget.h + +SOURCES += src/mesh.cpp +HEADERS += src/mesh.h + +SOURCES += src/main.cpp + +INCLUDEPATH += ../meshlite/include +LIBS += -L../meshlite/target/debug -lmeshlite + +target.path = ./ +INSTALLS += target \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..590996e7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "mainwindow.h" +#include "meshlite.h" + +int main(int argc, char ** argv) +{ + /* + void *lite = meshlite_create_context(); + int first = meshlite_import(lite, "/Users/jeremy/cube.obj"); + int second = meshlite_import(lite, "/Users/jeremy/ball.obj"); + meshlite_scale(lite, first, 0.65); + int result = meshlite_intersect(lite, first, second); + meshlite_export(lite, result, "/Users/jeremy/testlib.obj"); + */ + QApplication app(argc, argv); + QCoreApplication::setApplicationName("Dust 3D"); + MainWindow mainWindow; + mainWindow.resize(mainWindow.sizeHint()); + int desktopArea = QApplication::desktop()->width() * + QApplication::desktop()->height(); + int widgetArea = mainWindow.width() * mainWindow.height(); + if (((float)widgetArea / (float)desktopArea) < 0.75f) + mainWindow.show(); + else + mainWindow.showMaximized(); + return app.exec(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp new file mode 100644 index 00000000..7228d970 --- /dev/null +++ b/src/mainwindow.cpp @@ -0,0 +1,9 @@ +#include "mainwindow.h" + + +MainWindow::MainWindow() +{ + modelingWidget = new ModelingWidget(this); + setCentralWidget(modelingWidget); +} + diff --git a/src/mainwindow.h b/src/mainwindow.h new file mode 100644 index 00000000..4996439c --- /dev/null +++ b/src/mainwindow.h @@ -0,0 +1,17 @@ +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include +#include "modelingwidget.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT +public: + MainWindow(); + +private: + ModelingWidget *modelingWidget; +}; + +#endif diff --git a/src/mesh.cpp b/src/mesh.cpp new file mode 100644 index 00000000..8ba29ab1 --- /dev/null +++ b/src/mesh.cpp @@ -0,0 +1,57 @@ +#include "mesh.h" +#include "meshlite.h" +#include + +Mesh::Mesh(void *meshlite, int mesh_id) : + m_vertices(NULL), + m_vertexCount(0) +{ + int positionCount = meshlite_get_vertex_count(meshlite, mesh_id); + GLfloat *positions = new GLfloat[positionCount * 3]; + int loadedPositionItemCount = meshlite_get_vertex_position_array(meshlite, mesh_id, positions, positionCount * 3); + + int triangleCount = meshlite_get_face_count(meshlite, mesh_id); + int *triangleIndices = new int[triangleCount * 3]; + int loadedIndexItemCount = meshlite_get_triangle_index_array(meshlite, mesh_id, triangleIndices, triangleCount * 3); + GLfloat *normals = new GLfloat[triangleCount * 3]; + int loadedNormalItemCount = meshlite_get_triangle_normal_array(meshlite, mesh_id, normals, triangleCount * 3); + + m_vertexCount = triangleCount * 3; + m_vertices = new Vertex[m_vertexCount * 3]; + for (int i = 0; i < triangleCount; i++) { + int firstIndex = i * 3; + for (int j = 0; j < 3; j++) { + assert(firstIndex + j < loadedIndexItemCount); + int posIndex = triangleIndices[firstIndex + j] * 3; + assert(posIndex < loadedPositionItemCount); + Vertex *v = &m_vertices[firstIndex + j]; + v->posX = positions[posIndex + 0]; + v->posY = positions[posIndex + 1]; + v->posZ = positions[posIndex + 2]; + assert(firstIndex + 2 < loadedNormalItemCount); + v->normX = normals[firstIndex + 0]; + v->normY = normals[firstIndex + 1]; + v->normZ = normals[firstIndex + 2]; + } + } + + delete[] positions; + delete[] triangleIndices; + delete[] normals; +} + +Mesh::~Mesh() +{ + delete[] m_vertices; + m_vertexCount = 0; +} + +Vertex *Mesh::vertices() +{ + return m_vertices; +} + +int Mesh::vertexCount() +{ + return m_vertexCount; +} diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 00000000..7606fd7f --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,31 @@ +#ifndef MESH_H +#define MESH_H +#include +#include + +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + GLfloat posX; + GLfloat posY; + GLfloat posZ; + GLfloat normX; + GLfloat normY; + GLfloat normZ; +} Vertex; +#pragma pack(pop) + +class Mesh +{ +public: + Mesh(void *meshlite, int mesh_id); + ~Mesh(); + Vertex *vertices(); + int vertexCount(); +private: + Vertex *m_vertices; + int m_vertexCount; +}; + +#endif diff --git a/src/modelingwidget.cpp b/src/modelingwidget.cpp new file mode 100644 index 00000000..8dfe2513 --- /dev/null +++ b/src/modelingwidget.cpp @@ -0,0 +1,280 @@ +#include "modelingwidget.h" +#include +#include +#include +#include +#include "meshlite.h" + +// Modifed from http://doc.qt.io/qt-5/qtopengl-hellogl2-glwidget-cpp.html + +bool ModelingWidget::m_transparent = false; + +ModelingWidget::ModelingWidget(QWidget *parent) + : QOpenGLWidget(parent), + m_xRot(0), + m_yRot(0), + m_zRot(0), + m_program(0), + m_mesh(NULL) +{ + m_core = QSurfaceFormat::defaultFormat().profile() == QSurfaceFormat::CoreProfile; + // --transparent causes the clear color to be transparent. Therefore, on systems that + // support it, the widget will become transparent apart from the logo. + if (m_transparent) { + QSurfaceFormat fmt = format(); + fmt.setAlphaBufferSize(8); + setFormat(fmt); + } + + void *lite = meshlite_create_context(); + int first = meshlite_import(lite, "/Users/jeremy/cube.obj"); + int second = meshlite_import(lite, "/Users/jeremy/ball.obj"); + meshlite_scale(lite, first, 0.65); + int intersect = meshlite_intersect(lite, first, second); + int triangulate = meshlite_triangulate(lite, intersect); + meshlite_export(lite, triangulate, "/Users/jeremy/testlib.obj"); + Mesh *mesh = new Mesh(lite, triangulate); + updateMesh(mesh); +} + +ModelingWidget::~ModelingWidget() +{ + cleanup(); + delete m_mesh; +} + +QSize ModelingWidget::minimumSizeHint() const +{ + return QSize(50, 50); +} + +QSize ModelingWidget::sizeHint() const +{ + return QSize(400, 400); +} + +static void qNormalizeAngle(int &angle) +{ + while (angle < 0) + angle += 360 * 16; + while (angle > 360 * 16) + angle -= 360 * 16; +} + +void ModelingWidget::setXRotation(int angle) +{ + qNormalizeAngle(angle); + if (angle != m_xRot) { + m_xRot = angle; + emit xRotationChanged(angle); + update(); + } +} + +void ModelingWidget::setYRotation(int angle) +{ + qNormalizeAngle(angle); + if (angle != m_yRot) { + m_yRot = angle; + emit yRotationChanged(angle); + update(); + } +} + +void ModelingWidget::setZRotation(int angle) +{ + qNormalizeAngle(angle); + if (angle != m_zRot) { + m_zRot = angle; + emit zRotationChanged(angle); + update(); + } +} + +void ModelingWidget::cleanup() +{ + if (m_program == nullptr) + return; + makeCurrent(); + m_modelVbo.destroy(); + delete m_program; + m_program = 0; + doneCurrent(); +} + +static const char *vertexShaderSourceCore = + "#version 150\n" + "in vec4 vertex;\n" + "in vec3 normal;\n" + "out vec3 vert;\n" + "out vec3 vertNormal;\n" + "uniform mat4 projMatrix;\n" + "uniform mat4 mvMatrix;\n" + "uniform mat3 normalMatrix;\n" + "void main() {\n" + " vert = vertex.xyz;\n" + " vertNormal = normalMatrix * normal;\n" + " gl_Position = projMatrix * mvMatrix * vertex;\n" + "}\n"; + +static const char *fragmentShaderSourceCore = + "#version 150\n" + "in highp vec3 vert;\n" + "in highp vec3 vertNormal;\n" + "out highp vec4 fragColor;\n" + "uniform highp vec3 lightPos;\n" + "void main() {\n" + " highp vec3 L = normalize(lightPos - vert);\n" + " highp float NL = max(dot(normalize(vertNormal), L), 0.0);\n" + " highp vec3 color = vec3(0.39, 1.0, 0.0);\n" + " highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);\n" + " fragColor = vec4(col, 1.0);\n" + "}\n"; + +static const char *vertexShaderSource = + "attribute vec4 vertex;\n" + "attribute vec3 normal;\n" + "varying vec3 vert;\n" + "varying vec3 vertNormal;\n" + "uniform mat4 projMatrix;\n" + "uniform mat4 mvMatrix;\n" + "uniform mat3 normalMatrix;\n" + "void main() {\n" + " vert = vertex.xyz;\n" + " vertNormal = normalMatrix * normal;\n" + " gl_Position = projMatrix * mvMatrix * vertex;\n" + "}\n"; + +static const char *fragmentShaderSource = + "varying highp vec3 vert;\n" + "varying highp vec3 vertNormal;\n" + "uniform highp vec3 lightPos;\n" + "void main() {\n" + " highp vec3 L = normalize(lightPos - vert);\n" + " highp float NL = max(dot(normalize(vertNormal), L), 0.0);\n" + " highp vec3 color = vec3(0.39, 1.0, 0.0);\n" + " highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0);\n" + " gl_FragColor = vec4(col, 1.0);\n" + "}\n"; + +void ModelingWidget::initializeGL() +{ + // In this example the widget's corresponding top-level window can change + // several times during the widget's lifetime. Whenever this happens, the + // QOpenGLWidget's associated context is destroyed and a new one is created. + // Therefore we have to be prepared to clean up the resources on the + // aboutToBeDestroyed() signal, instead of the destructor. The emission of + // the signal will be followed by an invocation of initializeGL() where we + // can recreate all resources. + connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &ModelingWidget::cleanup); + + initializeOpenGLFunctions(); + glClearColor(0, 0, 0, m_transparent ? 0 : 1); + + m_program = new QOpenGLShaderProgram; + m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, m_core ? vertexShaderSourceCore : vertexShaderSource); + m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, m_core ? fragmentShaderSourceCore : fragmentShaderSource); + m_program->bindAttributeLocation("vertex", 0); + m_program->bindAttributeLocation("normal", 1); + m_program->link(); + + m_program->bind(); + m_projMatrixLoc = m_program->uniformLocation("projMatrix"); + m_mvMatrixLoc = m_program->uniformLocation("mvMatrix"); + m_normalMatrixLoc = m_program->uniformLocation("normalMatrix"); + m_lightPosLoc = m_program->uniformLocation("lightPos"); + + // Create a vertex array object. In OpenGL ES 2.0 and OpenGL 2.x + // implementations this is optional and support may not be present + // at all. Nonetheless the below code works in all cases and makes + // sure there is a VAO when one is needed. + m_vao.create(); + QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); + + // Setup our vertex buffer object. + m_modelVbo.create(); + m_modelVbo.bind(); + m_modelVbo.allocate(m_mesh->vertices(), m_mesh->vertexCount() * sizeof(Vertex)); + + // Store the vertex attribute bindings for the program. + setupVertexAttribs(); + + // Our camera never changes in this example. + m_camera.setToIdentity(); + m_camera.translate(0, 0, -4.5); + + // Light position is fixed. + m_program->setUniformValue(m_lightPosLoc, QVector3D(0, 0, 70)); + + m_program->release(); +} + +void ModelingWidget::setupVertexAttribs() +{ + m_modelVbo.bind(); + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glEnableVertexAttribArray(0); + f->glEnableVertexAttribArray(1); + f->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), 0); + f->glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), reinterpret_cast(3 * sizeof(GLfloat))); + m_modelVbo.release(); +} + +void ModelingWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + m_world.setToIdentity(); + m_world.rotate(180.0f - (m_xRot / 16.0f), 1, 0, 0); + m_world.rotate(m_yRot / 16.0f, 0, 1, 0); + m_world.rotate(m_zRot / 16.0f, 0, 0, 1); + + QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); + m_program->bind(); + m_program->setUniformValue(m_projMatrixLoc, m_proj); + m_program->setUniformValue(m_mvMatrixLoc, m_camera * m_world); + QMatrix3x3 normalMatrix = m_world.normalMatrix(); + m_program->setUniformValue(m_normalMatrixLoc, normalMatrix); + + glDrawArrays(GL_TRIANGLES, 0, m_mesh->vertexCount()); + + m_program->release(); +} + +void ModelingWidget::resizeGL(int w, int h) +{ + m_proj.setToIdentity(); + m_proj.perspective(45.0f, GLfloat(w) / h, 0.01f, 100.0f); +} + +void ModelingWidget::mousePressEvent(QMouseEvent *event) +{ + m_lastPos = event->pos(); +} + +void ModelingWidget::mouseMoveEvent(QMouseEvent *event) +{ + int dx = event->x() - m_lastPos.x(); + int dy = event->y() - m_lastPos.y(); + + if (event->buttons() & Qt::LeftButton) { + setXRotation(m_xRot - 8 * dy); + setYRotation(m_yRot - 8 * dx); + } else if (event->buttons() & Qt::RightButton) { + setXRotation(m_xRot - 8 * dy); + setZRotation(m_zRot - 8 * dx); + } + m_lastPos = event->pos(); +} + +void ModelingWidget::updateMesh(Mesh *mesh) +{ + QMutexLocker lock(&m_meshMutex); + if (mesh != m_mesh) { + delete m_mesh; + m_mesh = mesh; + } +} + diff --git a/src/modelingwidget.h b/src/modelingwidget.h new file mode 100644 index 00000000..5b4d47f1 --- /dev/null +++ b/src/modelingwidget.h @@ -0,0 +1,71 @@ +#ifndef MODELING_WIDGET_H +#define MODELING_WIDGET_H + +#include +#include +#include +#include +#include +#include +#include "mesh.h" + +QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) + +class ModelingWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + ModelingWidget(QWidget *parent = 0); + ~ModelingWidget(); + + static bool isTransparent() { return m_transparent; } + static void setTransparent(bool t) { m_transparent = t; } + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + + void updateMesh(Mesh *mesh); + +public slots: + void setXRotation(int angle); + void setYRotation(int angle); + void setZRotation(int angle); + void cleanup(); + +signals: + void xRotationChanged(int angle); + void yRotationChanged(int angle); + void zRotationChanged(int angle); + +protected: + void initializeGL() override; + void paintGL() override; + void resizeGL(int width, int height) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + +private: + void setupVertexAttribs(); + bool m_core; + int m_xRot; + int m_yRot; + int m_zRot; + QPoint m_lastPos; + QOpenGLVertexArrayObject m_vao; + QOpenGLBuffer m_modelVbo; + QOpenGLShaderProgram *m_program; + int m_projMatrixLoc; + int m_mvMatrixLoc; + int m_normalMatrixLoc; + int m_lightPosLoc; + QMatrix4x4 m_proj; + QMatrix4x4 m_camera; + QMatrix4x4 m_world; + static bool m_transparent; + + Mesh *m_mesh; + QMutex m_meshMutex; +}; + +#endif