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  BlenderDNA.inl
43 *  @brief Blender `DNA` (file format specification embedded in
44 *    blend file itself) loader.
45 */
46#ifndef INCLUDED_AI_BLEND_DNA_INL
47#define INCLUDED_AI_BLEND_DNA_INL
48
49#include <memory>
50#include "TinyFormatter.h"
51
52namespace Assimp {
53namespace Blender {
54
55//--------------------------------------------------------------------------------
56const Field& Structure :: operator [] (const std::string& ss) const
57{
58    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
59    if (it == indices.end()) {
60        throw Error((Formatter::format(),
61            "BlendDNA: Did not find a field named `",ss,"` in structure `",name,"`"
62            ));
63    }
64
65    return fields[(*it).second];
66}
67
68//--------------------------------------------------------------------------------
69const Field* Structure :: Get (const std::string& ss) const
70{
71    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
72    return it == indices.end() ? NULL : &fields[(*it).second];
73}
74
75//--------------------------------------------------------------------------------
76const Field& Structure :: operator [] (const size_t i) const
77{
78    if (i >= fields.size()) {
79        throw Error((Formatter::format(),
80            "BlendDNA: There is no field with index `",i,"` in structure `",name,"`"
81            ));
82    }
83
84    return fields[i];
85}
86
87//--------------------------------------------------------------------------------
88template <typename T> std::shared_ptr<ElemBase> Structure :: Allocate() const
89{
90    return std::shared_ptr<T>(new T());
91}
92
93//--------------------------------------------------------------------------------
94template <typename T> void Structure :: Convert(
95    std::shared_ptr<ElemBase> in,
96    const FileDatabase& db) const
97{
98    Convert<T> (*static_cast<T*> ( in.get() ),db);
99}
100
101//--------------------------------------------------------------------------------
102template <int error_policy, typename T, size_t M>
103void Structure :: ReadFieldArray(T (& out)[M], const char* name, const FileDatabase& db) const
104{
105    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
106    try {
107        const Field& f = (*this)[name];
108        const Structure& s = db.dna[f.type];
109
110        // is the input actually an array?
111        if (!(f.flags & FieldFlag_Array)) {
112            throw Error((Formatter::format(),"Field `",name,"` of structure `",
113                this->name,"` ought to be an array of size ",M
114                ));
115        }
116
117        db.reader->IncPtr(f.offset);
118
119        // size conversions are always allowed, regardless of error_policy
120        unsigned int i = 0;
121        for(; i < std::min(f.array_sizes[0],M); ++i) {
122            s.Convert(out[i],db);
123        }
124        for(; i < M; ++i) {
125            _defaultInitializer<ErrorPolicy_Igno>()(out[i]);
126        }
127    }
128    catch (const Error& e) {
129        _defaultInitializer<error_policy>()(out,e.what());
130    }
131
132    // and recover the previous stream position
133    db.reader->SetCurrentPos(old);
134
135#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
136    ++db.stats().fields_read;
137#endif
138}
139
140//--------------------------------------------------------------------------------
141template <int error_policy, typename T, size_t M, size_t N>
142void Structure :: ReadFieldArray2(T (& out)[M][N], const char* name, const FileDatabase& db) const
143{
144    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
145    try {
146        const Field& f = (*this)[name];
147        const Structure& s = db.dna[f.type];
148
149        // is the input actually an array?
150        if (!(f.flags & FieldFlag_Array)) {
151            throw Error((Formatter::format(),"Field `",name,"` of structure `",
152                this->name,"` ought to be an array of size ",M,"*",N
153                ));
154        }
155
156        db.reader->IncPtr(f.offset);
157
158        // size conversions are always allowed, regardless of error_policy
159        unsigned int i = 0;
160        for(; i < std::min(f.array_sizes[0],M); ++i) {
161            unsigned int j = 0;
162            for(; j < std::min(f.array_sizes[1],N); ++j) {
163                s.Convert(out[i][j],db);
164            }
165            for(; j < N; ++j) {
166                _defaultInitializer<ErrorPolicy_Igno>()(out[i][j]);
167            }
168        }
169        for(; i < M; ++i) {
170            _defaultInitializer<ErrorPolicy_Igno>()(out[i]);
171        }
172    }
173    catch (const Error& e) {
174        _defaultInitializer<error_policy>()(out,e.what());
175    }
176
177    // and recover the previous stream position
178    db.reader->SetCurrentPos(old);
179
180#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
181    ++db.stats().fields_read;
182#endif
183}
184
185//--------------------------------------------------------------------------------
186template <int error_policy, template <typename> class TOUT, typename T>
187bool Structure :: ReadFieldPtr(TOUT<T>& out, const char* name, const FileDatabase& db,
188    bool non_recursive /*= false*/) const
189{
190    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
191    Pointer ptrval;
192    const Field* f;
193    try {
194        f = &(*this)[name];
195
196        // sanity check, should never happen if the genblenddna script is right
197        if (!(f->flags & FieldFlag_Pointer)) {
198            throw Error((Formatter::format(),"Field `",name,"` of structure `",
199                this->name,"` ought to be a pointer"));
200        }
201
202        db.reader->IncPtr(f->offset);
203        Convert(ptrval,db);
204        // actually it is meaningless on which Structure the Convert is called
205        // because the `Pointer` argument triggers a special implementation.
206    }
207    catch (const Error& e) {
208        _defaultInitializer<error_policy>()(out,e.what());
209
210        out.reset();
211        return false;
212    }
213
214    // resolve the pointer and load the corresponding structure
215    const bool res = ResolvePointer(out,ptrval,db,*f, non_recursive);
216
217    if(!non_recursive) {
218        // and recover the previous stream position
219        db.reader->SetCurrentPos(old);
220    }
221
222#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
223    ++db.stats().fields_read;
224#endif
225
226    return res;
227}
228
229//--------------------------------------------------------------------------------
230template <int error_policy, template <typename> class TOUT, typename T, size_t N>
231bool Structure :: ReadFieldPtr(TOUT<T> (&out)[N], const char* name,
232    const FileDatabase& db) const
233{
234    // XXX see if we can reduce this to call to the 'normal' ReadFieldPtr
235    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
236    Pointer ptrval[N];
237    const Field* f;
238    try {
239        f = &(*this)[name];
240
241        // sanity check, should never happen if the genblenddna script is right
242        if ((FieldFlag_Pointer|FieldFlag_Pointer) != (f->flags & (FieldFlag_Pointer|FieldFlag_Pointer))) {
243            throw Error((Formatter::format(),"Field `",name,"` of structure `",
244                this->name,"` ought to be a pointer AND an array"));
245        }
246
247        db.reader->IncPtr(f->offset);
248
249        size_t i = 0;
250        for(; i < std::min(f->array_sizes[0],N); ++i) {
251            Convert(ptrval[i],db);
252        }
253        for(; i < N; ++i) {
254            _defaultInitializer<ErrorPolicy_Igno>()(ptrval[i]);
255        }
256
257        // actually it is meaningless on which Structure the Convert is called
258        // because the `Pointer` argument triggers a special implementation.
259    }
260    catch (const Error& e) {
261        _defaultInitializer<error_policy>()(out,e.what());
262        for(size_t i = 0; i < N; ++i) {
263            out[i].reset();
264        }
265        return false;
266    }
267
268    bool res = true;
269    for(size_t i = 0; i < N; ++i) {
270        // resolve the pointer and load the corresponding structure
271        res = ResolvePointer(out[i],ptrval[i],db,*f) && res;
272    }
273
274    // and recover the previous stream position
275    db.reader->SetCurrentPos(old);
276
277#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
278    ++db.stats().fields_read;
279#endif
280    return res;
281}
282
283//--------------------------------------------------------------------------------
284template <int error_policy, typename T>
285void Structure :: ReadField(T& out, const char* name, const FileDatabase& db) const
286{
287    const StreamReaderAny::pos old = db.reader->GetCurrentPos();
288    try {
289        const Field& f = (*this)[name];
290        // find the structure definition pertaining to this field
291        const Structure& s = db.dna[f.type];
292
293        db.reader->IncPtr(f.offset);
294        s.Convert(out,db);
295    }
296    catch (const Error& e) {
297        _defaultInitializer<error_policy>()(out,e.what());
298    }
299
300    // and recover the previous stream position
301    db.reader->SetCurrentPos(old);
302
303#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
304    ++db.stats().fields_read;
305#endif
306}
307
308
309//--------------------------------------------------------------------------------
310template <template <typename> class TOUT, typename T>
311bool Structure :: ResolvePointer(TOUT<T>& out, const Pointer & ptrval, const FileDatabase& db,
312    const Field& f,
313    bool non_recursive /*= false*/) const
314{
315    out.reset(); // ensure null pointers work
316    if (!ptrval.val) {
317        return false;
318    }
319    const Structure& s = db.dna[f.type];
320    // find the file block the pointer is pointing to
321    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
322
323    // also determine the target type from the block header
324    // and check if it matches the type which we expect.
325    const Structure& ss = db.dna[block->dna_index];
326    if (ss != s) {
327        throw Error((Formatter::format(),"Expected target to be of type `",s.name,
328            "` but seemingly it is a `",ss.name,"` instead"
329            ));
330    }
331
332    // try to retrieve the object from the cache
333    db.cache(out).get(s,out,ptrval);
334    if (out) {
335        return true;
336    }
337
338    // seek to this location, but save the previous stream pointer.
339    const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
340    db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
341    // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
342    // I really ought to improve StreamReader to work with 64 bit indices exclusively.
343
344    // continue conversion after allocating the required storage
345    size_t num = block->size / ss.size;
346    T* o = _allocate(out,num);
347
348    // cache the object before we convert it to avoid cyclic recursion.
349    db.cache(out).set(s,out,ptrval);
350
351    // if the non_recursive flag is set, we don't do anything but leave
352    // the cursor at the correct position to resolve the object.
353    if (!non_recursive) {
354        for (size_t i = 0; i < num; ++i,++o) {
355            s.Convert(*o,db);
356        }
357
358        db.reader->SetCurrentPos(pold);
359    }
360
361#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
362    if(out) {
363        ++db.stats().pointers_resolved;
364    }
365#endif
366    return false;
367}
368
369
370//--------------------------------------------------------------------------------
371inline bool Structure :: ResolvePointer( std::shared_ptr< FileOffset >& out, const Pointer & ptrval,
372    const FileDatabase& db,
373    const Field&,
374    bool) const
375{
376    // Currently used exclusively by PackedFile::data to represent
377    // a simple offset into the mapped BLEND file.
378    out.reset();
379    if (!ptrval.val) {
380        return false;
381    }
382
383    // find the file block the pointer is pointing to
384    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
385
386    out =  std::shared_ptr< FileOffset > (new FileOffset());
387    out->val = block->start+ static_cast<size_t>((ptrval.val - block->address.val) );
388    return false;
389}
390
391//--------------------------------------------------------------------------------
392template <template <typename> class TOUT, typename T>
393bool Structure :: ResolvePointer(vector< TOUT<T> >& out, const Pointer & ptrval,
394    const FileDatabase& db,
395    const Field& f,
396    bool) const
397{
398    // This is a function overload, not a template specialization. According to
399    // the partial ordering rules, it should be selected by the compiler
400    // for array-of-pointer inputs, i.e. Object::mats.
401
402    out.reset();
403    if (!ptrval.val) {
404        return false;
405    }
406
407    // find the file block the pointer is pointing to
408    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
409    const size_t num = block->size / (db.i64bit?8:4);
410
411    // keep the old stream position
412    const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
413    db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
414
415    bool res = false;
416    // allocate raw storage for the array
417    out.resize(num);
418    for (size_t i = 0; i< num; ++i) {
419        Pointer val;
420        Convert(val,db);
421
422        // and resolve the pointees
423        res = ResolvePointer(out[i],val,db,f) && res;
424    }
425
426    db.reader->SetCurrentPos(pold);
427    return res;
428}
429
430//--------------------------------------------------------------------------------
431template <> bool Structure :: ResolvePointer<std::shared_ptr,ElemBase>(std::shared_ptr<ElemBase>& out,
432    const Pointer & ptrval,
433    const FileDatabase& db,
434    const Field&,
435    bool
436) const
437{
438    // Special case when the data type needs to be determined at runtime.
439    // Less secure than in the `strongly-typed` case.
440
441    out.reset();
442    if (!ptrval.val) {
443        return false;
444    }
445
446    // find the file block the pointer is pointing to
447    const FileBlockHead* block = LocateFileBlockForAddress(ptrval,db);
448
449    // determine the target type from the block header
450    const Structure& s = db.dna[block->dna_index];
451
452    // try to retrieve the object from the cache
453    db.cache(out).get(s,out,ptrval);
454    if (out) {
455        return true;
456    }
457
458    // seek to this location, but save the previous stream pointer.
459    const StreamReaderAny::pos pold = db.reader->GetCurrentPos();
460    db.reader->SetCurrentPos(block->start+ static_cast<size_t>((ptrval.val - block->address.val) ));
461    // FIXME: basically, this could cause problems with 64 bit pointers on 32 bit systems.
462    // I really ought to improve StreamReader to work with 64 bit indices exclusively.
463
464    // continue conversion after allocating the required storage
465    DNA::FactoryPair builders = db.dna.GetBlobToStructureConverter(s,db);
466    if (!builders.first) {
467        // this might happen if DNA::RegisterConverters hasn't been called so far
468        // or if the target type is not contained in `our` DNA.
469        out.reset();
470        DefaultLogger::get()->warn((Formatter::format(),
471            "Failed to find a converter for the `",s.name,"` structure"
472            ));
473        return false;
474    }
475
476    // allocate the object hull
477    out = (s.*builders.first)();
478
479    // cache the object immediately to prevent infinite recursion in a
480    // circular list with a single element (i.e. a self-referencing element).
481    db.cache(out).set(s,out,ptrval);
482
483    // and do the actual conversion
484    (s.*builders.second)(out,db);
485    db.reader->SetCurrentPos(pold);
486
487    // store a pointer to the name string of the actual type
488    // in the object itself. This allows the conversion code
489    // to perform additional type checking.
490    out->dna_type = s.name.c_str();
491
492
493#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
494    ++db.stats().pointers_resolved;
495#endif
496    return false;
497}
498
499//--------------------------------------------------------------------------------
500const FileBlockHead* Structure :: LocateFileBlockForAddress(const Pointer & ptrval, const FileDatabase& db) const
501{
502    // the file blocks appear in list sorted by
503    // with ascending base addresses so we can run a
504    // binary search to locate the pointee quickly.
505
506    // NOTE: Blender seems to distinguish between side-by-side
507    // data (stored in the same data block) and far pointers,
508    // which are only used for structures starting with an ID.
509    // We don't need to make this distinction, our algorithm
510    // works regardless where the data is stored.
511    vector<FileBlockHead>::const_iterator it = std::lower_bound(db.entries.begin(),db.entries.end(),ptrval);
512    if (it == db.entries.end()) {
513        // this is crucial, pointers may not be invalid.
514        // this is either a corrupted file or an attempted attack.
515        throw DeadlyImportError((Formatter::format(),"Failure resolving pointer 0x",
516            std::hex,ptrval.val,", no file block falls into this address range"
517            ));
518    }
519    if (ptrval.val >= (*it).address.val + (*it).size) {
520        throw DeadlyImportError((Formatter::format(),"Failure resolving pointer 0x",
521            std::hex,ptrval.val,", nearest file block starting at 0x",
522            (*it).address.val," ends at 0x",
523            (*it).address.val + (*it).size
524            ));
525    }
526    return &*it;
527}
528
529// ------------------------------------------------------------------------------------------------
530// NOTE: The MSVC debugger keeps showing up this annoying `a cast to a smaller data type has
531// caused a loss of data`-warning. Avoid this warning by a masking with an appropriate bitmask.
532
533template <typename T> struct signless;
534template <> struct signless<char> {typedef unsigned char type;};
535template <> struct signless<short> {typedef unsigned short type;};
536template <> struct signless<int> {typedef unsigned int type;};
537template <> struct signless<unsigned char> { typedef unsigned char type; };
538template <typename T>
539struct static_cast_silent {
540    template <typename V>
541    T operator()(V in) {
542        return static_cast<T>(in & static_cast<typename signless<T>::type>(-1));
543    }
544};
545
546template <> struct static_cast_silent<float> {
547    template <typename V> float  operator()(V in) {
548        return static_cast<float> (in);
549    }
550};
551
552template <> struct static_cast_silent<double> {
553    template <typename V> double operator()(V in) {
554        return static_cast<double>(in);
555    }
556};
557
558// ------------------------------------------------------------------------------------------------
559template <typename T> inline void ConvertDispatcher(T& out, const Structure& in,const FileDatabase& db)
560{
561    if (in.name == "int") {
562        out = static_cast_silent<T>()(db.reader->GetU4());
563    }
564    else if (in.name == "short") {
565        out = static_cast_silent<T>()(db.reader->GetU2());
566    }
567    else if (in.name == "char") {
568        out = static_cast_silent<T>()(db.reader->GetU1());
569    }
570    else if (in.name == "float") {
571        out = static_cast<T>(db.reader->GetF4());
572    }
573    else if (in.name == "double") {
574        out = static_cast<T>(db.reader->GetF8());
575    }
576    else {
577        throw DeadlyImportError("Unknown source for conversion to primitive data type: "+in.name);
578    }
579}
580
581// ------------------------------------------------------------------------------------------------
582template <> inline void Structure :: Convert<int>    (int& dest,const FileDatabase& db) const
583{
584    ConvertDispatcher(dest,*this,db);
585}
586
587// ------------------------------------------------------------------------------------------------
588template<> inline void Structure :: Convert<short>  (short& dest,const FileDatabase& db) const
589{
590    // automatic rescaling from short to float and vice versa (seems to be used by normals)
591    if (name == "float") {
592        float f = db.reader->GetF4();
593        if ( f > 1.0f )
594            f = 1.0f;
595        dest = static_cast<short>( f * 32767.f);
596        //db.reader->IncPtr(-4);
597        return;
598    }
599    else if (name == "double") {
600        dest = static_cast<short>(db.reader->GetF8() * 32767.);
601        //db.reader->IncPtr(-8);
602        return;
603    }
604    ConvertDispatcher(dest,*this,db);
605}
606
607// ------------------------------------------------------------------------------------------------
608template <> inline void Structure :: Convert<char>   (char& dest,const FileDatabase& db) const
609{
610    // automatic rescaling from char to float and vice versa (seems useful for RGB colors)
611    if (name == "float") {
612        dest = static_cast<char>(db.reader->GetF4() * 255.f);
613        return;
614    }
615    else if (name == "double") {
616        dest = static_cast<char>(db.reader->GetF8() * 255.f);
617        return;
618    }
619    ConvertDispatcher(dest,*this,db);
620}
621
622// ------------------------------------------------------------------------------------------------
623template <> inline void Structure::Convert<unsigned char>(unsigned char& dest, const FileDatabase& db) const
624{
625	// automatic rescaling from char to float and vice versa (seems useful for RGB colors)
626	if (name == "float") {
627		dest = static_cast<unsigned char>(db.reader->GetF4() * 255.f);
628		return;
629	}
630	else if (name == "double") {
631		dest = static_cast<unsigned char>(db.reader->GetF8() * 255.f);
632		return;
633	}
634	ConvertDispatcher(dest, *this, db);
635}
636
637
638// ------------------------------------------------------------------------------------------------
639template <> inline void Structure :: Convert<float>  (float& dest,const FileDatabase& db) const
640{
641    // automatic rescaling from char to float and vice versa (seems useful for RGB colors)
642    if (name == "char") {
643        dest = db.reader->GetI1() / 255.f;
644        return;
645    }
646    // automatic rescaling from short to float and vice versa (used by normals)
647    else if (name == "short") {
648        dest = db.reader->GetI2() / 32767.f;
649        return;
650    }
651    ConvertDispatcher(dest,*this,db);
652}
653
654// ------------------------------------------------------------------------------------------------
655template <> inline void Structure :: Convert<double> (double& dest,const FileDatabase& db) const
656{
657    if (name == "char") {
658        dest = db.reader->GetI1() / 255.;
659        return;
660    }
661    else if (name == "short") {
662        dest = db.reader->GetI2() / 32767.;
663        return;
664    }
665    ConvertDispatcher(dest,*this,db);
666}
667
668// ------------------------------------------------------------------------------------------------
669template <> inline void Structure :: Convert<Pointer> (Pointer& dest,const FileDatabase& db) const
670{
671    if (db.i64bit) {
672        dest.val = db.reader->GetU8();
673        //db.reader->IncPtr(-8);
674        return;
675    }
676    dest.val = db.reader->GetU4();
677    //db.reader->IncPtr(-4);
678}
679
680//--------------------------------------------------------------------------------
681const Structure& DNA :: operator [] (const std::string& ss) const
682{
683    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
684    if (it == indices.end()) {
685        throw Error((Formatter::format(),
686            "BlendDNA: Did not find a structure named `",ss,"`"
687            ));
688    }
689
690    return structures[(*it).second];
691}
692
693//--------------------------------------------------------------------------------
694const Structure* DNA :: Get (const std::string& ss) const
695{
696    std::map<std::string, size_t>::const_iterator it = indices.find(ss);
697    return it == indices.end() ? NULL : &structures[(*it).second];
698}
699
700//--------------------------------------------------------------------------------
701const Structure& DNA :: operator [] (const size_t i) const
702{
703    if (i >= structures.size()) {
704        throw Error((Formatter::format(),
705            "BlendDNA: There is no structure with index `",i,"`"
706            ));
707    }
708
709    return structures[i];
710}
711
712//--------------------------------------------------------------------------------
713template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: get (
714    const Structure& s,
715    TOUT<T>& out,
716    const Pointer& ptr
717) const {
718
719    if(s.cache_idx == static_cast<size_t>(-1)) {
720        s.cache_idx = db.next_cache_idx++;
721        caches.resize(db.next_cache_idx);
722        return;
723    }
724
725    typename StructureCache::const_iterator it = caches[s.cache_idx].find(ptr);
726    if (it != caches[s.cache_idx].end()) {
727        out = std::static_pointer_cast<T>( (*it).second );
728
729#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
730        ++db.stats().cache_hits;
731#endif
732    }
733    // otherwise, out remains untouched
734}
735
736
737//--------------------------------------------------------------------------------
738template <template <typename> class TOUT> template <typename T> void ObjectCache<TOUT> :: set (
739    const Structure& s,
740    const TOUT<T>& out,
741    const Pointer& ptr
742) {
743    if(s.cache_idx == static_cast<size_t>(-1)) {
744        s.cache_idx = db.next_cache_idx++;
745        caches.resize(db.next_cache_idx);
746    }
747    caches[s.cache_idx][ptr] = std::static_pointer_cast<ElemBase>( out );
748
749#ifndef ASSIMP_BUILD_BLENDER_NO_STATS
750    ++db.stats().cached_objects;
751#endif
752}
753
754}}
755#endif
756