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