Coverage Report

Created: 2026-06-15 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/SIB/SIBImporter.cpp
Line
Count
Source
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2026, 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  SIBImporter.cpp
43
 *  @brief Implementation of the SIB importer class.
44
 *
45
 *  The Nevercenter Silo SIB format is undocumented.
46
 *  All details here have been reverse engineered from
47
 *  studying the binary files output by Silo.
48
 *
49
 *  Nevertheless, this implementation is reasonably complete.
50
 */
51
52
#ifndef ASSIMP_BUILD_NO_SIB_IMPORTER
53
54
// internal headers
55
#include "SIBImporter.h"
56
#include <assimp/ByteSwapper.h>
57
#include <assimp/StreamReader.h>
58
#include <assimp/TinyFormatter.h>
59
#include "utf8.h"
60
#include <assimp/importerdesc.h>
61
#include <assimp/scene.h>
62
#include <assimp/DefaultLogger.hpp>
63
#include <assimp/IOSystem.hpp>
64
#include <assimp/StringUtils.h>
65
66
#include <map>
67
68
namespace Assimp {
69
70
static constexpr aiImporterDesc desc = {
71
    "Silo SIB Importer",
72
    "Richard Mitton (http://www.codersnotes.com/about)",
73
    "",
74
    "Does not apply subdivision.",
75
    aiImporterFlags_SupportBinaryFlavour,
76
    0, 0,
77
    0, 0,
78
    "sib"
79
};
80
81
struct SIBChunk {
82
    uint32_t Tag;
83
    uint32_t Size;
84
};
85
86
enum {
87
    POS,
88
    NRM,
89
    UV,
90
    N
91
};
92
93
using SIBPair = std::pair<uint32_t, uint32_t>;
94
95
struct SIBEdge {
96
    uint32_t faceA, faceB;
97
    bool creased;
98
};
99
100
struct SIBMesh {
101
    aiMatrix4x4 axis;
102
    uint32_t numPts;
103
    std::vector<aiVector3D> pos, nrm, uv;
104
    std::vector<uint32_t> idx;
105
    std::vector<uint32_t> faceStart;
106
    std::vector<uint32_t> mtls;
107
    std::vector<SIBEdge> edges;
108
    std::map<SIBPair, uint32_t> edgeMap;
109
};
110
111
struct SIBObject {
112
    aiString name;
113
    aiMatrix4x4 axis;
114
    size_t meshIdx, meshCount;
115
};
116
117
struct SIB {
118
    std::vector<aiMaterial *> mtls;
119
    std::vector<aiMesh *> meshes;
120
    std::vector<aiLight *> lights;
121
    std::vector<SIBObject> objs, insts;
122
};
123
124
// ------------------------------------------------------------------------------------------------
125
0
static SIBEdge &GetEdge(SIBMesh *mesh, uint32_t posA, uint32_t posB) {
126
0
    SIBPair pair = (posA < posB) ? SIBPair(posA, posB) : SIBPair(posB, posA);
127
0
    std::map<SIBPair, uint32_t>::iterator it = mesh->edgeMap.find(pair);
128
0
    if (it != mesh->edgeMap.end())
129
0
        return mesh->edges[it->second];
130
131
0
    SIBEdge edge;
132
0
    edge.creased = false;
133
0
    edge.faceA = edge.faceB = 0xffffffff;
134
0
    mesh->edgeMap[pair] = static_cast<uint32_t>(mesh->edges.size());
135
0
    mesh->edges.push_back(edge);
136
0
    return mesh->edges.back();
137
0
}
138
139
// ------------------------------------------------------------------------------------------------
140
// Helpers for reading chunked data.
141
142
0
#define TAG(A, B, C, D) ((A << 24) | (B << 16) | (C << 8) | D)
143
144
0
static SIBChunk ReadChunk(StreamReaderLE *stream) {
145
0
    SIBChunk chunk;
146
0
    chunk.Tag = stream->GetU4();
147
0
    chunk.Size = stream->GetU4();
148
0
    if (chunk.Size > stream->GetRemainingSizeToLimit())
149
0
        ASSIMP_LOG_ERROR("SIB: Chunk overflow");
150
0
    ByteSwap::Swap4(&chunk.Tag);
151
0
    return chunk;
152
0
}
153
154
0
static aiColor3D ReadColor(StreamReaderLE *stream) {
155
0
    float r = stream->GetF4();
156
0
    float g = stream->GetF4();
157
0
    float b = stream->GetF4();
158
0
    stream->GetU4(); // Colors have an unused(?) 4th component.
159
0
    return aiColor3D(r, g, b);
160
0
}
161
162
0
static void UnknownChunk(StreamReaderLE * /*stream*/, const SIBChunk &chunk) {
163
0
    char temp[4] = {
164
0
        static_cast<char>((chunk.Tag >> 24) & 0xff),
165
0
        static_cast<char>((chunk.Tag >> 16) & 0xff),
166
0
        static_cast<char>((chunk.Tag >> 8) & 0xff),
167
0
        static_cast<char>(chunk.Tag & 0xff)
168
0
    };
169
170
0
    ASSIMP_LOG_WARN("SIB: Skipping unknown '", ai_str_toprintable(temp, 4), "' chunk.");
171
0
}
172
173
// Reads a UTF-16LE string and returns it at UTF-8.
174
0
static aiString ReadString(StreamReaderLE *stream, uint32_t numWChars) {
175
0
    if (nullptr == stream || 0 == numWChars) {
176
0
        return aiString();
177
0
    }
178
179
    // Allocate buffers (max expansion is 1 byte -> 4 bytes for UTF-8)
180
0
    std::vector<unsigned char> str;
181
0
    str.reserve(numWChars * 4 + 1);
182
0
    uint16_t *temp = new uint16_t[numWChars];
183
0
    for (uint32_t n = 0; n < numWChars; ++n) {
184
0
        temp[n] = stream->GetU2();
185
0
    }
186
187
    // Convert it and NUL-terminate.
188
0
    const uint16_t *start(temp), *end(temp + numWChars);
189
0
    utf8::utf16to8(start, end, back_inserter(str));
190
0
    str[str.size() - 1] = '\0';
191
192
    // Return the final string.
193
0
    aiString result = aiString((const char *)&str[0]);
194
0
    delete[] temp;
195
196
0
    return result;
197
0
}
198
199
// ------------------------------------------------------------------------------------------------
200
// Returns whether the class can handle the format of the given file.
201
68
bool SIBImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
202
68
    return SimpleExtensionCheck(filename, "sib");
203
68
}
204
205
// ------------------------------------------------------------------------------------------------
206
63.6k
const aiImporterDesc *SIBImporter::GetInfo() const {
207
63.6k
    return &desc;
208
63.6k
}
209
210
// ------------------------------------------------------------------------------------------------
211
0
static void ReadVerts(SIBMesh *mesh, StreamReaderLE *stream, uint32_t count) {
212
0
    if (nullptr == mesh || nullptr == stream) {
213
0
        return;
214
0
    }
215
216
0
    mesh->pos.resize(count);
217
0
    for (uint32_t n = 0; n < count; ++n) {
218
0
        mesh->pos[n].x = stream->GetF4();
219
0
        mesh->pos[n].y = stream->GetF4();
220
0
        mesh->pos[n].z = stream->GetF4();
221
0
    }
222
0
}
223
224
// ------------------------------------------------------------------------------------------------
225
0
static void ReadFaces(SIBMesh *mesh, StreamReaderLE *stream) {
226
0
    uint32_t ptIdx = 0;
227
0
    while (stream->GetRemainingSizeToLimit() > 0) {
228
0
        uint32_t numPoints = stream->GetU4();
229
230
        // Store room for the N index channels, plus the point count.
231
0
        size_t pos = mesh->idx.size() + 1;
232
0
        mesh->idx.resize(pos + numPoints * N);
233
0
        mesh->idx[pos - 1] = numPoints;
234
0
        uint32_t *idx = &mesh->idx[pos];
235
236
0
        mesh->faceStart.push_back(static_cast<uint32_t>(pos - 1));
237
0
        mesh->mtls.push_back(0);
238
239
        // Read all the position data.
240
        // UV/normals will be supplied later.
241
        // Positions are supplied indexed already, so we preserve that
242
        // mapping. UVs are supplied uniquely, so we allocate unique indices.
243
0
        for (uint32_t n = 0; n < numPoints; n++, idx += N, ptIdx++) {
244
0
            uint32_t p = stream->GetU4();
245
0
            if (p >= mesh->pos.size())
246
0
                throw DeadlyImportError("Vertex index is out of range.");
247
0
            idx[POS] = p;
248
0
            idx[NRM] = ptIdx;
249
0
            idx[UV] = ptIdx;
250
0
        }
251
0
    }
252
253
    // Allocate data channels for normals/UVs.
254
0
    mesh->nrm.resize(ptIdx, aiVector3D(0, 0, 0));
255
0
    mesh->uv.resize(ptIdx, aiVector3D(0, 0, 0));
256
257
0
    mesh->numPts = ptIdx;
258
0
}
259
260
// ------------------------------------------------------------------------------------------------
261
0
static void ReadUVs(SIBMesh *mesh, StreamReaderLE *stream) {
262
0
    while (stream->GetRemainingSizeToLimit() > 0) {
263
0
        uint32_t faceIdx = stream->GetU4();
264
0
        uint32_t numPoints = stream->GetU4();
265
266
0
        if (faceIdx >= mesh->faceStart.size())
267
0
            throw DeadlyImportError("Invalid face index.");
268
269
0
        uint32_t pos = mesh->faceStart[faceIdx];
270
0
        uint32_t *idx = &mesh->idx[pos + 1];
271
272
0
        for (uint32_t n = 0; n < numPoints; n++, idx += N) {
273
0
            uint32_t id = idx[UV];
274
0
            mesh->uv[id].x = stream->GetF4();
275
0
            mesh->uv[id].y = stream->GetF4();
276
0
        }
277
0
    }
278
0
}
279
280
// ------------------------------------------------------------------------------------------------
281
0
static void ReadMtls(SIBMesh *mesh, StreamReaderLE *stream) {
282
    // Material assignments are stored run-length encoded.
283
    // Also, we add 1 to each material so that we can use mtl #0
284
    // as the default material.
285
0
    uint32_t prevFace = stream->GetU4();
286
0
    uint32_t prevMtl = stream->GetU4() + 1;
287
0
    while (stream->GetRemainingSizeToLimit() > 0) {
288
0
        uint32_t face = stream->GetU4();
289
0
        uint32_t mtl = stream->GetU4() + 1;
290
0
        while (prevFace < face) {
291
0
            if (prevFace >= mesh->mtls.size())
292
0
                throw DeadlyImportError("Invalid face index.");
293
0
            mesh->mtls[prevFace++] = prevMtl;
294
0
        }
295
296
0
        prevFace = face;
297
0
        prevMtl = mtl;
298
0
    }
299
300
0
    while (prevFace < mesh->mtls.size())
301
0
        mesh->mtls[prevFace++] = prevMtl;
302
0
}
303
304
// ------------------------------------------------------------------------------------------------
305
0
static void ReadAxis(aiMatrix4x4 &axis, StreamReaderLE *stream) {
306
0
    axis.a4 = stream->GetF4();
307
0
    axis.b4 = stream->GetF4();
308
0
    axis.c4 = stream->GetF4();
309
0
    axis.d4 = 1;
310
0
    axis.a1 = stream->GetF4();
311
0
    axis.b1 = stream->GetF4();
312
0
    axis.c1 = stream->GetF4();
313
0
    axis.d1 = 0;
314
0
    axis.a2 = stream->GetF4();
315
0
    axis.b2 = stream->GetF4();
316
0
    axis.c2 = stream->GetF4();
317
0
    axis.d2 = 0;
318
0
    axis.a3 = stream->GetF4();
319
0
    axis.b3 = stream->GetF4();
320
0
    axis.c3 = stream->GetF4();
321
0
    axis.d3 = 0;
322
0
}
323
324
// ------------------------------------------------------------------------------------------------
325
0
static void ReadEdges(SIBMesh *mesh, StreamReaderLE *stream) {
326
0
    while (stream->GetRemainingSizeToLimit() > 0) {
327
0
        uint32_t posA = stream->GetU4();
328
0
        uint32_t posB = stream->GetU4();
329
0
        GetEdge(mesh, posA, posB);
330
0
    }
331
0
}
332
333
// ------------------------------------------------------------------------------------------------
334
0
static void ReadCreases(SIBMesh *mesh, StreamReaderLE *stream) {
335
0
    while (stream->GetRemainingSizeToLimit() > 0) {
336
0
        uint32_t edge = stream->GetU4();
337
0
        if (edge >= mesh->edges.size())
338
0
            throw DeadlyImportError("SIB: Invalid edge index.");
339
0
        mesh->edges[edge].creased = true;
340
0
    }
341
0
}
342
343
// ------------------------------------------------------------------------------------------------
344
0
static void ConnectFaces(SIBMesh *mesh) {
345
    // Find faces connected to each edge.
346
0
    size_t numFaces = mesh->faceStart.size();
347
0
    for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) {
348
0
        uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]];
