Coverage Report

Created: 2026-05-04 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/assimp/code/AssetLib/FBX/FBXDocument.cpp
Line
Count
Source
1
/*
2
Open Asset Import Library (assimp)
3
----------------------------------------------------------------------
4
5
Copyright (c) 2006-2026, 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
/** @file  FBXDocument.cpp
43
 *  @brief Implementation of the FBX DOM classes
44
 */
45
46
#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
47
48
#include "FBXDocument.h"
49
#include "FBXMeshGeometry.h"
50
#include "FBXParser.h"
51
#include "FBXUtil.h"
52
#include "FBXImporter.h"
53
#include "FBXImportSettings.h"
54
#include "FBXDocumentUtil.h"
55
#include "FBXProperties.h"
56
57
#include <assimp/DefaultLogger.hpp>
58
59
#include <functional>
60
#include <map>
61
#include <memory>
62
#include <utility>
63
64
namespace Assimp {
65
namespace FBX {
66
67
using namespace Util;
68
69
// ------------------------------------------------------------------------------------------------
70
LazyObject::LazyObject(uint64_t id, const Element& element, const Document& doc) :
71
69.8k
        doc(doc), element(element), id(id), flags() {
72
    // empty
73
69.8k
}
74
75
// ------------------------------------------------------------------------------------------------
76
303k
const Object* LazyObject::Get(bool dieOnError) {
77
303k
    if(IsBeingConstructed() || FailedToConstruct()) {
78
220k
        return nullptr;
79
220k
    }
80
81
83.4k
    if (object) {
82
21.6k
        return object.get();
83
21.6k
    }
84
85
61.8k
    const Token& key = element.KeyToken();
86
61.8k
    const TokenList& tokens = element.Tokens();
87
88
61.8k
    if(tokens.size() < 3) {
89
0
        DOMError("expected at least 3 tokens: id, name and class tag",&element);
90
0
    }
91
92
61.8k
    const char* err;
93
61.8k
    std::string name = ParseTokenAsString(*tokens[1],err);
94
61.8k
    if (err) {
95
1
        DOMError(err,&element);
96
1
    }
97
98
    // small fix for binary reading: binary fbx files don't use
99
    // prefixes such as Model:: in front of their names. The
100
    // loading code expects this at many places, though!
101
    // so convert the binary representation (a 0x0001) to the
102
    // double colon notation.
103
61.8k
    if(tokens[1]->IsBinary()) {
104
937k
        for (size_t i = 0; i < name.length(); ++i) {
105
875k
            if (name[i] == 0x0 && name[i+1] == 0x1) {
106
61.3k
                name = name.substr(i+2) + "::" + name.substr(0,i);
107
61.3k
            }
108
875k
        }
109
61.4k
    }
110
111
61.8k
    const std::string classtag = ParseTokenAsString(*tokens[2],err);
112
61.8k
    if (err) {
113
9
        DOMError(err,&element);
114
9
    }
115
116
    // prevent recursive calls
117
61.7k
    flags |= BEING_CONSTRUCTED;
118
119
61.7k
    try {
120
        // this needs to be relatively fast since it happens a lot,
121
        // so avoid constructing strings all the time.
122
61.7k
        const char* obtype = key.begin();
123
61.7k
        const size_t length = static_cast<size_t>(key.end()-key.begin());
124
125
        // For debugging
126
        //dumpObjectClassInfo( objtype, classtag );
127
128
61.7k
        if (!strncmp(obtype,"Geometry",length)) {
129
1.37k
            if (!strcmp(classtag.c_str(),"Mesh")) {
130
1.36k
                object.reset(new MeshGeometry(id,element,name,doc));
131
1.36k
            }
132
1.37k
            if (!strcmp(classtag.c_str(), "Shape")) {
133
0
                object.reset(new ShapeGeometry(id, element, name, doc));
134
0
            }
135
1.37k
            if (!strcmp(classtag.c_str(), "Line")) {
136
0
                object.reset(new LineGeometry(id, element, name, doc));
137
0
            }
138
1.37k
        }
139
60.4k
        else if (!strncmp(obtype,"NodeAttribute",length)) {
140
4.92k
            if (!strcmp(classtag.c_str(),"Camera")) {
141
318
                object.reset(new Camera(id,element,doc,name));
142
318
            }
143
4.60k
            else if (!strcmp(classtag.c_str(),"CameraSwitcher")) {
144
0
                object.reset(new CameraSwitcher(id,element,doc,name));
145
0
            }
146
4.60k
            else if (!strcmp(classtag.c_str(),"Light")) {
147
294
                object.reset(new Light(id,element,doc,name));
148
294
            }
149
4.31k
            else if (!strcmp(classtag.c_str(),"Null")) {
150
245
                object.reset(new Null(id,element,doc,name));
151
245
            }
152
4.07k
            else if (!strcmp(classtag.c_str(),"LimbNode")) {
153
4.00k
                object.reset(new LimbNode(id,element,doc,name));
154
4.00k
            }
155
4.92k
        }
156
55.4k
        else if (!strncmp(obtype,"Deformer",length)) {
157
3.54k
            if (!strcmp(classtag.c_str(),"Cluster")) {
158
3.29k
                object.reset(new Cluster(id,element,doc,name));
159
3.29k
            }
160
250
            else if (!strcmp(classtag.c_str(),"Skin")) {
161
242
                object.reset(new Skin(id,element,doc,name));
162
242
            }
163
8
            else if (!strcmp(classtag.c_str(), "BlendShape")) {
164
0
                object.reset(new BlendShape(id, element, doc, name));
165
0
            }
166
8
            else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) {
167
0
                object.reset(new BlendShapeChannel(id, element, doc, name));
168
0
            }
169
3.54k
        }
170
51.9k
        else if ( !strncmp( obtype, "Model", length ) ) {
171
            // FK and IK effectors are not supported
172
6.39k
            if ( strcmp( classtag.c_str(), "IKEffector" ) && strcmp( classtag.c_str(), "FKEffector" ) ) {
173
6.39k
                object.reset( new Model( id, element, doc, name ) );
174
6.39k
            }
175
6.39k
        }
176
45.5k
        else if (!strncmp(obtype,"Material",length)) {
177
517
            object.reset(new Material(id,element,doc,name));
178
517
        }
179
45.0k
        else if (!strncmp(obtype,"Texture",length)) {
180
281
            object.reset(new Texture(id,element,doc,name));
181
281
        }
182
44.7k
        else if (!strncmp(obtype,"LayeredTexture",length)) {
183
4
            object.reset(new LayeredTexture(id,element,doc,name));
184
4
        }
185
44.7k
        else if (!strncmp(obtype,"Video",length)) {
186
221
            object.reset(new Video(id,element,doc,name));
187
221
        }
188
44.5k
        else if (!strncmp(obtype,"AnimationStack",length)) {
189
303
            object.reset(new AnimationStack(id,element,name,doc));
190
303
        }
191
44.2k
        else if (!strncmp(obtype,"AnimationLayer",length)) {
192
279
            object.reset(new AnimationLayer(id,element,name,doc));
193
279
        }
194
        // note: order matters for these two
195
43.9k
        else if (!strncmp(obtype,"AnimationCurve",length)) {
196
32.0k
            object.reset(new AnimationCurve(id,element,name,doc));
197
32.0k
        }
198
11.8k
        else if (!strncmp(obtype,"AnimationCurveNode",length)) {
199
11.8k
            object.reset(new AnimationCurveNode(id,element,name,doc));
200
11.8k
        }
201
61.7k
    }
202
61.7k
    catch (std::bad_alloc&) {
203
        // out-of-memory is unrecoverable and should always lead to a failure
204
205
0
        flags &= ~BEING_CONSTRUCTED;
206
0
        flags |= FAILED_TO_CONSTRUCT;
207
208
0
        throw;
209
0
    }
210
61.7k
    catch(std::exception& ex) {
211
2.88k
        flags &= ~BEING_CONSTRUCTED;
212
2.88k
        flags |= FAILED_TO_CONSTRUCT;
213
214
2.88k
        if(dieOnError || doc.Settings().strictMode) {
215
0
            throw;
216
0
        }
217
218
        // note: the error message is already formatted, so raw logging is ok
219
2.88k
        if(!DefaultLogger::isNullLogger()) {
220
0
            ASSIMP_LOG_ERROR(ex.what());
221
0
        }
222
2.88k
        return nullptr;
223
2.88k
    }
224
225
58.9k
    if (!object) {
226
        //DOMError("failed to convert element to DOM object, class: " + classtag + ", name: " + name,&element);
227
71
    }
228
229
58.9k
    flags &= ~BEING_CONSTRUCTED;
230
58.9k
    return object.get();
231
61.7k
}
232
233
// ------------------------------------------------------------------------------------------------
234
Object::Object(uint64_t id, const Element& element, const std::string& name) :
235
61.7k
        element(element), name(name), id(id) {
236
    // empty
237
61.7k
}
238
239
// ------------------------------------------------------------------------------------------------
240
FileGlobalSettings::FileGlobalSettings(const Document &doc, std::shared_ptr<const PropertyTable> props) :
241
438
        props(std::move(props)), doc(doc) {
242
    // empty
243
438
}
244
245
// ------------------------------------------------------------------------------------------------
246
Document::Document(Parser& parser, const ImportSettings& settings) :
247
448
     settings(settings), parser(parser) {
248
448
  ASSIMP_LOG_DEBUG("Creating FBX Document");
249
250
    // Cannot use array default initialization syntax because vc8 fails on it
251
3.13k
    for (auto &timeStamp : creationTimeStamp) {
252
3.13k
        timeStamp = 0;
253
3.13k
    }
254
255
448
    ReadHeader();
256
448
    ReadPropertyTemplates();
257
258
448
    ReadGlobalSettings();
259
260
    // This order is important, connections need parsed objects to check
261
    // whether connections are ok or not. Objects may not be evaluated yet,
262
    // though, since this may require valid connections.
263
448
    ReadObjects();
264
448
    ReadConnections();
265
448
}
266
267
// ------------------------------------------------------------------------------------------------
268
Document::~Document()
269
412
{
270
  // The document does not own the memory for the following objects, but we need to call their d'tor
271
  // so they can properly free memory like string members:
272
273
69.7k
    for (ObjectMap::value_type &v : objects) {
274
69.7k
        delete_LazyObject(v.second);
275
69.7k
    }
276
277
92.7k
    for (ConnectionMap::value_type &v : src_connections) {
278
92.7k
        delete_Connection(v.second);
279
92.7k
    }
280
    // |dest_connections| contain the same Connection objects as the |src_connections|
281
412
}
282
283
// ------------------------------------------------------------------------------------------------
284
static const unsigned int LowerSupportedVersion = 7100;
285
static const unsigned int UpperSupportedVersion = 7400;
286
287
448
void Document::ReadHeader() {
288
    // Read ID objects from "Objects" section
289
448
    const Scope& sc = parser.GetRootScope();
290
448
    const Element* const ehead = sc["FBXHeaderExtension"];
291
448
    if(!ehead || !ehead->Compound()) {
292
6
        DOMError("no FBXHeaderExtension dictionary found");
293
6
    }
294
295
442
    const Scope& shead = *ehead->Compound();
296
442
    fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead,"FBXVersion",ehead),0));
