Coverage Report

Created: 2026-02-05 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/Pbrt/PbrtExporter.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
/* TODO:
43
44
Material improvements:
45
- don't export embedded textures that we're not going to use
46
- diffuse roughness
47
- what is with the uv mapping, uv transform not coming through??
48
- metal? glass? mirror?  detect these better?
49
  - eta/k from RGB?
50
- emissive textures: warn at least
51
52
Other:
53
- use aiProcess_GenUVCoords if needed to handle spherical/planar uv mapping?
54
- don't build up a big string in memory but write directly to a file
55
- aiProcess_Triangulate meshes to get triangles only?
56
- animation (allow specifying a time)
57
58
 */
59
60
#ifndef ASSIMP_BUILD_NO_EXPORT
61
#ifndef ASSIMP_BUILD_NO_PBRT_EXPORTER
62
63
#include "PbrtExporter.h"
64
65
#include <assimp/version.h>
66
#include <assimp/DefaultIOSystem.h>
67
#include <assimp/IOSystem.hpp>
68
#include <assimp/Exporter.hpp>
69
#include <assimp/DefaultLogger.hpp>
70
#include <assimp/StreamWriter.h>
71
#include <assimp/Exceptional.h>
72
#include <assimp/material.h>
73
#include <assimp/scene.h>
74
#include <assimp/mesh.h>
75
76
#include <algorithm>
77
#include <cctype>
78
#include <cmath>
79
#include <fstream>
80
#include <functional>
81
#include <iostream>
82
#include <memory>
83
#include <sstream>
84
#include <string>
85
86
#include "Common/StbCommon.h"
87
88
using namespace Assimp;
89
90
namespace Assimp {
91
92
void ExportScenePbrt(const char *pFile, IOSystem *pIOSystem, const aiScene *pScene,
93
0
        const ExportProperties *) {
94
0
    std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
95
0
    std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
96
0
    std::string texturesPath = path;
97
0
    if (!texturesPath.empty()) {
98
0
        texturesPath+=pIOSystem->getOsSeparator(); 
99
0
    }
100
0
    texturesPath+="textures";
101
    
102
    // initialize the exporter
103
0
    PbrtExporter exporter(pScene, pIOSystem, path, file, texturesPath);
104
0
}
105
106
} // end of namespace Assimp
107
108
0
static void create_embedded_textures_folder(const aiScene *scene, IOSystem *pIOSystem, const std::string &texturesPath) {
109
0
    if (scene->mNumTextures > 0) {
110
0
        if (!pIOSystem->Exists(texturesPath)) {
111
0
            if (!pIOSystem->CreateDirectory(texturesPath)) {
112
0
                throw DeadlyExportError("Could not create textures/ directory.");
113
0
            }
114
0
        }
115
0
    }
116
0
}
117
118
PbrtExporter::PbrtExporter(
119
        const aiScene *pScene, IOSystem *pIOSystem,
120
        const std::string &path, const std::string &file, const std::string &texturesPath) :
121
0
        mScene(pScene),
122
0
        mIOSystem(pIOSystem),
123
0
        mPath(path),
124
0
        mFile(file),
125
0
        mTexturesPath(texturesPath),