349
0
        uint32_t numPoints = *idx++;
350
0
        uint32_t prev = idx[(numPoints - 1) * N + POS];
351
352
0
        for (uint32_t i = 0; i < numPoints; i++, idx += N) {
353
0
            uint32_t next = idx[POS];
354
355
            // Find this edge.
356
0
            SIBEdge &edge = GetEdge(mesh, prev, next);
357
358
            // Link this face onto it.
359
            // This gives potentially undesirable normals when used
360
            // with non-2-manifold surfaces, but then so does Silo to begin with.
361
0
            if (edge.faceA == 0xffffffff)
362
0
                edge.faceA = static_cast<uint32_t>(faceIdx);
363
0
            else if (edge.faceB == 0xffffffff)
364
0
                edge.faceB = static_cast<uint32_t>(faceIdx);
365
366
0
            prev = next;
367
0
        }
368
0
    }
369
0
}
370
371
// ------------------------------------------------------------------------------------------------
372
static aiVector3D CalculateVertexNormal(SIBMesh *mesh, uint32_t faceIdx, uint32_t pos,
373
0
        const std::vector<aiVector3D> &faceNormals) {
374
    // Creased edges complicate this. We need to find the start/end range of the
375
    // ring of faces that touch this position.
376
    // We do this in two passes. The first pass is to find the end of the range,
377
    // the second is to work backwards to the start and calculate the final normal.
378
0
    aiVector3D vtxNormal;
379
0
    for (int pass = 0; pass < 2; pass++) {
380
0
        vtxNormal = aiVector3D(0, 0, 0);
381
0
        uint32_t startFaceIdx = faceIdx;
382
0
        uint32_t prevFaceIdx = faceIdx;
383
384
        // Process each connected face.
385
0
        while (true) {
386
            // Accumulate the face normal.
387
0
            vtxNormal += faceNormals[faceIdx];
388
389
0
            uint32_t nextFaceIdx = 0xffffffff;
390
391
            // Move to the next edge sharing this position.
392
0
            uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]];
393
0
            uint32_t numPoints = *idx++;
394
0
            uint32_t posA = idx[(numPoints - 1) * N + POS];
395
0
            for (uint32_t n = 0; n < numPoints; n++, idx += N) {
396
0
                uint32_t posB = idx[POS];
397
398
                // Test if this edge shares our target position.
399
0
                if (posA == pos || posB == pos) {
400
0
                    SIBEdge &edge = GetEdge(mesh, posA, posB);
401
402
                    // Non-manifold meshes can produce faces which share
403
                    // positions but have no edge entry, so check it.
404
0
                    if (edge.faceA == faceIdx || edge.faceB == faceIdx) {
405
                        // Move to whichever side we didn't just come from.
406
0
                        if (!edge.creased) {
407
0
                            if (edge.faceA != prevFaceIdx && edge.faceA != faceIdx && edge.faceA != 0xffffffff)
408
0
                                nextFaceIdx = edge.faceA;
409
0
                            else if (edge.faceB != prevFaceIdx && edge.faceB != faceIdx && edge.faceB != 0xffffffff)
410
0
                                nextFaceIdx = edge.faceB;
411
0
                        }
412
0
                    }
413
0
                }
414
415
0
                posA = posB;
416
0
            }
417
418
            // Stop once we hit either an creased/unconnected edge, or we
419
            // wrapped around and hit our start point.
420
0
            if (nextFaceIdx == 0xffffffff || nextFaceIdx == startFaceIdx)
421
0
                break;
422
423
0
            prevFaceIdx = faceIdx;
424
0
            faceIdx = nextFaceIdx;
425
0
        }
