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