Coverage Report

Created: 2025-11-11 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/OFF/OFFLoader.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2025, assimp team
7
8
All rights reserved.
9
10
Redistribution and use of this software in source and binary forms,
11
with or without modification, are permitted provided that the following
12
conditions are met:
13
14
* Redistributions of source code must retain the above
15
  copyright notice, this list of conditions and the
16
  following disclaimer.
17
18
* Redistributions in binary form must reproduce the above
19
  copyright notice, this list of conditions and the
20
  following disclaimer in the documentation and/or other
21
  materials provided with the distribution.
22
23
* Neither the name of the assimp team, nor the names of its
24
  contributors may be used to endorse or promote products
25
  derived from this software without specific prior
26
  written permission of the assimp team.
27
28
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
---------------------------------------------------------------------------
40
*/
41
42
/** @file  OFFLoader.cpp
43
 *  @brief Implementation of the OFF importer class
44
 */
45
46
#ifndef ASSIMP_BUILD_NO_OFF_IMPORTER
47
48
// internal headers
49
#include "OFFLoader.h"
50
#include <assimp/ParsingUtils.h>
51
#include <assimp/fast_atof.h>
52
#include <memory>
53
#include <assimp/IOSystem.hpp>
54
#include <assimp/scene.h>
55
#include <assimp/DefaultLogger.hpp>
56
#include <assimp/importerdesc.h>
57
58
namespace Assimp {
59
60
static constexpr aiImporterDesc desc = {
61
    "OFF Importer",
62
    "",
63
    "",
64
    "",
65
    aiImporterFlags_SupportBinaryFlavour,
66
    0,
67
    0,
68
    0,
69
    0,
70
    "off"
71
};
72
73
// ------------------------------------------------------------------------------------------------
74
// Returns whether the class can handle the format of the given file.
75
409
bool OFFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
76
409
    static const char *tokens[] = { "off" };
77
409
    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 3);
78
409
}
79
80
// ------------------------------------------------------------------------------------------------
81
877
const aiImporterDesc *OFFImporter::GetInfo() const {
82
877
    return &desc;
83
877
}
84
85
// skip blank space, lines and comments
86
0
static void NextToken(const char **car, const char *end) {
87
0
    SkipSpacesAndLineEnd(car, end);
88
0
    while (*car < end && (**car == '#' || **car == '\n' || **car == '\r')) {
89
0
        SkipLine(car, end);
90
0
        SkipSpacesAndLineEnd(car, end);
91
0
    }
92
0
}
93
94
// ------------------------------------------------------------------------------------------------
95
// Imports the given file into the given scene structure.
96
0
void OFFImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
97
0
    std::unique_ptr<IOStream> file(pIOHandler->Open(pFile, "rb"));
98
99
    // Check whether we can read from the file
100
0
    if (file == nullptr) {
101
0
        throw DeadlyImportError("Failed to open OFF file ", pFile, ".");
102
0
    }
103
104
    // allocate storage and copy the contents of the file to a memory buffer
105
0
    std::vector<char> mBuffer2;
106
0
    TextFileToBuffer(file.get(), mBuffer2);
107
0
    const char *buffer = &mBuffer2[0];
108
109
    // Proper OFF header parser. We only implement normal loading for now.
110
0
    bool hasTexCoord = false, hasNormals = false, hasColors = false;
111
0
    bool hasHomogenous = false, hasDimension = false;
112
0
    unsigned int dimensions = 3;
113
0
    const char *car = buffer;
114
0
    const char *end = buffer + mBuffer2.size();
115
0
    NextToken(&car, end);
116
117
0
    if (car < end - 2 && car[0] == 'S' && car[1] == 'T') {
118
0
        hasTexCoord = true;
119
0
        car += 2;
120
0
    }
121
0
    if (car < end - 1 && car[0] == 'C') {
122
0
        hasColors = true;
123
0
        car++;
124
0
    }
125
0
    if (car < end - 1 && car[0] == 'N') {
126
0
        hasNormals = true;
127
0
        car++;
128
0
    }
129
0
    if (car < end - 1 && car[0] == '4') {
130
0
        hasHomogenous = true;
131
0
        car++;
132
0
    }
133
0
    if (car < end - 1 && car[0] == 'n') {
134
0
        hasDimension = true;
135
0
        car++;
136
0
    }
