Skip to content

Commit ece7d97

Browse files
authored
Merge pull request #8 from masonlet/feature/obj-parser
Feature/obj parser
2 parents 7da6d75 + e062b49 commit ece7d97

7 files changed

Lines changed: 891 additions & 8 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#pragma once
2+
3+
#include "mesh_parser_base.hpp"
4+
#include <vector>
5+
6+
namespace Starlet {
7+
8+
namespace Math {
9+
struct Vertex;
10+
template<typename T> struct Vec3;
11+
template<typename T> struct Vec2;
12+
}
13+
14+
namespace Serializer {
15+
struct MeshData;
16+
17+
class ObjParser : public MeshParserBase {
18+
public:
19+
bool parse(const std::string& path, MeshData& out);
20+
21+
private:
22+
struct ObjVertex {
23+
int posI;
24+
int texI;
25+
int normI;
26+
};
27+
28+
bool parsePosition(const unsigned char*& p,
29+
std::vector<Starlet::Math::Vec3<float>>& positions,
30+
std::vector<Starlet::Math::Vec4<float>>& colours);
31+
bool parseTexCoord(const unsigned char*& p, std::vector<Starlet::Math::Vec2<float>>& texCoords);
32+
bool parseNormal(const unsigned char*& p, std::vector<Starlet::Math::Vec3<float>>& normals);
33+
34+
void fillMeshData(
35+
MeshData& out,
36+
std::vector<Starlet::Math::Vertex>& vertices,
37+
std::vector<unsigned int>& indices,
38+
bool usedTexCoords,
39+
bool usedNormals
40+
);
41+
};
42+
}
43+
44+
}