126
0
        mRootTransform(
127
            // rotates the (already left-handed) CRS -90 degrees around the x axis in order to
128
            // make +Z 'up' and +Y 'towards viewer', as in default in pbrt
129
0
            1.f,  0.f,  0.f, 0.f, //
130
0
            0.f,  0.f, -1.f, 0.f, //
131
0
            0.f,  1.f,  0.f, 0.f, //
132
0
            0.f,  0.f,  0.f, 1.f  //
133
0
        ) {
134
135
0
    mRootTransform = aiMatrix4x4(
136
0
        -1.f,  0,  0.f, 0.f, //
137
0
        0.0f,  -1.f,  0.f, 0.f, //
138
0
        0.f,  0.f,  1.f, 0.f, //
139
0
        0.f,  0.f,  0.f, 1.f  //
140
0
    ) * mRootTransform;
141
142
    // Export embedded textures.
143
0
    create_embedded_textures_folder(mScene, mIOSystem, mTexturesPath);
144
145
0
    for (unsigned int i = 0; i < mScene->mNumTextures; ++i) {
146
0
        aiTexture* tex = mScene->mTextures[i];
147
0
        std::string fn = CleanTextureFilename(tex->mFilename, false);
148
0
        std::cerr << "Writing embedded texture: " << tex->mFilename.C_Str() << " -> "
149
0
                  << fn << "\n";
150
151
0
        std::unique_ptr<IOStream> outfile(mIOSystem->Open(fn, "wb"));
152
0
        if (!outfile) {
153
0
            throw DeadlyExportError("could not open output texture file: " + fn);
154
0
        }
155
0
        if (tex->mHeight == 0) {
156
            // It's binary data
157
0
            outfile->Write(tex->pcData, tex->mWidth, 1);
158
0
        } else {
159
0
            std::cerr << fn << ": TODO handle uncompressed embedded textures.\n";
160
0
        }
161
0
    }
162
163
#if 0
164
    // Debugging: print the full node hierarchy
165
    std::function<void(aiNode*, int)> visitNode;
166
    visitNode = [&](aiNode* node, int depth) {
167
        for (int i = 0; i < depth; ++i) std::cerr << "    ";
168
        std::cerr << node->mName.C_Str() << "\n";
169
        for (int i = 0; i < node->mNumChildren; ++i)
170
            visitNode(node->mChildren[i], depth + 1);
171
    };
172
    visitNode(mScene->mRootNode, 0);
173
#endif
174
175
0
    mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
176
177
    // Write everything out
178
0
    WriteMetaData();
179
0
    WriteCameras();
180
0
    WriteWorldDefinition();
181
182
    // And write the file to disk...
183
0
    std::string outputFilePath = mPath;
184
0
    if (!outputFilePath.empty()) {
185
0
        outputFilePath = outputFilePath + mIOSystem->getOsSeparator();
186
0
    }
187
0
    outputFilePath = outputFilePath + mFile +".pbrt";
188
189
0
    std::unique_ptr<IOStream> outfile(mIOSystem->Open(outputFilePath,"wt"));
190
0
    if (!outfile) {
191
0
        throw DeadlyExportError("could not open output .pbrt file: " + std::string(mFile));
192
0
    }
193
0
    outfile->Write(mOutput.str().c_str(), mOutput.str().length(), 1);
194
0
}
195
196
0
void PbrtExporter::WriteMetaData() {
197
0
    mOutput << "#############################\n";
198
0
    mOutput << "# Scene metadata:\n";
199
200
0
    aiMetadata* pMetaData = mScene->mMetaData;
201
0
    for (unsigned int i = 0; i < pMetaData->mNumProperties; i++) {
202
0
        mOutput << "# - ";
203
0
        mOutput << pMetaData->mKeys[i].C_Str() << " :";
204
0
        switch(pMetaData->mValues[i].mType) {
205
0
            case AI_BOOL : {
206
0
                mOutput << " ";
207
0
                if (*static_cast<bool*>(pMetaData->mValues[i].mData))
208
0
                    mOutput << "TRUE\n";
209
0
                else
210
0
                    mOutput << "FALSE\n";
211
0
                break;
212
0
            }
213
0
            case AI_INT32 : {
214
0
                mOutput << " " <<
215
0
                    *static_cast<int32_t*>(pMetaData->mValues[i].mData) <<
216
0
                    std::endl;
217
0
                break;
218
0
            }
219
0
            case AI_UINT64 :
220
0
                mOutput << " " <<
221
0
                    *static_cast<uint64_t*>(pMetaData->mValues[i].mData) <<
222
0
                    std::endl;
223
0
                break;
224
0
            case AI_FLOAT :
225
0
                mOutput << " " <<
226
0
                    *static_cast<float*>(pMetaData->mValues[i].mData) <<
227
0
                    std::endl;
228
0
                break;
229
0
            case AI_DOUBLE :
230
0
                mOutput << " " <<
231
0
                    *static_cast<double*>(pMetaData->mValues[i].mData) <<
232
0
                    std::endl;
233
0
                break;
234
0
            case AI_AISTRING : {
235
0
                aiString* value =
236
0
                    static_cast<aiString*>(pMetaData->mValues[i].mData);
237
0
                std::string svalue = value->C_Str();
238
0
                std::size_t found = svalue.find_first_of('\n');
239
0
                mOutput << "\n";
240
0
                while (found != std::string::npos) {
241
0
                    mOutput << "#     " << svalue.substr(0, found) << "\n";
242
0
                    svalue = svalue.substr(found + 1);
243
0
                    found = svalue.find_first_of('\n');
244
0
                }
245
0
                mOutput << "#     " << svalue << "\n";
246
0
                break;
247
0
            }
248
0
            case AI_AIVECTOR3D :
249
                // TODO
250
0
                mOutput << " Vector3D (unable to print)\n";
251
0
                break;
252
0
            default:
253
                // AI_META_MAX and FORCE_32BIT
254
0
                mOutput << " META_MAX or FORCE_32Bit (unable to print)\n";
255
0
                break;
256
0
        }
257
0
    }
258
0
}
259
260
0
void PbrtExporter::WriteCameras() {
261
0
    mOutput << "\n";
262
0
    mOutput << "###############################\n";
263
0
    mOutput << "# Cameras (" << mScene->mNumCameras << ") total\n\n";
264
265
0
    if (mScene->mNumCameras == 0) {
266
0
        std::cerr << "Warning: No cameras found in scene file.\n";
267
0
        return;
268
0
    }
269
270
0
    if (mScene->mNumCameras > 1) {
271
0
        std::cerr << "Multiple cameras found in scene file; defaulting to first one specified.\n";
272
0
    }
273
274
0
    for (unsigned int i = 0; i < mScene->mNumCameras; i++) {
275
0
        WriteCamera(i);
276
0
    }
277
0
}
278
279
0
aiMatrix4x4 PbrtExporter::GetNodeTransform(const aiString &name) const {
280
0
    aiMatrix4x4 m;
281
0
    auto node = mScene->mRootNode->FindNode(name);
282
0
    if (!node) {
283
0
        std::cerr << '"' << name.C_Str() << "\": node not found in scene tree.\n";
284
0
        throw DeadlyExportError("Could not find node");
285
0
    }
286
0
    else {
287
0
        while (node) {
288
0
            m = node->mTransformation * m;
289
0
            node = node->mParent;
290
0
        }
291
0
    }
292
0
    return mRootTransform * m;
293
0
}
294
295
0
std::string PbrtExporter::TransformAsString(const aiMatrix4x4 &m) {
296
    // Transpose on the way out to match pbrt's expected layout (sanity
297
    // check: the translation component should be the last 3 entries
298
    // before a '1' as the final entry in the matrix, assuming it's
299
    // non-projective.)
300
0
    std::stringstream s;
301
0
    s << m.a1 << " " << m.b1 << " " << m.c1 << " " << m.d1 << " "
302
0
      << m.a2 << " " << m.b2 << " " << m.c2 << " " << m.d2 << " "
303
0
      << m.a3 << " " << m.b3 << " " << m.c3 << " " << m.d3 << " "
304
0
      << m.a4 << " " << m.b4 << " " << m.c4 << " " << m.d4;
305
0
    return s.str();
306
0
}
307
308
0
void PbrtExporter::WriteCamera(int i) {
309
0
    auto camera = mScene->mCameras[i];
310
0
    bool cameraActive = i == 0;
311
312
0
    mOutput << "# - Camera " << i+1 <<  ": "
313
0
        << camera->mName.C_Str() << "\n";
314
315
    // Get camera aspect ratio
316
0
    float aspect = camera->mAspect;
317
0
    if (aspect == 0) {
318
0
        aspect = 4.f/3.f;
319
0
        mOutput << "#   - Aspect ratio : 1.33333 (no aspect found, defaulting to 4/3)\n";
320
0
    } else {
321
0
        mOutput << "#   - Aspect ratio : " << aspect << "\n";
322
0
    }
323
324
    // Get Film xres and yres
325
0
    int xres = 1920;
326
0
    int yres = (int)round(xres/aspect);
327
328
    // Print Film for this camera
329
0
    if (!cameraActive)
330
0
        mOutput << "# ";
331
0
    mOutput << "Film \"rgb\" \"string filename\" \"" << mFile << ".exr\"\n";
332
0
    if (!cameraActive)
333
0
        mOutput << "# ";
334
0
    mOutput << "    \"integer xresolution\" [" << xres << "]\n";
335
0
    if (!cameraActive)
336
0
        mOutput << "# ";
337
0
    mOutput << "    \"integer yresolution\" [" << yres << "]\n";
338
339
    // Get camera fov
340
0
    float hfov = AI_RAD_TO_DEG(camera->mHorizontalFOV);
341
0
    float fov = (aspect >= 1.0) ? hfov : (hfov / aspect);
342
0
    if (fov < 5) {
343
0
        std::cerr << fov << ": suspiciously low field of view specified by camera. Setting to 45 degrees.\n";
344
0
        fov = 45;
345
0
    }
346
347
    // Get camera transform
348
0
    aiMatrix4x4 worldFromCamera = GetNodeTransform(camera->mName);
349
350
    // Print Camera LookAt
351
0
    auto position = worldFromCamera * camera->mPosition;
352
0
    auto lookAt = worldFromCamera * (camera->mPosition + camera->mLookAt);
353
0
    aiMatrix3x3 worldFromCamera3(worldFromCamera);
354
0
    auto up = worldFromCamera3 * camera->mUp;
355
0
    up.Normalize();
356
357
0
    if (!cameraActive)
358
0
        mOutput << "# ";
359
0
    mOutput << "Scale 1 1 1\n";
360
0
    if (!cameraActive)
361
0
        mOutput << "# ";
362
0
    mOutput << "LookAt "
363
0
        << position.x << " " << position.y << " " << position.z << "\n";
364
0
    if (!cameraActive)
365
0
        mOutput << "# ";
366
0
    mOutput << "       "
367
0
        << lookAt.x << " " << lookAt.y << " " << lookAt.z << "\n";
368
0
    if (!cameraActive)
369
0
        mOutput << "# ";
370
0
    mOutput << "       "
371
0
        << up.x << " " << up.y << " " << up.z << "\n";
372
373
    // Print camera descriptor
374
0
    if (!cameraActive)
375
0
        mOutput << "# ";
376
0
    mOutput << "Camera \"perspective\" \"float fov\" " << "[" << fov << "]\n\n";
377
0
}
378
379
0
void PbrtExporter::WriteWorldDefinition() {
380
    // Figure out which meshes are referenced multiple times; those will be
381
    // emitted as object instances and the rest will be emitted directly.
382
0
    std::map<int, int> meshUses;
383
0
    std::function<void(aiNode*)> visitNode;
384
0
    visitNode = [&](aiNode* node) {
385
0
        for (unsigned int i = 0; i < node->mNumMeshes; ++i)
386
0
            ++meshUses[node->mMeshes[i]];
387
0
        for (unsigned int i = 0; i < node->mNumChildren; ++i)
388
0
            visitNode(node->mChildren[i]);
389
0
    };
390
0
    visitNode(mScene->mRootNode);
391
0
    int nInstanced = 0, nUnused = 0;
392
0
    for (const auto &u : meshUses) {
393
0
        if (u.second == 0) ++nUnused;
394
0
        else if (u.second > 1) ++nInstanced;
395
0
    }
396
0
    std::cerr << nInstanced << " / " << mScene->mNumMeshes << " meshes instanced.\n";
397
0
    if (nUnused)
398
0
        std::cerr << nUnused << " meshes defined but not used in scene.\n";
399
400
0
    mOutput << "WorldBegin\n";
401
402
0
    WriteLights();
403
0
    WriteTextures();
404
0
    WriteMaterials();
405
406
    // Object instance definitions
407
0
    mOutput << "# Object instance definitions\n\n";
408
0
    for (const auto &mu : meshUses) {
409
0
        if (mu.second > 1) {
410
0
            WriteInstanceDefinition(mu.first);
411
0
        }
412
0
    }
413
414
0
    mOutput << "# Geometry\n\n";
415
416
0
    WriteGeometricObjects(mScene->mRootNode, mRootTransform, meshUses);
417
0
}
418
419
0
void PbrtExporter::WriteTextures() {
420
0
    mOutput << "###################\n";
421
0
    mOutput << "# Textures\n\n";
422
423
0
    C_STRUCT aiString path;
424
0
    aiTextureMapping mapping;
425
0
    unsigned int uvIndex;
426
0
    ai_real blend;
427
0
    aiTextureOp op;
428
0
    aiTextureMapMode mapMode[3];
429
430
    // For every material in the scene,
431
0
    for (unsigned int m = 0 ; m < mScene->mNumMaterials; m++) {
432
0
        auto material = mScene->mMaterials[m];
433
        // Parse through all texture types,
434
0
        for (int tt = 1; tt <= aiTextureType_UNKNOWN; tt++) {
435
0
            int ttCount = material->GetTextureCount(aiTextureType(tt));
436
            // ... and get every texture
437
0
            for (int t = 0; t < ttCount; t++) {
438
                // TODO write out texture specifics
439
                // TODO UV transforms may be material specific
440
                //        so those may need to be baked into unique tex name
441
0
                if (material->GetTexture(aiTextureType(tt), t, &path, &mapping,
442
0
                                         &uvIndex, &blend, &op, mapMode) != AI_SUCCESS) {
443
0
                    std::cerr << "Error getting texture! " << m << " " << tt << " " << t << "\n";
444
0
                    continue;
445
0
                }
446
447
0
                std::string filename = CleanTextureFilename(path);
448
449
0
                if (uvIndex != 0)
450
0
                    std::cerr << "Warning: texture \"" << filename << "\" uses uv set #" <<
451
0
                        uvIndex << " but the pbrt converter only exports uv set 0.\n";
452
#if 0
453
                if (op != aiTextureOp_Multiply)
454
                    std::cerr << "Warning: unexpected texture op " << (int)op <<
455
                        " encountered for texture \"" <<
456
                        filename << "\". The resulting scene may have issues...\n";
457
                if (blend != 1)
458
                    std::cerr << "Blend value of " << blend << " found for texture \"" << filename
459
                              << "\" but not handled in converter.\n";
460
#endif
461
462
0
                std::string mappingString;
463
#if 0
464
                if (mapMode[0] != mapMode[1])
465
                    std::cerr << "Different texture boundary mode for u and v for texture \"" <<
466
                        filename << "\". Using u for both.\n";
467
                switch (mapMode[0]) {
468
                case aiTextureMapMode_Wrap:
469
                    // pbrt's default
470
                    break;
471
                case aiTextureMapMode_Clamp:
472
                    mappingString = "\"string wrap\" \"clamp\"";
473
                    break;
474
                case aiTextureMapMode_Decal:
475
                    std::cerr << "Decal texture boundary mode not supported by pbrt for texture \"" <<
476
                        filename << "\"\n";
477
                    break;
478
                case aiTextureMapMode_Mirror:
479
                    std::cerr << "Mirror texture boundary mode not supported by pbrt for texture \"" <<
480
                        filename << "\"\n";
481
                    break;
482
                default:
483
                    std::cerr << "Unexpected map mode " << (int)mapMode[0] << " for texture \"" <<
484
                        filename << "\"\n";
485
                    //throw DeadlyExportError("Unexpected aiTextureMapMode");
486
                }
487
#endif
488
489
#if 0
490
                aiUVTransform uvTransform;
491
                if (material->Get(AI_MATKEY_TEXTURE(tt, t), uvTransform) == AI_SUCCESS) {
492
                    mOutput << "# UV transform " << uvTransform.mTranslation.x << " "
493
                            << uvTransform.mTranslation.y << " " << uvTransform.mScaling.x << " "
494
                            << uvTransform.mScaling.y << " " << uvTransform.mRotation << "\n";
495
                }
496
#endif
497
498
0
                std::string texName, texType, texOptions;
499
0
                if (aiTextureType(tt) == aiTextureType_SHININESS ||
500
0
                    aiTextureType(tt) == aiTextureType_OPACITY ||
501
0
                    aiTextureType(tt) == aiTextureType_HEIGHT ||
502
0
                    aiTextureType(tt) == aiTextureType_DISPLACEMENT ||
503
0
                    aiTextureType(tt) == aiTextureType_METALNESS ||
504
0
                    aiTextureType(tt) == aiTextureType_DIFFUSE_ROUGHNESS) {
505
0
                    texType = "float";
506
0
                    texName = std::string("float:") + RemoveSuffix(filename);
507
508
0
                    if (aiTextureType(tt) == aiTextureType_SHININESS) {
509
0
                        texOptions = "    \"bool invert\" true\n";
510
0
                        texName += "_Roughness";
511
0
                    }
512
0
                } else if (aiTextureType(tt) == aiTextureType_DIFFUSE ||
513
0
                           aiTextureType(tt) == aiTextureType_BASE_COLOR) {
514
0
                    texType = "spectrum";
515
0
                    texName = std::string("rgb:") + RemoveSuffix(filename);
516
0
                }
517
518
                // Don't export textures we're not actually going to use...
519
0
                if (texName.empty())
520
0
                    continue;
521
522
0
                if (mTextureSet.find(texName) == mTextureSet.end()) {
523
0
                    mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n"
524
0
                            << texOptions
525
0
                            << "    \"string filename\" \"" << filename << "\" " << mappingString << '\n';
526
0
                    mTextureSet.insert(texName);
527
0
                }
528
529
                // Also emit a float version for use with alpha testing...
530
0
                if ((aiTextureType(tt) == aiTextureType_DIFFUSE ||
531
0
                     aiTextureType(tt) == aiTextureType_BASE_COLOR) &&
532
0
                    TextureHasAlphaMask(filename)) {
533
0
                    texType = "float";
534
0
                    texName = std::string("alpha:") + filename;
535
0
                    if (mTextureSet.find(texName) == mTextureSet.end()) {
536
0
                        mOutput << "Texture \"" << texName << "\" \"" << texType << "\" \"imagemap\"\n"
537
0
                                << "    \"string filename\" \"" << filename << "\" " << mappingString << '\n';
538
0
                        mTextureSet.insert(texName);
539
0
                    }
540
0
                }
541
0
            }
542
0
        }
543
0
    }
544
0
}
545
546
0
bool PbrtExporter::TextureHasAlphaMask(const std::string &filename) {
547
    // TODO: STBIDEF int      stbi_info               (char const *filename,     int *x, int *y, int *comp);
548
    // quick return if it's 3
549
550
0
    int xSize, ySize, nComponents;
551
0
    unsigned char *data = stbi_load(filename.c_str(), &xSize, &ySize, &nComponents, 0);
552
0
    if (!data) {
553
0
        std::cerr << filename << ": unable to load texture and check for alpha mask in texture. "
554
0
        "Geometry will not be alpha masked with this texture.\n";
555
0
        return false;
556
0
    }
557
558
0
    bool hasMask = false;
559
0
    switch (nComponents) {
560
0
    case 1:
561
0
        for (int i = 0; i < xSize * ySize; ++i)
562
0
            if (data[i] != 255) {
563
0
                hasMask = true;
564
0
                break;
565
0
            }
566
0
        break;
567
0
    case 2:
568
0
          for (int y = 0; y < ySize; ++y)
569
0
              for (int x = 0; x < xSize; ++x)
570
0
                  if (data[2 * (x + y * xSize) + 1] != 255) {
571
0
                      hasMask = true;
572
0
                      break;
573
0
                  }
574
0
        break;
575
0
    case 3:
576
0
        break;
577
0
    case 4:
578
0
          for (int y = 0; y < ySize; ++y)
579
0
              for (int x = 0; x < xSize; ++x)
580
0
                  if (data[4 * (x + y * xSize) + 3] != 255) {
581
0
                      hasMask = true;
582
0
                      break;
583
0
                  }
584
0
          break;
585
0
    default:
586
0
        std::cerr << filename << ": unexpected number of image channels, " <<
587
0
            nComponents << ".\n";
588
0
    }
589
590
0
    stbi_image_free(data);
591
0
    return hasMask;
592
0
}
593
594
0
void PbrtExporter::WriteMaterials() {
595
0
    mOutput << "\n";
596
0
    mOutput << "####################\n";
597
0
    mOutput << "# Materials (" << mScene->mNumMaterials << ") total\n\n";
598
599
0
    for (unsigned int i = 0; i < mScene->mNumMaterials; i++) {
600
0
        WriteMaterial(i);
601
0
    }
602
0
    mOutput << "\n\n";
603
0
}
604
605
0
void PbrtExporter::WriteMaterial(int m) {
606
0
    aiMaterial* material = mScene->mMaterials[m];
607
608
    // get material name
609
0
    auto materialName = material->GetName();
610
0
    mOutput << std::endl << "# - Material " << m+1 <<  ": " << materialName.C_Str() << "\n";
611
612
    // Print out number of properties
613
0
    mOutput << "#   - Number of Material Properties: " << material->mNumProperties << "\n";
614
615
    // Print out texture type counts
616
0
    mOutput << "#   - Non-Zero Texture Type Counts: ";
617
0
    for (int i = 1; i <= aiTextureType_UNKNOWN; i++) {
618
0
        int count = material->GetTextureCount(aiTextureType(i));
619
0
        if (count > 0)
620
0
            mOutput << aiTextureTypeToString(aiTextureType(i)) << ": " <<  count << " ";
621
0
    }
622
0
    mOutput << "\n";
623
624
0
    auto White = [](const aiColor3D &c) { return c.r == 1 && c.g == 1 && c.b == 1; };
625
0
    auto Black = [](const aiColor3D &c) { return c.r == 0 && c.g == 0 && c.b == 0; };
626
627
0
    aiColor3D diffuse, specular, transparency;
628
0
    bool constantDiffuse = (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse) == AI_SUCCESS &&
629
0
                            !White(diffuse));