297
442
  ASSIMP_LOG_DEBUG("FBX Version: ", fbxVersion);
298
299
    // While we may have some success with newer files, we don't support
300
    // the older 6.n fbx format
301
442
    if(fbxVersion < LowerSupportedVersion ) {
302
0
        DOMError("unsupported, old format version, supported are only FBX 2011, FBX 2012 and FBX 2013");
303
0
    }
304
442
    if(fbxVersion > UpperSupportedVersion ) {
305
97
        if(Settings().strictMode) {
306
0
            DOMError("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013"
307
0
                " (turn off strict mode to try anyhow) ");
308
0
        }
309
97
        else {
310
97
            DOMWarning("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013,"
311
97
                " trying to read it nevertheless");
312
97
        }
313
97
    }
314
315
442
    const Element* const ecreator = shead["Creator"];
316
442
    if(ecreator) {
317
365
        creator = ParseTokenAsString(GetRequiredToken(*ecreator,0));
318
365
    }
319
320
442
    const Element* const etimestamp = shead["CreationTimeStamp"];
321
442
    if(etimestamp && etimestamp->Compound()) {
322
380
        const Scope& stimestamp = *etimestamp->Compound();
323
380
        creationTimeStamp[0] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Year"),0));
324
380
        creationTimeStamp[1] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Month"),0));
325
380
        creationTimeStamp[2] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Day"),0));
326
380
        creationTimeStamp[3] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Hour"),0));
327
380
        creationTimeStamp[4] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Minute"),0));
328
380
        creationTimeStamp[5] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Second"),0));
329
380
        creationTimeStamp[6] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Millisecond"),0));
330
380
    }
