]> git.mxchange.org Git - simgear.git/blobdiff - simgear/scene/model/ModelRegistry.cxx
Build boundingvolumes in the model loading phase.
[simgear.git] / simgear / scene / model / ModelRegistry.cxx
index a4cef6abab10f086dbd586edb99e2cb24f98f302..7c2afff648f3e10ab7aabf85770f3d22a02cf367 100644 (file)
@@ -19,6 +19,8 @@
 #include "ModelRegistry.hxx"
 
 #include <algorithm>
+#include <utility>
+#include <vector>
 
 #include <OpenThreads/ScopedLock>
 
 #include <simgear/scene/util/SGSceneFeatures.hxx>
 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
+#include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
 
 #include <simgear/structure/exception.hxx>
 #include <simgear/props/props.hxx>
 #include <simgear/props/props_io.hxx>
 #include <simgear/props/condition.hxx>
 
+#include "BoundingVolumeBuildVisitor.hxx"
+
 using namespace std;
 using namespace osg;
 using namespace osgUtil;
 using namespace osgDB;
 using namespace simgear;
 
+using OpenThreads::ReentrantMutex;
+using OpenThreads::ScopedLock;
+
 // Little helper class that holds an extra reference to a
 // loaded 3d model.
 // Since we clone all structural nodes from our 3d models,
@@ -76,71 +84,144 @@ private:
   ref_ptr<Referenced> mReferenced;
 };
 
-// Visitor for 
-class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
+// Set the name of a Texture to the simple name of its image
+// file. This can be used to do livery substitution after the image
+// has been deallocated.
+class TextureNameVisitor  : public NodeAndDrawableVisitor {
 public:
-  SGTextureUpdateVisitor(const FilePathList& pathList) :
-    mPathList(pathList)
-  { }
-  Texture2D* textureReplace(int unit,
-                            StateSet::RefAttributePair& refAttr)
-  {
-    Texture2D* texture;
-    texture = dynamic_cast<Texture2D*>(refAttr.first.get());
-    if (!texture)
-      return 0;
-    
-    ref_ptr<Image> image = texture->getImage(0);
-    if (!image)
-      return 0;
-
-    // The currently loaded file name
-    string fullFilePath = image->getFileName();
-    // The short name
-    string fileName = getSimpleFileName(fullFilePath);
-    // The name that should be found with the current database path
-    string fullLiveryFile = findFileInPath(fileName, mPathList);
-    // If they are identical then there is nothing to do
-    if (fullLiveryFile == fullFilePath)
-      return 0;
-
-    image = readImageFile(fullLiveryFile);
-    if (!image)
-      return 0;
+    TextureNameVisitor(NodeVisitor::TraversalMode tm = NodeVisitor::TRAVERSE_ALL_CHILDREN) :
+        NodeAndDrawableVisitor(tm)
+    {
+    }
 
-    CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
-    texture = static_cast<Texture2D*>(copyOp(texture));
-    if (!texture)
-      return 0;
-    texture->setImage(image.get());
-    return texture;
-  }
-  virtual void apply(StateSet* stateSet)
-  {
-    if (!stateSet)
-      return;
+    virtual void apply(Node& node)
+    {
+        nameTextures(node.getStateSet());
+        traverse(node);
+    }
 
-    // get a copy that we can safely modify the statesets values.
-    StateSet::TextureAttributeList attrList;
-    attrList = stateSet->getTextureAttributeList();
-    for (unsigned unit = 0; unit < attrList.size(); ++unit) {
-      StateSet::AttributeList::iterator i = attrList[unit].begin();
-      while (i != attrList[unit].end()) {
-        Texture2D* texture = textureReplace(unit, i->second);
-        if (texture) {
-          stateSet->removeTextureAttribute(unit, i->second.first.get());
-          stateSet->setTextureAttribute(unit, texture, i->second.second);
-          stateSet->setTextureMode(unit, GL_TEXTURE_2D, StateAttribute::ON);
+    virtual void apply(Drawable& drawable)
+    {
+        nameTextures(drawable.getStateSet());
+    }
+protected:
+    void nameTextures(StateSet* stateSet)
+    {
+        if (!stateSet)
+            return;
+        int numUnits = stateSet->getTextureAttributeList().size();
+        for (int i = 0; i < numUnits; ++i) {
+            StateAttribute* attr
+                = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
+            Texture2D* texture = dynamic_cast<Texture2D*>(attr);
+            if (!texture || !texture->getName().empty())
+                continue;
+            const Image *image = texture->getImage();
+            if (!image)
+                continue;
+            texture->setName(image->getFileName());
         }
-        ++i;
-      }
     }
-  }
+};
+
+// Change the StateSets of a model to hold different textures based on
+// a livery path.
+
+class TextureUpdateVisitor : public NodeAndDrawableVisitor {
+public:
+    TextureUpdateVisitor(const FilePathList& pathList) :
+        NodeAndDrawableVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
+        _pathList(pathList)
+    {
+    }
+    
+    virtual void apply(Node& node)
+    {
+        StateSet* stateSet = cloneStateSet(node.getStateSet());
+        if (stateSet)
+            node.setStateSet(stateSet);
+        traverse(node);
+    }
+
+    virtual void apply(Drawable& drawable)
+    {
+        StateSet* stateSet = cloneStateSet(drawable.getStateSet());
+        if (stateSet)
+            drawable.setStateSet(stateSet);
+    }
+    // Copied from Mathias' earlier SGTextureUpdateVisitor
+protected:
+    Texture2D* textureReplace(int unit, const StateAttribute* attr)
+    {
+        const Texture2D* texture = dynamic_cast<const Texture2D*>(attr);
 
+        if (!texture)
+            return 0;
+    
+        const Image* image = texture->getImage();
+        const string* fullFilePath = 0;
+        if (image) {
+            // The currently loaded file name
+            fullFilePath = &image->getFileName();
+
+        } else {
+            fullFilePath = &texture->getName();
+        }
+        // The short name
+        string fileName = getSimpleFileName(*fullFilePath);
+        if (fileName.empty())
+            return 0;
+        // The name that should be found with the current database path
+        string fullLiveryFile = findFileInPath(fileName, _pathList);
+        // If it is empty or they are identical then there is nothing to do
+        if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath)
+            return 0;
+        Image* newImage = readImageFile(fullLiveryFile);
+        if (!newImage)
+            return 0;
+        CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
+        Texture2D* newTexture = static_cast<Texture2D*>(copyOp(texture));
+        if (!newTexture) {
+            return 0;
+        } else {
+            newTexture->setImage(newImage);
+            return newTexture;
+        }
+    }
+    
+    StateSet* cloneStateSet(const StateSet* stateSet)
+    {
+        typedef pair<int, Texture2D*> Tex2D;
+        vector<Tex2D> newTextures;
+        StateSet* result = 0;
+
+        if (!stateSet)
+            return 0;
+        int numUnits = stateSet->getTextureAttributeList().size();
+        if (numUnits > 0) {
+            for (int i = 0; i < numUnits; ++i) {
+                const StateAttribute* attr
+                    = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
+                Texture2D* newTexture = textureReplace(i, attr);
+                if (newTexture)
+                    newTextures.push_back(Tex2D(i, newTexture));
+            }
+            if (!newTextures.empty()) {
+                result = static_cast<StateSet*>(stateSet->clone(CopyOp()));
+                for (vector<Tex2D>::iterator i = newTextures.begin();
+                     i != newTextures.end();
+                     ++i) {
+                    result->setTextureAttribute(i->first, i->second);
+                }
+            }
+        }
+        return result;
+    }
 private:
-  FilePathList mPathList;
+    FilePathList _pathList;
 };
 
+
 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
 public:
   virtual void apply(int, StateSet::RefAttributePair& refAttr)
@@ -176,7 +257,7 @@ public:
     texture = dynamic_cast<Texture*>(refAttr.first.get());
     if (!texture)
       return;
-    
+
     texture->setDataVariance(Object::STATIC);
   }
 
