Coverage Report

Created: 2026-01-07 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/IQM/IQMImporter.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2021, 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_IQM_IMPORTER
43
44
#include <assimp/DefaultIOSystem.h>
45
#include <assimp/IOStreamBuffer.h>
46
#include <assimp/ai_assert.h>
47
#include <assimp/importerdesc.h>
48
#include <assimp/scene.h>
49
#include <assimp/DefaultLogger.hpp>
50
#include <assimp/Importer.hpp>
51
#include <assimp/ByteSwapper.h>
52
#include <memory>
53
#include <numeric>
54
55
#include "IQMImporter.h"
56
#include "iqm.h"
57
58
// RESOURCES:
59
// http://sauerbraten.org/iqm/
60
// https://github.com/lsalzman/iqm
61
62
0
inline void swap_block( uint32_t *block, size_t size ){
63
0
    (void)block; // suppress 'unreferenced formal parameter' MSVC warning
64
0
    size >>= 2;
65
0
    for ( size_t i = 0; i < size; ++i )
66
0
        AI_SWAP4( block[ i ] );
67
0
}
68
69
static constexpr aiImporterDesc desc = {
70
    "Inter-Quake Model Importer",
71
    "",
72
    "",
73
    "",
74
    aiImporterFlags_SupportBinaryFlavour,
75
    0,
76
    0,
77
    0,
78
    0,
79
    "iqm"
80
};
81
82
namespace Assimp {
83
84
// ------------------------------------------------------------------------------------------------
85
//  Default constructor
86
IQMImporter::IQMImporter() :
87
327
        mScene(nullptr) {
88
    // empty
89
327
}
90
91
// ------------------------------------------------------------------------------------------------
92
//  Returns true, if file is a binary Inter-Quake Model file.
93
27
bool IQMImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
94
27
    const std::string extension = GetExtension(pFile);
95
96
27
    if (extension == "iqm")
97
0
        return true;
98
27
    else if (!extension.length() || checkSig) {
99
27
        if (!pIOHandler) {
100
0
            return true;
101
0
        }
102
27
        std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
103
27
        unsigned char data[15];
104
27
        if (!pStream || 15 != pStream->Read(data, 1, 15)) {
105
0
            return false;
106
0
        }
107
27
        return !memcmp(data, "INTERQUAKEMODEL", 15);
108
27
    }
109
0
    return false;
110
27
}
111
112
// ------------------------------------------------------------------------------------------------
113
333
const aiImporterDesc *IQMImporter::GetInfo() const {
114
333
    return &desc;
115
333
}
116
117
// ------------------------------------------------------------------------------------------------
118
//  Model 3D import implementation
119
0
void IQMImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
120
    // Read file into memory
121
0
    std::unique_ptr<IOStream> pStream(pIOHandler->Open(file, "rb"));
122
0
    if (!pStream) {
123
0
        throw DeadlyImportError("Failed to open file ", file, ".");
124
0
    }
125
126
    // Get the file-size and validate it, throwing an exception when fails
127
0
    const size_t fileSize = pStream->FileSize();
128
0
    if (fileSize < sizeof( iqmheader )) {
129
0
        throw DeadlyImportError("IQM-file ", file, " is too small.");
130
0
    }
131
0
    std::vector<unsigned char> buffer(fileSize);
132
0
    unsigned char *data = buffer.data();
133
0
    if (fileSize != pStream->Read(data, 1, fileSize)) {
134
0
        throw DeadlyImportError("Failed to read the file ", file, ".");
135
0
    }
136
137
    // get header
138
0
    iqmheader &hdr = reinterpret_cast<iqmheader&>( *data );
139
0
    swap_block( &hdr.version, sizeof( iqmheader ) - sizeof( iqmheader::magic ) );
140
141
    // extra check for header
142
0
    if (memcmp(data, IQM_MAGIC, sizeof( IQM_MAGIC ) )
143
0
     || hdr.version != IQM_VERSION
144
0
     || hdr.filesize != fileSize) {
145
0
        throw DeadlyImportError("Bad binary header in file ", file, ".");
146
0
    }
