aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/arap.cpp57
-rw-r--r--src/arap.h46
-rwxr-xr-xsrc/glwidget.cpp304
-rwxr-xr-xsrc/glwidget.h72
-rw-r--r--src/graphics/camera.cpp196
-rw-r--r--src/graphics/camera.h55
-rw-r--r--src/graphics/graphicsdebug.cpp126
-rw-r--r--src/graphics/graphicsdebug.h15
-rw-r--r--src/graphics/meshloader.cpp65
-rw-r--r--src/graphics/meshloader.h18
-rw-r--r--src/graphics/shader.cpp286
-rw-r--r--src/graphics/shader.h97
-rw-r--r--src/graphics/shape.cpp272
-rw-r--r--src/graphics/shape.h72
-rwxr-xr-xsrc/main.cpp38
-rwxr-xr-xsrc/mainwindow.cpp17
-rwxr-xr-xsrc/mainwindow.h16
17 files changed, 1752 insertions, 0 deletions
diff --git a/src/arap.cpp b/src/arap.cpp
new file mode 100644
index 0000000..06b8829
--- /dev/null
+++ b/src/arap.cpp
@@ -0,0 +1,57 @@
+#include "arap.h"
+#include "graphics/meshloader.h"
+
+#include <iostream>
+#include <set>
+#include <map>
+#include <vector>
+
+using namespace std;
+using namespace Eigen;
+
+ARAP::ARAP() {}
+
+void ARAP::init(Eigen::Vector3f &coeffMin, Eigen::Vector3f &coeffMax)
+{
+ vector<Vector3f> vertices;
+ vector<Vector3i> triangles;
+
+ // If this doesn't work for you, remember to change your working directory
+ if (MeshLoader::loadTriMesh("meshes/cactus.obj", vertices, triangles)) {
+ m_shape.init(vertices, triangles);
+ }
+
+ // Students, please don't touch this code: get min and max for viewport stuff
+ MatrixX3f all_vertices = MatrixX3f(vertices.size(), 3);
+ int i = 0;
+ for (unsigned long i = 0; i < vertices.size(); ++i) {
+ all_vertices.row(i) = vertices[i];
+ }
+ coeffMin = all_vertices.colwise().minCoeff();
+ coeffMax = all_vertices.colwise().maxCoeff();
+}
+
+// Move an anchored vertex, defined by its index, to targetPosition
+void ARAP::move(int vertex, Vector3f targetPosition)
+{
+ std::vector<Eigen::Vector3f> new_vertices = m_shape.getVertices();
+ const std::unordered_set<int>& anchors = m_shape.getAnchors();
+
+ // TODO: implement ARAP here
+ new_vertices[vertex] = targetPosition;
+
+ // Here are some helpful controls for the application
+ //
+ // - You start in first-person camera mode
+ // - WASD to move, left-click and drag to rotate
+ // - R and F to move vertically up and down
+ //
+ // - C to change to orbit camera mode
+ //
+ // - Right-click (and, optionally, drag) to anchor/unanchor points
+ // - Left-click an anchored point to move it around
+ //
+ // - Minus and equal keys (click repeatedly) to change the size of the vertices
+
+ m_shape.setVertices(new_vertices);
+}
diff --git a/src/arap.h b/src/arap.h
new file mode 100644
index 0000000..afa7d63
--- /dev/null
+++ b/src/arap.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "graphics/shape.h"
+#include "Eigen/StdList"
+#include "Eigen/StdVector"
+
+class Shader;
+
+class ARAP
+{
+private:
+ Shape m_shape;
+
+public:
+ ARAP();
+
+ void init(Eigen::Vector3f &min, Eigen::Vector3f &max);
+ void move(int vertex, Eigen::Vector3f pos);
+
+ // ================== Students, If You Choose To Modify The Code Below, It's On You
+
+ int getClosestVertex(Eigen::Vector3f start, Eigen::Vector3f ray, float threshold)
+ {
+ return m_shape.getClosestVertex(start, ray, threshold);
+ }
+
+ void draw(Shader *shader, GLenum mode)
+ {
+ m_shape.draw(shader, mode);
+ }
+
+ SelectMode select(Shader *shader, int vertex)
+ {
+ return m_shape.select(shader, vertex);
+ }
+
+ bool selectWithSpecifiedMode(Shader *shader, int vertex, SelectMode mode)
+ {
+ return m_shape.selectWithSpecifiedMode(shader, vertex, mode);
+ }
+
+ bool getAnchorPos(int lastSelected, Eigen::Vector3f& pos, Eigen::Vector3f ray, Eigen::Vector3f start)
+ {
+ return m_shape.getAnchorPos(lastSelected, pos, ray, start);
+ }
+};
diff --git a/src/glwidget.cpp b/src/glwidget.cpp
new file mode 100755
index 0000000..8c92f5b
--- /dev/null
+++ b/src/glwidget.cpp
@@ -0,0 +1,304 @@
+#include "glwidget.h"
+
+#include <QApplication>
+#include <QKeyEvent>
+#include <iostream>
+
+#define SPEED 1.5
+#define ROTATE_SPEED 0.0025
+
+using namespace std;
+using namespace Eigen;
+
+GLWidget::GLWidget(QWidget *parent) :
+ QOpenGLWidget(parent),
+ m_arap(),
+ m_camera(),
+ m_defaultShader(),
+ m_pointShader(),
+ m_vSize(),
+ m_movementScaling(),
+ m_vertexSelectionThreshold(),
+ // Movement
+ m_deltaTimeProvider(),
+ m_intervalTimer(),
+ // Timing
+ m_forward(),
+ m_sideways(),
+ m_vertical(),
+ // Mouse handler stuff
+ m_lastX(),
+ m_lastY(),
+ m_leftCapture(false),
+ m_rightCapture(false),
+ m_rightClickSelectMode(SelectMode::None),
+ m_lastSelectedVertex(-1)
+{
+ // GLWidget needs all mouse move events, not just mouse drag events
+ setMouseTracking(true);
+
+ // Hide the cursor since this is a fullscreen app
+ QApplication::setOverrideCursor(Qt::ArrowCursor);
+
+ // GLWidget needs keyboard focus
+ setFocusPolicy(Qt::StrongFocus);
+
+ // Function tick() will be called once per interva
+ connect(&m_intervalTimer, SIGNAL(timeout()), this, SLOT(tick()));
+}
+
+GLWidget::~GLWidget()
+{
+ if (m_defaultShader != nullptr) delete m_defaultShader;
+ if (m_pointShader != nullptr) delete m_pointShader;
+}
+
+// ================== Basic OpenGL Overrides
+
+void GLWidget::initializeGL()
+{
+ // Initialize GL extension wrangler
+ glewExperimental = GL_TRUE;
+ GLenum err = glewInit();
+ if (err != GLEW_OK) fprintf(stderr, "Error while initializing GLEW: %s\n", glewGetErrorString(err));
+ fprintf(stdout, "Successfully initialized GLEW %s\n", glewGetString(GLEW_VERSION));
+
+ // Set clear color to white
+ glClearColor(1, 1, 1, 1);
+
+ // Enable depth-testing and backface culling
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+ glCullFace(GL_BACK);
+
+ // Initialize shaders
+ m_defaultShader = new Shader(":resources/shaders/shader.vert", ":resources/shaders/shader.frag");
+ m_pointShader = new Shader(":resources/shaders/anchorPoint.vert", ":resources/shaders/anchorPoint.geom", ":resources/shaders/anchorPoint.frag");
+
+ // Initialize ARAP, and get parameters needed to decide the camera position, etc
+ Vector3f coeffMin, coeffMax;
+ m_arap.init(coeffMin, coeffMax);
+
+ Vector3f center = (coeffMax + coeffMin) / 2.0f;
+ float extentLength = (coeffMax - coeffMin).norm();
+
+ // Screen-space size of vertex points
+ m_vSize = 0.005 * extentLength;
+
+ // Scale all movement by this amount
+ m_movementScaling = extentLength * 0.5;
+
+ // When raycasting, select closest vertex within this distance
+ m_vertexSelectionThreshold = extentLength * 0.025;
+
+ // Note for maintainers: Z-up
+ float fovY = 120;
+ float nearPlane = 0.0001f;
+ float farPlane = 3 * extentLength;
+
+ // Initialize camera with a reasonable transform
+ Eigen::Vector3f eye = center - Eigen::Vector3f::UnitZ() * extentLength;
+ Eigen::Vector3f target = center;
+ m_camera.lookAt(eye, target);
+ m_camera.setOrbitPoint(target);
+ m_camera.setPerspective(120, width() / static_cast<float>(height()), nearPlane, farPlane);
+
+ m_deltaTimeProvider.start();
+ m_intervalTimer.start(1000 / 60);
+}
+
+void GLWidget::paintGL()
+{
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ m_defaultShader->bind();
+ m_defaultShader->setUniform("proj", m_camera.getProjection());
+ m_defaultShader->setUniform("view", m_camera.getView());
+ m_arap.draw(m_defaultShader, GL_TRIANGLES);
+ m_defaultShader->unbind();
+
+ glClear(GL_DEPTH_BUFFER_BIT);
+
+ m_pointShader->bind();
+ m_pointShader->setUniform("proj", m_camera.getProjection());
+ m_pointShader->setUniform("view", m_camera.getView());
+ m_pointShader->setUniform("vSize", m_vSize);
+ m_pointShader->setUniform("width", width());
+ m_pointShader->setUniform("height", height());
+ m_arap.draw(m_pointShader, GL_POINTS);
+ m_pointShader->unbind();
+}
+
+void GLWidget::resizeGL(int w, int h)
+{
+ glViewport(0, 0, w, h);
+ m_camera.setAspect(static_cast<float>(w) / h);
+}
+
+// ================== Event Listeners
+
+Eigen::Vector3f GLWidget::transformToWorldRay(int x, int y)
+{
+ Eigen::Vector4f clipCoords = Eigen::Vector4f(
+ (float(x) / width()) * 2.f - 1.f,
+ 1.f - (float(y) / height()) * 2.f,
+ -1.f,
+ 1.f);
+
+ Eigen::Vector4f transformed_coords = m_camera.getProjection().inverse() * clipCoords;
+ transformed_coords = Eigen::Vector4f(transformed_coords.x(), transformed_coords.y(), -1.f, 0.f);
+ transformed_coords = m_camera.getView().inverse() * transformed_coords;
+
+ return Eigen::Vector3f(transformed_coords.x(), transformed_coords.y(), transformed_coords.z()).normalized();
+}
+
+void GLWidget::mousePressEvent(QMouseEvent *event)
+{
+ // Get current mouse coordinates
+ const int currX = event->position().x();
+ const int currY = event->position().y();
+
+ // Get closest vertex to ray
+ const Vector3f ray = transformToWorldRay(currX, currY);
+ const int closest_vertex = m_arap.getClosestVertex(m_camera.getPosition(), ray, m_vertexSelectionThreshold);
+
+ // Switch on button
+ switch (event->button()) {
+ case Qt::MouseButton::RightButton: {
+ // Capture
+ m_rightCapture = true;
+ // Anchor/un-anchor the vertex
+ m_rightClickSelectMode = m_arap.select(m_pointShader, closest_vertex);
+ break;
+ }
+ case Qt::MouseButton::LeftButton: {
+ // Capture
+ m_leftCapture = true;
+ // Select this vertex
+ m_lastSelectedVertex = closest_vertex;
+ break;
+ }
+ default: break;
+ }
+
+ // Set last mouse coordinates
+ m_lastX = currX;
+ m_lastY = currY;
+}
+
+void GLWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ // Return if neither mouse button is currently held down
+ if (!(m_leftCapture || m_rightCapture)) {
+ return;
+ }
+
+ // Get current mouse coordinates
+ const int currX = event->position().x();
+ const int currY = event->position().y();
+
+ // Find ray
+ const Vector3f ray = transformToWorldRay(event->position().x(), event->position().y());
+ Vector3f pos;
+
+ // If right is held down
+ if (m_rightCapture) {
+ // Get closest vertex to ray
+ const int closest_vertex = m_arap.getClosestVertex(m_camera.getPosition(), ray, m_vertexSelectionThreshold);
+
+ // Anchor/un-anchor the vertex
+ if (m_rightClickSelectMode == SelectMode::None) {
+ m_rightClickSelectMode = m_arap.select(m_pointShader, closest_vertex);
+ } else {
+ m_arap.selectWithSpecifiedMode(m_pointShader, closest_vertex, m_rightClickSelectMode);
+ }
+
+ return;
+ }
+
+ // If the selected point is an anchor point
+ if (m_lastSelectedVertex != -1 && m_arap.getAnchorPos(m_lastSelectedVertex, pos, ray, m_camera.getPosition())) {
+ // Move it
+ m_arap.move(m_lastSelectedVertex, pos);
+ } else {
+ // Rotate the camera
+ const int deltaX = currX - m_lastX;
+ const int deltaY = currY - m_lastY;
+ if (deltaX != 0 || deltaY != 0) {
+ m_camera.rotate(deltaY * ROTATE_SPEED, -deltaX * ROTATE_SPEED);
+ }
+ }
+
+ // Set last mouse coordinates
+ m_lastX = currX;
+ m_lastY = currY;
+}
+
+void GLWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ m_leftCapture = false;
+ m_lastSelectedVertex = -1;
+
+ m_rightCapture = false;
+ m_rightClickSelectMode = SelectMode::None;
+}
+
+void GLWidget::wheelEvent(QWheelEvent *event)
+{
+ float zoom = 1 - event->pixelDelta().y() * 0.1f / 120.f;
+ m_camera.zoom(zoom);
+}
+
+void GLWidget::keyPressEvent(QKeyEvent *event)
+{
+ if (event->isAutoRepeat()) return;
+
+ switch (event->key())
+ {
+ case Qt::Key_W: m_forward += SPEED; break;
+ case Qt::Key_S: m_forward -= SPEED; break;
+ case Qt::Key_A: m_sideways -= SPEED; break;
+ case Qt::Key_D: m_sideways += SPEED; break;
+ case Qt::Key_F: m_vertical -= SPEED; break;
+ case Qt::Key_R: m_vertical += SPEED; break;
+ case Qt::Key_C: m_camera.toggleIsOrbiting(); break;
+ case Qt::Key_Equal: m_vSize *= 11.0f / 10.0f; break;
+ case Qt::Key_Minus: m_vSize *= 10.0f / 11.0f; break;
+ case Qt::Key_Escape: QApplication::quit();
+ }
+}
+
+void GLWidget::keyReleaseEvent(QKeyEvent *event)
+{
+ if (event->isAutoRepeat()) return;
+
+ switch (event->key())
+ {
+ case Qt::Key_W: m_forward -= SPEED; break;
+ case Qt::Key_S: m_forward += SPEED; break;
+ case Qt::Key_A: m_sideways += SPEED; break;
+ case Qt::Key_D: m_sideways -= SPEED; break;
+ case Qt::Key_F: m_vertical += SPEED; break;
+ case Qt::Key_R: m_vertical -= SPEED; break;
+ }
+}
+
+// ================== Physics Tick
+
+void GLWidget::tick()
+{
+ float deltaSeconds = m_deltaTimeProvider.restart() / 1000.f;
+
+ // Move camera
+ auto look = m_camera.getLook();
+ look.y() = 0;
+ look.normalize();
+ Eigen::Vector3f perp(-look.z(), 0, look.x());
+ Eigen::Vector3f moveVec = m_forward * look.normalized() + m_sideways * perp.normalized() + m_vertical * Eigen::Vector3f::UnitY();
+ moveVec *= m_movementScaling;
+ moveVec *= deltaSeconds;
+ m_camera.move(moveVec);
+
+ // Flag this view for repainting (Qt will call paintGL() soon after)
+ update();
+}
diff --git a/src/glwidget.h b/src/glwidget.h
new file mode 100755
index 0000000..b319756
--- /dev/null
+++ b/src/glwidget.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#ifdef __APPLE__
+#define GL_SILENCE_DEPRECATION
+#endif
+
+#include "arap.h"
+#include "graphics/camera.h"
+#include "graphics/shader.h"
+
+#include <QOpenGLWidget>
+#include <QElapsedTimer>
+#include <QTimer>
+#include <memory>
+
+class GLWidget : public QOpenGLWidget
+{
+ Q_OBJECT
+
+public:
+ GLWidget(QWidget *parent = nullptr);
+ ~GLWidget();
+
+private:
+ static const int FRAMES_TO_AVERAGE = 30;
+
+ Eigen::Vector3f transformToWorldRay(int x, int y);
+
+ // Basic OpenGL Overrides
+ void initializeGL() override;
+ void paintGL() override;
+ void resizeGL(int w, int h) override;
+
+ // Event Listeners
+ void mousePressEvent (QMouseEvent *event) override;
+ void mouseMoveEvent (QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void wheelEvent (QWheelEvent *event) override;
+ void keyPressEvent (QKeyEvent *event) override;
+ void keyReleaseEvent (QKeyEvent *event) override;
+
+private slots:
+ // Physics Tick
+ void tick();
+
+private:
+ ARAP m_arap;
+ Camera m_camera;
+ Shader *m_defaultShader;
+ Shader *m_pointShader;
+
+ float m_movementScaling;
+ float m_vertexSelectionThreshold;
+ float m_vSize;
+
+ // Timing
+ QElapsedTimer m_deltaTimeProvider; // For measuring elapsed time
+ QTimer m_intervalTimer; // For triggering timed events
+
+ // Movement
+ int m_forward;
+ int m_sideways;
+ int m_vertical;
+
+ // Mouse handler stuff
+ int m_lastX;
+ int m_lastY;
+ bool m_leftCapture;
+ bool m_rightCapture;
+ SelectMode m_rightClickSelectMode;
+ int m_lastSelectedVertex = -1;
+};
diff --git a/src/graphics/camera.cpp b/src/graphics/camera.cpp
new file mode 100644
index 0000000..cf0277a
--- /dev/null
+++ b/src/graphics/camera.cpp
@@ -0,0 +1,196 @@
+#include "graphics/camera.h"
+
+#include <iostream>
+
+Camera::Camera()
+ : m_position(0,0,0),
+ m_pitch(0),
+ m_yaw(0),
+ m_look(0, 0, 1),
+ m_orbitPoint(0, 0, 0),
+ m_isOrbiting(false),
+ m_view(Eigen::Matrix4f::Identity()),
+ m_proj(Eigen::Matrix4f::Identity()),
+ m_viewDirty(true),
+ m_projDirty(true),
+ m_fovY(90),
+ m_aspect(1),
+ m_near(0.1f),
+ m_far(50.f),
+ m_zoom(1)
+{}
+
+// ================== Position
+
+void Camera::setPosition(const Eigen::Vector3f &position)
+{
+ m_position = position;
+ m_viewDirty = true;
+}
+
+void Camera::move(const Eigen::Vector3f &deltaPosition)
+{
+ if (deltaPosition.squaredNorm() == 0) return;
+
+ m_position += deltaPosition;
+
+ if (m_isOrbiting) {
+ m_orbitPoint += deltaPosition;
+ }
+
+ m_viewDirty = true;
+}
+
+Eigen::Vector3f Camera::getPosition()
+{
+ return m_position;
+}
+
+// ================== Rotation
+
+void Camera::setRotation(float pitch, float yaw)
+{
+ m_pitch = pitch;
+ m_yaw = yaw;
+ m_viewDirty = true;
+ updateLook();
+}
+
+void Camera::rotate(float deltaPitch, float deltaYaw)
+{
+ m_pitch += deltaPitch;
+ m_yaw += deltaYaw;
+ m_pitch = std::clamp(m_pitch, (float) -M_PI_2 + 0.01f, (float) M_PI_2 - 0.01f);
+ m_viewDirty = true;
+ updateLook();
+
+ if (m_isOrbiting) {
+ m_position = m_orbitPoint - m_look * m_zoom;
+ }
+}
+
+// ================== Position and Rotation
+
+void Camera::lookAt(const Eigen::Vector3f &eye, const Eigen::Vector3f &target)
+{
+ m_position = eye;
+ m_look = (target - eye).normalized();
+ m_viewDirty = true;
+ updatePitchAndYaw();
+}
+
+// ================== Orbiting
+
+void Camera::setOrbitPoint(const Eigen::Vector3f &orbitPoint)
+{
+ m_orbitPoint = orbitPoint;
+ m_viewDirty = true;
+}
+
+bool Camera::getIsOrbiting()
+{
+ return m_isOrbiting;
+}
+
+void Camera::setIsOrbiting(bool isOrbiting)
+{
+ m_isOrbiting = isOrbiting;
+ m_viewDirty = true;
+}
+
+void Camera::toggleIsOrbiting()
+{
+ m_isOrbiting = !m_isOrbiting;
+ m_viewDirty = true;
+
+ if (m_isOrbiting) {
+ m_zoom = (m_orbitPoint - m_position).norm();
+ m_look = (m_orbitPoint - m_position).normalized();
+ updatePitchAndYaw();
+ }
+}
+
+void Camera::zoom(float zoomMultiplier)
+{
+ if (!m_isOrbiting) return;
+
+ m_zoom *= zoomMultiplier;
+ m_position = m_orbitPoint - m_look * m_zoom;
+ m_viewDirty = true;
+}
+
+// ================== Important Getters
+
+const Eigen::Matrix4f &Camera::getView()
+{
+ if (m_viewDirty) {
+ Eigen::Matrix3f R;
+ Eigen::Vector3f f = m_look.normalized();
+ Eigen::Vector3f u = Eigen::Vector3f::UnitY();
+ Eigen::Vector3f s = f.cross(u).normalized();
+ u = s.cross(f);
+ R.col(0) = s;
+ R.col(1) = u;
+ R.col(2) = -f;
+ m_view.topLeftCorner<3, 3>() = R.transpose();
+ m_view.topRightCorner<3, 1>() = -R.transpose() * m_position;
+ m_view(3, 3) = 1.f;
+ m_viewDirty = false;
+ }
+ return m_view;
+}
+
+const Eigen::Matrix4f &Camera::getProjection()
+{
+ if (m_projDirty) {
+ float theta = m_fovY * 0.5f;
+ float invRange = 1.f / (m_far - m_near);
+ float invtan = 1.f / tanf(theta);
+ m_proj(0, 0) = invtan / m_aspect;
+ m_proj(1, 1) = invtan;
+ m_proj(2, 2) = -(m_near + m_far) * invRange;
+ m_proj(3, 2) = -1;
+ m_proj(2, 3) = -2 * m_near * m_far * invRange;
+ m_proj(3, 3) = 0;
+ m_projDirty = false;
+ }
+ return m_proj;
+}
+
+const Eigen::Vector3f &Camera::getLook()
+{
+ return m_look;
+}
+
+// ================== Intrinsics
+
+void Camera::setPerspective(float fovY, float aspect, float near, float far)
+{
+ m_fovY = fovY;
+ m_aspect = aspect;
+ m_near = near;
+ m_far = far;
+ m_projDirty = true;
+}
+
+void Camera::setAspect(float aspect)
+{
+ m_aspect = aspect;
+ m_projDirty = true;
+}
+
+// ================== Private Helpers
+
+void Camera::updateLook()
+{
+ m_look = Eigen::Vector3f(0, 0, 1);
+ m_look = Eigen::AngleAxis<float>(m_pitch, Eigen::Vector3f::UnitX()) * m_look;
+ m_look = Eigen::AngleAxis<float>(m_yaw, Eigen::Vector3f::UnitY()) * m_look;
+ m_look = m_look.normalized();
+}
+
+void Camera::updatePitchAndYaw()
+{
+ m_pitch = asinf(-m_look.y());
+ m_yaw = atan2f(m_look.x(), m_look.z());
+}
diff --git a/src/graphics/camera.h b/src/graphics/camera.h
new file mode 100644
index 0000000..de362e0
--- /dev/null
+++ b/src/graphics/camera.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "Eigen/Dense"
+
+class Camera
+{
+public:
+ EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+ Camera();
+
+ void setPosition(const Eigen::Vector3f &position);
+ void move (const Eigen::Vector3f &deltaPosition);
+
+ Eigen::Vector3f getPosition();
+
+ void setRotation(float pitch, float yaw);
+ void rotate (float deltaPitch, float deltaYaw);
+
+ void lookAt(const Eigen::Vector3f &eye, const Eigen::Vector3f &target);
+
+ void setOrbitPoint(const Eigen::Vector3f &target);
+ bool getIsOrbiting();
+ void setIsOrbiting(bool orbit);
+ void toggleIsOrbiting();
+ void zoom(float zoomMultiplier);
+
+ const Eigen::Matrix4f &getView();
+ const Eigen::Matrix4f &getProjection();
+ const Eigen::Vector3f &getLook();
+
+ void setPerspective(float fovY, float aspect, float near, float far);
+ void setAspect(float aspect);
+
+private:
+ void updateLook();
+ void updatePitchAndYaw();
+
+ Eigen::Vector3f m_position;
+ float m_pitch;
+ float m_yaw;
+ Eigen::Vector3f m_look;
+ Eigen::Vector3f m_orbitPoint;
+ bool m_isOrbiting;
+
+ Eigen::Matrix4f m_view;
+ Eigen::Matrix4f m_proj;
+ bool m_viewDirty;
+ bool m_projDirty;
+
+ float m_fovY;
+ float m_aspect;
+ float m_near;
+ float m_far;
+ float m_zoom;
+};
diff --git a/src/graphics/graphicsdebug.cpp b/src/graphics/graphicsdebug.cpp
new file mode 100644
index 0000000..b9d831c
--- /dev/null
+++ b/src/graphics/graphicsdebug.cpp
@@ -0,0 +1,126 @@
+#include <GL/glew.h>
+#include "graphics/graphicsdebug.h"
+
+#include <iostream>
+#include <vector>
+
+
+void checkError(std::string prefix) {
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ std::cerr << prefix << (prefix == std::string("") ? "" : ": ") << "GL is in an error state before painting." << std::endl;
+ printGLErrorCodeInEnglish(err);
+ }
+}
+
+void printGLErrorCodeInEnglish(GLenum err) {
+ std::cerr << "GL error code " << err << ":" << std::endl;
+ switch(err) {
+ case GL_INVALID_ENUM:
+ std::cerr << "GL_INVALID_ENUM" << std::endl;
+ std::cerr << "An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_INVALID_VALUE:
+ std::cerr << "GL_INVALID_VALUE" << std::endl;
+ std::cerr << "A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_INVALID_OPERATION:
+ std::cerr << "GL_INVALID_OPERATION" << std::endl;
+ std::cerr << "The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ std::cerr << "GL_INVALID_FRAMEBUFFER_OPERATION" << std::endl;
+ std::cerr << "The framebuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag." << std::endl;
+ break;
+ case GL_OUT_OF_MEMORY:
+ std::cerr << "GL_OUT_OF_MEMORY" << std::endl;
+ std::cerr << "There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded." << std::endl;
+ break;
+ case GL_STACK_UNDERFLOW:
+ std::cerr << "GL_STACK_UNDERFLOW" << std::endl;
+ std::cerr << "An attempt has been made to perform an operation that would cause an internal stack to underflow." << std::endl;
+ break;
+ case GL_STACK_OVERFLOW:
+ std::cerr << "GL_STACK_OVERFLOW" << std::endl;
+ std::cerr << "An attempt has been made to perform an operation that would cause an internal stack to overflow." << std::endl;
+ break;
+ default:
+ std::cerr << "Unknown GL error code" << std::endl;
+ }
+}
+
+void checkFramebufferStatus() {
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ std::cerr << "Framebuffer is incomplete." << std::endl;
+ printFramebufferErrorCodeInEnglish(status);
+ }
+}
+
+void printFramebufferErrorCodeInEnglish(GLenum err) {
+ switch(err) {
+ case GL_FRAMEBUFFER_UNDEFINED:
+ std:: cerr << "GL_FRAMEBUFFER_UNDEFINED is returned if the specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT is returned if any of the framebuffer attachment points are framebuffer incomplete." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT is returned if the framebuffer does not have at least one image attached to it." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER is returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point(s) named by GL_DRAW_BUFFERi." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER is returned if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_UNSUPPORTED:
+ std::cerr << "GL_FRAMEBUFFER_UNSUPPORTED is returned if the combination of internal formats of the attached images violates an implementation-dependent set of restrictions." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES." << std::endl;
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE is also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures." << std::endl;
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
+ std::cerr << "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS is returned if any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target." << std::endl;
+ break;
+ }
+}
+
+void checkShaderCompilationStatus(GLuint shaderID) {
+ GLint status;
+ glGetShaderiv(shaderID, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE) {
+ std::cerr << "Error: Could not compile shader." << std::endl;
+
+ GLint maxLength = 0;
+ glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &maxLength);
+
+ // The maxLength includes the null character
+ std::vector<GLchar> errorLog(maxLength);
+ glGetShaderInfoLog(shaderID, maxLength, &maxLength, &errorLog[0]);
+
+ std::cerr << &errorLog[0] << std::endl;
+ } else {
+ std::cerr << "Shader compiled." << std::endl;
+ }
+}
+
+void checkShaderLinkStatus(GLuint shaderProgramID) {
+ GLint linked;
+ glGetProgramiv(shaderProgramID, GL_LINK_STATUS, &linked);
+ if (linked == GL_FALSE) {
+ std::cerr << "Shader failed to link" << std::endl;
+
+ GLint maxLength = 0;
+ glGetProgramiv(shaderProgramID, GL_INFO_LOG_LENGTH, &maxLength);
+
+ // The maxLength includes the null character
+ std::vector<GLchar> errorLog(maxLength);
+ glGetProgramInfoLog(shaderProgramID, maxLength, &maxLength, &errorLog[0]);
+
+ std::cerr << &errorLog[0] << std::endl;
+ } else {
+ std::cerr << "Shader linked successfully." << std::endl;
+ }
+}
diff --git a/src/graphics/graphicsdebug.h b/src/graphics/graphicsdebug.h
new file mode 100644
index 0000000..9be33b4
--- /dev/null
+++ b/src/graphics/graphicsdebug.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <GL/glew.h>
+#include <string>
+
+#define GRAPHICS_DEBUG_LEVEL 0
+
+void checkError(std::string prefix = "");
+void printGLErrorCodeInEnglish(GLenum err);
+
+void checkFramebufferStatus();
+void printFramebufferErrorCodeInEnglish(GLenum err);
+
+void checkShaderCompilationStatus(GLuint shaderID);
+void checkShaderLinkStatus(GLuint shaderProgramID);
diff --git a/src/graphics/meshloader.cpp b/src/graphics/meshloader.cpp
new file mode 100644
index 0000000..fc95f6f
--- /dev/null
+++ b/src/graphics/meshloader.cpp
@@ -0,0 +1,65 @@
+#include "graphics/meshloader.h"
+
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "util/tiny_obj_loader.h"
+
+#include <iostream>
+
+#include <QString>
+#include <QFile>
+#include <QTextStream>
+#include <QRegularExpression>
+#include <QFileInfo>
+#include <iostream>
+#include <set>
+
+using namespace std;
+using namespace Eigen;
+
+MeshLoader::MeshLoader() {}
+
+bool MeshLoader::loadTriMesh(const string &filePath, vector<Vector3f> &vertices, vector<Vector3i> &faces)
+{
+ tinyobj::attrib_t attrib;
+ vector<tinyobj::shape_t> shapes;
+ vector<tinyobj::material_t> materials;
+
+ QFileInfo info(QString(filePath.c_str()));
+ string err;
+ bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err,
+ info.absoluteFilePath().toStdString().c_str(), (info.absolutePath().toStdString() + "/").c_str(), true);
+ if (!err.empty()) {
+ cerr << err << endl;
+ }
+
+ if (!ret) {
+ cerr << "Failed to load/parse .obj file" << endl;
+ return false;
+ }
+
+ for (size_t s = 0; s < shapes.size(); s++) {
+ size_t index_offset = 0;
+ for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {
+ unsigned int fv = shapes[s].mesh.num_face_vertices[f];
+
+ Vector3i face;
+ for (size_t v = 0; v < fv; v++) {
+ tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v];
+
+ face[v] = idx.vertex_index;
+
+ }
+ faces.push_back(face);
+
+ index_offset += fv;
+ }
+ }
+
+ for (size_t i = 0; i < attrib.vertices.size(); i += 3) {
+ vertices.emplace_back(attrib.vertices[i], attrib.vertices[i + 1], attrib.vertices[i + 2]);
+ }
+
+ cout << "Loaded " << faces.size() << " faces and " << vertices.size() << " vertices" << endl;
+
+ return true;
+}
diff --git a/src/graphics/meshloader.h b/src/graphics/meshloader.h
new file mode 100644
index 0000000..63a175a
--- /dev/null
+++ b/src/graphics/meshloader.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <vector>
+#include "Eigen/Dense"
+#include "Eigen/StdVector"
+
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Matrix4i)
+
+class MeshLoader
+{
+public:
+ static bool loadTriMesh(const std::string &filepath,
+ std::vector<Eigen::Vector3f> &vertices,
+ std::vector<Eigen::Vector3i> &faces);
+
+private:
+ MeshLoader();
+};
diff --git a/src/graphics/shader.cpp b/src/graphics/shader.cpp
new file mode 100644
index 0000000..6ac9949
--- /dev/null
+++ b/src/graphics/shader.cpp
@@ -0,0 +1,286 @@
+#include "shader.h"
+
+#include <QFile>
+#include <QString>
+#include <QTextStream>
+#include <algorithm>
+#include <iostream>
+#include <utility>
+
+#include "graphicsdebug.h"
+
+Shader::Shader(const std::string &vertexPath, const std::string &fragmentPath)
+{
+ createProgramID();
+ std::vector<GLuint> shaders;
+ shaders.push_back(createVertexShaderFromSource(getFileContents(vertexPath)));
+ shaders.push_back(createFragmentShaderFromSource(getFileContents(fragmentPath)));
+ buildShaderProgramFromShaders(shaders);
+ discoverShaderData();
+}
+
+Shader::Shader(const std::string &vertexPath, const std::string &geometryPath, const std::string &fragmentPath) {
+ createProgramID();
+ std::vector<GLuint> shaders;
+ shaders.push_back(createVertexShaderFromSource(getFileContents(vertexPath)));
+ shaders.push_back(createGeometryShaderFromSource(getFileContents(geometryPath)));
+ shaders.push_back(createFragmentShaderFromSource(getFileContents(fragmentPath)));
+ buildShaderProgramFromShaders(shaders);
+ discoverShaderData();
+}
+
+Shader::~Shader()
+{
+ glDeleteProgram(m_programID);
+}
+
+Shader::Shader(Shader &&that) :
+ m_programID(that.m_programID),
+ m_attributes(std::move(that.m_attributes)),
+ m_uniforms(std::move(that.m_uniforms))
+{
+ that.m_programID = 0;
+}
+
+Shader& Shader::operator=(Shader &&that) {
+ this->~Shader();
+
+ m_programID = that.m_programID;
+ m_attributes = std::move(that.m_attributes);
+ m_uniforms = std::move(that.m_uniforms);
+
+ that.m_programID = 0;
+
+ return *this;
+}
+
+void Shader::bind() const {
+ glUseProgram(m_programID);
+}
+
+void Shader::unbind() const {
+ glUseProgram(0);
+}
+
+GLuint Shader::getUniformLocation(std::string name) {
+ return glGetUniformLocation(m_programID, name.c_str());
+}
+
+GLuint Shader::getEnumeratedUniformLocation(std::string name, int index) {
+ std::string n = name + "[" + std::to_string(index) + "]";
+ return glGetUniformLocation(m_programID, n.c_str());
+}
+
+void Shader::setUniform(const std::string &name, float f) {
+ glUniform1f(m_uniforms[name], f);
+}
+
+void Shader::setUniform(const std::string &name, int i) {
+ glUniform1i(m_uniforms[name], i);
+}
+
+void Shader::setUniform(const std::string &name, bool b) {
+ glUniform1i(m_uniforms[name], static_cast<GLint>(b));
+}
+
+void Shader::setUniformArrayByIndex(const std::string &name, float f, size_t index) {
+ glUniform1f(m_uniformArrays[std::make_tuple(name, index)], f);
+}
+
+void Shader::setUniformArrayByIndex(const std::string &name, int i, size_t index) {
+ glUniform1i(m_uniformArrays[std::make_tuple(name, index)], i);
+}
+
+void Shader::setUniformArrayByIndex(const std::string &name, bool b, size_t index) {
+ glUniform1i(m_uniformArrays[std::make_tuple(name, index)], static_cast<GLint>(b));
+}
+
+void Shader::attachShaders(const std::vector<GLuint> &shaders) {
+ std::for_each(shaders.begin(), shaders.end(), [this](int s){ glAttachShader(m_programID, s); });
+}
+
+void Shader::buildShaderProgramFromShaders(const std::vector<GLuint> &shaders) {
+ attachShaders(shaders);
+ linkShaderProgram();
+ detachShaders(shaders);
+ deleteShaders(shaders);
+}
+
+GLuint Shader::createFragmentShaderFromSource(const std::string &source) {
+ return createShaderFromSource(source, GL_FRAGMENT_SHADER);
+}
+
+GLuint Shader::createGeometryShaderFromSource(const std::string &source) {
+ return createShaderFromSource(source, GL_GEOMETRY_SHADER);
+}
+
+void Shader::compileShader(GLuint handle, const std::string &source) {
+ const GLchar* codeArray[] = { source.c_str() };
+ glShaderSource(handle, 1, codeArray, nullptr);
+ glCompileShader(handle);
+}
+
+GLuint Shader::createVertexShaderFromSource(const std::string &source) {
+ return createShaderFromSource(source, GL_VERTEX_SHADER);
+}
+
+GLuint Shader::createShaderFromSource(const std::string &source, GLenum shaderType) {
+ GLuint shaderHandle = glCreateShader(shaderType);
+ compileShader(shaderHandle, source);
+ checkShaderCompilationStatus(shaderHandle);
+ return shaderHandle;
+}
+
+void Shader::createProgramID() {
+ m_programID = glCreateProgram();
+}
+
+void Shader::detachShaders(const std::vector<GLuint> &shaders) {
+ std::for_each(shaders.begin(), shaders.end(), [this](int s){ glDetachShader(m_programID, s); });
+}
+
+void Shader::deleteShaders(const std::vector<GLuint> &shaders) {
+ std::for_each(shaders.begin(), shaders.end(), [](int s){ glDeleteShader(s); });
+}
+
+void Shader::linkShaderProgram() {
+ glLinkProgram(m_programID);
+ checkShaderLinkStatus(m_programID);
+}
+
+void Shader::discoverShaderData() {
+ discoverAttributes();
+ discoverUniforms();
+}
+
+void Shader::discoverAttributes() {
+ bind();
+ GLint attribCount;
+ glGetProgramiv(m_programID, GL_ACTIVE_ATTRIBUTES, &attribCount);
+ for (int i = 0; i < attribCount; i++) {
+ const GLsizei bufSize = 256;
+ GLsizei nameLength = 0;
+ GLint arraySize = 0;
+ GLenum type;
+ GLchar name[bufSize];
+ glGetActiveAttrib(m_programID, i, bufSize, &nameLength, &arraySize, &type, name);
+ name[std::min(nameLength, bufSize - 1)] = 0;
+ m_attributes[std::string(name)] = glGetAttribLocation(m_programID, name);
+ }
+ unbind();
+}
+
+void Shader::discoverUniforms() {
+ bind();
+ GLint uniformCount;
+ glGetProgramiv(m_programID, GL_ACTIVE_UNIFORMS, &uniformCount);
+ for (int i = 0; i < uniformCount; i++) {
+ const GLsizei bufSize = 256;
+ GLsizei nameLength = 0;
+ GLint arraySize = 0;
+ GLenum type;
+ GLchar name[bufSize];
+ glGetActiveUniform(m_programID, i, bufSize, &nameLength, &arraySize, &type, name);
+ name[std::min(nameLength, bufSize - 1)] = 0;
+
+ std::string strname(name);
+ if (isUniformArray(name, nameLength)) {
+ addUniformArray(strname, arraySize);
+ } else if (isTexture(type)) {
+ addTexture(strname);
+ } else {
+ addUniform(strname);
+ }
+ }
+ unbind();
+}
+
+bool Shader::isUniformArray(const GLchar *name, GLsizei nameLength) {
+ // Check if the last 3 characters are '[0]'
+ return (name[nameLength - 3] == '[') &&
+ (name[nameLength - 2] == '0') &&
+ (name[nameLength - 1] == ']');
+}
+
+bool Shader::isTexture(GLenum type) {
+ return (type == GL_SAMPLER_2D) ||
+ (type == GL_SAMPLER_CUBE);
+}
+
+void Shader::addUniformArray(const std::string &name, size_t size) {
+ std::string cleanName = name.substr(0, name.length() - 3);
+ for (auto i = static_cast<size_t>(0); i < size; i++) {
+ std::string enumeratedName = name;
+ enumeratedName[enumeratedName.length() - 2] = static_cast<char>('0' + i);
+ std::tuple< std::string, size_t > nameIndexTuple = std::make_tuple(cleanName, i);
+ m_uniformArrays[nameIndexTuple] = glGetUniformLocation(m_programID, enumeratedName.c_str());
+ }
+
+#if GRAPHICS_DEBUG_LEVEL > 0
+ m_trackedUniformArrays[name] = false;
+#endif
+}
+
+void Shader::addTexture(const std::string &name) {
+ GLint location = glGetUniformLocation(m_programID, name.c_str());
+ m_textureLocations[name] = location;
+ GLint slot = m_textureSlots.size();
+ m_textureSlots[location] = slot; // Assign slots in increasing order.
+
+#if GRAPHICS_DEBUG_LEVEL > 0
+ m_trackedTextures[name] = false;
+#endif
+}
+
+void Shader::addUniform(const std::string &name) {
+ m_uniforms[name] = glGetUniformLocation(m_programID, name.c_str());
+
+#if GRAPHICS_DEBUG_LEVEL > 0
+ m_trackedUniforms[name] = false;
+#endif
+}
+
+bool Shader::printDebug() {
+ bool noErrors = true;
+
+ for (auto &pair : m_trackedUniforms) {
+ if (!pair.second) {
+ std::cerr << "Uniform '" << pair.first << "' was not set." << std::endl;
+ noErrors = false;
+ }
+ }
+
+ for (auto &pair : m_trackedTextures) {
+ if (!pair.second) {
+ std::cerr << "Texture '" << pair.first << "' was not set." << std::endl;
+ noErrors = false;
+ }
+ }
+
+ return noErrors;
+}
+
+void Shader::resetDebug() {
+ for (auto &pair : m_trackedUniforms) {
+ m_trackedUniforms[pair.first] = false;
+ }
+
+ for (auto &pair : m_trackedTextures) {
+ m_trackedTextures[pair.first] = false;
+ }
+}
+
+std::string Shader::getFileContents(std::string path)
+{
+ QString qpath = QString::fromStdString(path);
+ QFile file(qpath);
+
+ if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ QTextStream stream(&file);
+ QString contents = stream.readAll();
+ file.close();
+ return contents.toStdString();
+ }
+ return "";
+}
+
diff --git a/src/graphics/shader.h b/src/graphics/shader.h
new file mode 100644
index 0000000..bc3c0c1
--- /dev/null
+++ b/src/graphics/shader.h
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <map>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "GL/glew.h"
+#include "Eigen/Dense"
+#include <util/unsupportedeigenthing/OpenGLSupport>
+
+
+class Shader {
+public:
+ Shader(const std::string &vertexPath, const std::string &fragmentPath);
+ Shader(const std::string &vertexPath, const std::string &geometryPath, const std::string &fragmentPath);
+
+ Shader(Shader &that) = delete;
+ Shader& operator=(Shader &that) = delete;
+ virtual ~Shader();
+ Shader(Shader &&that);
+ Shader& operator=(Shader &&that);
+
+ GLuint getUniformLocation(std::string name);
+ GLuint getEnumeratedUniformLocation(std::string name, int index);
+
+ template<typename type, int n, int m>
+ void setUniform(const std::string &name, const Eigen::Matrix<type, n, m> &mat) {
+ glUniform(m_uniforms[name], mat);
+ }
+
+ void setUniform(const std::string &name, float f);
+ void setUniform(const std::string &name, int i);
+ void setUniform(const std::string &name, bool b);
+
+ void setUniformArrayByIndex(const std::string &name, float f, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Vector2f &vec2, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Vector3f &vec3, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Vector4f &vec4, size_t index);
+ void setUniformArrayByIndex(const std::string &name, int i, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Vector2i &ivec2, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Vector3i &ivec3, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Vector4i &ivec4, size_t index);
+ void setUniformArrayByIndex(const std::string &name, bool b, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Matrix2f &mat2, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Matrix3f &mat3, size_t index);
+ void setUniformArrayByIndex(const std::string &name, const Eigen::Matrix4f &mat4, size_t index);
+
+
+ void bind() const;
+ void unbind() const;
+ GLuint id() const { return m_programID; }
+
+ bool printDebug();
+ void resetDebug();
+
+private:
+
+ std::string getFileContents(std::string path);
+
+ GLuint createFragmentShaderFromSource(const std::string &source);
+ GLuint createGeometryShaderFromSource(const std::string &source);
+ void compileShader(GLuint handle, const std::string &source);
+ GLuint createVertexShaderFromSource(const std::string &source);
+ GLuint createShaderFromSource(const std::string &source, GLenum shaderType);
+
+ void createProgramID();
+ void attachShaders(const std::vector<GLuint> &shaders);
+ void buildShaderProgramFromShaders(const std::vector<GLuint> &shaders);
+ void linkShaderProgram();
+ void detachShaders(const std::vector<GLuint> &shaders);
+ void deleteShaders(const std::vector<GLuint> &shaders);
+
+ void discoverShaderData();
+ void discoverAttributes();
+ void discoverUniforms();
+
+ bool isUniformArray(const GLchar *name , GLsizei nameLength);
+ bool isTexture(GLenum type);
+ void addUniform(const std::string &name);
+ void addUniformArray(const std::string &name, size_t size);
+ void addTexture(const std::string &name);
+ GLuint m_programID;
+
+ std::map<std::string, GLuint> m_attributes;
+ std::map<std::string, GLuint> m_uniforms;
+ std::map<std::tuple<std::string, size_t>, GLuint> m_uniformArrays;
+ std::map<std::string, GLuint> m_textureLocations; // name to uniform location
+ std::map<GLuint, GLuint> m_textureSlots; // uniform location to texture slot
+
+ // Debugging
+ std::map<std::string, bool> m_trackedUniforms;
+ std::map<std::string, bool> m_trackedTextures;
+ std::map<std::string, bool> m_trackedUniformArrays;
+
+ friend class Graphics;
+};
diff --git a/src/graphics/shape.cpp b/src/graphics/shape.cpp
new file mode 100644
index 0000000..8fe35b1
--- /dev/null
+++ b/src/graphics/shape.cpp
@@ -0,0 +1,272 @@
+#include "shape.h"
+
+#include <iostream>
+#include "graphics/shader.h"
+
+using namespace Eigen;
+using namespace std;
+
+// ================== Constructor
+
+Shape::Shape() :
+ m_surfaceVao(),
+ m_surfaceVbo(),
+ m_surfaceIbo(),
+ m_numSurfaceVertices(),
+ m_verticesSize(),
+ m_red(),
+ m_blue(),
+ m_green(),
+ m_alpha(),
+ m_faces(),
+ m_vertices(),
+ m_anchors(),
+ m_modelMatrix(Matrix4f::Identity()),
+ lastSelected(-1)
+{}
+
+// ================== Initialization and Updating
+
+void Shape::init(const vector<Vector3f> &vertices, const vector<Vector3i> &triangles)
+{
+ m_vertices.clear();
+ copy(vertices.begin(), vertices.end(), back_inserter(m_vertices));
+
+ vector<Vector3f> verts;
+ vector<Vector3f> normals;
+ vector<Vector3f> colors;
+ vector<Vector3i> faces;
+ faces.reserve(triangles.size());
+
+ for (int s = 0; s < triangles.size() * 3; s+=3) faces.push_back(Vector3i(s, s + 1, s + 2));
+ updateMesh(triangles, vertices, verts, normals, colors);
+
+ glGenBuffers(1, &m_surfaceVbo);
+ glGenBuffers(1, &m_surfaceIbo);
+ glGenVertexArrays(1, &m_surfaceVao);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * ((verts.size() * 3) + (normals.size() * 3) + (colors.size() * 3)), nullptr, GL_DYNAMIC_DRAW);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * verts.size() * 3, static_cast<const void *>(verts.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * verts.size() * 3, sizeof(float) * normals.size() * 3, static_cast<const void *>(normals.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * ((verts.size() * 3) + (normals.size() * 3)), sizeof(float) * colors.size() * 3, static_cast<const void *>(colors.data()));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfaceIbo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * 3 * faces.size(), static_cast<const void *>(faces.data()), GL_STATIC_DRAW);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ glBindVertexArray(m_surfaceVao);
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, static_cast<GLvoid *>(0));
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<GLvoid *>(sizeof(float) * verts.size() * 3));
+ glEnableVertexAttribArray(2);
+ glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<GLvoid *>(sizeof(float) * (verts.size() * 3 + colors.size() * 3)));
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfaceIbo);
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+ m_numSurfaceVertices = faces.size() * 3;
+ m_verticesSize = vertices.size();
+ m_faces = triangles;
+ m_red = 0.5f + 0.5f * rand() / ((float) RAND_MAX);
+ m_blue = 0.5f + 0.5f * rand() / ((float) RAND_MAX);
+ m_green = 0.5f + 0.5f * rand() / ((float) RAND_MAX);
+ m_alpha = 1.0f;
+}
+
+void Shape::setVertices(const vector<Vector3f> &vertices)
+{
+ m_vertices.clear();
+ copy(vertices.begin(), vertices.end(), back_inserter(m_vertices));
+
+ vector<Vector3f> verts;
+ vector<Vector3f> normals;
+ vector<Vector3f> colors;
+
+ updateMesh(m_faces, vertices, verts, normals, colors);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * ((verts.size() * 3) + (normals.size() * 3) + (colors.size() * 3)), nullptr, GL_DYNAMIC_DRAW);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * verts.size() * 3, static_cast<const void *>(verts.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * verts.size() * 3, sizeof(float) * normals.size() * 3, static_cast<const void *>(normals.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * ((verts.size() * 3) + (normals.size() * 3)), sizeof(float) * colors.size() * 3, static_cast<const void *>(colors.data()));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+// ================== Model Matrix
+
+void Shape::setModelMatrix(const Affine3f &model) { m_modelMatrix = model.matrix(); }
+
+// ================== General Graphics Stuff
+
+void Shape::draw(Shader *shader, GLenum mode)
+{
+ Eigen::Matrix3f m3 = m_modelMatrix.topLeftCorner(3, 3);
+ Eigen::Matrix3f inverseTransposeModel = m3.inverse().transpose();
+
+ switch(mode) {
+ case GL_TRIANGLES:
+ {
+ shader->setUniform("wire", 0);
+ shader->setUniform("model", m_modelMatrix);
+ shader->setUniform("inverseTransposeModel", inverseTransposeModel);
+ shader->setUniform("red", m_red);
+ shader->setUniform("green", m_green);
+ shader->setUniform("blue", m_blue);
+ shader->setUniform("alpha", m_alpha);
+ glBindVertexArray(m_surfaceVao);
+ glDrawElements(mode, m_numSurfaceVertices, GL_UNSIGNED_INT, reinterpret_cast<GLvoid *>(0));
+ glBindVertexArray(0);
+ break;
+ }
+ case GL_POINTS:
+ {
+ shader->setUniform("model", m_modelMatrix);
+ shader->setUniform("inverseTransposeModel", inverseTransposeModel);
+ glBindVertexArray(m_surfaceVao);
+ glDrawElements(mode, m_numSurfaceVertices, GL_UNSIGNED_INT, reinterpret_cast<GLvoid *>(0));
+ glBindVertexArray(0);
+ break;
+ }
+ }
+}
+
+SelectMode Shape::select(Shader *shader, int closest_vertex)
+{
+ if (closest_vertex == -1) return SelectMode::None;
+
+ bool vertexIsNowSelected = m_anchors.find(closest_vertex) == m_anchors.end();
+
+ if (vertexIsNowSelected) {
+ m_anchors.insert(closest_vertex);
+ } else {
+ m_anchors.erase(closest_vertex);
+ }
+
+ selectHelper();
+
+ return vertexIsNowSelected ? SelectMode::Anchor : SelectMode::Unanchor;
+}
+
+bool Shape::selectWithSpecifiedMode(Shader *shader, int closest_vertex, SelectMode mode)
+{
+ switch (mode) {
+ case SelectMode::None: {
+ return false;
+ }
+ case SelectMode::Anchor: {
+ if (m_anchors.find(closest_vertex) != m_anchors.end()) return false;
+ m_anchors.insert(closest_vertex);
+ break;
+ }
+ case SelectMode::Unanchor: {
+ if (m_anchors.find(closest_vertex) == m_anchors.end()) return false;
+ m_anchors.erase(closest_vertex);
+ break;
+ }
+ }
+
+ selectHelper();
+
+ return true;
+}
+
+int Shape::getClosestVertex(Vector3f start, Vector3f ray, float threshold)
+{
+ int closest_vertex = -1;
+ int i = 0;
+ float dist = numeric_limits<float>::max();
+ ParametrizedLine line = ParametrizedLine<float, 3>::Through(start, start + ray);
+
+ for (const Vector3f &v : m_vertices) {
+ float d = line.distance(v);
+ if (d<dist) {
+ dist = d;
+ closest_vertex = i;
+ }
+ ++i;
+ }
+
+ if (dist >= threshold) closest_vertex = -1;
+
+ return closest_vertex;
+}
+
+bool Shape::getAnchorPos(int lastSelected,
+ Eigen::Vector3f& pos,
+ Eigen::Vector3f ray,
+ Eigen::Vector3f start)
+{
+ bool isAnchor = m_anchors.find(lastSelected) != m_anchors.end();
+ if (isAnchor) {
+ Eigen::Vector3f oldPos = m_vertices[lastSelected];
+ Eigen::ParametrizedLine line = ParametrizedLine<float, 3>::Through(start, start+ray);
+ pos = line.projection(oldPos);
+ }
+ return isAnchor;
+}
+
+// ================== Accessors
+
+const vector<Vector3f> &Shape::getVertices() { return m_vertices; }
+const vector<Vector3i> &Shape::getFaces() { return m_faces; }
+const unordered_set<int> &Shape::getAnchors() { return m_anchors; }
+
+// ================== Helpers
+
+void Shape::selectHelper()
+{
+ vector<Vector3f> verts;
+ vector<Vector3f> normals;
+ vector<Vector3f> colors;
+ updateMesh(m_faces, m_vertices, verts, normals, colors);
+
+ glBindBuffer(GL_ARRAY_BUFFER, m_surfaceVbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(float) * ((verts.size() * 3) + (normals.size() * 3) + (colors.size() * 3)), nullptr, GL_DYNAMIC_DRAW);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * verts.size() * 3, static_cast<const void *>(verts.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * verts.size() * 3, sizeof(float) * normals.size() * 3, static_cast<const void *>(normals.data()));
+ glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * ((verts.size() * 3) + (normals.size() * 3)), sizeof(float) * colors.size() * 3, static_cast<const void *>(colors.data()));
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+Vector3f Shape::getNormal(const Vector3i& face)
+{
+ Vector3f& v1 = m_vertices[face[0]];
+ Vector3f& v2 = m_vertices[face[1]];
+ Vector3f& v3 = m_vertices[face[2]];
+ Vector3f e1 = v2 - v1;
+ Vector3f e2 = v3 - v1;
+ Vector3f n = e1.cross(e2);
+ return n.normalized();
+}
+
+void Shape::updateMesh(const std::vector<Eigen::Vector3i> &faces,
+ const std::vector<Eigen::Vector3f> &vertices,
+ std::vector<Eigen::Vector3f>& verts,
+ std::vector<Eigen::Vector3f>& normals,
+ std::vector<Eigen::Vector3f>& colors)
+{
+ verts.reserve(faces.size() * 3);
+ normals.reserve(faces.size() * 3);
+
+ for (const Eigen::Vector3i& face : faces) {
+ Vector3f n = getNormal(face);
+
+ for (auto& v: {face[0], face[1], face[2]}) {
+ normals.push_back(n);
+ verts.push_back(vertices[v]);
+
+ if (m_anchors.find(v) == m_anchors.end()) {
+ colors.push_back(Vector3f(1,0,0));
+ } else {
+ colors.push_back(Vector3f(0, 1 - m_green, 1 - m_blue));
+ }
+ }
+ }
+}
+
diff --git a/src/graphics/shape.h b/src/graphics/shape.h
new file mode 100644
index 0000000..3ba81fb
--- /dev/null
+++ b/src/graphics/shape.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include <GL/glew.h>
+#include <vector>
+#include <unordered_set>
+
+#define EIGEN_DONT_VECTORIZE
+#define EIGEN_DISABLE_UNALIGNED_ARRAY_ASSERT
+#include "Eigen/StdVector"
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Matrix2f)
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Matrix3f)
+EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(Eigen::Matrix3i)
+#include "Eigen/Dense"
+
+enum SelectMode
+{
+ None = 0,
+ Anchor = 1,
+ Unanchor = 2
+};
+
+class Shader;
+
+class Shape
+{
+public:
+ Shape();
+
+ void init(const std::vector<Eigen::Vector3f> &vertices, const std::vector<Eigen::Vector3i> &triangles);
+ void setVertices(const std::vector<Eigen::Vector3f> &vertices);
+
+ void setModelMatrix(const Eigen::Affine3f &model);
+
+ void draw(Shader *shader, GLenum mode);
+ SelectMode select(Shader *shader, int vertex);
+ bool selectWithSpecifiedMode(Shader *shader, int vertex, SelectMode mode);
+ int getClosestVertex(Eigen::Vector3f start, Eigen::Vector3f ray, float threshold);
+ bool getAnchorPos(int lastSelected, Eigen::Vector3f& pos, Eigen::Vector3f ray, Eigen::Vector3f start);
+
+ const std::vector<Eigen::Vector3f>& getVertices();
+ const std::vector<Eigen::Vector3i>& getFaces();
+ const std::unordered_set<int>& getAnchors();
+
+private:
+ GLuint m_surfaceVao;
+ GLuint m_surfaceVbo;
+ GLuint m_surfaceIbo;
+
+ unsigned int m_numSurfaceVertices;
+ unsigned int m_verticesSize;
+ float m_red;
+ float m_blue;
+ float m_green;
+ float m_alpha;
+
+ std::vector<Eigen::Vector3i> m_faces;
+ std::vector<Eigen::Vector3f> m_vertices;
+ std::unordered_set<int> m_anchors;
+
+ Eigen::Matrix4f m_modelMatrix;
+ int lastSelected = -1;
+
+ // Helpers
+
+ void selectHelper();
+ Eigen::Vector3f getNormal(const Eigen::Vector3i& face);
+ void updateMesh(const std::vector<Eigen::Vector3i> &triangles,
+ const std::vector<Eigen::Vector3f> &vertices,
+ std::vector<Eigen::Vector3f>& verts,
+ std::vector<Eigen::Vector3f>& normals,
+ std::vector<Eigen::Vector3f>& colors);
+};
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100755
index 0000000..a11e7d8
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,38 @@
+#include "mainwindow.h"
+#include <cstdlib>
+#include <ctime>
+
+#include <QApplication>
+#include <QSurfaceFormat>
+#include <QScreen>
+
+int main(int argc, char *argv[])
+{
+ srand(static_cast<unsigned>(time(0)));
+
+ // Create a Qt application
+ QApplication a(argc, argv);
+ QCoreApplication::setApplicationName("ARAP");
+ QCoreApplication::setOrganizationName("CS 2240");
+ QCoreApplication::setApplicationVersion(QT_VERSION_STR);
+
+ // Set OpenGL version to 4.1 and context to Core
+ QSurfaceFormat fmt;
+ fmt.setVersion(4, 1);
+ fmt.setProfile(QSurfaceFormat::CoreProfile);
+ QSurfaceFormat::setDefaultFormat(fmt);
+
+ // Create a GUI window
+ MainWindow w;
+ w.resize(600, 500);
+ int desktopArea = QGuiApplication::primaryScreen()->size().width() *
+ QGuiApplication::primaryScreen()->size().height();
+ int widgetArea = w.width() * w.height();
+ if (((float)widgetArea / (float)desktopArea) < 0.75f)
+ w.show();
+ else
+ w.showMaximized();
+
+
+ return a.exec();
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
new file mode 100755
index 0000000..4157237
--- /dev/null
+++ b/src/mainwindow.cpp
@@ -0,0 +1,17 @@
+#include "mainwindow.h"
+#include <QHBoxLayout>
+
+MainWindow::MainWindow()
+{
+ glWidget = new GLWidget();
+
+ QHBoxLayout *container = new QHBoxLayout;
+ container->addWidget(glWidget);
+ this->setLayout(container);
+}
+
+
+MainWindow::~MainWindow()
+{
+ delete glWidget;
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
new file mode 100755
index 0000000..ac827b0
--- /dev/null
+++ b/src/mainwindow.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <QMainWindow>
+#include "glwidget.h"
+
+class MainWindow : public QWidget
+{
+ Q_OBJECT
+
+public:
+ MainWindow();
+ ~MainWindow();
+
+private:
+ GLWidget *glWidget;
+};