630
0
    bool constantSpecular = (material->Get(AI_MATKEY_COLOR_SPECULAR, specular) == AI_SUCCESS &&
631
0
                             !White(specular));
632
0
    bool constantTransparency = (material->Get(AI_MATKEY_COLOR_TRANSPARENT, transparency) == AI_SUCCESS &&
633
0
                                 !Black(transparency));
634
635
0
    float opacity, shininess, shininessStrength, eta;
636
0
    bool constantOpacity = (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS &&
637
0
                            opacity != 0);
638
0
    bool constantShininess = material->Get(AI_MATKEY_SHININESS, shininess) == AI_SUCCESS;
639
0
    bool constantShininessStrength = material->Get(AI_MATKEY_SHININESS_STRENGTH, shininessStrength) == AI_SUCCESS;
640
0
    bool constantEta = (material->Get(AI_MATKEY_REFRACTI, eta) == AI_SUCCESS &&
641
0
                        eta != 1);
642
643
0
    mOutput << "#    - Constants: diffuse " << constantDiffuse << " specular " << constantSpecular <<
644
0
        " transparency " << constantTransparency << " opacity " << constantOpacity <<
645
0
        " shininess " << constantShininess << " shininess strength " << constantShininessStrength <<
646
0
        " eta " << constantEta << "\n";
