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#ifndef ASSIMP_BUILD_NO_EXPORT
43#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
44
45#include "ObjExporter.h"
46#include "Exceptional.h"
47#include "StringComparison.h"
48#include <assimp/version.h>
49#include <assimp/IOSystem.hpp>
50#include <assimp/Exporter.hpp>
51#include <assimp/material.h>
52#include <assimp/scene.h>
53#include <memory>
54
55using namespace Assimp;
56
57namespace Assimp {
58
59// ------------------------------------------------------------------------------------------------
60// Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp
61void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) {
62 // invoke the exporter
63 ObjExporter exporter(pFile, pScene);
64
65 if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
66 throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
67 }
68
69 // we're still here - export successfully completed. Write both the main OBJ file and the material script
70 {
71 std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
72 if(outfile == NULL) {
73 throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
74 }
75 outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
76 }
77 {
78 std::unique_ptr<IOStream> outfile (pIOSystem->Open(exporter.GetMaterialLibFileName(),"wt"));
79 if(outfile == NULL) {
80 throw DeadlyExportError("could not open output .mtl file: " + std::string(exporter.GetMaterialLibFileName()));
81 }
82 outfile->Write( exporter.mOutputMat.str().c_str(), static_cast<size_t>(exporter.mOutputMat.tellp()),1);
83 }
84}
85
86// ------------------------------------------------------------------------------------------------
87// Worker function for exporting a scene to Wavefront OBJ without the material file. Prototyped and registered in Exporter.cpp
88void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties) {
89 // invoke the exporter
90 ObjExporter exporter(pFile, pScene, true);
91
92 if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
93 throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
94 }
95
96 // we're still here - export successfully completed. Write both the main OBJ file and the material script
97 {
98 std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
99 if(outfile == NULL) {
100 throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
101 }
102 outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
103 }
104
105
106}
107
108} // end of namespace Assimp
109
110static const std::string MaterialExt = ".mtl";
111
112// ------------------------------------------------------------------------------------------------
113ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl)
114: filename(_filename)
115, pScene(pScene)
116, vp()
117, vn()
118, vt()
119, vc()
120, mVpMap()
121, mVnMap()
122, mVtMap()
123, mVcMap()
124, mMeshes()
125, endl("\n") {
126 // make sure that all formatting happens using the standard, C locale and not the user's current locale
127 const std::locale& l = std::locale("C");
128 mOutput.imbue(l);
129 mOutput.precision(16);
130 mOutputMat.imbue(l);
131 mOutputMat.precision(16);
132
133 WriteGeometryFile(noMtl);
134 if (!noMtl)
135 WriteMaterialFile();
136}
137
138// ------------------------------------------------------------------------------------------------
139ObjExporter::~ObjExporter() {
140
141}
142
143// ------------------------------------------------------------------------------------------------
144std::string ObjExporter :: GetMaterialLibName()
145{
146 // within the Obj file, we use just the relative file name with the path stripped
147 const std::string& s = GetMaterialLibFileName();
148 std::string::size_type il = s.find_last_of("/\\");
149 if (il != std::string::npos) {
150 return s.substr(il + 1);
151 }
152
153 return s;
154}
155
156// ------------------------------------------------------------------------------------------------
157std::string ObjExporter::GetMaterialLibFileName() {
158 // Remove existing .obj file extension so that the final material file name will be fileName.mtl and not fileName.obj.mtl
159 size_t lastdot = filename.find_last_of('.');
160 if (lastdot != std::string::npos)
161 return filename.substr(0, lastdot) + MaterialExt;
162
163 return filename + MaterialExt;
164}
165
166// ------------------------------------------------------------------------------------------------
167void ObjExporter::WriteHeader(std::ostringstream& out) {
168 out << "# File produced by Open Asset Import Library (http://www.assimp.sf.net)" << endl;
169 out << "# (assimp v" << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.'
170 << aiGetVersionRevision() << ")" << endl << endl;
171}
172
173// ------------------------------------------------------------------------------------------------
174std::string ObjExporter::GetMaterialName(unsigned int index)
175{
176 const aiMaterial* const mat = pScene->mMaterials[index];
177 if ( nullptr == mat ) {
178 static const std::string EmptyStr;
179 return EmptyStr;
180 }
181
182 aiString s;
183 if(AI_SUCCESS == mat->Get(AI_MATKEY_NAME,s)) {
184 return std::string(s.data,s.length);
185 }
186
187 char number[ sizeof(unsigned int) * 3 + 1 ];
188 ASSIMP_itoa10(number,index);
189 return "$Material_" + std::string(number);
190}
191
192// ------------------------------------------------------------------------------------------------
193void ObjExporter::WriteMaterialFile()
194{
195 WriteHeader(mOutputMat);
196
197 for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
198 const aiMaterial* const mat = pScene->mMaterials[i];
199
200 int illum = 1;
201 mOutputMat << "newmtl " << GetMaterialName(i) << endl;
202
203 aiColor4D c;
204 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE,c)) {
205 mOutputMat << "Kd " << c.r << " " << c.g << " " << c.b << endl;
206 }
207 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_AMBIENT,c)) {
208 mOutputMat << "Ka " << c.r << " " << c.g << " " << c.b << endl;
209 }
210 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR,c)) {
211 mOutputMat << "Ks " << c.r << " " << c.g << " " << c.b << endl;
212 }
213 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_EMISSIVE,c)) {
214 mOutputMat << "Ke " << c.r << " " << c.g << " " << c.b << endl;
215 }
216 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_TRANSPARENT,c)) {
217 mOutputMat << "Tf " << c.r << " " << c.g << " " << c.b << endl;
218 }
219
220 ai_real o;
221 if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) {
222 mOutputMat << "d " << o << endl;
223 }
224 if(AI_SUCCESS == mat->Get(AI_MATKEY_REFRACTI,o)) {
225 mOutputMat << "Ni " << o << endl;
226 }
227
228 if(AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS,o) && o) {
229 mOutputMat << "Ns " << o << endl;
230 illum = 2;
231 }
232
233 mOutputMat << "illum " << illum << endl;
234
235 aiString s;
236 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_DIFFUSE(0),s)) {
237 mOutputMat << "map_Kd " << s.data << endl;
238 }
239 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_AMBIENT(0),s)) {
240 mOutputMat << "map_Ka " << s.data << endl;
241 }
242 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SPECULAR(0),s)) {
243 mOutputMat << "map_Ks " << s.data << endl;
244 }
245 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SHININESS(0),s)) {
246 mOutputMat << "map_Ns " << s.data << endl;
247 }
248 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_OPACITY(0),s)) {
249 mOutputMat << "map_d " << s.data << endl;
250 }
251 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_HEIGHT(0),s) || AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_NORMALS(0),s)) {
252 // implementations seem to vary here, so write both variants
253 mOutputMat << "bump " << s.data << endl;
254 mOutputMat << "map_bump " << s.data << endl;
255 }
256
257 mOutputMat << endl;
258 }
259}
260
261void ObjExporter::WriteGeometryFile(bool noMtl) {
262 WriteHeader(mOutput);
263 if (!noMtl)
264 mOutput << "mtllib " << GetMaterialLibName() << endl << endl;
265
266 // collect mesh geometry
267 aiMatrix4x4 mBase;
268 AddNode(pScene->mRootNode, mBase);
269
270 // write vertex positions with colors, if any
271 mVpMap.getVectors( vp );
272 mVcMap.getColors( vc );
273 if ( vc.empty() ) {
274 mOutput << "# " << vp.size() << " vertex positions" << endl;
275 for ( const aiVector3D& v : vp ) {
276 mOutput << "v " << v.x << " " << v.y << " " << v.z << endl;
277 }
278 } else {
279 mOutput << "# " << vp.size() << " vertex positions and colors" << endl;
280 size_t colIdx = 0;
281 for ( const aiVector3D& v : vp ) {
282 if ( colIdx < vc.size() ) {
283 mOutput << "v " << v.x << " " << v.y << " " << v.z << " " << vc[ colIdx ].r << " " << vc[ colIdx ].g << " " << vc[ colIdx ].b << endl;
284 }
285 ++colIdx;
286 }
287 }
288 mOutput << endl;
289
290 // write uv coordinates
291 mVtMap.getVectors(vt);
292 mOutput << "# " << vt.size() << " UV coordinates" << endl;
293 for(const aiVector3D& v : vt) {
294 mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl;
295 }
296 mOutput << endl;
297
298 // write vertex normals
299 mVnMap.getVectors(vn);
300 mOutput << "# " << vn.size() << " vertex normals" << endl;
301 for(const aiVector3D& v : vn) {
302 mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl;
303 }
304 mOutput << endl;
305
306 // now write all mesh instances
307 for(const MeshInstance& m : mMeshes) {
308 mOutput << "# Mesh \'" << m.name << "\' with " << m.faces.size() << " faces" << endl;
309 if (!m.name.empty()) {
310 mOutput << "g " << m.name << endl;
311 }
312 if (!noMtl)
313 mOutput << "usemtl " << m.matname << endl;
314
315 for(const Face& f : m.faces) {
316 mOutput << f.kind << ' ';
317 for(const FaceVertex& fv : f.indices) {
318 mOutput << ' ' << fv.vp;
319
320 if (f.kind != 'p') {
321 if (fv.vt || f.kind == 'f') {
322 mOutput << '/';
323 }
324 if (fv.vt) {
325 mOutput << fv.vt;
326 }
327 if (f.kind == 'f' && fv.vn) {
328 mOutput << '/' << fv.vn;
329 }
330 }
331 }
332
333 mOutput << endl;
334 }
335 mOutput << endl;
336 }
337}
338
339// ------------------------------------------------------------------------------------------------
340int ObjExporter::vecIndexMap::getIndex(const aiVector3D& vec) {
341 vecIndexMap::dataType::iterator vertIt = vecMap.find(vec);
342 // vertex already exists, so reference it
343 if(vertIt != vecMap.end()){
344 return vertIt->second;
345 }
346 vecMap[vec] = mNextIndex;
347 int ret = mNextIndex;
348 mNextIndex++;
349 return ret;
350}
351
352// ------------------------------------------------------------------------------------------------
353void ObjExporter::vecIndexMap::getVectors( std::vector<aiVector3D>& vecs ) {
354 vecs.resize(vecMap.size());
355 for(vecIndexMap::dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){
356 vecs[it->second-1] = it->first;
357 }
358}
359
360// ------------------------------------------------------------------------------------------------
361int ObjExporter::colIndexMap::getIndex( const aiColor4D& col ) {
362 colIndexMap::dataType::iterator vertIt = colMap.find( col );
363 // vertex already exists, so reference it
364 if ( vertIt != colMap.end() ) {
365 return vertIt->second;
366 }
367 colMap[ col ] = mNextIndex;
368 int ret = mNextIndex;
369 mNextIndex++;
370
371 return ret;
372}
373
374// ------------------------------------------------------------------------------------------------
375void ObjExporter::colIndexMap::getColors( std::vector<aiColor4D> &colors ) {
376 colors.resize( colMap.size() );
377 for ( colIndexMap::dataType::iterator it = colMap.begin(); it != colMap.end(); ++it ) {
378 colors[ it->second - 1 ] = it->first;
379 }
380}
381
382// ------------------------------------------------------------------------------------------------
383void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat) {
384 mMeshes.push_back(MeshInstance());
385 MeshInstance& mesh = mMeshes.back();
386
387 mesh.name = std::string( name.data, name.length );
388 mesh.matname = GetMaterialName(m->mMaterialIndex);
389
390 mesh.faces.resize(m->mNumFaces);
391
392 for(unsigned int i = 0; i < m->mNumFaces; ++i) {
393 const aiFace& f = m->mFaces[i];
394
395 Face& face = mesh.faces[i];
396 switch (f.mNumIndices) {
397 case 1:
398 face.kind = 'p';
399 break;
400 case 2:
401 face.kind = 'l';
402 break;
403 default:
404 face.kind = 'f';
405 }
406 face.indices.resize(f.mNumIndices);
407
408 for(unsigned int a = 0; a < f.mNumIndices; ++a) {
409 const unsigned int idx = f.mIndices[a];
410
411 aiVector3D vert = mat * m->mVertices[idx];
412 face.indices[a].vp = mVpMap.getIndex(vert);
413
414 if (m->mNormals) {
415 aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx];
416 face.indices[a].vn = mVnMap.getIndex(norm);
417 } else {
418 face.indices[a].vn = 0;
419 }
420
421 if ( nullptr != m->mColors[ 0 ] ) {
422 aiColor4D col4 = m->mColors[ 0 ][ idx ];
423 face.indices[ a ].vc = mVcMap.getIndex( col4 );
424 } else {
425 face.indices[ a ].vc = 0;
426 }
427
428 if ( m->mTextureCoords[ 0 ] ) {
429 face.indices[a].vt = mVtMap.getIndex(m->mTextureCoords[0][idx]);
430 } else {
431 face.indices[a].vt = 0;
432 }
433 }
434 }
435}
436
437// ------------------------------------------------------------------------------------------------
438void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent)
439{
440 const aiMatrix4x4& mAbs = mParent * nd->mTransformation;
441
442 for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {
443 AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs);
444 }
445
446 for(unsigned int i = 0; i < nd->mNumChildren; ++i) {
447 AddNode(nd->mChildren[i], mAbs);
448 }
449}
450
451// ------------------------------------------------------------------------------------------------
452
453#endif // ASSIMP_BUILD_NO_OBJ_EXPORTER
454#endif // ASSIMP_BUILD_NO_EXPORT
455