inc/starlet-serializer/parser/mesh_parser.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class MeshParser : public Parser {
1313
private:
1414
enum class MeshFormat {
1515
PLY,
16+
OBJ,
1617
UNKNOWN
1718
};
1819

src/parser/mesh/obj_parser.cpp

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
#include "starlet-serializer/parser/mesh/obj_parser.hpp"
2+
#include "starlet-serializer/data/mesh_data.hpp"
3+
#include "starlet-logger/logger.hpp"
4+
5+
#include "starlet-math/vertex.hpp"
6+
#include "starlet-math/vec3.hpp"
7+
#include "starlet-math/vec2.hpp"
8+
9+
#include <cstring>
10+
#include <cfloat>
11+
#include <vector>
12+
#include <map>
13+
#include <tuple>
14+
15+
namespace Starlet::Serializer {
16+
17+
bool ObjParser::parse(const std::string& path, MeshData& out) {
18+
std::vector<unsigned char> file;
19+
if (!loadBinaryFile(file, path)) return false;
20+
21+
if (file.empty() || file[0] == '\0')
22+
return Logger::error("ObjParser", "parse", "File is empty");
23+
24+
std::vector<Starlet::Math::Vec3<float>> positions;
25+
std::vector<Starlet::Math::Vec4<float>> colours;
26+
27+
std::vector<Starlet::Math::Vec2<float>> texCoords;
28+
std::vector<Starlet::Math::Vec3<float>> normals;
29+
30+
bool usedTexCoords = false;
31+
bool usedNormals = false;
32+
33+
std::map<std::tuple<int, int, int>, unsigned int> vertexMap;
34+
std::vector<Math::Vertex> vertices;
35+
std::vector<unsigned int> indices;
36+
37+
const unsigned char* p = file.data();
38+
while (*p) {
39+
p = skipWhitespace(p);
40+
if (*p == '\0') break;
41+
42+
if (*p == '#') {
43+
p = skipToNextLine(p);
44+
continue;
45+
}
46+
47+
unsigned char cmd[32]{};
48+
if (!parseToken(p, cmd, sizeof(cmd))) {
49+
p = skipToNextLine(p);
50+
continue;
51+
}
52+
53+
if (strcmp(reinterpret_cast<const char*>(cmd), "v") == 0) {
54+
if (!parsePosition(p, positions, colours))
55+
return Logger::error("ObjParser", "parse", "Failed to parse vertex position at vertex " + std::to_string(positions.size()));
56+
}
57+
else if (strcmp(reinterpret_cast<const char*>(cmd), "vt") == 0) {
58+
if (!parseTexCoord(p, texCoords))
59+
return Logger::error("ObjParser", "parse", "Failed to parse texture coordinate at texCoord " + std::to_string(texCoords.size()));
60+
}
61+
else if (strcmp(reinterpret_cast<const char*>(cmd), "vn") == 0) {
62+
if (!parseNormal(p, normals))
63+
return Logger::error("ObjParser", "parse", "Failed to parse normal at normal " + std::to_string(normals.size()));
64+
}
65+
else if (strcmp((const char*)cmd, "f") == 0) {
66+
std::vector<ObjVertex> faceVertices;
67+
68+
while (true) {
69+
p = skipWhitespace(p);
70+
if (!*p || *p == '\n' || *p == '\r') break;
71+
72+
ObjVertex fv{ -1, -1, -1 };
73+
74+
int posI = 0;
75+
bool negative = (*p == '-');
76+
if (negative) ++p;
77+
78+
unsigned int absVal;
79+
if (!parseUInt(p, absVal)) break;
80+
posI = negative ? -static_cast<int>(absVal) : static_cast<int>(absVal);
81+
82+
if (posI > 0) --posI;
83+
else if (posI < 0) posI = static_cast<int>(positions.size()) + posI;
84+
else return Logger::error("ObjParser", "parse", "Face index cannot be 0");
85+
86+
if (posI < 0 || posI >= static_cast<int>(positions.size()))
87+
return Logger::error("ObjParser", "parse", "Face index out of bounds" + std::to_string(posI));
88+
89+
fv.posI = posI;
90+
91+
while (*p == '/') {
92+
++p;
93+
94+
if (*p == '/') {
95+
++p;
96+
97+
if (*p != ' ' && *p != '\n' && *p != '\r') {
98+
int normI{ 0 };
99+
negative = (*p == '-');
100+
if (negative) ++p;
101+
102+
if (parseUInt(p, absVal)) {
103+
normI = negative ? -static_cast<int>(absVal) : static_cast<int>(absVal);
104+
105+
if (normI > 0) normI--;
106+
else if (normI < 0) normI = static_cast<int>(normals.size()) + normI;
107+
else return Logger::error("ObjParser", "parse", "Normal index cannot be 0");
108+
109+
if (normI < 0 || normI >= static_cast<int>(normals.size()))
110+
return Logger::error("ObjParser", "parse", "Normal index out of bounds: " + std::to_string(normI));
111+
112+
fv.normI = normI;
113+
}
114+
}
115+
break;
116+
}
117+
118+
if (*p == ' ' || *p == '\n' || *p == '\r')
119+
return Logger::error("ObjParser", "parse", "Expected texture coordinate index after '/'");
120+
121+
int texCoordI{ 0 };
122+
negative = (*p == '-');
123+
if (negative) ++p;
124+
125+
if (!parseUInt(p, absVal))
126+
return Logger::error("ObjParser", "parse", "Expected texture coordinate index after '/'");
127+
128+
texCoordI = negative ? -static_cast<int>(absVal) : static_cast<int>(absVal);
129+
130+
if (texCoordI > 0) texCoordI--;
131+
else if (texCoordI < 0) texCoordI = static_cast<int>(texCoords.size()) + texCoordI;
132+
else return Logger::error("ObjParser", "parse", "TexCoord index cannot be 0");
133+
134+
if (texCoordI < 0 || texCoordI >= static_cast<int>(texCoords.size()))
135+
return Logger::error("ObjParser", "parse", "TexCoord index out of bounds: " + std::to_string(texCoordI));
136+
137+
fv.texI = texCoordI;
138+
139+
if (*p == '/') {
140+
++p;
141+
142+
if (*p != ' ' && *p != '\n' && *p != '\r') {
143+
int normI{ 0 };
144+
negative = (*p == '-');
145+
if (negative) ++p;
146+
147+
if (parseUInt(p, absVal)) {
148+
normI = negative ? -static_cast<int>(absVal) : static_cast<int>(absVal);
149+
150+
if (normI > 0) normI--;
151+
else if (normI < 0) normI = static_cast<int>(normals.size()) + normI;
152+
else return Logger::error("ObjParser", "parse", "Normal index cannot be 0");
153+
154+
if (normI < 0 || normI >= static_cast<int>(normals.size()))
155+
return Logger::error("ObjParser", "parse", "Normal index out of bounds: " + std::to_string(normI));
156+
157+
fv.normI = normI;
158+
}
159+
}
160+
}
161+
break;
162+
}
163+
164+
faceVertices.push_back(fv);
165+
}
166+
167+
if (faceVertices.size() < 3)
168+
return Logger::error("ObjParser", "parse", "Face has fewer than 3 vertices");
169+
170+
std::vector<unsigned int> faceIndices;
171+
for (const ObjVertex& fv : faceVertices) {
172+
std::tuple<int, int, int> key = std::make_tuple(fv.posI, fv.texI, fv.normI);
173+
174+
auto it = vertexMap.find(key);
175+
if (it != vertexMap.end())
176+
faceIndices.push_back(it->second);
177+
else {
178+
Math::Vertex v{};
179+
v.pos = positions[fv.posI];
180+
v.col = colours[fv.posI];
181+
if (fv.texI >= 0) {
182+
v.texCoord = texCoords[fv.texI];
183+
usedTexCoords = true;
184+
}
185+
if (fv.normI >= 0) {
186+
v.norm = normals[fv.normI];
187+
usedNormals = true;
188+
}
189+
190+
unsigned int i = static_cast<unsigned int>(vertices.size());
191+
vertices.push_back(v);
192+
vertexMap[key] = i;
193+
faceIndices.push_back(i);
194+
}
195+
}
196+
197+
for (size_t i = 2; i < faceIndices.size(); ++i) {
198+
indices.push_back(faceIndices[0]);
199+
indices.push_back(faceIndices[i - 1]);
200+
indices.push_back(faceIndices[i]);
201+
}
202+
}
203+
else p = skipToNextLine(p);
204+
}
205+
206+
if (vertices.empty() && !positions.empty()) {
207+
for (size_t i = 0; i < positions.size(); ++i) {
208+
Math::Vertex v{};
209+
210+
v.pos = positions[i];
211+
v.col = colours[i];
212+
213+
vertices.push_back(v);
214+
}
215+
}
216+
217+
fillMeshData(out, vertices, indices, usedTexCoords, usedNormals);
218+
return true;
219+
}
220+
221+
bool ObjParser::parsePosition(const unsigned char*& p,
222+
std::vector<Starlet::Math::Vec3<float>>& positions,
223+
std::vector<Starlet::Math::Vec4<float>>& colours) {
224+
225+
Starlet::Math::Vec3<float> pos;
226+
if (!parseVec3f(p, pos))
227+
return false;
228+
229+
Starlet::Math::Vec4<float> col{ 1.0f, 1.0f, 1.0f, 1.0f };
230+
float w{ 1.0f };
231+
232+
const unsigned char* start = p;
233+
std::vector<float> extra;
234+
while (true) {
235+
float val;
236+
const unsigned char* save = p;
237+
if (parseFloat(p, val))
238+
extra.push_back(val);
239+
else {
240+
p = save;
241+
break;
242+
}
243+
}
244+
245+
if (extra.size() == 1) {
246+
w = extra[0];
247+
}
248+
else if (extra.size() == 3) {
249+
col.x = extra[0];
250+
col.y = extra[1];
251+
col.z = extra[2];
252+
}
253+
else if (extra.size() == 4) {
254+
col.x = extra[0];
255+
col.y = extra[1];
256+
col.z = extra[2];
257+
col.w = extra[3];
258+
}
259+
260+
colours.push_back(col);
261+
positions.push_back(pos);
262+
return true;
263+
}
264+
265+
bool ObjParser::parseTexCoord(const unsigned char*& p, std::vector<Starlet::Math::Vec2<float>>& texCoords) {
266+
Starlet::Math::Vec2<float> tex;
267+
if (!parseVec2f(p, tex))
268+
return false;
269+
270+
float w;
271+
parseFloat(p, w);
272+
273+
texCoords.push_back(tex);
274+
return true;
275+
}
276+
277+
bool ObjParser::parseNormal(const unsigned char*& p, std::vector<Starlet::Math::Vec3<float>>& normals) {
278+
Starlet::Math::Vec3<float> norm;
279+
if (!parseVec3f(p, norm))
280+
return false;
281+
282+
normals.push_back(norm);
283+
return true;
284+
}
285+
286+
void ObjParser::fillMeshData(
287+
MeshData& out,
288+
std::vector<Starlet::Math::Vertex>& vertices,
289+
std::vector<unsigned int>& indices,
290+
bool usedTexCoords,
291+
bool usedNormals
292+
) {
293+
out.vertices = std::move(vertices);
294+
out.numVertices = out.vertices.size();
295+
296+
out.numIndices = indices.size();
297+
out.numTriangles = out.numIndices / 3;
298+
out.indices = std::move(indices);
299+
300+
out.hasTexCoords = usedTexCoords;
301+
out.hasNormals = usedNormals;
302+
}
303+
304+
}

src/parser/mesh_parser.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "starlet-serializer/parser/mesh_parser.hpp"
22

33
#include "starlet-serializer/parser/mesh/ply_parser.hpp"
4+
#include "starlet-serializer/parser/mesh/obj_parser.hpp"
45

56
#include "starlet-logger/logger.hpp"
67

@@ -14,6 +15,9 @@ bool MeshParser::parse(const std::string& path, MeshData& out) {
1415
case MeshFormat::PLY:
1516
parser = std::make_unique<PlyParser>();
1617
break;
18+
case MeshFormat::OBJ:
19+
parser = std::make_unique<ObjParser>();
20+
break;
1721
default:
1822
Logger::error("MeshParser", "parse", "Unsupported mesh format: " + path);
1923
return false;
@@ -31,6 +35,7 @@ MeshParser::MeshFormat MeshParser::detectFormat(const std::string& path) {
3135
for (char& c : extension) c = static_cast<char>(tolower(c));
3236

3337
if (extension == "ply") return MeshFormat::PLY;
38+
if (extension == "obj") return MeshFormat::OBJ;
3439
else return MeshFormat::UNKNOWN;
3540
}
3641

0 commit comments

Comments
 (0)