1 //******************************************************************************
2 ///
3 /// @file parser/parser_obj.cpp
4 ///
5 /// This module implements import of Wavefront OBJ files.
6 ///
7 /// @copyright
8 /// @parblock
9 ///
10 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
11 /// Copyright 1991-2018 Persistence of Vision Raytracer Pty. Ltd.
12 ///
13 /// POV-Ray is free software: you can redistribute it and/or modify
14 /// it under the terms of the GNU Affero General Public License as
15 /// published by the Free Software Foundation, either version 3 of the
16 /// License, or (at your option) any later version.
17 ///
18 /// POV-Ray is distributed in the hope that it will be useful,
19 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
20 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 /// GNU Affero General Public License for more details.
22 ///
23 /// You should have received a copy of the GNU Affero General Public License
24 /// along with this program. If not, see <http://www.gnu.org/licenses/>.
25 ///
26 /// ----------------------------------------------------------------------------
27 ///
28 /// POV-Ray is based on the popular DKB raytracer version 2.12.
29 /// DKBTrace was originally written by David K. Buck.
30 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
31 ///
32 /// @endparblock
33 ///
34 //******************************************************************************
35
36 // Unit header file must be the first file included within POV-Ray *.cpp files (pulls in config)
37 #include "parser/parser.h"
38
39 #if POV_PARSER_EXPERIMENTAL_OBJ_IMPORT
40
41 #include <cctype>
42
43 #include "core/material/interior.h"
44 #include "core/shape/mesh.h"
45
46 // this must be the last file included
47 #include "base/povdebug.h"
48
49 namespace pov_parser
50 {
51
52 using namespace pov;
53
54 static const int kMaxObjBufferSize = 1024;
55
56 #define PAIR(c1,c2) ( ((int)(c1)) + (((int)(c2))<<8) )
57 #define TRIPLET(c1,c2,c3) ( ((int)(c1)) + (((int)(c2))<<8) + (((int)(c3))<<16) )
58
59 struct FaceVertex
60 {
61 MeshIndex vertexId;
62 MeshIndex normalId;
63 MeshIndex uvId;
64 };
65
66 struct FaceData
67 {
68 FaceVertex vertexList[3];
69 MeshIndex materialId;
70 };
71
72 struct MaterialData
73 {
74 std::string mtlName;
75 TEXTURE *texture;
76 };
77
78 /// Fills the buffer with the next word from the current line.
79 /// A trailing null character will be appended.
ReadWord(char * buffer,pov_base::ITextStream * file)80 static bool ReadWord (char *buffer, pov_base::ITextStream *file)
81 {
82 int i;
83 int c;
84 for (i = 0; i < kMaxObjBufferSize-1; ++i)
85 {
86 c = file->getchar();
87 if ((c == EOF) || isspace (c))
88 break;
89 buffer[i] = c;
90 }
91 buffer[i] = '\0';
92 while ((c != '\n') && isspace (c))
93 c = file->getchar();
94 if (c != EOF)
95 file->ungetchar (c);
96 return (buffer[0] != '\0');
97 }
98
HasMoreWords(pov_base::ITextStream * file)99 static bool HasMoreWords (pov_base::ITextStream *file)
100 {
101 int c = file->getchar();
102 file->ungetchar (c);
103 return ((c != '\n') && (c != EOF));
104 }
105
AdvanceLine(pov_base::ITextStream * file)106 static void AdvanceLine (pov_base::ITextStream *file)
107 {
108 int c;
109 do
110 c = file->getchar();
111 while ((c != '\n') && (c != EOF));
112 }
113
ReadFloat(DBL & result,char * buffer,pov_base::ITextStream * file)114 inline static bool ReadFloat (DBL& result, char *buffer, pov_base::ITextStream *file)
115 {
116 if (!ReadWord (buffer, file))
117 return false;
118 if (sscanf (buffer, POV_DBL_FORMAT_STRING, &result) == 0)
119 return false;
120 return true;
121 }
122
ReadFaceVertexField(MeshIndex & result,char ** p)123 inline static bool ReadFaceVertexField (MeshIndex& result, char **p)
124 {
125 POV_LONG temp = 0;
126 do
127 {
128 if (!isdigit (**p))
129 return false;
130 temp = (temp * 10) + (**p - '0');
131 ++(*p);
132 }
133 while ((**p != '\0') && (**p != '/'));
134 if ((temp < 1) || (temp > std::numeric_limits<MeshIndex>::max()))
135 return false;
136 result = (MeshIndex)temp;
137 return true;
138 }
139
ReadFaceVertex(FaceVertex & result,char * buffer,pov_base::ITextStream * file)140 inline static bool ReadFaceVertex (FaceVertex& result, char *buffer, pov_base::ITextStream *file)
141 {
142 if (!ReadWord (buffer, file))
143 return false;
144
145 char *p = buffer;
146 // parse face id
147 result.vertexId = 0;
148 result.uvId = 0;
149 result.normalId = 0;
150 if (!ReadFaceVertexField (result.vertexId, &p))
151 return false;
152 if (*p == '/')
153 {
154 ++p;
155 if (*p != '/')
156 {
157 if (!ReadFaceVertexField (result.uvId, &p))
158 return false;
159 }
160 if (*p == '/')
161 {
162 ++p;
163 if (!ReadFaceVertexField (result.normalId, &p))
164 return false;
165 }
166 }
167 return true;
168 }
169
170
Parse_Obj(Mesh * mesh)171 void Parser::Parse_Obj (Mesh* mesh)
172 {
173 UCS2 *fileName;
174 char *s;
175 UCS2String ign;
176 IStream *stream = nullptr;
177 pov_base::ITextStream *textStream = nullptr;
178 char wordBuffer [kMaxObjBufferSize];
179 std::string materialPrefix;
180 std::string materialSuffix;
181
182 Vector3d insideVector(0.0);
183 Vector3d v3;
184 Vector2d v2;
185 bool foundZeroNormal = false;
186 bool fullyTextured = true;
187 bool havePolygonFaces = false;
188
189 FaceData face;
190 MaterialData material;
191 MATERIAL *povMaterial;
192
193 vector<Vector3d> vertexList;
194 vector<Vector3d> normalList;
195 vector<Vector2d> uvList;
196 vector<FaceData> faceList;
197 vector<FaceData> flatFaceList;
198 vector<MaterialData> materialList;
199 size_t materialId = 0;
200
201 fileName = Parse_String (true);
202
203 EXPECT
204 CASE (TEXTURE_LIST_TOKEN)
205 Parse_Begin();
206 EXPECT
207 CASE5 (STRING_LITERAL_TOKEN,CHR_TOKEN,SUBSTR_TOKEN,STR_TOKEN,VSTR_TOKEN)
208 CASE5 (CONCAT_TOKEN,STRUPR_TOKEN,STRLWR_TOKEN,DATETIME_TOKEN,STRING_ID_TOKEN)
209 UNGET
210 s = Parse_C_String();
211 material.mtlName = s;
212 POV_FREE (s);
213
214 EXPECT_ONE
215 CASE (TEXTURE_TOKEN)
216 Parse_Begin ();
217 material.texture = Parse_Texture();
218 Parse_End ();
219 END_CASE
220 CASE (MATERIAL_TOKEN)
221 povMaterial = Create_Material();
222 Parse_Material (povMaterial);
223 material.texture = Copy_Textures (povMaterial->Texture);
224 Destroy_Material (povMaterial);
225 END_CASE
226 OTHERWISE
227 Expectation_Error ("texture or material");
228 END_CASE
229 END_EXPECT
230
231 Post_Textures(material.texture);
232 materialList.push_back (material);
233 END_CASE
234 CASE (PREFIX_TOKEN)
235 s = Parse_C_String();
236 materialPrefix = s;
237 POV_FREE (s);
238 END_CASE
239 CASE (SUFFIX_TOKEN)
240 s = Parse_C_String();
241 materialSuffix = s;
242 POV_FREE (s);
243 END_CASE
244 OTHERWISE
245 UNGET
246 EXIT
247 END_CASE
248 END_EXPECT
249 Parse_End();
250 END_CASE
251
252 CASE (INSIDE_VECTOR_TOKEN)
253 Parse_Vector (insideVector);
254 END_CASE
255
256 OTHERWISE
257 UNGET
258 EXIT
259 END_CASE
260 END_EXPECT
261
262 // open obj file
263
264 stream = Locate_File (fileName, POV_File_Text_OBJ, ign, true);
265 if (stream)
266 {
267 textStream = new IBufferedTextStream (fileName, stream);
268 if (!textStream)
269 delete stream;
270 }
271 if (!textStream)
272 Error ("Cannot open obj file %s.", UCS2toASCIIString(fileName).c_str());
273
274 // mark end of buffer with a non-null character to identify situations where a word may not have fit entirely inside the buffer.
275 wordBuffer[kMaxObjBufferSize-1] = '*';
276
277 while (!textStream->eof())
278 {
279 (void)ReadWord (wordBuffer, textStream);
280 if (wordBuffer[0] == '\0')
281 wordBuffer[1] = '\0';
282 if (wordBuffer[1] == '\0')
283 wordBuffer[2] = '\0';
284
285 bool unsupportedCmd = false;
286 bool skipLine = false;
287
288 switch (TRIPLET (wordBuffer[0], wordBuffer[1], wordBuffer[2]))
289 {
290 case '\0': // empty line
291 case '#': // comment
292 case 'g': // group ("g NAME")
293 case 'o': // object name ("o NAME")
294 skipLine = true;
295 break;
296
297 case 'f': // face ("f VERTEXID VERTEXID VERTEXID ...")
298 {
299 face.materialId = materialId;
300 if (materialId == 0)
301 fullyTextured = false;
302 int haveVertices = 0;
303 int haveUV = 0;
304 int haveNormal = 0;
305 int vertex = 0;
306 while (HasMoreWords (textStream))
307 {
308 if (!ReadFaceVertex (face.vertexList[vertex], wordBuffer, textStream))
309 Error ("Invalid or unsupported face index data '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
310 if (face.vertexList[vertex].vertexId > vertexList.size())
311 Error ("Vertex index out of range in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
312 if (face.vertexList[vertex].uvId > uvList.size())
313 Error ("UV index out of range in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
314 if (face.vertexList[vertex].normalId > normalList.size())
315 Error ("Normal index out of range in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
316 ++haveVertices;
317 if (face.vertexList[vertex].uvId > 0)
318 ++haveUV;
319 if (face.vertexList[vertex].normalId > 0)
320 ++haveNormal;
321
322 if (haveVertices >= 3)
323 {
324 if (haveNormal == haveVertices)
325 faceList.push_back (face);
326 else
327 flatFaceList.push_back (face);
328 face.vertexList[1] = face.vertexList[2];
329 }
330 else
331 ++vertex;
332 }
333 if ((haveVertices > 3) && !havePolygonFaces)
334 {
335 Warning ("Non-triangular faces found in obj file %s. Faces will only import properly if they are convex and planar.", UCS2toASCIIString(fileName).c_str());
336 havePolygonFaces = true;
337 }
338 if (haveVertices < 3)
339 Error ("Insufficient number of vertices per face in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
340 if ((haveUV != 0) && (haveUV != haveVertices))
341 Error ("Inconsistent use of UV indices in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
342 if ((haveNormal != 0) && (haveNormal != haveVertices))
343 Error ("Inconsistent use of normal indices in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
344 if (haveNormal > 0)
345 faceList.push_back (face);
346 else
347 flatFaceList.push_back (face);
348 }
349 break;
350
351 case TRIPLET('m','t','l'): // presumably material library ("mtllib FILE FILE ...")
352 if (strcmp (wordBuffer, "mtllib") == 0)
353 {
354 // TODO
355 unsupportedCmd = true;
356 }
357 else
358 unsupportedCmd = true;
359 break;
360
361 case TRIPLET('u','s','e'): // presumably material selection ("usemtl NAME")
362 if (strcmp (wordBuffer, "usemtl") == 0)
363 {
364 if (!ReadWord (wordBuffer, textStream))
365 Error ("Invalid material name '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
366 for (materialId = 0; materialId < materialList.size(); ++materialId)
367 {
368 if (materialList[materialId].mtlName.compare (wordBuffer) == 0)
369 break;
370 }
371 if (materialId == materialList.size())
372 {
373 material.mtlName = wordBuffer;
374 material.texture = nullptr;
375 std::string identifier = materialPrefix + std::string(wordBuffer) + materialSuffix;
376 SYM_ENTRY *symbol = Find_Symbol (identifier.c_str());
377 if (symbol == nullptr)
378 Error ("No matching texture for obj file material '%s': Identifier '%s' not found.", wordBuffer, identifier.c_str());
379 else if (symbol->Token_Number == TEXTURE_ID_TOKEN)
380 material.texture = Copy_Textures(reinterpret_cast<TEXTURE *>(symbol->Data));
381 else if (symbol->Token_Number == MATERIAL_ID_TOKEN)
382 material.texture = Copy_Textures(reinterpret_cast<MATERIAL *>(symbol->Data)->Texture);
383 else
384 Error ("No matching texture for obj file material '%s': Identifier '%s' is not a texture or material.", wordBuffer, identifier.c_str());
385 Post_Textures (material.texture);
386 materialList.push_back (material);
387 }
388 materialId ++;
389 }
390 else
391 unsupportedCmd = true;
392 break;
393
394 case 'v': // vertex XYZ coordinates ("v FLOAT FLOAT FLOAT")
395 for (int dimension = X; dimension <= Z; ++dimension)
396 {
397 if (!ReadFloat (v3[dimension], wordBuffer, textStream))
398 Error ("Invalid coordinate value '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
399 }
400 vertexList.push_back (v3);
401 break;
402
403 case PAIR('v','n'): // vertex normal vector ("vn FLOAT FLOAT FLOAT")
404 for (int dimension = X; dimension <= Z; ++dimension)
405 {
406 if (!ReadFloat (v3[dimension], wordBuffer, textStream))
407 Error ("Invalid coordinate value '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
408 }
409 normalList.push_back (v3);
410 break;
411
412 case PAIR('v','t'): // vertex UV coordinates ("vt FLOAT FLOAT")
413 for (int dimension = X; dimension <= Y; ++dimension)
414 {
415 if (!ReadFloat (v2[dimension], wordBuffer, textStream))
416 Error ("Invalid coordinate value '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
417 }
418 uvList.push_back (v2);
419 break;
420
421 default:
422 unsupportedCmd = true;
423 break;
424 }
425
426 if (unsupportedCmd)
427 {
428 Warning("Unsupported command '%s' skipped in obj file %s line %i.", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
429 skipLine = true;
430 }
431
432 if (!skipLine && HasMoreWords (textStream))
433 PossibleError("Unexpected extra data skipped in obj file %s line %i.", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
434
435 // skip remainder of line
436 AdvanceLine (textStream);
437
438 if (wordBuffer[kMaxObjBufferSize-1] == '\0')
439 PossibleError("Excessively long data in obj file %s line %i.", UCS2toASCIIString(fileName).c_str(), (int)textStream->line());
440 }
441
442 // close obj file
443 delete textStream;
444
445 size_t smoothFaces = faceList.size();
446 faceList.insert (faceList.end(), flatFaceList.begin(), flatFaceList.end());
447
448 MeshVector *vertexArray = nullptr;
449 MeshVector *normalArray = nullptr;
450 MeshUVVector *uvArray = nullptr;
451 TEXTURE **textureArray = nullptr;
452 MESH_TRIANGLE *triangleArray = nullptr;
453
454 if (vertexList.empty())
455 Error ("No vertices in obj file.");
456 else if (vertexList.size() >= std::numeric_limits<int>::max())
457 Error ("Too many UV vectors in obj file.");
458
459 vertexArray = reinterpret_cast<MeshVector *>(POV_MALLOC(vertexList.size()*sizeof(MeshVector), "triangle mesh data"));
460 for (size_t i = 0; i < vertexList.size(); ++i)
461 vertexArray[i] = MeshVector (vertexList[i]);
462
463 if (!normalList.empty())
464 {
465 if (normalList.size() >= std::numeric_limits<int>::max())
466 Error ("Too many normal vectors in obj file.");
467
468 normalArray = reinterpret_cast<MeshVector *>(POV_MALLOC((normalList.size()+faceList.size())*sizeof(MeshVector), "triangle mesh data"));
469 for (size_t i = 0; i < normalList.size(); ++i)
470 {
471 Vector3d& n = normalList[i];
472 if ((fabs(n.x()) < EPSILON) && (fabs(n.x()) < EPSILON) && (fabs(n.z()) < EPSILON))
473 {
474 n.x() = 1.0; // make it nonzero
475 if (!foundZeroNormal)
476 Warning("Normal vector in mesh2 cannot be zero - changing it to <1,0,0>.");
477 foundZeroNormal = true;
478 }
479 normalArray[i] = MeshVector(n);
480 }
481 }
482
483 // make sure we at least have one UV coordinate
484 if (uvList.empty())
485 uvList.push_back (Vector2d(0.0, 0.0));
486 else if (uvList.size() >= std::numeric_limits<int>::max())
487 Error ("Too many UV vectors in obj file.");
488
489 uvArray = reinterpret_cast<MeshUVVector *>(POV_MALLOC(uvList.size() *sizeof(MeshUVVector), "triangle mesh data"));
490 for (size_t i = 0; i < uvList.size(); ++i)
491 uvArray[i] = MeshUVVector(uvList[i]);
492
493 if (!materialList.empty())
494 {
495 if (materialList.size() >= std::numeric_limits<int>::max())
496 Error ("Too many materials in obj file.");
497
498 textureArray = reinterpret_cast<TEXTURE **>(POV_MALLOC(materialList.size() *sizeof(TEXTURE*), "triangle mesh data"));
499 for (size_t i = 0; i < materialList.size(); ++i)
500 textureArray[i] = materialList[i].texture;
501 }
502
503 if (faceList.empty())
504 Error ("No faces in obj file.");
505
506 triangleArray = reinterpret_cast<MESH_TRIANGLE *>(POV_MALLOC(faceList.size()*sizeof(MESH_TRIANGLE), "triangle mesh data"));
507 for (size_t i = 0, j = normalList.size(); i < faceList.size(); ++i, ++j)
508 {
509 const FaceData& objTriangle = faceList[i];
510 MESH_TRIANGLE& triangle = triangleArray[i];
511 mesh->Init_Mesh_Triangle (&triangle);
512 triangle.P1 = objTriangle.vertexList[0].vertexId - 1;
513 triangle.P2 = objTriangle.vertexList[1].vertexId - 1;
514 triangle.P3 = objTriangle.vertexList[2].vertexId - 1;
515 triangle.Texture = objTriangle.materialId - 1;
516 triangle.UV1 = max(1, objTriangle.vertexList[0].uvId) - 1;
517 triangle.UV2 = max(1, objTriangle.vertexList[1].uvId) - 1;
518 triangle.UV3 = max(1, objTriangle.vertexList[2].uvId) - 1;
519 triangle.Smooth = (i < smoothFaces);
520 const Vector3d& P1 = vertexList[triangle.P1];
521 const Vector3d& P2 = vertexList[triangle.P2];
522 const Vector3d& P3 = vertexList[triangle.P3];
523 Vector3d N;
524 if (triangle.Smooth)
525 {
526 triangle.N1 = objTriangle.vertexList[0].normalId - 1;
527 triangle.N2 = objTriangle.vertexList[1].normalId - 1;
528 triangle.N3 = objTriangle.vertexList[2].normalId - 1;
529 Vector3d& N1 = normalList[triangle.N1];
530 Vector3d& N2 = normalList[triangle.N2];
531 Vector3d& N3 = normalList[triangle.N3];
532
533 // check for equal normals
534 Vector3d D1 = N1 - N2;
535 Vector3d D2 = N1 - N3;
536 double l1 = D1.lengthSqr();
537 double l2 = D2.lengthSqr();
538 triangle.Smooth = ((fabs(l1) > EPSILON) || (fabs(l2) > EPSILON));
539 }
540 mesh->Compute_Mesh_Triangle (&triangle, triangle.Smooth, P1, P2, P3, N);
541 triangle.Normal_Ind = j;
542 normalArray[j] = MeshVector(N);
543 }
544
545 if (fullyTextured)
546 mesh->Type |= TEXTURED_OBJECT;
547
548 mesh->Data = reinterpret_cast<MESH_DATA *>(POV_MALLOC(sizeof(MESH_DATA), "triangle mesh data"));
549 mesh->Data->References = 1;
550 mesh->Data->Tree = nullptr;
551
552 mesh->has_inside_vector = insideVector.IsNearNull (EPSILON);
553 if (mesh->has_inside_vector)
554 {
555 mesh->Data->Inside_Vect = insideVector.normalized();
556 mesh->Type &= ~PATCH_OBJECT;
557 }
558 else
559 {
560 mesh->Type |= PATCH_OBJECT;
561 }
562
563 mesh->Data->Normals = normalArray;
564 mesh->Data->Triangles = triangleArray;
565 mesh->Data->Vertices = vertexArray;
566 mesh->Data->UVCoords = uvArray;
567 mesh->Textures = textureArray;
568
569 /* copy number of for normals, textures, triangles and vertices. */
570 mesh->Data->Number_Of_Normals = normalList.size() + faceList.size();
571 mesh->Data->Number_Of_Triangles = faceList.size();
572 mesh->Data->Number_Of_Vertices = vertexList.size();
573 mesh->Data->Number_Of_UVCoords = uvList.size();
574 mesh->Number_Of_Textures = materialList.size();
575
576 if (!materialList.empty())
577 Set_Flag(mesh, MULTITEXTURE_FLAG);
578 }
579
580 }
581
582 #endif // POV_PARSER_EXPERIMENTAL_OBJ_IMPORT
583