Coverage Report

Created: 2026-01-07 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/Ogre/OgreMaterial.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2025, 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_OGRE_IMPORTER
43
44
#include "OgreImporter.h"
45
#include <assimp/StringUtils.h>
46
#include <assimp/TinyFormatter.h>
47
#include <assimp/fast_atof.h>
48
#include <assimp/material.h>
49
#include <assimp/scene.h>
50
#include <assimp/DefaultLogger.hpp>
51
52
#include <memory>
53
#include <sstream>
54
#include <vector>
55
56
using namespace std;
57
58
namespace Assimp {
59
namespace Ogre {
60
61
static const string partComment = "//";
62
static const string partBlockStart = "{";
63
static const string partBlockEnd = "}";
64
65
0
void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, Mesh *mesh) {
66
0
    std::vector<aiMaterial *> materials;
67
68
    // Create materials that can be found and parsed via the IOSystem.
69
0
    for (size_t i = 0, len = mesh->NumSubMeshes(); i < len; ++i) {
70
0
        SubMesh *submesh = mesh->GetSubMesh(i);
71
0
        if (submesh && !submesh->materialRef.empty()) {
72
0
            aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef);
73
0
            if (material) {
74
0
                submesh->materialIndex = static_cast<int>(materials.size());
75
0
                materials.push_back(material);
76
0
            }
77
0
        }
78
0
    }
79
80
0
    AssignMaterials(pScene, materials);
81
0
}
82
83
0
void OgreImporter::ReadMaterials(const std::string &pFile, Assimp::IOSystem *pIOHandler, aiScene *pScene, MeshXml *mesh) {
84
0
    std::vector<aiMaterial *> materials;
85
86
    // Create materials that can be found and parsed via the IOSystem.
87
0
    for (size_t i = 0, len = mesh->NumSubMeshes(); i < len; ++i) {
88
0
        SubMeshXml *submesh = mesh->GetSubMesh(static_cast<uint16_t>(i));
89
0
        if (submesh && !submesh->materialRef.empty()) {
90
0
            aiMaterial *material = ReadMaterial(pFile, pIOHandler, submesh->materialRef);
91
0
            if (material) {
92
0
                submesh->materialIndex = static_cast<int>(materials.size());
93
0
                materials.push_back(material);
94
0
            }
95
0
        }
96
0
    }
97
98
0
    AssignMaterials(pScene, materials);
99
0
}
100
101
0
void OgreImporter::AssignMaterials(aiScene *pScene, std::vector<aiMaterial *> &materials) {
102
0
    pScene->mNumMaterials = static_cast<unsigned int>(materials.size());
103
0
    if (pScene->mNumMaterials > 0) {
104
0
        pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
105
0
        for (size_t i = 0; i < pScene->mNumMaterials; ++i) {
106
0
            pScene->mMaterials[i] = materials[i];
107
0
        }
108
0
    }
109
0
}
110
111
0
aiMaterial *OgreImporter::ReadMaterial(const std::string &pFile, Assimp::IOSystem *pIOHandler, const std::string &materialName) {
112
0
    if (materialName.empty()) {
113
0
        return nullptr;
114
0
    }
115
116
    // Full reference and examples of Ogre Material Script
117
    // can be found from http://www.ogre3d.org/docs/manual/manual_14.html
118
119
    /*and here is another one:
120
121
    import * from abstract_base_passes_depth.material
122
    import * from abstract_base.material
123
    import * from mat_shadow_caster.material
124
    import * from mat_character_singlepass.material
125
126
    material hero/hair/caster : mat_shadow_caster_skin_areject
127
    {
128
      set $diffuse_map "hero_hair_alpha_c.dds"
129
    }
130
131
    material hero/hair_alpha : mat_char_cns_singlepass_areject_4weights
132
    {
133
      set $diffuse_map  "hero_hair_alpha_c.dds"
134
      set $specular_map "hero_hair_alpha_s.dds"
135
      set $normal_map   "hero_hair_alpha_n.dds"
136
      set $light_map    "black_lightmap.dds"
137
138
      set $shadow_caster_material "hero/hair/caster"
139
    }
140
    */
141
142
0
    stringstream ss;
143
144
    // Scope for scopre_ptr auto release
145
0
    {
146
        /* There are three .material options in priority order:
147
            1) File with the material name (materialName)
148
            2) File with the mesh files base name (pFile)
149
            3) Optional user defined material library file (m_userDefinedMaterialLibFile) */
150
0
        std::vector<string> potentialFiles;
151
0
        potentialFiles.push_back(materialName + ".material");
152
0
        potentialFiles.push_back(pFile.substr(0, pFile.rfind(".mesh")) + ".material");
153
0
        if (!m_userDefinedMaterialLibFile.empty())
154
0
            potentialFiles.push_back(m_userDefinedMaterialLibFile);
155
156
0
        IOStream *materialFile = nullptr;
157
0
        for (size_t i = 0; i < potentialFiles.size(); ++i) {
158
0
            materialFile = pIOHandler->Open(potentialFiles[i]);
159
0
            if (materialFile) {
160
0
                break;
161
0
            }
162
0
            ASSIMP_LOG_VERBOSE_DEBUG("Source file for material '", materialName, "' ", potentialFiles[i], " does not exist");
163
0
        }
164
0
        if (!materialFile) {
165
0
            ASSIMP_LOG_ERROR("Failed to find source file for material '", materialName, "'");
166
0
            return nullptr;
167
0
        }
168
169
0
        std::unique_ptr<IOStream> stream(materialFile);
170
0
        if (stream->FileSize() == 0) {
171
0
            ASSIMP_LOG_WARN("Source file for material '", materialName, "' is empty (size is 0 bytes)");
172
0
            return nullptr;
173
0
        }
174
175
        // Read bytes
176
0
        vector<char> data(stream->FileSize());
177
0
        stream->Read(&data[0], stream->FileSize(), 1);
178
179
        // Convert to UTF-8 and terminate the string for ss
180
0
        BaseImporter::ConvertToUTF8(data);
181
0
        data.push_back('\0');
182
183
0
        ss << &data[0];
184
0
    }
185
186
0
    ASSIMP_LOG_VERBOSE_DEBUG("Reading material '", materialName, "'");
187
188
0
    aiMaterial *material = new aiMaterial();
189
0
    m_textures.clear();
190
191
0
    aiString matName(materialName);
192
0
    material->AddProperty(&matName, AI_MATKEY_NAME);
193
194
    // The stringstream will push words from a line until newline.
195
    // It will also trim whitespace from line start and between words.
196
0
    string linePart;
197
0
    ss >> linePart;
198
199
0
    const string partMaterial = "material";
200
0
    const string partTechnique = "technique";
201
202
0
    while (!ss.eof()) {
203
        // Skip commented lines
204
0
        if (linePart == partComment) {
205
0
            NextAfterNewLine(ss, linePart);
206
0
            continue;
207
0
        }
208
0
        if (linePart != partMaterial) {
209
0
            ss >> linePart;
210
0
            continue;
211
0
        }
212
213
0
        ss >> linePart;
214
0
        if (linePart != materialName) {
215
0
            ss >> linePart;
216
0
            continue;
217
0
        }
218
219
0
        NextAfterNewLine(ss, linePart);
220
0
        if (linePart != partBlockStart) {
221
0
            ASSIMP_LOG_ERROR("Invalid material: block start missing near index ", ss.tellg());
222
0
            return material;
223
0
        }
224
225
0
        ASSIMP_LOG_VERBOSE_DEBUG("material '", materialName, "'");
226
227
0
        while (linePart != partBlockEnd) {
228
            // Proceed to the first technique
229
0
            ss >> linePart;
230
231
0
            if (linePart == partTechnique) {
232
0
                std::string techniqueName = SkipLine(ss);
233
0
                ReadTechnique(ai_trim(techniqueName), ss, material);
234
0
            }
235
236
            // Read information from a custom material
237
            /** @todo This "set $x y" does not seem to be a official Ogre material system feature.
238
                Materials can inherit other materials and override texture units by using the (unique)
239
                parent texture unit name in your cloned material.
240
                This is not yet supported and below code is probably some hack from the original
241
                author of this Ogre importer. Should be removed? */
242
0
            if (linePart == "set") {
243
0
                ss >> linePart;
244
0
                if (linePart == "$specular") //todo load this values:
245
0
                {
246
0
                } else if (linePart == "$diffuse") {
247
0
                } else if (linePart == "$ambient") {
248
0
                } else if (linePart == "$colormap") {
249
0
                    ss >> linePart;
250
0
                    aiString cm(linePart);
251
0
                    material->AddProperty(&cm, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
252
0
                } else if (linePart == "$normalmap") {
253
0
                    ss >> linePart;
254
0
                    aiString nm(linePart);
255
0
                    material->AddProperty(&nm, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0));
256
0
                } else if (linePart == "$shininess_strength") {
257
0
                    ss >> linePart;
258
0
                    float Shininess = fast_atof(linePart.c_str());
259
0
                    material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS_STRENGTH);
260
0
                } else if (linePart == "$shininess_exponent") {
261
0
                    ss >> linePart;
262
0
                    float Shininess = fast_atof(linePart.c_str());
263
0
                    material->AddProperty(&Shininess, 1, AI_MATKEY_SHININESS);
264
0
                }
265
                //Properties from Venetica:
266
0
                else if (linePart == "$diffuse_map") {
267
0
                    ss >> linePart;
268
0
                    if (linePart[0] == '"') // "file" -> file
269
0
                        linePart = linePart.substr(1, linePart.size() - 2);
270
0
                    aiString ts(linePart);
271
0
                    material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
272
0
                } else if (linePart == "$specular_map") {
273
0
                    ss >> linePart;
274
0
                    if (linePart[0] == '"') // "file" -> file
275
0
                        linePart = linePart.substr(1, linePart.size() - 2);
276
0
                    aiString ts(linePart);
277
0
                    material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_SHININESS, 0));
278
0
                } else if (linePart == "$normal_map") {
279
0
                    ss >> linePart;
280
0
                    if (linePart[0] == '"') // "file" -> file
281
0
                        linePart = linePart.substr(1, linePart.size() - 2);
282
0
                    aiString ts(linePart);
283
0
                    material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0));