647
648
0
    aiString roughnessMap;
649
0
    if (material->Get(AI_MATKEY_TEXTURE_SHININESS(0), roughnessMap) == AI_SUCCESS) {
650
0
        std::string roughnessTexture = std::string("float:") +
651
0
            RemoveSuffix(CleanTextureFilename(roughnessMap)) + "_Roughness";
652
0
        mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\""
653
0
                << " \"string type\" \"coateddiffuse\"\n"
654
0
                << "    \"texture roughness\" \"" << roughnessTexture << "\"\n";
655
0
    } else if (constantShininess) {
656
        // Assume plastic for now at least
657
0
        float roughness = std::max(0.f, 1.f - shininess);
658
0
        mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\""
659
0
                << " \"string type\" \"coateddiffuse\"\n"
660
0
                << "    \"float roughness\" " << roughness << "\n";
661
0
    } else
662
        // Diffuse
663
0
        mOutput << "MakeNamedMaterial \"" << materialName.C_Str() << "\""
664
0
                << " \"string type\" \"diffuse\"\n";
665
666
0
    aiString diffuseTexture;
667
0
    if (material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), diffuseTexture) == AI_SUCCESS)
668
0
        mOutput << "    \"texture reflectance\" \"rgb:" << RemoveSuffix(CleanTextureFilename(diffuseTexture)) << "\"\n";
