]> git.mxchange.org Git - simgear.git/blob - simgear/scene/model/model.cxx
Manage OSG object cache explicitly
[simgear.git] / simgear / scene / model / model.cxx
1 // model.cxx - manage a 3D aircraft model.
2 // Written by David Megginson, started 2002.
3 //
4 // This file is in the Public Domain, and comes with no warranty.
5
6 #ifdef HAVE_CONFIG_H
7 #include <simgear_config.h>
8 #endif
9
10 #include <osg/observer_ptr>
11 #include <osg/ref_ptr>
12 #include <osg/Group>
13 #include <osg/NodeCallback>
14 #include <osg/Switch>
15 #include <osg/MatrixTransform>
16 #include <osgDB/Archive>
17 #include <osgDB/FileNameUtils>
18 #include <osgDB/FileUtils>
19 #include <osgDB/ReadFile>
20 #include <osgDB/WriteFile>
21 #include <osgDB/Registry>
22 #include <osgDB/SharedStateManager>
23 #include <osgUtil/Optimizer>
24
25 #include <simgear/scene/util/SGSceneFeatures.hxx>
26 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
27 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
28
29 #include <simgear/structure/exception.hxx>
30 #include <simgear/props/props.hxx>
31 #include <simgear/props/props_io.hxx>
32 #include <simgear/props/condition.hxx>
33
34 #include "animation.hxx"
35 #include "model.hxx"
36
37 SG_USING_STD(vector);
38 SG_USING_STD(set);
39
40 // Little helper class that holds an extra reference to a
41 // loaded 3d model.
42 // Since we clone all structural nodes from our 3d models,
43 // the database pager will only see one single reference to
44 // top node of the model and expire it relatively fast.
45 // We attach that extra reference to every model cloned from
46 // a base model in the pager. When that cloned model is deleted
47 // this extra reference is deleted too. So if there are no
48 // cloned models left the model will expire.
49 class SGDatabaseReference : public osg::Observer {
50 public:
51   SGDatabaseReference(osg::Referenced* referenced) :
52     mReferenced(referenced)
53   { }
54   virtual void objectDeleted(void*)
55   {
56     mReferenced = 0;
57   }
58 private:
59   osg::ref_ptr<osg::Referenced> mReferenced;
60 };
61
62 // Visitor for 
63 class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
64 public:
65   SGTextureUpdateVisitor(const osgDB::FilePathList& pathList) :
66     mPathList(pathList)
67   { }
68   osg::Texture2D* textureReplace(int unit,
69                                  osg::StateSet::RefAttributePair& refAttr)
70   {
71     osg::Texture2D* texture;
72     texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
73     if (!texture)
74       return 0;
75     
76     osg::ref_ptr<osg::Image> image = texture->getImage(0);
77     if (!image)
78       return 0;
79
80     // The currently loaded file name
81     std::string fullFilePath = image->getFileName();
82     // The short name
83     std::string fileName = osgDB::getSimpleFileName(fullFilePath);
84     // The name that should be found with the current database path
85     std::string fullLiveryFile = osgDB::findFileInPath(fileName, mPathList);
86     // If they are identical then there is nothing to do
87     if (fullLiveryFile == fullFilePath)
88       return 0;
89
90     image = osgDB::readImageFile(fullLiveryFile);
91     if (!image)
92       return 0;
93
94     osg::CopyOp copyOp(osg::CopyOp::DEEP_COPY_ALL &
95                        ~osg::CopyOp::DEEP_COPY_IMAGES);
96     texture = static_cast<osg::Texture2D*>(copyOp(texture));
97     if (!texture)
98       return 0;
99     texture->setImage(image.get());
100     return texture;
101   }
102   virtual void apply(osg::StateSet* stateSet)
103   {
104     if (!stateSet)
105       return;
106
107     // get a copy that we can safely modify the statesets values.
108     osg::StateSet::TextureAttributeList attrList;
109     attrList = stateSet->getTextureAttributeList();
110     for (unsigned unit = 0; unit < attrList.size(); ++unit) {
111       osg::StateSet::AttributeList::iterator i;
112       i = attrList[unit].begin();
113       while (i != attrList[unit].end()) {
114         osg::Texture2D* texture = textureReplace(unit, i->second);
115         if (texture) {
116           stateSet->removeTextureAttribute(unit, i->second.first.get());
117           stateSet->setTextureAttribute(unit, texture, i->second.second);
118           stateSet->setTextureMode(unit, GL_TEXTURE_2D,
119                                    osg::StateAttribute::ON);
120         }
121         ++i;
122       }
123     }
124   }
125
126 private:
127   osgDB::FilePathList mPathList;
128 };
129
130 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
131 public:
132   virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
133   {
134     osg::Texture2D* texture;
135     texture = dynamic_cast<osg::Texture2D*>(refAttr.first.get());
136     if (!texture)
137       return;
138     
139     osg::Image* image = texture->getImage(0);
140     if (!image)
141       return;
142
143     int s = image->s();
144     int t = image->t();
145
146     if (s <= t && 32 <= s) {
147       SGSceneFeatures::instance()->setTextureCompression(texture);
148     } else if (t < s && 32 <= t) {
149       SGSceneFeatures::instance()->setTextureCompression(texture);
150     }
151   }
152 };
153
154 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
155 public:
156   virtual void apply(int, osg::StateSet::RefAttributePair& refAttr)
157   {
158     osg::Texture* texture;
159     texture = dynamic_cast<osg::Texture*>(refAttr.first.get());
160     if (!texture)
161       return;
162     
163     texture->setDataVariance(osg::Object::STATIC);
164   }
165 };
166
167 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
168 public:
169   virtual void apply(osg::StateSet::RefAttributePair& refAttr)
170   {
171     osg::Material* material;
172     material = dynamic_cast<osg::Material*>(refAttr.first.get());
173     if (!material)
174       return;
175     material->setColorMode(osg::Material::AMBIENT_AND_DIFFUSE);
176   }
177 };
178
179 class SGReadFileCallback :
180   public osgDB::Registry::ReadFileCallback {
181 public:
182   virtual osgDB::ReaderWriter::ReadResult
183   readImage(const std::string& fileName,
184             const osgDB::ReaderWriter::Options* opt)
185   {
186     std::string absFileName = osgDB::findDataFile(fileName);
187     if (!osgDB::fileExists(absFileName)) {
188       SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
189              << fileName << "\"");
190       return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
191     }
192
193     osgDB::Registry* registry = osgDB::Registry::instance();
194     osgDB::ReaderWriter::ReadResult res;
195     res = registry->readImageImplementation(absFileName, opt);
196     if (res.loadedFromCache())
197       SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
198              << res.getImage()->getFileName() << "\"");
199     else
200       SG_LOG(SG_IO, SG_INFO, "Reading image \""
201              << res.getImage()->getFileName() << "\"");
202
203     return res;
204   }
205
206   virtual osgDB::ReaderWriter::ReadResult
207   readNode(const std::string& fileName,
208            const osgDB::ReaderWriter::Options* opt)
209   {
210     osgDB::Registry* registry = osgDB::Registry::instance();
211     osgDB::ReaderWriter::ReadResult res;
212     osg::Node* cached = 0;
213     // The BTG loader automatically looks for ".btg.gz" if a file with
214     // the .btg extension doesn't exist. Also, we don't want to add
215     // nodes, run the optimizer, etc. on the btg model.So, let it do
216     // its thing.
217     if (osgDB::equalCaseInsensitive(osgDB::getFileExtension(fileName), "btg")) {
218       return registry->readNodeImplementation(fileName, opt);
219     }
220     std::string absFileName = osgDB::findDataFile(fileName);
221     if (!osgDB::fileExists(absFileName)) {
222       SG_LOG(SG_IO, SG_ALERT, "Cannot find model file \""
223              << fileName << "\"");
224       return osgDB::ReaderWriter::ReadResult::FILE_NOT_FOUND;
225     }
226     cached
227         = dynamic_cast<osg::Node*>(registry->getFromObjectCache(absFileName));
228     if (cached) {
229       SG_LOG(SG_IO, SG_INFO, "Got cached model \""
230              << absFileName << "\"");
231     } else {
232       SG_LOG(SG_IO, SG_INFO, "Reading model \""
233              << absFileName << "\"");
234       res = registry->readNodeImplementation(absFileName, opt);
235       if (!res.validNode())
236         return res;
237
238       bool needTristrip = true;
239       if (osgDB::getLowerCaseFileExtension(absFileName) == "ac") {
240         // we get optimal geometry from the loader.
241         needTristrip = false;
242         osg::Matrix m(1, 0, 0, 0,
243                       0, 0, 1, 0,
244                       0, -1, 0, 0,
245                       0, 0, 0, 1);
246         
247         osg::ref_ptr<osg::Group> root = new osg::Group;
248         osg::MatrixTransform* transform = new osg::MatrixTransform;
249         root->addChild(transform);
250         
251         transform->setDataVariance(osg::Object::STATIC);
252         transform->setMatrix(m);
253         transform->addChild(res.getNode());
254         
255         res = osgDB::ReaderWriter::ReadResult(0);
256         
257         osgUtil::Optimizer optimizer;
258         unsigned opts = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
259         optimizer.optimize(root.get(), opts);
260
261         // strip away unneeded groups
262         if (root->getNumChildren() == 1 && root->getName().empty()) {
263           res = osgDB::ReaderWriter::ReadResult(root->getChild(0));
264         } else
265           res = osgDB::ReaderWriter::ReadResult(root.get());
266         
267         // Ok, this step is questionable.
268         // It is there to have the same visual appearance of ac objects for the
269         // first cut. Osg's ac3d loader will correctly set materials from the
270         // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
271         // materials that in effect igored the ambient part specified in the
272         // file. We emulate that for the first cut here by changing all
273         // ac models here. But in the long term we should use the
274         // unchanged model and fix the input files instead ...
275         SGAcMaterialCrippleVisitor matCriple;
276         res.getNode()->accept(matCriple);
277       }
278       
279       osgUtil::Optimizer optimizer;
280       unsigned opts = 0;
281       // Don't use this one. It will break animation names ...
282       // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
283
284       // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
285       // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
286       // opts |= osgUtil::Optimizer::SHARE_DUPLICATE_STATE;
287       opts |= osgUtil::Optimizer::MERGE_GEOMETRY;
288       // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
289       // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
290       // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
291       opts |= osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS;
292       if (needTristrip)
293         opts |= osgUtil::Optimizer::TRISTRIP_GEOMETRY;
294       // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
295       // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
296       optimizer.optimize(res.getNode(), opts);
297
298       // Make sure the data variance of sharable objects is set to STATIC ...
299       SGTexDataVarianceVisitor dataVarianceVisitor;
300       res.getNode()->accept(dataVarianceVisitor);
301       // ... so that textures are now globally shared
302       registry->getSharedStateManager()->share(res.getNode());
303       
304       SGTexCompressionVisitor texComp;
305       res.getNode()->accept(texComp);
306       cached = res.getNode();
307       registry->addEntryToObjectCache(absFileName, cached);
308     }
309     // Add an extra reference to the model stored in the database.
310     // That it to avoid expiring the object from the cache even if it is still
311     // in use. Note that the object cache will think that a model is unused
312     // if the reference count is 1. If we clone all structural nodes here
313     // we need that extra reference to the original object
314     SGDatabaseReference* databaseReference;
315     databaseReference = new SGDatabaseReference(cached);
316     osg::CopyOp::CopyFlags flags = osg::CopyOp::DEEP_COPY_ALL;
317     flags &= ~osg::CopyOp::DEEP_COPY_TEXTURES;
318     flags &= ~osg::CopyOp::DEEP_COPY_IMAGES;
319     flags &= ~osg::CopyOp::DEEP_COPY_ARRAYS;
320     flags &= ~osg::CopyOp::DEEP_COPY_PRIMITIVES;
321     // This will safe display lists ...
322     flags &= ~osg::CopyOp::DEEP_COPY_DRAWABLES;
323     flags &= ~osg::CopyOp::DEEP_COPY_SHAPES;
324     res = osgDB::ReaderWriter::ReadResult(osg::CopyOp(flags)(cached));
325     res.getNode()->addObserver(databaseReference);
326
327     // Update liveries
328     SGTextureUpdateVisitor liveryUpdate(osgDB::getDataFilePathList());
329     res.getNode()->accept(liveryUpdate);
330
331     // Make sure the data variance of sharable objects is set to STATIC ...
332     SGTexDataVarianceVisitor dataVarianceVisitor;
333     res.getNode()->accept(dataVarianceVisitor);
334     // ... so that textures are now globally shared
335     registry->getOrCreateSharedStateManager()->share(res.getNode(), 0);
336
337     return res;
338   }
339 };
340
341 class SGReadCallbackInstaller {
342 public:
343   SGReadCallbackInstaller()
344   {
345     osg::Referenced::setThreadSafeReferenceCounting(true);
346
347     osgDB::Registry* registry = osgDB::Registry::instance();
348     osgDB::ReaderWriter::Options* options = new osgDB::ReaderWriter::Options;
349     // We manage node caching ourselves
350     int cacheOptions = osgDB::ReaderWriter::Options::CACHE_ALL
351       & ~osgDB::ReaderWriter::Options::CACHE_NODES;
352     options->
353       setObjectCacheHint((osgDB::ReaderWriter::Options::CacheHintOptions)cacheOptions);
354     registry->setOptions(options);
355     registry->getOrCreateSharedStateManager()->setShareMode(osgDB::SharedStateManager::SHARE_TEXTURES);
356     registry->setReadFileCallback(new SGReadFileCallback);
357   }
358 };
359
360 static SGReadCallbackInstaller readCallbackInstaller;
361
362 osg::Texture2D*
363 SGLoadTexture2D(const std::string& path, bool wrapu, bool wrapv, int)
364 {
365   osg::Image* image = osgDB::readImageFile(path);
366   osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
367   texture->setImage(image);
368   if (wrapu)
369     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
370   else
371     texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP);
372   if (wrapv)
373     texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
374   else
375     texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP);
376
377   if (image) {
378     int s = image->s();
379     int t = image->t();
380
381     if (s <= t && 32 <= s) {
382       SGSceneFeatures::instance()->setTextureCompression(texture.get());
383     } else if (t < s && 32 <= t) {
384       SGSceneFeatures::instance()->setTextureCompression(texture.get());
385     }
386   }
387
388   // Make sure the texture is shared if we already have the same texture
389   // somewhere ...
390   {
391     osg::ref_ptr<osg::Node> tmpNode = new osg::Node;
392     osg::StateSet* stateSet = tmpNode->getOrCreateStateSet();
393     stateSet->setTextureAttribute(0, texture.get());
394
395     // OSGFIXME: don't forget that mutex here
396     osgDB::Registry* registry = osgDB::Registry::instance();
397     registry->getOrCreateSharedStateManager()->share(tmpNode.get(), 0);
398
399     // should be the same, but be paranoid ...
400     stateSet = tmpNode->getStateSet();
401     osg::StateAttribute* stateAttr;
402     stateAttr = stateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE);
403     osg::Texture2D* texture2D = dynamic_cast<osg::Texture2D*>(stateAttr);
404     if (texture2D)
405       texture = texture2D;
406   }
407
408   return texture.release();
409 }
410
411 class SGSwitchUpdateCallback : public osg::NodeCallback {
412 public:
413   SGSwitchUpdateCallback(SGCondition* condition) :
414     mCondition(condition) {}
415   virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
416   { 
417     assert(dynamic_cast<osg::Switch*>(node));
418     osg::Switch* s = static_cast<osg::Switch*>(node);
419
420     if (mCondition && mCondition->test()) {
421       s->setAllChildrenOn();
422       // note, callback is responsible for scenegraph traversal so
423       // should always include call traverse(node,nv) to ensure 
424       // that the rest of cullbacks and the scene graph are traversed.
425       traverse(node, nv);
426     } else
427       s->setAllChildrenOff();
428   }
429
430 private:
431   SGSharedPtr<SGCondition> mCondition;
432 };
433
434 \f
435 ////////////////////////////////////////////////////////////////////////
436 // Global functions.
437 ////////////////////////////////////////////////////////////////////////
438
439 osg::Node *
440 sgLoad3DModel( const string &fg_root, const string &path,
441                SGPropertyNode *prop_root,
442                double sim_time_sec, osg::Node *(*load_panel)(SGPropertyNode *),
443                SGModelData *data,
444                const SGPath& externalTexturePath )
445 {
446   osg::ref_ptr<osg::Node> model;
447   SGPropertyNode props;
448
449   // Load the 3D aircraft object itself
450   SGPath modelpath = path, texturepath = path;
451   if ( !ulIsAbsolutePathName( path.c_str() ) ) {
452     SGPath tmp = fg_root;
453     tmp.append(modelpath.str());
454     modelpath = texturepath = tmp;
455   }
456
457   // Check for an XML wrapper
458   if (modelpath.str().substr(modelpath.str().size() - 4, 4) == ".xml") {
459     readProperties(modelpath.str(), &props);
460     if (props.hasValue("/path")) {
461       modelpath = modelpath.dir();
462       modelpath.append(props.getStringValue("/path"));
463       if (props.hasValue("/texture-path")) {
464         texturepath = texturepath.dir();
465         texturepath.append(props.getStringValue("/texture-path"));
466       }
467     } else {
468       if (!model)
469         model = new osg::Switch;
470     }
471   }
472
473   osgDB::FilePathList pathList = osgDB::getDataFilePathList();
474   osgDB::Registry::instance()->initFilePathLists();
475
476   // Assume that textures are in
477   // the same location as the XML file.
478   if (!model) {
479     if (texturepath.extension() != "")
480           texturepath = texturepath.dir();
481
482     osgDB::Registry::instance()->getDataFilePathList().push_front(texturepath.str());
483
484     model = osgDB::readNodeFile(modelpath.str());
485     if (model == 0)
486       throw sg_io_exception("Failed to load 3D model", 
487                             sg_location(modelpath.str()));
488   }
489
490   osgDB::Registry::instance()->getDataFilePathList().push_front(externalTexturePath.str());
491
492   // Set up the alignment node
493   osg::ref_ptr<osg::MatrixTransform> alignmainmodel = new osg::MatrixTransform;
494   alignmainmodel->addChild(model.get());
495   osg::Matrix res_matrix;
496   res_matrix.makeRotate(
497     props.getFloatValue("/offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
498     osg::Vec3(0, 1, 0),
499     props.getFloatValue("/offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
500     osg::Vec3(1, 0, 0),
501     props.getFloatValue("/offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
502     osg::Vec3(0, 0, 1));
503
504   osg::Matrix tmat;
505   tmat.makeTranslate(props.getFloatValue("/offsets/x-m", 0.0),
506                      props.getFloatValue("/offsets/y-m", 0.0),
507                      props.getFloatValue("/offsets/z-m", 0.0));
508   alignmainmodel->setMatrix(res_matrix*tmat);
509
510   // Load sub-models
511   vector<SGPropertyNode_ptr> model_nodes = props.getChildren("model");
512   for (unsigned i = 0; i < model_nodes.size(); i++) {
513     SGPropertyNode_ptr node = model_nodes[i];
514     osg::ref_ptr<osg::MatrixTransform> align = new osg::MatrixTransform;
515     res_matrix.makeIdentity();
516     res_matrix.makeRotate(
517       node->getDoubleValue("offsets/pitch-deg", 0.0)*SG_DEGREES_TO_RADIANS,
518       osg::Vec3(0, 1, 0),
519       node->getDoubleValue("offsets/roll-deg", 0.0)*SG_DEGREES_TO_RADIANS,
520       osg::Vec3(1, 0, 0),
521       node->getDoubleValue("offsets/heading-deg", 0.0)*SG_DEGREES_TO_RADIANS,
522       osg::Vec3(0, 0, 1));
523     
524     tmat.makeIdentity();
525     tmat.makeTranslate(node->getDoubleValue("offsets/x-m", 0),
526                        node->getDoubleValue("offsets/y-m", 0),
527                        node->getDoubleValue("offsets/z-m", 0));
528     align->setMatrix(res_matrix*tmat);
529
530     osg::ref_ptr<osg::Node> kid;
531     const char* submodel = node->getStringValue("path");
532     try {
533       kid = sgLoad3DModel( fg_root, submodel, prop_root, sim_time_sec, load_panel );
534
535     } catch (const sg_throwable &t) {
536       SG_LOG(SG_INPUT, SG_ALERT, "Failed to load submodel: " << t.getFormattedMessage());
537       throw;
538     }
539     align->addChild(kid.get());
540
541     align->setName(node->getStringValue("name", ""));
542
543     SGPropertyNode *cond = node->getNode("condition", false);
544     if (cond) {
545       osg::ref_ptr<osg::Switch> sw = new osg::Switch;
546       sw->setUpdateCallback(new SGSwitchUpdateCallback(sgReadCondition(prop_root, cond)));
547       alignmainmodel->addChild(sw.get());
548       sw->addChild(align.get());
549       sw->setName("submodel condition switch");
550     } else {
551       alignmainmodel->addChild(align.get());
552     }
553   }
554
555   if ( load_panel ) {
556     // Load panels
557     vector<SGPropertyNode_ptr> panel_nodes = props.getChildren("panel");
558     for (unsigned i = 0; i < panel_nodes.size(); i++) {
559         SG_LOG(SG_INPUT, SG_DEBUG, "Loading a panel");
560         osg::ref_ptr<osg::Node> panel = load_panel(panel_nodes[i]);
561         if (panel_nodes[i]->hasValue("name"))
562             panel->setName((char *)panel_nodes[i]->getStringValue("name"));
563         alignmainmodel->addChild(panel.get());
564     }
565   }
566
567   if (data) {
568     alignmainmodel->setUserData(data);
569     data->modelLoaded(path, &props, alignmainmodel.get());
570   }
571
572   std::vector<SGPropertyNode_ptr> animation_nodes;
573   animation_nodes = props.getChildren("animation");
574   for (unsigned i = 0; i < animation_nodes.size(); ++i)
575     /// OSGFIXME: duh, why not only model?????
576     SGAnimation::animate(alignmainmodel.get(), animation_nodes[i], prop_root);
577
578   // restore old path list
579   osgDB::setDataFilePathList(pathList);
580
581   if (props.hasChild("debug-outfile")) {
582     std::string outputfile = props.getStringValue("debug-outfile",
583                                                   "debug-model.osg");
584     osgDB::writeNodeFile(*alignmainmodel, outputfile);
585   }
586
587   return alignmainmodel.release();
588 }
589
590 // end of model.cxx