284
0
                } else if (linePart == "$light_map") {
285
0
                    ss >> linePart;
286
0
                    if (linePart[0] == '"') {
287
0
                        linePart = linePart.substr(1, linePart.size() - 2);
288
0
                    }
289
0
                    aiString ts(linePart);
290
0
                    material->AddProperty(&ts, AI_MATKEY_TEXTURE(aiTextureType_LIGHTMAP, 0));
291
0
                }
292
0
            }
293
0
        }
294
0
        ss >> linePart;
295
0
    }
296
297
0
    return material;
298
0
}
299
300
0
bool OgreImporter::ReadTechnique(const std::string &techniqueName, stringstream &ss, aiMaterial *material) {
301
0
    string linePart;
302
0
    ss >> linePart;
303
304
0
    if (linePart != partBlockStart) {
305
0
        ASSIMP_LOG_ERROR("Invalid material: Technique block start missing near index ", ss.tellg());
306
0
        return false;
307
0
    }
308
309
0
    ASSIMP_LOG_VERBOSE_DEBUG(" technique '", techniqueName, "'");
310
311
0
    const string partPass = "pass";
312
313
0
    while (linePart != partBlockEnd) {
314
0
        ss >> linePart;
315
316
        // Skip commented lines
317
0
        if (linePart == partComment) {
318
0
            SkipLine(ss);
319
0
            continue;
320
0
        }
321
322
        /// @todo Techniques have other attributes than just passes.
323
0
        if (linePart == partPass) {
324
0
            string passName = SkipLine(ss);
325
0
            ReadPass(ai_trim(passName), ss, material);
326
0
        }
327
0
    }
328
0
    return true;
329
0
}
330
331
0
bool OgreImporter::ReadPass(const std::string &passName, stringstream &ss, aiMaterial *material) {
332
0
    string linePart;
333
0
    ss >> linePart;
334
335
0
    if (linePart != partBlockStart) {
336
0
        ASSIMP_LOG_ERROR("Invalid material: Pass block start missing near index ", ss.tellg());
337
0
        return false;
338
0
    }
339
340
0
    ASSIMP_LOG_VERBOSE_DEBUG("  pass '", passName, "'");
341
342
0
    const string partAmbient = "ambient";
343
0
    const string partDiffuse = "diffuse";
344
0
    const string partSpecular = "specular";
345
0
    const string partEmissive = "emissive";
346
0
    const string partTextureUnit = "texture_unit";
347
348
0
    while (linePart != partBlockEnd) {
349
0
        ss >> linePart;
350
351
        // Skip commented lines
352
0
        if (linePart == partComment) {
353
0
            SkipLine(ss);
354
0
            continue;
355
0
        }
356
357
        // Colors
358
        /// @todo Support alpha via aiColor4D.
359
0
        if (linePart == partAmbient || linePart == partDiffuse || linePart == partSpecular || linePart == partEmissive) {
360
0
            float r, g, b;
361
0
            ss >> r >> g >> b;
362
0
            const aiColor3D color(r, g, b);
363
364
0
            ASSIMP_LOG_VERBOSE_DEBUG("   ", linePart, " ", r, " ", g, " ", b);
365
366
0
            if (linePart == partAmbient) {
367
0
                material->AddProperty(&color, 1, AI_MATKEY_COLOR_AMBIENT);
368
0
            } else if (linePart == partDiffuse) {
369
0
                material->AddProperty(&color, 1, AI_MATKEY_COLOR_DIFFUSE);
370
0
            } else if (linePart == partSpecular) {
371
0
                material->AddProperty(&color, 1, AI_MATKEY_COLOR_SPECULAR);
372
0
            } else if (linePart == partEmissive) {
373
0
                material->AddProperty(&color, 1, AI_MATKEY_COLOR_EMISSIVE);
374
0
            }
375
0
        } else if (linePart == partTextureUnit) {
376
0
            string textureUnitName = SkipLine(ss);
377
0
            ReadTextureUnit(ai_trim(textureUnitName), ss, material);
378
0
        }
379
0
    }
380
0
    return true;
381
0
}
382
383
0
bool OgreImporter::ReadTextureUnit(const std::string &textureUnitName, stringstream &ss, aiMaterial *material) {
384
0
    string linePart;
385
0
    ss >> linePart;
386
387
0
    if (linePart != partBlockStart) {
388
0
        ASSIMP_LOG_ERROR("Invalid material: Texture unit block start missing near index ", ss.tellg());
389
0
        return false;
390
0
    }
391
392
0
    ASSIMP_LOG_VERBOSE_DEBUG("   texture_unit '", textureUnitName, "'");
393
394
0
    const string partTexture = "texture";
395
0
    const string partTextCoordSet = "tex_coord_set";
396
0
    const string partColorOp = "colour_op";
397
398
0
    aiTextureType textureType = aiTextureType_NONE;
399
0
    std::string textureRef;
400
0
    int uvCoord = 0;
401
402
0
    while (linePart != partBlockEnd) {
403
0
        ss >> linePart;
404
405
        // Skip commented lines
406
0
        if (linePart == partComment) {
407
0
            SkipLine(ss);
408
0
            continue;
409
0
        }
410
411
0
        if (linePart == partTexture) {
412
0
            ss >> linePart;
413
0
            textureRef = linePart;
414
415
            // User defined Assimp config property to detect texture type from filename.
416
0
            if (m_detectTextureTypeFromFilename) {
417
0
                size_t posSuffix = textureRef.find_last_of('.');
418
0
                size_t posUnderscore = textureRef.find_last_of('_');
419
420
0
                if (posSuffix != string::npos && posUnderscore != string::npos && posSuffix > posUnderscore) {
421
0
                    string identifier = ai_tolower(textureRef.substr(posUnderscore, posSuffix - posUnderscore));
422
0
                    ASSIMP_LOG_VERBOSE_DEBUG("Detecting texture type from filename postfix '", identifier, "'");
423
424
0
                    if (identifier == "_n" || identifier == "_nrm" || identifier == "_nrml" || identifier == "_normal" || identifier == "_normals" || identifier == "_normalmap") {
425
0
                        textureType = aiTextureType_NORMALS;
426
0
                    } else if (identifier == "_s" || identifier == "_spec" || identifier == "_specular" || identifier == "_specularmap") {
427
0
                        textureType = aiTextureType_SPECULAR;
428
0
                    } else if (identifier == "_l" || identifier == "_light" || identifier == "_lightmap" || identifier == "_occ" || identifier == "_occlusion") {
429
0
                        textureType = aiTextureType_LIGHTMAP;
430
0
                    } else if (identifier == "_disp" || identifier == "_displacement") {
431
0
                        textureType = aiTextureType_DISPLACEMENT;
432
0
                    } else {
433
0
                        textureType = aiTextureType_DIFFUSE;
434
0
                    }
435
0
                } else {
436
0
                    textureType = aiTextureType_DIFFUSE;
437
0
                }
438
0
            }
439
            // Detect from texture unit name. This cannot be too broad as
440
            // authors might give names like "LightSaber" or "NormalNinja".
441
0
            else {
442
0
                string unitNameLower = ai_tolower(textureUnitName);
443
0
                if (unitNameLower.find("normalmap") != string::npos) {
444
0
                    textureType = aiTextureType_NORMALS;
445
0
                } else if (unitNameLower.find("specularmap") != string::npos) {
446
0
                    textureType = aiTextureType_SPECULAR;
447
0
                } else if (unitNameLower.find("lightmap") != string::npos) {
448
0
                    textureType = aiTextureType_LIGHTMAP;
449
0
                } else if (unitNameLower.find("displacementmap") != string::npos) {
450
0
                    textureType = aiTextureType_DISPLACEMENT;
451
0
                } else {
452
0
                    textureType = aiTextureType_DIFFUSE;
453
0
                }
454
0
            }
455
0
        } else if (linePart == partTextCoordSet) {
456
0
            ss >> uvCoord;
457
0
        }
458
        /// @todo Implement
459
0
        else if (linePart == partColorOp) {
460
            /*
461
            ss >> linePart;
462
            if("replace"==linePart)//I don't think, assimp has something for this...
463
            {
464
            }
465
            else if("modulate"==linePart)
466
            {
467
                //TODO: set value
468
                //material->AddProperty(aiTextureOp_Multiply)
469
            }
470
            */
471
0
        }
472
0
    }
473
474
0
    if (textureRef.empty()) {
475
0
        ASSIMP_LOG_WARN("Texture reference is empty, ignoring texture_unit.");
476
0
        return false;
477
0
    }
478
0
    if (textureType == aiTextureType_NONE) {
479
0
        ASSIMP_LOG_WARN("Failed to detect texture type for '", textureRef, "', ignoring texture_unit.");
480
0
        return false;
481
0
    }
482
483
0
    unsigned int textureTypeIndex = m_textures[textureType];
484
0
    m_textures[textureType]++;
485
486
0
    ASSIMP_LOG_VERBOSE_DEBUG("    texture '", textureRef, "' type ", textureType,
487
0
            " index ", textureTypeIndex, " UV ", uvCoord);
488
489
0
    aiString assimpTextureRef(textureRef);
490
0
    material->AddProperty(&assimpTextureRef, AI_MATKEY_TEXTURE(textureType, textureTypeIndex));
491
0
    material->AddProperty(&uvCoord, 1, AI_MATKEY_UVWSRC(textureType, textureTypeIndex));
492
493
0
    return true;
494
0
}
495
496
} // namespace Ogre
497
} // namespace Assimp
498
499
#endif // ASSIMP_BUILD_NO_OGRE_IMPORTER