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