331
442
}
332
333
// ------------------------------------------------------------------------------------------------
334
438
void Document::ReadGlobalSettings() {
335
438
    const Scope& sc = parser.GetRootScope();
336
438
    const Element* const ehead = sc["GlobalSettings"];
337
438
    if ( nullptr == ehead || !ehead->Compound() ) {
338
85
        DOMWarning( "no GlobalSettings dictionary found" );
339
85
        globals.reset(new FileGlobalSettings(*this, std::make_shared<const PropertyTable>()));
340
85
        return;
341
85
    }
342
343
353
    std::shared_ptr<const PropertyTable> props = GetPropertyTable( *this, "", *ehead, *ehead->Compound(), true );
344
345
    //double v = PropertyGet<float>( *props.get(), std::string("UnitScaleFactor"), 1.0 );
346
347
353
    if(!props) {
348
0
        DOMError("GlobalSettings dictionary contains no property table");
349
0
    }
350
351
353
    globals.reset(new FileGlobalSettings(*this, std::move(props)));
352
353
}
353
354
// ------------------------------------------------------------------------------------------------
355
438
void Document::ReadObjects() {
356
    // read ID objects from "Objects" section
357
438
    const Scope& sc = parser.GetRootScope();
358
438
    const Element* const eobjects = sc["Objects"];
359
438
    if(!eobjects || !eobjects->Compound()) {
360
16
        DOMError("no Objects dictionary found");
361
16
    }
362
363
422
    StackAllocator &allocator = parser.GetAllocator();
364
365
    // add a dummy entry to represent the Model::RootNode object (id 0),
366
    // which is only indirectly defined in the input file
367
422
    objects[0] = new_LazyObject(0L, *eobjects, *this);
368
369
422
    const Scope& sobjects = *eobjects->Compound();
370
69.4k
    for(const ElementMap::value_type& el : sobjects.Elements()) {
371
372
        // extract ID
373
69.4k
        const TokenList& tok = el.second->Tokens();
374
375
69.4k
        if (tok.empty()) {
376
0
            DOMError("expected ID after object key",el.second);
377
0
        }
378
379
69.4k
        const char* err;
380
69.4k
        const uint64_t id = ParseTokenAsID(*tok[0], err);
381
69.4k
        if(err) {
382
0
            DOMError(err,el.second);
383
0
        }
384
385
        // id=0 is normally implicit
386
69.4k
        if(id == 0L) {
387
0
            DOMError("encountered object with implicitly defined id 0",el.second);
388
0
        }
389
390
69.4k
        const auto foundObject = objects.find(id);
391
69.4k
        if(foundObject != objects.end()) {
392
36
            DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second);
393
36
            delete_LazyObject(foundObject->second);
394
36
        }
395
396
69.4k
        objects[id] = new_LazyObject(id, *el.second, *this);
397
398
        // grab all animation stacks upfront since there is no listing of them
399
69.4k
        if(!strcmp(el.first.c_str(),"AnimationStack")) {
400
305
            animationStacks.push_back(id);
401
305
        }
402
69.4k
    }
