summaryrefslogtreecommitdiff
path: root/gfx
diff options
context:
space:
mode:
authorDan Goodliffe <dan@randomdan.homeip.net>2021-01-17 19:36:30 +0000
committerDan Goodliffe <dan@randomdan.homeip.net>2021-01-17 22:16:48 +0000
commit0bf4ad9e4a9e1c97e92aa23a365405dfef89bd7c (patch)
tree68a80976a247836bbb1eecc835af437e3489fbb7 /gfx
parentFirst cut modernizing and sanitizing (diff)
downloadilt-0bf4ad9e4a9e1c97e92aa23a365405dfef89bd7c.tar.bz2
ilt-0bf4ad9e4a9e1c97e92aa23a365405dfef89bd7c.tar.xz
ilt-0bf4ad9e4a9e1c97e92aa23a365405dfef89bd7c.zip
Big reshuffle
Fixes code quality warnings now picked up.
Diffstat (limited to 'gfx')
-rw-r--r--gfx/gl/camera.cpp46
-rw-r--r--gfx/gl/camera.h24
-rw-r--r--gfx/gl/shader.cpp100
-rw-r--r--gfx/gl/shader.h35
-rw-r--r--gfx/gl/shaders/basicShader.fs13
-rw-r--r--gfx/gl/shaders/basicShader.vs18
-rw-r--r--gfx/gl/transform.cpp27
-rw-r--r--gfx/gl/transform.h58
-rw-r--r--gfx/models/mesh.cpp75
-rw-r--r--gfx/models/mesh.h35
-rw-r--r--gfx/models/obj_loader.cpp428
-rw-r--r--gfx/models/obj_loader.h53
-rw-r--r--gfx/models/stb_image.c8
-rw-r--r--gfx/models/texture.cpp35
-rw-r--r--gfx/models/texture.h23
-rw-r--r--gfx/models/vertex.hpp37
16 files changed, 1015 insertions, 0 deletions
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 <glm/gtx/transform.hpp>
+
+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 <glm/glm.hpp>
+
+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 <gfx/gl/shaders/fs-basicShader.h>
+#include <gfx/gl/shaders/vs-basicShader.h>
+#include <glm/glm.hpp>
+#include <stdexcept>
+
+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<GLchar, 1024> 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 <GL/glew.h>
+#include <array>
+#include <special_members.hpp>
+#include <string>
+
+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<GLuint, NUM_SHADERS> m_shaders;
+ std::array<GLint, NUM_UNIFORMS> 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 <glm/gtx/transform.hpp>
+
+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 <glm/glm.hpp>
+
+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 <glm/glm.hpp>
+#include <memory>
+#include <vector>
+
+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 <GL/glew.h>
+#include <array>
+#include <cstddef>
+#include <special_members.hpp>
+#include <string>
+
+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<GLuint, NUM_BUFFERS> 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 <algorithm>
+#include <fstream>
+#include <map>
+#include <memory>
+#include <stdexcept>
+#include <utility>
+
+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<std::string> 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<OBJIndex *> indexLookup;
+
+ for (unsigned int i = 0; i < numIndices; i++) {
+ indexLookup.push_back(&OBJIndices[i]);
+ }
+
+ std::sort(indexLookup.begin(), indexLookup.end(), CompareOBJIndexPtr);
+
+ std::map<OBJIndex, unsigned int> normalModelIndexMap;
+ std::map<unsigned int, unsigned int> 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<OBJIndex, unsigned int>(*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<unsigned int, unsigned int>(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<OBJIndex *> & 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<std::string> 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<std::string>
+SplitString(const std::string & s, char delim)
+{
+ std::vector<std::string> 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 <glm/glm.hpp>
+#include <string>
+#include <vector>
+
+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<glm::vec3> positions;
+ std::vector<glm::vec2> texCoords;
+ std::vector<glm::vec3> normals;
+ std::vector<unsigned int> indices;
+
+ void CalcNormals();
+};
+
+class OBJModel {
+public:
+ std::vector<OBJIndex> OBJIndices;
+ std::vector<glm::vec3> vertices;
+ std::vector<glm::vec2> uvs;
+ std::vector<glm::vec3> normals;
+ bool hasUVs;
+ bool hasNormals;
+
+ explicit OBJModel(const std::string & fileName);
+
+ IndexedModel ToIndexedModel();
+
+private:
+ unsigned int FindLastVertexIndex(
+ const std::vector<OBJIndex *> & 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 <stdexcept>
+
+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 <GL/glew.h>
+#include <special_members.hpp>
+#include <string>
+
+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 <glm/glm.hpp>
+
+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