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