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.
19 #include "ModelRegistry.hxx"
23 #include <OpenThreads/ScopedLock>
25 #include <osg/observer_ptr>
26 #include <osg/ref_ptr>
28 #include <osg/NodeCallback>
30 #include <osg/Material>
31 #include <osg/MatrixTransform>
32 #include <osgDB/Archive>
33 #include <osgDB/FileNameUtils>
34 #include <osgDB/FileUtils>
35 #include <osgDB/ReadFile>
36 #include <osgDB/WriteFile>
37 #include <osgDB/Registry>
38 #include <osgDB/SharedStateManager>
39 #include <osgUtil/Optimizer>
41 #include <simgear/scene/util/SGSceneFeatures.hxx>
42 #include <simgear/scene/util/SGStateAttributeVisitor.hxx>
43 #include <simgear/scene/util/SGTextureStateAttributeVisitor.hxx>
45 #include <simgear/structure/exception.hxx>
46 #include <simgear/props/props.hxx>
47 #include <simgear/props/props_io.hxx>
48 #include <simgear/props/condition.hxx>
52 using namespace osgUtil;
53 using namespace osgDB;
54 using namespace simgear;
56 // Little helper class that holds an extra reference to a
58 // Since we clone all structural nodes from our 3d models,
59 // the database pager will only see one single reference to
60 // top node of the model and expire it relatively fast.
61 // We attach that extra reference to every model cloned from
62 // a base model in the pager. When that cloned model is deleted
63 // this extra reference is deleted too. So if there are no
64 // cloned models left the model will expire.
66 class SGDatabaseReference : public Observer {
68 SGDatabaseReference(Referenced* referenced) :
69 mReferenced(referenced)
71 virtual void objectDeleted(void*)
76 ref_ptr<Referenced> mReferenced;
80 class SGTextureUpdateVisitor : public SGTextureStateAttributeVisitor {
82 SGTextureUpdateVisitor(const FilePathList& pathList) :
85 Texture2D* textureReplace(int unit,
86 StateSet::RefAttributePair& refAttr)
89 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
93 ref_ptr<Image> image = texture->getImage(0);
97 // The currently loaded file name
98 string fullFilePath = image->getFileName();
100 string fileName = getSimpleFileName(fullFilePath);
101 // The name that should be found with the current database path
102 string fullLiveryFile = findFileInPath(fileName, mPathList);
103 // If they are identical then there is nothing to do
104 if (fullLiveryFile == fullFilePath)
107 image = readImageFile(fullLiveryFile);
111 CopyOp copyOp(CopyOp::DEEP_COPY_ALL & ~CopyOp::DEEP_COPY_IMAGES);
112 texture = static_cast<Texture2D*>(copyOp(texture));
115 texture->setImage(image.get());
118 virtual void apply(StateSet* stateSet)
123 // get a copy that we can safely modify the statesets values.
124 StateSet::TextureAttributeList attrList;
125 attrList = stateSet->getTextureAttributeList();
126 for (unsigned unit = 0; unit < attrList.size(); ++unit) {
127 StateSet::AttributeList::iterator i = attrList[unit].begin();
128 while (i != attrList[unit].end()) {
129 Texture2D* texture = textureReplace(unit, i->second);
131 stateSet->removeTextureAttribute(unit, i->second.first.get());
132 stateSet->setTextureAttribute(unit, texture, i->second.second);
133 stateSet->setTextureMode(unit, GL_TEXTURE_2D, StateAttribute::ON);
141 FilePathList mPathList;
144 class SGTexCompressionVisitor : public SGTextureStateAttributeVisitor {
146 virtual void apply(int, StateSet::RefAttributePair& refAttr)
149 texture = dynamic_cast<Texture2D*>(refAttr.first.get());
154 texture->setDataVariance(osg::Object::STATIC);
156 Image* image = texture->getImage(0);
163 if (s <= t && 32 <= s) {
164 SGSceneFeatures::instance()->setTextureCompression(texture);
165 } else if (t < s && 32 <= t) {
166 SGSceneFeatures::instance()->setTextureCompression(texture);
171 class SGTexDataVarianceVisitor : public SGTextureStateAttributeVisitor {
173 virtual void apply(int, StateSet::RefAttributePair& refAttr)
176 texture = dynamic_cast<Texture*>(refAttr.first.get());
180 texture->setDataVariance(Object::STATIC);
183 virtual void apply(StateSet* stateSet)
187 SGTextureStateAttributeVisitor::apply(stateSet);
188 stateSet->setDataVariance(Object::STATIC);
192 class SGAcMaterialCrippleVisitor : public SGStateAttributeVisitor {
194 virtual void apply(StateSet::RefAttributePair& refAttr)
197 material = dynamic_cast<Material*>(refAttr.first.get());
200 material->setColorMode(Material::AMBIENT_AND_DIFFUSE);
204 // Work around an OSG bug - the file loaders don't use the file path
205 // in options while the file is being loaded.
207 struct OptionsPusher {
208 FilePathList localPathList;
210 OptionsPusher(const ReaderWriter::Options* options):
215 Registry* registry = Registry::instance();
216 localPathList = registry->getDataFilePathList();
217 const FilePathList& regPathList = registry->getDataFilePathList();
218 const FilePathList& optionsPathList = options->getDatabasePathList();
219 for (FilePathList::const_iterator iter = optionsPathList.begin();
220 iter != optionsPathList.end();
222 if (find(regPathList.begin(), regPathList.end(), *iter)
223 == regPathList.end())
224 localPathList.push_back(*iter);
226 // Save the current Registry path list and install the augmented one.
227 localPathList.swap(registry->getDataFilePathList());
232 // Restore the old path list
234 localPathList.swap(Registry::instance()->getDataFilePathList());
239 ReaderWriter::ReadResult
240 ModelRegistry::readImage(const string& fileName,
241 const ReaderWriter::Options* opt)
243 OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
244 CallbackMap::iterator iter
245 = imageCallbackMap.find(getFileExtension(fileName));
246 // XXX Workaround for OSG plugin bug
248 OptionsPusher pusher(opt);
249 if (iter != imageCallbackMap.end() && iter->second.valid())
250 return iter->second->readImage(fileName, opt);
251 string absFileName = findDataFile(fileName);
252 if (!fileExists(absFileName)) {
253 SG_LOG(SG_IO, SG_ALERT, "Cannot find image file \""
254 << fileName << "\"");
255 return ReaderWriter::ReadResult::FILE_NOT_FOUND;
258 Registry* registry = Registry::instance();
259 ReaderWriter::ReadResult res;
260 res = registry->readImageImplementation(absFileName, opt);
261 if (res.loadedFromCache())
262 SG_LOG(SG_IO, SG_INFO, "Returning cached image \""
263 << res.getImage()->getFileName() << "\"");
265 SG_LOG(SG_IO, SG_INFO, "Reading image \""
266 << res.getImage()->getFileName() << "\"");
273 osg::Node* DefaultCachePolicy::find(const string& fileName,
274 const ReaderWriter::Options* opt)
276 Registry* registry = Registry::instance();
278 = dynamic_cast<Node*>(registry->getFromObjectCache(fileName));
280 SG_LOG(SG_IO, SG_INFO, "Got cached model \""
281 << fileName << "\"");
283 SG_LOG(SG_IO, SG_INFO, "Reading model \""
284 << fileName << "\"");
288 void DefaultCachePolicy::addToCache(const string& fileName,
291 Registry::instance()->addEntryToObjectCache(fileName, node);
294 // Optimizations we don't use:
295 // Don't use this one. It will break animation names ...
296 // opts |= osgUtil::Optimizer::REMOVE_REDUNDANT_NODES;
298 // opts |= osgUtil::Optimizer::REMOVE_LOADED_PROXY_NODES;
299 // opts |= osgUtil::Optimizer::COMBINE_ADJACENT_LODS;
300 // opts |= osgUtil::Optimizer::CHECK_GEOMETRY;
301 // opts |= osgUtil::Optimizer::SPATIALIZE_GROUPS;
302 // opts |= osgUtil::Optimizer::COPY_SHARED_NODES;
303 // opts |= osgUtil::Optimizer::TESSELATE_GEOMETRY;
304 // opts |= osgUtil::Optimizer::OPTIMIZE_TEXTURE_SETTINGS;
306 OptimizeModelPolicy::OptimizeModelPolicy(const string& extension) :
307 _osgOptions(Optimizer::SHARE_DUPLICATE_STATE
308 | Optimizer::MERGE_GEOMETRY
309 | Optimizer::FLATTEN_STATIC_TRANSFORMS
310 | Optimizer::TRISTRIP_GEOMETRY)
314 osg::Node* OptimizeModelPolicy::optimize(osg::Node* node,
315 const string& fileName,
316 const osgDB::ReaderWriter::Options* opt)
318 osgUtil::Optimizer optimizer;
319 optimizer.optimize(node, _osgOptions);
321 // Make sure the data variance of sharable objects is set to
322 // STATIC so that textures will be globally shared.
323 SGTexDataVarianceVisitor dataVarianceVisitor;
324 node->accept(dataVarianceVisitor);
326 SGTexCompressionVisitor texComp;
327 node->accept(texComp);
331 osg::Node* DefaultCopyPolicy::copy(osg::Node* model, const string& fileName,
332 const osgDB::ReaderWriter::Options* opt)
334 // Add an extra reference to the model stored in the database.
335 // That it to avoid expiring the object from the cache even if it is still
336 // in use. Note that the object cache will think that a model is unused
337 // if the reference count is 1. If we clone all structural nodes here
338 // we need that extra reference to the original object
339 SGDatabaseReference* databaseReference;
340 databaseReference = new SGDatabaseReference(model);
341 CopyOp::CopyFlags flags = CopyOp::DEEP_COPY_ALL;
342 flags &= ~CopyOp::DEEP_COPY_TEXTURES;
343 flags &= ~CopyOp::DEEP_COPY_IMAGES;
344 flags &= ~CopyOp::DEEP_COPY_STATESETS;
345 flags &= ~CopyOp::DEEP_COPY_STATEATTRIBUTES;
346 flags &= ~CopyOp::DEEP_COPY_ARRAYS;
347 flags &= ~CopyOp::DEEP_COPY_PRIMITIVES;
348 // This will safe display lists ...
349 flags &= ~CopyOp::DEEP_COPY_DRAWABLES;
350 flags &= ~CopyOp::DEEP_COPY_SHAPES;
351 osg::Node* res = CopyOp(flags)(model);
352 res->addObserver(databaseReference);
355 SGTextureUpdateVisitor liveryUpdate(opt->getDatabasePathList());
356 res->accept(liveryUpdate);
360 string OSGSubstitutePolicy::substitute(const string& name,
361 const ReaderWriter::Options* opt)
363 string fileSansExtension = getNameLessExtension(name);
364 string osgFileName = fileSansExtension + ".osg";
365 string absFileName = findDataFile(osgFileName, opt);
369 ModelRegistry::ModelRegistry() :
370 _defaultCallback(new DefaultCallback(""))
375 ModelRegistry::addImageCallbackForExtension(const string& extension,
376 Registry::ReadFileCallback* callback)
378 imageCallbackMap.insert(CallbackMap::value_type(extension, callback));
382 ModelRegistry::addNodeCallbackForExtension(const string& extension,
383 Registry::ReadFileCallback* callback)
385 nodeCallbackMap.insert(CallbackMap::value_type(extension, callback));
388 ref_ptr<ModelRegistry> ModelRegistry::instance;
390 ModelRegistry* ModelRegistry::getInstance()
393 if (!instance.valid())
394 instance = new ModelRegistry;
395 return instance.get();
398 ReaderWriter::ReadResult
399 ModelRegistry::readNode(const string& fileName,
400 const ReaderWriter::Options* opt)
402 OpenThreads::ScopedLock<OpenThreads::ReentrantMutex> lock(readerMutex);
403 // XXX Workaround for OSG plugin bug.
404 OptionsPusher pusher(opt);
405 Registry* registry = Registry::instance();
406 ReaderWriter::ReadResult res;
408 CallbackMap::iterator iter
409 = nodeCallbackMap.find(getFileExtension(fileName));
410 if (iter != nodeCallbackMap.end() && iter->second.valid())
411 return iter->second->readNode(fileName, opt);
412 return _defaultCallback->readNode(fileName, opt);
415 class SGReadCallbackInstaller {
417 SGReadCallbackInstaller()
419 // XXX I understand why we want this, but this seems like a weird
420 // place to set this option.
421 Referenced::setThreadSafeReferenceCounting(true);
423 Registry* registry = Registry::instance();
424 ReaderWriter::Options* options = new ReaderWriter::Options;
425 int cacheOptions = ReaderWriter::Options::CACHE_ALL;
427 setObjectCacheHint((ReaderWriter::Options::CacheHintOptions)cacheOptions);
428 registry->setOptions(options);
429 registry->getOrCreateSharedStateManager()->
430 setShareMode(SharedStateManager::SHARE_STATESETS);
431 registry->setReadFileCallback(ModelRegistry::getInstance());
435 static SGReadCallbackInstaller readCallbackInstaller;
437 // we get optimal geometry from the loader.
438 struct ACOptimizePolicy : public OptimizeModelPolicy {
439 ACOptimizePolicy(const string& extension) :
440 OptimizeModelPolicy(extension)
442 _osgOptions &= ~Optimizer::TRISTRIP_GEOMETRY;
446 struct ACProcessPolicy {
447 ACProcessPolicy(const string& extension) {}
448 Node* process(Node* node, const string& filename,
449 const ReaderWriter::Options* opt)
455 // XXX Does there need to be a Group node here to trick the
456 // optimizer into optimizing the static transform?
457 osg::Group* root = new Group;
458 MatrixTransform* transform = new MatrixTransform;
459 root->addChild(transform);
461 transform->setDataVariance(Object::STATIC);
462 transform->setMatrix(m);
463 transform->addChild(node);
464 // Ok, this step is questionable.
465 // It is there to have the same visual appearance of ac objects for the
466 // first cut. Osg's ac3d loader will correctly set materials from the
467 // ac file. But the old plib loader used GL_AMBIENT_AND_DIFFUSE for the
468 // materials that in effect igored the ambient part specified in the
469 // file. We emulate that for the first cut here by changing all
470 // ac models here. But in the long term we should use the
471 // unchanged model and fix the input files instead ...
472 SGAcMaterialCrippleVisitor matCriple;
473 root->accept(matCriple);
478 typedef ModelRegistryCallback<ACProcessPolicy, DefaultCachePolicy,
479 ACOptimizePolicy, DefaultCopyPolicy,
480 OSGSubstitutePolicy> ACCallback;
484 ModelRegistryCallbackProxy<ACCallback> g_acRegister("ac");