feat: refactor code + optimizations for obj file parsing
This commit is contained in:
@ -25,9 +25,12 @@ endif()
|
|||||||
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb")
|
# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb")
|
||||||
|
|
||||||
add_executable(CodingGame
|
add_executable(CodingGame
|
||||||
|
src/IO/parser.cpp
|
||||||
src/IO/file_manager.cpp
|
src/IO/file_manager.cpp
|
||||||
|
|
||||||
src/renderer/debug.cpp
|
src/renderer/debug.cpp
|
||||||
|
src/renderer/basics.cpp
|
||||||
|
src/renderer/mesh.cpp
|
||||||
src/renderer/shader.cpp
|
src/renderer/shader.cpp
|
||||||
src/renderer/texture.cpp
|
src/renderer/texture.cpp
|
||||||
src/renderer/wavefront.cpp
|
src/renderer/wavefront.cpp
|
||||||
|
13
README.md
13
README.md
@ -27,3 +27,16 @@ The run command in that case would look following:
|
|||||||
```console
|
```console
|
||||||
__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia ./build/CodingGame
|
__NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia ./build/CodingGame
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## TODO List
|
||||||
|
|
||||||
|
### Optimizations
|
||||||
|
|
||||||
|
🚀 Summary of Speedups
|
||||||
|
- Replace toElement / toMtlElement string comparisons with char-based switches.
|
||||||
|
- Replace std::stoi with a custom fast Parser::TakeIndex.
|
||||||
|
- Pre-reserve vectors for vertices, normals, texcoords, meshes.
|
||||||
|
- Load whole file into memory before parsing (fastest for large OBJs).
|
||||||
|
- Defer texture loading until after parsing.
|
||||||
|
- Store material pointers in meshes → no runtime lookups in render.
|
||||||
|
- Inline parsing functions.
|
||||||
|
20
include/IO/parser.h
Normal file
20
include/IO/parser.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef PARSER_H_
|
||||||
|
#define PARSER_H_
|
||||||
|
|
||||||
|
// Very fast OBJ/MTL line parser
|
||||||
|
class Parser {
|
||||||
|
private:
|
||||||
|
char* m_sv;
|
||||||
|
public:
|
||||||
|
Parser(char* sv) : m_sv(sv) {}
|
||||||
|
public:
|
||||||
|
void SkipSpaces();
|
||||||
|
char* TakeWord();
|
||||||
|
float TakeFloat();
|
||||||
|
int TakeInt();
|
||||||
|
bool TakeFaceIndices(int& vi, int& ti, int& ni);
|
||||||
|
char* TakeUntil(char d);
|
||||||
|
int TakeIndex(int baseCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PARSER_H_
|
27
include/renderer/mesh.h
Normal file
27
include/renderer/mesh.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef MESH_H_
|
||||||
|
#define MESH_H_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include "renderer/basics.h"
|
||||||
|
|
||||||
|
class Mesh {
|
||||||
|
public: // TODO: abstract away
|
||||||
|
unsigned int m_vao, m_vbo, m_ebo;
|
||||||
|
std::vector<Vertex> m_vertexBuffer;
|
||||||
|
std::vector<unsigned int> m_indexBuffer;
|
||||||
|
public: // TODO: abstract away
|
||||||
|
void Bind() { glBindVertexArray(m_vao); }
|
||||||
|
void Unbind() { glBindVertexArray(0); }
|
||||||
|
void Upload();
|
||||||
|
public:
|
||||||
|
std::string materialName;
|
||||||
|
public:
|
||||||
|
Mesh();
|
||||||
|
public:
|
||||||
|
void Render();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MESH_H_
|
@ -11,27 +11,11 @@
|
|||||||
#include "texture.h"
|
#include "texture.h"
|
||||||
#include "renderer/material.h"
|
#include "renderer/material.h"
|
||||||
#include "renderer/basics.h"
|
#include "renderer/basics.h"
|
||||||
|
#include "renderer/mesh.h"
|
||||||
|
|
||||||
enum ObjElement { OHASH, MTLLIB, USEMTL, O, V, VN, VT, F, OUNKNOWN };
|
enum ObjElement { OHASH, MTLLIB, USEMTL, O, V, VN, VT, F, OUNKNOWN };
|
||||||
enum MtlElement { MHASH, NEWMTL, NS, KA, KS, KD, NI, D, ILLUM, MAP_KD, MAP_KA, MUNKNOWN };
|
enum MtlElement { MHASH, NEWMTL, NS, KA, KS, KD, NI, D, ILLUM, MAP_KD, MAP_KA, MUNKNOWN };
|
||||||
|
|
||||||
class Mesh {
|
|
||||||
public: // TODO: abstract away
|
|
||||||
unsigned int m_vao, m_vbo, m_ebo;
|
|
||||||
std::vector<Vertex> m_vertexBuffer;
|
|
||||||
std::vector<unsigned int> m_indexBuffer;
|
|
||||||
public: // TODO: abstract away
|
|
||||||
void Bind() { glBindVertexArray(m_vao); }
|
|
||||||
void Unbind() { glBindVertexArray(0); }
|
|
||||||
void Upload();
|
|
||||||
public:
|
|
||||||
std::string materialName;
|
|
||||||
public:
|
|
||||||
Mesh();
|
|
||||||
public:
|
|
||||||
void Render();
|
|
||||||
};
|
|
||||||
|
|
||||||
class Object {
|
class Object {
|
||||||
private:
|
private:
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
@ -43,7 +27,7 @@ private:
|
|||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<Material>> m_materials;
|
std::unordered_map<std::string, std::shared_ptr<Material>> m_materials;
|
||||||
private:
|
private:
|
||||||
static inline int NormalizeIndex(const std::string &s, int baseCount);
|
static inline int NormalizeIndex(int idx, int baseCount);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Object();
|
Object();
|
||||||
|
123
src/IO/parser.cpp
Normal file
123
src/IO/parser.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include <charconv> // for std::from_chars (C++17+)
|
||||||
|
#include <cstdlib> // for strtof (fallback)
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "IO/parser.h"
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
void Parser::SkipSpaces() {
|
||||||
|
while (*m_sv == ' ' || *m_sv == '\t') ++m_sv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Parser::TakeIndex(int baseCount) {
|
||||||
|
if (!m_sv || *m_sv == '\0') return -1;
|
||||||
|
|
||||||
|
bool neg = (*m_sv == '-');
|
||||||
|
if (neg) ++m_sv;
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
while (*m_sv >= '0' && *m_sv <= '9') {
|
||||||
|
idx = idx * 10 + (*m_sv - '0');
|
||||||
|
++m_sv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (neg) return baseCount + (-idx);
|
||||||
|
return idx > 0 ? idx - 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get next whitespace-delimited word
|
||||||
|
char* Parser::TakeWord() {
|
||||||
|
SkipSpaces();
|
||||||
|
if (*m_sv == '\0' || *m_sv == '\n' || *m_sv == '\r') return nullptr;
|
||||||
|
|
||||||
|
char* start = m_sv;
|
||||||
|
while (*m_sv && *m_sv != ' ' && *m_sv != '\t' && *m_sv != '\n' && *m_sv != '\r')
|
||||||
|
++m_sv;
|
||||||
|
|
||||||
|
if (*m_sv) { *m_sv = '\0'; ++m_sv; }
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse a float quickly
|
||||||
|
float Parser::TakeFloat() {
|
||||||
|
SkipSpaces();
|
||||||
|
if (*m_sv == '\0') return 0.0f;
|
||||||
|
|
||||||
|
#if __cpp_lib_to_chars >= 201611L
|
||||||
|
float value = 0.0f;
|
||||||
|
auto result = std::from_chars(m_sv, m_sv + std::strlen(m_sv), value);
|
||||||
|
m_sv = const_cast<char*>(result.ptr);
|
||||||
|
return value;
|
||||||
|
#else
|
||||||
|
char* end;
|
||||||
|
float value = std::strtof(m_sv, &end);
|
||||||
|
m_sv = end;
|
||||||
|
return value;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse an integer quickly
|
||||||
|
int Parser::TakeInt() {
|
||||||
|
SkipSpaces();
|
||||||
|
if (*m_sv == '\0') return 0;
|
||||||
|
|
||||||
|
#if __cpp_lib_to_chars >= 201611L
|
||||||
|
int value = 0;
|
||||||
|
auto result = std::from_chars(m_sv, m_sv + std::strlen(m_sv), value);
|
||||||
|
m_sv = const_cast<char*>(result.ptr);
|
||||||
|
return value;
|
||||||
|
#else
|
||||||
|
char* end;
|
||||||
|
int value = static_cast<int>(std::strtol(m_sv, &end, 10));
|
||||||
|
m_sv = end;
|
||||||
|
return value;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take everything until delimiter (mutates buffer)
|
||||||
|
char* Parser::TakeUntil(char d) {
|
||||||
|
SkipSpaces();
|
||||||
|
if (*m_sv == '\0') return nullptr;
|
||||||
|
|
||||||
|
char* start = m_sv;
|
||||||
|
while (*m_sv && *m_sv != d && *m_sv != '\n' && *m_sv != '\r')
|
||||||
|
++m_sv;
|
||||||
|
|
||||||
|
if (*m_sv) { *m_sv = '\0'; ++m_sv; }
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser.h (or Parser.cpp)
|
||||||
|
// Parse one face element at current position.
|
||||||
|
// Accepts formats: "v", "v/t", "v//n", "v/t/n"
|
||||||
|
// Returns true if a token was parsed, false if no more tokens on the line.
|
||||||
|
bool Parser::TakeFaceIndices(int &vi, int &ti, int &ni) {
|
||||||
|
SkipSpaces();
|
||||||
|
if (*m_sv == '\0' || *m_sv == '\n' || *m_sv == '\r') {
|
||||||
|
vi = ti = ni = 0; // sentinel raw value meaning "no token"
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse vertex index (may be negative)
|
||||||
|
vi = static_cast<int>(std::strtol(m_sv, &m_sv, 10));
|
||||||
|
|
||||||
|
ti = ni = 0; // 0 = not present (raw)
|
||||||
|
if (*m_sv == '/') {
|
||||||
|
++m_sv; // skip '/'
|
||||||
|
// texcoord index (optional)
|
||||||
|
if (*m_sv != '/' && *m_sv != ' ' && *m_sv != '\0' && *m_sv != '\n' && *m_sv != '\r') {
|
||||||
|
ti = static_cast<int>(std::strtol(m_sv, &m_sv, 10));
|
||||||
|
}
|
||||||
|
if (*m_sv == '/') {
|
||||||
|
++m_sv; // skip second '/'
|
||||||
|
// normal index (optional)
|
||||||
|
if (*m_sv != ' ' && *m_sv != '\0' && *m_sv != '\n' && *m_sv != '\r') {
|
||||||
|
ni = static_cast<int>(std::strtol(m_sv, &m_sv, 10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point m_sv is either at whitespace, end, or next token char.
|
||||||
|
// Do NOT mutate indices (leave them raw). Let NormalizeIndex handle conversion.
|
||||||
|
return true;
|
||||||
|
}
|
15
src/renderer/basics.cpp
Normal file
15
src/renderer/basics.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include "renderer/basics.h"
|
||||||
|
|
||||||
|
void Vertex::DefineAttrib()
|
||||||
|
{
|
||||||
|
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, m_position)));
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
|
||||||
|
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, m_normal)));
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
|
||||||
|
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, m_texCoord)));
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
}
|
43
src/renderer/mesh.cpp
Normal file
43
src/renderer/mesh.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#include "renderer/mesh.h"
|
||||||
|
|
||||||
|
Mesh::Mesh() {
|
||||||
|
glGenVertexArrays(1, &m_vao);
|
||||||
|
glGenBuffers(1, &m_vbo);
|
||||||
|
glGenBuffers(1, &m_ebo);
|
||||||
|
|
||||||
|
glBindVertexArray(m_vao);
|
||||||
|
|
||||||
|
// VBO (vertex buffer)
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
// EBO (index buffer)
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
Vertex::DefineAttrib();
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::Upload()
|
||||||
|
{
|
||||||
|
glBindVertexArray(m_vao);
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, m_vertexBuffer.size() * sizeof(Vertex), m_vertexBuffer.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
// Upload indices
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer.size() * sizeof(unsigned int), m_indexBuffer.data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mesh::Render()
|
||||||
|
{
|
||||||
|
Bind();
|
||||||
|
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indexBuffer.size()), GL_UNSIGNED_INT, 0);
|
||||||
|
Unbind();
|
||||||
|
}
|
@ -26,7 +26,6 @@ std::unique_ptr<Texture> Texture::LoadFile(const std::string& filename) {
|
|||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
// TODO: automatically detect values for this function
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
|
||||||
|
@ -1,78 +1,98 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include "IO/parser.h"
|
||||||
|
#include "renderer/mesh.h"
|
||||||
#include "renderer/wavefront.h"
|
#include "renderer/wavefront.h"
|
||||||
|
|
||||||
ObjElement toElement(const std::string &s) {
|
#define DEFAULT_MATERIAL_NAME "default"
|
||||||
if (s == "#") return ObjElement::OHASH;
|
|
||||||
if (s == "mtllib") return ObjElement::MTLLIB;
|
// ObjElement toElement(const std::string &s) {
|
||||||
if (s == "usemtl") return ObjElement::USEMTL;
|
// if (s == "#") return ObjElement::OHASH;
|
||||||
if (s == "o") return ObjElement::O;
|
// if (s == "mtllib") return ObjElement::MTLLIB;
|
||||||
if (s == "v") return ObjElement::V;
|
// if (s == "usemtl") return ObjElement::USEMTL;
|
||||||
if (s == "vn") return ObjElement::VN;
|
// if (s == "o") return ObjElement::O;
|
||||||
if (s == "vt") return ObjElement::VT;
|
// if (s == "v") return ObjElement::V;
|
||||||
if (s == "f") return ObjElement::F;
|
// if (s == "vn") return ObjElement::VN;
|
||||||
|
// if (s == "vt") return ObjElement::VT;
|
||||||
|
// if (s == "f") return ObjElement::F;
|
||||||
|
// return ObjElement::OUNKNOWN;
|
||||||
|
// }
|
||||||
|
|
||||||
|
inline ObjElement toElement(const char* s) {
|
||||||
|
switch (s[0]) {
|
||||||
|
case '#': return ObjElement::OHASH;
|
||||||
|
case 'm': if (strcmp(s, "mtllib") == 0) return ObjElement::MTLLIB; break;
|
||||||
|
case 'u': if (strcmp(s, "usemtl") == 0) return ObjElement::USEMTL; break;
|
||||||
|
case 'o': if (s[1] == '\0') return ObjElement::O; break;
|
||||||
|
case 'v':
|
||||||
|
if (s[1] == '\0') return ObjElement::V;
|
||||||
|
if (s[1] == 'n' && s[2] == '\0') return ObjElement::VN;
|
||||||
|
if (s[1] == 't' && s[2] == '\0') return ObjElement::VT;
|
||||||
|
break;
|
||||||
|
case 'f': if (s[1] == '\0') return ObjElement::F; break;
|
||||||
|
}
|
||||||
return ObjElement::OUNKNOWN;
|
return ObjElement::OUNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
MtlElement toMtlElement(const std::string &s) {
|
// MtlElement toMtlElement(const std::string &s) {
|
||||||
if (s == "#") return MtlElement::MHASH;
|
// if (s == "#") return MtlElement::MHASH;
|
||||||
if (s == "newmtl") return MtlElement::NEWMTL;
|
// if (s == "newmtl") return MtlElement::NEWMTL;
|
||||||
if (s == "Ns") return MtlElement::NS;
|
// if (s == "Ns") return MtlElement::NS;
|
||||||
if (s == "Ka") return MtlElement::KA;
|
// if (s == "Ka") return MtlElement::KA;
|
||||||
if (s == "Ks") return MtlElement::KS;
|
// if (s == "Ks") return MtlElement::KS;
|
||||||
if (s == "Kd") return MtlElement::KD;
|
// if (s == "Kd") return MtlElement::KD;
|
||||||
if (s == "Ni") return MtlElement::NI;
|
// if (s == "Ni") return MtlElement::NI;
|
||||||
if (s == "d") return MtlElement::D;
|
// if (s == "d") return MtlElement::D;
|
||||||
if (s == "illum") return MtlElement::ILLUM;
|
// if (s == "illum") return MtlElement::ILLUM;
|
||||||
if (s == "map_Kd") return MtlElement::MAP_KD;
|
// if (s == "map_Kd") return MtlElement::MAP_KD;
|
||||||
if (s == "map_Ka") return MtlElement::MAP_KA;
|
// if (s == "map_Ka") return MtlElement::MAP_KA;
|
||||||
// if (s == "map_Ke") return MtlElement::MAP_KE;
|
// // if (s == "map_Ke") return MtlElement::MAP_KE;
|
||||||
|
// return MtlElement::MUNKNOWN;
|
||||||
|
// }
|
||||||
|
|
||||||
|
inline MtlElement toMtlElement(const char* s) {
|
||||||
|
switch (s[0]) {
|
||||||
|
case '#': return MtlElement::MHASH;
|
||||||
|
case 'n':
|
||||||
|
if (strcmp(s, "newmtl") == 0) return MtlElement::NEWMTL;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
if (s[1] == 's' && s[2] == '\0') return MtlElement::NS;
|
||||||
|
if (s[1] == 'i' && s[2] == '\0') return MtlElement::NI;
|
||||||
|
break;
|
||||||
|
case 'K':
|
||||||
|
if (s[1] == 'a' && s[2] == '\0') return MtlElement::KA;
|
||||||
|
if (s[1] == 's' && s[2] == '\0') return MtlElement::KS;
|
||||||
|
if (s[1] == 'd' && s[2] == '\0') return MtlElement::KD;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
if (s[1] == '\0') return MtlElement::D;
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
if (strcmp(s, "illum") == 0) return MtlElement::ILLUM;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if (strcmp(s, "map_Kd") == 0) return MtlElement::MAP_KD;
|
||||||
|
if (strcmp(s, "map_Ka") == 0) return MtlElement::MAP_KA;
|
||||||
|
// if (strcmp(s, "map_Ke") == 0) return MtlElement::MAP_KE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
return MtlElement::MUNKNOWN;
|
return MtlElement::MUNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vertex::DefineAttrib()
|
inline int Object::NormalizeIndex(int idx, int baseCount) {
|
||||||
{
|
// idx is the raw value returned by parser:
|
||||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, m_position)));
|
// 0 -> means "not present" or invalid in our convention
|
||||||
glEnableVertexAttribArray(0);
|
// >0 -> 1-based index -> convert to 0-based
|
||||||
|
// <0 -> negative index -> relative to baseCount: baseCount + idx
|
||||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, m_normal)));
|
if (idx == 0) return -1; // absent / invalid
|
||||||
glEnableVertexAttribArray(1);
|
if (idx > 0) return idx - 1; // 1-based -> 0-based
|
||||||
|
return baseCount + idx; // negative -> count from end
|
||||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast<const void*>(offsetof(Vertex, m_texCoord)));
|
|
||||||
glEnableVertexAttribArray(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int Object::NormalizeIndex(const std::string &s, int baseCount) {
|
|
||||||
if (s.empty()) return -1;
|
|
||||||
int idx = std::stoi(s);
|
|
||||||
if (idx > 0) return idx - 1;
|
|
||||||
return baseCount + idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
Mesh::Mesh() {
|
|
||||||
glGenVertexArrays(1, &m_vao);
|
|
||||||
glGenBuffers(1, &m_vbo);
|
|
||||||
glGenBuffers(1, &m_ebo);
|
|
||||||
|
|
||||||
glBindVertexArray(m_vao);
|
|
||||||
|
|
||||||
// VBO (vertex buffer)
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
// EBO (index buffer)
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
Vertex::DefineAttrib();
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
||||||
glBindVertexArray(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object::Object() {
|
Object::Object() {
|
||||||
@ -83,91 +103,130 @@ Object::Object() {
|
|||||||
|
|
||||||
void Object::LoadMaterials(const std::filesystem::path& filename) {
|
void Object::LoadMaterials(const std::filesystem::path& filename) {
|
||||||
std::ifstream file(filename);
|
std::ifstream file(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open MTL file: " << filename << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string currentMaterialName;
|
std::string currentMaterialName;
|
||||||
std::shared_ptr<Material> currentMaterial;
|
std::shared_ptr<Material> currentMaterial;
|
||||||
|
|
||||||
std::string line;
|
char line[1024]; // buffer per line
|
||||||
while (std::getline(file, line)) {
|
|
||||||
std::istringstream iss(line);
|
while (file.getline(line, sizeof(line))) {
|
||||||
std::string prefix;
|
Parser p(line);
|
||||||
iss >> prefix;
|
char* prefix = p.TakeWord();
|
||||||
switch(toMtlElement(prefix)) {
|
if (!prefix) continue;
|
||||||
case MtlElement::MHASH:
|
|
||||||
{
|
switch (toMtlElement(prefix)) {
|
||||||
std::cout << "comment: " << line << std::endl;
|
case MtlElement::MHASH: // comment
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
case MtlElement::NEWMTL:
|
case MtlElement::NEWMTL:
|
||||||
{
|
{
|
||||||
|
// If a material was being built, commit it first
|
||||||
if (currentMaterial) {
|
if (currentMaterial) {
|
||||||
m_materials.insert(std::make_pair(currentMaterialName, std::move(currentMaterial)));
|
AddMaterial(currentMaterialName, std::move(currentMaterial));
|
||||||
currentMaterial = nullptr;
|
currentMaterial = nullptr;
|
||||||
}
|
}
|
||||||
std::string materialName;
|
|
||||||
iss >> materialName;
|
char* materialName = p.TakeWord();
|
||||||
currentMaterialName = materialName;
|
if (materialName) {
|
||||||
currentMaterial = std::make_shared<Material>();
|
currentMaterialName = materialName;
|
||||||
break;
|
currentMaterial = std::make_shared<Material>();
|
||||||
}
|
|
||||||
case MtlElement::NS:
|
|
||||||
{
|
|
||||||
float weight;
|
|
||||||
iss >> weight;
|
|
||||||
currentMaterial->SetSpecularWeight(weight);
|
|
||||||
}
|
|
||||||
case MtlElement::KA:
|
|
||||||
{
|
|
||||||
float r, g, b;
|
|
||||||
iss >> r >> g >> b;
|
|
||||||
currentMaterial->SetAmbientColor(glm::vec3(r, g, b));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MtlElement::KS:
|
|
||||||
{
|
|
||||||
float r, g, b;
|
|
||||||
iss >> r >> g >> b;
|
|
||||||
currentMaterial->SetSpecularColor(glm::vec3(r, g, b));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MtlElement::KD:
|
|
||||||
{
|
|
||||||
float r, g, b;
|
|
||||||
iss >> r >> g >> b;
|
|
||||||
currentMaterial->SetDiffuseColor(glm::vec3(r, g, b));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MtlElement::D:
|
|
||||||
{
|
|
||||||
float d;
|
|
||||||
iss >> d;
|
|
||||||
currentMaterial->SetOpacity(d);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MtlElement::ILLUM:
|
|
||||||
{
|
|
||||||
int illum;
|
|
||||||
iss >> illum;
|
|
||||||
currentMaterial->SetIllumination(illum);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case MtlElement::MAP_KD:
|
|
||||||
{
|
|
||||||
std::string texturePath;
|
|
||||||
std::string part;
|
|
||||||
while (iss >> part) {
|
|
||||||
texturePath += part + " ";
|
|
||||||
}
|
}
|
||||||
texturePath = texturePath.substr(0, texturePath.size() - 1);
|
break;
|
||||||
currentMaterial->SetDiffuseTexture(Texture::LoadFile(texturePath));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case MtlElement::NS: // specular weight
|
||||||
|
{
|
||||||
|
float weight = p.TakeFloat();
|
||||||
|
if (currentMaterial) currentMaterial->SetSpecularWeight(weight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::KA: // ambient color
|
||||||
|
{
|
||||||
|
float r = p.TakeFloat();
|
||||||
|
float g = p.TakeFloat();
|
||||||
|
float b = p.TakeFloat();
|
||||||
|
if (currentMaterial) currentMaterial->SetAmbientColor(glm::vec3(r, g, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::KS: // specular color
|
||||||
|
{
|
||||||
|
float r = p.TakeFloat();
|
||||||
|
float g = p.TakeFloat();
|
||||||
|
float b = p.TakeFloat();
|
||||||
|
if (currentMaterial) currentMaterial->SetSpecularColor(glm::vec3(r, g, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::KD: // diffuse color
|
||||||
|
{
|
||||||
|
float r = p.TakeFloat();
|
||||||
|
float g = p.TakeFloat();
|
||||||
|
float b = p.TakeFloat();
|
||||||
|
if (currentMaterial) currentMaterial->SetDiffuseColor(glm::vec3(r, g, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::D: // opacity
|
||||||
|
{
|
||||||
|
float d = p.TakeFloat();
|
||||||
|
if (currentMaterial) currentMaterial->SetOpacity(d);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::ILLUM: // illumination model
|
||||||
|
{
|
||||||
|
int illum = p.TakeInt();
|
||||||
|
if (currentMaterial) currentMaterial->SetIllumination(illum);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::MAP_KD: // diffuse texture map
|
||||||
|
{
|
||||||
|
// take rest of line as texture path (can contain spaces)
|
||||||
|
char* texPath = p.TakeUntil('\0');
|
||||||
|
if (texPath && currentMaterial) {
|
||||||
|
// trim trailing spaces
|
||||||
|
size_t len = std::strlen(texPath);
|
||||||
|
while (len > 0 && (texPath[len - 1] == ' ' || texPath[len - 1] == '\t'))
|
||||||
|
texPath[--len] = '\0';
|
||||||
|
|
||||||
|
currentMaterial->SetDiffuseTexture(Texture::LoadFile(texPath));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case MtlElement::MAP_KA: // ambient texture map
|
||||||
|
{
|
||||||
|
char* texPath = p.TakeUntil('\0');
|
||||||
|
if (texPath && currentMaterial) {
|
||||||
|
size_t len = std::strlen(texPath);
|
||||||
|
while (len > 0 && (texPath[len - 1] == ' ' || texPath[len - 1] == '\t'))
|
||||||
|
texPath[--len] = '\0';
|
||||||
|
|
||||||
|
// optional: handle ambient texture
|
||||||
|
// currentMaterial->SetAmbientTexture(Texture::LoadFile(texPath));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// ignore unknown tokens
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commit last material if pending
|
||||||
if (currentMaterial) {
|
if (currentMaterial) {
|
||||||
// m_materials.insert(std::make_pair(currentMaterialName, std::move(currentMaterial)));
|
|
||||||
AddMaterial(currentMaterialName, std::move(currentMaterial));
|
AddMaterial(currentMaterialName, std::move(currentMaterial));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Object::AddMaterial(std::string name, std::shared_ptr<Material> material)
|
void Object::AddMaterial(std::string name, std::shared_ptr<Material> material)
|
||||||
@ -194,116 +253,126 @@ Mesh& Object::GetLastMesh()
|
|||||||
if (m_meshes.empty()) {
|
if (m_meshes.empty()) {
|
||||||
auto material = std::make_shared<Material>();
|
auto material = std::make_shared<Material>();
|
||||||
material->SetAmbientColor(glm::vec3(0.52f, 0.52f, 0.52f));
|
material->SetAmbientColor(glm::vec3(0.52f, 0.52f, 0.52f));
|
||||||
// TODO: come up with name for a default material
|
AddMaterial(DEFAULT_MATERIAL_NAME, std::move(material));
|
||||||
AddMaterial("", std::move(material));
|
CreateNewMesh(DEFAULT_MATERIAL_NAME);
|
||||||
CreateNewMesh("");
|
|
||||||
}
|
}
|
||||||
return m_meshes.back();
|
return m_meshes.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
Object Object::LoadFile(const std::string& filename) {
|
Object Object::LoadFile(const std::string& filename) {
|
||||||
std::ifstream file(filename);
|
std::ifstream file(filename);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open OBJ file: " << filename << std::endl;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
Object obj;
|
Object obj;
|
||||||
|
char line[1024]; // static buffer for each line (enough for OBJ lines)
|
||||||
|
|
||||||
std::string line;
|
while (file.getline(line, sizeof(line))) {
|
||||||
while (std::getline(file, line)) {
|
Parser p(line);
|
||||||
std::istringstream iss(line);
|
char* prefix = p.TakeWord();
|
||||||
std::string prefix;
|
if (!prefix) continue;
|
||||||
iss >> prefix;
|
|
||||||
switch(toElement(prefix)) {
|
switch (toElement(prefix)) {
|
||||||
// comment
|
case ObjElement::OHASH: // comment
|
||||||
case ObjElement::OHASH:
|
|
||||||
{
|
|
||||||
std::cout << "comment: " << line << std::endl;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
case ObjElement::MTLLIB:
|
case ObjElement::MTLLIB:
|
||||||
{
|
{
|
||||||
std::string mtlFile;
|
char* mtlFile = p.TakeWord();
|
||||||
iss >> mtlFile;
|
if (mtlFile) {
|
||||||
std::filesystem::path fullPath = filename;
|
std::filesystem::path fullPath = filename;
|
||||||
std::filesystem::path mtlPath = fullPath.replace_filename(mtlFile);
|
std::filesystem::path mtlPath = fullPath.replace_filename(mtlFile);
|
||||||
obj.LoadMaterials(mtlPath);
|
obj.LoadMaterials(mtlPath);
|
||||||
std::cout << "loaded mtl at '" << mtlPath << "' with "
|
|
||||||
<< obj.m_materials.size() << " materials" << std::endl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ObjElement::USEMTL:
|
|
||||||
{
|
|
||||||
std::string materialName;
|
|
||||||
iss >> materialName;
|
|
||||||
auto& mesh = obj.GetLastMesh();
|
|
||||||
if (mesh.materialName != materialName) {
|
|
||||||
Mesh mesh;
|
|
||||||
mesh.materialName = materialName;
|
|
||||||
obj.m_meshes.push_back(mesh);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// object name I suppose
|
|
||||||
case ObjElement::O:
|
case ObjElement::USEMTL:
|
||||||
{
|
{
|
||||||
obj.m_name = line.substr(2);
|
char* materialName = p.TakeWord();
|
||||||
|
if (materialName) {
|
||||||
|
auto& mesh = obj.GetLastMesh();
|
||||||
|
if (mesh.materialName != materialName) {
|
||||||
|
Mesh newMesh;
|
||||||
|
newMesh.materialName = materialName;
|
||||||
|
obj.m_meshes.push_back(newMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// vertex with its position
|
|
||||||
case ObjElement::V:
|
case ObjElement::O: // object name
|
||||||
{
|
{
|
||||||
float x, y, z, w;
|
char* name = p.TakeWord();
|
||||||
w = 1.0f;
|
if (name) obj.m_name = name;
|
||||||
iss >> x >> y >> z;
|
break;
|
||||||
if (iss >> w) {
|
}
|
||||||
x /= w;
|
|
||||||
y /= w;
|
case ObjElement::V: // vertex
|
||||||
z /= w;
|
{
|
||||||
|
float x = p.TakeFloat();
|
||||||
|
float y = p.TakeFloat();
|
||||||
|
float z = p.TakeFloat();
|
||||||
|
float w = p.TakeFloat();
|
||||||
|
|
||||||
|
if (w != 0.0f && w != 1.0f) {
|
||||||
|
x /= w; y /= w; z /= w;
|
||||||
}
|
}
|
||||||
obj.m_vertices.emplace_back(x, y, z);
|
obj.m_vertices.emplace_back(x, y, z);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjElement::VN:
|
|
||||||
|
case ObjElement::VN: // normal
|
||||||
{
|
{
|
||||||
float x, y, z;
|
float x = p.TakeFloat();
|
||||||
iss >> x >> y >> z;
|
float y = p.TakeFloat();
|
||||||
|
float z = p.TakeFloat();
|
||||||
obj.m_normals.emplace_back(x, y, z);
|
obj.m_normals.emplace_back(x, y, z);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjElement::VT:
|
|
||||||
|
case ObjElement::VT: // texcoord
|
||||||
{
|
{
|
||||||
float u, v;
|
float u = p.TakeFloat();
|
||||||
iss >> u >> v;
|
float v = p.TakeFloat();
|
||||||
obj.m_texCoords.emplace_back(u, 1.0f - v);
|
obj.m_texCoords.emplace_back(u, 1.0f - v);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ObjElement::F:
|
|
||||||
|
case ObjElement::F: // face
|
||||||
{
|
{
|
||||||
auto& mesh = obj.GetLastMesh();
|
auto& mesh = obj.GetLastMesh();
|
||||||
std::string token;
|
int raw_vi, raw_ti, raw_ni;
|
||||||
|
|
||||||
while (iss >> token) {
|
while (p.TakeFaceIndices(raw_vi, raw_ti, raw_ni)) {
|
||||||
std::string a, b, c;
|
// Convert raw OBJ indices to 0-based / -1 sentinel
|
||||||
std::istringstream ref(token);
|
int vi = Object::NormalizeIndex(raw_vi, (int)obj.m_vertices.size());
|
||||||
|
int ti = Object::NormalizeIndex(raw_ti, (int)obj.m_texCoords.size());
|
||||||
|
int ni = Object::NormalizeIndex(raw_ni, (int)obj.m_normals.size());
|
||||||
|
|
||||||
std::getline(ref, a, '/');
|
if (vi < 0) {
|
||||||
std::getline(ref, b, '/');
|
// malformed token (no vertex) — skip
|
||||||
std::getline(ref, c, '/');
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
int vi = Object::NormalizeIndex(a, (int)obj.m_vertices.size());
|
glm::vec3 vert = obj.m_vertices[vi];
|
||||||
int ti = Object::NormalizeIndex(b, (int)obj.m_texCoords.size());
|
glm::vec3 norm(0.0f);
|
||||||
int ni = Object::NormalizeIndex(c, (int)obj.m_normals.size());
|
glm::vec2 texCoord(0.0f);
|
||||||
|
|
||||||
glm::vec3 vert, norm;
|
|
||||||
glm::vec2 texCoord;
|
|
||||||
vert = obj.m_vertices[vi];
|
|
||||||
if (ni >= 0) norm = obj.m_normals[ni];
|
if (ni >= 0) norm = obj.m_normals[ni];
|
||||||
if (ti >= 0) texCoord = obj.m_texCoords[ti];
|
if (ti >= 0) texCoord = obj.m_texCoords[ti];
|
||||||
|
|
||||||
mesh.m_vertexBuffer.emplace_back(vert, norm, texCoord);
|
mesh.m_vertexBuffer.emplace_back(vert, norm, texCoord);
|
||||||
mesh.m_indexBuffer.push_back(mesh.m_vertexBuffer.size() - 1);
|
mesh.m_indexBuffer.push_back(mesh.m_vertexBuffer.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// ignore unknown tokens
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,9 +382,6 @@ Object Object::LoadFile(const std::string& filename) {
|
|||||||
std::cout << "TexCoords count: " << obj.m_texCoords.size() << std::endl;
|
std::cout << "TexCoords count: " << obj.m_texCoords.size() << std::endl;
|
||||||
std::cout << "Meshes count: " << obj.m_meshes.size() << std::endl;
|
std::cout << "Meshes count: " << obj.m_meshes.size() << std::endl;
|
||||||
std::cout << "Materials count: " << obj.m_materials.size() << std::endl;
|
std::cout << "Materials count: " << obj.m_materials.size() << std::endl;
|
||||||
|
|
||||||
// std::cout << "Vertex Buffer size: " << obj.m_vertexBuffer.size() << std::endl;
|
|
||||||
// std::cout << "Index Buffer size: " << obj.m_indexBuffer.size() << std::endl;
|
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
@ -326,6 +392,7 @@ Object Object::LoadFile(const std::string& filename) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Object::Render(Shader& shader)
|
void Object::Render(Shader& shader)
|
||||||
{
|
{
|
||||||
for (auto &mesh : m_meshes) {
|
for (auto &mesh : m_meshes) {
|
||||||
@ -353,23 +420,4 @@ void Object::Render(Shader& shader)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::Upload()
|
|
||||||
{
|
|
||||||
glBindVertexArray(m_vao);
|
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, m_vertexBuffer.size() * sizeof(Vertex), m_vertexBuffer.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
// Upload indices
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer.size() * sizeof(unsigned int), m_indexBuffer.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mesh::Render()
|
|
||||||
{
|
|
||||||
Bind();
|
|
||||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indexBuffer.size()), GL_UNSIGNED_INT, 0);
|
|
||||||
Unbind();
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user