1 // ModelRegistry.hxx -- interface to the OSG model registry
3 // Copyright (C) 2005-2007 Mathias Froehlich
4 // Copyright (C) 2007 Tim Moore <timoore@redhat.com>
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.
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.
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.
21 # include <simgear_config.h>
24 #include "ModelRegistry.hxx"
30 #include <OpenThreads/ScopedLock>
32 #include <osg/observer_ptr>
33 #include <osg/ref_ptr>
35 #include <osg/NodeCallback>
37 #include <osg/Material>
38 #include <osg/MatrixTransform>
39 #include <osgDB/Archive>
40 #include <osgDB/FileNameUtils>
41 #include <osgDB/FileUtils>
42 #include <osgDB/ReadFile>
43 #include <osgDB/WriteFile>
44 #include <osgDB/Registry>
45 #include <osgDB/SharedStateManager>
46 #include <osgUtil/Optimizer>
48 #include <simgear/scene/util/SGSceneFeatures.hxx>
49 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
50 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
51 #include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
53 #include <simgear/structure/exception.hxx>
54 #include <simgear/props/props.hxx>
55 #include <simgear/props/props_io.hxx>
56 #include <simgear/props/condition.hxx>
58 #include "BoundingVolumeBuildVisitor.hxx"
62 using namespace osgUtil;
63 using namespace osgDB;
64 using namespace simgear;
66 using OpenThreads::ReentrantMutex;
67 using OpenThreads::ScopedLock;
69 // Little helper class that holds an extra reference to a
71 // Since we clone all structural nodes from our 3d models,
72 // the database pager will only see one single reference to
73 // top node of the model and expire it relatively fast.
74 // We attach that extra reference to every model cloned from
75 // a base model in the pager. When that cloned model is deleted
76 // this extra reference is deleted too. So if there are no
77 // cloned models left the model will expire.
79 class SGDatabaseReference : public Observer {
81 SGDatabaseReference(Referenced* referenced) :
82 mReferenced(referenced)
84 virtual void objectDeleted(void*)
89 ref_ptr<Referenced> mReferenced;
92 // Set the name of a Texture to the simple name of its image
93 // file. This can be used to do livery substitution after the image
94 // has been deallocated.
95 class TextureNameVisitor : public NodeAndDrawableVisitor {
97 TextureNameVisitor(NodeVisitor::TraversalMode tm = NodeVisitor::TRAVERSE_ALL_CHILDREN) :
98 NodeAndDrawableVisitor(tm)
102 virtual void apply(Node& node)
104 nameTextures(node.getStateSet());
108 virtual void apply(Drawable& drawable)
110 nameTextures(drawable.getStateSet());
113 void nameTextures(StateSet* stateSet)
117 int numUnits = stateSet->getTextureAttributeList().size();
118 for (int i = 0; i < numUnits; ++i) {
120 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
121 Texture2D* texture = dynamic_cast<Texture2D*>(attr);
122 if (!texture || !texture->getName().empty())
124 const Image *image = texture->getImage();
127 texture->setName(image->getFileName());
132 // Change the StateSets of a model to hold different textures based on
135 class TextureUpdateVisitor : public NodeAndDrawableVisitor {
137 TextureUpdateVisitor(const FilePathList& pathList) :
138 NodeAndDrawableVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
143 virtual void apply(Node& node)
145 StateSet* stateSet = cloneStateSet(node.getStateSet());
147 node.setStateSet(stateSet);
151 virtual void apply(Drawable& drawable)
153 StateSet* stateSet = cloneStateSet(drawable.getStateSet());
155 drawable.setStateSet(stateSet);
157 // Copied from Mathias' earlier SGTextureUpdateVisitor
159 Texture2D* textureReplace(int unit, const StateAttribute* attr)
161 const Texture2D* texture = dynamic_cast<const Texture2D*>(attr);
166 const Image* image = texture->getImage();
167 const string* fullFilePath = 0;
169 // The currently loaded file name
170 fullFilePath = &image->getFileName();
173 fullFilePath = &texture->getName();
176 string fileName = getSimpleFileName(*fullFilePath);
177 if (fileName.empty())
179 // The name that should be found with the current database path
180 string fullLiveryFile = findFileInPath(fileName, _pathList);
181 // If it is empty or they are identical then there is nothing to do
182 if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath)
184 Image* newImage = readImageFile(fullLiveryFile);
187 CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
188 Texture2D* newTexture = static_cast<Texture2D*>(copyOp(texture));
192 newTexture->setImage(newImage);
197 StateSet* cloneStateSet(const StateSet* stateSet)
199 typedef pair<int, Texture2D*> Tex2D;
200 vector<Tex2D> newTextures;
201 StateSet* result = 0;
205 int numUnits = stateSet->getTextureAttributeList().size();
207 for (int i = 0; i < numUnits; ++i) {
208 const StateAttribute* attr
209 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
210 Texture2D* newTexture = textureReplace(i, attr);
212 newTextures.push_back(Tex2D(i, newTexture));
214 if (!newTextures.empty()) {
215 result = static_cast<StateSet*>(stateSet->clone(CopyOp()));
216 for (vector<Tex2D>::iterator i = newTextures.begin();
217 i != newTextures.end();
219 result->setTextureAttribute(i->first, i->second);
226 FilePathList _pathList;
229 // Create new userdata structs in a copied model.
230 // The BVH trees are shared with the original model, but the velocity fields
231 // should usually be distinct fields for distinct models.
232 class UserDataCopyVisitor : public osg::NodeVisitor {
234 UserDataCopyVisitor() :
235 osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
236 osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
239 virtual void apply(osg::Node& node)
241 osg::ref_ptr<SGSceneUserData> userData;
242 userData = SGSceneUserData::getSceneUserData(&node);
243 if (userData.valid()) {
244 SGSceneUserData* newUserData = new SGSceneUserData(*userData);
245 newUserData->setVelocity(0);
246 node.setUserData(newUserData);
248 node.traverse(*this);
252 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
254 virtual void apply(int, StateSet::RefAttributePair& refAttr)
257 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
262 texture->setDataVariance(osg::Object::STATIC);
264 Image* image = texture->getImage(0);
271 if (s <= t && 32 <= s) {
272 SGSceneFeatures::instance()->setTextureCompression(texture);
273 } else if (t < s && 32 <= t) {
274 SGSceneFeatures::instance()->setTextureCompression(texture);
279 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
281 virtual void apply(int, StateSet::RefAttributePair& refAttr)
284 texture = dynamic_cast<Texture*>(refAttr.first.get());
288 texture->setDataVariance(Object::STATIC);
291 virtual void apply(StateSet* stateSet)
295 SGTextureStateAttributeVisitor::apply(stateSet);
296 stateSet->setDataVariance(Object::STATIC);
302 Node* DefaultProcessPolicy::process(Node* node, const string& filename,
303 const ReaderWriter::Options* opt)
305 TextureNameVisitor nameVisitor;
306 node->accept(nameVisitor);
310 ReaderWriter::ReadResult
311 ModelRegistry::readImage(const string& fileName,
312 const ReaderWriter::Options* opt)
314 ScopedLock<ReentrantMutex> lock(readerMutex);
315 CallbackMap::iterator iter
316 = imageCallbackMap.find(getFileExtension(fileName));
317 // XXX Workaround for OSG plugin bug
319 if (iter != imageCallbackMap.end() && iter->second.valid())
320 return iter->second->readImage(fileName, opt);
321 string absFileName = findDataFile(fileName, opt);
322 if (!fileExists(absFileName)) {
323 SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
324 << fileName << "\"");
325 return ReaderWriter::ReadResult::FILE_NOT_FOUND;
328 Registry* registry = Registry::instance();
329 ReaderWriter::ReadResult res;
330 res = registry->readImageImplementation(absFileName, opt);
331 if (!res.success()) {
332 SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
336 if (res.loadedFromCache())
337 SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
338 << res.getImage()->getFileName() << "\"");
340 SG_LOG(SG_IO, SG_INFO, "Reading image \""
341 << res.getImage()->getFileName() << "\"");
348 osg::Node* DefaultCachePolicy::find(const string& fileName,
349 const ReaderWriter::Options* opt)
351 Registry* registry = Registry::instance();
353 = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
355 SG_LOG(SG_IO, SG_INFO, "Got cached model \""
356 << fileName << "\"");
358 SG_LOG(SG_IO, SG_INFO, "Reading model \""
359 << fileName << "\"");
363 void DefaultCachePolicy::addToCache(const string& fileName,
366 Registry::instance()->addEntryToObjectCache(fileName, node);
369 // Optimizations we don't use:
370 // Don't use this one. It will break animation names ...
371 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
373 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
374 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
375 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
376 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
377 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
378 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
379 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
381 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
382 _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
383 | Optimizer::MERGE_GEOMETRY
384 | Optimizer::FLATTEN_STATIC_TRANSFORMS
385 | Optimizer::TRISTRIP_GEOMETRY)
389 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
390 const string& fileName,
391 const osgDB::ReaderWriter::Options* opt)
393 osgUtil::Optimizer optimizer;
394 optimizer.optimize(node, _osgOptions);
396 // Make sure the data variance of sharable objects is set to
397 // STATIC so that textures will be globally shared.
398 SGTexDataVarianceVisitor dataVarianceVisitor;
399 node->accept(dataVarianceVisitor);
401 SGTexCompressionVisitor texComp;
402 node->accept(texComp);
406 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
407 const osgDB::ReaderWriter::Options* opt)
409 // Add an extra reference to the model stored in the database.
410 // That is to avoid expiring the object from the cache even if it is still
411 // in use. Note that the object cache will think that a model is unused
412 // if the reference count is 1. If we clone all structural nodes here
413 // we need that extra reference to the original object
414 SGDatabaseReference* databaseReference;
415 databaseReference = new SGDatabaseReference(model);
416 CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
417 flags &= ~CopyOp::DEEP_COPY_TEXTURES;
418 flags &= ~CopyOp::DEEP_COPY_IMAGES;
419 flags &= ~CopyOp::DEEP_COPY_STATESETS;
420 flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
421 flags &= ~CopyOp::DEEP_COPY_ARRAYS;
422 flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
423 // This will safe display lists ...
424 flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
425 flags &= ~CopyOp::DEEP_COPY_SHAPES;
426 osg::Node* res = CopyOp(flags)(model);
427 res->addObserver(databaseReference);
430 TextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
431 res->accept(liveryUpdate);
433 // Copy the userdata fields, still sharing the boundingvolumes,
434 // but introducing new data for velocities.
435 UserDataCopyVisitor userDataCopyVisitor;
436 res->accept(userDataCopyVisitor);
441 string OSGSubstitutePolicy::substitute(const string& name,
442 const ReaderWriter::Options* opt)
444 string fileSansExtension = getNameLessExtension(name);
445 string osgFileName = fileSansExtension + ".osg";
446 string absFileName = findDataFile(osgFileName, opt);
452 BuildLeafBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
454 SG_LOG(SG_IO, SG_INFO, "Building leaf attached boundingvolume tree for \""
455 << fileName << "\".");
456 BoundingVolumeBuildVisitor bvBuilder(true);
457 node->accept(bvBuilder);
461 BuildGroupBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
463 SG_LOG(SG_IO, SG_INFO, "Building group attached boundingvolume tree for \""
464 << fileName << "\".");
465 BoundingVolumeBuildVisitor bvBuilder(false);
466 node->accept(bvBuilder);
470 NoBuildBVHPolicy::buildBVH(const std::string& fileName, osg::Node*)
472 SG_LOG(SG_IO, SG_INFO, "Omitting boundingvolume tree for \""
473 << fileName << "\".");
476 ModelRegistry::ModelRegistry() :
477 _defaultCallback(new DefaultCallback(""))
482 ModelRegistry::addImageCallbackForExtension(const string& extension,
483 Registry::ReadFileCallback* callback)
485 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
489 ModelRegistry::addNodeCallbackForExtension(const string& extension,
490 Registry::ReadFileCallback* callback)
492 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
495 ReaderWriter::ReadResult
496 ModelRegistry::readNode(const string& fileName,
497 const ReaderWriter::Options* opt)
499 ScopedLock<ReentrantMutex> lock(readerMutex);
501 // XXX Workaround for OSG plugin bug.
502 Registry* registry = Registry::instance();
503 ReaderWriter::ReadResult res;
504 CallbackMap::iterator iter
505 = nodeCallbackMap.find(getFileExtension(fileName));
506 ReaderWriter::ReadResult result;
507 if (iter != nodeCallbackMap.end() && iter->second.valid())
508 result = iter->second->readNode(fileName, opt);
510 result = _defaultCallback->readNode(fileName, opt);
515 class SGReadCallbackInstaller {
517 SGReadCallbackInstaller()
519 // XXX I understand why we want this, but this seems like a weird
520 // place to set this option.
521 Referenced::setThreadSafeReferenceCounting(true);
523 Registry* registry = Registry::instance();
524 ReaderWriter::Options* options = new ReaderWriter::Options;
525 int cacheOptions = ReaderWriter::Options::CACHE_ALL;
527 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
528 registry->setOptions(options);
529 registry->getOrCreateSharedStateManager()->
530 setShareMode(SharedStateManager::SHARE_STATESETS);
531 registry->setReadFileCallback(ModelRegistry::instance());
535 static SGReadCallbackInstaller readCallbackInstaller;
537 // we get optimal geometry from the loader.
538 struct ACOptimizePolicy : public OptimizeModelPolicy {
539 ACOptimizePolicy(const string& extension) :
540 OptimizeModelPolicy(extension)
542 _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
544 Node* optimize(Node* node, const string& fileName,
545 const ReaderWriter::Options* opt)
547 ref_ptr<Node> optimized
548 = OptimizeModelPolicy::optimize(node, fileName, opt);
549 Group* group = dynamic_cast<Group*>(optimized.get());
550 MatrixTransform* transform
551 = dynamic_cast<MatrixTransform*>(optimized.get());
552 if (((transform && transform->getMatrix().isIdentity()) || group)
553 && group->getName().empty()
554 && group->getNumChildren() == 1) {
555 optimized = static_cast<Node*>(group->getChild(0));
556 group = dynamic_cast<Group*>(optimized.get());
557 if (group && group->getName().empty()
558 && group->getNumChildren() == 1)
559 optimized = static_cast<Node*>(group->getChild(0));
561 return optimized.release();
565 struct ACProcessPolicy {
566 ACProcessPolicy(const string& extension) {}
567 Node* process(Node* node, const string& filename,
568 const ReaderWriter::Options* opt)
574 // XXX Does there need to be a Group node here to trick the
575 // optimizer into optimizing the static transform?
576 osg::Group* root = new Group;
577 MatrixTransform* transform = new MatrixTransform;
578 root->addChild(transform);
580 transform->setDataVariance(Object::STATIC);
581 transform->setMatrix(m);
582 transform->addChild(node);
588 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
589 ACOptimizePolicy, DefaultCopyPolicy,
590 OSGSubstitutePolicy, BuildLeafBVHPolicy>
595 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");