diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/arap.cpp | 74 | ||||
-rw-r--r-- | src/arap.h | 69 | ||||
-rwxr-xr-x | src/glwidget.cpp | 304 | ||||
-rwxr-xr-x | src/glwidget.h | 72 | ||||
-rw-r--r-- | src/graphics/camera.cpp | 196 | ||||
-rw-r--r-- | src/graphics/camera.h | 55 | ||||
-rw-r--r-- | src/graphics/graphicsdebug.cpp | 126 | ||||
-rw-r--r-- | src/graphics/graphicsdebug.h | 15 | ||||
-rw-r--r-- | src/graphics/meshloader.cpp | 65 | ||||
-rw-r--r-- | src/graphics/meshloader.h | 18 | ||||
-rw-r--r-- | src/graphics/shader.cpp | 286 | ||||
-rw-r--r-- | src/graphics/shader.h | 97 | ||||
-rw-r--r-- | src/graphics/shape.cpp | 272 | ||||
-rw-r--r-- | src/graphics/shape.h | 72 | ||||
-rwxr-xr-x | src/main.cpp | 38 | ||||
-rwxr-xr-x | src/mainwindow.cpp | 17 | ||||
-rwxr-xr-x | src/mainwindow.h | 16 | ||||
-rw-r--r-- | src/ocean/ocean.cpp | 259 | ||||
-rw-r--r-- | src/ocean/ocean.h | 71 |
19 files changed, 2122 insertions, 0 deletions
diff --git a/src/arap.cpp b/src/arap.cpp new file mode 100644 index 0000000..714a7db --- /dev/null +++ b/src/arap.cpp @@ -0,0 +1,74 @@ +#include "arap.h" +#include "graphics/meshloader.h" + +#include <iostream> +#include <set> +#include <map> +#include <vector> + +#include <Eigen/Core> +#include <Eigen/Sparse> +#include <Eigen/SVD> + +#include "ocean/ocean.h" + +using namespace std; +using namespace Eigen; + +ARAP::ARAP() {} + +void ARAP::init + ( + Eigen::Vector3f &coeffMin, + Eigen::Vector3f &coeffMax + ) +{ + m_num_iterations = 1000; + m_mesh_path = "meshes/bunny.obj"; + + vector<Vector3f> vertices; + vector<Vector3i> triangles; + + // If this doesn't work for you, remember to change your working directory +// if (MeshLoader::loadTriMesh(m_mesh_path, vertices, triangles)) { +// m_shape.init(vertices, triangles); +// } + + ocean o = ocean(); + vertices = o.get_vertices(); + triangles = o.get_faces(); + 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 + ) +{ + // 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 +} + diff --git a/src/arap.h b/src/arap.h new file mode 100644 index 0000000..1d58f07 --- /dev/null +++ b/src/arap.h @@ -0,0 +1,69 @@ +#pragma once + +#include "graphics/shape.h" +#include "Eigen/StdList" +#include "Eigen/StdVector" +#include <Eigen/Core> +#include <Eigen/Dense> +#include <Eigen/Sparse> +#include <Eigen/SVD> +#include <QList> +#include <QtConcurrent> + +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); + } + + // for determinig when to recompute + int num_anchors; + + typedef Eigen::Matrix<float, 3, Eigen::Dynamic> PM; // position matrix + typedef Eigen::Matrix<float, 3, 3> RM; // rotation matrix + typedef Eigen::SparseMatrix<float, Eigen::RowMajor> Sparse; + + std::vector<RM> m_rotations; + Sparse m_edge_weights; + PM m_b, m_b_fixed; + std::vector<Eigen::Index> m_vtx_to_free_vtx; + Eigen::SimplicialLDLT<Eigen::SparseMatrix<float>> m_solver; + + int m_num_iterations; + const char * m_mesh_path; +}; + 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; +}; diff --git a/src/ocean/ocean.cpp b/src/ocean/ocean.cpp new file mode 100644 index 0000000..f70a553 --- /dev/null +++ b/src/ocean/ocean.cpp @@ -0,0 +1,259 @@ +// +// Created by Michael Foiani on 4/9/24. +// + +#include <iostream> +#include "ocean.h" + +// TODO: make these private instance variables + +ocean::ocean() +{ + initial_h = std::vector<std::pair<double, double>>(); + // initialize the initial height fields + for (int i = 0; i < N; i++) + { + initial_h.push_back(amplitude_0(i)); + } +} + +/* Maps the 1D k-index into it's 2D waveform vector */ +std::pair<double, double> ocean::k_index_to_k_vector + ( + int k_index + ) +{ + // get the x and z indices + int x = k_index % length; + int z = k_index / length; + + // calculate the k_x and k_z values, according to the length + double k_x = (2 * M_PI * x - N) / (double) length; + double k_z = (2 * M_PI * z - N) / (double) width; + + return std::make_pair(k_x, k_z); +} + +/* + * Randomly generates a complex number by + * importance sampling a Gaussian distribution + * with mean 0 and variance 1. + * + * Applies the Box-Muller transform to generate, + * citation: en.wikipedia.org/wiki/Box%E2%80%93Muller_transform + */ +std::pair<double, double> ocean::sample_complex_gaussian + () +{ + double uniform_1 = (double)rand() / (RAND_MAX); + double uniform_2 = (double)rand() / (RAND_MAX); + + // set a lower bound on zero to avoid undefined log(0) + if (uniform_1 == 0) + { + uniform_1 = 1e-10; + } + if (uniform_2 == 0) + { + uniform_2 = 1e-10; + } + + // real and imaginary parts of the complex number + double real = sqrt(-2 * log(uniform_1)) * cos(2 * M_PI * uniform_2); + double imag = sqrt(-2 * log(uniform_1)) * sin(2 * M_PI * uniform_2); + + return std::make_pair(real, imag); +} + +/* + * Generates the Phillips spectrum for a given k-index. + * See section 4.3 of the paper. + */ +double ocean::phillips_spectrum + ( + int k_index + ) +{ + // get the k_x, k_z values & amplitude of k + std::pair<double, double> k = k_index_to_k_vector(k_index); + double k_x = k.first; + double k_z = k.second; + double k_magnitude = sqrt(k_x * k_x + k_z * k_z); + + // calculate L, the max wave size from wind speed V + double L = (V * V) / 9.81; + // get the strength of k onto the wind direction + double k_dot_omega = k_x * omega_wind.first + k_z * omega_wind.second; + + // calculate certain parts of the Phillips formula + double k_dot_omega_squared = k_dot_omega * k_dot_omega; + double KL_squared = (k_magnitude * L) * (k_magnitude * L); + double k_fourth = k_magnitude * k_magnitude * k_magnitude * k_magnitude; + + double phillips = + A // numeric constant + * exp(-1 / KL_squared) + / (k_fourth) + * k_dot_omega_squared; + + // TODO: consider the small length check as described in the paper + return phillips; +} + +/* + * Generates the initial (i.e. t = 0) fourier amplitude. + * See section 4.4 of the paper. + */ +std::pair<double, double> ocean::amplitude_0 + ( + int k_index + ) +{ + std::pair<double, double> xi = sample_complex_gaussian(); + + double xi_real = xi.first; + double xi_imag = xi.second; + + double sqrt_phillips = sqrt(phillips_spectrum(k_index)); + + double real = (1 / sqrt(2)) * xi_real * sqrt_phillips; + double imag = (1 / sqrt(2)) * xi_imag * sqrt_phillips; + + return std::make_pair(real, imag); +} + +/* + * Maps the negative k-index from a given k-index, + * used for the complex conjugate in amplitude_t + */ +int ocean::k_index_to_negative_k_index + ( + int k_index + ) +{ + int x = k_index % length; + int z = k_index / length; + + int x_neg = length - x; + int z_neg = width - z; + + return z_neg * length + x_neg; +} + +/* + * Calculates the dispersion relation for a given k-index. + * See section 4.2 of the paper. + */ +double ocean::omega_dispersion + ( + double k_magnitude, + bool is_shallow + ) +{ + if (is_shallow) + { + double tanh_kD = tanh(D * k_magnitude); + return sqrt(9.81 * k_magnitude * tanh_kD); + } + else + { + return sqrt(9.81 * k_magnitude); + } +} + +std::pair<double, double> ocean::exp_complex + ( + std::pair<double, double> z + ) +{ + double real = exp(z.first) * cos(z.second); + double imag = exp(z.first) * sin(z.second); + + return std::make_pair(real, imag); +} + +/* + * Generates the fourier amplitude at time t. + * See section 4.4 of the paper. + */ +std::pair<double, double> ocean::amplitude_t + ( + double t, + int k_index + ) +{ + // get the initial height (and it's conjugate) + std::pair<double, double> h_0 = initial_h[k_index]; + std::pair<double, double> h_0_conjugate = initial_h[k_index_to_negative_k_index(k_index)]; + h_0_conjugate = std::make_pair(h_0_conjugate.first, -h_0_conjugate.second); + + // get dispersion from k + std::pair<double, double> k = k_index_to_k_vector(k_index); + double k_magnitude = sqrt(k.first * k.first + k.second * k.second); + double omega = omega_dispersion(k_magnitude); + + // calculate the complex exponential terms + double omega_t = omega * t; + std::pair<double, double> exp_positive = exp_complex(std::make_pair(0, omega_t)); + std::pair<double, double> exp_negative = exp_complex(std::make_pair(0, -omega_t)); + + // add the real and imaginary part together from both h_0 and h_0_conjugate + double real = + // h+0 real + h_0.first * exp_positive.first + - h_0.second * exp_positive.second + // h_0_conjugate real + + h_0_conjugate.first * exp_negative.first + - h_0_conjugate.second * exp_negative.second; + double imag = + // h_0 imaginary + h_0.first * exp_positive.second + + h_0.second * exp_positive.first + // h_0_conjugate imaginary + + h_0_conjugate.first * exp_negative.second + + h_0_conjugate.second * exp_negative.first; + + return std::make_pair(real, imag); +} + +std::vector<Eigen::Vector3f> ocean::get_vertices() +{ + std::vector<Eigen::Vector3f> vertices = std::vector<Eigen::Vector3f>(); + for (int i = 0; i < N; i++) + { + std::pair<double, double> k = k_index_to_k_vector(i); + double k_x = k.first; + double k_z = k.second; + + double amplitude = initial_h[i].first; + + std::cout << "k_x: " << k_x << " k_z: " << k_z << " amplitude: " << amplitude << std::endl; + + vertices.emplace_back(k_x, amplitude, k_z); + } + return vertices; +} + +std::vector<Eigen::Vector3i> ocean::get_faces() +{ + // connect the vertices into faces + std::vector<Eigen::Vector3i> faces = std::vector<Eigen::Vector3i>(); + for (int i = 0; i < N; i++) + { + int x = i % length; + int z = i / length; + + // connect the vertices into faces + if (x < length - 1 && z < width - 1) + { + int i1 = i; + int i2 = i + 1; + int i3 = i + length; + int i4 = i + length + 1; + + faces.emplace_back(i1, i2, i3); + faces.emplace_back(i2, i3, i4); + } + } + return faces; +} diff --git a/src/ocean/ocean.h b/src/ocean/ocean.h new file mode 100644 index 0000000..7e436cf --- /dev/null +++ b/src/ocean/ocean.h @@ -0,0 +1,71 @@ +// +// Created by Michael Foiani on 4/9/24. +// + +#ifndef OCEAN_H +#define OCEAN_H + +#include <vector> +#include <utility> +#include <Eigen/Dense> + + +class ocean +{ +public: + ocean(); + + const int length = 16; // length of grid + const int width = 16; // width of grid + const int N = length * width; // total number of grid points + + const double A = 100; // numeric constant for the Phillips spectrum + const double V = 5.0; // wind speed + const std::pair<double, double> omega_wind + = std::make_pair(1.0, 0.0); // wind direction + + std::vector<std::pair<double, double>> initial_h; // initial height fields for each K + + const double D = 1; // Depth below mean water level (for dispersion relation) + + std::vector<Eigen::Vector3f> get_vertices + (); + std::vector<Eigen::Vector3i> get_faces + (); + + std::pair<double, double> k_index_to_k_vector + ( + int k_index + ); + std::pair<double, double> sample_complex_gaussian + (); + double phillips_spectrum + ( + int k_index + ); + std::pair<double, double> amplitude_0 + ( + int k_index + ); + int k_index_to_negative_k_index + ( + int k_index + ); + double omega_dispersion + ( + double k_magnitude, + bool is_shallow=false + ); + std::pair<double, double> exp_complex + ( + std::pair<double, double> z + ); + std::pair<double, double> amplitude_t + ( + double t, + int k_index + ); +}; + + +#endif //OCEAN_H |