]> 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       if (osgDB::getLowerCaseFileExtension(absFileName) == "ac") {
216         osg::Matrix m(1, 0, 0, 0,
217                       0, 0, 1, 0,
218                       0, -1, 0, 0,
219                       0, 0, 0, 1);
220         
221         osg::ref_ptr<osg::Group> root = new osg::Group;
222         osg::MatrixTransform* transform = new osg::MatrixTransform;
223         root->addChild(transform);
224         
225         transform->setDataVariance(osg::Object::STATIC);
226         transform->setMatrix(m);
227         transform->addChild(res.getNode());
228         
229         res = osgDB::ReaderWriter::ReadResult(0);
230         
231         osgUtil::Optimizer optimizer;
232         unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
233         optimizer.optimize(root.get(), opts);
234         
235         // Ok, this step is questionable.
236         // It is there to have the same visual appearance of ac objects for the
237         // first cut. Osg's ac3d loader will correctly set materials from the
238         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
239         // materials that in effect igored the ambient part specified in the
240         // file. We emulate that for the first cut here by changing all ac models
241         // here. But in the long term we should use the unchanged model and fix
242         // the input files instead ...
243         SGAcMaterialCrippleVisitor matCriple;
244         root->accept(matCriple);
245         
246         res = osgDB::ReaderWriter::ReadResult(root.get());
247       }
248       
249       osgUtil::Optimizer optimizer;
250       unsigned opts = 0;
251       // Don't use this one. It will break animation names ...
252       // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
253       
254       // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
255       // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
256       // opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
257       opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
258       // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
259       // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
260       // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
261       opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
262       // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
263       // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
264       optimizer.optimize(res.getNode(), opts);
265
266       // OSGFIXME
267       registry->getSharedStateManager()->share(res.getNode());
268       
269       // OSGFIXME: guard that with a flag
270       // OSGFIXME: in the long term it is unclear if we have an OpenGL context here...
271       osg::Texture::Extensions* e = osg::Texture::getExtensions(0, true);
272       if (e->isTextureCompressionARBSupported()) {
273         SGTexCompressionVisitor texComp(osg::Texture::USE_ARB_COMPRESSION);
274         res.getNode()->accept(texComp);
275       } else if (e->isTextureCompressionS3TCSupported()) {
276         SGTexCompressionVisitor texComp(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
277         res.getNode()->accept(texComp);
278       }
279     }
280
281     // Add an extra reference to the model stored in the database.
282     // That it to avoid expiring the object from the cache even if it is still
283     // in use. Note that the object cache will think that a model is unused if the
284     // reference count is 1. If we clone all structural nodes here we need that extra
285     // reference to the original object
286     SGDatabaseReference* databaseReference = new SGDatabaseReference(res.getNode());
287     osg::CopyOp::CopyFlags flags = osg::CopyOp::DEEP_COPY_ALL;
288     flags &= ~osg::CopyOp::DEEP_COPY_TEXTURES;
289     flags &= ~osg::CopyOp::DEEP_COPY_IMAGES;
290     flags &= ~osg::CopyOp::DEEP_COPY_ARRAYS;
291     flags &= ~osg::CopyOp::DEEP_COPY_PRIMITIVES;
292     // This will safe display lists ...
293     flags &= ~osg::CopyOp::DEEP_COPY_DRAWABLES;
294     flags &= ~osg::CopyOp::DEEP_COPY_SHAPES;
295     res = osgDB::ReaderWriter::ReadResult(osg::CopyOp(flags)(res.getNode()));
296     res.getNode()->addObserver(databaseReference);
297
298     SGTextureUpdateVisitor liveryUpdate(osgDB::getDataFilePathList());
299     res.getNode()->accept(liveryUpdate);
300
301     // OSGFIXME: don't forget that mutex here
302     registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
303
304     return res;
305   }
306 };
307
308 class SGReadCallbackInstaller {
309 public:
310   SGReadCallbackInstaller()
311   {
312     osg::Referenced::setThreadSafeReferenceCounting(true);
313
314     osgDB::Registry* registry = osgDB::Registry::instance();
315     osgDB::ReaderWriter::Options* options = new osgDB::ReaderWriter::Options;
316     options->setObjectCacheHint(osgDB::ReaderWriter::Options::CACHE_ALL);
317     registry->setOptions(options);
318     registry->getOrCreateSharedStateManager()->setShareMode(osgDB::SharedStateManager::SHARE_TEXTURES);
319     registry->setReadFileCallback(new SGReadFileCallback);
320   }
321 };
322
323 static SGReadCallbackInstaller readCallbackInstaller;
324
325 osg::Texture2D*
326 SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int)
327 {
328   osg::Image* image = osgDB::readImageFile(path);
329   osg::Texture2D* texture = new osg::Texture2D;
330   texture->setImage(image);
331   if (wrapu)
332     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
333   else
334     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
335   if (wrapv)
336     texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
337   else
338     texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
339
340   if (image) {
341     int s = image->s();
342     int t = image->t();
343
344     // OSGFIXME: guard with a flag
345     if (osg::Texture::getExtensions(0, true)->isTextureCompressionARBSupported()) {
346       if (s <= t && 32 <= s) {
347         texture->setInternalFormatMode(osg::Texture::USE_ARB_COMPRESSION);
348       } else if (t < s && 32 <= t) {
349         texture->setInternalFormatMode(osg::Texture::USE_ARB_COMPRESSION);
350       }
351     } else if (osg::Texture::getExtensions(0, true)->isTextureCompressionS3TCSupported()) {
352       if (s <= t && 32 <= s) {
353         texture->setInternalFormatMode(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
354       } else if (t < s && 32 <= t) {
355         texture->setInternalFormatMode(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
356       }
357     }
358   }
359   return texture;
360 }
361
362 class SGSwitchUpdateCallback : public osg::NodeCallback {
363 public:
364   SGSwitchUpdateCallback(SGCondition* condition) :
365     mCondition(condition) {}
366   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
367   { 
368     assert(dynamic_cast<osg::Switch*>(node));
369     osg::Switch* s = static_cast<osg::Switch*>(node);
370
371     if (mCondition && mCondition->test()) {
372       s->setAllChildrenOn();
373       // note, callback is responsible for scenegraph traversal so
374       // should always include call traverse(node,nv) to ensure 
375       // that the rest of cullbacks and the scene graph are traversed.
376       traverse(node, nv);
377     } else
378       s->setAllChildrenOff();
379   }
380
381 private:
382   SGCondition* mCondition;
383 };
384
385 /**
386  * Locate a named node in a branch.
387  */
388 class NodeFinder : public osg::NodeVisitor {
389 public:
390   NodeFinder(const std::string& nameToFind) :
391     osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
392                      osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
393     mName(nameToFind),
394     mNode(0)
395   { }
396   virtual void apply(osg::Node& node)
397   {
398     if (mNode)
399       return;
400     if (mName == node.getName()) {
401       mNode = &node;
402       return;
403     }
404     traverse(node);
405   }
406
407   osg::Node* getNode() const
408   { return mNode; }
409 private:
410   std::string mName;
411   osg::Node* mNode;
412 };
413
414 /**
415  * Splice a branch in between all child nodes and their parents.
416  */
417 static void
418 splice_branch(osg::Group* group, osg::Node* child)
419 {
420   osg::Node::ParentList parents = child->getParents();
421   group->addChild(child);
422   osg::Node::ParentList::iterator i;
423   for (i = parents.begin(); i != parents.end(); ++i)
424     (*i)->replaceChild(child, group);
425 }
426
427 void
428 sgMakeAnimation( osg::Node * model,
429                  const char * name,
430                  vector<SGPropertyNode_ptr> &name_nodes,
431                  SGPropertyNode *prop_root,
432                  SGPropertyNode_ptr node,
433                  double sim_time_sec,
434                  SGPath &texture_path,
435                  set<osg::Node*> &ignore_branches )
436 {
437   bool ignore = false;
438   SGAnimation * animation = 0;
439   const char * type = node->getStringValue("type", "none");
440   if (!strcmp("none", type)) {
441     animation = new SGNullAnimation(node);
442   } else if (!strcmp("range", type)) {
443     animation = new SGRangeAnimation(prop_root, node);
444   } else if (!strcmp("billboard", type)) {
445     animation = new SGBillboardAnimation(node);
446   } else if (!strcmp("select", type)) {
447     animation = new SGSelectAnimation(prop_root, node);
448   } else if (!strcmp("spin", type)) {
449     animation = new SGSpinAnimation(prop_root, node, sim_time_sec );
450   } else if (!strcmp("timed", type)) {
451     animation = new SGTimedAnimation(node);
452   } else if (!strcmp("rotate", type)) {
453     animation = new SGRotateAnimation(prop_root, node);
454   } else if (!strcmp("translate", type)) {
455     animation = new SGTranslateAnimation(prop_root, node);
456   } else if (!strcmp("scale", type)) {
457     animation = new SGScaleAnimation(prop_root, node);
458   } else if (!strcmp("texrotate", type)) {
459     animation = new SGTexRotateAnimation(prop_root, node);
460   } else if (!strcmp("textranslate", type)) {
461     animation = new SGTexTranslateAnimation(prop_root, node);
462   } else if (!strcmp("texmultiple", type)) {
463     animation = new SGTexMultipleAnimation(prop_root, node);
464   } else if (!strcmp("blend", type)) {
465     animation = new SGBlendAnimation(prop_root, node);
466     ignore = true;
467   } else if (!strcmp("alpha-test", type)) {
468     animation = new SGAlphaTestAnimation(node);
469   } else if (!strcmp("material", type)) {
470     animation = new SGMaterialAnimation(prop_root, node, texture_path);
471   } else if (!strcmp("flash", type)) {
472     animation = new SGFlashAnimation(node);
473   } else if (!strcmp("dist-scale", type)) {
474     animation = new SGDistScaleAnimation(node);
475   } else if (!strcmp("noshadow", type)) {
476     animation = new SGShadowAnimation(prop_root, node);
477   } else if (!strcmp("shader", type)) {
478     animation = new SGShaderAnimation(prop_root, node);
479   } else {
480     animation = new SGNullAnimation(node);
481     SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type);
482   }
483
484   if (name != 0)
485       animation->setName(name);
486
487   osg::Node * object = 0;
488   if (!name_nodes.empty()) {
489     const char * name = name_nodes[0]->getStringValue();
490     NodeFinder nodeFinder(name);
491     model->accept(nodeFinder);
492     object = nodeFinder.getNode();
493     if (object == 0) {
494       SG_LOG(SG_INPUT, SG_ALERT, "Object " << name << " not found");
495       delete animation;
496       animation = 0;
497     }
498   } else {
499     object = model;
500   }
501
502   if ( animation == 0 )
503      return;
504
505   osg::Group* branch = animation->getBranch();
506   splice_branch(branch, object);
507
508   for (unsigned int i = 1; i < name_nodes.size(); i++) {
509       const char * name = name_nodes[i]->getStringValue();
510       NodeFinder nodeFinder(name);
511       model->accept(nodeFinder);
512       object = nodeFinder.getNode();
513       if (object == 0) {
514           SG_LOG(SG_INPUT, SG_ALERT, "Object " << name << " not found");
515           delete animation;
516           animation = 0;
517       } else {
518           osg::Group* oldParent = object->getParent(0);
519           branch->addChild(object);
520           oldParent->removeChild(object);
521       }
522   }
523
524   if ( animation != 0 ) {
525     animation->init();
526     branch->setUpdateCallback(animation);
527     if ( ignore ) {
528       ignore_branches.insert( branch );
529     }
530   }
531 }
532
533 \f
534 ////////////////////////////////////////////////////////////////////////
535 // Global functions.
536 ////////////////////////////////////////////////////////////////////////
537
538 osg::Node *
539 sgLoad3DModel( const string &fg_root, const string &path,
540                SGPropertyNode *prop_root,
541                double sim_time_sec, osg::Node *(*load_panel)(SGPropertyNode *),
542                SGModelData *data,
543                const SGPath& externalTexturePath )
544 {
545   osg::Switch* model = 0;
546   SGPropertyNode props;
547
548                                 // Load the 3D aircraft object itself
549   SGPath modelpath = path, texturepath = path;
550   if ( !ulIsAbsolutePathName( path.c_str() ) ) {
551     SGPath tmp = fg_root;
552     tmp.append(modelpath.str());
553     modelpath = texturepath = tmp;
554   }
555
556                                 // Check for an XML wrapper
557   if (modelpath.str().substr(modelpath.str().size() - 4, 4) == ".xml") {
558     readProperties(modelpath.str(), &props);
559     if (props.hasValue("/path")) {
560       modelpath = modelpath.dir();
561       modelpath.append(props.getStringValue("/path"));
562       if (props.hasValue("/texture-path")) {
563         texturepath = texturepath.dir();
564         texturepath.append(props.getStringValue("/texture-path"));
565       }
566     } else {
567       if (model == 0)
568         model = new osg::Switch;
569     }
570   }
571
572   osgDB::FilePathList pathList = osgDB::getDataFilePathList();
573   osgDB::Registry::instance()->initFilePathLists();
574
575                                 // Assume that textures are in
576                                 // the same location as the XML file.
577   if (model == 0) {
578     if (texturepath.extension() != "")
579           texturepath = texturepath.dir();
580
581     osgDB::Registry::instance()->getDataFilePathList().push_front(texturepath.str());
582
583     osg::Node* node = osgDB::readNodeFile(modelpath.str());
584     if (node == 0)
585       throw sg_io_exception("Failed to load 3D model", 
586                             sg_location(modelpath.str()));
587     model = new osg::Switch;
588     model->addChild(node, true);
589   }
590
591   osgDB::Registry::instance()->getDataFilePathList().push_front(externalTexturePath.str());
592
593                                 // Set up the alignment node
594   osg::MatrixTransform* alignmainmodel = new osg::MatrixTransform;
595   alignmainmodel->addChild(model);
596   osg::Matrix res_matrix;
597   res_matrix.makeRotate(
598     props.getFloatValue("/offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
599     osg::Vec3(0, 0, 1),
600     props.getFloatValue("/offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
601     osg::Vec3(1, 0, 0),
602     props.getFloatValue("/offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
603     osg::Vec3(0, 1, 0));
604
605   osg::Matrix tmat;
606   tmat.makeTranslate(props.getFloatValue("/offsets/x-m", 0.0),
607                      props.getFloatValue("/offsets/y-m", 0.0),
608                      props.getFloatValue("/offsets/z-m", 0.0));
609   alignmainmodel->setMatrix(res_matrix*tmat);
610
611   unsigned int i;
612
613                                 // Load sub-models
614   vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
615   for (i = 0; i < model_nodes.size(); i++) {
616     SGPropertyNode_ptr node = model_nodes[i];
617     osg::MatrixTransform* align = new osg::MatrixTransform;
618     res_matrix.makeIdentity();
619     res_matrix.makeRotate(
620       node->getFloatValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
621       osg::Vec3(0, 0, 1),
622       node->getFloatValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
623       osg::Vec3(1, 0, 0),
624       node->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
625       osg::Vec3(0, 1, 0));
626     
627     tmat.makeIdentity();
628     tmat.makeTranslate(node->getFloatValue("offsets/x-m", 0.0),
629                        node->getFloatValue("offsets/y-m", 0.0),
630                        node->getFloatValue("offsets/z-m", 0.0));
631     align->setMatrix(res_matrix*tmat);
632
633     osg::Node* kid;
634     const char * submodel = node->getStringValue("path");
635     try {
636       kid = sgLoad3DModel( fg_root, submodel, prop_root, sim_time_sec, load_panel );
637
638     } catch (const sg_throwable &t) {
639       SG_LOG(SG_INPUT, SG_ALERT, "Failed to load submodel: " << t.getFormattedMessage());
640       throw;
641     }
642     align->addChild(kid);
643
644     align->setName(node->getStringValue("name", ""));
645     model->addChild(align);
646
647     SGPropertyNode *cond = node->getNode("condition", false);
648     if (cond)
649       model->setUpdateCallback(new SGSwitchUpdateCallback(sgReadCondition(prop_root, cond)));
650   }
651
652   if ( load_panel ) {
653                                 // Load panels
654     vector<SGPropertyNode_ptr> panel_nodes = props.getChildren("panel");
655     for (i = 0; i < panel_nodes.size(); i++) {
656         SG_LOG(SG_INPUT, SG_DEBUG, "Loading a panel");
657         osg::Node * panel = load_panel(panel_nodes[i]);
658         if (panel_nodes[i]->hasValue("name"))
659             panel->setName((char *)panel_nodes[i]->getStringValue("name"));
660         model->addChild(panel);
661     }
662   }
663
664   if (data) {
665     alignmainmodel->setUserData(data);
666     data->modelLoaded(path, &props, alignmainmodel);
667   }
668                                 // Load animations
669   set<osg::Node*> ignore_branches;
670   vector<SGPropertyNode_ptr> animation_nodes = props.getChildren("animation");
671   for (i = 0; i < animation_nodes.size(); i++) {
672     const char * name = animation_nodes[i]->getStringValue("name", 0);
673     vector<SGPropertyNode_ptr> name_nodes =
674       animation_nodes[i]->getChildren("object-name");
675     sgMakeAnimation( model, name, name_nodes, prop_root, animation_nodes[i],
676                      sim_time_sec, texturepath, ignore_branches);
677   }
678
679   // restore old path list
680   osgDB::setDataFilePathList(pathList);
681
682   return alignmainmodel;
683 }
684
685 // end of model.cxx