403
422
}
404
405
// ------------------------------------------------------------------------------------------------
406
438
void Document::ReadPropertyTemplates() {
407
438
    const Scope& sc = parser.GetRootScope();
408
    // read property templates from "Definitions" section
409
438
    const Element* const edefs = sc["Definitions"];
410
438
    if(!edefs || !edefs->Compound()) {
411
63
        DOMWarning("no Definitions dictionary found");
412
63
        return;
413
63
    }
414
415
375
    const Scope& sdefs = *edefs->Compound();
416
375
    const ElementCollection otypes = sdefs.GetCollection("ObjectType");
417
5.95k
    for(ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) {
418
5.57k
        const Element& el = *(*it).second;
419
5.57k
        const Scope* curSc = el.Compound();
420
5.57k
        if (!curSc) {
421
0
            DOMWarning("expected nested scope in ObjectType, ignoring",&el);
422
0
            continue;
423
0
        }
424
425
5.57k
        const TokenList& tok = el.Tokens();
426
5.57k
        if(tok.empty()) {
427
0
            DOMWarning("expected name for ObjectType element, ignoring",&el);
428
0
            continue;
429
0
        }
430
431
5.57k
        const std::string& oname = ParseTokenAsString(*tok[0]);
432
433
5.57k
        const ElementCollection templs = curSc->GetCollection("PropertyTemplate");
434
10.4k
        for (ElementMap::const_iterator elemIt = templs.first; elemIt != templs.second; ++elemIt) {
435
4.83k
            const Element &innerEl = *(*elemIt).second;
436
4.83k
            const Scope *innerSc = innerEl.Compound();
437
4.83k
            if (!innerSc) {
438
1.78k
                DOMWarning("expected nested scope in PropertyTemplate, ignoring",&el);
439
1.78k
                continue;
440
1.78k
            }
441
442
3.05k
            const TokenList &curTok = innerEl.Tokens();
443
3.05k
            if (curTok.empty()) {
444
0
                DOMWarning("expected name for PropertyTemplate element, ignoring",&el);
445
0
                continue;
446
0
            }
447
448
3.05k
            const std::string &pname = ParseTokenAsString(*curTok[0]);
449
450
3.05k
            const Element *Properties70 = (*innerSc)["Properties70"];
451
3.05k
            if(Properties70) {
452
3.01k
                std::shared_ptr<const PropertyTable> props = std::make_shared<const PropertyTable>(
453
3.01k
                        *Properties70, std::shared_ptr<const PropertyTable>(static_cast<const PropertyTable *>(nullptr))
454
3.01k
                );
455
456
3.01k
                templates[oname+"."+pname] = props;
457
3.01k
            }
458
3.05k
        }
459
5.57k
    }
460
375
}
461
462
// ------------------------------------------------------------------------------------------------
463
void Document::ReadConnections()
464
418
{
465
418
    StackAllocator &allocator = parser.GetAllocator();
466
418
    const Scope &sc = parser.GetRootScope();
467
    // read property templates from "Definitions" section
468
418
    const Element* const econns = sc["Connections"];
469
418
    if(!econns || !econns->Compound()) {
470
2
        DOMError("no Connections dictionary found");
471
2
    }
472
473
416
    uint64_t insertionOrder = 0l;
474
416
    const Scope& sconns = *econns->Compound();
475
416
    const ElementCollection conns = sconns.GetCollection("C");
476
107k
    for(ElementMap::const_iterator it = conns.first; it != conns.second; ++it) {
477
106k
        const Element& el = *(*it).second;
478
106k
        const std::string& type = ParseTokenAsString(GetRequiredToken(el,0));
479
480
        // PP = property-property connection, ignored for now
481
        // (tokens: "PP", ID1, "Property1", ID2, "Property2")
482
106k
        if ( type == "PP" ) {
483
1
            continue;
484
1
        }
485
486
106k
        const uint64_t src = ParseTokenAsID(GetRequiredToken(el,1));
487
106k
        const uint64_t dest = ParseTokenAsID(GetRequiredToken(el,2));
488
489
        // OO = object-object connection
490
        // OP = object-property connection, in which case the destination property follows the object ID
491
106k
        const std::string& prop = (type == "OP" ? ParseTokenAsString(GetRequiredToken(el,3)) : "");
492
493
106k
        if(objects.find(src) == objects.end()) {
494
5.05k
            DOMWarning("source object for connection does not exist",&el);
495
5.05k
            continue;
496
5.05k
        }
497
498
        // dest may be 0 (root node) but we added a dummy object before
499
101k
        if(objects.find(dest) == objects.end()) {
500
2.53k
            DOMWarning("destination object for connection does not exist",&el);
501
2.53k
            continue;
502
2.53k
        }
503
504
        // add new connection
505
99.3k
        const Connection* const c = new_Connection(insertionOrder++,src,dest,prop,*this);
506
99.3k
        src_connections.insert(ConnectionMap::value_type(src,c));
507
99.3k
        dest_connections.insert(ConnectionMap::value_type(dest,c));
508
99.3k
    }
509
416
}
510
511
// ------------------------------------------------------------------------------------------------
512
412
const std::vector<const AnimationStack*>& Document::AnimationStacks() const {
513
412
    if (!animationStacksResolved.empty() || animationStacks.empty()) {
514
107
        return animationStacksResolved;
515
107
    }
516
517
305
    animationStacksResolved.reserve(animationStacks.size());
518
305
    for(uint64_t id : animationStacks) {
519
305
        LazyObject* const lazy = GetObject(id);
520
305
        const AnimationStack *stack = lazy->Get<AnimationStack>();
521
305
        if(!lazy || nullptr == stack ) {
522
2
            DOMWarning("failed to read AnimationStack object");
523
2
            continue;
524
2
        }
525
303
        animationStacksResolved.push_back(stack);
526
303
    }
527
528
305
    return animationStacksResolved;
529
412
}
530
531
// ------------------------------------------------------------------------------------------------
532
443k
LazyObject* Document::GetObject(uint64_t id) const {
533
443k
    ObjectMap::const_iterator it = objects.find(id);
534
443k
    return it == objects.end() ? nullptr : (*it).second;
535
443k
}
536
537
constexpr size_t MAX_CLASSNAMES  = 6;
538
539
// ------------------------------------------------------------------------------------------------
540
1.27k
std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, const ConnectionMap& conns) const {
541
1.27k
    std::vector<const Connection*> temp;
542
543
1.27k
    const std::pair<ConnectionMap::const_iterator,ConnectionMap::const_iterator> range =
544
1.27k
        conns.equal_range(id);
545
546
1.27k
    temp.reserve(std::distance(range.first,range.second));
547
230k
    for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) {
548
228k
        temp.push_back((*it).second);
549
228k
    }
