diff options
Diffstat (limited to 'src/graphics')
-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 |
10 files changed, 1202 insertions, 0 deletions
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); +}; |