1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4
5 Copyright (c) 2006-2016, assimp team
6 All rights reserved.
7
8 Redistribution and use of this software in source and binary forms,
9 with or without modification, are permitted provided that the
10 following conditions are met:
11
12 * Redistributions of source code must retain the above
13 copyright notice, this list of conditions and the
14 following disclaimer.
15
16 * Redistributions in binary form must reproduce the above
17 copyright notice, this list of conditions and the
18 following disclaimer in the documentation and/or other
19 materials provided with the distribution.
20
21 * Neither the name of the assimp team, nor the names of its
22 contributors may be used to endorse or promote products
23 derived from this software without specific prior
24 written permission of the assimp team.
25
26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37
38 ----------------------------------------------------------------------
39 */
40
41
42
43 #ifndef ASSIMP_BUILD_NO_EXPORT
44 #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
45
46 #include "ObjExporter.h"
47 #include "Exceptional.h"
48 #include "StringComparison.h"
49 #include <assimp/version.h>
50 #include <assimp/IOSystem.hpp>
51 #include <assimp/Exporter.hpp>
52 #include <assimp/material.h>
53 #include <assimp/scene.h>
54 #include <memory>
55
56
57 using namespace Assimp;
58 namespace Assimp {
59
60 // ------------------------------------------------------------------------------------------------
61 // Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp
ExportSceneObj(const char * pFile,IOSystem * pIOSystem,const aiScene * pScene,const ExportProperties * pProperties)62 void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* pProperties)
63 {
64 // invoke the exporter
65 ObjExporter exporter(pFile, pScene);
66
67 // we're still here - export successfully completed. Write both the main OBJ file and the material script
68 {
69 std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
70 if(outfile == NULL) {
71 throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
72 }
73 outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
74 }
75 {
76 std::unique_ptr<IOStream> outfile (pIOSystem->Open(exporter.GetMaterialLibFileName(),"wt"));
77 if(outfile == NULL) {
78 throw DeadlyExportError("could not open output .mtl file: " + std::string(exporter.GetMaterialLibFileName()));
79 }
80 outfile->Write( exporter.mOutputMat.str().c_str(), static_cast<size_t>(exporter.mOutputMat.tellp()),1);
81 }
82 }
83
84 } // end of namespace Assimp
85
86 static const std::string MaterialExt = ".mtl";
87
88 // ------------------------------------------------------------------------------------------------
ObjExporter(const char * _filename,const aiScene * pScene)89 ObjExporter :: ObjExporter(const char* _filename, const aiScene* pScene)
90 : filename(_filename)
91 , pScene(pScene)
92 , endl("\n")
93 {
94 // make sure that all formatting happens using the standard, C locale and not the user's current locale
95 const std::locale& l = std::locale("C");
96 mOutput.imbue(l);
97 mOutput.precision(16);
98 mOutputMat.imbue(l);
99 mOutputMat.precision(16);
100
101 WriteGeometryFile();
102 WriteMaterialFile();
103 }
104
105 // ------------------------------------------------------------------------------------------------
GetMaterialLibName()106 std::string ObjExporter :: GetMaterialLibName()
107 {
108 // within the Obj file, we use just the relative file name with the path stripped
109 const std::string& s = GetMaterialLibFileName();
110 std::string::size_type il = s.find_last_of("/\\");
111 if (il != std::string::npos) {
112 return s.substr(il + 1);
113 }
114
115 return s;
116 }
117
118 // ------------------------------------------------------------------------------------------------
GetMaterialLibFileName()119 std::string ObjExporter :: GetMaterialLibFileName()
120 {
121 return filename + MaterialExt;
122 }
123
124 // ------------------------------------------------------------------------------------------------
WriteHeader(std::ostringstream & out)125 void ObjExporter :: WriteHeader(std::ostringstream& out)
126 {
127 out << "# File produced by Open Asset Import Library (http://www.assimp.sf.net)" << endl;
128 out << "# (assimp v" << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.' << aiGetVersionRevision() << ")" << endl << endl;
129 }
130
131 // ------------------------------------------------------------------------------------------------
GetMaterialName(unsigned int index)132 std::string ObjExporter :: GetMaterialName(unsigned int index)
133 {
134 const aiMaterial* const mat = pScene->mMaterials[index];
135 aiString s;
136 if(AI_SUCCESS == mat->Get(AI_MATKEY_NAME,s)) {
137 return std::string(s.data,s.length);
138 }
139
140 char number[ sizeof(unsigned int) * 3 + 1 ];
141 ASSIMP_itoa10(number,index);
142 return "$Material_" + std::string(number);
143 }
144
145 // ------------------------------------------------------------------------------------------------
WriteMaterialFile()146 void ObjExporter::WriteMaterialFile()
147 {
148 WriteHeader(mOutputMat);
149
150 for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
151 const aiMaterial* const mat = pScene->mMaterials[i];
152
153 int illum = 1;
154 mOutputMat << "newmtl " << GetMaterialName(i) << endl;
155
156 aiColor4D c;
157 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE,c)) {
158 mOutputMat << "Kd " << c.r << " " << c.g << " " << c.b << endl;
159 }
160 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_AMBIENT,c)) {
161 mOutputMat << "Ka " << c.r << " " << c.g << " " << c.b << endl;
162 }
163 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR,c)) {
164 mOutputMat << "Ks " << c.r << " " << c.g << " " << c.b << endl;
165 }
166 if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_EMISSIVE,c)) {
167 mOutputMat << "Ke " << c.r << " " << c.g << " " << c.b << endl;
168 }
169
170 float o;
171 if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) {
172 mOutputMat << "d " << o << endl;
173 }
174
175 if(AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS,o) && o) {
176 mOutputMat << "Ns " << o << endl;
177 illum = 2;
178 }
179
180 mOutputMat << "illum " << illum << endl;
181
182 aiString s;
183 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_DIFFUSE(0),s)) {
184 mOutputMat << "map_Kd " << s.data << endl;
185 }
186 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_AMBIENT(0),s)) {
187 mOutputMat << "map_Ka " << s.data << endl;
188 }
189 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SPECULAR(0),s)) {
190 mOutputMat << "map_Ks " << s.data << endl;
191 }
192 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SHININESS(0),s)) {
193 mOutputMat << "map_Ns " << s.data << endl;
194 }
195 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_OPACITY(0),s)) {
196 mOutputMat << "map_d " << s.data << endl;
197 }
198 if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_HEIGHT(0),s) || AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_NORMALS(0),s)) {
199 // implementations seem to vary here, so write both variants
200 mOutputMat << "bump " << s.data << endl;
201 mOutputMat << "map_bump " << s.data << endl;
202 }
203
204 mOutputMat << endl;
205 }
206 }
207
208 // ------------------------------------------------------------------------------------------------
WriteGeometryFile()209 void ObjExporter :: WriteGeometryFile()
210 {
211 WriteHeader(mOutput);
212 mOutput << "mtllib " << GetMaterialLibName() << endl << endl;
213
214 // collect mesh geometry
215 aiMatrix4x4 mBase;
216 AddNode(pScene->mRootNode, mBase);
217
218 // write vertex positions
219 vpMap.getVectors(vp);
220 mOutput << "# " << vp.size() << " vertex positions" << endl;
221 for(const aiVector3D& v : vp) {
222 mOutput << "v " << v.x << " " << v.y << " " << v.z << endl;
223 }
224 mOutput << endl;
225
226 // write uv coordinates
227 vtMap.getVectors(vt);
228 mOutput << "# " << vt.size() << " UV coordinates" << endl;
229 for(const aiVector3D& v : vt) {
230 mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl;
231 }
232 mOutput << endl;
233
234 // write vertex normals
235 vnMap.getVectors(vn);
236 mOutput << "# " << vn.size() << " vertex normals" << endl;
237 for(const aiVector3D& v : vn) {
238 mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl;
239 }
240 mOutput << endl;
241
242 // now write all mesh instances
243 for(const MeshInstance& m : meshes) {
244 mOutput << "# Mesh \'" << m.name << "\' with " << m.faces.size() << " faces" << endl;
245 if (!m.name.empty()) {
246 mOutput << "g " << m.name << endl;
247 }
248 mOutput << "usemtl " << m.matname << endl;
249
250 for(const Face& f : m.faces) {
251 mOutput << f.kind << ' ';
252 for(const FaceVertex& fv : f.indices) {
253 mOutput << ' ' << fv.vp;
254
255 if (f.kind != 'p') {
256 if (fv.vt || f.kind == 'f') {
257 mOutput << '/';
258 }
259 if (fv.vt) {
260 mOutput << fv.vt;
261 }
262 if (f.kind == 'f' && fv.vn) {
263 mOutput << '/' << fv.vn;
264 }
265 }
266 }
267
268 mOutput << endl;
269 }
270 mOutput << endl;
271 }
272 }
273
274 // ------------------------------------------------------------------------------------------------
getIndex(const aiVector3D & vec)275 int ObjExporter::vecIndexMap::getIndex(const aiVector3D& vec)
276 {
277 vecIndexMap::dataType::iterator vertIt = vecMap.find(vec);
278 // vertex already exists, so reference it
279 if(vertIt != vecMap.end()){
280 return vertIt->second;
281 }
282 vecMap[vec] = mNextIndex;
283 int ret = mNextIndex;
284 mNextIndex++;
285 return ret;
286 }
287
288 // ------------------------------------------------------------------------------------------------
getVectors(std::vector<aiVector3D> & vecs)289 void ObjExporter::vecIndexMap::getVectors( std::vector<aiVector3D>& vecs )
290 {
291 vecs.resize(vecMap.size());
292 for(vecIndexMap::dataType::iterator it = vecMap.begin(); it != vecMap.end(); ++it){
293 vecs[it->second-1] = it->first;
294 }
295 }
296
297 // ------------------------------------------------------------------------------------------------
AddMesh(const aiString & name,const aiMesh * m,const aiMatrix4x4 & mat)298 void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat)
299 {
300 meshes.push_back(MeshInstance());
301 MeshInstance& mesh = meshes.back();
302
303 mesh.name = std::string(name.data,name.length) + (m->mName.length ? "_" + std::string(m->mName.data,m->mName.length) : "");
304 mesh.matname = GetMaterialName(m->mMaterialIndex);
305
306 mesh.faces.resize(m->mNumFaces);
307
308 for(unsigned int i = 0; i < m->mNumFaces; ++i) {
309 const aiFace& f = m->mFaces[i];
310
311 Face& face = mesh.faces[i];
312 switch (f.mNumIndices) {
313 case 1:
314 face.kind = 'p';
315 break;
316 case 2:
317 face.kind = 'l';
318 break;
319 default:
320 face.kind = 'f';
321 }
322 face.indices.resize(f.mNumIndices);
323
324 for(unsigned int a = 0; a < f.mNumIndices; ++a) {
325 const unsigned int idx = f.mIndices[a];
326
327 aiVector3D vert = mat * m->mVertices[idx];
328 face.indices[a].vp = vpMap.getIndex(vert);
329
330 if (m->mNormals) {
331 aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx];
332 face.indices[a].vn = vnMap.getIndex(norm);
333 }
334 else{
335 face.indices[a].vn = 0;
336 }
337
338 if (m->mTextureCoords[0]) {
339 face.indices[a].vt = vtMap.getIndex(m->mTextureCoords[0][idx]);
340 }
341 else{
342 face.indices[a].vt = 0;
343 }
344 }
345 }
346 }
347
348 // ------------------------------------------------------------------------------------------------
AddNode(const aiNode * nd,const aiMatrix4x4 & mParent)349 void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent)
350 {
351 const aiMatrix4x4& mAbs = mParent * nd->mTransformation;
352
353 for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {
354 AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs);
355 }
356
357 for(unsigned int i = 0; i < nd->mNumChildren; ++i) {
358 AddNode(nd->mChildren[i], mAbs);
359 }
360 }
361
362 // ------------------------------------------------------------------------------------------------
363
364 #endif // ASSIMP_BUILD_NO_OBJ_EXPORTER
365 #endif // ASSIMP_BUILD_NO_EXPORT
366