]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/model.cxx
Modified Files:
[simgear.git] / simgear / scene / model / model.cxx
1 // model.cxx - manage a 3D aircraft model.
2 // Written by David Megginson, started 2002.
3 //
4 // This file is in the Public Domain, and comes with no warranty.
5
6 #ifdef HAVE_CONFIG_H
7 #include <simgear_config.h>
8 #endif
9
10 #include <string.h>             // for strcmp()
11
12 #include <osg/observer_ptr>
13 #include <osg/ref_ptr>
14 #include <osg/Group>
15 #include <osg/NodeCallback>
16 #include <osg/Switch>
17 #include <osg/MatrixTransform>
18 #include <osgDB/Archive>
19 #include <osgDB/FileNameUtils>
20 #include <osgDB/FileUtils>
21 #include <osgDB/ReadFile>
22 #include <osgDB/Registry>
23 #include <osgDB/SharedStateManager>
24 #include <osgUtil/Optimizer>
25
26 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
27 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
28
29 #include <simgear/structure/exception.hxx>
30 #include <simgear/props/props.hxx>
31 #include <simgear/props/props_io.hxx>
32 #include <simgear/props/condition.hxx>
33
34 #include "animation.hxx"
35 #include "model.hxx"
36
37 SG_USING_STD(vector);
38 SG_USING_STD(set);
39
40 // Little helper class that holds an extra reference to a
41 // loaded 3d model.
42 // Since we clone all structural nodes from our 3d models,
43 // the database pager will only see one single reference to
44 // top node of the model and expire it relatively fast.
45 // We attach that extra reference to every model cloned from
46 // a base model in the pager. When that cloned model is deleted
47 // this extra reference is deleted too. So if there are no
48 // cloned models left the model will expire.
49 class SGDatabaseReference : public osg::Observer {
50 public:
51   SGDatabaseReference(osg::Referenced* referenced) :
52     mReferenced(referenced)
53   { }
54   virtual void objectDeleted(void*)
55   {
56     mReferenced = 0;
57   }
58 private:
59   osg::ref_ptr<osg::Referenced> mReferenced;
60 };
61
62 // Visitor for 
63 class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
64 public:
65   SGTextureUpdateVisitor(const osgDB::FilePathList& pathList) :
66     mPathList(pathList)
67   { }
68   osg::Texture2D* textureReplace(int unit, osg::StateSet::RefAttributePair& refAttr)
69   {
70     osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
71     if (!texture)
72       return 0;
73     
74     osg::ref_ptr<osg::Image> image = texture->getImage(0);
75     if (!image)
76       return 0;
77
78     // The currently loaded file name
79     std::string fullFilePath = image->getFileName();
80     // The short name
81     std::string fileName = osgDB::getSimpleFileName(fullFilePath);
82     // The name that should be found with the current database path
83     std::string fullLiveryFile = osgDB::findFileInPath(fileName, mPathList);
84     // If they are identical then there is nothing to do
85     if (fullLiveryFile == fullFilePath)
86       return 0;
87
88     image = osgDB::readImageFile(fullLiveryFile);
89     if (!image)
90       return 0;
91
92     osg::CopyOp copyOp(osg::CopyOp::DEEP_COPY_ALL &
93                        ~osg::CopyOp::DEEP_COPY_IMAGES);
94     texture = static_cast<osg::Texture2D*>(copyOp(texture));
95     if (!texture)
96       return 0;
97     texture->setImage(image.get());
98     return texture;
99   }
100   virtual void apply(osg::StateSet* stateSet)
101   {
102     if (!stateSet)
103       return;
104
105     // get a copy that we can safely modify the statesets values.
106     osg::StateSet::TextureAttributeList attrList;
107     attrList = stateSet->getTextureAttributeList();
108     for (unsigned unit = 0; unit < attrList.size(); ++unit) {
109       osg::StateSet::AttributeList::iterator i;
110       i = attrList[unit].begin();
111       while (i != attrList[unit].end()) {
112         osg::Texture2D* texture = textureReplace(unit, i->second);
113         if (texture) {
114           stateSet->removeTextureAttribute(unit, i->second.first.get());
115           stateSet->setTextureAttribute(unit, texture, i->second.second);
116           stateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON);
117         }
118         ++i;
119       }
120     }
121   }
122
123 private:
124   osgDB::FilePathList mPathList;
125 };
126
127 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
128 public:
129   SGTexCompressionVisitor(osg::Texture::InternalFormatMode formatMode) :
130     mFormatMode(formatMode)
131   { }
132
133   virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
134   {
135     osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
136     if (!texture)
137       return;
138     
139     osg::Image* image = texture->getImage(0);
140     if (!image)
141       return;
142
143     int s = image->s();
144     int t = image->t();
145     if (s <= t && 32 <= s) {
146       texture->setInternalFormatMode(mFormatMode);
147     } else if (t < s && 32 <= t) {
148       texture->setInternalFormatMode(mFormatMode);
149     }
150   }
151
152 private:
153   osg::Texture::InternalFormatMode mFormatMode;
154 };
155
156 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
157 public:
158   virtual void apply(osg::StateSet::RefAttributePair& refAttr)
159   {
160     osg::Material* material = dynamic_cast<osg::Material*>(refAttr.first.get());
161     if (!material)
162       return;
163     material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
164   }
165 };
166
167 class SGReadFileCallback :
168   public osgDB::Registry::ReadFileCallback {
169 public:
170   virtual osgDB::ReaderWriter::ReadResult
171   readImage(const std::string& fileName, const osgDB::ReaderWriter::Options* opt)
172   {
173     std::string absFileName = osgDB::findDataFile(fileName);
174     if (!osgDB::fileExists(absFileName)) {
175       SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
176              << fileName << "\"");
177       return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
178     }
179
180     osgDB::Registry* registry = osgDB::Registry::instance();
181     osgDB::ReaderWriter::ReadResult res = registry->readImageImplementation(absFileName, opt);
182     if (res.loadedFromCache())
183       SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
184              << res.getImage()->getFileName() << "\"");
185     else
186       SG_LOG(SG_IO, SG_INFO, "Reading image \""
187              << res.getImage()->getFileName() << "\"");
188
189     return res;
190   }
191
192   virtual osgDB::ReaderWriter::ReadResult
193   readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* opt)
194   {
195     std::string absFileName = osgDB::findDataFile(fileName);
196     if (!osgDB::fileExists(absFileName)) {
197       SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
198              << fileName << "\"");
199       return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
200     }
201
202     osgDB::Registry* registry = osgDB::Registry::instance();
203     osgDB::ReaderWriter::ReadResult res;
204     res = registry->readNodeImplementation(absFileName, opt);
205     if (!res.validNode())
206       return res;
207
208     if (res.loadedFromCache()) {
209       SG_LOG(SG_IO, SG_INFO, "Returning cached model \""
210              << absFileName << "\"");
211     } else {
212       SG_LOG(SG_IO, SG_INFO, "Reading model \""
213              << absFileName << "\"");
214
215       bool needTristrip = true;
216       if (osgDB::getLowerCaseFileExtension(absFileName) == "ac") {
217         // we get optimal geometry from the loader.
218         needTristrip = false;
219         osg::Matrix m(1, 0, 0, 0,
220                       0, 0, 1, 0,
221                       0, -1, 0, 0,
222                       0, 0, 0, 1);
223         
224         osg::ref_ptr<osg::Group> root = new osg::Group;
225         osg::MatrixTransform* transform = new osg::MatrixTransform;
226         root->addChild(transform);
227         
228         transform->setDataVariance(osg::Object::STATIC);
229         transform->setMatrix(m);
230         transform->addChild(res.getNode());
231         
232         res = osgDB::ReaderWriter::ReadResult(0);
233         
234         osgUtil::Optimizer optimizer;
235         unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
236         optimizer.optimize(root.get(), opts);
237         
238         // Ok, this step is questionable.
239         // It is there to have the same visual appearance of ac objects for the
240         // first cut. Osg's ac3d loader will correctly set materials from the
241         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
242         // materials that in effect igored the ambient part specified in the
243         // file. We emulate that for the first cut here by changing all ac models
244         // here. But in the long term we should use the unchanged model and fix
245         // the input files instead ...
246         SGAcMaterialCrippleVisitor matCriple;
247         root->accept(matCriple);
248         
249         res = osgDB::ReaderWriter::ReadResult(root.get());
250       }
251       
252       osgUtil::Optimizer optimizer;
253       unsigned opts = 0;
254       // Don't use this one. It will break animation names ...
255       // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
256
257       // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
258       // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
259       // opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
260       opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
261       // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
262       // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
263       // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
264       if (needTristrip)
265         opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
266       // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
267       opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
268       optimizer.optimize(res.getNode(), opts);
269
270       // OSGFIXME
271       registry->getSharedStateManager()->share(res.getNode());
272       
273       // OSGFIXME: guard that with a flag
274       // OSGFIXME: in the long term it is unclear if we have an OpenGL context here...
275       osg::Texture::Extensions* e = osg::Texture::getExtensions(0, true);
276       if (e->isTextureCompressionARBSupported()) {
277         SGTexCompressionVisitor texComp(osg::Texture::USE_ARB_COMPRESSION);
278         res.getNode()->accept(texComp);
279       } else if (e->isTextureCompressionS3TCSupported()) {
280         SGTexCompressionVisitor texComp(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
281         res.getNode()->accept(texComp);
282       }
283     }
284
285     // Add an extra reference to the model stored in the database.
286     // That it to avoid expiring the object from the cache even if it is still
287     // in use. Note that the object cache will think that a model is unused if the
288     // reference count is 1. If we clone all structural nodes here we need that extra
289     // reference to the original object
290     SGDatabaseReference* databaseReference = new SGDatabaseReference(res.getNode());
291     osg::CopyOp::CopyFlags flags = osg::CopyOp::DEEP_COPY_ALL;
292     flags &= ~osg::CopyOp::DEEP_COPY_TEXTURES;
293     flags &= ~osg::CopyOp::DEEP_COPY_IMAGES;
294     flags &= ~osg::CopyOp::DEEP_COPY_ARRAYS;
295     flags &= ~osg::CopyOp::DEEP_COPY_PRIMITIVES;
296     // This will safe display lists ...
297     flags &= ~osg::CopyOp::DEEP_COPY_DRAWABLES;
298     flags &= ~osg::CopyOp::DEEP_COPY_SHAPES;
299     res = osgDB::ReaderWriter::ReadResult(osg::CopyOp(flags)(res.getNode()));
300     res.getNode()->addObserver(databaseReference);
301
302     SGTextureUpdateVisitor liveryUpdate(osgDB::getDataFilePathList());
303     res.getNode()->accept(liveryUpdate);
304
305     // OSGFIXME: don't forget that mutex here
306     registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
307
308     return res;
309   }
310 };
311
312 class SGReadCallbackInstaller {
313 public:
314   SGReadCallbackInstaller()
315   {
316     osg::Referenced::setThreadSafeReferenceCounting(true);
317
318     osgDB::Registry* registry = osgDB::Registry::instance();
319     osgDB::ReaderWriter::Options* options = new osgDB::ReaderWriter::Options;
320     options->setObjectCacheHint(osgDB::ReaderWriter::Options::CACHE_ALL);
321     registry->setOptions(options);
322     registry->getOrCreateSharedStateManager()->setShareMode(osgDB::SharedStateManager::SHARE_TEXTURES);
323     registry->setReadFileCallback(new SGReadFileCallback);
324   }
325 };
326
327 static SGReadCallbackInstaller readCallbackInstaller;
328
329 osg::Texture2D*
330 SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int)
331 {
332   osg::Image* image = osgDB::readImageFile(path);
333   osg::Texture2D* texture = new osg::Texture2D;
334   texture->setImage(image);
335   if (wrapu)
336     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
337   else
338     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
339   if (wrapv)
340     texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
341   else
342     texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
343
344   if (image) {
345     int s = image->s();
346     int t = image->t();
347
348     // OSGFIXME: guard with a flag
349     if (osg::Texture::getExtensions(0, true)->isTextureCompressionARBSupported()) {
350       if (s <= t && 32 <= s) {
351         texture->setInternalFormatMode(osg::Texture::USE_ARB_COMPRESSION);
352       } else if (t < s && 32 <= t) {
353         texture->setInternalFormatMode(osg::Texture::USE_ARB_COMPRESSION);
354       }
355     } else if (osg::Texture::getExtensions(0, true)->isTextureCompressionS3TCSupported()) {
356       if (s <= t && 32 <= s) {
357         texture->setInternalFormatMode(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
358       } else if (t < s && 32 <= t) {
359         texture->setInternalFormatMode(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
360       }
361     }
362   }
363   return texture;
364 }
365
366 class SGSwitchUpdateCallback : public osg::NodeCallback {
367 public:
368   SGSwitchUpdateCallback(SGCondition* condition) :
369     mCondition(condition) {}
370   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
371   { 
372     assert(dynamic_cast<osg::Switch*>(node));
373     osg::Switch* s = static_cast<osg::Switch*>(node);
374
375     if (mCondition && mCondition->test()) {
376       s->setAllChildrenOn();
377       // note, callback is responsible for scenegraph traversal so
378       // should always include call traverse(node,nv) to ensure 
379       // that the rest of cullbacks and the scene graph are traversed.
380       traverse(node, nv);
381     } else
382       s->setAllChildrenOff();
383   }
384
385 private:
386   SGCondition* mCondition;
387 };
388
389 /**
390  * Locate a named node in a branch.
391  */
392 class NodeFinder : public osg::NodeVisitor {
393 public:
394   NodeFinder(const std::string& nameToFind) :
395     osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
396                      osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
397     mName(nameToFind),
398     mNode(0)
399   { }
400   virtual void apply(osg::Node& node)
401   {
402     if (mNode)
403       return;
404     if (mName == node.getName()) {
405       mNode = &node;
406       return;
407     }
408     traverse(node);
409   }
410
411   osg::Node* getNode() const
412   { return mNode; }
413 private:
414   std::string mName;
415   osg::Node* mNode;
416 };
417
418 /**
419  * Splice a branch in between all child nodes and their parents.
420  */
421 static void
422 splice_branch(osg::Group* group, osg::Node* child)
423 {
424   osg::Node::ParentList parents = child->getParents();
425   group->addChild(child);
426   osg::Node::ParentList::iterator i;
427   for (i = parents.begin(); i != parents.end(); ++i)
428     (*i)->replaceChild(child, group);
429 }
430
431 void
432 sgMakeAnimation( osg::Node * model,
433                  const char * name,
434                  vector<SGPropertyNode_ptr> &name_nodes,
435                  SGPropertyNode *prop_root,
436                  SGPropertyNode_ptr node,
437                  double sim_time_sec,
438                  SGPath &texture_path,
439                  set<osg::Node*> &ignore_branches )
440 {
441   bool ignore = false;
442   SGAnimation * animation = 0;
443   const char * type = node->getStringValue("type", "none");
444   if (!strcmp("none", type)) {
445     animation = new SGNullAnimation(node);
446   } else if (!strcmp("range", type)) {
447     animation = new SGRangeAnimation(prop_root, node);
448   } else if (!strcmp("billboard", type)) {
449     animation = new SGBillboardAnimation(node);
450   } else if (!strcmp("select", type)) {
451     animation = new SGSelectAnimation(prop_root, node);
452   } else if (!strcmp("spin", type)) {
453     animation = new SGSpinAnimation(prop_root, node, sim_time_sec );
454   } else if (!strcmp("timed", type)) {
455     animation = new SGTimedAnimation(node);
456   } else if (!strcmp("rotate", type)) {
457     animation = new SGRotateAnimation(prop_root, node);
458   } else if (!strcmp("translate", type)) {
459     animation = new SGTranslateAnimation(prop_root, node);
460   } else if (!strcmp("scale", type)) {
461     animation = new SGScaleAnimation(prop_root, node);
462   } else if (!strcmp("texrotate", type)) {
463     animation = new SGTexRotateAnimation(prop_root, node);
464   } else if (!strcmp("textranslate", type)) {
465     animation = new SGTexTranslateAnimation(prop_root, node);
466   } else if (!strcmp("texmultiple", type)) {
467     animation = new SGTexMultipleAnimation(prop_root, node);
468   } else if (!strcmp("blend", type)) {
469     animation = new SGBlendAnimation(prop_root, node);
470     ignore = true;
471   } else if (!strcmp("alpha-test", type)) {
472     animation = new SGAlphaTestAnimation(node);
473   } else if (!strcmp("material", type)) {
474     animation = new SGMaterialAnimation(prop_root, node, texture_path);
475   } else if (!strcmp("flash", type)) {
476     animation = new SGFlashAnimation(node);
477   } else if (!strcmp("dist-scale", type)) {
478     animation = new SGDistScaleAnimation(node);
479   } else if (!strcmp("noshadow", type)) {
480     animation = new SGShadowAnimation(prop_root, node);
481   } else if (!strcmp("shader", type)) {
482     animation = new SGShaderAnimation(prop_root, node);
483   } else {
484     animation = new SGNullAnimation(node);
485     SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type);
486   }
487
488   if (name != 0)
489       animation->setName(name);
490
491   osg::Node * object = 0;
492   if (!name_nodes.empty()) {
493     const char * name = name_nodes[0]->getStringValue();
494     NodeFinder nodeFinder(name);
495     model->accept(nodeFinder);
496     object = nodeFinder.getNode();
497     if (object == 0) {
498       SG_LOG(SG_INPUT, SG_ALERT, "Object " << name << " not found");
499       delete animation;
500       animation = 0;
501     }
502   } else {
503     object = model;
504   }
505
506   if ( animation == 0 )
507      return;
508
509   osg::Group* branch = animation->getBranch();
510   splice_branch(branch, object);
511
512   for (unsigned int i = 1; i < name_nodes.size(); i++) {
513       const char * name = name_nodes[i]->getStringValue();
514       NodeFinder nodeFinder(name);
515       model->accept(nodeFinder);
516       object = nodeFinder.getNode();
517       if (object == 0) {
518           SG_LOG(SG_INPUT, SG_ALERT, "Object " << name << " not found");
519           delete animation;
520           animation = 0;
521       } else {
522           osg::Group* oldParent = object->getParent(0);
523           branch->addChild(object);
524           oldParent->removeChild(object);
525       }
526   }
527
528   if ( animation != 0 ) {
529     animation->init();
530     branch->setUpdateCallback(animation);
531     if ( ignore ) {
532       ignore_branches.insert( branch );
533     }
534   }
535 }
536
537 \f
538 ////////////////////////////////////////////////////////////////////////
539 // Global functions.
540 ////////////////////////////////////////////////////////////////////////
541
542 osg::Node *
543 sgLoad3DModel( const string &fg_root, const string &path,
544                SGPropertyNode *prop_root,
545                double sim_time_sec, osg::Node *(*load_panel)(SGPropertyNode *),
546                SGModelData *data,
547                const SGPath& externalTexturePath )
548 {
549   osg::Switch* model = 0;
550   SGPropertyNode props;
551
552                                 // Load the 3D aircraft object itself
553   SGPath modelpath = path, texturepath = path;
554   if ( !ulIsAbsolutePathName( path.c_str() ) ) {
555     SGPath tmp = fg_root;
556     tmp.append(modelpath.str());
557     modelpath = texturepath = tmp;
558   }
559
560                                 // Check for an XML wrapper
561   if (modelpath.str().substr(modelpath.str().size() - 4, 4) == ".xml") {
562     readProperties(modelpath.str(), &props);
563     if (props.hasValue("/path")) {
564       modelpath = modelpath.dir();
565       modelpath.append(props.getStringValue("/path"));
566       if (props.hasValue("/texture-path")) {
567         texturepath = texturepath.dir();
568         texturepath.append(props.getStringValue("/texture-path"));
569       }
570     } else {
571       if (model == 0)
572         model = new osg::Switch;
573     }
574   }
575
576   osgDB::FilePathList pathList = osgDB::getDataFilePathList();
577   osgDB::Registry::instance()->initFilePathLists();
578
579                                 // Assume that textures are in
580                                 // the same location as the XML file.
581   if (model == 0) {
582     if (texturepath.extension() != "")
583           texturepath = texturepath.dir();
584
585     osgDB::Registry::instance()->getDataFilePathList().push_front(texturepath.str());
586
587     osg::Node* node = osgDB::readNodeFile(modelpath.str());
588     if (node == 0)
589       throw sg_io_exception("Failed to load 3D model", 
590                             sg_location(modelpath.str()));
591     model = new osg::Switch;
592     model->addChild(node, true);
593   }
594
595   osgDB::Registry::instance()->getDataFilePathList().push_front(externalTexturePath.str());
596
597                                 // Set up the alignment node
598   osg::MatrixTransform* alignmainmodel = new osg::MatrixTransform;
599   alignmainmodel->addChild(model);
600   osg::Matrix res_matrix;
601   res_matrix.makeRotate(
602     props.getFloatValue("/offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
603     osg::Vec3(0, 0, 1),
604     props.getFloatValue("/offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
605     osg::Vec3(1, 0, 0),
606     props.getFloatValue("/offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
607     osg::Vec3(0, 1, 0));
608
609   osg::Matrix tmat;
610   tmat.makeTranslate(props.getFloatValue("/offsets/x-m", 0.0),
611                      props.getFloatValue("/offsets/y-m", 0.0),
612                      props.getFloatValue("/offsets/z-m", 0.0));
613   alignmainmodel->setMatrix(res_matrix*tmat);
614
615   unsigned int i;
616
617                                 // Load sub-models
618   vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
619   for (i = 0; i < model_nodes.size(); i++) {
620     SGPropertyNode_ptr node = model_nodes[i];
621     osg::MatrixTransform* align = new osg::MatrixTransform;
622     res_matrix.makeIdentity();
623     res_matrix.makeRotate(
624       node->getFloatValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
625       osg::Vec3(0, 0, 1),
626       node->getFloatValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
627       osg::Vec3(1, 0, 0),
628       node->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
629       osg::Vec3(0, 1, 0));
630     
631     tmat.makeIdentity();
632     tmat.makeTranslate(node->getFloatValue("offsets/x-m", 0.0),
633                        node->getFloatValue("offsets/y-m", 0.0),
634                        node->getFloatValue("offsets/z-m", 0.0));
635     align->setMatrix(res_matrix*tmat);
636
637     osg::Node* kid;
638     const char * submodel = node->getStringValue("path");
639     try {
640       kid = sgLoad3DModel( fg_root, submodel, prop_root, sim_time_sec, load_panel );
641
642     } catch (const sg_throwable &t) {
643       SG_LOG(SG_INPUT, SG_ALERT, "Failed to load submodel: " << t.getFormattedMessage());
644       throw;
645     }
646     align->addChild(kid);
647
648     align->setName(node->getStringValue("name", ""));
649     model->addChild(align);
650
651     SGPropertyNode *cond = node->getNode("condition", false);
652     if (cond)
653       model->setUpdateCallback(new SGSwitchUpdateCallback(sgReadCondition(prop_root, cond)));
654   }
655
656   if ( load_panel ) {
657                                 // Load panels
658     vector<SGPropertyNode_ptr> panel_nodes = props.getChildren("panel");
659     for (i = 0; i < panel_nodes.size(); i++) {
660         SG_LOG(SG_INPUT, SG_DEBUG, "Loading a panel");
661         osg::Node * panel = load_panel(panel_nodes[i]);
662         if (panel_nodes[i]->hasValue("name"))
663             panel->setName((char *)panel_nodes[i]->getStringValue("name"));
664         model->addChild(panel);
665     }
666   }
667
668   if (data) {
669     alignmainmodel->setUserData(data);
670     data->modelLoaded(path, &props, alignmainmodel);
671   }
672                                 // Load animations
673   set<osg::Node*> ignore_branches;
674   vector<SGPropertyNode_ptr> animation_nodes = props.getChildren("animation");
675   for (i = 0; i < animation_nodes.size(); i++) {
676     const char * name = animation_nodes[i]->getStringValue("name", 0);
677     vector<SGPropertyNode_ptr> name_nodes =
678       animation_nodes[i]->getChildren("object-name");
679     sgMakeAnimation( model, name, name_nodes, prop_root, animation_nodes[i],
680                      sim_time_sec, texturepath, ignore_branches);
681   }
682
683   // restore old path list
684   osgDB::setDataFilePathList(pathList);
685
686   return alignmainmodel;
687 }
688
689 // end of model.cxx