147
148
0
    ASSIMP_LOG_DEBUG("IQM: loading ", file);
149
150
    // create the root node
151
0
    pScene->mRootNode = new aiNode( "<IQMRoot>" );
152
    // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
153
0
    pScene->mRootNode->mTransformation = aiMatrix4x4(
154
0
            1.f, 0.f, 0.f, 0.f,
155
0
            0.f, 0.f, 1.f, 0.f,
156
0
            0.f, -1.f, 0.f, 0.f,
157
0
            0.f, 0.f, 0.f, 1.f);
158
0
    pScene->mRootNode->mNumMeshes = hdr.num_meshes;
159
0
    pScene->mRootNode->mMeshes = new unsigned int[hdr.num_meshes];
160
0
    std::iota( pScene->mRootNode->mMeshes, pScene->mRootNode->mMeshes + pScene->mRootNode->mNumMeshes, 0 );
161
162
0
    mScene = pScene;
163
164
    // Allocate output storage
165
0
    pScene->mNumMeshes = 0;
166
0
    pScene->mMeshes = new aiMesh *[hdr.num_meshes](); // Set arrays to zero to ensue proper destruction if an exception is raised
167
168
0
    pScene->mNumMaterials = 0;
169
0
    pScene->mMaterials = new aiMaterial *[hdr.num_meshes]();
170
171
    // swap vertex arrays beforehand...
172
0
    for( auto array = reinterpret_cast<iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end = array + hdr.num_vertexarrays; array != end; ++array )
173
0
    {
174
0
        swap_block( &array->type, sizeof( iqmvertexarray ) );
175
0
    }
176
177
    // Read all surfaces from the file
178
0
    for( auto imesh = reinterpret_cast<iqmmesh*>( data + hdr.ofs_meshes ), end_ = imesh + hdr.num_meshes; imesh != end_; ++imesh )
179
0
    {
180
0
        swap_block( &imesh->name, sizeof( iqmmesh ) );
181
        // Allocate output mesh & material
182
0
        auto mesh = pScene->mMeshes[pScene->mNumMeshes++] = new aiMesh();
183
0
        mesh->mMaterialIndex = pScene->mNumMaterials;
184
0
        auto mat = pScene->mMaterials[pScene->mNumMaterials++] = new aiMaterial();
185
186
0
        {
187
0
            auto text = reinterpret_cast<char*>( data + hdr.ofs_text );
188
0
            aiString name( text + imesh->material );
189
0
            mat->AddProperty( &name, AI_MATKEY_NAME );
190
0
            mat->AddProperty( &name, AI_MATKEY_TEXTURE_DIFFUSE(0) );
191
0
        }
192
193
        // Fill mesh information
194
0
        mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
195
0
        mesh->mNumFaces = 0;
196
0
        mesh->mFaces = new aiFace[imesh->num_triangles];
197
198
        // Fill in all triangles
199
0
        for( auto tri = reinterpret_cast<iqmtriangle*>( data + hdr.ofs_triangles ) + imesh->first_triangle, end = tri + imesh->num_triangles; tri != end; ++tri )
200
0
        {
201
0
            swap_block( tri->vertex, sizeof( tri->vertex ) );
202
0
            auto& face = mesh->mFaces[mesh->mNumFaces++];
203
0
            face.mNumIndices = 3;
204
0
            face.mIndices = new unsigned int[3]{ tri->vertex[0] - imesh->first_vertex,
205
0
                                                 tri->vertex[2] - imesh->first_vertex,
206
0
                                                 tri->vertex[1] - imesh->first_vertex };
207
0
        }
208
209
        // Fill in all vertices
210
0
        for( auto array = reinterpret_cast<const iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end__ = array + hdr.num_vertexarrays; array != end__; ++array )
211
0
        {
212
0
            const unsigned int nVerts = imesh->num_vertexes;
213
0
            const unsigned int step = array->size;
214
215
0
            switch ( array->type )
216
0
            {
217
0
            case IQM_POSITION:
218
0
                if( array->format == IQM_FLOAT && step >= 3 ){
219
0
                    mesh->mNumVertices = nVerts;
220
0
                    auto v = mesh->mVertices = new aiVector3D[nVerts];
221
0
                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
222
0
                        end = f + nVerts * step; f != end; f += step, ++v )
223
0
                    {
224
0
                        *v = { AI_BE( f[0] ),
225
0
                               AI_BE( f[1] ),
226
0
                               AI_BE( f[2] ) };
227
0
                    }
228
0
                }
229
0
                break;
230
0
               case IQM_TEXCOORD:
231
0
                if( array->format == IQM_FLOAT && step >= 2)
232
0
                {
233
0
                    auto v = mesh->mTextureCoords[0] = new aiVector3D[nVerts];
234
0
                    mesh->mNumUVComponents[0] = 2;
235
0
                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
236
0
                        end = f + nVerts * step; f != end; f += step, ++v )
237
0
                    {
238
0
                        *v = { AI_BE( f[0] ),
239
0
                           1 - AI_BE( f[1] ), 0 };
240
0
                    }
241
0
                }
242
0
                break;
243
0
            case IQM_NORMAL:
244
0
                if (array->format == IQM_FLOAT && step >= 3)
245
0
                {
246
0
                    auto v = mesh->mNormals = new aiVector3D[nVerts];
247
0
                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
248
0
                        end = f + nVerts * step; f != end; f += step, ++v )
249
0
                    {
250
0
                        *v = { AI_BE( f[0] ),
251
0
                               AI_BE( f[1] ),
252
0
                               AI_BE( f[2] ) };
253
0
                    }
254
0
                }
