| /* |
| Open Asset Import Library (assimp) |
| ---------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| |
| |
| #if !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) |
| |
| #include "PlyExporter.h" |
| #include <memory> |
| #include <cmath> |
| #include "Exceptional.h" |
| #include <assimp/scene.h> |
| #include <assimp/version.h> |
| #include <assimp/IOSystem.hpp> |
| #include <assimp/Exporter.hpp> |
| #include "qnan.h" |
| |
| |
| //using namespace Assimp; |
| namespace Assimp { |
| |
| // make sure type_of returns consistent output across different platforms |
| // also consider using: typeid(VAR).name() |
| template <typename T> const char* type_of(T&) { return "unknown"; } |
| template<> const char* type_of(float&) { return "float"; } |
| template<> const char* type_of(double&) { return "double"; } |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Worker function for exporting a scene to PLY. Prototyped and registered in Exporter.cpp |
| void ExportScenePly(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) |
| { |
| // invoke the exporter |
| PlyExporter exporter(pFile, pScene); |
| |
| if (exporter.mOutput.fail()) { |
| throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile)); |
| } |
| |
| // we're still here - export successfully completed. Write the file. |
| std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt")); |
| if(outfile == NULL) { |
| throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); |
| } |
| |
| outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1); |
| } |
| |
| void ExportScenePlyBinary(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) |
| { |
| // invoke the exporter |
| PlyExporter exporter(pFile, pScene, true); |
| |
| // we're still here - export successfully completed. Write the file. |
| std::unique_ptr<IOStream> outfile(pIOSystem->Open(pFile, "wb")); |
| if (outfile == NULL) { |
| throw DeadlyExportError("could not open output .ply file: " + std::string(pFile)); |
| } |
| |
| outfile->Write(exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()), 1); |
| } |
| |
| #define PLY_EXPORT_HAS_NORMALS 0x1 |
| #define PLY_EXPORT_HAS_TANGENTS_BITANGENTS 0x2 |
| #define PLY_EXPORT_HAS_TEXCOORDS 0x4 |
| #define PLY_EXPORT_HAS_COLORS (PLY_EXPORT_HAS_TEXCOORDS << AI_MAX_NUMBER_OF_TEXTURECOORDS) |
| |
| // ------------------------------------------------------------------------------------------------ |
| PlyExporter::PlyExporter(const char* _filename, const aiScene* pScene, bool binary) |
| : filename(_filename) |
| , endl("\n") |
| { |
| // make sure that all formatting happens using the standard, C locale and not the user's current locale |
| const std::locale& l = std::locale("C"); |
| mOutput.imbue(l); |
| mOutput.precision(16); |
| |
| unsigned int faces = 0u, vertices = 0u, components = 0u; |
| for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
| const aiMesh& m = *pScene->mMeshes[i]; |
| faces += m.mNumFaces; |
| vertices += m.mNumVertices; |
| |
| if (m.HasNormals()) { |
| components |= PLY_EXPORT_HAS_NORMALS; |
| } |
| if (m.HasTangentsAndBitangents()) { |
| components |= PLY_EXPORT_HAS_TANGENTS_BITANGENTS; |
| } |
| for (unsigned int t = 0; m.HasTextureCoords(t); ++t) { |
| components |= PLY_EXPORT_HAS_TEXCOORDS << t; |
| } |
| for (unsigned int t = 0; m.HasVertexColors(t); ++t) { |
| components |= PLY_EXPORT_HAS_COLORS << t; |
| } |
| } |
| |
| mOutput << "ply" << endl; |
| if (binary) { |
| #if (defined AI_BUILD_BIG_ENDIAN) |
| mOutput << "format binary_big_endian 1.0" << endl; |
| #else |
| mOutput << "format binary_little_endian 1.0" << endl; |
| #endif |
| } |
| else { |
| mOutput << "format ascii 1.0" << endl; |
| } |
| mOutput << "comment Created by Open Asset Import Library - http://assimp.sf.net (v" |
| << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.' |
| << aiGetVersionRevision() << ")" << endl; |
| |
| // TODO: probably want to check here rather than just assume something |
| // definitely not good to always write float even if we might have double precision |
| |
| ai_real tmp = 0.0; |
| const char * typeName = type_of(tmp); |
| |
| mOutput << "element vertex " << vertices << endl; |
| mOutput << "property " << typeName << " x" << endl; |
| mOutput << "property " << typeName << " y" << endl; |
| mOutput << "property " << typeName << " z" << endl; |
| |
| if(components & PLY_EXPORT_HAS_NORMALS) { |
| mOutput << "property " << typeName << " nx" << endl; |
| mOutput << "property " << typeName << " ny" << endl; |
| mOutput << "property " << typeName << " nz" << endl; |
| } |
| |
| // write texcoords first, just in case an importer does not support tangents |
| // bitangents and just skips over the rest of the line upon encountering |
| // unknown fields (Ply leaves pretty much every vertex component open, |
| // but in reality most importers only know about vertex positions, normals |
| // and texture coordinates). |
| for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
| if (!c) { |
| mOutput << "property " << typeName << " s" << endl; |
| mOutput << "property " << typeName << " t" << endl; |
| } |
| else { |
| mOutput << "property " << typeName << " s" << c << endl; |
| mOutput << "property " << typeName << " t" << c << endl; |
| } |
| } |
| |
| for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
| if (!c) { |
| mOutput << "property " << typeName << " r" << endl; |
| mOutput << "property " << typeName << " g" << endl; |
| mOutput << "property " << typeName << " b" << endl; |
| mOutput << "property " << typeName << " a" << endl; |
| } |
| else { |
| mOutput << "property " << typeName << " r" << c << endl; |
| mOutput << "property " << typeName << " g" << c << endl; |
| mOutput << "property " << typeName << " b" << c << endl; |
| mOutput << "property " << typeName << " a" << c << endl; |
| } |
| } |
| |
| if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
| mOutput << "property " << typeName << " tx" << endl; |
| mOutput << "property " << typeName << " ty" << endl; |
| mOutput << "property " << typeName << " tz" << endl; |
| mOutput << "property " << typeName << " bx" << endl; |
| mOutput << "property " << typeName << " by" << endl; |
| mOutput << "property " << typeName << " bz" << endl; |
| } |
| |
| mOutput << "element face " << faces << endl; |
| |
| // 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. |
| // For instance, MeshLab fails to load meshes in which both types are uint. Houdini seems to have problems as well. |
| // Obviously, using uchar will not work for meshes with polygons with more than 255 indices, but how realistic is this case? |
| mOutput << "property list uchar int vertex_index" << endl; |
| |
| mOutput << "end_header" << endl; |
| |
| for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) { |
| if (binary) { |
| WriteMeshVertsBinary(pScene->mMeshes[i], components); |
| } |
| else { |
| WriteMeshVerts(pScene->mMeshes[i], components); |
| } |
| } |
| for (unsigned int i = 0, ofs = 0; i < pScene->mNumMeshes; ++i) { |
| if (binary) { |
| WriteMeshIndicesBinary(pScene->mMeshes[i], ofs); |
| } |
| else { |
| WriteMeshIndices(pScene->mMeshes[i], ofs); |
| } |
| ofs += pScene->mMeshes[i]->mNumVertices; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| PlyExporter::~PlyExporter() { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void PlyExporter::WriteMeshVerts(const aiMesh* m, unsigned int components) |
| { |
| static const ai_real inf = std::numeric_limits<ai_real>::infinity(); |
| |
| // If a component (for instance normal vectors) is present in at least one mesh in the scene, |
| // then default values are written for meshes that do not contain this component. |
| for (unsigned int i = 0; i < m->mNumVertices; ++i) { |
| mOutput << |
| m->mVertices[i].x << " " << |
| m->mVertices[i].y << " " << |
| m->mVertices[i].z |
| ; |
| if(components & PLY_EXPORT_HAS_NORMALS) { |
| if (m->HasNormals() && is_not_qnan(m->mNormals[i].x) && std::fabs(m->mNormals[i].x) != inf) { |
| mOutput << |
| " " << m->mNormals[i].x << |
| " " << m->mNormals[i].y << |
| " " << m->mNormals[i].z; |
| } |
| else { |
| mOutput << " 0.0 0.0 0.0"; |
| } |
| } |
| |
| for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
| if (m->HasTextureCoords(c)) { |
| mOutput << |
| " " << m->mTextureCoords[c][i].x << |
| " " << m->mTextureCoords[c][i].y; |
| } |
| else { |
| mOutput << " -1.0 -1.0"; |
| } |
| } |
| |
| for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
| if (m->HasVertexColors(c)) { |
| mOutput << |
| " " << m->mColors[c][i].r << |
| " " << m->mColors[c][i].g << |
| " " << m->mColors[c][i].b << |
| " " << m->mColors[c][i].a; |
| } |
| else { |
| mOutput << " -1.0 -1.0 -1.0 -1.0"; |
| } |
| } |
| |
| if(components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
| if (m->HasTangentsAndBitangents()) { |
| mOutput << |
| " " << m->mTangents[i].x << |
| " " << m->mTangents[i].y << |
| " " << m->mTangents[i].z << |
| " " << m->mBitangents[i].x << |
| " " << m->mBitangents[i].y << |
| " " << m->mBitangents[i].z |
| ; |
| } |
| else { |
| mOutput << " 0.0 0.0 0.0 0.0 0.0 0.0"; |
| } |
| } |
| |
| mOutput << endl; |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void PlyExporter::WriteMeshVertsBinary(const aiMesh* m, unsigned int components) |
| { |
| // If a component (for instance normal vectors) is present in at least one mesh in the scene, |
| // then default values are written for meshes that do not contain this component. |
| aiVector3D defaultNormal(0, 0, 0); |
| aiVector2D defaultUV(-1, -1); |
| aiColor4D defaultColor(-1, -1, -1, -1); |
| for (unsigned int i = 0; i < m->mNumVertices; ++i) { |
| mOutput.write(reinterpret_cast<const char*>(&m->mVertices[i].x), 12); |
| if (components & PLY_EXPORT_HAS_NORMALS) { |
| if (m->HasNormals()) { |
| mOutput.write(reinterpret_cast<const char*>(&m->mNormals[i].x), 12); |
| } |
| else { |
| mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
| } |
| } |
| |
| for (unsigned int n = PLY_EXPORT_HAS_TEXCOORDS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_TEXTURECOORDS; n <<= 1, ++c) { |
| if (m->HasTextureCoords(c)) { |
| mOutput.write(reinterpret_cast<const char*>(&m->mTextureCoords[c][i].x), 8); |
| } |
| else { |
| mOutput.write(reinterpret_cast<const char*>(&defaultUV.x), 8); |
| } |
| } |
| |
| for (unsigned int n = PLY_EXPORT_HAS_COLORS, c = 0; (components & n) && c != AI_MAX_NUMBER_OF_COLOR_SETS; n <<= 1, ++c) { |
| if (m->HasVertexColors(c)) { |
| mOutput.write(reinterpret_cast<const char*>(&m->mColors[c][i].r), 16); |
| } |
| else { |
| mOutput.write(reinterpret_cast<const char*>(&defaultColor.r), 16); |
| } |
| } |
| |
| if (components & PLY_EXPORT_HAS_TANGENTS_BITANGENTS) { |
| if (m->HasTangentsAndBitangents()) { |
| mOutput.write(reinterpret_cast<const char*>(&m->mTangents[i].x), 12); |
| mOutput.write(reinterpret_cast<const char*>(&m->mBitangents[i].x), 12); |
| } |
| else { |
| mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
| mOutput.write(reinterpret_cast<const char*>(&defaultNormal.x), 12); |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void PlyExporter::WriteMeshIndices(const aiMesh* m, unsigned int offset) |
| { |
| for (unsigned int i = 0; i < m->mNumFaces; ++i) { |
| const aiFace& f = m->mFaces[i]; |
| mOutput << f.mNumIndices << " "; |
| for(unsigned int c = 0; c < f.mNumIndices; ++c) { |
| mOutput << (f.mIndices[c] + offset) << (c == f.mNumIndices-1 ? endl : " "); |
| } |
| } |
| } |
| |
| // Generic method in case we want to use different data types for the indices or make this configurable. |
| template<typename NumIndicesType, typename IndexType> |
| void WriteMeshIndicesBinary_Generic(const aiMesh* m, unsigned int offset, std::ostringstream& output) |
| { |
| for (unsigned int i = 0; i < m->mNumFaces; ++i) { |
| const aiFace& f = m->mFaces[i]; |
| NumIndicesType numIndices = static_cast<NumIndicesType>(f.mNumIndices); |
| output.write(reinterpret_cast<const char*>(&numIndices), sizeof(NumIndicesType)); |
| for (unsigned int c = 0; c < f.mNumIndices; ++c) { |
| IndexType index = f.mIndices[c] + offset; |
| output.write(reinterpret_cast<const char*>(&index), sizeof(IndexType)); |
| } |
| } |
| } |
| |
| void PlyExporter::WriteMeshIndicesBinary(const aiMesh* m, unsigned int offset) |
| { |
| WriteMeshIndicesBinary_Generic<unsigned char, int>(m, offset, mOutput); |
| } |
| |
| } // end of namespace Assimp |
| |
| #endif // !defined(ASSIMP_BUILD_NO_EXPORT) && !defined(ASSIMP_BUILD_NO_PLY_EXPORTER) |