1 | /* |
2 | Open Asset Import Library (assimp) |
3 | ---------------------------------------------------------------------- |
4 | |
5 | Copyright (c) 2006-2017, assimp team |
6 | |
7 | All rights reserved. |
8 | |
9 | Redistribution and use of this software in source and binary forms, |
10 | with or without modification, are permitted provided that the |
11 | following conditions are met: |
12 | |
13 | * Redistributions of source code must retain the above |
14 | copyright notice, this list of conditions and the |
15 | following disclaimer. |
16 | |
17 | * Redistributions in binary form must reproduce the above |
18 | copyright notice, this list of conditions and the |
19 | following disclaimer in the documentation and/or other |
20 | materials provided with the distribution. |
21 | |
22 | * Neither the name of the assimp team, nor the names of its |
23 | contributors may be used to endorse or promote products |
24 | derived from this software without specific prior |
25 | written permission of the assimp team. |
26 | |
27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
31 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
38 | |
39 | ---------------------------------------------------------------------- |
40 | */ |
41 | |
42 | |
43 | |
44 | #if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) |
45 | |
46 | #include "PlyExporter.h" |
47 | #include <memory> |
48 | #include <cmath> |
49 | #include "Exceptional.h" |
50 | #include <assimp/scene.h> |
51 | #include <assimp/version.h> |
52 | #include <assimp/IOSystem.hpp> |
53 | #include <assimp/Exporter.hpp> |
54 | #include "qnan.h" |
55 | |
56 | |
57 | //using namespace Assimp; |
58 | namespace Assimp { |
59 | |
60 | // make sure type_of returns consistent output across different platforms |
61 | // also consider using: typeid(VAR).name() |
62 | template <typename T> const char* type_of(T&) { return "unknown" ; } |
63 | template<> const char* type_of(float&) { return "float" ; } |
64 | template<> const char* type_of(double&) { return "double" ; } |
65 | |
66 | // ------------------------------------------------------------------------------------------------ |
67 | // Worker function for exporting a scene to PLY. Prototyped and registered in Exporter.cpp |
68 | void ExportScenePly(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) |
69 | { |
70 | // invoke the exporter |
71 | PlyExporter exporter(pFile, pScene); |
72 | |
73 | if (exporter.mOutput.fail()) { |
74 | throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); |
75 | } |
76 | |
77 | // we're still here - export successfully completed. Write the file. |
78 | std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt" )); |
79 | if(outfile == NULL) { |
80 | throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); |
81 | } |
82 | |
83 | outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); |
84 | } |
85 | |
86 | void ExportScenePlyBinary(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) |
87 | { |
88 | // invoke the exporter |
89 | PlyExporter exporter(pFile, pScene, true); |
90 | |
91 | // we're still here - export successfully completed. Write the file. |
92 | std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb" )); |
93 | if (outfile == NULL) { |
94 | throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); |
95 | } |
96 | |
97 | outfile->Write(exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()), 1); |
98 | } |
99 | |
100 | #define PLY_EXPORT_HAS_NORMALS 0x1 |
101 | #define PLY_EXPORT_HAS_TANGENTS_BITANGENTS 0x2 |
102 | #define PLY_EXPORT_HAS_TEXCOORDS 0x4 |
103 | #define PLY_EXPORT_HAS_COLORS (PLY_EXPORT_HAS_TEXCOORDS << AI_MAX_NUMBER_OF_TEXTURECOORDS) |
104 | |
105 | // ------------------------------------------------------------------------------------------------ |
106 | PlyExporter::PlyExporter(const char* _filename, const aiScene* pScene, bool binary) |
107 | : filename(_filename) |
108 | , endl("\n" ) |
109 | { |
110 | // make sure that all formatting happens using the standard, C locale and not the user's current locale |
111 | const std::locale& l = std::locale("C" ); |
112 | mOutput.imbue(l); |
113 | mOutput.precision(16); |
114 | |
115 | unsigned int faces = 0u, vertices = 0u, components = 0u; |
116 | for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
117 | const aiMesh& m = *pScene->mMeshes[i]; |
118 | faces += m.mNumFaces; |
119 | vertices += m.mNumVertices; |
120 | |
121 | if (m.HasNormals()) { |
122 | components |= PLY_EXPORT_HAS_NORMALS; |
123 | } |
124 | if (m.HasTangentsAndBitangents()) { |
125 | components |= PLY_EXPORT_HAS_TANGENTS_BITANGENTS; |
126 | } |
127 | for (unsigned int t = 0; m.HasTextureCoords(t); ++t) { |
128 | components |= PLY_EXPORT_HAS_TEXCOORDS << t; |
129 | } |
130 | for (unsigned int t = 0; m.HasVertexColors(t); ++t) { |
131 | components |= PLY_EXPORT_HAS_COLORS << t; |
132 | } |
133 | } |
134 | |
135 | mOutput << "ply" << endl; |
136 | if (binary) { |
137 | #if (defined AI_BUILD_BIG_ENDIAN) |
138 | mOutput << "format binary_big_endian 1.0" << endl; |
139 | #else |
140 | mOutput << "format binary_little_endian 1.0" << endl; |
141 | #endif |
142 | } |
143 | else { |
144 | mOutput << "format ascii 1.0" << endl; |
145 | } |
146 | mOutput << "comment Created by Open Asset Import Library - http://assimp.sf.net (v" |
147 | << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.' |
148 | << aiGetVersionRevision() << ")" << endl; |
149 | |
150 | // TODO: probably want to check here rather than just assume something |
151 | // definitely not good to always write float even if we might have double precision |
152 | |
153 | ai_real tmp = 0.0; |
154 | const char * typeName = type_of(tmp); |
155 | |
156 | mOutput << "element vertex " << vertices << endl; |
157 | mOutput << "property " << typeName << " x" << endl; |
158 | mOutput << "property " << typeName << " y" << endl; |
159 | mOutput << "property " << typeName << " z" << endl; |
160 | |
161 | if(components & PLY_EXPORT_HAS_NORMALS) { |
162 | mOutput << "property " << typeName << " nx" << endl; |
163 | mOutput << "property " << typeName << " ny" << endl; |
164 | mOutput << "property " << typeName << " nz" << endl; |
165 | } |
166 | |
167 | // write texcoords first, just in case an importer does not support tangents |
168 | // bitangents and just skips over the rest of the line upon encountering |
169 | // unknown fields (Ply leaves pretty much every vertex component open, |
170 | // but in reality most importers only know about vertex positions, normals |
171 | // and texture coordinates). |
172 | for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
173 | if (!c) { |
174 | mOutput << "property " << typeName << " s" << endl; |
175 | mOutput << "property " << typeName << " t" << endl; |
176 | } |
177 | else { |
178 | mOutput << "property " << typeName << " s" << c << endl; |
179 | mOutput << "property " << typeName << " t" << c << endl; |
180 | } |
181 | } |
182 | |
183 | for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
184 | if (!c) { |
185 | mOutput << "property " << typeName << " r" << endl; |
186 | mOutput << "property " << typeName << " g" << endl; |
187 | mOutput << "property " << typeName << " b" << endl; |
188 | mOutput << "property " << typeName << " a" << endl; |
189 | } |
190 | else { |
191 | mOutput << "property " << typeName << " r" << c << endl; |
192 | mOutput << "property " << typeName << " g" << c << endl; |
193 | mOutput << "property " << typeName << " b" << c << endl; |
194 | mOutput << "property " << typeName << " a" << c << endl; |
195 | } |
196 | } |
197 | |
198 | if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
199 | mOutput << "property " << typeName << " tx" << endl; |
200 | mOutput << "property " << typeName << " ty" << endl; |
201 | mOutput << "property " << typeName << " tz" << endl; |
202 | mOutput << "property " << typeName << " bx" << endl; |
203 | mOutput << "property " << typeName << " by" << endl; |
204 | mOutput << "property " << typeName << " bz" << endl; |
205 | } |
206 | |
207 | mOutput << "element face " << faces << endl; |
208 | |
209 | // uchar seems to be the most common type for the number of indices per polygon and int seems to be most common for the vertex indices. |
210 | // For instance, MeshLab fails to load meshes in which both types are uint. Houdini seems to have problems as well. |
211 | // Obviously, using uchar will not work for meshes with polygons with more than 255 indices, but how realistic is this case? |
212 | mOutput << "property list uchar int vertex_index" << endl; |
213 | |
214 | mOutput << "end_header" << endl; |
215 | |
216 | for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
217 | if (binary) { |
218 | WriteMeshVertsBinary(pScene->mMeshes[i], components); |
219 | } |
220 | else { |
221 | WriteMeshVerts(pScene->mMeshes[i], components); |
222 | } |
223 | } |
224 | for (unsigned int i = 0, ofs = 0; i < pScene->mNumMeshes; ++i) { |
225 | if (binary) { |
226 | WriteMeshIndicesBinary(pScene->mMeshes[i], ofs); |
227 | } |
228 | else { |
229 | WriteMeshIndices(pScene->mMeshes[i], ofs); |
230 | } |
231 | ofs += pScene->mMeshes[i]->mNumVertices; |
232 | } |
233 | } |
234 | |
235 | // ------------------------------------------------------------------------------------------------ |
236 | PlyExporter::~PlyExporter() { |
237 | // empty |
238 | } |
239 | |
240 | // ------------------------------------------------------------------------------------------------ |
241 | void PlyExporter::WriteMeshVerts(const aiMesh* m, unsigned int components) |
242 | { |
243 | static const ai_real inf = std::numeric_limits<ai_real>::infinity(); |
244 | |
245 | // If a component (for instance normal vectors) is present in at least one mesh in the scene, |
246 | // then default values are written for meshes that do not contain this component. |
247 | for (unsigned int i = 0; i < m->mNumVertices; ++i) { |
248 | mOutput << |
249 | m->mVertices[i].x << " " << |
250 | m->mVertices[i].y << " " << |
251 | m->mVertices[i].z |
252 | ; |
253 | if(components & PLY_EXPORT_HAS_NORMALS) { |
254 | if (m->HasNormals() && is_not_qnan(m->mNormals[i].x) && std::fabs(m->mNormals[i].x) != inf) { |
255 | mOutput << |
256 | " " << m->mNormals[i].x << |
257 | " " << m->mNormals[i].y << |
258 | " " << m->mNormals[i].z; |
259 | } |
260 | else { |
261 | mOutput << " 0.0 0.0 0.0" ; |
262 | } |
263 | } |
264 | |
265 | for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
266 | if (m->HasTextureCoords(c)) { |
267 | mOutput << |
268 | " " << m->mTextureCoords[c][i].x << |
269 | " " << m->mTextureCoords[c][i].y; |
270 | } |
271 | else { |
272 | mOutput << " -1.0 -1.0" ; |
273 | } |
274 | } |
275 | |
276 | for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
277 | if (m->HasVertexColors(c)) { |
278 | mOutput << |
279 | " " << m->mColors[c][i].r << |
280 | " " << m->mColors[c][i].g << |
281 | " " << m->mColors[c][i].b << |
282 | " " << m->mColors[c][i].a; |
283 | } |
284 | else { |
285 | mOutput << " -1.0 -1.0 -1.0 -1.0" ; |
286 | } |
287 | } |
288 | |
289 | if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
290 | if (m->HasTangentsAndBitangents()) { |
291 | mOutput << |
292 | " " << m->mTangents[i].x << |
293 | " " << m->mTangents[i].y << |
294 | " " << m->mTangents[i].z << |
295 | " " << m->mBitangents[i].x << |
296 | " " << m->mBitangents[i].y << |
297 | " " << m->mBitangents[i].z |
298 | ; |
299 | } |
300 | else { |
301 | mOutput << " 0.0 0.0 0.0 0.0 0.0 0.0" ; |
302 | } |
303 | } |
304 | |
305 | mOutput << endl; |
306 | } |
307 | } |
308 | |
309 | // ------------------------------------------------------------------------------------------------ |
310 | void PlyExporter::WriteMeshVertsBinary(const aiMesh* m, unsigned int components) |
311 | { |
312 | // If a component (for instance normal vectors) is present in at least one mesh in the scene, |
313 | // then default values are written for meshes that do not contain this component. |
314 | aiVector3D defaultNormal(0, 0, 0); |
315 | aiVector2D defaultUV(-1, -1); |
316 | aiColor4D defaultColor(-1, -1, -1, -1); |
317 | for (unsigned int i = 0; i < m->mNumVertices; ++i) { |
318 | mOutput.write(reinterpret_cast<const char*>(&m->mVertices[i].x), 12); |
319 | if (components & PLY_EXPORT_HAS_NORMALS) { |
320 | if (m->HasNormals()) { |
321 | mOutput.write(reinterpret_cast<const char*>(&m->mNormals[i].x), 12); |
322 | } |
323 | else { |
324 | mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
325 | } |
326 | } |
327 | |
328 | for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
329 | if (m->HasTextureCoords(c)) { |
330 | mOutput.write(reinterpret_cast<const char*>(&m->mTextureCoords[c][i].x), 8); |
331 | } |
332 | else { |
333 | mOutput.write(reinterpret_cast<const char*>(&defaultUV.x), 8); |
334 | } |
335 | } |
336 | |
337 | for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
338 | if (m->HasVertexColors(c)) { |
339 | mOutput.write(reinterpret_cast<const char*>(&m->mColors[c][i].r), 16); |
340 | } |
341 | else { |
342 | mOutput.write(reinterpret_cast<const char*>(&defaultColor.r), 16); |
343 | } |
344 | } |
345 | |
346 | if (components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
347 | if (m->HasTangentsAndBitangents()) { |
348 | mOutput.write(reinterpret_cast<const char*>(&m->mTangents[i].x), 12); |
349 | mOutput.write(reinterpret_cast<const char*>(&m->mBitangents[i].x), 12); |
350 | } |
351 | else { |
352 | mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
353 | mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
354 | } |
355 | } |
356 | } |
357 | } |
358 | |
359 | // ------------------------------------------------------------------------------------------------ |
360 | void PlyExporter::WriteMeshIndices(const aiMesh* m, unsigned int offset) |
361 | { |
362 | for (unsigned int i = 0; i < m->mNumFaces; ++i) { |
363 | const aiFace& f = m->mFaces[i]; |
364 | mOutput << f.mNumIndices << " " ; |
365 | for(unsigned int c = 0; c < f.mNumIndices; ++c) { |
366 | mOutput << (f.mIndices[c] + offset) << (c == f.mNumIndices-1 ? endl : " " ); |
367 | } |
368 | } |
369 | } |
370 | |
371 | // Generic method in case we want to use different data types for the indices or make this configurable. |
372 | template<typename NumIndicesType, typename IndexType> |
373 | void WriteMeshIndicesBinary_Generic(const aiMesh* m, unsigned int offset, std::ostringstream& output) |
374 | { |
375 | for (unsigned int i = 0; i < m->mNumFaces; ++i) { |
376 | const aiFace& f = m->mFaces[i]; |
377 | NumIndicesType numIndices = static_cast<NumIndicesType>(f.mNumIndices); |
378 | output.write(reinterpret_cast<const char*>(&numIndices), sizeof(NumIndicesType)); |
379 | for (unsigned int c = 0; c < f.mNumIndices; ++c) { |
380 | IndexType index = f.mIndices[c] + offset; |
381 | output.write(reinterpret_cast<const char*>(&index), sizeof(IndexType)); |
382 | } |
383 | } |
384 | } |
385 | |
386 | void PlyExporter::WriteMeshIndicesBinary(const aiMesh* m, unsigned int offset) |
387 | { |
388 | WriteMeshIndicesBinary_Generic<unsigned char, int>(m, offset, mOutput); |
389 | } |
390 | |
391 | } // end of namespace Assimp |
392 | |
393 | #endif // !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) |
394 | |