Coverage Report

Created: 2026-10-28 07:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Obj/ObjExporter.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, assimp team
6
7
All rights reserved.
8
9
Redistribution and use of this software in source and binary forms,
10
with or without modification, are permitted provided that the
11
following 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
27
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
OF 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 <assimp/Exceptional.h>
47
#include <assimp/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
55
using namespace Assimp;
56
57
namespace Assimp {
58
59
// ------------------------------------------------------------------------------------------------
60
// Worker function for exporting a scene to Wavefront OBJ. Prototyped and registered in Exporter.cpp
61
1.81k
void ExportSceneObj(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* props) {
62
    // invoke the exporter
63
1.81k
    ObjExporter exporter(pFile, pScene, false, props);
64
65
1.81k
    if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
66
0
        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
67
0
    }
68
69
    // we're still here - export successfully completed. Write both the main OBJ file and the material script
70
1.81k
    {
71
1.81k
        std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
72
1.81k
        if (outfile == nullptr) {
73
0
            throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
74
0
        }
75
1.81k
        outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
76
1.81k
    }
77
0
    {
78
1.81k
        std::unique_ptr<IOStream> outfile (pIOSystem->Open(exporter.GetMaterialLibFileName(),"wt"));
79
1.81k
        if (outfile == nullptr) {
80
0
            throw DeadlyExportError("could not open output .mtl file: " + std::string(exporter.GetMaterialLibFileName()));
81
0
        }
82
1.81k
        outfile->Write( exporter.mOutputMat.str().c_str(), static_cast<size_t>(exporter.mOutputMat.tellp()),1);
83
1.81k
    }
84
1.81k
}
85
86
// ------------------------------------------------------------------------------------------------
87
// Worker function for exporting a scene to Wavefront OBJ without the material file. Prototyped and registered in Exporter.cpp
88
1.81k
void ExportSceneObjNoMtl(const char* pFile,IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* props) {
89
    // invoke the exporter
90
1.81k
    ObjExporter exporter(pFile, pScene, true, props);
91
92
1.81k
    if (exporter.mOutput.fail() || exporter.mOutputMat.fail()) {
93
0
        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
94
0
    }
95
96
    // we're still here - export successfully completed. Write both the main OBJ file and the material script
97
1.81k
    {
98
1.81k
        std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
99
1.81k
        if (outfile == nullptr) {
100
0
            throw DeadlyExportError("could not open output .obj file: " + std::string(pFile));
101
0
        }
102
1.81k
        outfile->Write( exporter.mOutput.str().c_str(), static_cast<size_t>(exporter.mOutput.tellp()),1);
103
1.81k
    }
104
105
106
1.81k
}
107
108
} // end of namespace Assimp
109
110
static const std::string MaterialExt = ".mtl";
111
112
// ------------------------------------------------------------------------------------------------
113
ObjExporter::ObjExporter(const char* _filename, const aiScene* pScene, bool noMtl, const ExportProperties* props)
114
3.63k
: filename(_filename)
115
3.63k
, pScene(pScene)
116
3.63k
, vn()
117
3.63k
, vt()
118
3.63k
, vp()
119
3.63k
, useVc(false)
120
3.63k
, mVnMap()
121
3.63k
, mVtMap()
122
3.63k
, mVpMap()
123
3.63k
, mMeshes()
124
3.63k
, endl("\n") {
125
    // make sure that all formatting happens using the standard, C locale and not the user's current locale
126
3.63k
    const std::locale& l = std::locale("C");
127
3.63k
    mOutput.imbue(l);
128
3.63k
    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
129
3.63k
    mOutputMat.imbue(l);
130
3.63k
    mOutputMat.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
131
132
3.63k
    WriteGeometryFile(
133
3.63k
        noMtl,
134
3.63k
        props == nullptr ? true : props->GetPropertyBool("bJoinIdenticalVertices", true)
135
3.63k
    );
136
3.63k
    if ( !noMtl ) {
137
1.81k
        WriteMaterialFile();
138
1.81k
    }
139
3.63k
}
140
141
// ------------------------------------------------------------------------------------------------
142
3.63k
ObjExporter::~ObjExporter() = default;
143
144
// ------------------------------------------------------------------------------------------------
145
1.81k
std::string ObjExporter::GetMaterialLibName() {
146
    // within the Obj file, we use just the relative file name with the path stripped
147
1.81k
    const std::string& s = GetMaterialLibFileName();
148
1.81k
    std::string::size_type il = s.find_last_of("/\\");
149
1.81k
    if (il != std::string::npos) {
150
0
        return s.substr(il + 1);
151
0
    }
152
153
1.81k
    return s;
154
1.81k
}
155
156
// ------------------------------------------------------------------------------------------------
157
3.63k
std::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
3.63k
    size_t lastdot = filename.find_last_of('.');
