Coverage Report

Created: 2024-08-02 07:04

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