diff --git a/code/ObjExporter.cpp b/code/ObjExporter.cpp index 2a1c35f70..54c9a490b 100644 --- a/code/ObjExporter.cpp +++ b/code/ObjExporter.cpp @@ -38,8 +38,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ - - #ifndef ASSIMP_BUILD_NO_EXPORT #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER @@ -53,9 +51,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include - using namespace Assimp; -namespace Assimp { + +namespace Assimp { // ------------------------------------------------------------------------------------------------ // Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp @@ -90,7 +88,10 @@ ObjExporter :: ObjExporter(const char* _filename, const aiScene* pScene) : filename(_filename) , pScene(pScene) , endl("\n") -{ +, vp() +, vn() +, vt() +, vc() { // 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); @@ -170,7 +171,6 @@ void ObjExporter::WriteMaterialFile() mOutputMat << "Tf " << c.r << " " << c.g << " " << c.b << endl; } - ai_real o; if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) { mOutputMat << "d " << o << endl; @@ -213,8 +213,7 @@ void ObjExporter::WriteMaterialFile() } // ------------------------------------------------------------------------------------------------ -void ObjExporter :: WriteGeometryFile() -{ +void ObjExporter::WriteGeometryFile() { WriteHeader(mOutput); mOutput << "mtllib " << GetMaterialLibName() << endl << endl; @@ -222,11 +221,21 @@ void ObjExporter :: WriteGeometryFile() aiMatrix4x4 mBase; AddNode(pScene->mRootNode, mBase); - // write vertex positions - vpMap.getVectors(vp); - mOutput << "# " << vp.size() << " vertex positions" << endl; - for(const aiVector3D& v : vp) { - mOutput << "v " << v.x << " " << v.y << " " << v.z << endl; + // write vertex positions with colors, if any + vpMap.getVectors( vp ); + vcMap.getColors( vc ); + if ( vc.empty() ) { + mOutput << "# " << vp.size() << " vertex positions" << endl; + for ( const aiVector3D& v : vp ) { + mOutput << "v " << v.x << " " << v.y << " " << v.z << endl; + } + } else { + mOutput << "# " << vp.size() << " vertex positions and colors" << endl; + size_t colIdx = 0; + for ( const aiVector3D& v : vp ) { + mOutput << "v " << v.x << " " << v.y << " " << v.z << " " << vc[ colIdx ].r << " " << vc[ colIdx ].g << " " << vc[ colIdx ].b << endl; + colIdx++; + } } mOutput << endl; @@ -279,8 +288,7 @@ void ObjExporter :: WriteGeometryFile() } // ------------------------------------------------------------------------------------------------ -int ObjExporter::vecIndexMap::getIndex(const aiVector3D& vec) -{ +int ObjExporter::vecIndexMap::getIndex(const aiVector3D& vec) { vecIndexMap::dataType::iterator vertIt = vecMap.find(vec); // vertex already exists, so reference it if(vertIt != vecMap.end()){ @@ -293,8 +301,7 @@ int ObjExporter::vecIndexMap::getIndex(const aiVector3D& vec) } // ------------------------------------------------------------------------------------------------ -void ObjExporter::vecIndexMap::getVectors( std::vector& vecs ) -{ +void ObjExporter::vecIndexMap::getVectors( std::vector& vecs ) { vecs.resize(vecMap.size()); for(vecIndexMap::dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){ vecs[it->second-1] = it->first; @@ -302,8 +309,29 @@ void ObjExporter::vecIndexMap::getVectors( std::vector& vecs ) } // ------------------------------------------------------------------------------------------------ -void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) -{ +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 &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) { meshes.push_back(MeshInstance()); MeshInstance& mesh = meshes.back(); @@ -337,15 +365,20 @@ void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4 if (m->mNormals) { aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx]; face.indices[a].vn = vnMap.getIndex(norm); - } - else{ + } else { face.indices[a].vn = 0; } - if (m->mTextureCoords[0]) { - face.indices[a].vt = vtMap.getIndex(m->mTextureCoords[0][idx]); + if ( nullptr != m->mColors[ 0 ] ) { + aiColor4D col4 = m->mColors[ 0 ][ idx ]; + face.indices[ a ].vc = vcMap.getIndex( col4 ); + } else { + face.indices[ a ].vc = 0; } - else{ + + if ( m->mTextureCoords[ 0 ] ) { + face.indices[a].vt = vtMap.getIndex(m->mTextureCoords[0][idx]); + } else { face.indices[a].vt = 0; } } diff --git a/code/ObjExporter.h b/code/ObjExporter.h index 9d817f067..00baec78f 100644 --- a/code/ObjExporter.h +++ b/code/ObjExporter.h @@ -59,34 +59,33 @@ namespace Assimp // ------------------------------------------------------------------------------------------------ /** Helper class to export a given scene to an OBJ file. */ // ------------------------------------------------------------------------------------------------ -class ObjExporter -{ +class ObjExporter { public: /// Constructor for a specific scene to export ObjExporter(const char* filename, const aiScene* pScene); public: - std::string GetMaterialLibName(); std::string GetMaterialLibFileName(); public: - - /// public stringstreams to write all output into + /// public string-streams to write all output into std::ostringstream mOutput, mOutputMat; private: // intermediate data structures - struct FaceVertex - { + struct FaceVertex { FaceVertex() - : vp(),vn(),vt() - { + : vp() + , vn() + , vt() + , vc() { + // empty } // one-based, 0 means: 'does not exist' - unsigned int vp,vn,vt; + unsigned int vp, vn, vt, vc; }; struct Face { @@ -111,17 +110,14 @@ private: void AddNode(const aiNode* nd, const aiMatrix4x4& mParent); private: - const std::string filename; const aiScene* const pScene; std::vector vp, vn, vt; + std::vector vc; - - struct aiVectorCompare - { - bool operator() (const aiVector3D& a, const aiVector3D& b) const - { + 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; @@ -131,21 +127,52 @@ private: } }; - class vecIndexMap - { + 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; + return false; + } + }; + + class vecIndexMap { int mNextIndex; typedef std::map dataType; dataType vecMap; + public: - - vecIndexMap():mNextIndex(1) - {} + vecIndexMap() + : mNextIndex(1) { + // empty + } int getIndex(const aiVector3D& vec); void getVectors( std::vector& vecs ); }; + class colIndexMap { + int mNextIndex; + typedef std::map dataType; + dataType colMap; + + public: + colIndexMap() + : mNextIndex( 1 ) { + // empty + } + + int getIndex( const aiColor4D& col ); + void getColors( std::vector &colors ); + }; + vecIndexMap vpMap, vnMap, vtMap; + colIndexMap vcMap; std::vector meshes; // this endl() doesn't flush() the stream diff --git a/test/unit/AbstractImportExportBase.h b/test/unit/AbstractImportExportBase.h index 32c52d9ab..529b9cf7d 100644 --- a/test/unit/AbstractImportExportBase.h +++ b/test/unit/AbstractImportExportBase.h @@ -46,4 +46,10 @@ class AbstractImportExportBase : public ::testing::Test { public: virtual ~AbstractImportExportBase(); virtual bool importerTest() = 0; + virtual bool exporterTest(); }; + +inline +bool AbstractImportExportBase::exporterTest() { + return true; +} diff --git a/test/unit/utObjImportExport.cpp b/test/unit/utObjImportExport.cpp index c6f597b9d..49d02c37e 100644 --- a/test/unit/utObjImportExport.cpp +++ b/test/unit/utObjImportExport.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AbstractImportExportBase.h" #include +#include #include using namespace Assimp; @@ -194,6 +195,16 @@ protected: return nullptr != scene; } + virtual bool exporterTest() { + Assimp::Importer importer; + Assimp::Exporter exporter; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj", 0 ); + EXPECT_NE( nullptr, scene ); + EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/spider.obj" ) ); + + return true; + } + protected: Assimp::Importer *m_im; aiScene *m_expectedScene; @@ -203,6 +214,10 @@ TEST_F( utObjImportExport, importObjFromFileTest ) { EXPECT_TRUE( importerTest() ); } +TEST_F( utObjImportExport, exportObjFromFileTest ) { + EXPECT_TRUE( exporterTest() ); +} + TEST_F( utObjImportExport, obj_import_test ) { const aiScene *scene = m_im->ReadFileFromMemory( (void*) ObjModel.c_str(), ObjModel.size(), 0 ); aiScene *expected = createScene(); @@ -219,3 +234,12 @@ TEST_F( utObjImportExport, issue1111_no_mat_name_Test ) { const aiScene *scene = m_im->ReadFileFromMemory( ( void* ) ObjModel_Issue1111.c_str(), ObjModel_Issue1111.size(), 0 ); EXPECT_NE( nullptr, scene ); } + +TEST_F( utObjImportExport, issue809_vertex_color_Test ) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/OBJ/cube_with_vertexcolors.obj", 0 ); + EXPECT_NE( nullptr, scene ); + + Assimp::Exporter exporter; + EXPECT_EQ( aiReturn_SUCCESS, exporter.Export( scene, "obj", ASSIMP_TEST_MODELS_DIR "/OBJ/test.obj" ) ); +}