669
0
    else
670
0
        mOutput << "    \"rgb reflectance\" [ " << diffuse.r << " " << diffuse.g <<
671
0
            " " << diffuse.b << " ]\n";
672
673
0
    aiString displacementTexture, normalMap;
674
0
    if (material->Get(AI_MATKEY_TEXTURE_NORMALS(0), displacementTexture) == AI_SUCCESS)
675
0
        mOutput << "    \"string normalmap\" \"" << CleanTextureFilename(displacementTexture) << "\"\n";
676
0
    else if (material->Get(AI_MATKEY_TEXTURE_HEIGHT(0), displacementTexture) == AI_SUCCESS)
677
0
        mOutput << "    \"texture displacement\" \"float:" <<
678
0
            RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n";
679
0
    else if (material->Get(AI_MATKEY_TEXTURE_DISPLACEMENT(0), displacementTexture) == AI_SUCCESS)
680
0
        mOutput << "    \"texture displacement\" \"float:" <<
681
0
            RemoveSuffix(CleanTextureFilename(displacementTexture)) << "\"\n";
682
0
}
683
684
0
std::string PbrtExporter::CleanTextureFilename(const aiString &f, bool rewriteExtension) const {
685
0
    std::string fn = f.C_Str();
686
    // Remove directory name
687
0
    size_t offset = fn.find_last_of("/\\");
688
0
    if (offset != std::string::npos) {
689
0
        fn.erase(0, offset + 1);
690
0
    }
691
692
    // Expect all textures in textures
693
0
    fn = mTexturesPath + mIOSystem->getOsSeparator() + fn;
694
695
    // Rewrite extension for unsupported file formats.
696
0
    if (rewriteExtension) {
697
0
        offset = fn.rfind('.');
698
0
        if (offset != std::string::npos) {
699
0
            std::string extension = fn;
700
0
            extension.erase(0, offset + 1);
701
0
            std::transform(extension.begin(), extension.end(), extension.begin(),
702
0
                           [](unsigned char c) { return (char)std::tolower(c); });
703
704
0
            if (extension != "tga" && extension != "exr" && extension != "png" &&
705
0
                extension != "pfm" && extension != "hdr") {
706
0
                std::string orig = fn;
707
0
                fn.erase(offset + 1);
708
0
                fn += "png";
709
710
                // Does it already exist? Warn if not.
711
0
                std::ifstream filestream(fn);
712
0
                if (!filestream.good())
713
0
                    std::cerr << orig << ": must convert this texture to PNG.\n";
714
0
            }
715
0
        }
716
0
    }
717
718
0
    return fn;
719
0
}
720
721
0
std::string PbrtExporter::RemoveSuffix(std::string filename) {
722
0
    size_t offset = filename.rfind('.');
723
0
    if (offset != std::string::npos)
724
0
        filename.erase(offset);
725
0
    return filename;
726
0
}
727
728
0
void PbrtExporter::WriteLights() {
729
0
    mOutput << "\n";
730
0
    mOutput << "#################\n";
731
0
    mOutput << "# Lights\n\n";
732
0
    if (mScene->mNumLights == 0) {
733
        // Skip the default light if no cameras and this is flat up geometry
734
0
        if (mScene->mNumCameras > 0) {
735
0
            std::cerr << "No lights specified. Using default infinite light.\n";
736
737
0
            mOutput << "AttributeBegin\n";
738
0
            mOutput << "    # default light\n";
739
0
            mOutput << "    LightSource \"infinite\" \"blackbody L\" [6000 1]\n";
740
741
0
            mOutput << "AttributeEnd\n\n";
742
0
        }
743
0
    } else {
744
0
        for (unsigned int i = 0; i < mScene->mNumLights; ++i) {
745
0
            const aiLight *light = mScene->mLights[i];
746
747
0
            mOutput << "# Light " << light->mName.C_Str() << "\n";
748
0
            mOutput << "AttributeBegin\n";
749
750
0
            aiMatrix4x4 worldFromLight = GetNodeTransform(light->mName);
751
0
            mOutput << "    Transform [ " << TransformAsString(worldFromLight) << " ]\n";
752
753
0
            aiColor3D color = light->mColorDiffuse + light->mColorSpecular;
754
0
            if (light->mAttenuationConstant != 0)
755
0
                color = color * (ai_real)(1. / light->mAttenuationConstant);
756
757
0
            switch (light->mType) {
758
0
            case aiLightSource_DIRECTIONAL: {
759
0
                mOutput << "    LightSource \"distant\"\n";
760
0
                mOutput << "        \"point3 from\" [ " << light->mPosition.x << " " <<
761
0
                    light->mPosition.y << " " << light->mPosition.z << " ]\n";
762
0
                aiVector3D to = light->mPosition + light->mDirection;
763
0
                mOutput << "        \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n";
764
0
                mOutput << "        \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n";
765
0
                break;
766
0
            } case aiLightSource_POINT:
767
0
                mOutput << "    LightSource \"distant\"\n";
768
0
                mOutput << "        \"point3 from\" [ " << light->mPosition.x << " " <<
769
0
                    light->mPosition.y << " " << light->mPosition.z << " ]\n";
770
0
                mOutput << "        \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n";
771
0
                break;
772
0
            case aiLightSource_SPOT: {
773
0
                mOutput << "    LightSource \"spot\"\n";
774
0
                mOutput << "        \"point3 from\" [ " << light->mPosition.x << " " <<
775
0
                    light->mPosition.y << " " << light->mPosition.z << " ]\n";
776
0
                aiVector3D to = light->mPosition + light->mDirection;
777
0
                mOutput << "        \"point3 to\" [ " << to.x << " " << to.y << " " << to.z << " ]\n";
778
0
                mOutput << "        \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n";
779
0
                mOutput << "        \"float coneangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone) << " ]\n";
780
0
                mOutput << "        \"float conedeltaangle\" [ " << AI_RAD_TO_DEG(light->mAngleOuterCone -
781
0
                                                                                  light->mAngleInnerCone) << " ]\n";
782
0
                break;
783
0
            } case aiLightSource_AMBIENT:
784
0
                mOutput << "# ignored ambient light source\n";
785
0
                break;
786
0
            case aiLightSource_AREA: {
787
0
                aiVector3D left = light->mDirection ^ light->mUp;
788
                // rectangle. center at position, direction is normal vector
789
0
                ai_real dLeft = light->mSize.x / 2, dUp = light->mSize.y / 2;
790
0
                aiVector3D vertices[4] = {
791
0
                     light->mPosition - dLeft * left - dUp * light->mUp,
792
0
                     light->mPosition + dLeft * left - dUp * light->mUp,
793
0
                     light->mPosition - dLeft * left + dUp * light->mUp,
794
0
                     light->mPosition + dLeft * left + dUp * light->mUp };
795
0
                mOutput << "    AreaLightSource \"diffuse\"\n";
796
0
                mOutput << "        \"rgb L\" [ " << color.r << " " << color.g << " " << color.b << " ]\n";
797
0
                mOutput << "    Shape \"bilinearmesh\"\n";
798
0
                mOutput << "        \"point3 p\" [ ";
799
0
                for (int j = 0; j < 4; ++j)
800
0
                    mOutput << vertices[j].x << " " << vertices[j].y << " " << vertices[j].z;
801
0
                mOutput << " ]\n";
802
0
                mOutput << "        \"integer indices\" [ 0 1 2 3 ]\n";
803
0
                break;
804
0
            } default:
805
0
                mOutput << "# ignored undefined light source type\n";
806
0
                break;
807
0
            }
808
0
            mOutput << "AttributeEnd\n\n";
809
0
        }
810
0
    }
