]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/model/model.cxx
Improove texture sharing.
[simgear.git] / simgear / scene / model / model.cxx
index acc8a1f81e1a9ea07a4ef6944bb18e8b80754f21..749b45c3f7d2a023a85f9b908f01a0c0c00e8db8 100644 (file)
@@ -7,8 +7,6 @@
 #include <simgear_config.h>
 #endif
 
-#include <string.h>             // for strcmp()
-
 #include <osg/observer_ptr>
 #include <osg/ref_ptr>
 #include <osg/Group>
 #include <osgDB/FileNameUtils>
 #include <osgDB/FileUtils>
 #include <osgDB/ReadFile>
+#include <osgDB/WriteFile>
 #include <osgDB/Registry>
 #include <osgDB/SharedStateManager>
 #include <osgUtil/Optimizer>
 
+#include <simgear/scene/util/SGSceneFeatures.hxx>
 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
 
@@ -65,9 +65,11 @@ public:
   SGTextureUpdateVisitor(const osgDB::FilePathList& pathList) :
     mPathList(pathList)
   { }
-  osg::Texture2D* textureReplace(int unit, osg::StateSet::RefAttributePair& refAttr)
+  osg::Texture2D* textureReplace(int unit,
+                                 osg::StateSet::RefAttributePair& refAttr)
   {
-    osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
+    osg::Texture2D* texture;
+    texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
     if (!texture)
       return 0;
     
@@ -113,7 +115,8 @@ public:
         if (texture) {
           stateSet->removeTextureAttribute(unit, i->second.first.get());
           stateSet->setTextureAttribute(unit, texture, i->second.second);
-          stateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON);
+          stateSet->setTextureMode(unit, GL_TEXTURE_2D,
+                                   osg::StateAttribute::ON);
         }
         ++i;
       }
@@ -126,38 +129,50 @@ private:
 
 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
 public:
-  SGTexCompressionVisitor(osg::Texture::InternalFormatMode formatMode) :
-    mFormatMode(formatMode)
-  { }
-
   virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
   {
-    osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
+    osg::Texture2D* texture;
+    texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
     if (!texture)
       return;
     
+    // Hmm, true??
+    texture->setDataVariance(osg::Object::STATIC);
+
     osg::Image* image = texture->getImage(0);
     if (!image)
       return;
 
     int s = image->s();
     int t = image->t();
+
     if (s <= t && 32 <= s) {
-      texture->setInternalFormatMode(mFormatMode);
+      SGSceneFeatures::instance()->setTextureCompression(texture);
     } else if (t < s && 32 <= t) {
-      texture->setInternalFormatMode(mFormatMode);
+      SGSceneFeatures::instance()->setTextureCompression(texture);
     }
   }
+};
 
