1 | /* |
2 | Open Asset Import Library (assimp) |
3 | ---------------------------------------------------------------------- |
4 | |
5 | Copyright (c) 2006-2017, 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 <memory> |
58 | #include <functional> |
59 | #include <map> |
60 | |
61 | namespace Assimp { |
62 | namespace FBX { |
63 | |
64 | using namespace Util; |
65 | |
66 | // ------------------------------------------------------------------------------------------------ |
67 | LazyObject::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 | // ------------------------------------------------------------------------------------------------ |
77 | LazyObject::~LazyObject() |
78 | { |
79 | // empty |
80 | } |
81 | |
82 | // ------------------------------------------------------------------------------------------------ |
83 | const 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 | // ------------------------------------------------------------------------------------------------ |
230 | Object::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 | // ------------------------------------------------------------------------------------------------ |
239 | Object::~Object() |
240 | { |
241 | // empty |
242 | } |
243 | |
244 | // ------------------------------------------------------------------------------------------------ |
245 | FileGlobalSettings::FileGlobalSettings(const Document& doc, std::shared_ptr<const PropertyTable> props) |
246 | : props(props) |
247 | , doc(doc) |
248 | { |
249 | // empty |
250 | } |
251 | |
252 | // ------------------------------------------------------------------------------------------------ |
253 | FileGlobalSettings::~FileGlobalSettings() |
254 | { |
255 | // empty |
256 | } |
257 | |
258 | // ------------------------------------------------------------------------------------------------ |
259 | Document::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 | // ------------------------------------------------------------------------------------------------ |
281 | Document::~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 | // ------------------------------------------------------------------------------------------------ |
294 | static const unsigned int LowerSupportedVersion = 7100; |
295 | static const unsigned int UpperSupportedVersion = 7400; |
296 | |
297 | void Document::() { |
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 | // ------------------------------------------------------------------------------------------------ |
343 | void 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 | // ------------------------------------------------------------------------------------------------ |
364 | void 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 | // ------------------------------------------------------------------------------------------------ |
412 | void 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 | // ------------------------------------------------------------------------------------------------ |
470 | void 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 | // ------------------------------------------------------------------------------------------------ |
518 | const 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 | // ------------------------------------------------------------------------------------------------ |
539 | LazyObject* 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 | // ------------------------------------------------------------------------------------------------ |
548 | std::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 | // ------------------------------------------------------------------------------------------------ |
566 | std::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 | // ------------------------------------------------------------------------------------------------ |
616 | std::vector<const Connection*> Document::GetConnectionsBySourceSequenced(uint64_t source) const |
617 | { |
618 | return GetConnectionsSequenced(source, ConnectionsBySource()); |
619 | } |
620 | |
621 | // ------------------------------------------------------------------------------------------------ |
622 | std::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 | // ------------------------------------------------------------------------------------------------ |
629 | std::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 | // ------------------------------------------------------------------------------------------------ |
636 | std::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 | // ------------------------------------------------------------------------------------------------ |
644 | std::vector<const Connection*> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const |
645 | { |
646 | return GetConnectionsSequenced(dest, ConnectionsByDestination()); |
647 | } |
648 | |
649 | // ------------------------------------------------------------------------------------------------ |
650 | std::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 | // ------------------------------------------------------------------------------------------------ |
658 | Connection::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 | // ------------------------------------------------------------------------------------------------ |
673 | Connection::~Connection() |
674 | { |
675 | // empty |
676 | } |
677 | |
678 | // ------------------------------------------------------------------------------------------------ |
679 | LazyObject& Connection::LazySourceObject() const |
680 | { |
681 | LazyObject* const lazy = doc.GetObject(src); |
682 | ai_assert(lazy); |
683 | return *lazy; |
684 | } |
685 | |
686 | // ------------------------------------------------------------------------------------------------ |
687 | LazyObject& Connection::LazyDestinationObject() const |
688 | { |
689 | LazyObject* const lazy = doc.GetObject(dest); |
690 | ai_assert(lazy); |
691 | return *lazy; |
692 | } |
693 | |
694 | // ------------------------------------------------------------------------------------------------ |
695 | const Object* Connection::SourceObject() const |
696 | { |
697 | LazyObject* const lazy = doc.GetObject(src); |
698 | ai_assert(lazy); |
699 | return lazy->Get(); |
700 | } |
701 | |
702 | // ------------------------------------------------------------------------------------------------ |
703 | const 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 | |