811
0
}
812
813
0
void PbrtExporter::WriteMesh(aiMesh* mesh) {
814
0
    mOutput << "# - Mesh: ";
815
0
    const char* mName;
816
0
    if (mesh->mName == aiString(""))
817
0
        mName = "<No Name>";
818
0
    else
819
0
        mName = mesh->mName.C_Str();
820
0
    mOutput << mName << "\n";
821
822
    // Check if any types other than tri
823
0
    if (   (mesh->mPrimitiveTypes & aiPrimitiveType_POINT)
824
0
           || (mesh->mPrimitiveTypes & aiPrimitiveType_LINE)
825
0
           || (mesh->mPrimitiveTypes & aiPrimitiveType_POLYGON)) {
826
0
        std::cerr << "Error: ignoring point / line / polygon mesh " << mName << ".\n";
827
0
        return;
828
0
    }
829
830
0
    mOutput << "AttributeBegin\n";
831
0
    aiMaterial* material = mScene->mMaterials[mesh->mMaterialIndex];
832
0
    mOutput << "    NamedMaterial \"" << material->GetName().C_Str() << "\"\n";
833
834
    // Handle area lights
835
0
    aiColor3D emission;
836
0
    if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emission) == AI_SUCCESS &&
837
0
        (emission.r > 0 || emission.g > 0 || emission.b > 0))
