]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/ModelRegistry.cxx
b19c853c12389940121086aaf921bf6783b573e8
[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 <algorithm>
22 #include <utility>
23 #include <vector>
24
25 #include <OpenThreads/ScopedLock>
26
27 #include <osg/observer_ptr>
28 #include <osg/ref_ptr>
29 #include <osg/Group>
30 #include <osg/NodeCallback>
31 #include <osg/Switch>
32 #include <osg/Material>
33 #include <osg/MatrixTransform>
34 #include <osgDB/Archive>
35 #include <osgDB/FileNameUtils>
36 #include <osgDB/FileUtils>
37 #include <osgDB/ReadFile>
38 #include <osgDB/WriteFile>
39 #include <osgDB/Registry>
40 #include <osgDB/SharedStateManager>
41 #include <osgUtil/Optimizer>
42
43 #include <simgear/scene/util/SGSceneFeatures.hxx>
44 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
45 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
46 #include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
47
48 #include <simgear/structure/exception.hxx>
49 #include <simgear/props/props.hxx>
50 #include <simgear/props/props_io.hxx>
51 #include <simgear/props/condition.hxx>
52
53 using namespace std;
54 using namespace osg;
55 using namespace osgUtil;
56 using namespace osgDB;
57 using namespace simgear;
58
59 using OpenThreads::ReentrantMutex;
60 using OpenThreads::ScopedLock;
61
62 // Little helper class that holds an extra reference to a
63 // loaded 3d model.
64 // Since we clone all structural nodes from our 3d models,
65 // the database pager will only see one single reference to
66 // top node of the model and expire it relatively fast.
67 // We attach that extra reference to every model cloned from
68 // a base model in the pager. When that cloned model is deleted
69 // this extra reference is deleted too. So if there are no
70 // cloned models left the model will expire.
71 namespace {
72 class SGDatabaseReference : public Observer {
73 public:
74   SGDatabaseReference(Referenced* referenced) :
75     mReferenced(referenced)
76   { }
77   virtual void objectDeleted(void*)
78   {
79     mReferenced = 0;
80   }
81 private:
82   ref_ptr<Referenced> mReferenced;
83 };
84
85 // Set the name of a Texture to the simple name of its image
86 // file. This can be used to do livery substitution after the image
87 // has been deallocated.
88 class TextureNameVisitor  : public NodeAndDrawableVisitor {
89 public:
90     TextureNameVisitor(NodeVisitor::TraversalMode tm = NodeVisitor::TRAVERSE_ALL_CHILDREN) :
91         NodeAndDrawableVisitor(tm)
92     {
93     }
94
95     virtual void apply(Node& node)
96     {
97         nameTextures(node.getStateSet());
98         traverse(node);
99     }
100
101     virtual void apply(Drawable& drawable)
102     {
103         nameTextures(drawable.getStateSet());
104     }
105 protected:
106     void nameTextures(StateSet* stateSet)
107     {
108         if (!stateSet)
109             return;
110         int numUnits = stateSet->getTextureAttributeList().size();
111         for (int i = 0; i < numUnits; ++i) {
112             StateAttribute* attr
113                 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
114             Texture2D* texture = dynamic_cast<Texture2D*>(attr);
115             if (!texture || !texture->getName().empty())
116                 continue;
117             const Image *image = texture->getImage();
118             if (!image)
119                 continue;
120             texture->setName(image->getFileName());
121         }
122     }
123 };
124
125 // Change the StateSets of a model to hold different textures based on
126 // a livery path.
127
128 class TextureUpdateVisitor : public NodeAndDrawableVisitor {
129 public:
130     TextureUpdateVisitor(const FilePathList& pathList) :
131         NodeAndDrawableVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
132         _pathList(pathList)
133     {
134     }
135     
136     virtual void apply(Node& node)
137     {
138         StateSet* stateSet = cloneStateSet(node.getStateSet());
139         if (stateSet)
140             node.setStateSet(stateSet);
141         traverse(node);
142     }
143
144     virtual void apply(Drawable& drawable)
145     {
146         StateSet* stateSet = cloneStateSet(drawable.getStateSet());
147         if (stateSet)
148             drawable.setStateSet(stateSet);
149     }
150     // Copied from Mathias' earlier SGTextureUpdateVisitor
151 protected:
152     Texture2D* textureReplace(int unit, const StateAttribute* attr)
153     {
154         const Texture2D* texture = dynamic_cast<const Texture2D*>(attr);
155
156         if (!texture)
157             return 0;
158     
159         const Image* image = texture->getImage();
160         const string* fullFilePath = 0;
161         if (image) {
162             // The currently loaded file name
163             fullFilePath = &image->getFileName();
164
165         } else {
166             fullFilePath = &texture->getName();
167         }
168         // The short name
169         string fileName = getSimpleFileName(*fullFilePath);
170         if (fileName.empty())
171             return 0;
172         // The name that should be found with the current database path
173         string fullLiveryFile = findFileInPath(fileName, _pathList);
174         // If it is empty or they are identical then there is nothing to do
175         if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath)
176             return 0;
177         Image* newImage = readImageFile(fullLiveryFile);
178         if (!newImage)
179             return 0;
180         CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
181         Texture2D* newTexture = static_cast<Texture2D*>(copyOp(texture));
182         if (!newTexture) {
183             return 0;
184         } else {
185             newTexture->setImage(newImage);
186             return newTexture;
187         }
188     }
189     
190     StateSet* cloneStateSet(const StateSet* stateSet)
191     {
192         typedef pair<int, Texture2D*> Tex2D;
193         vector<Tex2D> newTextures;
194         StateSet* result = 0;
195
196         if (!stateSet)
197             return 0;
198         int numUnits = stateSet->getTextureAttributeList().size();
199         if (numUnits > 0) {
200             for (int i = 0; i < numUnits; ++i) {
201                 const StateAttribute* attr
202                     = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
203                 Texture2D* newTexture = textureReplace(i, attr);
204                 if (newTexture)
205                     newTextures.push_back(Tex2D(i, newTexture));
206             }
207             if (!newTextures.empty()) {
208                 result = static_cast<StateSet*>(stateSet->clone(CopyOp()));
209                 for (vector<Tex2D>::iterator i = newTextures.begin();
210                      i != newTextures.end();
211                      ++i) {
212                     result->setTextureAttribute(i->first, i->second);
213                 }
214             }
215         }
216         return result;
217     }
218 private:
219     FilePathList _pathList;
220 };
221
222
223 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
224 public:
225   virtual void apply(int, StateSet::RefAttributePair& refAttr)
226   {
227     Texture2D* texture;
228     texture = dynamic_cast<Texture2D*>(refAttr.first.get());
229     if (!texture)
230       return;
231
232     // Hmm, true??
233     texture->setDataVariance(osg::Object::STATIC);
234
235     Image* image = texture->getImage(0);
236     if (!image)
237       return;
238
239     int s = image->s();
240     int t = image->t();
241
242     if (s <= t && 32 <= s) {
243       SGSceneFeatures::instance()->setTextureCompression(texture);
244     } else if (t < s && 32 <= t) {
245       SGSceneFeatures::instance()->setTextureCompression(texture);
246     }
247   }
248 };
249
250 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
251 public:
252   virtual void apply(int, StateSet::RefAttributePair& refAttr)
253   {
254     Texture* texture;
255     texture = dynamic_cast<Texture*>(refAttr.first.get());
256     if (!texture)
257       return;
258
259     texture->setDataVariance(Object::STATIC);
260   }
261
262   virtual void apply(StateSet* stateSet)
263   {
264     if (!stateSet)
265       return;
266     SGTextureStateAttributeVisitor::apply(stateSet);
267     stateSet->setDataVariance(Object::STATIC);
268   }
269 };
270
271 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
272 public:
273   virtual void apply(StateSet::RefAttributePair& refAttr)
274   {
275     Material* material;
276     material = dynamic_cast<Material*>(refAttr.first.get());
277     if (!material)
278       return;
279     material->setColorMode(Material::AMBIENT_AND_DIFFUSE);
280   }
281 };
282
283 // Work around an OSG bug - the file loaders don't use the file path
284 // in options while the file is being loaded.
285
286 struct OptionsPusher {
287     FilePathList localPathList;
288     bool validOptions;
289     OptionsPusher(const ReaderWriter::Options* options):
290         validOptions(false)
291     {
292         if (!options)
293             return;
294         Registry* registry = Registry::instance();
295         localPathList = registry->getDataFilePathList();
296         const FilePathList& regPathList = registry->getDataFilePathList();
297         const FilePathList& optionsPathList = options->getDatabasePathList();
298         for (FilePathList::const_iterator iter = optionsPathList.begin();
299              iter != optionsPathList.end();
300              ++iter) {
301             if (find(regPathList.begin(), regPathList.end(), *iter)
302                 == regPathList.end())
303                 localPathList.push_back(*iter);
304         }
305         // Save the current Registry path list and install the augmented one.
306         localPathList.swap(registry->getDataFilePathList());
307         validOptions = true;
308     }
309     ~OptionsPusher()
310     {
311         // Restore the old path list
312         if (validOptions)
313             localPathList.swap(Registry::instance()->getDataFilePathList());
314     }
315 };
316 } // namespace
317
318 Node* DefaultProcessPolicy::process(Node* node, const string& filename,
319                                     const ReaderWriter::Options* opt)
320 {
321     TextureNameVisitor nameVisitor;
322     node->accept(nameVisitor);
323     return node;
324 }
325
326 ReaderWriter::ReadResult
327 ModelRegistry::readImage(const string& fileName,
328                          const ReaderWriter::Options* opt)
329 {
330     ScopedLock<ReentrantMutex> lock(readerMutex);
331     CallbackMap::iterator iter
332         = imageCallbackMap.find(getFileExtension(fileName));
333     // XXX Workaround for OSG plugin bug
334     {
335         OptionsPusher pusher(opt);
336         if (iter != imageCallbackMap.end() && iter->second.valid())
337             return iter->second->readImage(fileName, opt);
338         string absFileName = findDataFile(fileName);
339         if (!fileExists(absFileName)) {
340             SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
341                    << fileName << "\"");
342             return ReaderWriter::ReadResult::FILE_NOT_FOUND;
343         }
344
345         Registry* registry = Registry::instance();
346         ReaderWriter::ReadResult res;
347         res = registry->readImageImplementation(absFileName, opt);
348         if (!res.success()) {
349           SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
350           return res;
351         }
352         
353         if (res.loadedFromCache())
354             SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
355                    << res.getImage()->getFileName() << "\"");
356         else
357             SG_LOG(SG_IO, SG_INFO, "Reading image \""
358                    << res.getImage()->getFileName() << "\"");
359
360         return res;
361     }
362 }
363
364
365 osg::Node* DefaultCachePolicy::find(const string& fileName,
366                                     const ReaderWriter::Options* opt)
367 {
368     Registry* registry = Registry::instance();
369     osg::Node* cached
370         = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
371     if (cached)
372         SG_LOG(SG_IO, SG_INFO, "Got cached model \""
373                << fileName << "\"");
374     else
375         SG_LOG(SG_IO, SG_INFO, "Reading model \""
376                << fileName << "\"");
377     return cached;
378 }
379
380 void DefaultCachePolicy::addToCache(const string& fileName,
381                                     osg::Node* node)
382 {
383     Registry::instance()->addEntryToObjectCache(fileName, node);
384 }
385
386 // Optimizations we don't use:
387 // Don't use this one. It will break animation names ...
388 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
389 //
390 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
391 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
392 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
393 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
394 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
395 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
396 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
397
398 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
399     _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
400                 | Optimizer::MERGE_GEOMETRY
401                 | Optimizer::FLATTEN_STATIC_TRANSFORMS
402                 | Optimizer::TRISTRIP_GEOMETRY)
403 {
404 }
405
406 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
407                                          const string& fileName,
408                                          const osgDB::ReaderWriter::Options* opt)
409 {
410     osgUtil::Optimizer optimizer;
411     optimizer.optimize(node, _osgOptions);
412
413     // Make sure the data variance of sharable objects is set to
414     // STATIC so that textures will be globally shared.
415     SGTexDataVarianceVisitor dataVarianceVisitor;
416     node->accept(dataVarianceVisitor);
417
418     SGTexCompressionVisitor texComp;
419     node->accept(texComp);
420     return node;
421 }
422
423 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
424                     const osgDB::ReaderWriter::Options* opt)
425 {
426     // Add an extra reference to the model stored in the database.
427     // That it to avoid expiring the object from the cache even if it is still
428     // in use. Note that the object cache will think that a model is unused
429     // if the reference count is 1. If we clone all structural nodes here
430     // we need that extra reference to the original object
431     SGDatabaseReference* databaseReference;
432     databaseReference = new SGDatabaseReference(model);
433     CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
434     flags &= ~CopyOp::DEEP_COPY_TEXTURES;
435     flags &= ~CopyOp::DEEP_COPY_IMAGES;
436     flags &= ~CopyOp::DEEP_COPY_STATESETS;
437     flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
438     flags &= ~CopyOp::DEEP_COPY_ARRAYS;
439     flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
440     // This will safe display lists ...
441     flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
442     flags &= ~CopyOp::DEEP_COPY_SHAPES;
443     osg::Node* res = CopyOp(flags)(model);
444     res->addObserver(databaseReference);
445
446     // Update liveries
447     TextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
448     res->accept(liveryUpdate);
449
450     return res;
451 }
452
453 string OSGSubstitutePolicy::substitute(const string& name,
454                                        const ReaderWriter::Options* opt)
455 {
456     string fileSansExtension = getNameLessExtension(name);
457     string osgFileName = fileSansExtension + ".osg";
458     string absFileName = findDataFile(osgFileName, opt);
459     return absFileName;
460 }
461
462 ModelRegistry::ModelRegistry() :
463     _defaultCallback(new DefaultCallback(""))
464 {
465 }
466
467 void
468 ModelRegistry::addImageCallbackForExtension(const string& extension,
469                                             Registry::ReadFileCallback* callback)
470 {
471     imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
472 }
473
474 void
475 ModelRegistry::addNodeCallbackForExtension(const string& extension,
476                                            Registry::ReadFileCallback* callback)
477 {
478     nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
479 }
480
481 ReaderWriter::ReadResult
482 ModelRegistry::readNode(const string& fileName,
483                         const ReaderWriter::Options* opt)
484 {
485     ScopedLock<ReentrantMutex> lock(readerMutex);
486     // XXX Workaround for OSG plugin bug.
487     OptionsPusher pusher(opt);
488     Registry* registry = Registry::instance();
489     ReaderWriter::ReadResult res;
490     Node* cached = 0;
491     CallbackMap::iterator iter
492         = nodeCallbackMap.find(getFileExtension(fileName));
493     if (iter != nodeCallbackMap.end() && iter->second.valid())
494         return iter->second->readNode(fileName, opt);
495     return _defaultCallback->readNode(fileName, opt);
496 }
497
498 class SGReadCallbackInstaller {
499 public:
500   SGReadCallbackInstaller()
501   {
502     // XXX I understand why we want this, but this seems like a weird
503     // place to set this option.
504     Referenced::setThreadSafeReferenceCounting(true);
505
506     Registry* registry = Registry::instance();
507     ReaderWriter::Options* options = new ReaderWriter::Options;
508     int cacheOptions = ReaderWriter::Options::CACHE_ALL;
509     options->
510       setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
511     registry->setOptions(options);
512     registry->getOrCreateSharedStateManager()->
513       setShareMode(SharedStateManager::SHARE_STATESETS);
514     registry->setReadFileCallback(ModelRegistry::instance());
515   }
516 };
517
518 static SGReadCallbackInstaller readCallbackInstaller;
519
520 // we get optimal geometry from the loader.
521 struct ACOptimizePolicy : public OptimizeModelPolicy {
522     ACOptimizePolicy(const string& extension)  :
523         OptimizeModelPolicy(extension)
524     {
525         _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
526     }
527     Node* optimize(Node* node, const string& fileName,
528                    const ReaderWriter::Options* opt)
529     {
530         ref_ptr<Node> optimized
531             = OptimizeModelPolicy::optimize(node, fileName, opt);
532         MatrixTransform* transform
533             = dynamic_cast<MatrixTransform*>(optimized.get());
534         if (transform && transform->getMatrix().isIdentity()
535             && transform->getName().empty()
536             && transform->getNumChildren() == 1) {
537             optimized = static_cast<Node*>(transform->getChild(0));
538             Group* group = dynamic_cast<Group*>(optimized.get());
539             if (group && group->getName().empty()
540                 && group->getNumChildren() == 1)
541                 optimized = static_cast<Node*>(group->getChild(0));
542         }
543         return optimized.release();
544     }
545 };
546
547 struct ACProcessPolicy {
548     ACProcessPolicy(const string& extension) {}
549     Node* process(Node* node, const string& filename,
550                   const ReaderWriter::Options* opt)
551     {
552         Matrix m(1, 0, 0, 0,
553                  0, 0, 1, 0,
554                  0, -1, 0, 0,
555                  0, 0, 0, 1);
556         // XXX Does there need to be a Group node here to trick the
557         // optimizer into optimizing the static transform?
558         osg::Group* root = new Group;
559         MatrixTransform* transform = new MatrixTransform;
560         root->addChild(transform);
561         
562         transform->setDataVariance(Object::STATIC);
563         transform->setMatrix(m);
564         transform->addChild(node);
565         // Ok, this step is questionable.
566         // It is there to have the same visual appearance of ac objects for the
567         // first cut. Osg's ac3d loader will correctly set materials from the
568         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
569         // materials that in effect igored the ambient part specified in the
570         // file. We emulate that for the first cut here by changing all
571         // ac models here. But in the long term we should use the
572         // unchanged model and fix the input files instead ...
573         SGAcMaterialCrippleVisitor matCriple;
574         root->accept(matCriple);
575         return root;
576     }
577 };
578
579 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
580                               ACOptimizePolicy, DefaultCopyPolicy,
581                               OSGSubstitutePolicy> ACCallback;
582
583 namespace
584 {
585 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
586 }