1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following 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
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF 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;
58namespace Assimp {
59
60// make sure type_of returns consistent output across different platforms
61// also consider using: typeid(VAR).name()
62template <typename T> const char* type_of(T&) { return "unknown"; }
63template<> const char* type_of(float&) { return "float"; }
64template<> const char* type_of(double&) { return "double"; }
65
66// ------------------------------------------------------------------------------------------------
67// Worker function for exporting a scene to PLY. Prototyped and registered in Exporter.cpp
68void 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
86void 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// ------------------------------------------------------------------------------------------------
106PlyExporter::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// ------------------------------------------------------------------------------------------------
236PlyExporter::~PlyExporter() {
237 // empty
238}
239
240// ------------------------------------------------------------------------------------------------
241void 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// ------------------------------------------------------------------------------------------------
310void 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// ------------------------------------------------------------------------------------------------
360void 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.
372template<typename NumIndicesType, typename IndexType>
373void 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
386void 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