From 0bf4ad9e4a9e1c97e92aa23a365405dfef89bd7c Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 17 Jan 2021 19:36:30 +0000 Subject: Big reshuffle Fixes code quality warnings now picked up. --- gfx/gl/camera.cpp | 46 +++++ gfx/gl/camera.h | 24 +++ gfx/gl/shader.cpp | 100 ++++++++++ gfx/gl/shader.h | 35 ++++ gfx/gl/shaders/basicShader.fs | 13 ++ gfx/gl/shaders/basicShader.vs | 18 ++ gfx/gl/transform.cpp | 27 +++ gfx/gl/transform.h | 58 ++++++ gfx/models/mesh.cpp | 75 ++++++++ gfx/models/mesh.h | 35 ++++ gfx/models/obj_loader.cpp | 428 ++++++++++++++++++++++++++++++++++++++++++ gfx/models/obj_loader.h | 53 ++++++ gfx/models/stb_image.c | 8 + gfx/models/texture.cpp | 35 ++++ gfx/models/texture.h | 23 +++ gfx/models/vertex.hpp | 37 ++++ 16 files changed, 1015 insertions(+) create mode 100644 gfx/gl/camera.cpp create mode 100644 gfx/gl/camera.h create mode 100644 gfx/gl/shader.cpp create mode 100644 gfx/gl/shader.h create mode 100644 gfx/gl/shaders/basicShader.fs create mode 100644 gfx/gl/shaders/basicShader.vs create mode 100644 gfx/gl/transform.cpp create mode 100644 gfx/gl/transform.h create mode 100644 gfx/models/mesh.cpp create mode 100644 gfx/models/mesh.h create mode 100644 gfx/models/obj_loader.cpp create mode 100644 gfx/models/obj_loader.h create mode 100644 gfx/models/stb_image.c create mode 100644 gfx/models/texture.cpp create mode 100644 gfx/models/texture.h create mode 100644 gfx/models/vertex.hpp (limited to 'gfx') diff --git a/gfx/gl/camera.cpp b/gfx/gl/camera.cpp new file mode 100644 index 0000000..b4a76d0 --- /dev/null +++ b/gfx/gl/camera.cpp @@ -0,0 +1,46 @@ +#include "camera.h" +#include + +Camera::Camera(glm::vec3 pos, float fov, float aspect, float zNear, float zFar) : + projection {glm::perspective(fov, aspect, zNear, zFar)}, pos {pos}, forward {0.0F, 0.0F, 1.0F}, up {0.0F, 1.0F, + 0.0F} +{ +} + +glm::mat4 +Camera::GetViewProjection() const +{ + return projection * glm::lookAt(pos, pos + forward, up); +} + +void +Camera::MoveForward(float amt) +{ + pos += forward * amt; +} + +void +Camera::MoveRight(float amt) +{ + pos += glm::cross(up, forward) * amt; +} + +void +Camera::Pitch(float angle) +{ + const auto right = glm::normalize(glm::cross(up, forward)); + + forward = glm::vec3(glm::normalize(glm::rotate(angle, right) * glm::vec4(forward, 0.0))); + up = glm::normalize(glm::cross(forward, right)); +} + +void +Camera::RotateY(float angle) +{ + static constexpr glm::vec3 UP {0.0F, 1.0F, 0.0F}; + + const auto rotation = glm::rotate(angle, UP); + + forward = glm::vec3(glm::normalize(rotation * glm::vec4(forward, 0.0))); + up = glm::vec3(glm::normalize(rotation * glm::vec4(up, 0.0))); +} diff --git a/gfx/gl/camera.h b/gfx/gl/camera.h new file mode 100644 index 0000000..fa4296d --- /dev/null +++ b/gfx/gl/camera.h @@ -0,0 +1,24 @@ +#ifndef CAMERA_INCLUDED_H +#define CAMERA_INCLUDED_H + +#include + +class Camera { +public: + Camera(glm::vec3 pos, float fov, float aspect, float zNear, float zFar); + + [[nodiscard]] glm::mat4 GetViewProjection() const; + + void MoveForward(float amt); + void MoveRight(float amt); + void Pitch(float angle); + void RotateY(float angle); + +private: + glm::mat4 projection; + glm::vec3 pos; + glm::vec3 forward; + glm::vec3 up; +}; + +#endif diff --git a/gfx/gl/shader.cpp b/gfx/gl/shader.cpp new file mode 100644 index 0000000..33fdf61 --- /dev/null +++ b/gfx/gl/shader.cpp @@ -0,0 +1,100 @@ +#include "shader.h" +#include "transform.h" +#include +#include +#include +#include + +Shader::Shader(const std::string &) : + m_program {glCreateProgram()}, m_shaders {CreateShader( + (GLchar *)(basicShader_vs), basicShader_vs_len, GL_VERTEX_SHADER), + CreateShader( + (GLchar *)basicShader_fs, basicShader_fs_len, GL_FRAGMENT_SHADER)}, + m_uniforms {} +{ + for (auto m_shader : m_shaders) { + glAttachShader(m_program, m_shader); + } + + glBindAttribLocation(m_program, 0, "position"); + glBindAttribLocation(m_program, 1, "texCoord"); + glBindAttribLocation(m_program, 2, "normal"); + + glLinkProgram(m_program); + CheckShaderError(m_program, GL_LINK_STATUS, true, "Error linking shader program"); + + glValidateProgram(m_program); + CheckShaderError(m_program, GL_VALIDATE_STATUS, true, "Invalid shader program"); + + m_uniforms = {glGetUniformLocation(m_program, "MVP"), glGetUniformLocation(m_program, "Normal"), + glGetUniformLocation(m_program, "lightDirection")}; +} + +Shader::~Shader() +{ + for (auto m_shader : m_shaders) { + glDetachShader(m_program, m_shader); + glDeleteShader(m_shader); + } + + glDeleteProgram(m_program); +} + +void +Shader::Bind() const +{ + glUseProgram(m_program); +} + +void +Shader::Update(const Transform & transform, const Camera & camera) const +{ + glm::mat4 MVP = transform.GetMVP(camera); + glm::mat4 Normal = transform.GetModel(); + + glUniformMatrix4fv(m_uniforms[0], 1, GL_FALSE, &MVP[0][0]); + glUniformMatrix4fv(m_uniforms[1], 1, GL_FALSE, &Normal[0][0]); + glUniform3f(m_uniforms[2], 0.0F, 0.0F, 1.0F); +} + +void +Shader::CheckShaderError(GLuint shader, GLuint flag, bool isProgram, const std::string & errorMessage) +{ + GLint success = 0; + std::array error {}; + + if (isProgram) { + glGetProgramiv(shader, flag, &success); + } + else { + glGetShaderiv(shader, flag, &success); + } + + if (success == GL_FALSE) { + if (isProgram) { + glGetProgramInfoLog(shader, error.size(), nullptr, error.data()); + } + else { + glGetShaderInfoLog(shader, error.size(), nullptr, error.data()); + } + + throw std::runtime_error {errorMessage + ": '" + std::string {error.data(), error.size()} + "'"}; + } +} + +GLuint +Shader::CreateShader(const GLchar * text, GLint len, unsigned int type) +{ + GLuint shader = glCreateShader(type); + + if (shader == 0) { + throw std::runtime_error {"Error compiling shader type " + std::to_string(type)}; + } + + glShaderSource(shader, 1, &text, &len); + glCompileShader(shader); + + CheckShaderError(shader, GL_COMPILE_STATUS, false, "Error compiling shader!"); + + return shader; +} diff --git a/gfx/gl/shader.h b/gfx/gl/shader.h new file mode 100644 index 0000000..2072199 --- /dev/null +++ b/gfx/gl/shader.h @@ -0,0 +1,35 @@ +#ifndef SHADER_INCLUDED_H +#define SHADER_INCLUDED_H + +#include +#include +#include +#include + +class Camera; +class Transform; + +class Shader { +public: + explicit Shader(const std::string & fileName); + virtual ~Shader(); + + NO_COPY(Shader); + NO_MOVE(Shader); + + void Bind() const; + void Update(const Transform & transform, const Camera & camera) const; + +private: + static constexpr unsigned int NUM_SHADERS = 2; + static constexpr unsigned int NUM_UNIFORMS = 3; + + static void CheckShaderError(GLuint shader, GLuint flag, bool isProgram, const std::string & errorMessage); + static GLuint CreateShader(const GLchar * text, GLint len, unsigned int type); + + GLuint m_program; + std::array m_shaders; + std::array m_uniforms; +}; + +#endif diff --git a/gfx/gl/shaders/basicShader.fs b/gfx/gl/shaders/basicShader.fs new file mode 100644 index 0000000..3aa974f --- /dev/null +++ b/gfx/gl/shaders/basicShader.fs @@ -0,0 +1,13 @@ +#version 120 + +varying vec2 texCoord0; +varying vec3 normal0; + +uniform sampler2D sampler; +uniform vec3 lightDirection; + +void main() +{ + gl_FragColor = texture2D(sampler, texCoord0) * + clamp(dot(-lightDirection, normal0), 0.0, 1.0); +} diff --git a/gfx/gl/shaders/basicShader.vs b/gfx/gl/shaders/basicShader.vs new file mode 100644 index 0000000..e673548 --- /dev/null +++ b/gfx/gl/shaders/basicShader.vs @@ -0,0 +1,18 @@ +#version 120 + +attribute vec3 position; +attribute vec2 texCoord; +attribute vec3 normal; + +varying vec2 texCoord0; +varying vec3 normal0; + +uniform mat4 MVP; +uniform mat4 Normal; + +void main() +{ + gl_Position = MVP * vec4(position, 1.0); + texCoord0 = texCoord; + normal0 = (Normal * vec4(normal, 0.0)).xyz; +} diff --git a/gfx/gl/transform.cpp b/gfx/gl/transform.cpp new file mode 100644 index 0000000..7b256af --- /dev/null +++ b/gfx/gl/transform.cpp @@ -0,0 +1,27 @@ +#include "transform.h" +#include "camera.h" +#include + +Transform::Transform(glm::vec3 pos, glm::vec3 rot, glm::vec3 scale) : pos {pos}, rot {rot}, scale {scale} { } + +glm::mat4 +Transform::GetModel() const +{ + const auto posMat = glm::translate(pos); + const auto scaleMat = glm::scale(scale); + const auto rotX = glm::rotate(rot.x, glm::vec3(1.0, 0.0, 0.0)); + const auto rotY = glm::rotate(rot.y, glm::vec3(0.0, 1.0, 0.0)); + const auto rotZ = glm::rotate(rot.z, glm::vec3(0.0, 0.0, 1.0)); + const auto rotMat = rotX * rotY * rotZ; + + return posMat * rotMat * scaleMat; +} + +glm::mat4 +Transform::GetMVP(const Camera & camera) const +{ + const auto VP = camera.GetViewProjection(); + const auto M = GetModel(); + + return VP * M; +} diff --git a/gfx/gl/transform.h b/gfx/gl/transform.h new file mode 100644 index 0000000..07040ae --- /dev/null +++ b/gfx/gl/transform.h @@ -0,0 +1,58 @@ +#ifndef TRANSFORM_INCLUDED_H +#define TRANSFORM_INCLUDED_H + +#include + +class Camera; + +class Transform { +public: + explicit Transform(glm::vec3 pos = {}, glm::vec3 rot = {}, glm::vec3 scale = {1.0F, 1.0F, 1.0F}); + + [[nodiscard]] glm::mat4 GetModel() const; + + [[nodiscard]] glm::mat4 GetMVP(const Camera & camera) const; + + [[nodiscard]] inline glm::vec3 & + GetPos() + { + return pos; + } + + [[nodiscard]] inline glm::vec3 & + GetRot() + { + return rot; + } + + [[nodiscard]] inline glm::vec3 & + GetScale() + { + return scale; + } + + inline void + SetPos(glm::vec3 && pos) + { + this->pos = pos; + } + + inline void + SetRot(glm::vec3 && rot) + { + this->rot = rot; + } + + inline void + SetScale(glm::vec3 && scale) + { + this->scale = scale; + } + +private: + glm::vec3 pos; + glm::vec3 rot; + glm::vec3 scale; +}; + +#endif diff --git a/gfx/models/mesh.cpp b/gfx/models/mesh.cpp new file mode 100644 index 0000000..8c304b3 --- /dev/null +++ b/gfx/models/mesh.cpp @@ -0,0 +1,75 @@ +#include "mesh.h" +#include "obj_loader.h" +#include "vertex.hpp" +#include +#include +#include + +Mesh::Mesh(const std::string & fileName) : Mesh(OBJModel(fileName).ToIndexedModel()) { } + +Mesh::Mesh(const IndexedModel & model) : + m_vertexArrayObject {}, m_vertexArrayBuffers {}, m_numIndices {model.indices.size()} +{ + glGenVertexArrays(1, &m_vertexArrayObject); + glBindVertexArray(m_vertexArrayObject); + + glGenBuffers(NUM_BUFFERS, m_vertexArrayBuffers.data()); + + glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[POSITION_VB]); + glBufferData( + GL_ARRAY_BUFFER, sizeof(model.positions[0]) * model.positions.size(), &model.positions[0], GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[TEXCOORD_VB]); + glBufferData( + GL_ARRAY_BUFFER, sizeof(model.texCoords[0]) * model.texCoords.size(), &model.texCoords[0], GL_STATIC_DRAW); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[NORMAL_VB]); + glBufferData(GL_ARRAY_BUFFER, sizeof(model.normals[0]) * model.normals.size(), &model.normals[0], GL_STATIC_DRAW); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_vertexArrayBuffers[INDEX_VB]); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(model.indices[0]) * model.indices.size(), &model.indices[0], + GL_STATIC_DRAW); + + glBindVertexArray(0); +} + +Mesh::Mesh(Vertex * vertices, unsigned int numVertices, unsigned int * indices, unsigned int numIndices) : + Mesh {[vertices, numVertices, indices, numIndices]() { + IndexedModel model; + + for (unsigned int i = 0; i < numVertices; i++) { + model.positions.push_back(vertices[i].GetPos()); + model.texCoords.push_back(vertices[i].GetTexCoord()); + model.normals.push_back(vertices[i].GetNormal()); + } + + for (unsigned int i = 0; i < numIndices; i++) { + model.indices.push_back(indices[i]); + } + + return model; + }()} +{ +} + +Mesh::~Mesh() +{ + glDeleteBuffers(NUM_BUFFERS, m_vertexArrayBuffers.data()); + glDeleteVertexArrays(1, &m_vertexArrayObject); +} + +void +Mesh::Draw() +{ + glBindVertexArray(m_vertexArrayObject); + + glDrawElementsBaseVertex(GL_TRIANGLES, m_numIndices, GL_UNSIGNED_INT, nullptr, 0); + + glBindVertexArray(0); +} diff --git a/gfx/models/mesh.h b/gfx/models/mesh.h new file mode 100644 index 0000000..453e54a --- /dev/null +++ b/gfx/models/mesh.h @@ -0,0 +1,35 @@ +#ifndef MESH_INCLUDED_H +#define MESH_INCLUDED_H + +#include +#include +#include +#include +#include + +class IndexedModel; +class Vertex; + +enum MeshBufferPositions { POSITION_VB, TEXCOORD_VB, NORMAL_VB, INDEX_VB }; + +class Mesh { +public: + explicit Mesh(const std::string & fileName); + explicit Mesh(const IndexedModel & model); + Mesh(Vertex * vertices, unsigned int numVertices, unsigned int * indices, unsigned int numIndices); + virtual ~Mesh(); + + NO_COPY(Mesh); + NO_MOVE(Mesh); + + void Draw(); + +private: + static constexpr unsigned int NUM_BUFFERS {4}; + + GLuint m_vertexArrayObject; + std::array m_vertexArrayBuffers; + size_t m_numIndices; +}; + +#endif diff --git a/gfx/models/obj_loader.cpp b/gfx/models/obj_loader.cpp new file mode 100644 index 0000000..7611a2c --- /dev/null +++ b/gfx/models/obj_loader.cpp @@ -0,0 +1,428 @@ +#include "obj_loader.h" +#include +#include +#include +#include +#include +#include + +static bool CompareOBJIndexPtr(const OBJIndex * a, const OBJIndex * b); +static inline unsigned int FindNextChar(unsigned int start, const char * str, unsigned int length, char token); +static inline unsigned int ParseOBJIndexValue(const std::string & token, unsigned int start, unsigned int end); +static inline float ParseOBJFloatValue(const std::string & token, unsigned int start, unsigned int end); +static inline std::vector SplitString(const std::string & s, char delim); + +OBJModel::OBJModel(const std::string & fileName) +{ + hasUVs = false; + hasNormals = false; + std::ifstream file; + file.open(fileName.c_str()); + + std::string line; + if (file.is_open()) { + while (file.good()) { + getline(file, line); + + unsigned int lineLength = line.length(); + + if (lineLength < 2) { + continue; + } + + const char * lineCStr = line.c_str(); + + switch (lineCStr[0]) { + case 'v': + switch (lineCStr[1]) { + case 't': + this->uvs.push_back(ParseOBJVec2(line)); + break; + case 'n': + this->normals.push_back(ParseOBJVec3(line)); + break; + case ' ': + case '\t': + this->vertices.push_back(ParseOBJVec3(line)); + } + break; + case 'f': + CreateOBJFace(line); + break; + default: + break; + }; + } + } + else { + throw std::runtime_error {"Unable to load mesh: " + fileName}; + } +} + +void +IndexedModel::CalcNormals() +{ + for (unsigned int i = 0; i < indices.size(); i += 3) { + int i0 = indices[i]; + int i1 = indices[i + 1]; + int i2 = indices[i + 2]; + + glm::vec3 v1 = positions[i1] - positions[i0]; + glm::vec3 v2 = positions[i2] - positions[i0]; + + glm::vec3 normal = glm::normalize(glm::cross(v1, v2)); + + normals[i0] += normal; + normals[i1] += normal; + normals[i2] += normal; + } + + for (unsigned int i = 0; i < positions.size(); i++) { + normals[i] = glm::normalize(normals[i]); + } +} + +IndexedModel +OBJModel::ToIndexedModel() +{ + IndexedModel result; + IndexedModel normalModel; + + unsigned int numIndices = OBJIndices.size(); + + std::vector indexLookup; + + for (unsigned int i = 0; i < numIndices; i++) { + indexLookup.push_back(&OBJIndices[i]); + } + + std::sort(indexLookup.begin(), indexLookup.end(), CompareOBJIndexPtr); + + std::map normalModelIndexMap; + std::map indexMap; + + for (unsigned int i = 0; i < numIndices; i++) { + OBJIndex * currentIndex = &OBJIndices[i]; + + glm::vec3 currentPosition = vertices[currentIndex->vertexIndex]; + glm::vec2 currentTexCoord; + glm::vec3 currentNormal; + + if (hasUVs) { + currentTexCoord = uvs[currentIndex->uvIndex]; + } + else { + currentTexCoord = glm::vec2(0, 0); + } + + if (hasNormals) { + currentNormal = normals[currentIndex->normalIndex]; + } + else { + currentNormal = glm::vec3(0, 0, 0); + } + + unsigned int normalModelIndex; + unsigned int resultModelIndex; + + // Create model to properly generate normals on + const auto it = normalModelIndexMap.find(*currentIndex); + if (it == normalModelIndexMap.end()) { + normalModelIndex = normalModel.positions.size(); + + normalModelIndexMap.insert(std::pair(*currentIndex, normalModelIndex)); + normalModel.positions.push_back(currentPosition); + normalModel.texCoords.push_back(currentTexCoord); + normalModel.normals.push_back(currentNormal); + } + else { + normalModelIndex = it->second; + } + + // Create model which properly separates texture coordinates + unsigned int previousVertexLocation = FindLastVertexIndex(indexLookup, currentIndex, result); + + if (previousVertexLocation == (unsigned int)-1) { + resultModelIndex = result.positions.size(); + + result.positions.push_back(currentPosition); + result.texCoords.push_back(currentTexCoord); + result.normals.push_back(currentNormal); + } + else { + resultModelIndex = previousVertexLocation; + } + + normalModel.indices.push_back(normalModelIndex); + result.indices.push_back(resultModelIndex); + indexMap.insert(std::pair(resultModelIndex, normalModelIndex)); + } + + if (!hasNormals) { + normalModel.CalcNormals(); + + for (unsigned int i = 0; i < result.positions.size(); i++) { + result.normals[i] = normalModel.normals[indexMap[i]]; + } + } + + return result; +}; + +unsigned int +OBJModel::FindLastVertexIndex( + const std::vector & indexLookup, const OBJIndex * currentIndex, const IndexedModel & result) +{ + unsigned int start = 0; + unsigned int end = indexLookup.size(); + unsigned int current = (end - start) / 2 + start; + unsigned int previous = start; + + while (current != previous) { + OBJIndex * testIndex = indexLookup[current]; + + if (testIndex->vertexIndex == currentIndex->vertexIndex) { + unsigned int countStart = current; + + for (unsigned int i = 0; i < current; i++) { + OBJIndex * possibleIndex = indexLookup[current - i]; + + if (possibleIndex == currentIndex) { + continue; + } + + if (possibleIndex->vertexIndex != currentIndex->vertexIndex) { + break; + } + + countStart--; + } + + for (unsigned int i = countStart; i < indexLookup.size() - countStart; i++) { + OBJIndex * possibleIndex = indexLookup[current + i]; + + if (possibleIndex == currentIndex) { + continue; + } + + if (possibleIndex->vertexIndex != currentIndex->vertexIndex) { + break; + } + else if ((!hasUVs || possibleIndex->uvIndex == currentIndex->uvIndex) + && (!hasNormals || possibleIndex->normalIndex == currentIndex->normalIndex)) { + glm::vec3 currentPosition = vertices[currentIndex->vertexIndex]; + glm::vec2 currentTexCoord; + glm::vec3 currentNormal; + + if (hasUVs) { + currentTexCoord = uvs[currentIndex->uvIndex]; + } + else { + currentTexCoord = glm::vec2(0, 0); + } + + if (hasNormals) { + currentNormal = normals[currentIndex->normalIndex]; + } + else { + currentNormal = glm::vec3(0, 0, 0); + } + + for (unsigned int j = 0; j < result.positions.size(); j++) { + if (currentPosition == result.positions[j] + && ((!hasUVs || currentTexCoord == result.texCoords[j]) + && (!hasNormals || currentNormal == result.normals[j]))) { + return j; + } + } + } + } + + return -1; + } + else { + if (testIndex->vertexIndex < currentIndex->vertexIndex) { + start = current; + } + else { + end = current; + } + } + + previous = current; + current = (end - start) / 2 + start; + } + + return -1; +} + +void +OBJModel::CreateOBJFace(const std::string & line) +{ + std::vector tokens = SplitString(line, ' '); + + this->OBJIndices.push_back(ParseOBJIndex(tokens[1], &this->hasUVs, &this->hasNormals)); + this->OBJIndices.push_back(ParseOBJIndex(tokens[2], &this->hasUVs, &this->hasNormals)); + this->OBJIndices.push_back(ParseOBJIndex(tokens[3], &this->hasUVs, &this->hasNormals)); + + if ((int)tokens.size() > 4) { + this->OBJIndices.push_back(ParseOBJIndex(tokens[1], &this->hasUVs, &this->hasNormals)); + this->OBJIndices.push_back(ParseOBJIndex(tokens[3], &this->hasUVs, &this->hasNormals)); + this->OBJIndices.push_back(ParseOBJIndex(tokens[4], &this->hasUVs, &this->hasNormals)); + } +} + +OBJIndex +OBJModel::ParseOBJIndex(const std::string & token, bool * hasUVs, bool * hasNormals) +{ + unsigned int tokenLength = token.length(); + const char * tokenString = token.c_str(); + + unsigned int vertIndexStart = 0; + unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/'); + + OBJIndex result { + .vertexIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd), + .uvIndex = 0, + .normalIndex = 0, + }; + + if (vertIndexEnd >= tokenLength) { + return result; + } + + vertIndexStart = vertIndexEnd + 1; + vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/'); + + result.uvIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd); + *hasUVs = true; + + if (vertIndexEnd >= tokenLength) { + return result; + } + + vertIndexStart = vertIndexEnd + 1; + vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, '/'); + + result.normalIndex = ParseOBJIndexValue(token, vertIndexStart, vertIndexEnd); + *hasNormals = true; + + return result; +} + +glm::vec3 +OBJModel::ParseOBJVec3(const std::string & line) +{ + unsigned int tokenLength = line.length(); + const char * tokenString = line.c_str(); + + unsigned int vertIndexStart = 2; + + while (vertIndexStart < tokenLength) { + if (tokenString[vertIndexStart] != ' ') { + break; + } + vertIndexStart++; + } + + unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); + + float x = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); + + vertIndexStart = vertIndexEnd + 1; + vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); + + float y = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); + + vertIndexStart = vertIndexEnd + 1; + vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); + + float z = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); + + return glm::vec3(x, y, z); +} + +glm::vec2 +OBJModel::ParseOBJVec2(const std::string & line) +{ + unsigned int tokenLength = line.length(); + const char * tokenString = line.c_str(); + + unsigned int vertIndexStart = 3; + + while (vertIndexStart < tokenLength) { + if (tokenString[vertIndexStart] != ' ') { + break; + } + vertIndexStart++; + } + + unsigned int vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); + + float x = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); + + vertIndexStart = vertIndexEnd + 1; + vertIndexEnd = FindNextChar(vertIndexStart, tokenString, tokenLength, ' '); + + float y = ParseOBJFloatValue(line, vertIndexStart, vertIndexEnd); + + return glm::vec2(x, y); +} + +static bool +CompareOBJIndexPtr(const OBJIndex * a, const OBJIndex * b) +{ + return a->vertexIndex < b->vertexIndex; +} + +static inline unsigned int +FindNextChar(unsigned int start, const char * str, unsigned int length, char token) +{ + unsigned int result = start; + while (result < length) { + result++; + if (str[result] == token) { + break; + } + } + + return result; +} + +static inline unsigned int +ParseOBJIndexValue(const std::string & token, unsigned int start, unsigned int end) +{ + return std::stoul(token.substr(start, end - start)) - 1; +} + +static inline float +ParseOBJFloatValue(const std::string & token, unsigned int start, unsigned int end) +{ + return std::stof(token.substr(start, end - start)); +} + +static inline std::vector +SplitString(const std::string & s, char delim) +{ + std::vector elems; + + const char * cstr = s.c_str(); + unsigned int strLength = s.length(); + unsigned int start = 0; + unsigned int end = 0; + + while (end <= strLength) { + while (end <= strLength) { + if (cstr[end] == delim) { + break; + } + end++; + } + + elems.push_back(s.substr(start, end - start)); + start = end + 1; + end = start; + } + + return elems; +} diff --git a/gfx/models/obj_loader.h b/gfx/models/obj_loader.h new file mode 100644 index 0000000..11c6b38 --- /dev/null +++ b/gfx/models/obj_loader.h @@ -0,0 +1,53 @@ +#ifndef OBJ_LOADER_H_INCLUDED +#define OBJ_LOADER_H_INCLUDED + +#include +#include +#include + +struct OBJIndex { + unsigned int vertexIndex; + unsigned int uvIndex; + unsigned int normalIndex; + + bool + operator<(const OBJIndex & r) const + { + return vertexIndex < r.vertexIndex; + } +}; + +class IndexedModel { +public: + std::vector positions; + std::vector texCoords; + std::vector normals; + std::vector indices; + + void CalcNormals(); +}; + +class OBJModel { +public: + std::vector OBJIndices; + std::vector vertices; + std::vector uvs; + std::vector normals; + bool hasUVs; + bool hasNormals; + + explicit OBJModel(const std::string & fileName); + + IndexedModel ToIndexedModel(); + +private: + unsigned int FindLastVertexIndex( + const std::vector & indexLookup, const OBJIndex * currentIndex, const IndexedModel & result); + void CreateOBJFace(const std::string & line); + + glm::vec2 ParseOBJVec2(const std::string & line); + glm::vec3 ParseOBJVec3(const std::string & line); + OBJIndex ParseOBJIndex(const std::string & token, bool * hasUVs, bool * hasNormals); +}; + +#endif // OBJ_LOADER_H_INCLUDED diff --git a/gfx/models/stb_image.c b/gfx/models/stb_image.c new file mode 100644 index 0000000..a839b61 --- /dev/null +++ b/gfx/models/stb_image.c @@ -0,0 +1,8 @@ +#ifndef TIDY +# define STB_IMAGE_IMPLEMENTATION +# pragma GCC diagnostic ignored "-Wsign-compare" +# ifndef __clang__ +# pragma GCC diagnostic ignored "-Wunused-but-set-variable" +# endif +# include "stb_image.h" +#endif diff --git a/gfx/models/texture.cpp b/gfx/models/texture.cpp new file mode 100644 index 0000000..a388b32 --- /dev/null +++ b/gfx/models/texture.cpp @@ -0,0 +1,35 @@ +#include "texture.h" +#include "stb_image.h" +#include + +Texture::Texture(const std::string & fileName) : m_texture {} +{ + int width, height, numComponents; + unsigned char * data = stbi_load((fileName).c_str(), &width, &height, &numComponents, 4); + + if (!data) { + throw std::runtime_error {"Unable to load texture: " + fileName}; + } + + glGenTextures(1, &m_texture); + glBindTexture(GL_TEXTURE_2D, m_texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + stbi_image_free(data); +} + +Texture::~Texture() +{ + glDeleteTextures(1, &m_texture); +} + +void +Texture::Bind() const +{ + glBindTexture(GL_TEXTURE_2D, m_texture); +} diff --git a/gfx/models/texture.h b/gfx/models/texture.h new file mode 100644 index 0000000..6ebf88d --- /dev/null +++ b/gfx/models/texture.h @@ -0,0 +1,23 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include +#include +#include + +class Texture { +public: + explicit Texture(const std::string & fileName); + + virtual ~Texture(); + + NO_COPY(Texture); + NO_MOVE(Texture); + + void Bind() const; + +private: + GLuint m_texture; +}; + +#endif diff --git a/gfx/models/vertex.hpp b/gfx/models/vertex.hpp new file mode 100644 index 0000000..4ab6024 --- /dev/null +++ b/gfx/models/vertex.hpp @@ -0,0 +1,37 @@ +#ifndef VERTEX_H +#define VERTEX_H + +#include + +class Vertex { +public: + Vertex(glm::vec3 pos, glm::vec2 texCoord, glm::vec3 normal) : + pos {std::move(pos)}, texCoord {std::move(texCoord)}, normal {std::move(normal)} + { + } + + glm::vec3 & + GetPos() + { + return pos; + } + + glm::vec2 & + GetTexCoord() + { + return texCoord; + } + + glm::vec3 & + GetNormal() + { + return normal; + } + +private: + glm::vec3 pos; + glm::vec2 texCoord; + glm::vec3 normal; +}; + +#endif -- cgit v1.2.3