426
0
    }
427
428
    // Normalize it.
429
0
    float len = vtxNormal.Length();
430
0
    if (len > 0.000000001f)
431
0
        vtxNormal /= len;
432
0
    return vtxNormal;
433
0
}
434
435
// ------------------------------------------------------------------------------------------------
436
0
static void CalculateNormals(SIBMesh *mesh) {
437
0
    size_t numFaces = mesh->faceStart.size();
438
439
    // Calculate face normals.
440
0
    std::vector<aiVector3D> faceNormals(numFaces);
441
0
    for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) {
442
0
        uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]];
443
0
        uint32_t numPoints = *idx++;
444
445
0
        aiVector3D faceNormal(0, 0, 0);
446
447
0
        uint32_t *prev = &idx[(numPoints - 1) * N];
448
449
0
        for (uint32_t i = 0; i < numPoints; i++) {
450
0
            uint32_t *next = &idx[i * N];
451
452
0
            faceNormal += mesh->pos[prev[POS]] ^ mesh->pos[next[POS]];
453
0
            prev = next;
454
0
        }
455
456
0
        faceNormals[faceIdx] = faceNormal;
457
0
    }
458
459
    // Calculate vertex normals.
460
0
    for (size_t faceIdx = 0; faceIdx < numFaces; faceIdx++) {
461
0
        uint32_t *idx = &mesh->idx[mesh->faceStart[faceIdx]];
462
0
        uint32_t numPoints = *idx++;
463
464
0
        for (uint32_t i = 0; i < numPoints; i++) {
465
0
            uint32_t pos = idx[i * N + POS];
466
0
            uint32_t nrm = idx[i * N + NRM];
467
0
            aiVector3D vtxNorm = CalculateVertexNormal(mesh, static_cast<uint32_t>(faceIdx), pos, faceNormals);
468
0
            mesh->nrm[nrm] = vtxNorm;
469
0
        }
470
0
    }
