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