550
551
1.27k
    std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare));
552
553
1.27k
    return temp; // NRVO should handle this
554
1.27k
}
555
556
// ------------------------------------------------------------------------------------------------
557
std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, bool is_src,
558
        const ConnectionMap& conns,
559
        const char* const* classnames,
560
44.6k
        size_t count) const {
561
44.6k
    ai_assert(classnames);
562
44.6k
    ai_assert( count != 0 );
563
44.6k
    ai_assert( count <= MAX_CLASSNAMES);
564
565
44.6k
    size_t lengths[MAX_CLASSNAMES] = {};
566
567
44.6k
    const size_t c = count;
568
125k
    for (size_t i = 0; i < c; ++i) {
569
81.1k
        lengths[ i ] = strlen(classnames[i]);
570
81.1k
    }
571
572
44.6k
    std::vector<const Connection*> temp;
573
44.6k
    const std::pair<ConnectionMap::const_iterator,ConnectionMap::const_iterator> range =
574
44.6k
        conns.equal_range(id);
575
576
44.6k
    temp.reserve(std::distance(range.first,range.second));
577
184k
    for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) {
578
139k
        const Token& key = (is_src
579
139k
            ? (*it).second->LazyDestinationObject()
580
139k
            : (*it).second->LazySourceObject()
581
139k
        ).GetElement().KeyToken();
582
583
139k
        const char* obtype = key.begin();
584
585
267k
        for (size_t i = 0; i < c; ++i) {
586
206k
            ai_assert(classnames[i]);
587
206k
            if(static_cast<size_t>(std::distance(key.begin(),key.end())) == lengths[i] && !strncmp(classnames[i],obtype,lengths[i])) {
588
78.1k
                obtype = nullptr;
589
78.1k
                break;
590
78.1k
            }
591
206k
        }
592
593
139k
        if(obtype) {
594
61.3k
            continue;
595
61.3k
        }
596
597
78.1k
        temp.push_back((*it).second);
598
78.1k
    }