-private:
-  osg::Texture::InternalFormatMode mFormatMode;
+class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
+public:
+  virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
+  {
+    osg::Texture* texture;
+    texture = dynamic_cast<osg::Texture*>(refAttr.first.get());
+    if (!texture)
+      return;
+    
+    texture->setDataVariance(osg::Object::STATIC);
+  }
 };
 
 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
 public:
   virtual void apply(osg::StateSet::RefAttributePair& refAttr)
   {
-    osg::Material* material = dynamic_cast<osg::Material*>(refAttr.first.get());
+    osg::Material* material;
+    material = dynamic_cast<osg::Material*>(refAttr.first.get());
     if (!material)
       return;
     material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
@@ -168,7 +183,8 @@ class SGReadFileCallback :
   public osgDB::Registry::ReadFileCallback {
 public:
   virtual osgDB::ReaderWriter::ReadResult
-  readImage(const std::string& fileName, const osgDB::ReaderWriter::Options* opt)
+  readImage(const std::string& fileName,
+            const osgDB::ReaderWriter::Options* opt)
   {
     std::string absFileName = osgDB::findDataFile(fileName);
     if (!osgDB::fileExists(absFileName)) {
@@ -178,7 +194,8 @@ public:
     }
 
     osgDB::Registry* registry = osgDB::Registry::instance();
-    osgDB::ReaderWriter::ReadResult res = registry->readImageImplementation(absFileName, opt);
+    osgDB::ReaderWriter::ReadResult res;
+    res = registry->readImageImplementation(absFileName, opt);
     if (res.loadedFromCache())
       SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
              << res.getImage()->getFileName() << "\"");
@@ -190,29 +207,52 @@ public:
   }
 
   virtual osgDB::ReaderWriter::ReadResult
-  readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* opt)
+  readNode(const std::string& fileName,
+           const osgDB::ReaderWriter::Options* opt)
   {
-    std::string absFileName = osgDB::findDataFile(fileName);
+    osgDB::Registry* registry = osgDB::Registry::instance();
+    osgDB::ReaderWriter::ReadResult res;
+    osg::Node* cached = 0;
+    // The BTG loader automatically looks for ".btg.gz" if a file with
+    // the .btg extension doesn't exist. Also, we don't want to add
+    // nodes, run the optimizer, etc. on the btg model.So, let it do
+    // its thing.
+    if (osgDB::equalCaseInsensitive(osgDB::getFileExtension(fileName), "btg")) {
+      return registry->readNodeImplementation(fileName, opt);
+    }
+    // First, look for a file with the same name, and the extension
+    // ".osg" and, if it exists, load it instead. This allows for
+    // substitution of optimized models for ones named in the scenery.
+    bool optimizeModel = true;
+    std::string fileSansExtension = osgDB::getNameLessExtension(fileName);
+    std::string osgFileName = fileSansExtension + ".osg";
+    std::string absFileName = osgDB::findDataFile(osgFileName);
+    if (osgDB::fileExists(absFileName)) {
+      optimizeModel = false;
+    } else {
+      absFileName = osgDB::findDataFile(fileName);
+    }
     if (!osgDB::fileExists(absFileName)) {
       SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
              << fileName << "\"");
       return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
     }
