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