599
600
44.6k
    std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare));
601
44.6k
    return temp; // NRVO should handle this
602
44.6k
}
603
604
// ------------------------------------------------------------------------------------------------
605
0
std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source) const {
606
0
    return GetConnectionsSequenced(source, ConnectionsBySource());
607
0
}
608
609
// ------------------------------------------------------------------------------------------------
610
0
std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t src, const char* classname) const {
611
0
    const char* arr[] = {classname};
612
0
    return GetConnectionsBySourceSequenced(src, arr,1);
613
0
}
614
615
// ------------------------------------------------------------------------------------------------
616
std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source,
617
11.8k
        const char* const* classnames, size_t count) const {
618
11.8k
    return GetConnectionsSequenced(source, true, ConnectionsBySource(),classnames, count);
619
11.8k
}
620
621
// ------------------------------------------------------------------------------------------------
622
std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest,
623
26.3k
        const char* classname) const {
624
26.3k
    const char* arr[] = {classname};
625
26.3k
    return GetConnectionsByDestinationSequenced(dest, arr,1);
626
26.3k
}
627
628
// ------------------------------------------------------------------------------------------------
629
1.27k
std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const {
630
1.27k
    return GetConnectionsSequenced(dest, ConnectionsByDestination());
631
1.27k
}
632
633
// ------------------------------------------------------------------------------------------------
634
std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest,
635
32.7k
        const char* const* classnames, size_t count) const {
636
32.7k
    return GetConnectionsSequenced(dest, false, ConnectionsByDestination(),classnames, count);
637
32.7k
}
638
639
// ------------------------------------------------------------------------------------------------
640
Connection::Connection(uint64_t insertionOrder,  uint64_t src, uint64_t dest, const std::string& prop,
641
            const Document& doc) :