160
3.63k
    if ( lastdot != std::string::npos ) {
161
0
        return filename.substr( 0, lastdot ) + MaterialExt;
162
0
    }
163
164
3.63k
    return filename + MaterialExt;
165
3.63k
}
166
167
// ------------------------------------------------------------------------------------------------
168
5.45k
void ObjExporter::WriteHeader(std::ostringstream& out) {
169
5.45k
    out << "# File produced by Open Asset Import Library (https://www.assimp.org)" << endl;
170
5.45k
    out << "# (assimp v" << aiGetVersionMajor() << '.' << aiGetVersionMinor() << '.'
171
5.45k
        << aiGetVersionRevision() << ")" << endl  << endl;
172
5.45k
}
173
174
// ------------------------------------------------------------------------------------------------
175
43.4k
std::string ObjExporter::GetMaterialName(unsigned int index) {
176
43.4k
    static const std::string EmptyStr;
177
43.4k
    if ( nullptr == pScene->mMaterials ) {
178
0
        return EmptyStr;
179
0
    }
180
43.4k
    const aiMaterial* const mat = pScene->mMaterials[index];
181
43.4k
    if ( nullptr == mat ) {
182
0
        return EmptyStr;
183
0
    }
184
185
43.4k
    aiString s;
186
43.4k
    if(AI_SUCCESS == mat->Get(AI_MATKEY_NAME,s)) {
187
41.0k
        return std::string(s.data,s.length);
188
41.0k
    }
189
190
2.33k
    char number[ sizeof(unsigned int) * 3 + 1 ];
191
2.33k
    ASSIMP_itoa10(number,index);
192
2.33k
    return "$Material_" + std::string(number);
193
43.4k
}
194
195
// ------------------------------------------------------------------------------------------------
196
1.81k
void ObjExporter::WriteMaterialFile() {
197
1.81k
    WriteHeader(mOutputMat);
198
199
3.82k
    for(unsigned int i = 0; i < pScene->mNumMaterials; ++i) {
200
2.00k
        const aiMaterial* const mat = pScene->mMaterials[i];
201
202
2.00k
        int illum = 1;
203
2.00k
        mOutputMat << "newmtl " << GetMaterialName(i)  << endl;
204
205
2.00k
        aiColor4D c;
206
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_DIFFUSE,c)) {
207
1.80k
            mOutputMat << "Kd " << c.r << " " << c.g << " " << c.b << endl;
208
1.80k
        }
209
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_AMBIENT,c)) {
210
1.39k
            mOutputMat << "Ka " << c.r << " " << c.g << " " << c.b << endl;
211
1.39k
        }
212
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_SPECULAR,c)) {
213
1.58k
            mOutputMat << "Ks " << c.r << " " << c.g << " " << c.b << endl;
214
1.58k
        }
215
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_EMISSIVE,c)) {
216
1.44k
            mOutputMat << "Ke " << c.r << " " << c.g << " " << c.b << endl;
217
1.44k
        }
218
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_COLOR_TRANSPARENT,c)) {
219
685
            mOutputMat << "Tf " << c.r << " " << c.g << " " << c.b << endl;
220
685
        }
221
222
2.00k
        ai_real o;
223
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_OPACITY,o)) {
224
1.21k
            mOutputMat << "d " << o << endl;
225
1.21k
        }
226
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_REFRACTI,o)) {
227
885
            mOutputMat << "Ni " << o << endl;
228
885
        }
229
230
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_SHININESS,o) && o) {
231
472
            mOutputMat << "Ns " << o << endl;
232
472
            illum = 2;
233
472
        }
234
235
2.00k
        mOutputMat << "illum " << illum << endl;
236
237
2.00k
        aiString s;
238
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_DIFFUSE(0),s)) {
239
584
            mOutputMat << "map_Kd " << s.data << endl;
240
584
        }
241
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_AMBIENT(0),s)) {
242
0
            mOutputMat << "map_Ka " << s.data << endl;
243
0
        }
