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+ }
0 commit comments