838
0
        mOutput << "    AreaLightSource \"diffuse\" \"rgb L\" [ " << emission.r <<
839
0
            " " << emission.g << " " << emission.b << " ]\n";
840
841
    // Alpha mask
842
0
    std::string alpha;
843
0
    aiString opacityTexture;
844
0
    if (material->Get(AI_MATKEY_TEXTURE_OPACITY(0), opacityTexture) == AI_SUCCESS ||
845
0
        material->Get(AI_MATKEY_TEXTURE_DIFFUSE(0), opacityTexture) == AI_SUCCESS) {
846
        // material->Get(AI_MATKEY_TEXTURE_BASE_COLOR(0), opacityTexture) == AI_SUCCESS)
847
0
        std::string texName = std::string("alpha:") + CleanTextureFilename(opacityTexture);
848
0
        if (mTextureSet.find(texName) != mTextureSet.end())
849
0
            alpha = std::string("    \"texture alpha\" \"") + texName + "\"\n";
850
0
    } else {
851
0
        float opacity = 1;
852
0
        if (material->Get(AI_MATKEY_OPACITY, opacity) == AI_SUCCESS && opacity < 1)
853
0
            alpha = std::string("    \"float alpha\" [ ") + std::to_string(opacity) + " ]\n";
854
0
    }