471
0
}
472
473
// ------------------------------------------------------------------------------------------------
474
struct TempMesh {
475
    std::vector<aiVector3D> vtx;
476
    std::vector<aiVector3D> nrm;
477
    std::vector<aiVector3D> uv;
478
    std::vector<aiFace> faces;
479
};
480
481
0
static void ReadShape(SIB *sib, StreamReaderLE *stream) {
482
0
    SIBMesh smesh;
483
0
    aiString name;
484
485
0
    while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) {
486
0
        SIBChunk chunk = ReadChunk(stream);
487
0
        unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size);
488
489
0
        switch (chunk.Tag) {
490
0
        case TAG('M', 'I', 'R', 'P'): break; // mirror plane maybe?
491
0
        case TAG('I', 'M', 'R', 'P'): break; // instance mirror? (not supported here yet)
492
0
        case TAG('D', 'I', 'N', 'F'): break; // display info, not needed
493
0
        case TAG('P', 'I', 'N', 'F'): break; // ?
494
0
        case TAG('V', 'M', 'I', 'R'): break; // ?
495
0
        case TAG('F', 'M', 'I', 'R'): break; // ?
496
0
        case TAG('T', 'X', 'S', 'M'): break; // ?
497
0
        case TAG('F', 'A', 'H', 'S'): break; // ?
498
0
        case TAG('V', 'R', 'T', 'S'): ReadVerts(&smesh, stream, chunk.Size / 12); break;
499
0
        case TAG('F', 'A', 'C', 'S'): ReadFaces(&smesh, stream); break;
500
0
        case TAG('F', 'T', 'V', 'S'): ReadUVs(&smesh, stream); break;
501
0
        case TAG('S', 'N', 'A', 'M'): name = ReadString(stream, chunk.Size / 2); break;
502
0
        case TAG('F', 'A', 'M', 'A'): ReadMtls(&smesh, stream); break;
503
0
        case TAG('A', 'X', 'I', 'S'): ReadAxis(smesh.axis, stream); break;
504
0
        case TAG('E', 'D', 'G', 'S'): ReadEdges(&smesh, stream); break;
505
0
        case TAG('E', 'C', 'R', 'S'): ReadCreases(&smesh, stream); break;
506
0
        default: UnknownChunk(stream, chunk); break;
507
0
        }
