]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/ModelRegistry.cxx
Csaba/Jester : fix a problem with material animation
[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.loadedFromCache())
349             SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
350                    << res.getImage()->getFileName() << "\"");
351         else
352             SG_LOG(SG_IO, SG_INFO, "Reading image \""
353                    << res.getImage()->getFileName() << "\"");
354
355         return res;
356     }
357 }
358
359
360 osg::Node* DefaultCachePolicy::find(const string& fileName,
361                                     const ReaderWriter::Options* opt)
362 {
363     Registry* registry = Registry::instance();
364     osg::Node* cached
365         = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
366     if (cached)
367         SG_LOG(SG_IO, SG_INFO, "Got cached model \""
368                << fileName << "\"");
369     else
370         SG_LOG(SG_IO, SG_INFO, "Reading model \""
371                << fileName << "\"");
372     return cached;
373 }
374
375 void DefaultCachePolicy::addToCache(const string& fileName,
376                                     osg::Node* node)
377 {
378     Registry::instance()->addEntryToObjectCache(fileName, node);
379 }
380
381 // Optimizations we don't use:
382 // Don't use this one. It will break animation names ...
383 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
384 //
385 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
386 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
387 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
388 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
389 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
390 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
391 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
392
393 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
394     _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
395                 | Optimizer::MERGE_GEOMETRY
396                 | Optimizer::FLATTEN_STATIC_TRANSFORMS
397                 | Optimizer::TRISTRIP_GEOMETRY)
398 {
399 }
400
401 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
402                                          const string& fileName,
403                                          const osgDB::ReaderWriter::Options* opt)
404 {
405     osgUtil::Optimizer optimizer;
406     optimizer.optimize(node, _osgOptions);
407
408     // Make sure the data variance of sharable objects is set to
409     // STATIC so that textures will be globally shared.
410     SGTexDataVarianceVisitor dataVarianceVisitor;
411     node->accept(dataVarianceVisitor);
412
413     SGTexCompressionVisitor texComp;
414     node->accept(texComp);
415     return node;
416 }
417
418 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
419                     const osgDB::ReaderWriter::Options* opt)
420 {
421     // Add an extra reference to the model stored in the database.
422     // That it to avoid expiring the object from the cache even if it is still
423     // in use. Note that the object cache will think that a model is unused
424     // if the reference count is 1. If we clone all structural nodes here
425     // we need that extra reference to the original object
426     SGDatabaseReference* databaseReference;
427     databaseReference = new SGDatabaseReference(model);
428     CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
429     flags &= ~CopyOp::DEEP_COPY_TEXTURES;
430     flags &= ~CopyOp::DEEP_COPY_IMAGES;
431     flags &= ~CopyOp::DEEP_COPY_STATESETS;
432     flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
433     flags &= ~CopyOp::DEEP_COPY_ARRAYS;
434     flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
435     // This will safe display lists ...
436     flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
437     flags &= ~CopyOp::DEEP_COPY_SHAPES;
438     osg::Node* res = CopyOp(flags)(model);
439     res->addObserver(databaseReference);
440
441     // Update liveries
442     TextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
443     res->accept(liveryUpdate);
444
445     return res;
446 }
447
448 string OSGSubstitutePolicy::substitute(const string& name,
449                                        const ReaderWriter::Options* opt)
450 {
451     string fileSansExtension = getNameLessExtension(name);
452     string osgFileName = fileSansExtension + ".osg";
453     string absFileName = findDataFile(osgFileName, opt);
454     return absFileName;
455 }
456
457 ModelRegistry::ModelRegistry() :
458     _defaultCallback(new DefaultCallback(""))
459 {
460 }
461
462 void
463 ModelRegistry::addImageCallbackForExtension(const string& extension,
464                                             Registry::ReadFileCallback* callback)
465 {
466     imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
467 }
468
469 void
470 ModelRegistry::addNodeCallbackForExtension(const string& extension,
471                                            Registry::ReadFileCallback* callback)
472 {
473     nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
474 }
475
476 ReaderWriter::ReadResult
477 ModelRegistry::readNode(const string& fileName,
478                         const ReaderWriter::Options* opt)
479 {
480     ScopedLock<ReentrantMutex> lock(readerMutex);
481     // XXX Workaround for OSG plugin bug.
482     OptionsPusher pusher(opt);
483     Registry* registry = Registry::instance();
484     ReaderWriter::ReadResult res;
485     Node* cached = 0;
486     CallbackMap::iterator iter
487         = nodeCallbackMap.find(getFileExtension(fileName));
488     if (iter != nodeCallbackMap.end() && iter->second.valid())
489         return iter->second->readNode(fileName, opt);
490     return _defaultCallback->readNode(fileName, opt);
491 }
492
493 class SGReadCallbackInstaller {
494 public:
495   SGReadCallbackInstaller()
496   {
497     // XXX I understand why we want this, but this seems like a weird
498     // place to set this option.
499     Referenced::setThreadSafeReferenceCounting(true);
500
501     Registry* registry = Registry::instance();
502     ReaderWriter::Options* options = new ReaderWriter::Options;
503     int cacheOptions = ReaderWriter::Options::CACHE_ALL;
504     options->
505       setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
506     registry->setOptions(options);
507     registry->getOrCreateSharedStateManager()->
508       setShareMode(SharedStateManager::SHARE_STATESETS);
509     registry->setReadFileCallback(ModelRegistry::instance());
510   }
511 };
512
513 static SGReadCallbackInstaller readCallbackInstaller;
514
515 // we get optimal geometry from the loader.
516 struct ACOptimizePolicy : public OptimizeModelPolicy {
517     ACOptimizePolicy(const string& extension)  :
518         OptimizeModelPolicy(extension)
519     {
520         _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
521     }
522     Node* optimize(Node* node, const string& fileName,
523                    const ReaderWriter::Options* opt)
524     {
525         ref_ptr<Node> optimized
526             = OptimizeModelPolicy::optimize(node, fileName, opt);
527         MatrixTransform* transform
528             = dynamic_cast<MatrixTransform*>(optimized.get());
529         if (transform && transform->getMatrix().isIdentity()
530             && transform->getName().empty()
531             && transform->getNumChildren() == 1) {
532             optimized = static_cast<Node*>(transform->getChild(0));
533             Group* group = dynamic_cast<Group*>(optimized.get());
534             if (group && group->getName().empty()
535                 && group->getNumChildren() == 1)
536                 optimized = static_cast<Node*>(group->getChild(0));
537         }
538         return optimized.release();
539     }
540 };
541
542 struct ACProcessPolicy {
543     ACProcessPolicy(const string& extension) {}
544     Node* process(Node* node, const string& filename,
545                   const ReaderWriter::Options* opt)
546     {
547         Matrix m(1, 0, 0, 0,
548                  0, 0, 1, 0,
549                  0, -1, 0, 0,
550                  0, 0, 0, 1);
551         // XXX Does there need to be a Group node here to trick the
552         // optimizer into optimizing the static transform?
553         osg::Group* root = new Group;
554         MatrixTransform* transform = new MatrixTransform;
555         root->addChild(transform);
556         
557         transform->setDataVariance(Object::STATIC);
558         transform->setMatrix(m);
559         transform->addChild(node);
560         // Ok, this step is questionable.
561         // It is there to have the same visual appearance of ac objects for the
562         // first cut. Osg's ac3d loader will correctly set materials from the
563         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
564         // materials that in effect igored the ambient part specified in the
565         // file. We emulate that for the first cut here by changing all
566         // ac models here. But in the long term we should use the
567         // unchanged model and fix the input files instead ...
568         SGAcMaterialCrippleVisitor matCriple;
569         root->accept(matCriple);
570         return root;
571     }
572 };
573
574 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
575                               ACOptimizePolicy, DefaultCopyPolicy,
576                               OSGSubstitutePolicy> ACCallback;
577
578 namespace
579 {
580 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");
581 }