]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/ModelRegistry.cxx
Check for null options before changing path in registry
[simgear.git] / simgear / scene / model / ModelRegistry.cxx
1 // ModelRegistry.hxx -- interface to the OSG model registry
2 //
3 // Copyright (C) 2005-2007 Mathias Froehlich 
4 // Copyright (C) 2007  Tim Moore <timoore@redhat.com>
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License as
8 // published by the Free Software Foundation; either version 2 of the
9 // License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful, but
12 // WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19 #include "ModelRegistry.hxx"
20
21 #include <OpenThreads/ScopedLock>
22
23 #include <osg/observer_ptr>
24 #include <osg/ref_ptr>
25 #include <osg/Group>
26 #include <osg/NodeCallback>
27 #include <osg/Switch>
28 #include <osg/Material>
29 #include <osg/MatrixTransform>
30 #include <osgDB/Archive>
31 #include <osgDB/FileNameUtils>
32 #include <osgDB/FileUtils>
33 #include <osgDB/ReadFile>
34 #include <osgDB/WriteFile>
35 #include <osgDB/Registry>
36 #include <osgDB/SharedStateManager>
37 #include <osgUtil/Optimizer>
38
39 #include <simgear/scene/util/SGSceneFeatures.hxx>
40 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
41 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
42
43 #include <simgear/structure/exception.hxx>
44 #include <simgear/props/props.hxx>
45 #include <simgear/props/props_io.hxx>
46 #include <simgear/props/condition.hxx>
47
48 using namespace std;
49 using namespace osg;
50 using namespace osgUtil;
51 using namespace osgDB;
52 using namespace simgear;
53
54 // Little helper class that holds an extra reference to a
55 // loaded 3d model.
56 // Since we clone all structural nodes from our 3d models,
57 // the database pager will only see one single reference to
58 // top node of the model and expire it relatively fast.
59 // We attach that extra reference to every model cloned from
60 // a base model in the pager. When that cloned model is deleted
61 // this extra reference is deleted too. So if there are no
62 // cloned models left the model will expire.
63 namespace {
64 class SGDatabaseReference : public Observer {
65 public:
66   SGDatabaseReference(Referenced* referenced) :
67     mReferenced(referenced)
68   { }
69   virtual void objectDeleted(void*)
70   {
71     mReferenced = 0;
72   }
73 private:
74   ref_ptr<Referenced> mReferenced;
75 };
76
77 // Visitor for 
78 class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
79 public:
80   SGTextureUpdateVisitor(const FilePathList& pathList) :
81     mPathList(pathList)
82   { }
83   Texture2D* textureReplace(int unit,
84                             StateSet::RefAttributePair& refAttr)
85   {
86     Texture2D* texture;
87     texture = dynamic_cast<Texture2D*>(refAttr.first.get());
88     if (!texture)
89       return 0;
90     
91     ref_ptr<Image> image = texture->getImage(0);
92     if (!image)
93       return 0;
94
95     // The currently loaded file name
96     string fullFilePath = image->getFileName();
97     // The short name
98     string fileName = getSimpleFileName(fullFilePath);
99     // The name that should be found with the current database path
100     string fullLiveryFile = findFileInPath(fileName, mPathList);
101     // If they are identical then there is nothing to do
102     if (fullLiveryFile == fullFilePath)
103       return 0;
104
105     image = readImageFile(fullLiveryFile);
106     if (!image)
107       return 0;
108
109     CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
110     texture = static_cast<Texture2D*>(copyOp(texture));
111     if (!texture)
112       return 0;
113     texture->setImage(image.get());
114     return texture;
115   }
116   virtual void apply(StateSet* stateSet)
117   {
118     if (!stateSet)
119       return;
120
121     // get a copy that we can safely modify the statesets values.
122     StateSet::TextureAttributeList attrList;
123     attrList = stateSet->getTextureAttributeList();
124     for (unsigned unit = 0; unit < attrList.size(); ++unit) {
125       StateSet::AttributeList::iterator i = attrList[unit].begin();
126       while (i != attrList[unit].end()) {
127         Texture2D* texture = textureReplace(unit, i->second);
128         if (texture) {
129           stateSet->removeTextureAttribute(unit, i->second.first.get());
130           stateSet->setTextureAttribute(unit, texture, i->second.second);
131           stateSet->setTextureMode(unit, GL_TEXTURE_2D, StateAttribute::ON);
132         }
133         ++i;
134       }
135     }
136   }
137
138 private:
139   FilePathList mPathList;
140 };
141
142 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
143 public:
144   virtual void apply(int, StateSet::RefAttributePair& refAttr)
145   {
146     Texture2D* texture;
147     texture = dynamic_cast<Texture2D*>(refAttr.first.get());
148     if (!texture)
149       return;
150
151     // Hmm, true??
152     texture->setDataVariance(osg::Object::STATIC);
153
154     Image* image = texture->getImage(0);
155     if (!image)
156       return;
157
158     int s = image->s();
159     int t = image->t();
160
161     if (s <= t && 32 <= s) {
162       SGSceneFeatures::instance()->setTextureCompression(texture);
163     } else if (t < s && 32 <= t) {
164       SGSceneFeatures::instance()->setTextureCompression(texture);
165     }
166   }
167 };
168
169 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
170 public:
171   virtual void apply(int, StateSet::RefAttributePair& refAttr)
172   {
173     Texture* texture;
174     texture = dynamic_cast<Texture*>(refAttr.first.get());
175     if (!texture)
176       return;
177     
178     texture->setDataVariance(Object::STATIC);
179   }
180
181   virtual void apply(StateSet* stateSet)
182   {
183     if (!stateSet)
184       return;
185     SGTextureStateAttributeVisitor::apply(stateSet);
186     stateSet->setDataVariance(Object::STATIC);
187   }
188 };
189
190 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
191 public:
192   virtual void apply(StateSet::RefAttributePair& refAttr)
193   {
194     Material* material;
195     material = dynamic_cast<Material*>(refAttr.first.get());
196     if (!material)
197       return;
198     material->setColorMode(Material::AMBIENT_AND_DIFFUSE);
199   }
200 };
201
202 // Work around an OSG bug - the file loaders don't use the file path
203 // in options while the file is being loaded.
204
205 struct OptionsPusher {
206     FilePathList localPathList;
207     bool validOptions;
208     OptionsPusher(const ReaderWriter::Options* options):
209         validOptions(false)
210     {
211         if (!options)
212             return;
213         Registry* registry = Registry::instance();
214         localPathList = registry->getDataFilePathList();
215         const FilePathList& regPathList = registry->getDataFilePathList();
216         const FilePathList& optionsPathList = options->getDatabasePathList();
217         for (FilePathList::const_iterator iter = optionsPathList.begin();
218              iter != optionsPathList.end();
219              ++iter) {
220             if (find(regPathList.begin(), regPathList.end(), *iter)
221                 == regPathList.end())
222                 localPathList.push_front(*iter);
223         }
224         // Save the current Registry path list and install the augmented one.
225         localPathList.swap(registry->getDataFilePathList());
226         validOptions = true;
227     }
228     ~OptionsPusher()
229     {
230         // Restore the old path list
231         if (validOptions)
232             localPathList.swap(Registry::instance()->getDataFilePathList());
233     }
234 };
235 } // namespace
236
237 ReaderWriter::ReadResult
238 ModelRegistry::readImage(const string& fileName,
239                          const ReaderWriter::Options* opt)
240 {
241     OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
242     CallbackMap::iterator iter
243         = imageCallbackMap.find(getFileExtension(fileName));
244     // XXX Workaround for OSG plugin bug
245     OptionsPusher pusher(opt);
246     if (iter != imageCallbackMap.end() && iter->second.valid())
247         return iter->second->readImage(fileName, opt);
248     string absFileName = findDataFile(fileName);
249     if (!fileExists(absFileName)) {
250         SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
251                << fileName << "\"");
252         return ReaderWriter::ReadResult::FILE_NOT_FOUND;
253     }
254
255     Registry* registry = Registry::instance();
256     ReaderWriter::ReadResult res;
257     res = registry->readImageImplementation(absFileName, opt);
258     if (res.loadedFromCache())
259         SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
260                << res.getImage()->getFileName() << "\"");
261     else
262         SG_LOG(SG_IO, SG_INFO, "Reading image \""
263                << res.getImage()->getFileName() << "\"");
264
265     return res;
266 }
267
268
269 osg::Node* DefaultCachePolicy::find(const string& fileName,
270                                     const ReaderWriter::Options* opt)
271 {
272     Registry* registry = Registry::instance();
273     osg::Node* cached
274         = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
275     if (cached)
276         SG_LOG(SG_IO, SG_INFO, "Got cached model \""
277                << fileName << "\"");
278     else
279         SG_LOG(SG_IO, SG_INFO, "Reading model \""
280                << fileName << "\"");
281     return cached;
282 }
283
284 void DefaultCachePolicy::addToCache(const string& fileName,
285                                     osg::Node* node)
286 {
287     Registry::instance()->addEntryToObjectCache(fileName, node);
288 }
289
290 // Optimizations we don't use:
291 // Don't use this one. It will break animation names ...
292 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
293 //
294 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
295 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
296 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
297 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
298 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
299 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
300 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
301
302 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
303     _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
304                 | Optimizer::MERGE_GEOMETRY
305                 | Optimizer::FLATTEN_STATIC_TRANSFORMS
306                 | Optimizer::TRISTRIP_GEOMETRY)
307 {
308 }
309
310 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
311                                          const string& fileName,
312                                          const osgDB::ReaderWriter::Options* opt)
313 {
314     osgUtil::Optimizer optimizer;
315     optimizer.optimize(node, _osgOptions);
316
317     // Make sure the data variance of sharable objects is set to
318     // STATIC so that textures will be globally shared.
319     SGTexDataVarianceVisitor dataVarianceVisitor;
320     node->accept(dataVarianceVisitor);
321       
322     SGTexCompressionVisitor texComp;
323     node->accept(texComp);
324     return node;
325 }
326
327 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
328                     const osgDB::ReaderWriter::Options* opt)
329 {
330     // Add an extra reference to the model stored in the database.
331     // That it to avoid expiring the object from the cache even if it is still
332     // in use. Note that the object cache will think that a model is unused
333     // if the reference count is 1. If we clone all structural nodes here
334     // we need that extra reference to the original object
335     SGDatabaseReference* databaseReference;
336     databaseReference = new SGDatabaseReference(model);
337     CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
338     flags &= ~CopyOp::DEEP_COPY_TEXTURES;
339     flags &= ~CopyOp::DEEP_COPY_IMAGES;
340     flags &= ~CopyOp::DEEP_COPY_STATESETS;
341     flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
342     flags &= ~CopyOp::DEEP_COPY_ARRAYS;
343     flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
344     // This will safe display lists ...
345     flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
346     flags &= ~CopyOp::DEEP_COPY_SHAPES;
347     osg::Node* res = CopyOp(flags)(model);
348     res->addObserver(databaseReference);
349
350     // Update liveries
351     SGTextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
352     res->accept(liveryUpdate);
353     return res;
354 }
355
356 string OSGSubstitutePolicy::substitute(const string& name,
357                                        const ReaderWriter::Options* opt)
358 {
359     string fileSansExtension = getNameLessExtension(name);
360     string osgFileName = fileSansExtension + ".osg";
361     string absFileName = findDataFile(osgFileName, opt);
362     return absFileName;
363 }
364
365 ModelRegistry::ModelRegistry() :
366     _defaultCallback(new DefaultCallback(""))
367 {
368 }
369
370 void
371 ModelRegistry::addImageCallbackForExtension(const string& extension,
372                                             Registry::ReadFileCallback* callback)
373 {
374     imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
375 }
376
377 void
378 ModelRegistry::addNodeCallbackForExtension(const string& extension,
379                                            Registry::ReadFileCallback* callback)
380 {
381     nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
382 }
383
384 ref_ptr<ModelRegistry> ModelRegistry::instance;
385
386 ModelRegistry* ModelRegistry::getInstance()
387
388 {
389     if (!instance.valid())
390         instance = new ModelRegistry;
391     return instance.get();
392 }
393
394 ReaderWriter::ReadResult
395 ModelRegistry::readNode(const string& fileName,
396                         const ReaderWriter::Options* opt)
397 {
398     OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
399     // XXX Workaround for OSG plugin bug.
400     OptionsPusher pusher(opt);
401     Registry* registry = Registry::instance();
402     ReaderWriter::ReadResult res;
403     Node* cached = 0;
404     CallbackMap::iterator iter
405         = nodeCallbackMap.find(getFileExtension(fileName));
406     if (iter != nodeCallbackMap.end() && iter->second.valid())
407         return iter->second->readNode(fileName, opt);
408     return _defaultCallback->readNode(fileName, opt);
409 }
410
411 class SGReadCallbackInstaller {
412 public:
413   SGReadCallbackInstaller()
414   {
415     // XXX I understand why we want this, but this seems like a weird
416     // place to set this option.
417     Referenced::setThreadSafeReferenceCounting(true);
418
419     Registry* registry = Registry::instance();
420     ReaderWriter::Options* options = new ReaderWriter::Options;
421     int cacheOptions = ReaderWriter::Options::CACHE_ALL;
422     options->
423       setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
424     registry->setOptions(options);
425     registry->getOrCreateSharedStateManager()->
426       setShareMode(SharedStateManager::SHARE_STATESETS);
427     registry->setReadFileCallback(ModelRegistry::getInstance());
428   }
429 };
430
431 static SGReadCallbackInstaller readCallbackInstaller;
432
433 // we get optimal geometry from the loader.
434 struct ACOptimizePolicy : public OptimizeModelPolicy {
435     ACOptimizePolicy(const string& extension)  :
436         OptimizeModelPolicy(extension)
437     {
438         _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
439     }
440 };
441
442 struct ACProcessPolicy {
443     ACProcessPolicy(const string& extension) {}
444     Node* process(Node* node, const string& filename,
445                   const ReaderWriter::Options* opt)
446     {
447         Matrix m(1, 0, 0, 0,
448                  0, 0, 1, 0,
449                  0, -1, 0, 0,
450                  0, 0, 0, 1);
451         // XXX Does there need to be a Group node here to trick the
452         // optimizer into optimizing the static transform?
453         osg::Group* root = new Group;
454         MatrixTransform* transform = new MatrixTransform;
455         root->addChild(transform);
456         
457         transform->setDataVariance(Object::STATIC);
458         transform->setMatrix(m);
459         transform->addChild(node);
460         // Ok, this step is questionable.
461         // It is there to have the same visual appearance of ac objects for the
462         // first cut. Osg's ac3d loader will correctly set materials from the
463         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
464         // materials that in effect igored the ambient part specified in the
465         // file. We emulate that for the first cut here by changing all
466         // ac models here. But in the long term we should use the
467         // unchanged model and fix the input files instead ...
468         SGAcMaterialCrippleVisitor matCriple;
469         root->accept(matCriple);
470         return root;
471     }
472 };
473
474 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
475                               ACOptimizePolicy, DefaultCopyPolicy,
476                               OSGSubstitutePolicy> ACCallback;
477
478 namespace
479 {
480 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
481 }