From 97cf902b8751a7709f4f92bc34efd3ef0060104e Mon Sep 17 00:00:00 2001 From: Matt Keeter Date: Mon, 3 Mar 2014 21:14:16 -0500 Subject: [PATCH 1/1] Mostly complete from the Python original --- .gitignore | 3 ++ src/canvas.cpp | 129 ++++++++++++++++++++++++++++++++++++++++++++++ src/canvas.h | 52 +++++++++++++++++++ src/fstl.pro | 26 ++++++++++ src/gl/mesh.frag | 15 ++++++ src/gl/mesh.vert | 13 +++++ src/gl/quad.frag | 7 +++ src/gl/quad.vert | 10 ++++ src/glmesh.cpp | 35 +++++++++++++ src/glmesh.h | 18 +++++++ src/loader.cpp | 12 +++++ src/loader.h | 23 +++++++++ src/main.cpp | 16 ++++++ src/mesh.cpp | 87 +++++++++++++++++++++++++++++++ src/mesh.h | 28 ++++++++++ src/resources.qrc | 8 +++ src/window.cpp | 26 ++++++++++ src/window.h | 19 +++++++ 18 files changed, 527 insertions(+) create mode 100644 .gitignore create mode 100644 src/canvas.cpp create mode 100644 src/canvas.h create mode 100644 src/fstl.pro create mode 100644 src/gl/mesh.frag create mode 100644 src/gl/mesh.vert create mode 100644 src/gl/quad.frag create mode 100644 src/gl/quad.vert create mode 100644 src/glmesh.cpp create mode 100644 src/glmesh.h create mode 100644 src/loader.cpp create mode 100644 src/loader.h create mode 100644 src/main.cpp create mode 100644 src/mesh.cpp create mode 100644 src/mesh.h create mode 100644 src/resources.qrc create mode 100644 src/window.cpp create mode 100644 src/window.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deaad71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/* +*.pro.user +*.qmake.stash diff --git a/src/canvas.cpp b/src/canvas.cpp new file mode 100644 index 0000000..3468907 --- /dev/null +++ b/src/canvas.cpp @@ -0,0 +1,129 @@ +#include +#include + +#include "canvas.h" +#include "glmesh.h" +#include "mesh.h" + +Canvas::Canvas(const QGLFormat& format, QWidget *parent) + : QGLWidget(format, parent), mesh(NULL), + scale(1), tilt(90), yaw(0) +{ + // Nothing to do here +} + +Canvas::~Canvas() +{ + delete mesh; +} + +void Canvas::load_mesh(Mesh* m) +{ + mesh = new GLMesh(m); + center = QVector3D(m->xmin() + m->xmax(), + m->ymin() + m->ymax(), + m->zmin() + m->zmax()) / 2; + scale = 2 / sqrt( + pow(m->xmax() - m->xmin(), 2) + + pow(m->ymax() - m->ymin(), 2) + + pow(m->zmax() - m->zmin(), 2)); + + update(); + + delete m; +} + +void Canvas::initializeGL() +{ + mesh_shader.addShaderFromSourceFile(QGLShader::Vertex, ":/gl/mesh.vert"); + mesh_shader.addShaderFromSourceFile(QGLShader::Fragment, ":/gl/mesh.frag"); + mesh_shader.link(); + + glClearColor(0.0, 0.0, 0.0, 0.0); + glEnable(GL_DEPTH_TEST); +} + +void Canvas::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (mesh) draw_mesh(); +} + +void Canvas::draw_mesh() +{ + mesh_shader.bind(); + + // Load the transform and view matrices into the shader + glUniformMatrix4fv( + mesh_shader.uniformLocation("transform_matrix"), + 1, GL_FALSE, transform_matrix().data()); + glUniformMatrix4fv( + mesh_shader.uniformLocation("view_matrix"), + 1, GL_FALSE, view_matrix().data()); + + // Find and enable the attribute location for vertex position + const GLuint vp = mesh_shader.attributeLocation("vertex_position"); + glEnableVertexAttribArray(vp); + + // Then draw the mesh with that vertex position + mesh->draw(vp); + + // Clean up state machine + glDisableVertexAttribArray(vp); + mesh_shader.release(); +} + +QMatrix4x4 Canvas::transform_matrix() const +{ + QMatrix4x4 m; + m.rotate(tilt, QVector3D(1, 0, 0)); + m.rotate(yaw, QVector3D(0, 0, 1)); + m.scale(scale); + m.translate(-center); + return m; +} + +QMatrix4x4 Canvas::view_matrix() const +{ + QMatrix4x4 m; + if (width() > height()) + { + m.scale(height() / float(width()), 1, 0.5); + } + else + { + m.scale(1, width() / float(height()), 0.5); + } + return m; +} + +void Canvas::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + mouse_pos = event->pos(); + setCursor(Qt::ClosedHandCursor); + } +} + +void Canvas::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + unsetCursor(); + } +} + +void Canvas::mouseMoveEvent(QMouseEvent* event) +{ + if (event->buttons() & Qt::LeftButton) + { + auto p = event->pos(); + auto d = p - mouse_pos; + yaw = fmod(yaw + d.x(), 360); + tilt = fmax(0, fmin(180, tilt - d.y())); + mouse_pos = p; + update(); + } +} diff --git a/src/canvas.h b/src/canvas.h new file mode 100644 index 0000000..e867898 --- /dev/null +++ b/src/canvas.h @@ -0,0 +1,52 @@ +#ifndef CANVAS_H +#define CANVAS_H + +#include +#include +#include +#include + +class GLMesh; +class Mesh; + +class Canvas : public QGLWidget +{ + Q_OBJECT + +public: + Canvas(const QGLFormat& format, QWidget* parent=0); + + void initializeGL(); + void paintGL(); + ~Canvas(); + +public slots: + void load_mesh(Mesh* m); + + +protected: + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + + +private: + void draw_mesh(); + + QMatrix4x4 transform_matrix() const; + QMatrix4x4 view_matrix() const; + + QGLShaderProgram mesh_shader; + QGLShaderProgram quad_shader; + + GLMesh* mesh; + + QVector3D center; + float scale; + float tilt; + float yaw; + + QPoint mouse_pos; +}; + +#endif // CANVAS_H diff --git a/src/fstl.pro b/src/fstl.pro new file mode 100644 index 0000000..ac58ee1 --- /dev/null +++ b/src/fstl.pro @@ -0,0 +1,26 @@ +QT += core gui opengl widgets + +TARGET = fstl +TEMPLATE = app + +SOURCES += \ + main.cpp\ + canvas.cpp \ + mesh.cpp \ + glmesh.cpp \ + loader.cpp \ + window.cpp + +HEADERS += \ + canvas.h \ + mesh.h \ + glmesh.h \ + loader.h \ + window.h + +CONFIG += c++11 + +INCLUDEPATH += /usr/local/include/eigen3 + +RESOURCES += \ + resources.qrc diff --git a/src/gl/mesh.frag b/src/gl/mesh.frag new file mode 100644 index 0000000..5949025 --- /dev/null +++ b/src/gl/mesh.frag @@ -0,0 +1,15 @@ +#version 120 + +varying vec3 ec_pos; + +void main() { + vec3 base3 = vec3(0.99, 0.96, 0.89); + vec3 base2 = vec3(0.92, 0.91, 0.83); + vec3 base00 = vec3(0.40, 0.48, 0.51); + vec3 ec_normal = normalize(cross(dFdx(ec_pos), dFdy(ec_pos))); + float a = dot(ec_normal, vec3(0.0, 0.0, 1.0)); + float b = dot(ec_normal, vec3(-0.57, -0.57, 0.57)); + + gl_FragColor = vec4((a*base2 + (1-a)*base00)*0.5 + + (b*base3 + (1-b)*base00)*0.5, 1.0); +} diff --git a/src/gl/mesh.vert b/src/gl/mesh.vert new file mode 100644 index 0000000..e60e76b --- /dev/null +++ b/src/gl/mesh.vert @@ -0,0 +1,13 @@ +#version 120 +attribute vec3 vertex_position; + +uniform mat4 transform_matrix; +uniform mat4 view_matrix; + +varying vec3 ec_pos; + +void main() { + gl_Position = view_matrix*transform_matrix* + vec4(vertex_position, 1.0); + ec_pos = gl_Position.xyz; +} diff --git a/src/gl/quad.frag b/src/gl/quad.frag new file mode 100644 index 0000000..1c02e1b --- /dev/null +++ b/src/gl/quad.frag @@ -0,0 +1,7 @@ +#version 120 + +varying vec3 frag_color; + +void main() { + gl_FragColor = vec4(frag_color, 1.0); +} diff --git a/src/gl/quad.vert b/src/gl/quad.vert new file mode 100644 index 0000000..6698297 --- /dev/null +++ b/src/gl/quad.vert @@ -0,0 +1,10 @@ +#version 120 +attribute vec2 vertex_position; +attribute vec3 vertex_color; + +varying vec3 frag_color; + +void main() { + gl_Position = vec4(vertex_position, 0.9, 1.0); + frag_color = vertex_color; +} diff --git a/src/glmesh.cpp b/src/glmesh.cpp new file mode 100644 index 0000000..8e0cab3 --- /dev/null +++ b/src/glmesh.cpp @@ -0,0 +1,35 @@ +#include "glmesh.h" +#include "mesh.h" + +GLMesh::GLMesh(const Mesh* const mesh) + : vertices(QGLBuffer::VertexBuffer), indices(QGLBuffer::IndexBuffer) +{ + vertices.create(); + indices.create(); + + vertices.setUsagePattern(QGLBuffer::StaticDraw); + indices.setUsagePattern(QGLBuffer::StaticDraw); + + vertices.bind(); + vertices.allocate(mesh->vertices.data(), + mesh->vertices.size() * sizeof(float)); + vertices.release(); + + indices.bind(); + indices.allocate(mesh->indices.data(), + mesh->indices.size() * sizeof(uint32_t)); + indices.release(); +} + +void GLMesh::draw(GLuint vp) +{ + vertices.bind(); + indices.bind(); + + glVertexAttribPointer(vp, 3, GL_FLOAT, false, 3*sizeof(float), NULL); + glDrawElements(GL_TRIANGLES, indices.size() / sizeof(uint32_t), + GL_UNSIGNED_INT, NULL); + + vertices.release(); + indices.release(); +} diff --git a/src/glmesh.h b/src/glmesh.h new file mode 100644 index 0000000..01bf921 --- /dev/null +++ b/src/glmesh.h @@ -0,0 +1,18 @@ +#ifndef GLMESH_H +#define GLMESH_H + +#include + +class Mesh; + +class GLMesh +{ +public: + GLMesh(const Mesh* const mesh); + void draw(GLuint vp); +private: + QGLBuffer vertices; + QGLBuffer indices; +}; + +#endif // GLMESH_H diff --git a/src/loader.cpp b/src/loader.cpp new file mode 100644 index 0000000..8666161 --- /dev/null +++ b/src/loader.cpp @@ -0,0 +1,12 @@ +#include "loader.h" +#include "mesh.h" + +Loader::Loader(QObject* parent, const QString& filename) + : QThread(parent), filename(filename) +{ +} + +void Loader::run() +{ + emit got_mesh(Mesh::load_stl(filename)); +} diff --git a/src/loader.h b/src/loader.h new file mode 100644 index 0000000..29508f0 --- /dev/null +++ b/src/loader.h @@ -0,0 +1,23 @@ +#ifndef LOADER_H +#define LOADER_H + +#include + +#include "mesh.h" + +class Loader : public QThread +{ + Q_OBJECT +public: + explicit Loader(QObject* parent, const QString& filename); + void run(); + +signals: + void got_mesh(Mesh* m); + +private: + const QString filename; + +}; + +#endif // LOADER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..35a853a --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,16 @@ +#include + +#include "window.h" +#include "mesh.h" +#include "glmesh.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + Window window; + window.show(); + + window.load_stl("../../splitter/cayman.stl"); + return a.exec(); +} diff --git a/src/mesh.cpp b/src/mesh.cpp new file mode 100644 index 0000000..d7266e7 --- /dev/null +++ b/src/mesh.cpp @@ -0,0 +1,87 @@ +#include +#include + +#include + +#include "mesh.h" + +Mesh::Mesh(const Eigen::Matrix3Xf& v, const Eigen::Matrix3Xi& i) + : vertices(v), indices(i) +{ + // Nothing to do here +} + +Mesh* Mesh::load_stl(const QString& filename) +{ + QFile file(filename); + file.open(QIODevice::ReadOnly); + + QDataStream data(&file); + data.setByteOrder(QDataStream::LittleEndian); + data.setFloatingPointPrecision(QDataStream::SinglePrecision); + + data.skipRawData(80); + uint32_t tri_count; + data >> tri_count; + + // Extract vertices into a vector of Vector4d objects + std::vector verts(tri_count*3); + for (unsigned i=0; i < tri_count; ++i) + { + data.skipRawData(3*sizeof(float)); + for (int j=0; j < 3; ++j) + { + float x, y, z; + data >> x >> y >> z; + verts[3*i + j] << x, y, z, 3*i + j; + } + data.skipRawData(sizeof(uint16_t)); + } + + // Sort the set of vertices (to deduplicate) + std::sort(verts.begin(), verts.end(), + [](const Eigen::Vector4d& lhs, const Eigen::Vector4d& rhs) + { + if (lhs[0] != rhs[0]) return lhs[0] < rhs[0]; + else if (lhs[1] != rhs[1]) return lhs[1] < rhs[1]; + else if (lhs[2] != rhs[2]) return lhs[2] < rhs[2]; + else return false; + } + ); + + // This list will store unique vertices + std::list unique; + + // This vector will store triangles as rows of indices + Eigen::Matrix3Xi indices; + indices.resize(Eigen::NoChange, tri_count); + + // Go through the sorted vertex list, deduplicating and creating + // an indexed geometry representation for the triangles. + for (auto v : verts) + { + if (!unique.size() || v[0] != unique.back()[0] || + v[1] != unique.back()[1] || + v[2] != unique.back()[2]) + { + // Switch to a float vector and save in the list. + Eigen::Vector3f v_; + v_ << v[0], v[1], v[2]; + unique.push_back(v_); + } + indices(int(v[3]) % 3, int(v[3]) / 3) = unique.size() - 1; + } + + // Finally, pack unique vertices into a matrix. + Eigen::Matrix3Xf unique_verts; + unique_verts.resize(Eigen::NoChange, unique.size()); + { + auto v = unique.begin(); + for (unsigned i=0; i < unique.size(); ++i) + { + unique_verts.col(i) = *(v++); + } + } + + return new Mesh(unique_verts, indices); +} diff --git a/src/mesh.h b/src/mesh.h new file mode 100644 index 0000000..064d276 --- /dev/null +++ b/src/mesh.h @@ -0,0 +1,28 @@ +#ifndef MESH_H +#define MESH_H + +#include + +#include + +class Mesh +{ +public: + Mesh(const Eigen::Matrix3Xf &vertices, const Eigen::Matrix3Xi &indices); + static Mesh* load_stl(const QString& filename); + + float xmin() const { return vertices.row(0).minCoeff(); } + float xmax() const { return vertices.row(0).maxCoeff(); } + float ymin() const { return vertices.row(1).minCoeff(); } + float ymax() const { return vertices.row(1).maxCoeff(); } + float zmin() const { return vertices.row(2).minCoeff(); } + float zmax() const { return vertices.row(2).maxCoeff(); } + +private: + const Eigen::Matrix3Xf vertices; + const Eigen::Matrix3Xi indices; + + friend class GLMesh; +}; + +#endif // MESH_H diff --git a/src/resources.qrc b/src/resources.qrc new file mode 100644 index 0000000..1cb5c2c --- /dev/null +++ b/src/resources.qrc @@ -0,0 +1,8 @@ + + + gl/mesh.frag + gl/mesh.vert + gl/quad.frag + gl/quad.vert + + diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..55669a6 --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,26 @@ +#include "window.h" +#include "canvas.h" +#include "loader.h" + +Window::Window(QWidget *parent) : + QMainWindow(parent) +{ + setWindowTitle("fstl"); + + QGLFormat format; + format.setVersion(2, 1); + format.setSampleBuffers(true); + + canvas = new Canvas(format, this); + setCentralWidget(canvas); +} + +void Window::load_stl(const QString &filename) +{ + Loader* loader = new Loader(this, filename); + connect(loader, SIGNAL(got_mesh(Mesh*)), + canvas, SLOT(load_mesh(Mesh*))); + connect(loader, SIGNAL(finished()), + loader, SLOT(deleteLater())); + loader->start(); +} diff --git a/src/window.h b/src/window.h new file mode 100644 index 0000000..809aeb7 --- /dev/null +++ b/src/window.h @@ -0,0 +1,19 @@ +#ifndef WINDOW_H +#define WINDOW_H + +#include + +class Canvas; + +class Window : public QMainWindow +{ + Q_OBJECT +public: + explicit Window(QWidget* parent=0); + void load_stl(const QString& filename); + +private: + Canvas* canvas; +}; + +#endif // WINDOW_H -- 2.39.5