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