244
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SPECULAR(0),s)) {
245
2
            mOutputMat << "map_Ks " << s.data << endl;
246
2
        }
247
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_SHININESS(0),s)) {
248
7
            mOutputMat << "map_Ns " << s.data << endl;
249
7
        }
250
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_OPACITY(0),s)) {
251
82
            mOutputMat << "map_d " << s.data << endl;
252
82
        }
253
2.00k
        if(AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_HEIGHT(0),s) || AI_SUCCESS == mat->Get(AI_MATKEY_TEXTURE_NORMALS(0),s)) {
254
            // implementations seem to vary here, so write both variants
255
60
            mOutputMat << "bump " << s.data << endl;
256
60
            mOutputMat << "map_bump " << s.data << endl;
257
60
        }
258
259
2.00k
        mOutputMat << endl;
260
2.00k
    }
261
1.81k
}
262
263
3.63k
void ObjExporter::WriteGeometryFile(bool noMtl, bool merge_identical_vertices) {
264
3.63k
    WriteHeader(mOutput);
265
3.63k
    if (!noMtl)
266
1.81k
        mOutput << "mtllib "  << GetMaterialLibName() << endl << endl;
267
268
    // collect mesh geometry
269
3.63k
    aiMatrix4x4 mBase;
270
3.63k
    AddNode(pScene->mRootNode, mBase, merge_identical_vertices);
271
272
    // write vertex positions with colors, if any
273
3.63k
    mVpMap.getKeys( vp );
274
3.63k
    if ( !useVc ) {
275
2.97k
        mOutput << "# " << vp.size() << " vertex positions" << endl;
276
664k
        for ( const vertexData& v : vp ) {
277
664k
            mOutput << "v " << v.vp.x << " " << v.vp.y << " " << v.vp.z << endl;
278
664k
        }
279
2.97k
    } else {
280
666
        mOutput << "# " << vp.size() << " vertex positions and colors" << endl;
281
45.1k
        for ( const vertexData& v : vp ) {
282
45.1k
            mOutput << "v " << v.vp.x << " " << v.vp.y << " " << v.vp.z << " " << v.vc.r << " " << v.vc.g << " " << v.vc.b << endl;
283
45.1k
        }
284
666
    }
285
3.63k
    mOutput << endl;
286
287
    // write uv coordinates
288
3.63k
    mVtMap.getKeys(vt);
289
3.63k
    mOutput << "# " << vt.size() << " UV coordinates" << endl;
290
94.6k
    for(const aiVector3D& v : vt) {
291
94.6k
        mOutput << "vt " << v.x << " " << v.y << " " << v.z << endl;
292
94.6k
    }
293
3.63k
    mOutput << endl;
294
295
    // write vertex normals
296
3.63k
    mVnMap.getKeys(vn);
297
3.63k
    mOutput << "# " << vn.size() << " vertex normals" << endl;
298
289k
    for(const aiVector3D& v : vn) {
299
289k
        mOutput << "vn " << v.x << " " << v.y << " " << v.z << endl;
300
289k
    }
301
3.63k
    mOutput << endl;
302
303
    // now write all mesh instances
304
41.4k
    for(const MeshInstance& m : mMeshes) {
305
41.4k
        mOutput << "# Mesh \'" << m.name << "\' with " << m.faces.size() << " faces" << endl;
306
41.4k
        if (!m.name.empty()) {
307
36.9k
            mOutput << "g " << m.name << endl;
308
36.9k
        }
309
41.4k
        if ( !noMtl ) {
310
20.7k
            mOutput << "usemtl " << m.matname << endl;
311
20.7k
        }
312
313
1.06M
        for(const Face& f : m.faces) {
314
1.06M
            mOutput << f.kind << ' ';
315
2.89M
            for(const FaceVertex& fv : f.indices) {
316
2.89M
                mOutput << ' ' << fv.vp;
317
318
2.89M
                if (f.kind != 'p') {
319
2.81M
                    if (fv.vt || f.kind == 'f') {
320
2.53M
                        mOutput << '/';
321
2.53M
                    }
322
2.81M
                    if (fv.vt) {
323
858k
                        mOutput << fv.vt;
324
858k
                    }
325
2.81M
                    if (f.kind == 'f' && fv.vn) {
326
2.52M
                        mOutput << '/' << fv.vn;
327
2.52M
                    }
328
2.81M
                }
329
2.89M
            }
330
331
1.06M
            mOutput << endl;
332
1.06M
        }
333
41.4k
        mOutput << endl;
334
41.4k
    }
335
3.63k
}
336
337
// ------------------------------------------------------------------------------------------------
338
41.4k
void ObjExporter::AddMesh(const aiString& name, const aiMesh* m, const aiMatrix4x4& mat, bool merge_identical_vertices) {
339
41.4k
    mMeshes.emplace_back();
340
41.4k
    MeshInstance& mesh = mMeshes.back();
341
342
41.4k
    if ( nullptr != m->mColors[ 0 ] ) {
343
10.7k
        useVc = true;
344
10.7k
    }
345
346
41.4k
    mesh.name = std::string( name.data, name.length );
347
41.4k
    mesh.matname = GetMaterialName(m->mMaterialIndex);
348
349
41.4k
    mesh.faces.resize(m->mNumFaces);
350
351
1.11M
    for(unsigned int i = 0; i < m->mNumFaces; ++i) {
352
1.06M
        const aiFace& f = m->mFaces[i];
353
354
1.06M
        Face& face = mesh.faces[i];
355
1.06M
        switch (f.mNumIndices) {
356
82.1k
            case 1:
357
82.1k
                face.kind = 'p';
358
82.1k
                break;
359
145k
            case 2:
360
145k
                face.kind = 'l';
361
145k
                break;
362
841k
            default:
363
841k
                face.kind = 'f';
364
1.06M
        }
365
1.06M
        face.indices.resize(f.mNumIndices);
366
367
3.96M
        for(unsigned int a = 0; a < f.mNumIndices; ++a) {
368
2.89M
            const unsigned int idx = f.mIndices[a];
369
370
2.89M
            const unsigned int fi = merge_identical_vertices ? 0 : idx;
371
2.89M
            aiVector3D vert = mat * m->mVertices[idx];
372
373
2.89M
            if ( nullptr != m->mColors[ 0 ] ) {
374
245k
                aiColor4D col4 = m->mColors[ 0 ][ idx ];
375
245k
                face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(col4.r, col4.g, col4.b), fi});
376
2.65M
            } else {
377
2.65M
                face.indices[a].vp = mVpMap.getIndex({vert, aiColor3D(0,0,0), fi});
378
2.65M
            }
379
380
2.89M
            if (m->mNormals) {
381
2.86M
                aiVector3D norm = aiMatrix3x3(mat) * m->mNormals[idx];
382
2.86M
                face.indices[a].vn = mVnMap.getIndex(norm);
383
2.86M
            } else {
384
37.0k
                face.indices[a].vn = 0;
385
37.0k
            }
386
387
2.89M
            if ( m->mTextureCoords[ 0 ] ) {
388
864k
                face.indices[a].vt = mVtMap.getIndex(m->mTextureCoords[0][idx]);
389
2.03M
            } else {
390
2.03M
                face.indices[a].vt = 0;
391
2.03M
            }
392
2.89M
        }
