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