137
0
    if (car < end - 3 && car[0] == 'O' && car[1] == 'F' && car[2] == 'F') {
138
0
        car += 3;
139
0
        NextToken(&car, end);
140
0
    } else {
141
        // in case there is no OFF header (which is allowed by the
142
        // specification...), then we might have unintentionally read an
143
        // additional dimension from the primitive count fields
144
0
        dimensions = 3;
145
0
        hasHomogenous = false;
146
0
        NextToken(&car, end);
147
148
        // at this point the next token should be an integer number
149
0
        if (car >= end - 1 || *car < '0' || *car > '9') {
150
0
            throw DeadlyImportError("OFF: Header is invalid");
151
0
        }
152
0
    }
153
0
    if (hasDimension) {
154
0
        dimensions = strtoul10(car, &car);
155
0
        NextToken(&car, end);
156
0
    }
157
0
    if (dimensions > 3) {
158
0
        throw DeadlyImportError("OFF: Number of vertex coordinates higher than 3 unsupported");
159
0
    }
160
161
0
    NextToken(&car, end);
162
0
    const unsigned int numVertices = strtoul10(car, &car);
163
0
    NextToken(&car, end);
164
0
    const unsigned int numFaces = strtoul10(car, &car);
165
0
    NextToken(&car, end);
166
0
    strtoul10(car, &car); // skip edge count
167
0
    NextToken(&car, end);
168
169
0
    if (!numVertices) {
170
0
        throw DeadlyImportError("OFF: There are no valid vertices");
171
0
    }
172
0
    if (!numFaces) {
173
0
        throw DeadlyImportError("OFF: There are no valid faces");
174
0
    }
175
176
0
    pScene->mNumMeshes = 1;
177
0
    pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
178
179
0
    aiMesh *mesh = new aiMesh();
180
0
    pScene->mMeshes[0] = mesh;
181
182
0
    mesh->mNumFaces = numFaces;
183
0
    aiFace *faces = new aiFace[mesh->mNumFaces];
184
0
    mesh->mFaces = faces;
185
186
0
    mesh->mNumVertices = numVertices;
187
0
    mesh->mVertices = new aiVector3D[numVertices];
188
0
    mesh->mNormals = hasNormals ? new aiVector3D[numVertices] : nullptr;
189
0
    mesh->mColors[0] = hasColors ? new aiColor4D[numVertices] : nullptr;
190
191
0
    if (hasTexCoord) {
192
0
        mesh->mNumUVComponents[0] = 2;
193
0
        mesh->mTextureCoords[0] = new aiVector3D[numVertices];
194
0
    }
195
0
    char line[4096];
196
0
    buffer = car;
197
0
    const char *sz = car;
198
0
    const char *lineEnd = &line[4096];
199
200
    // now read all vertex lines