-
-    osgDB::Registry* registry = osgDB::Registry::instance();
-    osgDB::ReaderWriter::ReadResult res;
-    res = registry->readNodeImplementation(absFileName, opt);
-    if (!res.validNode())
-      return res;
-
-    if (res.loadedFromCache()) {
-      SG_LOG(SG_IO, SG_INFO, "Returning cached model \""
+    cached
+        = dynamic_cast<osg::Node*>(registry->getFromObjectCache(absFileName));
+    if (cached) {
+      SG_LOG(SG_IO, SG_INFO, "Got cached model \""
              << absFileName << "\"");
     } else {
       SG_LOG(SG_IO, SG_INFO, "Reading model \""
              << absFileName << "\"");
-
-      if (osgDB::getLowerCaseFileExtension(absFileName) == "ac") {
+      res = registry->readNodeImplementation(absFileName, opt);
+      if (!res.validNode())
+        return res;
+
+      bool needTristrip = true;
+      if (osgDB::getLowerCaseFileExtension(fileName) == "ac") {
+        // we get optimal geometry from the loader.
+        needTristrip = false;
         osg::Matrix m(1, 0, 0, 0,
                       0, 0, 1, 0,
                       0, -1, 0, 0,
@@ -227,63 +267,69 @@ public:
         transform->addChild(res.getNode());
         
         res = osgDB::ReaderWriter::ReadResult(0);
-        
-        osgUtil::Optimizer optimizer;
-        unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
-        optimizer.optimize(root.get(), opts);
+
+        if (optimizeModel) {
+          osgUtil::Optimizer optimizer;
+          unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
+          optimizer.optimize(root.get(), opts);
+        }
+
+        // strip away unneeded groups
+        if (root->getNumChildren() == 1 && root->getName().empty()) {
+          res = osgDB::ReaderWriter::ReadResult(root->getChild(0));
+        } else
+          res = osgDB::ReaderWriter::ReadResult(root.get());
         
         // Ok, this step is questionable.
         // It is there to have the same visual appearance of ac objects for the
         // first cut. Osg's ac3d loader will correctly set materials from the
         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
         // materials that in effect igored the ambient part specified in the
-        // file. We emulate that for the first cut here by changing all ac models
-        // here. But in the long term we should use the unchanged model and fix
-        // the input files instead ...
+        // file. We emulate that for the first cut here by changing all
+        // ac models here. But in the long term we should use the
+        // unchanged model and fix the input files instead ...
         SGAcMaterialCrippleVisitor matCriple;
-        root->accept(matCriple);
-        
-        res = osgDB::ReaderWriter::ReadResult(root.get());
+        res.getNode()->accept(matCriple);
       }
-      
-      osgUtil::Optimizer optimizer;
-      unsigned opts = 0;
-      // Don't use this one. It will break animation names ...
-      // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
-      
-      // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
-      // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
-      // opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
-      opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
-      // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
-      // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
-      // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
-      opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
-      // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
-      // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
-      optimizer.optimize(res.getNode(), opts);
-
-      // OSGFIXME
+
+      if (optimizeModel) {
+        osgUtil::Optimizer optimizer;
+        unsigned opts = 0;
+        // Don't use this one. It will break animation names ...
+        // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
+
+        // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
+        // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
+        // opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
+        opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
+        // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
+        // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
+        // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
+        opts |= osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
+        if (needTristrip)
+          opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
+        // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
+        // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
+        optimizer.optimize(res.getNode(), opts);
+      }
+      // Make sure the data variance of sharable objects is set to STATIC ...
+      SGTexDataVarianceVisitor dataVarianceVisitor;
+      res.getNode()->accept(dataVarianceVisitor);
+      // ... so that textures are now globally shared
       registry->getSharedStateManager()->share(res.getNode());
       
-      // OSGFIXME: guard that with a flag
-      // OSGFIXME: in the long term it is unclear if we have an OpenGL context here...
-      osg::Texture::Extensions* e = osg::Texture::getExtensions(0, true);
-      if (e->isTextureCompressionARBSupported()) {
-        SGTexCompressionVisitor texComp(osg::Texture::USE_ARB_COMPRESSION);
-        res.getNode()->accept(texComp);
-      } else if (e->isTextureCompressionS3TCSupported()) {
-        SGTexCompressionVisitor texComp(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
-        res.getNode()->accept(texComp);
-      }
+      SGTexCompressionVisitor texComp;
+      res.getNode()->accept(texComp);
+      cached = res.getNode();
+      registry->addEntryToObjectCache(absFileName, cached);
     }
-
     // Add an extra reference to the model stored in the database.
     // That it to avoid expiring the object from the cache even if it is still
-    // in use. Note that the object cache will think that a model is unused if the
-    // reference count is 1. If we clone all structural nodes here we need that extra
-    // reference to the original object
-    SGDatabaseReference* databaseReference = new SGDatabaseReference(res.getNode());
+    // in use. Note that the object cache will think that a model is unused
+    // if the reference count is 1. If we clone all structural nodes here
+    // we need that extra reference to the original object
+    SGDatabaseReference* databaseReference;
+    databaseReference = new SGDatabaseReference(cached);
     osg::CopyOp::CopyFlags flags = osg::CopyOp::DEEP_COPY_ALL;
     flags &= ~osg::CopyOp::DEEP_COPY_TEXTURES;
     flags &= ~osg::CopyOp::DEEP_COPY_IMAGES;
@@ -292,13 +338,17 @@ public:
     // This will safe display lists ...
     flags &= ~osg::CopyOp::DEEP_COPY_DRAWABLES;
     flags &= ~osg::CopyOp::DEEP_COPY_SHAPES;
-    res = osgDB::ReaderWriter::ReadResult(osg::CopyOp(flags)(res.getNode()));
+    res = osgDB::ReaderWriter::ReadResult(osg::CopyOp(flags)(cached));
     res.getNode()->addObserver(databaseReference);
 
+    // Update liveries
     SGTextureUpdateVisitor liveryUpdate(osgDB::getDataFilePathList());
     res.getNode()->accept(liveryUpdate);
 
-    // OSGFIXME: don't forget that mutex here
+    // Make sure the data variance of sharable objects is set to STATIC ...
+    SGTexDataVarianceVisitor dataVarianceVisitor;
+    res.getNode()->accept(dataVarianceVisitor);
+    // ... so that textures are now globally shared
     registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
 
     return res;
@@ -313,7 +363,11 @@ public:
 
     osgDB::Registry* registry = osgDB::Registry::instance();
     osgDB::ReaderWriter::Options* options = new osgDB::ReaderWriter::Options;
-    options->setObjectCacheHint(osgDB::ReaderWriter::Options::CACHE_ALL);
+    // We manage node caching ourselves
+    int cacheOptions = osgDB::ReaderWriter::Options::CACHE_ALL
+      & ~osgDB::ReaderWriter::Options::CACHE_NODES;
+    options->
+      setObjectCacheHint((osgDB::ReaderWriter::Options::CacheHintOptions)cacheOptions);
     registry->setOptions(options);
     registry->getOrCreateSharedStateManager()->setShareMode(osgDB::SharedStateManager::SHARE_TEXTURES);
     registry->setReadFileCallback(new SGReadFileCallback);
@@ -326,8 +380,9 @@ osg::Texture2D*
 SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int)
 {
   osg::Image* image = osgDB::readImageFile(path);
-  osg::Texture2D* texture = new osg::Texture2D;
+  osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
   texture->setImage(image);
+  texture->setDataVariance(osg::Object::STATIC);
   if (wrapu)
     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
   else
@@ -341,22 +396,34 @@ SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int)
     int s = image->s();
     int t = image->t();
 
-    // OSGFIXME: guard with a flag
-    if (osg::Texture::getExtensions(0, true)->isTextureCompressionARBSupported()) {
-      if (s <= t && 32 <= s) {
-        texture->setInternalFormatMode(osg::Texture::USE_ARB_COMPRESSION);
-      } else if (t < s && 32 <= t) {
-        texture->setInternalFormatMode(osg::Texture::USE_ARB_COMPRESSION);
-      }
-    } else if (osg::Texture::getExtensions(0, true)->isTextureCompressionS3TCSupported()) {
-      if (s <= t && 32 <= s) {
-        texture->setInternalFormatMode(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
-      } else if (t < s && 32 <= t) {
-        texture->setInternalFormatMode(osg::Texture::USE_S3TC_DXT5_COMPRESSION);
-      }
+    if (s <= t && 32 <= s) {
+      SGSceneFeatures::instance()->setTextureCompression(texture.get());
+    } else if (t < s && 32 <= t) {
+      SGSceneFeatures::instance()->setTextureCompression(texture.get());
     }
   }
-  return texture;
+
+  // Make sure the texture is shared if we already have the same texture
+  // somewhere ...
+  {
+    osg::ref_ptr<osg::Node> tmpNode = new osg::Node;
+    osg::StateSet* stateSet = tmpNode->getOrCreateStateSet();
+    stateSet->setTextureAttribute(0, texture.get());
+
+    // OSGFIXME: don't forget that mutex here
+    osgDB::Registry* registry = osgDB::Registry::instance();
+    registry->getSharedStateManager()->share(tmpNode.get(), 0);
+
+    // should be the same, but be paranoid ...
+    stateSet = tmpNode->getStateSet();
+    osg::StateAttribute* stateAttr;
+    stateAttr = stateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE);
+    osg::Texture2D* texture2D = dynamic_cast<osg::Texture2D*>(stateAttr);
+    if (texture2D)
+      texture = texture2D;
+  }
+
+  return texture.release();
 }
 
 class SGSwitchUpdateCallback : public osg::NodeCallback {
@@ -379,157 +446,9 @@ public:
   }
 
 private:
-  SGCondition* mCondition;
-};
-
-/**
- * Locate a named node in a branch.
- */
-class NodeFinder : public osg::NodeVisitor {
-public:
-  NodeFinder(const std::string& nameToFind) :
-    osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
-                     osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
-    mName(nameToFind),
-    mNode(0)
-  { }
-  virtual void apply(osg::Node& node)
-  {
-    if (mNode)
-      return;
-    if (mName == node.getName()) {
-      mNode = &node;
-      return;
-    }
-    traverse(node);
-  }
-
-  osg::Node* getNode() const
-  { return mNode; }
-private:
-  std::string mName;
-  osg::Node* mNode;
+  SGSharedPtr<SGCondition> mCondition;
 };
 
-/**
- * Splice a branch in between all child nodes and their parents.
- */
-static void
-splice_branch(osg::Group* group, osg::Node* child)
-{
-  osg::Node::ParentList parents = child->getParents();
-  group->addChild(child);
-  osg::Node::ParentList::iterator i;
-  for (i = parents.begin(); i != parents.end(); ++i)
-    (*i)->replaceChild(child, group);
-}
-
-void
-sgMakeAnimation( osg::Node * model,
-                 const char * name,
-                 vector<SGPropertyNode_ptr> &name_nodes,
-                 SGPropertyNode *prop_root,
-                 SGPropertyNode_ptr node,
-                 double sim_time_sec,
-                 SGPath &texture_path,
-                 set<osg::Node*> &ignore_branches )
-{
-  bool ignore = false;
-  SGAnimation * animation = 0;
-  const char * type = node->getStringValue("type", "none");
-  if (!strcmp("none", type)) {
-    animation = new SGNullAnimation(node);
-  } else if (!strcmp("range", type)) {
-    animation = new SGRangeAnimation(prop_root, node);
-  } else if (!strcmp("billboard", type)) {
-    animation = new SGBillboardAnimation(node);
-  } else if (!strcmp("select", type)) {
-    animation = new SGSelectAnimation(prop_root, node);
-  } else if (!strcmp("spin", type)) {
-    animation = new SGSpinAnimation(prop_root, node, sim_time_sec );
-  } else if (!strcmp("timed", type)) {
-    animation = new SGTimedAnimation(node);
-  } else if (!strcmp("rotate", type)) {
-    animation = new SGRotateAnimation(prop_root, node);
-  } else if (!strcmp("translate", type)) {
-    animation = new SGTranslateAnimation(prop_root, node);
-  } else if (!strcmp("scale", type)) {
-    animation = new SGScaleAnimation(prop_root, node);
-  } else if (!strcmp("texrotate", type)) {
-    animation = new SGTexRotateAnimation(prop_root, node);
-  } else if (!strcmp("textranslate", type)) {
-    animation = new SGTexTranslateAnimation(prop_root, node);
-  } else if (!strcmp("texmultiple", type)) {
-    animation = new SGTexMultipleAnimation(prop_root, node);
-  } else if (!strcmp("blend", type)) {
-    animation = new SGBlendAnimation(prop_root, node);
-    ignore = true;
-  } else if (!strcmp("alpha-test", type)) {
-    animation = new SGAlphaTestAnimation(node);
-  } else if (!strcmp("material", type)) {
-    animation = new SGMaterialAnimation(prop_root, node, texture_path);
-  } else if (!strcmp("flash", type)) {
-    animation = new SGFlashAnimation(node);
-  } else if (!strcmp("dist-scale", type)) {
-    animation = new SGDistScaleAnimation(node);
-  } else if (!strcmp("noshadow", type)) {
-    animation = new SGShadowAnimation(prop_root, node);
-  } else if (!strcmp("shader", type)) {
-    animation = new SGShaderAnimation(prop_root, node);
-  } else {
-    animation = new SGNullAnimation(node);
-    SG_LOG(SG_INPUT, SG_WARN, "Unknown animation type " << type);
-  }
-
-  if (name != 0)
-      animation->setName(name);
-
-  osg::Node * object = 0;
-  if (!name_nodes.empty()) {
-    const char * name = name_nodes[0]->getStringValue();
-    NodeFinder nodeFinder(name);
-    model->accept(nodeFinder);
-    object = nodeFinder.getNode();
-    if (object == 0) {
-      SG_LOG(SG_INPUT, SG_ALERT, "Object " << name << " not found");
-      delete animation;
-      animation = 0;
-    }
-  } else {
-    object = model;
-  }
-
-  if ( animation == 0 )
-     return;
-
-  osg::Group* branch = animation->getBranch();
-  splice_branch(branch, object);
-
-  for (unsigned int i = 1; i < name_nodes.size(); i++) {
-      const char * name = name_nodes[i]->getStringValue();
-      NodeFinder nodeFinder(name);
-      model->accept(nodeFinder);
-      object = nodeFinder.getNode();
-      if (object == 0) {
-          SG_LOG(SG_INPUT, SG_ALERT, "Object " << name << " not found");
-          delete animation;
-          animation = 0;
-      } else {
-          osg::Group* oldParent = object->getParent(0);
-          branch->addChild(object);
-          oldParent->removeChild(object);
-      }
-  }
-
-  if ( animation != 0 ) {
-    animation->init();
-    branch->setUpdateCallback(animation);
-    if ( ignore ) {
-      ignore_branches.insert( branch );
-    }
-  }
-}
-
 \f
 ////////////////////////////////////////////////////////////////////////
 // Global functions.
@@ -542,10 +461,10 @@ sgLoad3DModel( const string &fg_root, const string &path,
                SGModelData *data,
                const SGPath& externalTexturePath )
 {
-  osg::Switch* model = 0;
+  osg::ref_ptr<osg::Node> model;
   SGPropertyNode props;
 
-                                // Load the 3D aircraft object itself
+  // Load the 3D aircraft object itself
   SGPath modelpath = path, texturepath = path;
   if ( !ulIsAbsolutePathName( path.c_str() ) ) {
     SGPath tmp = fg_root;
@@ -553,7 +472,7 @@ sgLoad3DModel( const string &fg_root, const string &path,
     modelpath = texturepath = tmp;
   }
 
-                                // Check for an XML wrapper
+  // Check for an XML wrapper
   if (modelpath.str().substr(modelpath.str().size() - 4, 4) == ".xml") {
     readProperties(modelpath.str(), &props);
     if (props.hasValue("/path")) {
@@ -564,7 +483,7 @@ sgLoad3DModel( const string &fg_root, const string &path,
         texturepath.append(props.getStringValue("/texture-path"));
       }
     } else {
-      if (model == 0)
+      if (!model)
         model = new osg::Switch;
     }
   }
@@ -572,35 +491,33 @@ sgLoad3DModel( const string &fg_root, const string &path,
   osgDB::FilePathList pathList = osgDB::getDataFilePathList();
   osgDB::Registry::instance()->initFilePathLists();
 
-                                // Assume that textures are in
-                                // the same location as the XML file.
-  if (model == 0) {
+  // Assume that textures are in
+  // the same location as the XML file.
+  if (!model) {
     if (texturepath.extension() != "")
           texturepath = texturepath.dir();
 
     osgDB::Registry::instance()->getDataFilePathList().push_front(texturepath.str());
 
-    osg::Node* node = osgDB::readNodeFile(modelpath.str());
-    if (node == 0)
+    model = osgDB::readNodeFile(modelpath.str());
+    if (model == 0)
       throw sg_io_exception("Failed to load 3D model", 
                             sg_location(modelpath.str()));
-    model = new osg::Switch;
-    model->addChild(node, true);
   }
 
   osgDB::Registry::instance()->getDataFilePathList().push_front(externalTexturePath.str());
 
-                                // Set up the alignment node
-  osg::MatrixTransform* alignmainmodel = new osg::MatrixTransform;
-  alignmainmodel->addChild(model);
+  // Set up the alignment node
+  osg::ref_ptr<osg::MatrixTransform> alignmainmodel = new osg::MatrixTransform;
+  alignmainmodel->addChild(model.get());
   osg::Matrix res_matrix;
   res_matrix.makeRotate(
-    props.getFloatValue("/offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
-    osg::Vec3(0, 0, 1),
+    props.getFloatValue("/offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
+    osg::Vec3(0, 1, 0),
     props.getFloatValue("/offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
     osg::Vec3(1, 0, 0),
-    props.getFloatValue("/offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
-    osg::Vec3(0, 1, 0));
+    props.getFloatValue("/offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
+    osg::Vec3(0, 0, 1));
 
   osg::Matrix tmat;
   tmat.makeTranslate(props.getFloatValue("/offsets/x-m", 0.0),
@@ -608,30 +525,28 @@ sgLoad3DModel( const string &fg_root, const string &path,
                      props.getFloatValue("/offsets/z-m", 0.0));
   alignmainmodel->setMatrix(res_matrix*tmat);
 
-  unsigned int i;
-
-                                // Load sub-models
+  // Load sub-models
   vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
-  for (i = 0; i < model_nodes.size(); i++) {
+  for (unsigned i = 0; i < model_nodes.size(); i++) {
     SGPropertyNode_ptr node = model_nodes[i];
-    osg::MatrixTransform* align = new osg::MatrixTransform;
+    osg::ref_ptr<osg::MatrixTransform> align = new osg::MatrixTransform;
     res_matrix.makeIdentity();
     res_matrix.makeRotate(
-      node->getFloatValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
-      osg::Vec3(0, 0, 1),
-      node->getFloatValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
+      node->getDoubleValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
+      osg::Vec3(0, 1, 0),
+      node->getDoubleValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
       osg::Vec3(1, 0, 0),
-      node->getFloatValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
-      osg::Vec3(0, 1, 0));
+      node->getDoubleValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
+      osg::Vec3(0, 0, 1));
     
     tmat.makeIdentity();
-    tmat.makeTranslate(node->getFloatValue("offsets/x-m", 0.0),
-                       node->getFloatValue("offsets/y-m", 0.0),
-                       node->getFloatValue("offsets/z-m", 0.0));
+    tmat.makeTranslate(node->getDoubleValue("offsets/x-m", 0),
+                       node->getDoubleValue("offsets/y-m", 0),
+                       node->getDoubleValue("offsets/z-m", 0));
     align->setMatrix(res_matrix*tmat);
 
-    osg::Node* kid;
-    const char * submodel = node->getStringValue("path");
+    osg::ref_ptr<osg::Node> kid;
+    const char* submodel = node->getStringValue("path");
     try {
       kid = sgLoad3DModel( fg_root, submodel, prop_root, sim_time_sec, load_panel );
 
@@ -639,47 +554,55 @@ sgLoad3DModel( const string &fg_root, const string &path,
       SG_LOG(SG_INPUT, SG_ALERT, "Failed to load submodel: " << t.getFormattedMessage());
       throw;
     }
-    align->addChild(kid);
+    align->addChild(kid.get());
 
     align->setName(node->getStringValue("name", ""));
-    model->addChild(align);
 
     SGPropertyNode *cond = node->getNode("condition", false);
-    if (cond)
-      model->setUpdateCallback(new SGSwitchUpdateCallback(sgReadCondition(prop_root, cond)));
+    if (cond) {
+      osg::ref_ptr<osg::Switch> sw = new osg::Switch;
+      sw->setUpdateCallback(new SGSwitchUpdateCallback(sgReadCondition(prop_root, cond)));
+      alignmainmodel->addChild(sw.get());
+      sw->addChild(align.get());
+      sw->setName("submodel condition switch");
+    } else {
+      alignmainmodel->addChild(align.get());
+    }
   }
 
   if ( load_panel ) {
-                                // Load panels
+    // Load panels
     vector<SGPropertyNode_ptr> panel_nodes = props.getChildren("panel");
-    for (i = 0; i < panel_nodes.size(); i++) {
+    for (unsigned i = 0; i < panel_nodes.size(); i++) {
         SG_LOG(SG_INPUT, SG_DEBUG, "Loading a panel");
-        osg::Node * panel = load_panel(panel_nodes[i]);
+        osg::ref_ptr<osg::Node> panel = load_panel(panel_nodes[i]);
         if (panel_nodes[i]->hasValue("name"))
             panel->setName((char *)panel_nodes[i]->getStringValue("name"));
-        model->addChild(panel);
+        alignmainmodel->addChild(panel.get());
     }
   }
 
   if (data) {
     alignmainmodel->setUserData(data);
-    data->modelLoaded(path, &props, alignmainmodel);
-  }
-                                // Load animations
-  set<osg::Node*> ignore_branches;
-  vector<SGPropertyNode_ptr> animation_nodes = props.getChildren("animation");
-  for (i = 0; i < animation_nodes.size(); i++) {
-    const char * name = animation_nodes[i]->getStringValue("name", 0);
-    vector<SGPropertyNode_ptr> name_nodes =
-      animation_nodes[i]->getChildren("object-name");
-    sgMakeAnimation( model, name, name_nodes, prop_root, animation_nodes[i],
-                     sim_time_sec, texturepath, ignore_branches);
+    data->modelLoaded(path, &props, alignmainmodel.get());
   }
 
+  std::vector<SGPropertyNode_ptr> animation_nodes;
+  animation_nodes = props.getChildren("animation");
+  for (unsigned i = 0; i < animation_nodes.size(); ++i)
+    /// OSGFIXME: duh, why not only model?????
+    SGAnimation::animate(alignmainmodel.get(), animation_nodes[i], prop_root);
+
   // restore old path list
   osgDB::setDataFilePathList(pathList);
 
-  return alignmainmodel;
+  if (props.hasChild("debug-outfile")) {
+    std::string outputfile = props.getStringValue("debug-outfile",
+                                                  "debug-model.osg");
+    osgDB::writeNodeFile(*alignmainmodel, outputfile);
+  }
+
+  return alignmainmodel.release();
 }
 
 // end of model.cxx