255
0
                break;
256
0
            case IQM_COLOR:
257
0
                if (array->format == IQM_UBYTE && step >= 3)
258
0
                {
259
0
                    auto v = mesh->mColors[0] = new aiColor4D[nVerts];
260
0
                    for( auto f = ( data + array->offset ) + imesh->first_vertex * step,
261
0
                        end = f + nVerts * step; f != end; f += step, ++v )
262
0
                    {
263
0
                        *v = { ( f[0] ) / 255.f,
264
0
                               ( f[1] ) / 255.f,
265
0
                               ( f[2] ) / 255.f,
266
0
                               step == 3? 1 : ( f[3] ) / 255.f };
267
0
                    }
268
0
                }
269
0
                else if (array->format == IQM_FLOAT && step >= 3)
270
0
                {
271
0
                    auto v = mesh->mColors[0] = new aiColor4D[nVerts];
272
0
                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
273
0
                        end = f + nVerts * step; f != end; f += step, ++v )
274
0
                    {
275
0
                        *v = { AI_BE( f[0] ),
276
0
                               AI_BE( f[1] ),
277
0
                               AI_BE( f[2] ),
278
0
                               step == 3? 1 : AI_BE( f[3] ) };
279
0
                    }
280
0
                }
281
0
                break;
282
0
            case IQM_TANGENT:
283
#if 0
284
                if (array->format == IQM_FLOAT && step >= 3)
285
                {
286
                    auto v = mesh->mTangents = new aiVector3D[nVerts];
287
                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
288
                        end = f + nVerts * step; f != end; f += step, ++v )
289
                    {
290
                        *v = { AI_BE( f[0] ),
291
                               AI_BE( f[1] ),
292
                               AI_BE( f[2] ) };
293
                    }
294
                }
295
#endif
296
0
                break;
297
0
            case IQM_BLENDINDEXES:
298
0
            case IQM_BLENDWEIGHTS:
299
0
            case IQM_CUSTOM:
300
0
                break; // these attributes are not relevant.
301
302
0
            default:
303
0
                break;
304
0
            }
305
0
        }
306
0
    }
307
0
}
308
309
310
// ------------------------------------------------------------------------------------------------
311
312
} // Namespace Assimp
313
314
#endif // !! ASSIMP_BUILD_NO_IQM_IMPORTER