1 //-----------------------------------------------------------------------------
2 // Product:     OpenCTM tools
3 // File:        off.cpp
4 // Description: Implementation of the OFF file format importer/exporter.
5 //-----------------------------------------------------------------------------
6 // Copyright (c) 2009-2010 Marcus Geelnard
7 //
8 // This software is provided 'as-is', without any express or implied
9 // warranty. In no event will the authors be held liable for any damages
10 // arising from the use of this software.
11 //
12 // Permission is granted to anyone to use this software for any purpose,
13 // including commercial applications, and to alter it and redistribute it
14 // freely, subject to the following restrictions:
15 //
16 //     1. The origin of this software must not be misrepresented; you must not
17 //     claim that you wrote the original software. If you use this software
18 //     in a product, an acknowledgment in the product documentation would be
19 //     appreciated but is not required.
20 //
21 //     2. Altered source versions must be plainly marked as such, and must not
22 //     be misrepresented as being the original software.
23 //
24 //     3. This notice may not be removed or altered from any source
25 //     distribution.
26 //-----------------------------------------------------------------------------
27 
28 //-----------------------------------------------------------------------------
29 // The "Object File Format" (OFF) is, among other things, used by the Princeton
30 // Shape Benchmark data set (http://shape.cs.princeton.edu/benchmark). The file
31 // format specification can be found here:
32 // http://people.sc.fsu.edu/~burkardt/data/off/off.html
33 //-----------------------------------------------------------------------------
34 
35 #include <stdexcept>
36 #include <fstream>
37 #include <iomanip>
38 #include <string>
39 #include <sstream>
40 #include <vector>
41 #include <list>
42 #include "off.h"
43 #include "common.h"
44 
45 using namespace std;
46 
47 // Read the next line in a file (skip comments and empty lines)
ReadNextLine(ifstream & aStream,string & aResult,string & aComment)48 static void ReadNextLine(ifstream &aStream, string &aResult, string &aComment)
49 {
50   while(true)
51   {
52     // Read another line from the file
53     string line;
54     getline(aStream, line);
55 
56     // Check for comment
57     size_t commentPos = line.find('#');
58     if(commentPos != string::npos)
59     {
60       string lineComment = TrimString(line.substr(commentPos + 1));
61       if(lineComment.size() > 0)
62       {
63         if(aComment.size() > 0)
64           aComment = aComment + string(" ") + lineComment;
65         else
66           aComment = lineComment;
67       }
68       line = line.substr(0, commentPos);
69     }
70 
71     // Trim the string
72     aResult = TrimString(line);
73 
74     // Non-empty line?
75     if((aResult.size() > 0) || aStream.eof())
76       return;
77   }
78 }
79 
80 // Parse a 7 x float string as a Vector3 and a Vector4 element
ParseVeretex(const string aString,Vector3 * aCoord,Vector4 * aColor)81 static void ParseVeretex(const string aString, Vector3 * aCoord, Vector4 * aColor)
82 {
83   istringstream sstr(aString);
84   sstr >> aCoord->x;
85   sstr >> aCoord->y;
86   sstr >> aCoord->z;
87   sstr >> aColor->x;
88   sstr >> aColor->y;
89   sstr >> aColor->z;
90   sstr >> aColor->w;
91 }
92 
93 /// Import a mesh from an OFF file.
Import_OFF(const char * aFileName,Mesh * aMesh)94 void Import_OFF(const char * aFileName, Mesh * aMesh)
95 {
96   // Clear the mesh
97   aMesh->Clear();
98 
99   // Open the input file
100   ifstream f(aFileName, ios_base::in);
101   if(f.fail())
102     throw runtime_error("Could not open input file.");
103 
104   // Some state variables that we need...
105   unsigned int numVertices;
106   unsigned int numFaces;
107   string line, comment;
108   istringstream sstr;
109 
110   // Read header
111   ReadNextLine(f, line, comment);
112   if(line != string("OFF"))
113     throw runtime_error("Not a valid OFF format file (missing OFF signature).");
114   ReadNextLine(f, line, comment);
115   sstr.clear();
116   sstr.str(line);
117   sstr >> numVertices;
118   sstr >> numFaces;
119   if(numVertices < 1)
120     throw runtime_error("Not a valid OFF format file (bad vertex count).");
121   if(numFaces < 1)
122     throw runtime_error("Not a valid OFF format file (bad face count).");
123 
124   // Read vertices
125   aMesh->mVertices.resize(numVertices);
126   aMesh->mColors.resize(numVertices);
127   for(unsigned int i = 0; i < numVertices; ++ i)
128   {
129     ReadNextLine(f, line, comment);
130     ParseVeretex(line, &aMesh->mVertices[i], &aMesh->mColors[i]);
131   }
132 
133   // Check if there were vertex colors
134   bool hasVertexColors = false;
135   Vector4 firstColor = aMesh->mColors[0];
136   for(unsigned int i = 1; i < numVertices; ++ i)
137   {
138     if((aMesh->mColors[i].x != firstColor.x) || (aMesh->mColors[i].y != firstColor.y) ||
139        (aMesh->mColors[i].z != firstColor.z) || (aMesh->mColors[i].w != firstColor.w))
140     {
141       hasVertexColors = true;
142       break;
143     }
144   }
145   if(!hasVertexColors)
146     aMesh->mColors.clear();
147 
148   // Read faces
149   list<unsigned int> indices;
150   unsigned int idx[3];
151   for(unsigned int i = 0; i < numFaces; ++ i)
152   {
153     ReadNextLine(f, line, comment);
154     sstr.clear();
155     sstr.str(line);
156     int nodeCount;
157     sstr >> nodeCount;
158     if(nodeCount >= 3)
159     {
160       sstr >> idx[0];
161       sstr >> idx[1];
162       sstr >> idx[2];
163       nodeCount -= 3;
164       while(nodeCount >= 0)
165       {
166         indices.push_back(idx[0]);
167         indices.push_back(idx[1]);
168         indices.push_back(idx[2]);
169         if(nodeCount > 0)
170         {
171           idx[1] = idx[2];
172           sstr >> idx[2];
173         }
174         -- nodeCount;
175       }
176     }
177   }
178 
179   // Copy triangle indices from the read index list to the mesh index vector
180   aMesh->mIndices.resize(indices.size());
181   unsigned int j = 0;
182   for(list<unsigned int>::iterator i = indices.begin(); i != indices.end(); ++ i)
183   {
184     aMesh->mIndices[j] = (*i);
185     ++ j;
186   }
187 
188   // Close the input file
189   f.close();
190 
191   // Did we get a comment?
192   if(comment.size() > 0)
193     aMesh->mComment = comment;
194 }
195 
196 /// Export a mesh to an OFF file.
Export_OFF(const char * aFileName,Mesh * aMesh,Options & aOptions)197 void Export_OFF(const char * aFileName, Mesh * aMesh, Options &aOptions)
198 {
199   // Open the output file
200   ofstream f(aFileName, ios_base::out);
201   if(f.fail())
202     throw runtime_error("Could not open output file.");
203 
204   // Mesh information
205   unsigned int numVertices = (unsigned int) aMesh->mVertices.size();
206   unsigned int numFaces = (unsigned int) aMesh->mIndices.size() / 3;
207 
208   // Set floating point precision
209   f << setprecision(8);
210 
211   // Write OFF file header ID
212   f << "OFF" << endl;
213 
214   // Write comment
215   if(aMesh->mComment.size() > 0)
216   {
217     stringstream sstr(aMesh->mComment);
218     sstr.seekg(0);
219     while(!sstr.eof())
220     {
221       string line;
222       getline(sstr, line);
223       line = TrimString(line);
224       if(line.size() > 0)
225         f << "# " << line << endl;
226     }
227   }
228   f << endl;
229 
230   // Write mesh information
231   f << numVertices << " " << numFaces << " 0" << endl;
232 
233   // Write vertices
234   bool exportVertexColors = !aOptions.mNoColors && aMesh->HasColors();
235   for(unsigned int i = 0; i < numVertices; ++ i)
236   {
237     f << aMesh->mVertices[i].x << " " << aMesh->mVertices[i].y << " " << aMesh->mVertices[i].z;
238     if(exportVertexColors)
239       f << " " << aMesh->mColors[i].x << " " << aMesh->mColors[i].y << " " << aMesh->mColors[i].z << " " << aMesh->mColors[i].w;
240     f << endl;
241   }
242 
243   // Write faces
244   for(unsigned int i = 0; i < numFaces; ++ i)
245   {
246     f << "3 " << aMesh->mIndices[i * 3] << " " <<
247                  aMesh->mIndices[i * 3 + 1] << " " <<
248                  aMesh->mIndices[i * 3 + 2] << endl;
249   }
250 
251   // Close the output file
252   f.close();
253 }
254