201
0
    for (unsigned int i = 0; i < numVertices; ++i) {
202
0
        if (!GetNextLine(buffer, line)) {
203
0
            ASSIMP_LOG_ERROR("OFF: The number of verts in the header is incorrect");
204
0
            break;
205
0
        }
206
0
        aiVector3D &v = mesh->mVertices[i];
207
0
        sz = line;
208
209
        // helper array to write a for loop over possible dimension values
210
0
        ai_real *vec[3] = { &v.x, &v.y, &v.z };
211
212
        // stop at dimensions: this allows loading 1D or 2D coordinate vertices
213
0
        for (unsigned int dim = 0; dim < dimensions; ++dim) {
214
0
            SkipSpaces(&sz, lineEnd);
215
0
            sz = fast_atoreal_move(sz, *vec[dim]);
216
0
        }
217
218
        // if has homogeneous coordinate, divide others by this one
219
0
        if (hasHomogenous) {
220
0
            SkipSpaces(&sz, lineEnd);
221
0
            ai_real w = 1.;
222
0
            sz = fast_atoreal_move(sz, w);
223
0
            for (unsigned int dim = 0; dim < dimensions; ++dim) {
224
0
                *(vec[dim]) /= w;
225
0
            }
226
0
        }
227
228
        // read optional normals
229
0
        if (hasNormals) {
230
0
            aiVector3D &n = mesh->mNormals[i];
231
0
            SkipSpaces(&sz, lineEnd);
232
0
            sz = fast_atoreal_move(sz, n.x);
233
0
            SkipSpaces(&sz, lineEnd);
234
0
            sz = fast_atoreal_move(sz, n.y);
235
0
            SkipSpaces(&sz, lineEnd);
236
0
            fast_atoreal_move(sz, n.z);
237
0
        }
238
239
        // reading colors is a pain because the specification says it can be
240
        // integers or floats, and any number of them between 1 and 4 included,
241
        // until the next comment or end of line
242
        // in theory should be testing type !
243
0
        if (hasColors) {
244
0
            aiColor4D &c = mesh->mColors[0][i];
245
0
            SkipSpaces(&sz, lineEnd);
246
0
            sz = fast_atoreal_move(sz, c.r);
247
0
            if (*sz != '#' && *sz != '\n' && *sz != '\r') {
248
0
                SkipSpaces(&sz, lineEnd);
249
0
                sz = fast_atoreal_move(sz, c.g);
250
0
            } else {
251
0
                c.g = 0.;
252
0
            }
253
0
            if (*sz != '#' && *sz != '\n' && *sz != '\r') {
254
0
                SkipSpaces(&sz, lineEnd);
255
0
                sz = fast_atoreal_move(sz, c.b);
256
0
            } else {
257
0
                c.b = 0.;
258
0
            }
259
0
            if (*sz != '#' && *sz != '\n' && *sz != '\r') {
260
0
                SkipSpaces(&sz, lineEnd);
261
0
                sz = fast_atoreal_move(sz, c.a);
262
0
            } else {
263
0
                c.a = 1.;
264
0
            }
265
0
        }
266
0
        if (hasTexCoord) {
267
0
            aiVector3D &t = mesh->mTextureCoords[0][i];
268
0
            SkipSpaces(&sz, lineEnd);
269
0
            sz = fast_atoreal_move(sz, t.x);
270
0
            SkipSpaces(&sz, lineEnd);
271
0
            fast_atoreal_move(sz, t.y);
272
0
        }
273
0
    }
274
275
    // load faces with their indices
276
0
    faces = mesh->mFaces;
277
0
    for (unsigned int i = 0; i < numFaces;) {
278
0
        if (!GetNextLine(buffer, line)) {
279
0
            ASSIMP_LOG_ERROR("OFF: The number of faces in the header is incorrect");
280
0
            throw DeadlyImportError("OFF: The number of faces in the header is incorrect");
281
0
        }
282
0
        unsigned int idx;
283
0
        sz = line;
284
0
        SkipSpaces(&sz, lineEnd);
285
0
        idx = strtoul10(sz, &sz);
286
0
        if (!idx || idx > 9) {
287
0
            ASSIMP_LOG_ERROR("OFF: Faces with zero indices aren't allowed");
288
0
            --mesh->mNumFaces;
289
0
            ++i;
290
0
            continue;
291
0
        }
292
0
        faces->mNumIndices = idx;
293
0
        faces->mIndices = new unsigned int[faces->mNumIndices];
294
0
        for (unsigned int m = 0; m < faces->mNumIndices; ++m) {
295
0
            SkipSpaces(&sz, lineEnd);
296
0
            idx = strtoul10(sz, &sz);
297
0
            if (idx >= numVertices) {
298
0
                ASSIMP_LOG_ERROR("OFF: Vertex index is out of range");
299
0
                idx = numVertices - 1;
300
0
            }
301
0
            faces->mIndices[m] = idx;
302
0
        }
303
0
        ++i;
304
0
        ++faces;
305
0
    }
306
307
    // generate the output node graph
308
0
    pScene->mRootNode = new aiNode();
309
0
    pScene->mRootNode->mName.Set("<OFFRoot>");
310
0
    pScene->mRootNode->mNumMeshes = 1;
311
0
    pScene->mRootNode->mMeshes = new unsigned int[pScene->mRootNode->mNumMeshes];
312
0
    pScene->mRootNode->mMeshes[0] = 0;
313
314
    // generate a default material
315
0
    pScene->mNumMaterials = 1;
316
0
    pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
317
0
    aiMaterial *pcMat = new aiMaterial();
318
319
0
    aiColor4D clr(0.6f, 0.6f, 0.6f, 1.0f);
320
0
    pcMat->AddProperty(&clr, 1, AI_MATKEY_COLOR_DIFFUSE);
321
0
    pScene->mMaterials[0] = pcMat;
322
323
0
    const int twosided = 1;
324
    pcMat->AddProperty(&twosided, 1, AI_MATKEY_TWOSIDED);
325
0
}
326
327
} // namespace Assimp
328
329
#endif // !! ASSIMP_BUILD_NO_OFF_IMPORTER