--- /dev/null
+build/*
+*.pro.user
+*.qmake.stash
--- /dev/null
+#include <QMouseEvent>
+#include <QDebug>
+
+#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();
+ }
+}
--- /dev/null
+#ifndef CANVAS_H
+#define CANVAS_H
+
+#include <QWidget>
+#include <QtOpenGL/QGLWidget>
+#include <QtOpenGL/QGLShaderProgram>
+#include <QMatrix4x4>
+
+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
--- /dev/null
+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
--- /dev/null
+#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);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#version 120
+
+varying vec3 frag_color;
+
+void main() {
+ gl_FragColor = vec4(frag_color, 1.0);
+}
--- /dev/null
+#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;
+}
--- /dev/null
+#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();
+}
--- /dev/null
+#ifndef GLMESH_H
+#define GLMESH_H
+
+#include <QtOpenGL/QGLBuffer>
+
+class Mesh;
+
+class GLMesh
+{
+public:
+ GLMesh(const Mesh* const mesh);
+ void draw(GLuint vp);
+private:
+ QGLBuffer vertices;
+ QGLBuffer indices;
+};
+
+#endif // GLMESH_H
--- /dev/null
+#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));
+}
--- /dev/null
+#ifndef LOADER_H
+#define LOADER_H
+
+#include <QThread>
+
+#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
--- /dev/null
+#include <QApplication>
+
+#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();
+}
--- /dev/null
+#include <QFile>
+#include <QDataStream>
+
+#include <algorithm>
+
+#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<Eigen::Vector4d> 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<Eigen::Vector3f> 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);
+}
--- /dev/null
+#ifndef MESH_H
+#define MESH_H
+
+#include <QString>
+
+#include <Eigen/Dense>
+
+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
--- /dev/null
+<RCC>
+ <qresource prefix="/">
+ <file>gl/mesh.frag</file>
+ <file>gl/mesh.vert</file>
+ <file>gl/quad.frag</file>
+ <file>gl/quad.vert</file>
+ </qresource>
+</RCC>
--- /dev/null
+#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();
+}
--- /dev/null
+#ifndef WINDOW_H
+#define WINDOW_H
+
+#include <QMainWindow>
+
+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