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