Browse Source

Merge branch 'master' into fix_fbx_empty_names

Kim Kulling 7 years ago
parent
commit
7bb2d6271b

+ 13 - 6
code/Exporter.cpp

@@ -62,6 +62,7 @@ Here we implement only the C++ interface (Assimp::Exporter).
 #include "JoinVerticesProcess.h"
 #include "MakeVerboseFormat.h"
 #include "ConvertToLHProcess.h"
+#include "PretransformVertices.h"
 #include <assimp/Exceptional.h>
 #include "ScenePrivate.h"
 #include <memory>
@@ -397,6 +398,11 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
                         }
                     }
 
+                    bool exportPointCloud(false);
+                    if (nullptr != pProperties) {
+                        exportPointCloud = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
+                    }
+
                     // dispatch other processes
                     for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) {
                         BaseProcess* const p = pimpl->mPostProcessingSteps[a];
@@ -405,7 +411,9 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
                             && !dynamic_cast<FlipUVsProcess*>(p)
                             && !dynamic_cast<FlipWindingOrderProcess*>(p)
                             && !dynamic_cast<MakeLeftHandedProcess*>(p)) {
-
+                            if (dynamic_cast<PretransformVertices*>(p) && exportPointCloud) {
+                                continue;
+                            }
                             p->Execute(scenecopy.get());
                         }
                     }
@@ -441,7 +449,6 @@ const char* Exporter::GetErrorString() const {
     return pimpl->mError.c_str();
 }
 