@@ -201,54 +282,28 @@ public:
   }
 };
 
-// Work around an OSG bug - the file loaders don't use the file path
-// in options while the file is being loaded.
-
-struct OptionsPusher {
-    FilePathList localPathList;
-    bool validOptions;
-    OptionsPusher(const ReaderWriter::Options* options):
-        validOptions(false)
-    {
-        if (!options)
-            return;
-        Registry* registry = Registry::instance();
-        localPathList = registry->getDataFilePathList();
-        const FilePathList& regPathList = registry->getDataFilePathList();
-        const FilePathList& optionsPathList = options->getDatabasePathList();
-        for (FilePathList::const_iterator iter = optionsPathList.begin();
-             iter != optionsPathList.end();
-             ++iter) {
-            if (find(regPathList.begin(), regPathList.end(), *iter)
-                == regPathList.end())
-                localPathList.push_back(*iter);
-        }
-        // Save the current Registry path list and install the augmented one.
-        localPathList.swap(registry->getDataFilePathList());
-        validOptions = true;
-    }
-    ~OptionsPusher()
-    {
-        // Restore the old path list
-        if (validOptions)
-            localPathList.swap(Registry::instance()->getDataFilePathList());
-    }
-};
 } // namespace
 
+Node* DefaultProcessPolicy::process(Node* node, const string& filename,
+                                    const ReaderWriter::Options* opt)
+{
+    TextureNameVisitor nameVisitor;
+    node->accept(nameVisitor);
+    return node;
+}
+
 ReaderWriter::ReadResult
 ModelRegistry::readImage(const string& fileName,
                          const ReaderWriter::Options* opt)
 {
-    OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
+    ScopedLock<ReentrantMutex> lock(readerMutex);
     CallbackMap::iterator iter
         = imageCallbackMap.find(getFileExtension(fileName));
     // XXX Workaround for OSG plugin bug
     {
-        OptionsPusher pusher(opt);
         if (iter != imageCallbackMap.end() && iter->second.valid())
             return iter->second->readImage(fileName, opt);
-        string absFileName = findDataFile(fileName);
+        string absFileName = findDataFile(fileName, opt);
         if (!fileExists(absFileName)) {
             SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
                    << fileName << "\"");
@@ -258,6 +313,11 @@ ModelRegistry::readImage(const string& fileName,
         Registry* registry = Registry::instance();
         ReaderWriter::ReadResult res;
         res = registry->readImageImplementation(absFileName, opt);
+        if (!res.success()) {
+          SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
+          return res;
+        }
+        
         if (res.loadedFromCache())
             SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
                    << res.getImage()->getFileName() << "\"");
@@ -322,7 +382,7 @@ osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
     // STATIC so that textures will be globally shared.
     SGTexDataVarianceVisitor dataVarianceVisitor;
     node->accept(dataVarianceVisitor);
-      
+
     SGTexCompressionVisitor texComp;
     node->accept(texComp);
     return node;
@@ -352,8 +412,9 @@ osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
     res->addObserver(databaseReference);
 
     // Update liveries
-    SGTextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
+    TextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
     res->accept(liveryUpdate);
+
     return res;
 }
 
@@ -367,7 +428,8 @@ string OSGSubstitutePolicy::substitute(const string& name,
 }
 
 ModelRegistry::ModelRegistry() :
-    _defaultCallback(new DefaultCallback(""))
+    _defaultCallback(new DefaultCallback("")),
+    _nestingLevel(0)
 {
 }
 
@@ -385,31 +447,34 @@ ModelRegistry::addNodeCallbackForExtension(const string& extension,
     nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
 }
 
-ref_ptr<ModelRegistry> ModelRegistry::instance;
-
-ModelRegistry* ModelRegistry::getInstance()
-
-{
-    if (!instance.valid())
-        instance = new ModelRegistry;
-    return instance.get();
-}
-
 ReaderWriter::ReadResult
 ModelRegistry::readNode(const string& fileName,
                         const ReaderWriter::Options* opt)
 {
-    OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
+    ScopedLock<ReentrantMutex> lock(readerMutex);
+    ++_nestingLevel;
+
     // XXX Workaround for OSG plugin bug.
-    OptionsPusher pusher(opt);
     Registry* registry = Registry::instance();
     ReaderWriter::ReadResult res;
-    Node* cached = 0;
     CallbackMap::iterator iter
         = nodeCallbackMap.find(getFileExtension(fileName));
+    ReaderWriter::ReadResult result;
     if (iter != nodeCallbackMap.end() && iter->second.valid())
-        return iter->second->readNode(fileName, opt);
-    return _defaultCallback->readNode(fileName, opt);
+        result = iter->second->readNode(fileName, opt);
+    else
+        result = _defaultCallback->readNode(fileName, opt);
+
+    if (0 == --_nestingLevel) {
+        SG_LOG(SG_IO, SG_INFO, "Building boundingvolume tree for \""
+               << fileName << "\".");
+        BoundingVolumeBuildVisitor bvBuilder;
+        result.getNode()->accept(bvBuilder);
+    } else {
+        SG_LOG(SG_IO, SG_INFO, "Defering boundingvolume tree built for \""
+               << fileName << "\" to parent.");
+    }
+    return result;
 }
 
 class SGReadCallbackInstaller {
@@ -428,7 +493,7 @@ public:
     registry->setOptions(options);
     registry->getOrCreateSharedStateManager()->
       setShareMode(SharedStateManager::SHARE_STATESETS);
-    registry->setReadFileCallback(ModelRegistry::getInstance());
+    registry->setReadFileCallback(ModelRegistry::instance());
   }
 };
 
@@ -446,13 +511,14 @@ struct ACOptimizePolicy : public OptimizeModelPolicy {
     {
         ref_ptr<Node> optimized
             = OptimizeModelPolicy::optimize(node, fileName, opt);
+        Group* group = dynamic_cast<Group*>(optimized.get());
         MatrixTransform* transform
             = dynamic_cast<MatrixTransform*>(optimized.get());
-        if (transform && transform->getMatrix().isIdentity()
-            && transform->getName().empty()
-            && transform->getNumChildren() == 1) {
-            optimized = static_cast<Node*>(transform->getChild(0));
-            Group* group = dynamic_cast<Group*>(optimized.get());
+        if (((transform && transform->getMatrix().isIdentity()) || group)
+            && group->getName().empty()
+            && group->getNumChildren() == 1) {
+            optimized = static_cast<Node*>(group->getChild(0));
+            group = dynamic_cast<Group*>(optimized.get());
             if (group && group->getName().empty()
                 && group->getNumChildren() == 1)
                 optimized = static_cast<Node*>(group->getChild(0));
@@ -500,4 +566,4 @@ typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
 namespace
 {
 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
-}   
+}