855
856
    // Output the shape specification
857
0
    mOutput << "Shape \"trianglemesh\"\n" <<
858
0
        alpha <<
859
0
        "    \"integer indices\" [";
860
861
    // Start with faces (which hold indices)
862
0
    for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
863
0
        auto face = mesh->mFaces[i];
864
0
        if (face.mNumIndices != 3) throw DeadlyExportError("oh no not a tri!");
865
866
0
        for (unsigned int j = 0; j < face.mNumIndices; j++) {
867
0
            mOutput << face.mIndices[j] << " ";
868
0
        }
869
0
        if ((i % 7) == 6) mOutput << "\n    ";
870
0
    }
871
0
    mOutput << "]\n";
872
873
    // Then go to vertices
874
0
    mOutput << "    \"point3 P\" [";
875
0
    for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
876
0
        auto vector = mesh->mVertices[i];
877
0
        mOutput << vector.x << " " << vector.y << " " << vector.z << "  ";
878
0
        if ((i % 4) == 3) mOutput << "\n    ";
879
0
    }
880
0
    mOutput << "]\n";
881
882
    // Normals (if present)
883
0
    if (mesh->mNormals) {
884
0
        mOutput << "    \"normal N\" [";
885
0
        for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
886
0
            auto normal = mesh->mNormals[i];
887
0
            mOutput << normal.x << " " << normal.y << " " << normal.z << "  ";
888
0
            if ((i % 4) == 3) mOutput << "\n    ";
889
0
        }
890
0
        mOutput << "]\n";
891
0
    }
892
893
    // Tangents (if present)
894
0
    if (mesh->mTangents) {
895
0
        mOutput << "    \"vector3 S\" [";
896
0
        for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
897
0
            auto tangent = mesh->mTangents[i];
898
0
            mOutput << tangent.x << " " << tangent.y << " " << tangent.z << "  ";
899
0
            if ((i % 4) == 3) mOutput << "\n    ";
900
0
        }
901
0
        mOutput << "]\n";
902
0
    }
903
904
    // Texture Coords (if present)
905
    // Find the first set of 2D texture coordinates..
906
0
    for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
907
0
        if (mesh->mNumUVComponents[i] == 2) {
908
            // assert(mesh->mTextureCoords[i] != nullptr);
909
0
            aiVector3D* uv = mesh->mTextureCoords[i];
910
0
            mOutput << "    \"point2 uv\" [";
911
0
            for (unsigned int j = 0; j < mesh->mNumVertices; ++j) {
912
0
                mOutput << uv[j].x << " " << uv[j].y << " ";
913
0
                if ((j % 6) == 5) mOutput << "\n    ";
914
0
            }
915
0
            mOutput << "]\n";
916
0
            break;
917
0
        }
918
0
    }
919
    // TODO: issue warning if there are additional UV sets?
920
921
0
    mOutput << "AttributeEnd\n";
922
0
}
923
924
0
void PbrtExporter::WriteInstanceDefinition(int i) {
925
0
    aiMesh* mesh = mScene->mMeshes[i];
926
927
0
    mOutput << "ObjectBegin \"";
928
0
    if (mesh->mName == aiString(""))
929
0
        mOutput << "mesh_" << i+1 << "\"\n";
930
0
    else
931
0
        mOutput << mesh->mName.C_Str() << "_" << i+1 << "\"\n";
932
933
0
    WriteMesh(mesh);
934
935
0
    mOutput << "ObjectEnd\n";
936
0
}
937
938
void PbrtExporter::WriteGeometricObjects(aiNode* node, aiMatrix4x4 worldFromObject,
939
0
                                         std::map<int, int> &meshUses) {
940
    // Sometimes interior nodes have degenerate matrices??
941
0
    if (node->mTransformation.Determinant() != 0)
942
0
        worldFromObject = worldFromObject * node->mTransformation;
943
944
0
    if (node->mNumMeshes > 0) {
945
0
        mOutput << "AttributeBegin\n";
946
947
0
        mOutput << "  Transform [ " << TransformAsString(worldFromObject) << "]\n";
948
949
0
        for (unsigned int i = 0; i < node->mNumMeshes; i++) {
950
0
            aiMesh* mesh = mScene->mMeshes[node->mMeshes[i]];
951
0
            if (meshUses[node->mMeshes[i]] == 1) {
952
                // If it's only used once in the scene, emit it directly as
953
                // a triangle mesh.
954
0
                mOutput << "  # " << mesh->mName.C_Str();
955
0
                WriteMesh(mesh);
956
0
            } else {
957
                // If it's used multiple times, there will be an object
958
                // instance for it, so emit a reference to that.
959
0
                mOutput << "  ObjectInstance \"";
960
0
                if (mesh->mName == aiString(""))
961
0
                    mOutput << "mesh_" << node->mMeshes[i] + 1 << "\"\n";
962
0
                else
963
0
                    mOutput << mesh->mName.C_Str() << "_" << node->mMeshes[i] + 1 << "\"\n";
964
0
            }
965
0
        }
966
0
        mOutput << "AttributeEnd\n\n";
967
0
    }
968
969
    // Recurse through children
970
0
    for (unsigned int i = 0; i < node->mNumChildren; i++) {
971
0
        WriteGeometricObjects(node->mChildren[i], worldFromObject, meshUses);
972
0
    }
973
0
}
974
975
#endif // ASSIMP_BUILD_NO_PBRT_EXPORTER
976
#endif // ASSIMP_BUILD_NO_EXPORT