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