| /* |
| Open Asset Import Library (assimp) |
| ---------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the* |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| /** @file FBXDocument.cpp |
| * @brief Implementation of the FBX DOM classes |
| */ |
| |
| #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER |
| |
| #include "FBXDocument.h" |
| #include "FBXMeshGeometry.h" |
| #include "FBXParser.h" |
| #include "FBXUtil.h" |
| #include "FBXImporter.h" |
| #include "FBXImportSettings.h" |
| #include "FBXDocumentUtil.h" |
| #include "FBXProperties.h" |
| |
| #include <memory> |
| #include <functional> |
| #include <map> |
| |
| namespace Assimp { |
| namespace FBX { |
| |
| using namespace Util; |
| |
| // ------------------------------------------------------------------------------------------------ |
| LazyObject::LazyObject(uint64_t id, const Element& element, const Document& doc) |
| : doc(doc) |
| , element(element) |
| , id(id) |
| , flags() |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| LazyObject::~LazyObject() |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const Object* LazyObject::Get(bool dieOnError) |
| { |
| if(IsBeingConstructed() || FailedToConstruct()) { |
| return NULL; |
| } |
| |
| if (object.get()) { |
| return object.get(); |
| } |
| |
| // if this is the root object, we return a dummy since there |
| // is no root object int he fbx file - it is just referenced |
| // with id 0. |
| if(id == 0L) { |
| object.reset(new Object(id, element, "Model::RootNode")); |
| return object.get(); |
| } |
| |
| const Token& key = element.KeyToken(); |
| const TokenList& tokens = element.Tokens(); |
| |
| if(tokens.size() < 3) { |
| DOMError("expected at least 3 tokens: id, name and class tag",&element); |
| } |
| |
| const char* err; |
| std::string name = ParseTokenAsString(*tokens[1],err); |
| if (err) { |
| DOMError(err,&element); |
| } |
| |
| // small fix for binary reading: binary fbx files don't use |
| // prefixes such as Model:: in front of their names. The |
| // loading code expects this at many places, though! |
| // so convert the binary representation (a 0x0001) to the |
| // double colon notation. |
| if(tokens[1]->IsBinary()) { |
| for (size_t i = 0; i < name.length(); ++i) { |
| if (name[i] == 0x0 && name[i+1] == 0x1) { |
| name = name.substr(i+2) + "::" + name.substr(0,i); |
| } |
| } |
| } |
| |
| const std::string classtag = ParseTokenAsString(*tokens[2],err); |
| if (err) { |
| DOMError(err,&element); |
| } |
| |
| // prevent recursive calls |
| flags |= BEING_CONSTRUCTED; |
| |
| try { |
| // this needs to be relatively fast since it happens a lot, |
| // so avoid constructing strings all the time. |
| const char* obtype = key.begin(); |
| const size_t length = static_cast<size_t>(key.end()-key.begin()); |
| |
| // For debugging |
| //dumpObjectClassInfo( objtype, classtag ); |
| |
| if (!strncmp(obtype,"Geometry",length)) { |
| if (!strcmp(classtag.c_str(),"Mesh")) { |
| object.reset(new MeshGeometry(id,element,name,doc)); |
| } |
| } |
| else if (!strncmp(obtype,"NodeAttribute",length)) { |
| if (!strcmp(classtag.c_str(),"Camera")) { |
| object.reset(new Camera(id,element,doc,name)); |
| } |
| else if (!strcmp(classtag.c_str(),"CameraSwitcher")) { |
| object.reset(new CameraSwitcher(id,element,doc,name)); |
| } |
| else if (!strcmp(classtag.c_str(),"Light")) { |
| object.reset(new Light(id,element,doc,name)); |
| } |
| else if (!strcmp(classtag.c_str(),"Null")) { |
| object.reset(new Null(id,element,doc,name)); |
| } |
| else if (!strcmp(classtag.c_str(),"LimbNode")) { |
| object.reset(new LimbNode(id,element,doc,name)); |
| } |
| } |
| else if (!strncmp(obtype,"Deformer",length)) { |
| if (!strcmp(classtag.c_str(),"Cluster")) { |
| object.reset(new Cluster(id,element,doc,name)); |
| } |
| else if (!strcmp(classtag.c_str(),"Skin")) { |
| object.reset(new Skin(id,element,doc,name)); |
| } |
| } |
| else if ( !strncmp( obtype, "Model", length ) ) { |
| // FK and IK effectors are not supported |
| if ( strcmp( classtag.c_str(), "IKEffector" ) && strcmp( classtag.c_str(), "FKEffector" ) ) { |
| object.reset( new Model( id, element, doc, name ) ); |
| } |
| } |
| else if (!strncmp(obtype,"Material",length)) { |
| object.reset(new Material(id,element,doc,name)); |
| } |
| else if (!strncmp(obtype,"Texture",length)) { |
| object.reset(new Texture(id,element,doc,name)); |
| } |
| else if (!strncmp(obtype,"LayeredTexture",length)) { |
| object.reset(new LayeredTexture(id,element,doc,name)); |
| } |
| else if (!strncmp(obtype,"Video",length)) { |
| object.reset(new Video(id,element,doc,name)); |
| } |
| else if (!strncmp(obtype,"AnimationStack",length)) { |
| object.reset(new AnimationStack(id,element,name,doc)); |
| } |
| else if (!strncmp(obtype,"AnimationLayer",length)) { |
| object.reset(new AnimationLayer(id,element,name,doc)); |
| } |
| // note: order matters for these two |
| else if (!strncmp(obtype,"AnimationCurve",length)) { |
| object.reset(new AnimationCurve(id,element,name,doc)); |
| } |
| else if (!strncmp(obtype,"AnimationCurveNode",length)) { |
| object.reset(new AnimationCurveNode(id,element,name,doc)); |
| } |
| } |
| catch(std::exception& ex) { |
| flags &= ~BEING_CONSTRUCTED; |
| flags |= FAILED_TO_CONSTRUCT; |
| |
| if(dieOnError || doc.Settings().strictMode) { |
| throw; |
| } |
| |
| // note: the error message is already formatted, so raw logging is ok |
| if(!DefaultLogger::isNullLogger()) { |
| DefaultLogger::get()->error(ex.what()); |
| } |
| return NULL; |
| } |
| |
| if (!object.get()) { |
| //DOMError("failed to convert element to DOM object, class: " + classtag + ", name: " + name,&element); |
| } |
| |
| flags &= ~BEING_CONSTRUCTED; |
| return object.get(); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Object::Object(uint64_t id, const Element& element, const std::string& name) |
| : element(element) |
| , name(name) |
| , id(id) |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Object::~Object() |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| FileGlobalSettings::FileGlobalSettings(const Document& doc, std::shared_ptr<const PropertyTable> props) |
| : props(props) |
| , doc(doc) |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| FileGlobalSettings::~FileGlobalSettings() |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Document::Document(const Parser& parser, const ImportSettings& settings) |
| : settings(settings) |
| , parser(parser) |
| { |
| // Cannot use array default initialization syntax because vc8 fails on it |
| for (auto &timeStamp : creationTimeStamp) { |
| timeStamp = 0; |
| } |
| |
| ReadHeader(); |
| ReadPropertyTemplates(); |
| |
| ReadGlobalSettings(); |
| |
| // This order is important, connections need parsed objects to check |
| // whether connections are ok or not. Objects may not be evaluated yet, |
| // though, since this may require valid connections. |
| ReadObjects(); |
| ReadConnections(); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Document::~Document() |
| { |
| for(ObjectMap::value_type& v : objects) { |
| delete v.second; |
| } |
| |
| for(ConnectionMap::value_type& v : src_connections) { |
| delete v.second; |
| } |
| // |dest_connections| contain the same Connection objects as the |src_connections| |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| static const unsigned int LowerSupportedVersion = 7100; |
| static const unsigned int UpperSupportedVersion = 7400; |
| |
| void Document::ReadHeader() { |
| // Read ID objects from "Objects" section |
| const Scope& sc = parser.GetRootScope(); |
| const Element* const ehead = sc["FBXHeaderExtension"]; |
| if(!ehead || !ehead->Compound()) { |
| DOMError("no FBXHeaderExtension dictionary found"); |
| } |
| |
| const Scope& shead = *ehead->Compound(); |
| fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead,"FBXVersion",ehead),0)); |
| |
| // While we may have some success with newer files, we don't support |
| // the older 6.n fbx format |
| if(fbxVersion < LowerSupportedVersion ) { |
| DOMError("unsupported, old format version, supported are only FBX 2011, FBX 2012 and FBX 2013"); |
| } |
| if(fbxVersion > UpperSupportedVersion ) { |
| if(Settings().strictMode) { |
| DOMError("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013" |
| " (turn off strict mode to try anyhow) "); |
| } |
| else { |
| DOMWarning("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013," |
| " trying to read it nevertheless"); |
| } |
| } |
| |
| const Element* const ecreator = shead["Creator"]; |
| if(ecreator) { |
| creator = ParseTokenAsString(GetRequiredToken(*ecreator,0)); |
| } |
| |
| const Element* const etimestamp = shead["CreationTimeStamp"]; |
| if(etimestamp && etimestamp->Compound()) { |
| const Scope& stimestamp = *etimestamp->Compound(); |
| creationTimeStamp[0] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Year"),0)); |
| creationTimeStamp[1] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Month"),0)); |
| creationTimeStamp[2] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Day"),0)); |
| creationTimeStamp[3] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Hour"),0)); |
| creationTimeStamp[4] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Minute"),0)); |
| creationTimeStamp[5] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Second"),0)); |
| creationTimeStamp[6] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp,"Millisecond"),0)); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void Document::ReadGlobalSettings() |
| { |
| const Scope& sc = parser.GetRootScope(); |
| const Element* const ehead = sc["GlobalSettings"]; |
| if(!ehead || !ehead->Compound()) { |
| DOMWarning("no GlobalSettings dictionary found"); |
| |
| globals.reset(new FileGlobalSettings(*this, std::make_shared<const PropertyTable>())); |
| return; |
| } |
| |
| std::shared_ptr<const PropertyTable> props = GetPropertyTable(*this, "", *ehead, *ehead->Compound(), true); |
| |
| if(!props) { |
| DOMError("GlobalSettings dictionary contains no property table"); |
| } |
| |
| globals.reset(new FileGlobalSettings(*this, props)); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void Document::ReadObjects() |
| { |
| // read ID objects from "Objects" section |
| const Scope& sc = parser.GetRootScope(); |
| const Element* const eobjects = sc["Objects"]; |
| if(!eobjects || !eobjects->Compound()) { |
| DOMError("no Objects dictionary found"); |
| } |
| |
| // add a dummy entry to represent the Model::RootNode object (id 0), |
| // which is only indirectly defined in the input file |
| objects[0] = new LazyObject(0L, *eobjects, *this); |
| |
| const Scope& sobjects = *eobjects->Compound(); |
| for(const ElementMap::value_type& el : sobjects.Elements()) { |
| |
| // extract ID |
| const TokenList& tok = el.second->Tokens(); |
| |
| if (tok.empty()) { |
| DOMError("expected ID after object key",el.second); |
| } |
| |
| const char* err; |
| const uint64_t id = ParseTokenAsID(*tok[0], err); |
| if(err) { |
| DOMError(err,el.second); |
| } |
| |
| // id=0 is normally implicit |
| if(id == 0L) { |
| DOMError("encountered object with implicitly defined id 0",el.second); |
| } |
| |
| if(objects.find(id) != objects.end()) { |
| DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second); |
| } |
| |
| objects[id] = new LazyObject(id, *el.second, *this); |
| |
| // grab all animation stacks upfront since there is no listing of them |
| if(!strcmp(el.first.c_str(),"AnimationStack")) { |
| animationStacks.push_back(id); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void Document::ReadPropertyTemplates() |
| { |
| const Scope& sc = parser.GetRootScope(); |
| // read property templates from "Definitions" section |
| const Element* const edefs = sc["Definitions"]; |
| if(!edefs || !edefs->Compound()) { |
| DOMWarning("no Definitions dictionary found"); |
| return; |
| } |
| |
| const Scope& sdefs = *edefs->Compound(); |
| const ElementCollection otypes = sdefs.GetCollection("ObjectType"); |
| for(ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) { |
| const Element& el = *(*it).second; |
| const Scope* sc = el.Compound(); |
| if(!sc) { |
| DOMWarning("expected nested scope in ObjectType, ignoring",&el); |
| continue; |
| } |
| |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| DOMWarning("expected name for ObjectType element, ignoring",&el); |
| continue; |
| } |
| |
| const std::string& oname = ParseTokenAsString(*tok[0]); |
| |
| const ElementCollection templs = sc->GetCollection("PropertyTemplate"); |
| for(ElementMap::const_iterator it = templs.first; it != templs.second; ++it) { |
| const Element& el = *(*it).second; |
| const Scope* sc = el.Compound(); |
| if(!sc) { |
| DOMWarning("expected nested scope in PropertyTemplate, ignoring",&el); |
| continue; |
| } |
| |
| const TokenList& tok = el.Tokens(); |
| if(tok.empty()) { |
| DOMWarning("expected name for PropertyTemplate element, ignoring",&el); |
| continue; |
| } |
| |
| const std::string& pname = ParseTokenAsString(*tok[0]); |
| |
| const Element* Properties70 = (*sc)["Properties70"]; |
| if(Properties70) { |
| std::shared_ptr<const PropertyTable> props = std::make_shared<const PropertyTable>( |
| *Properties70,std::shared_ptr<const PropertyTable>(static_cast<const PropertyTable*>(NULL)) |
| ); |
| |
| templates[oname+"."+pname] = props; |
| } |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void Document::ReadConnections() |
| { |
| const Scope& sc = parser.GetRootScope(); |
| // read property templates from "Definitions" section |
| const Element* const econns = sc["Connections"]; |
| if(!econns || !econns->Compound()) { |
| DOMError("no Connections dictionary found"); |
| } |
| |
| uint64_t insertionOrder = 0l; |
| const Scope& sconns = *econns->Compound(); |
| const ElementCollection conns = sconns.GetCollection("C"); |
| for(ElementMap::const_iterator it = conns.first; it != conns.second; ++it) { |
| const Element& el = *(*it).second; |
| const std::string& type = ParseTokenAsString(GetRequiredToken(el,0)); |
| |
| // PP = property-property connection, ignored for now |
| // (tokens: "PP", ID1, "Property1", ID2, "Property2") |
| if ( type == "PP" ) { |
| continue; |
| } |
| |
| const uint64_t src = ParseTokenAsID(GetRequiredToken(el,1)); |
| const uint64_t dest = ParseTokenAsID(GetRequiredToken(el,2)); |
| |
| // OO = object-object connection |
| // OP = object-property connection, in which case the destination property follows the object ID |
| const std::string& prop = (type == "OP" ? ParseTokenAsString(GetRequiredToken(el,3)) : ""); |
| |
| if(objects.find(src) == objects.end()) { |
| DOMWarning("source object for connection does not exist",&el); |
| continue; |
| } |
| |
| // dest may be 0 (root node) but we added a dummy object before |
| if(objects.find(dest) == objects.end()) { |
| DOMWarning("destination object for connection does not exist",&el); |
| continue; |
| } |
| |
| // add new connection |
| const Connection* const c = new Connection(insertionOrder++,src,dest,prop,*this); |
| src_connections.insert(ConnectionMap::value_type(src,c)); |
| dest_connections.insert(ConnectionMap::value_type(dest,c)); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<const AnimationStack*>& Document::AnimationStacks() const |
| { |
| if (!animationStacksResolved.empty() || animationStacks.empty()) { |
| return animationStacksResolved; |
| } |
| |
| animationStacksResolved.reserve(animationStacks.size()); |
| for(uint64_t id : animationStacks) { |
| LazyObject* const lazy = GetObject(id); |
| const AnimationStack* stack; |
| if(!lazy || !(stack = lazy->Get<AnimationStack>())) { |
| DOMWarning("failed to read AnimationStack object"); |
| continue; |
| } |
| animationStacksResolved.push_back(stack); |
| } |
| |
| return animationStacksResolved; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| LazyObject* Document::GetObject(uint64_t id) const |
| { |
| ObjectMap::const_iterator it = objects.find(id); |
| return it == objects.end() ? NULL : (*it).second; |
| } |
| |
| #define MAX_CLASSNAMES 6 |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, const ConnectionMap& conns) const |
| { |
| std::vector<const Connection*> temp; |
| |
| const std::pair<ConnectionMap::const_iterator,ConnectionMap::const_iterator> range = |
| conns.equal_range(id); |
| |
| temp.reserve(std::distance(range.first,range.second)); |
| for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { |
| temp.push_back((*it).second); |
| } |
| |
| std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); |
| |
| return temp; // NRVO should handle this |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, bool is_src, |
| const ConnectionMap& conns, |
| const char* const* classnames, |
| size_t count) const |
| |
| { |
| ai_assert(classnames); |
| ai_assert( count != 0 ); |
| ai_assert( count <= MAX_CLASSNAMES); |
| |
| size_t lenghts[MAX_CLASSNAMES]; |
| |
| const size_t c = count; |
| for (size_t i = 0; i < c; ++i) { |
| lenghts[ i ] = strlen(classnames[i]); |
| } |
| |
| std::vector<const Connection*> temp; |
| const std::pair<ConnectionMap::const_iterator,ConnectionMap::const_iterator> range = |
| conns.equal_range(id); |
| |
| temp.reserve(std::distance(range.first,range.second)); |
| for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) { |
| const Token& key = (is_src |
| ? (*it).second->LazyDestinationObject() |
| : (*it).second->LazySourceObject() |
| ).GetElement().KeyToken(); |
| |
| const char* obtype = key.begin(); |
| |
| for (size_t i = 0; i < c; ++i) { |
| ai_assert(classnames[i]); |
| if(static_cast<size_t>(std::distance(key.begin(),key.end())) == lenghts[i] && !strncmp(classnames[i],obtype,lenghts[i])) { |
| obtype = NULL; |
| break; |
| } |
| } |
| |
| if(obtype) { |
| continue; |
| } |
| |
| temp.push_back((*it).second); |
| } |
| |
| std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare)); |
| return temp; // NRVO should handle this |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source) const |
| { |
| return GetConnectionsSequenced(source, ConnectionsBySource()); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t dest, const char* classname) const |
| { |
| const char* arr[] = {classname}; |
| return GetConnectionsBySourceSequenced(dest, arr,1); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source, |
| const char* const* classnames, size_t count) const |
| { |
| return GetConnectionsSequenced(source, true, ConnectionsBySource(),classnames, count); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest, |
| const char* classname) const |
| { |
| const char* arr[] = {classname}; |
| return GetConnectionsByDestinationSequenced(dest, arr,1); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const |
| { |
| return GetConnectionsSequenced(dest, ConnectionsByDestination()); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest, |
| const char* const* classnames, size_t count) const |
| |
| { |
| return GetConnectionsSequenced(dest, false, ConnectionsByDestination(),classnames, count); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Connection::Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string& prop, |
| const Document& doc) |
| |
| : insertionOrder(insertionOrder) |
| , prop(prop) |
| , src(src) |
| , dest(dest) |
| , doc(doc) |
| { |
| ai_assert(doc.Objects().find(src) != doc.Objects().end()); |
| // dest may be 0 (root node) |
| ai_assert(!dest || doc.Objects().find(dest) != doc.Objects().end()); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| Connection::~Connection() |
| { |
| // empty |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| LazyObject& Connection::LazySourceObject() const |
| { |
| LazyObject* const lazy = doc.GetObject(src); |
| ai_assert(lazy); |
| return *lazy; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| LazyObject& Connection::LazyDestinationObject() const |
| { |
| LazyObject* const lazy = doc.GetObject(dest); |
| ai_assert(lazy); |
| return *lazy; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const Object* Connection::SourceObject() const |
| { |
| LazyObject* const lazy = doc.GetObject(src); |
| ai_assert(lazy); |
| return lazy->Get(); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const Object* Connection::DestinationObject() const |
| { |
| LazyObject* const lazy = doc.GetObject(dest); |
| ai_assert(lazy); |
| return lazy->Get(); |
| } |
| |
| } // !FBX |
| } // !Assimp |
| |
| #endif |