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