508
509
0
        stream->SetCurrentPos(stream->GetReadLimit());
510
0
        stream->SetReadLimit(oldLimit);
511
0
    }
512
513
0
    ai_assert(smesh.faceStart.size() == smesh.mtls.size()); // sanity check
514
515
    // Silo doesn't store any normals in the file - we need to compute
516
    // them ourselves. We can't let AssImp handle it as AssImp doesn't
517
    // know about our creased edges.
518
0
    ConnectFaces(&smesh);
519
0
    CalculateNormals(&smesh);
520
521
    // Construct the transforms.
522
0
    aiMatrix4x4 worldToLocal = smesh.axis;
523
0
    worldToLocal.Inverse();
524
0
    aiMatrix4x4 worldToLocalN = worldToLocal;
525
0
    worldToLocalN.a4 = worldToLocalN.b4 = worldToLocalN.c4 = 0.0f;
526
0
    worldToLocalN.Inverse().Transpose();
527
528
    // Allocate final mesh data.
529
    // We'll allocate one mesh for each material. (we'll strip unused ones after)
530
0
    std::vector<TempMesh> meshes(sib->mtls.size());
531
532
    // Un-index the source data and apply to each vertex.
533
0
    for (unsigned fi = 0; fi < smesh.faceStart.size(); fi++) {
534
0
        uint32_t start = smesh.faceStart[fi];
535
0
        uint32_t mtl = smesh.mtls[fi];
536
0
        uint32_t *idx = &smesh.idx[start];
537
538
0
        if (mtl >= meshes.size()) {
539
0
            ASSIMP_LOG_ERROR("SIB: Face material index is invalid.");
540
0
            mtl = 0;
541
0
        }
542
543
0
        TempMesh &dest = meshes[mtl];
544
545
0
        aiFace face;
546
0
        face.mNumIndices = *idx++;
547
0
        face.mIndices = new unsigned[face.mNumIndices];
548
0
        for (unsigned pt = 0; pt < face.mNumIndices; pt++, idx += N) {
549
0
            size_t vtxIdx = dest.vtx.size();
550
0
            face.mIndices[pt] = static_cast<unsigned int>(vtxIdx);
551
552
            // De-index it. We don't need to validate here as
553
            // we did it when creating the data.
554
0
            aiVector3D pos = smesh.pos[idx[POS]];
555
0
            aiVector3D nrm = smesh.nrm[idx[NRM]];
556
0
            aiVector3D uv = smesh.uv[idx[UV]];
557
558
            // The verts are supplied in world-space, so let's
559
            // transform them back into the local space of this mesh:
560
0
            pos = worldToLocal * pos;
561
0
            nrm = worldToLocalN * nrm;
562
563
0
            dest.vtx.push_back(pos);
564
0
            dest.nrm.push_back(nrm);
565
0
            dest.uv.push_back(uv);
566
0
        }
567
0
        dest.faces.push_back(face);
568
0
    }
569
570
0
    SIBObject obj;
571
0
    obj.name = name;
572
0
    obj.axis = smesh.axis;
573
0
    obj.meshIdx = sib->meshes.size();
574
575
    // Now that we know the size of everything,
576
    // we can build the final one-material-per-mesh data.
577
0
    for (size_t n = 0; n < meshes.size(); n++) {
578
0
        TempMesh &src = meshes[n];
579
0
        if (src.faces.empty())
580
0
            continue;
581
582
0
        aiMesh *mesh = new aiMesh;
583
0
        mesh->mName = name;
584
0
        mesh->mNumFaces = static_cast<unsigned int>(src.faces.size());
585
0
        mesh->mFaces = new aiFace[mesh->mNumFaces];
586
0
        mesh->mNumVertices = static_cast<unsigned int>(src.vtx.size());
587
0
        mesh->mVertices = new aiVector3D[mesh->mNumVertices];
588
0
        mesh->mNormals = new aiVector3D[mesh->mNumVertices];
589
0
        mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
590
0
        mesh->mNumUVComponents[0] = 2;
591
0
        mesh->mMaterialIndex = static_cast<unsigned int>(n);
592
593
0
        for (unsigned i = 0; i < mesh->mNumVertices; i++) {
594
0
            mesh->mVertices[i] = src.vtx[i];
595
0
            mesh->mNormals[i] = src.nrm[i];
596
0
            mesh->mTextureCoords[0][i] = src.uv[i];
597
0
        }
598
0
        for (unsigned i = 0; i < mesh->mNumFaces; i++) {
599
0
            mesh->mFaces[i] = src.faces[i];
600
0
        }
601
602
0
        sib->meshes.push_back(mesh);
603
0
    }
