/src/assimp/code/AssetLib/Ply/PlyExporter.cpp
Line | Count | Source |
1 | | /* |
2 | | Open Asset Import Library (assimp) |
3 | | ---------------------------------------------------------------------- |
4 | | |
5 | | Copyright (c) 2006-2026, 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 | | #if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) |
42 | | |
43 | | #include "PlyExporter.h" |
44 | | #include <memory> |
45 | | #include <cmath> |
46 | | #include <assimp/Exceptional.h> |
47 | | #include <assimp/scene.h> |
48 | | #include <assimp/version.h> |
49 | | #include <assimp/IOSystem.hpp> |
50 | | #include <assimp/Exporter.hpp> |
51 | | #include <assimp/qnan.h> |
52 | | |
53 | | namespace Assimp { |
54 | | |
55 | | // make sure type_of returns consistent output across different platforms |
56 | | // also consider using: typeid(VAR).name() |
57 | | template <typename T> const char* type_of(T&) { return "unknown"; } |
58 | 0 | template<> const char* type_of(float&) { return "float"; } |
59 | 0 | template<> const char* type_of(double&) { return "double"; } |
60 | | |
61 | | // ------------------------------------------------------------------------------------------------ |
62 | | // Worker function for exporting a scene to PLY. Prototyped and registered in Exporter.cpp |
63 | 0 | void ExportScenePly(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) { |
64 | | // invoke the exporter |
65 | 0 | PlyExporter exporter(pFile, pScene); |
66 | |
|
67 | 0 | if (exporter.mOutput.fail()) { |
68 | 0 | throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); |
69 | 0 | } |
70 | | |
71 | | // we're still here - export successfully completed. Write the file. |
72 | 0 | std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); |
73 | 0 | if (outfile == nullptr) { |
74 | 0 | throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); |
75 | 0 | } |
76 | | |
77 | 0 | outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); |
78 | 0 | } |
79 | | |
80 | 0 | void ExportScenePlyBinary(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) { |
81 | | // invoke the exporter |
82 | 0 | PlyExporter exporter(pFile, pScene, true); |
83 | | |
84 | | // we're still here - export successfully completed. Write the file. |
85 | 0 | std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb")); |
86 | 0 | if (outfile == nullptr) { |
87 | 0 | throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); |
88 | 0 | } |
89 | | |
90 | 0 | outfile->Write(exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()), 1); |
91 | 0 | } |
92 | | |
93 | 0 | #define PLY_EXPORT_HAS_NORMALS 0x1 |
94 | 0 | #define PLY_EXPORT_HAS_TANGENTS_BITANGENTS 0x2 |
95 | 0 | #define PLY_EXPORT_HAS_TEXCOORDS 0x4 |
96 | 0 | #define PLY_EXPORT_HAS_COLORS (PLY_EXPORT_HAS_TEXCOORDS << AI_MAX_NUMBER_OF_TEXTURECOORDS) |
97 | | |
98 | | // ------------------------------------------------------------------------------------------------ |
99 | | PlyExporter::PlyExporter(const char* _filename, const aiScene* pScene, bool binary) : |
100 | 0 | filename(_filename), endl("\n") { |
101 | | // make sure that all formatting happens using the standard, C locale and not the user's current locale |
102 | 0 | const std::locale& l = std::locale("C"); |
103 | 0 | mOutput.imbue(l); |
104 | 0 | mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION); |
105 | |
|
106 | 0 | unsigned int faces = 0u, vertices = 0u, components = 0u; |
107 | 0 | for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
108 | 0 | const aiMesh& m = *pScene->mMeshes[i]; |
109 | 0 | faces += m.mNumFaces; |
110 | 0 | vertices += m.mNumVertices; |
111 | |
|
112 | 0 | if (m.HasNormals()) { |
113 | 0 | components |= PLY_EXPORT_HAS_NORMALS; |
114 | 0 | } |
115 | 0 | if (m.HasTangentsAndBitangents()) { |
116 | 0 | components |= PLY_EXPORT_HAS_TANGENTS_BITANGENTS; |
117 | 0 | } |
118 | 0 | for (unsigned int t = 0; m.HasTextureCoords(t); ++t) { |
119 | 0 | components |= PLY_EXPORT_HAS_TEXCOORDS << t; |
120 | 0 | } |
121 | 0 | for (unsigned int t = 0; m.HasVertexColors(t); ++t) { |
122 | 0 | components |= PLY_EXPORT_HAS_COLORS << t; |
123 | 0 | } |
124 | 0 | } |
125 | |
|
126 | 0 | mOutput << "ply" << endl; |
127 | 0 | if (binary) { |
128 | | #if (defined AI_BUILD_BIG_ENDIAN) |
129 | | mOutput << "format binary_big_endian 1.0" << endl; |
130 | | #else |
131 | 0 | mOutput << "format binary_little_endian 1.0" << endl; |
132 | 0 | #endif |
133 | 0 | } |
134 | 0 | else { |
135 | 0 | mOutput << "format ascii 1.0" << endl; |
136 | 0 | } |
137 | 0 | mOutput << "comment Created by Open Asset Import Library - http://assimp.sf.net (v" |
138 | 0 | << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.' |
139 | 0 | << aiGetVersionRevision() << ")" << endl; |
140 | | |
141 | | // Look through materials for a diffuse texture, and add it if found |
142 | 0 | for ( unsigned int i = 0; i < pScene->mNumMaterials; ++i ) { |
143 | 0 | const aiMaterial* const mat = pScene->mMaterials[i]; |
144 | 0 | aiString s; |
145 | 0 | if ( AI_SUCCESS == mat->Get( AI_MATKEY_TEXTURE_DIFFUSE( 0 ), s ) ) { |
146 | 0 | mOutput << "comment TextureFile " << s.data << endl; |
147 | 0 | } |
148 | 0 | } |
149 | |
|
150 | 0 | ai_real tmp = 0.0; |
151 | 0 | const char * typeName = type_of(tmp); |
152 | |
|
153 | 0 | mOutput << "element vertex " << vertices << endl; |
154 | 0 | mOutput << "property " << typeName << " x" << endl; |
155 | 0 | mOutput << "property " << typeName << " y" << endl; |
156 | 0 | mOutput << "property " << typeName << " z" << endl; |
157 | |
|
158 | 0 | if(components & PLY_EXPORT_HAS_NORMALS) { |
159 | 0 | mOutput << "property " << typeName << " nx" << endl; |
160 | 0 | mOutput << "property " << typeName << " ny" << endl; |
161 | 0 | mOutput << "property " << typeName << " nz" << endl; |
162 | 0 | } |
163 | | |
164 | | // write texcoords first, just in case an importer does not support tangents |
165 | | // bitangents and just skips over the rest of the line upon encountering |
166 | | // unknown fields (Ply leaves pretty much every vertex component open, |
167 | | // but in reality most importers only know about vertex positions, normals |
168 | | // and texture coordinates). |
169 | 0 | for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
170 | 0 | if (!c) { |
171 | 0 | mOutput << "property " << "uchar" << " red" << endl; |
172 | 0 | mOutput << "property " << "uchar" << " green" << endl; |
173 | 0 | mOutput << "property " << "uchar" << " blue" << endl; |
174 | 0 | mOutput << "property " << "uchar" << " alpha" << endl; |
175 | 0 | } else { |
176 | 0 | mOutput << "property " << "uchar" << " red" << c << endl; |
177 | 0 | mOutput << "property " << "uchar" << " green" << c << endl; |
178 | 0 | mOutput << "property " << "uchar" << " blue" << c << endl; |
179 | 0 | mOutput << "property " << "uchar" << " alpha" << c << endl; |
180 | 0 | } |
181 | 0 | } |
182 | |
|
183 | 0 | if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
184 | 0 | mOutput << "property " << typeName << " tx" << endl; |
185 | 0 | mOutput << "property " << typeName << " ty" << endl; |
186 | 0 | mOutput << "property " << typeName << " tz" << endl; |
187 | 0 | mOutput << "property " << typeName << " bx" << endl; |
188 | 0 | mOutput << "property " << typeName << " by" << endl; |
189 | 0 | mOutput << "property " << typeName << " bz" << endl; |
190 | 0 | } |
191 | |
|
192 | 0 | mOutput << "element face " << faces << endl; |
193 | | |
194 | | // 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. |
195 | | // For instance, MeshLab fails to load meshes in which both types are uint. Houdini seems to have problems as well. |
196 | | // Obviously, using uchar will not work for meshes with polygons with more than 255 indices, but how realistic is this case? |
197 | 0 | mOutput << "property list uchar int vertex_index" << endl; |
198 | |
|
199 | 0 | mOutput << "end_header" << endl; |
200 | |
|
201 | 0 | for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
202 | 0 | if (binary) { |
203 | 0 | WriteMeshVertsBinary(pScene->mMeshes[i], components); |
204 | 0 | } else { |
205 | 0 | WriteMeshVerts(pScene->mMeshes[i], components); |
206 | 0 | } |
207 | 0 | } |
208 | 0 | for (unsigned int i = 0, ofs = 0; i < pScene->mNumMeshes; ++i) { |
209 | 0 | if (binary) { |
210 | 0 | WriteMeshIndicesBinary(pScene->mMeshes[i], ofs); |
211 | 0 | } else { |
212 | 0 | WriteMeshIndices(pScene->mMeshes[i], ofs); |
213 | 0 | } |
214 | 0 | ofs += pScene->mMeshes[i]->mNumVertices; |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | | // ------------------------------------------------------------------------------------------------ |
219 | 0 | void PlyExporter::WriteMeshVerts(const aiMesh* m, unsigned int components) { |
220 | 0 | static constexpr ai_real inf = std::numeric_limits<ai_real>::infinity(); |
221 | | |
222 | | // If a component (for instance normal vectors) is present in at least one mesh in the scene, |
223 | | // then default values are written for meshes that do not contain this component. |
224 | 0 | for (unsigned int i = 0; i < m->mNumVertices; ++i) { |
225 | 0 | mOutput << |
226 | 0 | m->mVertices[i].x << " " << |
227 | 0 | m->mVertices[i].y << " " << |
228 | 0 | m->mVertices[i].z; |
229 | 0 | if(components & PLY_EXPORT_HAS_NORMALS) { |
230 | 0 | if (m->HasNormals() && is_not_qnan(m->mNormals[i].x) && std::fabs(m->mNormals[i].x) != inf) { |
231 | 0 | mOutput << |
232 | 0 | " " << m->mNormals[i].x << |
233 | 0 | " " << m->mNormals[i].y << |
234 | 0 | " " << m->mNormals[i].z; |
235 | 0 | } else { |
236 | 0 | mOutput << " 0.0 0.0 0.0"; |
237 | 0 | } |
238 | 0 | } |
239 | |
|
240 | 0 | for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
241 | 0 | if (m->HasTextureCoords(c)) { |
242 | 0 | if (m->mNumUVComponents[c] == 3) { |
243 | 0 | mOutput << |
244 | 0 | " " << m->mTextureCoords[c][i].x << |
245 | 0 | " " << m->mTextureCoords[c][i].y << |
246 | 0 | " " << m->mTextureCoords[c][i].z; |
247 | 0 | } else if (m->mNumUVComponents[c] == 2) { |
248 | 0 | mOutput << |
249 | 0 | " " << m->mTextureCoords[c][i].x << |
250 | 0 | " " << m->mTextureCoords[c][i].y; |
251 | 0 | } else { |
252 | 0 | throw DeadlyExportError("Invalid number of texture coordinates detected: " + std::to_string(m->mNumUVComponents[c])); |
253 | 0 | } |
254 | 0 | } else { |
255 | 0 | mOutput << " -1.0 -1.0"; |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | 0 | for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
260 | 0 | if (m->HasVertexColors(c)) { |
261 | 0 | mOutput << |
262 | 0 | " " << (int)(m->mColors[c][i].r * 255) << |
263 | 0 | " " << (int)(m->mColors[c][i].g * 255) << |
264 | 0 | " " << (int)(m->mColors[c][i].b * 255) << |
265 | 0 | " " << (int)(m->mColors[c][i].a * 255); |
266 | 0 | } else { |
267 | 0 | mOutput << " 0 0 0"; |
268 | 0 | } |
269 | 0 | } |
270 | |
|
271 | 0 | if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
272 | 0 | if (m->HasTangentsAndBitangents()) { |
273 | 0 | mOutput << |
274 | 0 | " " << m->mTangents[i].x << |
275 | 0 | " " << m->mTangents[i].y << |
276 | 0 | " " << m->mTangents[i].z << |
277 | 0 | " " << m->mBitangents[i].x << |
278 | 0 | " " << m->mBitangents[i].y << |
279 | 0 | " " << m->mBitangents[i].z; |
280 | 0 | } else { |
281 | 0 | mOutput << " 0.0 0.0 0.0 0.0 0.0 0.0"; |
282 | 0 | } |
283 | 0 | } |
284 | |
|
285 | 0 | mOutput << endl; |
286 | 0 | } |
287 | 0 | } |
288 | | |
289 | | // ------------------------------------------------------------------------------------------------ |
290 | 0 | void PlyExporter::WriteMeshVertsBinary(const aiMesh* m, unsigned int components) { |
291 | | // If a component (for instance normal vectors) is present in at least one mesh in the scene, |
292 | | // then default values are written for meshes that do not contain this component. |
293 | 0 | aiVector3D defaultNormal(0, 0, 0); |
294 | 0 | aiVector2D defaultUV(-1, -1); |
295 | 0 | aiColor4D defaultColor(-1, -1, -1, -1); |
296 | 0 | for (unsigned int i = 0; i < m->mNumVertices; ++i) { |
297 | 0 | mOutput.write(reinterpret_cast<const char*>(&m->mVertices[i].x), 12); |
298 | 0 | if (components & PLY_EXPORT_HAS_NORMALS) { |
299 | 0 | if (m->HasNormals()) { |
300 | 0 | mOutput.write(reinterpret_cast<const char*>(&m->mNormals[i].x), 12); |
301 | 0 | } else { |
302 | 0 | mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
303 | 0 | } |
304 | 0 | } |
305 | |
|
306 | 0 | for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
307 | 0 | if (m->HasTextureCoords(c)) { |
308 | 0 | mOutput.write(reinterpret_cast<const char*>(&m->mTextureCoords[c][i].x), 8); |
309 | 0 | } else { |
310 | 0 | mOutput.write(reinterpret_cast<const char*>(&defaultUV.x), 8); |
311 | 0 | } |
312 | 0 | } |
313 | |
|
314 | 0 | for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
315 | 0 | if (m->HasVertexColors(c)) { |
316 | 0 | unsigned char rgba[4] = { |
317 | 0 | static_cast<unsigned char>(m->mColors[c][i].r * 255), |
318 | 0 | static_cast<unsigned char>(m->mColors[c][i].g * 255), |
319 | 0 | static_cast<unsigned char>(m->mColors[c][i].b * 255), |
320 | 0 | static_cast<unsigned char>(m->mColors[c][i].a * 255) |
321 | 0 | }; |
322 | 0 | mOutput.write(reinterpret_cast<const char*>(&rgba), 4); |
323 | 0 | } else { |
324 | 0 | unsigned char rgba[4] = { |
325 | 0 | static_cast<unsigned char>(defaultColor.r * 255), |
326 | 0 | static_cast<unsigned char>(defaultColor.g * 255), |
327 | 0 | static_cast<unsigned char>(defaultColor.b * 255), |
328 | 0 | static_cast<unsigned char>(defaultColor.a * 255) |
329 | 0 | }; |
330 | 0 | mOutput.write(reinterpret_cast<const char*>(&rgba), 4); |
331 | 0 | } |
332 | 0 | } |
333 | |
|
334 | 0 | if (components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
335 | 0 | if (m->HasTangentsAndBitangents()) { |
336 | 0 | mOutput.write(reinterpret_cast<const char*>(&m->mTangents[i].x), 12); |
337 | 0 | mOutput.write(reinterpret_cast<const char*>(&m->mBitangents[i].x), 12); |
338 | 0 | } else { |
339 | 0 | mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
340 | 0 | mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
341 | 0 | } |
342 | 0 | } |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | | // ------------------------------------------------------------------------------------------------ |
347 | 0 | void PlyExporter::WriteMeshIndices(const aiMesh* m, unsigned int offset) { |
348 | 0 | for (unsigned int i = 0; i < m->mNumFaces; ++i) { |
349 | 0 | const aiFace& f = m->mFaces[i]; |
350 | 0 | mOutput << f.mNumIndices; |
351 | 0 | for(unsigned int c = 0; c < f.mNumIndices; ++c) { |
352 | 0 | mOutput << " " << (f.mIndices[c] + offset); |
353 | 0 | } |
354 | 0 | mOutput << endl; |
355 | 0 | } |
356 | 0 | } |
357 | | |
358 | | // ------------------------------------------------------------------------------------------------ |
359 | | // Generic method in case we want to use different data types for the indices or make this configurable. |
360 | | template<typename NumIndicesType, typename IndexType> |
361 | 0 | void WriteMeshIndicesBinary_Generic(const aiMesh* m, unsigned int offset, std::ostringstream& output) { |
362 | 0 | for (unsigned int i = 0; i < m->mNumFaces; ++i) { |
363 | 0 | const aiFace& f = m->mFaces[i]; |
364 | 0 | NumIndicesType numIndices = static_cast<NumIndicesType>(f.mNumIndices); |
365 | 0 | output.write(reinterpret_cast<const char*>(&numIndices), sizeof(NumIndicesType)); |
366 | 0 | for (unsigned int c = 0; c < f.mNumIndices; ++c) { |
367 | 0 | IndexType index = f.mIndices[c] + offset; |
368 | 0 | output.write(reinterpret_cast<const char*>(&index), sizeof(IndexType)); |
369 | 0 | } |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | // ------------------------------------------------------------------------------------------------ |
374 | 0 | void PlyExporter::WriteMeshIndicesBinary(const aiMesh* m, unsigned int offset) { |
375 | 0 | WriteMeshIndicesBinary_Generic<unsigned char, int>(m, offset, mOutput); |
376 | 0 | } |
377 | | |
378 | | } // end of namespace Assimp |
379 | | |
380 | | #endif // !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) |