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/ref_ptr>
34 #include <osg/NodeCallback>
36 #include <osg/Material>
37 #include <osg/MatrixTransform>
38 #include <osgDB/Archive>
39 #include <osgDB/FileNameUtils>
40 #include <osgDB/FileUtils>
41 #include <osgDB/ReadFile>
42 #include <osgDB/WriteFile>
43 #include <osgDB/Registry>
44 #include <osgDB/SharedStateManager>
45 #include <osgUtil/Optimizer>
47 #include <simgear/scene/util/SGSceneFeatures.hxx>
48 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
49 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
50 #include <simgear/scene/util/NodeAndDrawableVisitor.hxx>
52 #include <simgear/structure/exception.hxx>
53 #include <simgear/props/props.hxx>
54 #include <simgear/props/props_io.hxx>
55 #include <simgear/props/condition.hxx>
57 #include "BoundingVolumeBuildVisitor.hxx"
61 using namespace osgUtil;
62 using namespace osgDB;
63 using namespace simgear;
65 using OpenThreads::ReentrantMutex;
66 using OpenThreads::ScopedLock;
68 // Little helper class that holds an extra reference to a
70 // Since we clone all structural nodes from our 3d models,
71 // the database pager will only see one single reference to
72 // top node of the model and expire it relatively fast.
73 // We attach that extra reference to every model cloned from
74 // a base model in the pager. When that cloned model is deleted
75 // this extra reference is deleted too. So if there are no
76 // cloned models left the model will expire.
78 class SGDatabaseReference : public Observer {
80 SGDatabaseReference(Referenced* referenced) :
81 mReferenced(referenced)
83 virtual void objectDeleted(void*)
88 ref_ptr<Referenced> mReferenced;
91 // Set the name of a Texture to the simple name of its image
92 // file. This can be used to do livery substitution after the image
93 // has been deallocated.
94 class TextureNameVisitor : public NodeAndDrawableVisitor {
96 TextureNameVisitor(NodeVisitor::TraversalMode tm = NodeVisitor::TRAVERSE_ALL_CHILDREN) :
97 NodeAndDrawableVisitor(tm)
101 virtual void apply(Node& node)
103 nameTextures(node.getStateSet());
107 virtual void apply(Drawable& drawable)
109 nameTextures(drawable.getStateSet());
112 void nameTextures(StateSet* stateSet)
116 int numUnits = stateSet->getTextureAttributeList().size();
117 for (int i = 0; i < numUnits; ++i) {
119 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
120 Texture2D* texture = dynamic_cast<Texture2D*>(attr);
121 if (!texture || !texture->getName().empty())
123 const Image *image = texture->getImage();
126 texture->setName(image->getFileName());
131 // Change the StateSets of a model to hold different textures based on
134 class TextureUpdateVisitor : public NodeAndDrawableVisitor {
136 TextureUpdateVisitor(const FilePathList& pathList) :
137 NodeAndDrawableVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN),
142 virtual void apply(Node& node)
144 StateSet* stateSet = cloneStateSet(node.getStateSet());
146 node.setStateSet(stateSet);
150 virtual void apply(Drawable& drawable)
152 StateSet* stateSet = cloneStateSet(drawable.getStateSet());
154 drawable.setStateSet(stateSet);
156 // Copied from Mathias' earlier SGTextureUpdateVisitor
158 Texture2D* textureReplace(int unit, const StateAttribute* attr)
160 const Texture2D* texture = dynamic_cast<const Texture2D*>(attr);
165 const Image* image = texture->getImage();
166 const string* fullFilePath = 0;
168 // The currently loaded file name
169 fullFilePath = &image->getFileName();
172 fullFilePath = &texture->getName();
175 string fileName = getSimpleFileName(*fullFilePath);
176 if (fileName.empty())
178 // The name that should be found with the current database path
179 string fullLiveryFile = findFileInPath(fileName, _pathList);
180 // If it is empty or they are identical then there is nothing to do
181 if (fullLiveryFile.empty() || fullLiveryFile == *fullFilePath)
183 Image* newImage = readImageFile(fullLiveryFile);
186 CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
187 Texture2D* newTexture = static_cast<Texture2D*>(copyOp(texture));
191 newTexture->setImage(newImage);
196 StateSet* cloneStateSet(const StateSet* stateSet)
198 typedef pair<int, Texture2D*> Tex2D;
199 vector<Tex2D> newTextures;
200 StateSet* result = 0;
204 int numUnits = stateSet->getTextureAttributeList().size();
206 for (int i = 0; i < numUnits; ++i) {
207 const StateAttribute* attr
208 = stateSet->getTextureAttribute(i, StateAttribute::TEXTURE);
209 Texture2D* newTexture = textureReplace(i, attr);
211 newTextures.push_back(Tex2D(i, newTexture));
213 if (!newTextures.empty()) {
214 result = static_cast<StateSet*>(stateSet->clone(CopyOp()));
215 for (vector<Tex2D>::iterator i = newTextures.begin();
216 i != newTextures.end();
218 result->setTextureAttribute(i->first, i->second);
225 FilePathList _pathList;
228 // Create new userdata structs in a copied model.
229 // The BVH trees are shared with the original model, but the velocity fields
230 // should usually be distinct fields for distinct models.
231 class UserDataCopyVisitor : public osg::NodeVisitor {
233 UserDataCopyVisitor() :
234 osg::NodeVisitor(osg::NodeVisitor::NODE_VISITOR,
235 osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
238 virtual void apply(osg::Node& node)
240 osg::ref_ptr<SGSceneUserData> userData;
241 userData = SGSceneUserData::getSceneUserData(&node);
242 if (userData.valid()) {
243 SGSceneUserData* newUserData = new SGSceneUserData(*userData);
244 newUserData->setVelocity(0);
245 node.setUserData(newUserData);
247 node.traverse(*this);
251 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
253 virtual void apply(int, StateSet::RefAttributePair& refAttr)
256 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
260 // Do not touch dynamically generated textures.
261 if (texture->getReadPBuffer())
263 if (texture->getDataVariance() == osg::Object::DYNAMIC)
266 // If no image attached, we assume this one is dynamically generated
267 Image* image = texture->getImage(0);
274 if (s <= t && 32 <= s) {
275 SGSceneFeatures::instance()->setTextureCompression(texture);
276 } else if (t < s && 32 <= t) {
277 SGSceneFeatures::instance()->setTextureCompression(texture);
282 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
284 virtual void apply(int, StateSet::RefAttributePair& refAttr)
287 texture = dynamic_cast<Texture*>(refAttr.first.get());
291 // Cannot be static if this is a render to texture thing
292 if (texture->getReadPBuffer())
294 if (texture->getDataVariance() == osg::Object::DYNAMIC)
296 // If no image attached, we assume this one is dynamically generated
297 Image* image = texture->getImage(0);
301 texture->setDataVariance(Object::STATIC);
304 virtual void apply(StateSet* stateSet)
308 stateSet->setDataVariance(Object::STATIC);
309 SGTextureStateAttributeVisitor::apply(stateSet);
315 Node* DefaultProcessPolicy::process(Node* node, const string& filename,
316 const ReaderWriter::Options* opt)
318 TextureNameVisitor nameVisitor;
319 node->accept(nameVisitor);
323 ReaderWriter::ReadResult
324 ModelRegistry::readImage(const string& fileName,
325 const ReaderWriter::Options* opt)
327 ScopedLock<ReentrantMutex> lock(readerMutex);
328 CallbackMap::iterator iter
329 = imageCallbackMap.find(getFileExtension(fileName));
330 // XXX Workaround for OSG plugin bug
332 if (iter != imageCallbackMap.end() && iter->second.valid())
333 return iter->second->readImage(fileName, opt);
334 string absFileName = findDataFile(fileName, opt);
335 if (!fileExists(absFileName)) {
336 SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
337 << fileName << "\"");
338 return ReaderWriter::ReadResult::FILE_NOT_FOUND;
341 Registry* registry = Registry::instance();
342 ReaderWriter::ReadResult res;
343 res = registry->readImageImplementation(absFileName, opt);
344 if (!res.success()) {
345 SG_LOG(SG_IO, SG_WARN, "Image loading failed:" << res.message());
349 if (res.loadedFromCache())
350 SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
351 << res.getImage()->getFileName() << "\"");
353 SG_LOG(SG_IO, SG_INFO, "Reading image \""
354 << res.getImage()->getFileName() << "\"");
361 osg::Node* DefaultCachePolicy::find(const string& fileName,
362 const ReaderWriter::Options* opt)
364 Registry* registry = Registry::instance();
366 = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
368 SG_LOG(SG_IO, SG_INFO, "Got cached model \""
369 << fileName << "\"");
371 SG_LOG(SG_IO, SG_INFO, "Reading model \""
372 << fileName << "\"");
376 void DefaultCachePolicy::addToCache(const string& fileName,
379 Registry::instance()->addEntryToObjectCache(fileName, node);
382 // Optimizations we don't use:
383 // Don't use this one. It will break animation names ...
384 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
386 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
387 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
388 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
389 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
390 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
391 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
392 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
394 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
395 _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
396 | Optimizer::MERGE_GEOMETRY
397 | Optimizer::FLATTEN_STATIC_TRANSFORMS
398 | Optimizer::TRISTRIP_GEOMETRY)
402 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
403 const string& fileName,
404 const osgDB::ReaderWriter::Options* opt)
406 osgUtil::Optimizer optimizer;
407 optimizer.optimize(node, _osgOptions);
409 // Make sure the data variance of sharable objects is set to
410 // STATIC so that textures will be globally shared.
411 SGTexDataVarianceVisitor dataVarianceVisitor;
412 node->accept(dataVarianceVisitor);
414 SGTexCompressionVisitor texComp;
415 node->accept(texComp);
419 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
420 const osgDB::ReaderWriter::Options* opt)
422 // Add an extra reference to the model stored in the database.
423 // That is to avoid expiring the object from the cache even if it is still
424 // in use. Note that the object cache will think that a model is unused
425 // if the reference count is 1. If we clone all structural nodes here
426 // we need that extra reference to the original object
427 SGDatabaseReference* databaseReference;
428 databaseReference = new SGDatabaseReference(model);
429 CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
430 flags &= ~CopyOp::DEEP_COPY_TEXTURES;
431 flags &= ~CopyOp::DEEP_COPY_IMAGES;
432 flags &= ~CopyOp::DEEP_COPY_STATESETS;
433 flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
434 flags &= ~CopyOp::DEEP_COPY_ARRAYS;
435 flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
436 // This will safe display lists ...
437 flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
438 flags &= ~CopyOp::DEEP_COPY_SHAPES;
439 osg::Node* res = CopyOp(flags)(model);
440 res->addObserver(databaseReference);
443 TextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
444 res->accept(liveryUpdate);
446 // Copy the userdata fields, still sharing the boundingvolumes,
447 // but introducing new data for velocities.
448 UserDataCopyVisitor userDataCopyVisitor;
449 res->accept(userDataCopyVisitor);
454 string OSGSubstitutePolicy::substitute(const string& name,
455 const ReaderWriter::Options* opt)
457 string fileSansExtension = getNameLessExtension(name);
458 string osgFileName = fileSansExtension + ".osg";
459 string absFileName = findDataFile(osgFileName, opt);
465 BuildLeafBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
467 SG_LOG(SG_IO, SG_INFO, "Building leaf attached boundingvolume tree for \""
468 << fileName << "\".");
469 BoundingVolumeBuildVisitor bvBuilder(true);
470 node->accept(bvBuilder);
474 BuildGroupBVHPolicy::buildBVH(const std::string& fileName, osg::Node* node)
476 SG_LOG(SG_IO, SG_INFO, "Building group attached boundingvolume tree for \""
477 << fileName << "\".");
478 BoundingVolumeBuildVisitor bvBuilder(false);
479 node->accept(bvBuilder);
483 NoBuildBVHPolicy::buildBVH(const std::string& fileName, osg::Node*)
485 SG_LOG(SG_IO, SG_INFO, "Omitting boundingvolume tree for \""
486 << fileName << "\".");
489 ModelRegistry::ModelRegistry() :
490 _defaultCallback(new DefaultCallback(""))
495 ModelRegistry::addImageCallbackForExtension(const string& extension,
496 Registry::ReadFileCallback* callback)
498 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
502 ModelRegistry::addNodeCallbackForExtension(const string& extension,
503 Registry::ReadFileCallback* callback)
505 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
508 ReaderWriter::ReadResult
509 ModelRegistry::readNode(const string& fileName,
510 const ReaderWriter::Options* opt)
512 ScopedLock<ReentrantMutex> lock(readerMutex);
514 // XXX Workaround for OSG plugin bug.
515 Registry* registry = Registry::instance();
516 ReaderWriter::ReadResult res;
517 CallbackMap::iterator iter
518 = nodeCallbackMap.find(getFileExtension(fileName));
519 ReaderWriter::ReadResult result;
520 if (iter != nodeCallbackMap.end() && iter->second.valid())
521 result = iter->second->readNode(fileName, opt);
523 result = _defaultCallback->readNode(fileName, opt);
528 class SGReadCallbackInstaller {
530 SGReadCallbackInstaller()
532 // XXX I understand why we want this, but this seems like a weird
533 // place to set this option.
534 Referenced::setThreadSafeReferenceCounting(true);
536 Registry* registry = Registry::instance();
537 ReaderWriter::Options* options = new ReaderWriter::Options;
538 int cacheOptions = ReaderWriter::Options::CACHE_ALL;
540 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
541 registry->setOptions(options);
542 registry->getOrCreateSharedStateManager()->
543 setShareMode(SharedStateManager::SHARE_STATESETS);
544 registry->setReadFileCallback(ModelRegistry::instance());
548 static SGReadCallbackInstaller readCallbackInstaller;
550 // we get optimal geometry from the loader.
551 struct ACOptimizePolicy : public OptimizeModelPolicy {
552 ACOptimizePolicy(const string& extension) :
553 OptimizeModelPolicy(extension)
555 _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
557 Node* optimize(Node* node, const string& fileName,
558 const ReaderWriter::Options* opt)
560 ref_ptr<Node> optimized
561 = OptimizeModelPolicy::optimize(node, fileName, opt);
562 Group* group = dynamic_cast<Group*>(optimized.get());
563 MatrixTransform* transform
564 = dynamic_cast<MatrixTransform*>(optimized.get());
565 if (((transform && transform->getMatrix().isIdentity()) || group)
566 && group->getName().empty()
567 && group->getNumChildren() == 1) {
568 optimized = static_cast<Node*>(group->getChild(0));
569 group = dynamic_cast<Group*>(optimized.get());
570 if (group && group->getName().empty()
571 && group->getNumChildren() == 1)
572 optimized = static_cast<Node*>(group->getChild(0));
574 return optimized.release();
578 struct ACProcessPolicy {
579 ACProcessPolicy(const string& extension) {}
580 Node* process(Node* node, const string& filename,
581 const ReaderWriter::Options* opt)
587 // XXX Does there need to be a Group node here to trick the
588 // optimizer into optimizing the static transform?
589 osg::Group* root = new Group;
590 MatrixTransform* transform = new MatrixTransform;
591 root->addChild(transform);
593 transform->setDataVariance(Object::STATIC);
594 transform->setMatrix(m);
595 transform->addChild(node);
601 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
602 ACOptimizePolicy, DefaultCopyPolicy,
603 OSGSubstitutePolicy, BuildLeafBVHPolicy>
608 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");