393
1.06M
    }
394
41.4k
}
395
396
// ------------------------------------------------------------------------------------------------
397
198k
void ObjExporter::AddNode(const aiNode* nd, const aiMatrix4x4& mParent, bool merge_identical_vertices) {
398
198k
    if (nd == nullptr) {
399
0
        return;
400
0
    }
401
    
402
198k
    const aiMatrix4x4& mAbs = mParent * nd->mTransformation;
403
198k
    aiMesh *cm{nullptr};
404
239k
    for(unsigned int i = 0; i < nd->mNumMeshes; ++i) {
405
41.4k
        cm = pScene->mMeshes[nd->mMeshes[i]];
406
41.4k
        if (nullptr != cm) {
407
41.4k
            AddMesh(cm->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs, merge_identical_vertices);
408
41.4k
        } else {
409
0
            AddMesh(nd->mName, pScene->mMeshes[nd->mMeshes[i]], mAbs, merge_identical_vertices);
410
0
        }
411
41.4k
    }
412
413
393k
    for(unsigned int i = 0; i < nd->mNumChildren; ++i) {
414
194k
        AddNode(nd->mChildren[i], mAbs, merge_identical_vertices);
415
194k
    }
416
198k
}
417
418
// ------------------------------------------------------------------------------------------------
419
420
#endif // ASSIMP_BUILD_NO_OBJ_EXPORTER
421
#endif // ASSIMP_BUILD_NO_EXPORT