604
605
0
    obj.meshCount = sib->meshes.size() - obj.meshIdx;
606
0
    sib->objs.push_back(obj);
607
0
}
608
609
// ------------------------------------------------------------------------------------------------
610
0
static void ReadMaterial(SIB *sib, StreamReaderLE *stream) {
611
0
    aiColor3D diff = ReadColor(stream);
612
0
    aiColor3D ambi = ReadColor(stream);
613
0
    aiColor3D spec = ReadColor(stream);
614
0
    aiColor3D emis = ReadColor(stream);
615
0
    float shiny = (float)stream->GetU4();
616
617
0
    uint32_t nameLen = stream->GetU4();
618
0
    aiString name = ReadString(stream, nameLen / 2);
619
0
    uint32_t texLen = stream->GetU4();
620
0
    aiString tex = ReadString(stream, texLen / 2);
621
622
0
    aiMaterial *mtl = new aiMaterial();
623
0
    mtl->AddProperty(&diff, 1, AI_MATKEY_COLOR_DIFFUSE);
624
0
    mtl->AddProperty(&ambi, 1, AI_MATKEY_COLOR_AMBIENT);
625
0
    mtl->AddProperty(&spec, 1, AI_MATKEY_COLOR_SPECULAR);
626
0
    mtl->AddProperty(&emis, 1, AI_MATKEY_COLOR_EMISSIVE);
627
0
    mtl->AddProperty(&shiny, 1, AI_MATKEY_SHININESS);
628
0
    mtl->AddProperty(&name, AI_MATKEY_NAME);
629
0
    if (tex.length > 0) {
630
0
        mtl->AddProperty(&tex, AI_MATKEY_TEXTURE_DIFFUSE(0));
631
0
        mtl->AddProperty(&tex, AI_MATKEY_TEXTURE_AMBIENT(0));
632
0
    }
633
634
0
    sib->mtls.push_back(mtl);
635
0
}
636
637
// ------------------------------------------------------------------------------------------------
638
0
static void ReadLightInfo(aiLight *light, StreamReaderLE *stream) {
639
0
    uint32_t type = stream->GetU4();
640
0
    switch (type) {
641
0
    case 0: light->mType = aiLightSource_POINT; break;
642
0
    case 1: light->mType = aiLightSource_SPOT; break;
643
0
    case 2: light->mType = aiLightSource_DIRECTIONAL; break;
644
0
    default: light->mType = aiLightSource_UNDEFINED; break;
645
0
    }
646
647
0
    light->mPosition.x = stream->GetF4();
648
0
    light->mPosition.y = stream->GetF4();
649
0
    light->mPosition.z = stream->GetF4();
650
0
    light->mDirection.x = stream->GetF4();
651
0
    light->mDirection.y = stream->GetF4();
652
0
    light->mDirection.z = stream->GetF4();
653
0
    light->mColorDiffuse = ReadColor(stream);
654
0
    light->mColorAmbient = ReadColor(stream);
655
0
    light->mColorSpecular = ReadColor(stream);
656
0
    ai_real spotExponent = stream->GetF4();
657
0
    ai_real spotCutoff = stream->GetF4();
658
0
    light->mAttenuationConstant = stream->GetF4();
659
0
    light->mAttenuationLinear = stream->GetF4();
660
0
    light->mAttenuationQuadratic = stream->GetF4();
661
662
    // Silo uses the OpenGL default lighting model for it's
663
    // spot cutoff/exponent. AssImp unfortunately, does not.
664
    // Let's try and approximate it by solving for the
665
    // 99% and 1% percentiles.
666
    //    OpenGL: I = cos(angle)^E
667
    //   Solving: angle = acos(I^(1/E))
668
0
    ai_real E = ai_real(1.0) / std::max(spotExponent, (ai_real)0.00001);
669
0
    ai_real inner = std::acos(std::pow((ai_real)0.99, E));
670
0
    ai_real outer = std::acos(std::pow((ai_real)0.01, E));
671
672
    // Apply the cutoff.
673
0
    outer = std::min(outer, AI_DEG_TO_RAD(spotCutoff));
674
675
0
    light->mAngleInnerCone = std::min(inner, outer);
676
0
    light->mAngleOuterCone = outer;
677
0
}
678
679
0
static void ReadLight(SIB *sib, StreamReaderLE *stream) {
680
0
    aiLight *light = new aiLight();
681
682
0
    while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) {
683
0
        SIBChunk chunk = ReadChunk(stream);
684
0
        unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size);