-
 // ------------------------------------------------------------------------------------------------
 void Exporter::FreeBlob() {
     delete pimpl->blob;
@@ -470,7 +477,7 @@ size_t Exporter::GetExportFormatCount() const {
 // ------------------------------------------------------------------------------------------------
 const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
     if (index >= GetExportFormatCount()) {
-        return NULL;
+        return nullptr;
     }
 
     // Return from static storage if the requested index is built-in.
@@ -495,7 +502,8 @@ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
 
 // ------------------------------------------------------------------------------------------------
 void Exporter::UnregisterExporter(const char* id) {
-    for(std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin(); it != pimpl->mExporters.end(); ++it) {
+    for(std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin();
+            it != pimpl->mExporters.end(); ++it) {
         if (!strcmp((*it).mDescription.id,id)) {
             pimpl->mExporters.erase(it);
             break;
@@ -531,8 +539,7 @@ bool ExportProperties::SetPropertyFloat(const char* szName, ai_real iValue) {
 
 // ------------------------------------------------------------------------------------------------
 // Set a configuration property
-bool ExportProperties :: SetPropertyString(const char* szName, const std::string& value)
-{
+bool ExportProperties::SetPropertyString(const char* szName, const std::string& value) {
     return SetGenericProperty<std::string>(mStringProperties, szName,value);
 }
 

+ 22 - 68
code/ObjExporter.cpp

@@ -114,14 +114,13 @@ static const std::string MaterialExt = ".mtl";
 ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl)
 : filename(_filename)
 , pScene(pScene)
-, vp()
 , vn()
 , vt()
-, vc()
-, mVpMap()
+, vp()
+, useVc(false)
 , mVnMap()
 , mVtMap()
-, mVcMap()
+, mVpMap()
 , mMeshes()
 , endl("\n") {
     // make sure that all formatting happens using the standard, C locale and not the user's current locale
@@ -268,27 +267,22 @@ void ObjExporter::WriteGeometryFile(bool noMtl) {
     AddNode(pScene->mRootNode, mBase);
 
     // write vertex positions with colors, if any
-    mVpMap.getVectors( vp );
-    mVcMap.getColors( vc );
-    if ( vc.empty() ) {
+    mVpMap.getKeys( vp );
+    if ( !useVc ) {
         mOutput << "# " << vp.size() << " vertex positions" << endl;
-        for ( const aiVector3D& v : vp ) {
-            mOutput << "v  " << v.x << " " << v.y << " " << v.z << endl;
+        for ( const vertexData& v : vp ) {
+            mOutput << "v  " << v.vp.x << " " << v.vp.y << " " << v.vp.z << endl;
         }
     } else {
         mOutput << "# " << vp.size() << " vertex positions and colors" << endl;
-        size_t colIdx = 0;
-        for ( const aiVector3D& v : vp ) {
-            if ( colIdx < vc.size() ) {
-                mOutput << "v  " << v.x << " " << v.y << " " << v.z << " " << vc[ colIdx ].r << " " << vc[ colIdx ].g << " " << vc[ colIdx ].b << endl;
-            }
-            ++colIdx;
+        for ( const vertexData& v : vp ) {
+            mOutput << "v  " << v.vp.x << " " << v.vp.y << " " << v.vp.z << " " << v.vc.r << " " << v.vc.g << " " << v.vc.b << endl;
         }
     }
     mOutput << endl;
 
     // write uv coordinates
-    mVtMap.getVectors(vt);
+    mVtMap.getKeys(vt);
     mOutput << "# " << vt.size() << " UV coordinates" << endl;
     for(const aiVector3D& v : vt) {
         mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl;
@@ -296,7 +290,7 @@ void ObjExporter::WriteGeometryFile(bool noMtl) {
     mOutput << endl;
 
     // write vertex normals
-    mVnMap.getVectors(vn);
+    mVnMap.getKeys(vn);
     mOutput << "# " << vn.size() << " vertex normals" << endl;
     for(const aiVector3D& v : vn) {
         mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl;
@@ -337,54 +331,15 @@ void ObjExporter::WriteGeometryFile(bool noMtl) {
     }
 }
 
-// ------------------------------------------------------------------------------------------------
-int ObjExporter::vecIndexMap::getIndex(const aiVector3D& vec) {
-    vecIndexMap::dataType::iterator vertIt = vecMap.find(vec);
-    // vertex already exists, so reference it
-    if(vertIt != vecMap.end()){
-        return vertIt->second;
-    }
-    vecMap[vec] = mNextIndex;
-    int ret = mNextIndex;
-    mNextIndex++;
-    return ret;
-}
-
-// ------------------------------------------------------------------------------------------------
-void ObjExporter::vecIndexMap::getVectors( std::vector<aiVector3D>& vecs ) {
-    vecs.resize(vecMap.size());
-    for(vecIndexMap::dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){
-        vecs[it->second-1] = it->first;
-    }
-}
-
-// ------------------------------------------------------------------------------------------------
-int ObjExporter::colIndexMap::getIndex( const aiColor4D& col ) {
-    colIndexMap::dataType::iterator vertIt = colMap.find( col );
-    // vertex already exists, so reference it
-    if ( vertIt != colMap.end() ) {
-        return vertIt->second;
-    }
-    colMap[ col ] = mNextIndex;
-    int ret = mNextIndex;
-    mNextIndex++;
-
-    return ret;
-}
-
-// ------------------------------------------------------------------------------------------------
-void ObjExporter::colIndexMap::getColors( std::vector<aiColor4D> &colors ) {
-    colors.resize( colMap.size() );
-    for ( colIndexMap::dataType::iterator it = colMap.begin(); it != colMap.end(); ++it ) {
-        colors[ it->second - 1 ] = it->first;
-    }
-}
-
 // ------------------------------------------------------------------------------------------------
 void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) {
     mMeshes.push_back(MeshInstance() );
     MeshInstance& mesh = mMeshes.back();
 
+    if ( nullptr != m->mColors[ 0 ] ) {
+        useVc = true;
+    }
+
     mesh.name = std::string( name.data, name.length );
     mesh.matname = GetMaterialName(m->mMaterialIndex);
 
@@ -410,7 +365,13 @@ void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4
             const unsigned int idx = f.mIndices[a];
 
             aiVector3D vert = mat * m->mVertices[idx];
-            face.indices[a].vp = mVpMap.getIndex(vert);
+
+            if ( nullptr != m->mColors[ 0 ] ) {
+                aiColor4D col4 = m->mColors[ 0 ][ idx ];
+                face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b)});
+            } else {
+                face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0)});
+            }
 
             if (m->mNormals) {
                 aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx];
@@ -419,13 +380,6 @@ void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4
                 face.indices[a].vn = 0;
             }
 
-            if ( nullptr != m->mColors[ 0 ] ) {
-                aiColor4D col4 = m->mColors[ 0 ][ idx ];
-                face.indices[ a ].vc = mVcMap.getIndex( col4 );
-            } else {
-                face.indices[ a ].vc = 0;
-            }
-
             if ( m->mTextureCoords[ 0 ] ) {
                 face.indices[a].vt = mVtMap.getIndex(m->mTextureCoords[0][idx]);
             } else {

+ 57 - 44
code/ObjExporter.h

@@ -77,13 +77,12 @@ private:
         FaceVertex()
         : vp()
         , vn()
-        , vt()
-        , vc() {
+        , vt() {
             // empty
         }
 
         // one-based, 0 means: 'does not exist'
-        unsigned int vp, vn, vt, vc;
+        unsigned int vp, vn, vt;
     };
 
     struct Face {
@@ -106,66 +105,80 @@ private:
 private:
     std::string filename;
     const aiScene* const pScene;
-    std::vector<aiVector3D> vp, vn, vt;
-    std::vector<aiColor4D> vc;
 
-    struct aiVectorCompare {
-        bool operator() (const aiVector3D& a, const aiVector3D& b) const {
-            if(a.x < b.x) return true;
-            if(a.x > b.x) return false;
-            if(a.y < b.y) return true;
-            if(a.y > b.y) return false;
-            if(a.z < b.z) return true;
+    struct vertexData {
+        aiVector3D vp;
+        aiColor3D vc; // OBJ does not support 4D color
+    };
+
+    std::vector<aiVector3D> vn, vt;
+    std::vector<aiColor4D> vc;
+    std::vector<vertexData> vp;
+    bool useVc;
+
+    struct vertexDataCompare {
+        bool operator() ( const vertexData& a, const vertexData& b ) const {
+            // position
+            if (a.vp.x < b.vp.x) return true;
+            if (a.vp.x > b.vp.x) return false;
+            if (a.vp.y < b.vp.y) return true;
+            if (a.vp.y > b.vp.y) return false;
+            if (a.vp.z < b.vp.z) return true;
+            if (a.vp.z > b.vp.z) return false;
+
+            // color
+            if (a.vc.r < b.vc.r) return true;
+            if (a.vc.r > b.vc.r) return false;
+            if (a.vc.g < b.vc.g) return true;
+            if (a.vc.g > b.vc.g) return false;
+            if (a.vc.b < b.vc.b) return true;
+            if (a.vc.b > b.vc.b) return false;
             return false;
         }
     };
 
-    struct aiColor4Compare {
-        bool operator() ( const aiColor4D& a, const aiColor4D& b ) const {
-            if ( a.r < b.r ) return true;
-            if ( a.r > b.r ) return false;
-            if ( a.g < b.g ) return true;
-            if ( a.g > b.g ) return false;
-            if ( a.b < b.b ) return true;
-            if ( a.b > b.b ) return false;
-            if ( a.a < b.a ) return true;
-            if ( a.a > b.a ) return false;
+    struct aiVectorCompare { 
+        bool operator() (const aiVector3D& a, const aiVector3D& b) const { 
+            if(a.x < b.x) return true; 
+            if(a.x > b.x) return false; 
+            if(a.y < b.y) return true; 
+            if(a.y > b.y) return false; 
+            if(a.z < b.z) return true; 
             return false;
         }
     };
 
-    class vecIndexMap {
+    template <class T, class Compare = std::less<T>>
+    class indexMap {
         int mNextIndex;
-        typedef std::map<aiVector3D, int, aiVectorCompare> dataType;
+        typedef std::map<T, int, Compare> dataType;
         dataType vecMap;
     
     public:
-        vecIndexMap()
+        indexMap()
         : mNextIndex(1) {
             // empty
         }
 
-        int getIndex(const aiVector3D& vec);
-        void getVectors( std::vector<aiVector3D>& vecs );
-    };
-
-    class colIndexMap {
-        int mNextIndex;
-        typedef std::map<aiColor4D, int, aiColor4Compare> dataType;
-        dataType colMap;
-
-    public:
-        colIndexMap()
-        : mNextIndex( 1 ) {
-            // empty
-        }
-
-        int getIndex( const aiColor4D& col );
-        void getColors( std::vector<aiColor4D> &colors );
+        int getIndex(const T& key) {
+            typename dataType::iterator vertIt = vecMap.find(key);
+            // vertex already exists, so reference it
+            if(vertIt != vecMap.end()){
+                return vertIt->second;
+            }
+            return vecMap[key] = mNextIndex++;
+        };
+
+        void getKeys( std::vector<T>& keys ) {
+            keys.resize(vecMap.size());
+            for(typename dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){
+                keys[it->second-1] = it->first;
+            }
+        };
     };
 
-    vecIndexMap mVpMap, mVnMap, mVtMap;
-    colIndexMap mVcMap;
+    indexMap<aiVector3D, aiVectorCompare> mVnMap, mVtMap;
+    indexMap<vertexData, vertexDataCompare> mVpMap;
     std::vector<MeshInstance> mMeshes;
 
     // this endl() doesn't flush() the stream

+ 12 - 8
code/PretransformVertices.cpp

@@ -60,14 +60,17 @@ using namespace Assimp;
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 PretransformVertices::PretransformVertices()
-:   configKeepHierarchy (false), configNormalize(false), configTransform(false), configTransformation()
-{
+: configKeepHierarchy (false)
+, configNormalize(false)
+, configTransform(false)
+, configTransformation()
+, mConfigPointCloud( false ) {
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
-PretransformVertices::~PretransformVertices()
-{
+PretransformVertices::~PretransformVertices() {
     // nothing to do here
 }
 
@@ -89,6 +92,8 @@ void PretransformVertices::SetupProperties(const Importer* pImp)
     configTransform = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION,0));
 
     configTransformation = pImp->GetPropertyMatrix(AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION, aiMatrix4x4());
+
+    mConfigPointCloud = pImp->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -502,9 +507,7 @@ void PretransformVertices::Execute( aiScene* pScene)
             pScene->mMeshes[i]->mBones    = NULL;
             pScene->mMeshes[i]->mNumBones = 0;
         }
-    }
-    else {
-
+    } else {
         apcOutMeshes.reserve(pScene->mNumMaterials<<1u);
         std::list<unsigned int> aiVFormats;
 
@@ -556,7 +559,8 @@ void PretransformVertices::Execute( aiScene* pScene)
         }
 
         // If no meshes are referenced in the node graph it is possible that we get no output meshes.
-        if (apcOutMeshes.empty())   {
+        if (apcOutMeshes.empty()) {
+            
             throw DeadlyImportError("No output meshes: all meshes are orphaned and are not referenced by any nodes");
         }
         else

+ 2 - 7
code/PretransformVertices.h

@@ -61,15 +61,11 @@ namespace Assimp    {
  *  and removes the whole graph. The output is a list of meshes, one for
  *  each material.
 */
-class ASSIMP_API PretransformVertices : public BaseProcess
-{
+class ASSIMP_API PretransformVertices : public BaseProcess {
 public:
-
     PretransformVertices ();
     ~PretransformVertices ();
 
-public:
-
     // -------------------------------------------------------------------
     // Check whether step is active
     bool IsActive( unsigned int pFlags) const;
@@ -82,7 +78,6 @@ public:
     // Setup import settings
     void SetupProperties(const Importer* pImp);
 
-
     // -------------------------------------------------------------------
     /** @brief Toggle the 'keep hierarchy' option
      *  @param d hm ... difficult to guess what this means, hu!?
@@ -100,7 +95,6 @@ public:
     }
 
 private:
-
     // -------------------------------------------------------------------
     // Count the number of nodes
     unsigned int CountNodes( aiNode* pcNode );
@@ -161,6 +155,7 @@ private:
     bool configNormalize;
     bool configTransform;
     aiMatrix4x4 configTransformation;
+    bool mConfigPointCloud;
 };
 
 } // end of namespace Assimp

+ 1 - 0
code/RemoveRedundantMaterials.h

@@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/mesh.h>
 
 class RemoveRedundantMatsTest;
+
 namespace Assimp    {
 
 // ---------------------------------------------------------------------------

+ 52 - 12
code/STLExporter.cpp

@@ -54,14 +54,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/ByteSwapper.h>
 
 using namespace Assimp;
+
 namespace Assimp    {
 
 // ------------------------------------------------------------------------------------------------
 // Worker function for exporting a scene to Stereolithograpy. Prototyped and registered in Exporter.cpp
-void ExportSceneSTL(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
+void ExportSceneSTL(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties )
 {
+    bool exportPointClouds = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
+
     // invoke the exporter
-    STLExporter exporter(pFile, pScene);
+    STLExporter exporter(pFile, pScene, exportPointClouds );
 
     if (exporter.mOutput.fail()) {
         throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
@@ -75,10 +78,12 @@ void ExportSceneSTL(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene
 
     outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
 }
-void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/)
+void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties )
 {
+    bool exportPointClouds = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
+
     // invoke the exporter
-    STLExporter exporter(pFile, pScene, true);
+    STLExporter exporter(pFile, pScene, exportPointClouds, true);
 
     if (exporter.mOutput.fail()) {
         throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
@@ -95,9 +100,11 @@ void ExportSceneSTLBinary(const char* pFile,IOSystem* pIOSystem, const aiScene*
 
 } // end of namespace Assimp
 
+static const char *SolidToken = "solid";
+static const char *EndSolidToken = "endsolid";
 
 // ------------------------------------------------------------------------------------------------
-STLExporter :: STLExporter(const char* _filename, const aiScene* pScene, bool binary)
+STLExporter::STLExporter(const char* _filename, const aiScene* pScene, bool exportPointClouds, bool binary)
 : filename(_filename)
 , endl("\n")
 {
@@ -118,22 +125,55 @@ STLExporter :: STLExporter(const char* _filename, const aiScene* pScene, bool bi
         }
         AI_SWAP4(meshnum);
         mOutput.write((char *)&meshnum, 4);
+
+        if (exportPointClouds) {
+
+        }
+
         for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
             WriteMeshBinary(pScene->mMeshes[i]);
         }
     } else {
-        const std::string& name = "AssimpScene";
 
-        mOutput << "solid " << name << endl;
+        // Exporting only point clouds
+        if (exportPointClouds) {
+            WritePointCloud("Assimp_Pointcloud", pScene );
+            return;
+        } 
+
+        // Export the assimp mesh 
+        const std::string name = "AssimpScene";
+        mOutput << SolidToken << " " << name << endl;
         for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
-            WriteMesh(pScene->mMeshes[i]);
+            WriteMesh(pScene->mMeshes[ i ]);
         }
-        mOutput << "endsolid " << name << endl;
+        mOutput << EndSolidToken << name << endl;
     }
 }
 
 // ------------------------------------------------------------------------------------------------
-void STLExporter :: WriteMesh(const aiMesh* m)
+void STLExporter::WritePointCloud(const std::string &name, const aiScene* pScene) {
+    mOutput << " " << SolidToken << " " << name << endl;
+    aiVector3D nor;
+    mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl;
+    for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+        aiMesh *mesh = pScene->mMeshes[i];
+        if (nullptr == mesh) {
+            continue;
+        }
+
+        for (unsigned int a = 0; a < mesh->mNumVertices; ++a) {
+            const aiVector3D& v = mesh->mVertices[a];
+            mOutput << "  vertex " << v.x << " " << v.y << " " << v.z << endl;
+            mOutput << "  vertex " << v.x << " " << v.y << " " << v.z << endl;
+            mOutput << "  vertex " << v.x << " " << v.y << " " << v.z << endl;
+        }
+    }
+    mOutput << EndSolidToken << " " << name << endl;
+}
+
+// ------------------------------------------------------------------------------------------------
+void STLExporter::WriteMesh(const aiMesh* m)
 {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[i];
@@ -145,7 +185,7 @@ void STLExporter :: WriteMesh(const aiMesh* m)
             for(unsigned int a = 0; a < f.mNumIndices; ++a) {
                 nor += m->mNormals[f.mIndices[a]];
             }
-            nor.Normalize();
+            nor.NormalizeSafe();
         }
         mOutput << " facet normal " << nor.x << " " << nor.y << " " << nor.z << endl;
         mOutput << "  outer loop" << endl;
@@ -159,7 +199,7 @@ void STLExporter :: WriteMesh(const aiMesh* m)
     }
 }
 
-void STLExporter :: WriteMeshBinary(const aiMesh* m)
+void STLExporter::WriteMeshBinary(const aiMesh* m)
 {
     for (unsigned int i = 0; i < m->mNumFaces; ++i) {
         const aiFace& f = m->mFaces[i];

+ 3 - 6
code/STLExporter.h

@@ -52,8 +52,7 @@ struct aiScene;
 struct aiNode;
 struct aiMesh;
 
-namespace Assimp
-{
+namespace Assimp {
 
 // ------------------------------------------------------------------------------------------------
 /** Helper class to export a given scene to a STL file. */
@@ -62,15 +61,13 @@ class STLExporter
 {
 public:
     /// Constructor for a specific scene to export
-    STLExporter(const char* filename, const aiScene* pScene, bool binary = false);
-
-public:
+    STLExporter(const char* filename, const aiScene* pScene, bool exportPOintClouds, bool binary = false);
 
     /// public stringstreams to write all output into
     std::ostringstream mOutput;
 
 private:
-
+    void WritePointCloud(const std::string &name, const aiScene* pScene);
     void WriteMesh(const aiMesh* m);
     void WriteMeshBinary(const aiMesh* m);
 

+ 1 - 0
code/simd.cpp

@@ -75,4 +75,5 @@ bool CPUSupportsSSE2() {
 #endif
 }
 
+
 } // Namespace Assimp

+ 15 - 7
include/assimp/Exporter.hpp

@@ -115,12 +115,16 @@ public:
         }
     };
 
-
-public:
+    /**
+     *  @brief  The class constructor.
+     */
     Exporter();
+
+    /**
+    *  @brief  The class destructor.
+    */
     ~Exporter();
 
-public:
     // -------------------------------------------------------------------
     /** Supplies a custom IO handler to the exporter to use to open and
      * access files.
@@ -172,8 +176,10 @@ public:
     *   Any IO handlers set via #SetIOHandler are ignored here.
     * @note Use aiCopyScene() to get a modifiable copy of a previously
     *   imported scene. */
-    const aiExportDataBlob* ExportToBlob(const aiScene* pScene, const char* pFormatId, unsigned int pPreprocessing = 0u, const ExportProperties* = NULL);
-    const aiExportDataBlob* ExportToBlob(  const aiScene* pScene, const std::string& pFormatId, unsigned int pPreprocessing = 0u, const ExportProperties* pProperties = NULL);
+    const aiExportDataBlob* ExportToBlob(const aiScene* pScene, const char* pFormatId,
+        unsigned int pPreprocessing = 0u, const ExportProperties* = nullptr);
+    const aiExportDataBlob* ExportToBlob(  const aiScene* pScene, const std::string& pFormatId,
+        unsigned int pPreprocessing = 0u, const ExportProperties* pProperties = nullptr);
 
     // -------------------------------------------------------------------
     /** Convenience function to export directly to a file. Use
@@ -208,8 +214,10 @@ public:
      * @return AI_SUCCESS if everything was fine.
      * @note Use aiCopyScene() to get a modifiable copy of a previously
      *   imported scene.*/
-    aiReturn Export( const aiScene* pScene, const char* pFormatId, const char* pPath, unsigned int pPreprocessing = 0u, const ExportProperties* pProperties = NULL);
-    aiReturn Export( const aiScene* pScene, const std::string& pFormatId, const std::string& pPath,  unsigned int pPreprocessing = 0u, const ExportProperties* pProperties = NULL);
+    aiReturn Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
+        unsigned int pPreprocessing = 0u, const ExportProperties* pProperties = nullptr);
+    aiReturn Export( const aiScene* pScene, const std::string& pFormatId, const std::string& pPath,
+        unsigned int pPreprocessing = 0u, const ExportProperties* pProperties = nullptr);
 
     // -------------------------------------------------------------------
     /** Returns an error description of an error that occurred in #Export

+ 27 - 21
include/assimp/GenericProperty.h

@@ -46,15 +46,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/Importer.hpp>
 #include <assimp/ai_assert.h>
 #include "Hash.h"
-#include <map>
 
+#include <map>
 
 // ------------------------------------------------------------------------------------------------
 template <class T>
-inline bool SetGenericProperty(std::map< unsigned int, T >& list,
-    const char* szName, const T& value)
-{
-    ai_assert(NULL != szName);
+inline
+bool SetGenericProperty(std::map< unsigned int, T >& list,
+        const char* szName, const T& value) {
+    ai_assert(nullptr != szName);
     const uint32_t hash = SuperFastHash(szName);
 
     typename std::map<unsigned int, T>::iterator it = list.find(hash);
@@ -63,20 +63,22 @@ inline bool SetGenericProperty(std::map< unsigned int, T >& list,
         return false;
     }
     (*it).second = value;
+
     return true;
 }
 
 // ------------------------------------------------------------------------------------------------
 template <class T>
-inline const T& GetGenericProperty(const std::map< unsigned int, T >& list,
-    const char* szName, const T& errorReturn)
-{
-    ai_assert(NULL != szName);
+inline
+const T& GetGenericProperty(const std::map< unsigned int, T >& list,
+        const char* szName, const T& errorReturn) {
+    ai_assert(nullptr != szName);
     const uint32_t hash = SuperFastHash(szName);
 
     typename std::map<unsigned int, T>::const_iterator it = list.find(hash);
-    if (it == list.end())
+    if (it == list.end()) {
         return errorReturn;
+    }
 
     return (*it).second;
 }
@@ -85,16 +87,17 @@ inline const T& GetGenericProperty(const std::map< unsigned int, T >& list,
 // Special version for pointer types - they will be deleted when replaced with another value
 // passing NULL removes the whole property
 template <class T>
-inline void SetGenericPropertyPtr(std::map< unsigned int, T* >& list,
-    const char* szName, T* value, bool* bWasExisting = NULL)
-{
-    ai_assert(NULL != szName);
+inline
+void SetGenericPropertyPtr(std::map< unsigned int, T* >& list,
+        const char* szName, T* value, bool* bWasExisting = nullptr ) {
+    ai_assert(nullptr != szName);
     const uint32_t hash = SuperFastHash(szName);
 
     typename std::map<unsigned int, T*>::iterator it = list.find(hash);
     if (it == list.end())   {
-        if (bWasExisting)
+        if (bWasExisting) {
             *bWasExisting = false;
+        }
 
         list.insert(std::pair<unsigned int,T*>( hash, value ));
         return;
@@ -106,20 +109,23 @@ inline void SetGenericPropertyPtr(std::map< unsigned int, T* >& list,
     if (!value) {
         list.erase(it);
     }
-    if (bWasExisting)
+    if (bWasExisting) {
         *bWasExisting = true;
+    }
 }
 
 // ------------------------------------------------------------------------------------------------
 template <class T>
-inline bool HasGenericProperty(const std::map< unsigned int, T >& list,
-    const char* szName)
-{
-    ai_assert(NULL != szName);
+inline
+bool HasGenericProperty(const std::map< unsigned int, T >& list,
+        const char* szName) {
+    ai_assert(nullptr != szName);
     const uint32_t hash = SuperFastHash(szName);
 
     typename std::map<unsigned int, T>::const_iterator it = list.find(hash);
-    if (it == list.end()) return false;
+    if (it == list.end()) {
+        return false;
+    }
 
     return true;
 }

+ 2 - 4
include/assimp/cexport.h

@@ -85,7 +85,6 @@ struct aiExportFormatDesc
  */
 ASSIMP_API size_t aiGetExportFormatCount(void);
 
-
 // --------------------------------------------------------------------------------
 /** Returns a description of the nth export file format. Use #aiGetExportFormatCount()
  * to learn how many export formats are supported. The description must be released by
@@ -186,7 +185,6 @@ ASSIMP_API aiReturn aiExportSceneEx( const C_STRUCT aiScene* pScene,
     C_STRUCT aiFileIO* pIO,
     unsigned int pPreprocessing );
 
-
 // --------------------------------------------------------------------------------
 /** Describes a blob of exported scene data. Use #aiExportSceneToBlob() to create a blob containing an
 * exported scene. The memory referred by this structure is owned by Assimp.
@@ -245,8 +243,8 @@ private:
 * @param pPreprocessing Please see the documentation for #aiExportScene
 * @return the exported data or NULL in case of error
 */
-ASSIMP_API const C_STRUCT aiExportDataBlob* aiExportSceneToBlob( const C_STRUCT aiScene* pScene, const char* pFormatId,  unsigned int pPreprocessing );
-
+ASSIMP_API const C_STRUCT aiExportDataBlob* aiExportSceneToBlob( const C_STRUCT aiScene* pScene, const char* pFormatId,
+    unsigned int pPreprocessing );
 
 // --------------------------------------------------------------------------------
 /** Releases the memory associated with the given exported data. Use this function to free a data blob

+ 5 - 0
include/assimp/config.h.in

@@ -953,6 +953,11 @@ enum aiComponent
 
 #define AI_CONFIG_EXPORT_XFILE_64BIT "EXPORT_XFILE_64BIT"
 
+/**
+ *
+ */
+#define AI_CONFIG_EXPORT_POINT_CLOUDS "EXPORT_POINT_CLOUDS"
+
 /**
  *  @brief  Specifies a gobal key factor for scale, float value
  */

+ 8 - 8
test/models/OBJ/cube_with_vertexcolors.obj

@@ -1,13 +1,13 @@
 g cube
 
-v  0.0  0.0  0.0  124  110  120
-v  0.0  0.0  1.0  24  0  121
-v  0.0  1.0  0.0  4  0  44
-v  0.0  1.0  1.0  224  0  10
-v  1.0  0.0  0.0  24  200  25
-v  1.0  0.0  1.0  124  10  56
-v  1.0  1.0  0.0  78  10  50
-v  1.0  1.0  1.0  23  0  200
+v  0.0  0.0  0.0  0.48627  0.43137  0.47059
+v  0.0  0.0  1.0  0.09412  0.00000  0.47451
+v  0.0  1.0  0.0  0.01569  0.00000  0.17255
+v  0.0  1.0  1.0  0.87843  0.00000  0.03922
+v  1.0  0.0  0.0  0.09412  0.78431  0.09804
+v  1.0  0.0  1.0  0.48627  0.03922  0.21961
+v  1.0  1.0  0.0  0.30588  0.03922  0.19608
+v  1.0  1.0  1.0  0.09020  0.00000  0.78431
 
 vn 0.0 0.0 1.0
 vn 0.0 0.0 -1.0

+ 30 - 0
test/models/OBJ/cube_with_vertexcolors_uni.obj

@@ -0,0 +1,30 @@
+g cube
+
+v  0.0  0.0  0.0  0.0  0.0  0.0
+v  0.0  0.0  1.0  1.0  0.6  0.3
+v  0.0  1.0  0.0  0.0  0.0  0.0
+v  0.0  1.0  1.0  0.3  0.6  1.0
+v  1.0  0.0  0.0  0.0  0.0  0.0
+v  1.0  0.0  1.0  1.0  0.6  0.3
+v  1.0  1.0  0.0  0.0  0.0  0.0
+v  1.0  1.0  1.0  0.3  0.6  1.0
+
+vn 0.0 0.0 1.0
+vn 0.0 0.0 -1.0
+vn 0.0 1.0 0.0
+vn 0.0 -1.0 0.0
+vn 1.0 0.0 0.0
+vn -1.0 0.0 0.0
+
+f 1//2 7//2 5//2
+f 1//2 3//2 7//2
+f 1//6 4//6 3//6
+f 1//6 2//6 4//6
+f 3//3 8//3 7//3
+f 3//3 4//3 8//3
+f 5//5 7//5 8//5
+f 5//5 8//5 6//5
+f 1//4 5//4 6//4
+f 1//4 6//4 2//4
+f 2//1 6//1 8//1
+f 2//1 8//1 4//1

+ 22 - 0
test/unit/utObjImportExport.cpp

@@ -267,6 +267,28 @@ TEST_F( utObjImportExport, issue809_vertex_color_Test ) {
 #endif // ASSIMP_BUILD_NO_EXPORT
 }
 
+TEST_F( utObjImportExport, issue1923_vertex_color_Test ) {
+    ::Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/cube_with_vertexcolors_uni.obj", aiProcess_ValidateDataStructure );
+    EXPECT_NE( nullptr, scene );
+
+    scene = importer.GetOrphanedScene();
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+    ::Assimp::Exporter exporter;
+    const aiExportDataBlob* blob = exporter.ExportToBlob( scene, "obj");
+    EXPECT_NE( nullptr, blob );
+
+    const aiScene *sceneReImport = importer.ReadFileFromMemory( blob->data, blob->size, aiProcess_ValidateDataStructure );
+    EXPECT_NE( nullptr, scene );
+
+    SceneDiffer differ;
+    EXPECT_TRUE( differ.isEqual( scene, sceneReImport ) );
+#endif // ASSIMP_BUILD_NO_EXPORT
+
+    delete scene;
+}
+
 TEST_F( utObjImportExport, issue1453_segfault ) {
     static const std::string ObjModel =
         "v  0.0  0.0  0.0\n"

+ 12 - 11
test/unit/utPLYImportExport.cpp

@@ -138,18 +138,19 @@ TEST_F( utPLYImportExport, vertexColorTest ) {
     EXPECT_EQ(2u, first_face.mIndices[2]);
 }
 
-//Test issue #623, PLY importer should not automatically create faces
+// Test issue #623, PLY importer should not automatically create faces
 TEST_F(utPLYImportExport, pointcloudTest) {
-  Assimp::Importer importer;
-  //Could not use aiProcess_ValidateDataStructure since it's missing faces.
-  const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/PLY/issue623.ply", 0);
-  EXPECT_NE(nullptr, scene);
-
-  EXPECT_EQ(1u, scene->mNumMeshes);
-  EXPECT_NE(nullptr, scene->mMeshes[0]);
-  EXPECT_EQ(24u, scene->mMeshes[0]->mNumVertices);
-  EXPECT_EQ(aiPrimitiveType::aiPrimitiveType_POINT, scene->mMeshes[0]->mPrimitiveTypes);
-  EXPECT_EQ(0u, scene->mMeshes[0]->mNumFaces);
+    Assimp::Importer importer;
+
+    //Could not use aiProcess_ValidateDataStructure since it's missing faces.
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/PLY/issue623.ply", 0);
+    EXPECT_NE(nullptr, scene);
+
+    EXPECT_EQ(1u, scene->mNumMeshes);
+    EXPECT_NE(nullptr, scene->mMeshes[0]);
+    EXPECT_EQ(24u, scene->mMeshes[0]->mNumVertices);
+    EXPECT_EQ(aiPrimitiveType::aiPrimitiveType_POINT, scene->mMeshes[0]->mPrimitiveTypes);
+    EXPECT_EQ(0u, scene->mMeshes[0]->mNumFaces);
 }
 
 static const char *test_file =

+ 74 - 0
test/unit/utSTLImportExport.cpp

@@ -47,6 +47,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <assimp/Importer.hpp>
 #include <assimp/postprocess.h>
+#include <assimp/Exporter.hpp>
+#include <assimp/scene.h>
+
+#include <vector>
 
 using namespace Assimp;
 
@@ -68,3 +72,73 @@ TEST_F( utSTLImporterExporter, test_with_two_solids ) {
     const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/STL/triangle_with_two_solids.stl", aiProcess_ValidateDataStructure );
     EXPECT_NE( nullptr, scene );
 }
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+
+TEST_F(utSTLImporterExporter, exporterTest) {
+    Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/STL/Spider_ascii.stl", aiProcess_ValidateDataStructure);
+
+    Assimp::Exporter mAiExporter;
+    mAiExporter.Export( scene, "stl", "spiderExport.stl" );
+
+    const aiScene *scene2 = importer.ReadFile("spiderExport.stl", aiProcess_ValidateDataStructure);
+    EXPECT_NE(nullptr, scene2);
+}
+
+TEST_F(utSTLImporterExporter, test_export_pointclouds) {
+    struct XYZ {
+        float x, y, z;
+    };
+
+    std::vector<XYZ> points;
+
+    for (size_t i = 0; i < 10; ++i) {
+        XYZ current;
+        current.x = static_cast<float>(i);
+        current.y = static_cast<float>(i);
+        current.z = static_cast<float>(i);
+        points.push_back(current);
+    }
+    aiScene scene;
+    scene.mRootNode = new aiNode();
+
+    scene.mMeshes = new aiMesh*[1];
+    scene.mMeshes[0] = nullptr;
+    scene.mNumMeshes = 1;
+
+    scene.mMaterials = new aiMaterial*[1];
+    scene.mMaterials[0] = nullptr;
+    scene.mNumMaterials = 1;
+
+    scene.mMaterials[0] = new aiMaterial();
+
+    scene.mMeshes[0] = new aiMesh();
+    scene.mMeshes[0]->mMaterialIndex = 0;
+
+    scene.mRootNode->mMeshes = new unsigned int[1];
+    scene.mRootNode->mMeshes[0] = 0;
+    scene.mRootNode->mNumMeshes = 1;
+
+    auto pMesh = scene.mMeshes[0];
+
+    long numValidPoints = points.size();
+
+    pMesh->mVertices = new aiVector3D[numValidPoints];
+    pMesh->mNumVertices = numValidPoints;
+
+    int i = 0;
+    for (XYZ &p : points) {
+        pMesh->mVertices[i] = aiVector3D(p.x, p.y, p.z);
+        ++i;
+    }
+
+    Assimp::Exporter mAiExporter;
+    ExportProperties *properties = new ExportProperties;
+    properties->SetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS, true);
+    mAiExporter.Export(&scene, "stl", "testExport.stl", 0, properties );
+
+    delete properties;
+}
+
+#endif