642
99.3k
            insertionOrder(insertionOrder), prop(prop), src(src), dest(dest), doc(doc) {
643
99.3k
    ai_assert(doc.Objects().find(src) != doc.Objects().end());
644
    // dest may be 0 (root node)
645
99.3k
    ai_assert(!dest || doc.Objects().find(dest) != doc.Objects().end());
646
99.3k
}
647
648
// ------------------------------------------------------------------------------------------------
649
116k
LazyObject& Connection::LazySourceObject() const {
650
116k
    LazyObject* const lazy = doc.GetObject(src);
651
116k
    ai_assert(lazy);
652
116k
    return *lazy;
653
116k
}
654
655
// ------------------------------------------------------------------------------------------------
656
23.3k
LazyObject& Connection::LazyDestinationObject() const {
657
23.3k
    LazyObject* const lazy = doc.GetObject(dest);
658
23.3k
    ai_assert(lazy);
659
23.3k
    return *lazy;
660
23.3k
}
661
662
// ------------------------------------------------------------------------------------------------
663
292k
const Object* Connection::SourceObject() const {
664
292k
    LazyObject* const lazy = doc.GetObject(src);
665
292k
    ai_assert(lazy);
666
292k
    if (lazy == nullptr) {
667
0
        return nullptr;
668
0
    }
669
670
292k
    return lazy->Get();
671
292k
}
672
673
// ------------------------------------------------------------------------------------------------
674
11.4k
const Object* Connection::DestinationObject() const {
675
11.4k
    LazyObject* const lazy = doc.GetObject(dest);
676
11.4k
    ai_assert(lazy);
677
11.4k
    if (lazy == nullptr) {
678
0
        return nullptr;
679
0
    }
680
681
11.4k
    return lazy->Get();
682
11.4k
}
683
684
} // !FBX
685
} // !Assimp
686
687
#endif // ASSIMP_BUILD_NO_FBX_IMPORTER