685
686
0
        switch (chunk.Tag) {
687
0
        case TAG('L', 'N', 'F', 'O'): ReadLightInfo(light, stream); break;
688
0
        case TAG('S', 'N', 'A', 'M'): light->mName = ReadString(stream, chunk.Size / 2); break;
689
0
        default: UnknownChunk(stream, chunk); break;
690
0
        }
691
692
0
        stream->SetCurrentPos(stream->GetReadLimit());
693
0
        stream->SetReadLimit(oldLimit);
694
0
    }
695
696
0
    sib->lights.push_back(light);
697
0
}
698
699
// ------------------------------------------------------------------------------------------------
700
0
static void ReadScale(aiMatrix4x4 &axis, StreamReaderLE *stream) {
701
0
    aiMatrix4x4 scale;
702
0
    scale.a1 = stream->GetF4();
703
0
    scale.b1 = stream->GetF4();
704
0
    scale.c1 = stream->GetF4();
705
0
    scale.d1 = stream->GetF4();
706
0
    scale.a2 = stream->GetF4();
707
0
    scale.b2 = stream->GetF4();
708
0
    scale.c2 = stream->GetF4();
709
0
    scale.d2 = stream->GetF4();
710
0
    scale.a3 = stream->GetF4();
711
0
    scale.b3 = stream->GetF4();
712
0
    scale.c3 = stream->GetF4();
713
0
    scale.d3 = stream->GetF4();
714
0
    scale.a4 = stream->GetF4();
715
0
    scale.b4 = stream->GetF4();
716
0
    scale.c4 = stream->GetF4();
717
0
    scale.d4 = stream->GetF4();
718
719
0
    axis = axis * scale;
720
0
}
721
722
0
static void ReadInstance(SIB *sib, StreamReaderLE *stream) {
723
0
    SIBObject inst;
724
0
    uint32_t shapeIndex = 0;
725
726
0
    while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) {
727
0
        SIBChunk chunk = ReadChunk(stream);
728
0
        unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size);
729
730
0
        switch (chunk.Tag) {
731
0
        case TAG('D', 'I', 'N', 'F'): break; // display info, not needed
732
0
        case TAG('P', 'I', 'N', 'F'): break; // ?
733
0
        case TAG('A', 'X', 'I', 'S'): ReadAxis(inst.axis, stream); break;
734
0
        case TAG('I', 'N', 'S', 'I'): shapeIndex = stream->GetU4(); break;
735
0
        case TAG('S', 'M', 'T', 'X'): ReadScale(inst.axis, stream); break;
736
0
        case TAG('S', 'N', 'A', 'M'): inst.name = ReadString(stream, chunk.Size / 2); break;
737
0
        default: UnknownChunk(stream, chunk); break;
738
0
        }
739
740
0
        stream->SetCurrentPos(stream->GetReadLimit());
741
0
        stream->SetReadLimit(oldLimit);
742
0
    }
743
744
0
    if (shapeIndex >= sib->objs.size()) {
745
0
        throw DeadlyImportError("SIB: Invalid shape index.");
746
0
    }
747
748
0
    const SIBObject &src = sib->objs[shapeIndex];
749
0
    inst.meshIdx = src.meshIdx;
750
0
    inst.meshCount = src.meshCount;
751
0
    sib->insts.push_back(inst);
752
0
}
753
754
// ------------------------------------------------------------------------------------------------
755
0
static void CheckVersion(StreamReaderLE *stream) {
756
0
    uint32_t version = stream->GetU4();
757
0
    if (version < 1 || version > 2) {
758
0
        throw DeadlyImportError("SIB: Unsupported file version.");
759
0
    }
760
0
}
761
762
0
static void ReadScene(SIB *sib, StreamReaderLE *stream) {
763
    // Parse each chunk in turn.
764
0
    while (stream->GetRemainingSizeToLimit() >= sizeof(SIBChunk)) {
765
0
        SIBChunk chunk = ReadChunk(stream);
766
0
        unsigned oldLimit = stream->SetReadLimit(stream->GetCurrentPos() + chunk.Size);
767
768
0
        switch (chunk.Tag) {
769
0
        case TAG('H', 'E', 'A', 'D'): CheckVersion(stream); break;
770
0
        case TAG('S', 'H', 'A', 'P'): ReadShape(sib, stream); break;
771
0
        case TAG('G', 'R', 'P', 'S'): break; // group assignment, we don't import this
772
0
        case TAG('T', 'E', 'X', 'P'): break; // ?
773
0
        case TAG('I', 'N', 'S', 'T'): ReadInstance(sib, stream); break;
774
0
        case TAG('M', 'A', 'T', 'R'): ReadMaterial(sib, stream); break;
775
0
        case TAG('L', 'G', 'H', 'T'): ReadLight(sib, stream); break;
776
0
        default: UnknownChunk(stream, chunk); break;
777
0
        }
778
779
0
        stream->SetCurrentPos(stream->GetReadLimit());
780
0
        stream->SetReadLimit(oldLimit);
781
0
    }
782
0
}
783
784
// ------------------------------------------------------------------------------------------------
785
// Imports the given file into the given scene structure.
786
void SIBImporter::InternReadFile(const std::string &pFile,
787
0
        aiScene *pScene, IOSystem *pIOHandler) {
788
789
0
    auto file = pIOHandler->Open(pFile, "rb");
790
0
    if (!file)
791
0
        throw DeadlyImportError("SIB: Could not open ", pFile);
792
793
0
    StreamReaderLE stream(file);
794
795
    // We should have at least one chunk
796
0
    if (stream.GetRemainingSize() < 16)
797
0
        throw DeadlyImportError("SIB file is either empty or corrupt: ", pFile);
798
799
0
    SIB sib;
800
801
    // Default material.
802
0
    aiMaterial *defmtl = new aiMaterial;
803
0
    aiString defname = aiString(AI_DEFAULT_MATERIAL_NAME);
804
0
    defmtl->AddProperty(&defname, AI_MATKEY_NAME);
805
0
    sib.mtls.push_back(defmtl);
806
807
    // Read it all.
808
0
    ReadScene(&sib, &stream);
809
810
    // Join the instances and objects together.
811
0
    size_t firstInst = sib.objs.size();
812
0
    sib.objs.insert(sib.objs.end(), sib.insts.begin(), sib.insts.end());
813
0
    sib.insts.clear();
814
815
    // Transfer to the aiScene.
816
0
    pScene->mNumMaterials = static_cast<unsigned int>(sib.mtls.size());
817
0
    pScene->mNumMeshes = static_cast<unsigned int>(sib.meshes.size());
818
0
    pScene->mNumLights = static_cast<unsigned int>(sib.lights.size());
819
0
    pScene->mMaterials = pScene->mNumMaterials ? new aiMaterial *[pScene->mNumMaterials] : nullptr;
820
0
    pScene->mMeshes = pScene->mNumMeshes ? new aiMesh *[pScene->mNumMeshes] : nullptr;
821
0
    pScene->mLights = pScene->mNumLights ? new aiLight *[pScene->mNumLights] : nullptr;
822
0
    if (pScene->mNumMaterials)
823
0
        memcpy(pScene->mMaterials, &sib.mtls[0], sizeof(aiMaterial *) * pScene->mNumMaterials);
824
0
    if (pScene->mNumMeshes)
825
0
        memcpy(pScene->mMeshes, &sib.meshes[0], sizeof(aiMesh *) * pScene->mNumMeshes);
826
0
    if (pScene->mNumLights)
827
0
        memcpy(pScene->mLights, &sib.lights[0], sizeof(aiLight *) * pScene->mNumLights);
828
829
    // Construct the root node.
830
0
    size_t childIdx = 0;
831
0
    aiNode *root = new aiNode();
832
0
    root->mName.Set("<SIBRoot>");
833
0
    root->mNumChildren = static_cast<unsigned int>(sib.objs.size() + sib.lights.size());
834
0
    root->mChildren = root->mNumChildren ? new aiNode *[root->mNumChildren] : nullptr;
835
0
    pScene->mRootNode = root;
836
837
    // Add nodes for each object.
838
0
    for (size_t n = 0; n < sib.objs.size(); n++) {
839
0
        ai_assert(root->mChildren);
840
0
        SIBObject &obj = sib.objs[n];
841
0
        aiNode *node = new aiNode;
842
0
        root->mChildren[childIdx++] = node;
843
0
        node->mName = obj.name;
844
0
        node->mParent = root;
845
0
        node->mTransformation = obj.axis;
846
847
0
        node->mNumMeshes = static_cast<unsigned int>(obj.meshCount);
848
0
        node->mMeshes = node->mNumMeshes ? new unsigned[node->mNumMeshes] : nullptr;
849
0
        for (unsigned i = 0; i < node->mNumMeshes; i++)
850
0
            node->mMeshes[i] = static_cast<unsigned int>(obj.meshIdx + i);
851
852
        // Mark instanced objects as being so.
853
0
        if (n >= firstInst) {
854
0
            node->mMetaData = aiMetadata::Alloc(1);
855
0
            node->mMetaData->Set(0, "IsInstance", true);
856
0
        }
857
0
    }
858
859
    // Add nodes for each light.
860
    // (no transformation as the light is already in world space)
861
0
    for (size_t n = 0; n < sib.lights.size(); n++) {
862
0
        ai_assert(root->mChildren);
863
0
        aiLight *light = sib.lights[n];
864
0
        if (nullptr != light) {
865
0
            aiNode *node = new aiNode;
866
0
            root->mChildren[childIdx++] = node;
867
0
            node->mName = light->mName;
868
0
            node->mParent = root;
869
0
        }
870
0
    }
871
0
}
872
873
} // namespace Assimp
874
875
#endif // !! ASSIMP_BUILD_NO_SIB_IMPORTER